diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-03-27 00:55:15 +0100 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-03-29 15:56:48 +0200 |
| commit | 4d127a038bf768eded68aae3cfa5c1b12241bdac (patch) | |
| tree | 16bbb801caf6770546cff4fdb37b6408ec9ead98 /apps/web/src/hooks/use-focus-lock.ts | |
| parent | 82817cb50cb0487350573d6129130aec705f924b (diff) | |
| download | opencut-4d127a038bf768eded68aae3cfa5c1b12241bdac.tar.gz | |
chore: normalize line endings
Diffstat (limited to 'apps/web/src/hooks/use-focus-lock.ts')
| -rw-r--r-- | apps/web/src/hooks/use-focus-lock.ts | 158 |
1 files changed, 79 insertions, 79 deletions
diff --git a/apps/web/src/hooks/use-focus-lock.ts b/apps/web/src/hooks/use-focus-lock.ts index 80baebdb..236d2623 100644 --- a/apps/web/src/hooks/use-focus-lock.ts +++ b/apps/web/src/hooks/use-focus-lock.ts @@ -1,79 +1,79 @@ -import { useEffect, useRef } from "react"; - -type FocusLockCursor = "text" | "default" | "pointer" | "crosshair"; - -const DATA_ATTR = "data-focus-locked"; - -function buildFocusLockCSS({ - cursor, - allowSelector, -}: { - cursor: FocusLockCursor; - allowSelector?: string; -}) { - const rules = [ - `*, *::before, *::after { pointer-events: none !important; cursor: ${cursor} !important; }`, - `[${DATA_ATTR}], [${DATA_ATTR}] * { pointer-events: auto !important; cursor: auto !important; }`, - ]; - - if (allowSelector) { - rules.push( - `${allowSelector} { pointer-events: auto !important; cursor: auto !important; }`, - ); - } - - return rules.join("\n"); -} - -export function useFocusLock<T extends HTMLElement = HTMLElement>({ - isActive, - onDismiss, - cursor = "default", - allowSelector, -}: { - isActive: boolean; - onDismiss: () => void; - cursor?: FocusLockCursor; - allowSelector?: string; -}) { - const containerRef = useRef<T>(null); - const onDismissRef = useRef(onDismiss); - onDismissRef.current = onDismiss; - - useEffect(() => { - if (!isActive) return; - const container = containerRef.current; - if (!container) return; - - container.setAttribute(DATA_ATTR, ""); - - const focusLockStyle = document.createElement("style"); - focusLockStyle.textContent = buildFocusLockCSS({ cursor, allowSelector }); - document.head.appendChild(focusLockStyle); - - const handleOutsidePointerDown = (event: PointerEvent) => { - if (event.button !== 0) return; - if (container.contains(event.target as Node)) return; - - const target = event.target as Element | null; - const isAllowedTarget = allowSelector && target?.closest(allowSelector); - if (isAllowedTarget) return; - - onDismissRef.current(); - }; - - document.addEventListener("pointerdown", handleOutsidePointerDown, true); - - return () => { - document.removeEventListener( - "pointerdown", - handleOutsidePointerDown, - true, - ); - container.removeAttribute(DATA_ATTR); - focusLockStyle.remove(); - }; - }, [isActive, cursor, allowSelector]); - - return { containerRef }; -} +import { useEffect, useRef } from "react";
+
+type FocusLockCursor = "text" | "default" | "pointer" | "crosshair";
+
+const DATA_ATTR = "data-focus-locked";
+
+function buildFocusLockCSS({
+ cursor,
+ allowSelector,
+}: {
+ cursor: FocusLockCursor;
+ allowSelector?: string;
+}) {
+ const rules = [
+ `*, *::before, *::after { pointer-events: none !important; cursor: ${cursor} !important; }`,
+ `[${DATA_ATTR}], [${DATA_ATTR}] * { pointer-events: auto !important; cursor: auto !important; }`,
+ ];
+
+ if (allowSelector) {
+ rules.push(
+ `${allowSelector} { pointer-events: auto !important; cursor: auto !important; }`,
+ );
+ }
+
+ return rules.join("\n");
+}
+
+export function useFocusLock<T extends HTMLElement = HTMLElement>({
+ isActive,
+ onDismiss,
+ cursor = "default",
+ allowSelector,
+}: {
+ isActive: boolean;
+ onDismiss: () => void;
+ cursor?: FocusLockCursor;
+ allowSelector?: string;
+}) {
+ const containerRef = useRef<T>(null);
+ const onDismissRef = useRef(onDismiss);
+ onDismissRef.current = onDismiss;
+
+ useEffect(() => {
+ if (!isActive) return;
+ const container = containerRef.current;
+ if (!container) return;
+
+ container.setAttribute(DATA_ATTR, "");
+
+ const focusLockStyle = document.createElement("style");
+ focusLockStyle.textContent = buildFocusLockCSS({ cursor, allowSelector });
+ document.head.appendChild(focusLockStyle);
+
+ const handleOutsidePointerDown = (event: PointerEvent) => {
+ if (event.button !== 0) return;
+ if (container.contains(event.target as Node)) return;
+
+ const target = event.target as Element | null;
+ const isAllowedTarget = allowSelector && target?.closest(allowSelector);
+ if (isAllowedTarget) return;
+
+ onDismissRef.current();
+ };
+
+ document.addEventListener("pointerdown", handleOutsidePointerDown, true);
+
+ return () => {
+ document.removeEventListener(
+ "pointerdown",
+ handleOutsidePointerDown,
+ true,
+ );
+ container.removeAttribute(DATA_ATTR);
+ focusLockStyle.remove();
+ };
+ }, [isActive, cursor, allowSelector]);
+
+ return { containerRef };
+}
|
