32 lines
1.1 KiB
TypeScript
32 lines
1.1 KiB
TypeScript
import { useEffect } from "react";
|
|
|
|
const FOCUSABLE_SELECTOR =
|
|
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
|
|
export function useFocusTrap(ref: React.RefObject<HTMLElement | null>, isOpen: boolean) {
|
|
useEffect(() => {
|
|
if (!isOpen || !ref.current) return;
|
|
const el = ref.current;
|
|
|
|
const focusable = Array.from(el.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
const first = focusable[0];
|
|
const last = focusable[focusable.length - 1];
|
|
|
|
// Focus first element when modal opens
|
|
first?.focus();
|
|
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key !== "Tab") return;
|
|
if (focusable.length === 0) { e.preventDefault(); return; }
|
|
if (e.shiftKey) {
|
|
if (document.activeElement === first) { e.preventDefault(); last?.focus(); }
|
|
} else {
|
|
if (document.activeElement === last) { e.preventDefault(); first?.focus(); }
|
|
}
|
|
}
|
|
|
|
el.addEventListener("keydown", handleKeyDown);
|
|
return () => el.removeEventListener("keydown", handleKeyDown);
|
|
}, [isOpen, ref]);
|
|
}
|