diff --git a/package-lock.json b/package-lock.json index c8d9d7c..c64e325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", "copy-webpack-plugin": "^11.0.0", + "cross-env": "^7.0.3", "css-loader": "^6.8.1", "dotenv-webpack": "^8.0.1", "eslint": "^8.47.0", @@ -3260,6 +3261,24 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -13443,6 +13462,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", diff --git a/package.json b/package.json index 2c34e7c..db56efe 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "scripts": { "prepare": "husky install", - "dev": "NODE_ENV=development webpack serve --config src/webpack.config.ts", + "dev": "cross-env NODE_ENV=development webpack serve --config src/webpack.config.ts", "build": "webpack --config src/webpack.config.ts", "lint:eslint": "eslint src --ext .ts,.tsx", "lint:prettier": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,scss}\"", @@ -38,6 +38,7 @@ "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", "copy-webpack-plugin": "^11.0.0", + "cross-env": "^7.0.3", "css-loader": "^6.8.1", "dotenv-webpack": "^8.0.1", "eslint": "^8.47.0", diff --git a/src/pages/overlay/components/overlay/Overlay.tsx b/src/pages/overlay/components/overlay/Overlay.tsx index c99724a..151325f 100644 --- a/src/pages/overlay/components/overlay/Overlay.tsx +++ b/src/pages/overlay/components/overlay/Overlay.tsx @@ -17,6 +17,7 @@ import { type AmbassadorKey, } from "../../../../utils/ambassadors"; import { classes } from "../../../../utils/classes"; +import { visibleUnderCursor } from "../../../../utils/dom"; import useChatCommand from "../../../../hooks/useChatCommand"; @@ -137,24 +138,9 @@ export default function Overlay() { // Handle body clicks, dismissing the overlay if the user clicks outside of it const bodyClick = useCallback((e: MouseEvent) => { - // Get all the elements under the mouse - const elements = document.elementsFromPoint(e.clientX, e.clientY); - - // For each element, if it has a background then we want to ignore the click - // If we reach the body, then break out of the loop and close the panels - for (const element of elements) { - if (element === document.body) break; - - const style = getComputedStyle(element); - if ( - style.backgroundImage !== "none" || - style.backgroundColor !== "rgba(0, 0, 0, 0)" - ) { - return; - } + if (!visibleUnderCursor(e)) { + setVisibleOption(undefined); } - - setVisibleOption(undefined); }, []); // If the user clicks anywhere in the body, except the overlay itself, close the panels @@ -164,6 +150,21 @@ export default function Overlay() { return () => document.body.removeEventListener("click", bodyClick, true); }, [bodyClick]); + // Handle body double clicks, ignoring them inside of overlay elements + const bodyDblClick = useCallback((e: MouseEvent) => { + if (visibleUnderCursor(e)) { + e.stopPropagation(); + } + }, []); + + // If the user double clicks anywhere in the overlay itself, stop propagating the event + // This stops double clicks from toggling fullscreen video which is the default behavior + useEffect(() => { + document.body.addEventListener("dblclick", bodyDblClick, true); + return () => + document.body.removeEventListener("dblclick", bodyDblClick, true); + }, [bodyDblClick]); + // Generate the context for the overlay options const context = useMemo( () => ({ diff --git a/src/utils/dom.ts b/src/utils/dom.ts new file mode 100644 index 0000000..b5d0941 --- /dev/null +++ b/src/utils/dom.ts @@ -0,0 +1,25 @@ +/** + * Finds overlay element that is visible under the cursor + * + * @param {MouseEvent} e Mouse event containing cursor position + * @returns {Element|null} Element if one is visible under the cursor, null otherwise + */ +export function visibleUnderCursor(e: MouseEvent): Element | null { + // Get all the elements under the mouse + const elements = document.elementsFromPoint(e.clientX, e.clientY); + + // For each element, if it has a background then assume it is part of the overlay and return it + for (const element of elements) { + if (element === document.body) break; + + const style = getComputedStyle(element); + if ( + style.backgroundImage !== "none" || + style.backgroundColor !== "rgba(0, 0, 0, 0)" + ) { + return element; + } + } + + return null; +}