aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
280 lines (277 loc) • 7.32 kB
JavaScript
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