/** * Focus Trap Utility * Traps keyboard focus within a specified container for accessibility */ const FOCUSABLE_ELEMENTS = [ 'a[href]', 'button:not([disabled])', 'textarea:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', '[tabindex]:not([tabindex="-1"])', ].join(','); export function getFocusableElements(container: HTMLElement): HTMLElement[] { return Array.from(container.querySelectorAll(FOCUSABLE_ELEMENTS)).filter( (el) => !el.hasAttribute('disabled') && el.offsetParent !== null ); } export function trapFocus(container: HTMLElement): () => void { const focusableElements = getFocusableElements(container); if (focusableElements.length === 0) return () => {}; const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; // Store the element that had focus before the trap const previouslyFocusedElement = document.activeElement as HTMLElement; // Focus the first element firstElement.focus(); const handleKeyDown = (e: KeyboardEvent) => { if (e.key !== 'Tab') return; if (e.shiftKey) { // Shift + Tab if (document.activeElement === firstElement) { e.preventDefault(); lastElement.focus(); } } else { // Tab if (document.activeElement === lastElement) { e.preventDefault(); firstElement.focus(); } } }; container.addEventListener('keydown', handleKeyDown); // Return cleanup function return () => { container.removeEventListener('keydown', handleKeyDown); // Restore focus to the previously focused element if (previouslyFocusedElement && typeof previouslyFocusedElement.focus === 'function') { previouslyFocusedElement.focus(); } }; } export function restoreFocus(element: HTMLElement | null) { if (element && typeof element.focus === 'function') { element.focus(); } }