84 lines
2.4 KiB
JavaScript
84 lines
2.4 KiB
JavaScript
|
|
export default class FocusManager {
|
||
|
|
static trapFocus(element, logger = null) {
|
||
|
|
const focusableSelectors = [
|
||
|
|
'button',
|
||
|
|
'[href]',
|
||
|
|
'input',
|
||
|
|
'select',
|
||
|
|
'textarea',
|
||
|
|
'[tabindex]:not([tabindex="-1"])'
|
||
|
|
].join(', ');
|
||
|
|
|
||
|
|
const focusableElements = element.querySelectorAll(focusableSelectors);
|
||
|
|
|
||
|
|
if (focusableElements.length === 0) {
|
||
|
|
logger?.warn('No focusable elements found for trap');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const firstElement = focusableElements[0];
|
||
|
|
const lastElement = focusableElements[focusableElements.length - 1];
|
||
|
|
|
||
|
|
const handleKeydown = (e) => {
|
||
|
|
if (e.key !== 'Tab') return;
|
||
|
|
|
||
|
|
if (e.shiftKey) {
|
||
|
|
if (document.activeElement === firstElement) {
|
||
|
|
lastElement.focus();
|
||
|
|
e.preventDefault();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (document.activeElement === lastElement) {
|
||
|
|
firstElement.focus();
|
||
|
|
e.preventDefault();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
element.addEventListener('keydown', handleKeydown);
|
||
|
|
|
||
|
|
return () => element.removeEventListener('keydown', handleKeydown);
|
||
|
|
}
|
||
|
|
|
||
|
|
static moveFocusToElement(element, options = {}) {
|
||
|
|
const { smooth = true, center = true } = options;
|
||
|
|
|
||
|
|
element.focus({ preventScroll: !smooth });
|
||
|
|
|
||
|
|
if (smooth) {
|
||
|
|
element.scrollIntoView({
|
||
|
|
behavior: 'smooth',
|
||
|
|
block: center ? 'center' : 'nearest'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static getFirstFocusableElement(container) {
|
||
|
|
const focusableSelectors = [
|
||
|
|
'button',
|
||
|
|
'[href]',
|
||
|
|
'input',
|
||
|
|
'select',
|
||
|
|
'textarea',
|
||
|
|
'[tabindex]:not([tabindex="-1"])'
|
||
|
|
].join(', ');
|
||
|
|
|
||
|
|
return container.querySelector(focusableSelectors);
|
||
|
|
}
|
||
|
|
|
||
|
|
static restoreFocus(element) {
|
||
|
|
const prevElement = element.dataset.previousFocus;
|
||
|
|
if (prevElement) {
|
||
|
|
const el = document.querySelector(prevElement);
|
||
|
|
if (el) el.focus();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static saveFocus(element) {
|
||
|
|
const current = document.activeElement;
|
||
|
|
if (current) {
|
||
|
|
element.dataset.previousFocus = current.getAttribute('data-focus-id') || current.id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|