Skip to content
File

Blob: src/client/hooks/use-focus-trap.ts

typescript40 lines
1import { type RefObject, useEffect } from "react";
2 
3const FOCUSABLE = [
4 "a[href]",
5 "button:not(:disabled)",
6 "input:not(:disabled)",
7 "select:not(:disabled)",
8 "textarea:not(:disabled)",
9 '[tabindex]:not([tabindex="-1"])',
10].join(", ");
11 
12export function useFocusTrap(containerRef: RefObject<HTMLElement | null>): void {
13 useEffect(() => {
14 const el = containerRef.current;
15 if (!el) return;
16 
17 const focusable = () => Array.from(el.querySelectorAll<HTMLElement>(FOCUSABLE));
18 focusable()[0]?.focus();
19 
20 const onKeyDown = (e: KeyboardEvent) => {
21 if (e.key !== "Tab") return;
22 const items = focusable();
23 if (items.length === 0) return;
24 const first = items[0];
25 const last = items[items.length - 1];
26 
27 if (e.shiftKey && document.activeElement === first) {
28 e.preventDefault();
29 last.focus();
30 } else if (!e.shiftKey && document.activeElement === last) {
31 e.preventDefault();
32 first.focus();
33 }
34 };
35 
36 el.addEventListener("keydown", onKeyDown);
37 return () => el.removeEventListener("keydown", onKeyDown);
38 }, [containerRef]);
39}