import { useEffect, useRef } from 'react'; const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; function getFocusableElements(container) { return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter( (el) => !el.disabled && el.getAttribute('aria-hidden') !== 'true' ); } /** * Trap focus inside a modal while `active` and restore focus on close. * Returns a ref to attach to the dialog panel (not the backdrop). */ export function useFocusTrap(active) { const containerRef = useRef(null); const previouslyFocusedRef = useRef(null); useEffect(() => { if (!active) return; previouslyFocusedRef.current = document.activeElement; const container = containerRef.current; if (!container) return; const focusFirst = () => { const nodes = getFocusableElements(container); nodes[0]?.focus(); }; focusFirst(); const handleKeyDown = (event) => { if (event.key !== 'Tab') return; const nodes = getFocusableElements(container); if (nodes.length === 0) return; const first = nodes[0]; const last = nodes[nodes.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); const previous = previouslyFocusedRef.current; if (previous && typeof previous.focus === 'function') { previous.focus(); } }; }, [active]); return containerRef; }