Files
KVideo/lib/accessibility/focus-trap.ts
T

70 lines
1.9 KiB
TypeScript

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