aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
81 lines (79 loc) • 2.7 kB
JavaScript
/**
* AuraGlass Focus Utilities
* Keyboard navigation and focus management helpers
*/
/**
* Get all focusable elements within a container
*/
function getFocusableElements(container) {
const focusableSelectors = ['a[href]:not([disabled])', 'button:not([disabled])', 'input:not([disabled]):not([type="hidden"])', 'select:not([disabled])', 'textarea:not([disabled])', '[tabindex]:not([tabindex="-1"])', '[contenteditable="true"]', 'audio[controls]', 'video[controls]', 'details > summary'].join(', ');
return Array.from(container.querySelectorAll(focusableSelectors)).filter(el => {
// Check if element is visible
const styles = window.getComputedStyle(el);
return styles.display !== 'none' && styles.visibility !== 'hidden' && styles.opacity !== '0';
});
}
/**
* Trap focus within a container
*/
function trapFocus(container, options = {}) {
const focusableElements = getFocusableElements(container);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
// Store previously focused element
const previouslyFocused = document.activeElement;
// Set initial focus
if (options.initialFocus?.current) {
options.initialFocus.current.focus();
} else if (firstFocusable) {
firstFocusable.focus();
}
// Handle tab navigation
const handleKeyDown = e => {
if (e.key === 'Tab') {
if (focusableElements.length === 0) {
e.preventDefault();
return;
}
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable?.focus();
}
} else {
// Tab
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable?.focus();
}
}
}
// Handle escape
if (options.escapeDeactivates && e.key === 'Escape') {
releaseFocus();
}
};
// Handle clicks outside
const handleClickOutside = e => {
if (!options.allowOutsideClick && !container.contains(e.target)) {
e.preventDefault();
e.stopPropagation();
firstFocusable?.focus();
}
};
// Add event listeners
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('mousedown', handleClickOutside);
// Release function
const releaseFocus = () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('mousedown', handleClickOutside);
if (options.returnFocus && previouslyFocused) {
previouslyFocused.focus();
}
};
return releaseFocus;
}
export { getFocusableElements, trapFocus };
//# sourceMappingURL=focus.js.map