import { useEffect, useRef } from 'react'; const FOCUSABLE = [ 'a[href]', 'button:not([disabled])', 'input:not([disabled]):not([type="hidden"])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])', '[contenteditable="true"]', ].join(','); export default function useFocusTrap({ active, containerRef, initialFocusRef, restoreFocus = true }) { const previousActiveElement = useRef(null); useEffect(() => { if (!active) return undefined; previousActiveElement.current = document.activeElement; const container = containerRef.current; if (!container) return undefined; const focusTarget = initialFocusRef?.current ?? container.querySelector(FOCUSABLE) ?? container; if (focusTarget && typeof focusTarget.focus === 'function') { if (focusTarget === container && !container.hasAttribute('tabindex')) { container.setAttribute('tabindex', '-1'); } focusTarget.focus({ preventScroll: true }); } const handleKeyDown = (e) => { if (e.key !== 'Tab') return; const focusable = Array.from(container.querySelectorAll(FOCUSABLE)); if (focusable.length === 0) { e.preventDefault(); return; } const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); if ( restoreFocus && previousActiveElement.current && typeof previousActiveElement.current.focus === 'function' ) { previousActiveElement.current.focus({ preventScroll: true }); } }; }, [active, containerRef, initialFocusRef, restoreFocus]); }