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;
}
}
}