UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

280 lines (277 loc) 7.32 kB
import { useId } from 'react'; /** * AuraGlass Accessibility Utilities * WCAG 2.1 AA compliance and accessibility helpers */ /** * Keyboard navigation helpers */ const KEYS = { ENTER: 'Enter', SPACE: ' ', ESCAPE: 'Escape', TAB: 'Tab', ARROW_UP: 'ArrowUp', ARROW_DOWN: 'ArrowDown', ARROW_LEFT: 'ArrowLeft', ARROW_RIGHT: 'ArrowRight', HOME: 'Home', END: 'End', PAGE_UP: 'PageUp', PAGE_DOWN: 'PageDown' }; /** * React hook for generating consistent accessibility IDs */ const useA11yId = prefix => { const id = useId(); return prefix ? `${prefix}-${id}` : id; }; function createFormFieldA11y(options) { const { id, label, description, error, required = false, invalid = false, disabled = false, labelId, descriptionId, errorId } = options; const attrs = {}; if (id) attrs.id = id; if (label && !labelId) attrs['aria-label'] = label; if (labelId) attrs['aria-labelledby'] = labelId; if (required) attrs['aria-required'] = true; if (invalid) attrs['aria-invalid'] = true; if (disabled) attrs['aria-disabled'] = true; // Build describedby from available descriptions const describedByIds = []; if (description && descriptionId) describedByIds.push(descriptionId); if (error && errorId) describedByIds.push(errorId); if (describedByIds.length > 0) { attrs['aria-describedby'] = describedByIds.join(' '); } if (error && errorId) { attrs['aria-errormessage'] = errorId; } return attrs; } function createButtonA11y(options) { const { id, label, description, pressed, expanded, controls, haspopup, disabled = false, descriptionId } = options; const attrs = {}; if (id) attrs.id = id; if (label) attrs['aria-label'] = label; if (description && descriptionId) attrs['aria-describedby'] = descriptionId; if (pressed !== undefined) attrs['aria-pressed'] = pressed; if (expanded !== undefined) attrs['aria-expanded'] = expanded; if (controls) attrs['aria-controls'] = controls; if (haspopup !== undefined) attrs['aria-haspopup'] = haspopup; if (disabled) attrs['aria-disabled'] = true; return attrs; } function createNavigationA11y(options) { const { id, label, current, expanded, controls, owns, level, posinset, setsize } = options; const attrs = {}; if (id) attrs.id = id; if (label) attrs['aria-label'] = label; if (current !== undefined) attrs['aria-current'] = current; if (expanded !== undefined) attrs['aria-expanded'] = expanded; if (controls) attrs['aria-controls'] = controls; if (owns) attrs['aria-owns'] = owns; if (level !== undefined) attrs['aria-level'] = level; if (posinset !== undefined) attrs['aria-posinset'] = posinset; if (setsize !== undefined) attrs['aria-setsize'] = setsize; return attrs; } function createModalA11y(options) { const { id, titleId, descriptionId, modal = true } = options; const attrs = { role: 'dialog' }; if (id) attrs.id = id; if (modal) attrs['aria-modal'] = true; if (titleId) attrs['aria-labelledby'] = titleId; if (descriptionId) attrs['aria-describedby'] = descriptionId; return attrs; } /** * Pagination accessibility helpers */ function createPaginationA11y(options) { const { id, label, current, disabled, page } = options; const attrs = {}; if (id) attrs.id = id; if (label) attrs['aria-label'] = label; if (current) attrs['aria-current'] = 'page'; if (disabled) attrs['aria-disabled'] = true; return attrs; } /** * Focus management utilities */ const focusUtils = { /** * Set focus to element by ID with optional delay */ focusById: (id, delay = 0) => { setTimeout(() => { const element = document.getElementById(id); if (element) { element.focus(); } }, delay); }, /** * Focus first focusable element within container */ focusFirst: container => { const focusable = container.querySelector('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'); if (focusable) { focusable.focus(); } }, /** * Focus last focusable element within container */ focusLast: container => { const focusable = container.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'); const lastFocusable = focusable[focusable.length - 1]; if (lastFocusable) { lastFocusable.focus(); } }, /** * Get all focusable elements within container */ getFocusableElements: container => { const elements = container.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'); return Array.from(elements); } }; /** * Keyboard event handlers for common accessibility patterns */ const keyboardHandlers = { /** * Handle escape key */ onEscape: callback => event => { if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); callback(); } }, /** * Handle enter or space key for button-like elements */ onActivate: callback => event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); event.stopPropagation(); callback(); } }, /** * Handle arrow navigation */ onArrowNavigation: options => event => { const { onArrowUp, onArrowDown, onArrowLeft, onArrowRight, onHome, onEnd } = options; switch (event.key) { case 'ArrowUp': event.preventDefault(); onArrowUp?.(); break; case 'ArrowDown': event.preventDefault(); onArrowDown?.(); break; case 'ArrowLeft': event.preventDefault(); onArrowLeft?.(); break; case 'ArrowRight': event.preventDefault(); onArrowRight?.(); break; case 'Home': event.preventDefault(); onHome?.(); break; case 'End': event.preventDefault(); onEnd?.(); break; } } }; /** * Announce messages to screen readers */ const announceToScreenReader = (message, priority = 'polite') => { const announcement = document.createElement('div'); announcement.setAttribute('aria-live', priority); announcement.setAttribute('aria-atomic', 'true'); announcement.className = 'sr-only'; announcement.style.cssText = ` position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; `; announcement.textContent = message; document.body.appendChild(announcement); // Remove after announcement setTimeout(() => { if (document.body.contains(announcement)) { document.body.removeChild(announcement); } }, 1000); }; export { KEYS, announceToScreenReader, createButtonA11y, createFormFieldA11y, createModalA11y, createNavigationA11y, createPaginationA11y, focusUtils, keyboardHandlers, useA11yId }; //# sourceMappingURL=a11y.js.map