Files
website/src/lib/ui/focusTrap.ts
T

52 lines
1.5 KiB
TypeScript

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<HTMLElement>(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 });
}
};
}