Trap keyboard focus in popovers
This commit is contained in:
1 parent
2432113c7f
commit
411fb03c22
6 files changed
+61
-5
No files matched your search
@@ -0,0 +1,51 @@
|
||||
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 });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user