const focusableSelector = [ 'a[href]', 'button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])' ].join(', '); function focusableElements(container: HTMLElement): HTMLElement[] { return [...container.querySelectorAll(focusableSelector)].filter( (element) => element.getClientRects().length > 0 ); } export function focusTrap(container: HTMLElement) { const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null; (focusableElements(container)[0] ?? container).focus({ preventScroll: true }); function handleKeydown(event: KeyboardEvent) { if (event.key !== 'Tab') return; const elements = focusableElements(container); const first = elements[0]; const last = elements.at(-1); const active = document.activeElement; if (!first || !last) { event.preventDefault(); container.focus({ preventScroll: true }); } else if (!container.contains(active)) { event.preventDefault(); first.focus(); } else if (event.shiftKey && active === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && active === last) { event.preventDefault(); first.focus(); } } document.addEventListener('keydown', handleKeydown); return { destroy() { document.removeEventListener('keydown', handleKeydown); if (previouslyFocused?.isConnected) previouslyFocused.focus({ preventScroll: true }); } }; }