import { useEffect, type RefObject } from 'react'; const FOCUSABLE = 'input:not([disabled]), button:not([disabled]), textarea:not([disabled]), select:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'; export function useFocusTrap( ref: RefObject, active: boolean, ): void { useEffect(() => { if (!active || !ref.current) return; const root = ref.current; const previouslyFocused = document.activeElement as HTMLElement | null; const focusables = () => Array.from(root.querySelectorAll(FOCUSABLE)).filter( (el) => !el.hasAttribute('aria-hidden') && el.offsetParent !== null, ); const autoFocus = root.querySelector('[data-autofocus]') ?? focusables()[0]; autoFocus?.focus(); const handler = (e: KeyboardEvent) => { if (e.key !== 'Tab') return; const items = focusables(); if (items.length === 0) { e.preventDefault(); return; } const first = items[0]; const last = items[items.length - 1]; const current = document.activeElement as HTMLElement | null; if (e.shiftKey && current === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && current === last) { e.preventDefault(); first.focus(); } }; root.addEventListener('keydown', handler); return () => { root.removeEventListener('keydown', handler); previouslyFocused?.focus?.(); }; }, [ref, active]); }