profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
useModalDialog.ts 1,715 bytes
1 import { useEffect, useRef } from "react";
2
3 const FOCUSABLE = "button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), a[href], [tabindex]:not([tabindex='-1'])";
4
5 export function useModalDialog(onClose: () => void) {
6 const dialogRef = useRef<HTMLElement>(null);
7 const onCloseRef = useRef(onClose);
8
9 useEffect(() => {
10 onCloseRef.current = onClose;
11 }, [onClose]);
12
13 useEffect(() => {
14 const previousOverflow = document.body.style.overflow;
15 const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
16 document.body.style.overflow = "hidden";
17 dialogRef.current?.querySelector<HTMLElement>("[data-initial-focus], " + FOCUSABLE)?.focus();
18
19 function handleKeyDown(event: KeyboardEvent) {
20 if (event.key === "Escape") {
21 event.preventDefault();
22 onCloseRef.current();
23 return;
24 }
25 if (event.key !== "Tab" || !dialogRef.current) return;
26 const focusable = Array.from(dialogRef.current.querySelectorAll<HTMLElement>(FOCUSABLE));
27 if (!focusable.length) return;
28 const first = focusable[0];
29 const last = focusable[focusable.length - 1];
30 if (event.shiftKey && document.activeElement === first) {
31 event.preventDefault();
32 last.focus();
33 } else if (!event.shiftKey && document.activeElement === last) {
34 event.preventDefault();
35 first.focus();
36 }
37 }
38
39 document.addEventListener("keydown", handleKeyDown);
40 return () => {
41 document.removeEventListener("keydown", handleKeyDown);
42 document.body.style.overflow = previousOverflow;
43 previousFocus?.focus();
44 };
45 }, []);
46
47 return dialogRef;
48 }
49