UNPKG

@ordojs/accessibility

Version:

Comprehensive accessibility system for OrdoJS with ARIA generation, automated testing, and screen reader support

365 lines (363 loc) 10.2 kB
import { EventEmitter } from 'events'; // src/focus/index.ts var FocusManager = class extends EventEmitter { config; focusableElements; focusTraps; skipLinks; isInitialized; /** * Create a new FocusManager instance * * @param config - Focus configuration */ constructor(config) { super(); this.config = config; this.focusableElements = /* @__PURE__ */ new Map(); this.focusTraps = /* @__PURE__ */ new Map(); this.skipLinks = /* @__PURE__ */ new Map(); this.isInitialized = false; } /** * Initialize the focus manager */ async initialize() { if (this.isInitialized) { console.warn("Focus manager is already initialized"); return; } try { await this.initializeFocusManagement(); this.isInitialized = true; console.log("Focus manager initialized successfully"); this.emit("initialized"); } catch (error) { console.error("Failed to initialize focus manager:", error); this.emit("error", error); throw error; } } /** * Register focusable element * * @param element - Element selector * @param info - Focus information */ registerFocusableElement(element, info = {}) { const focusInfo = { element, tabIndex: info.tabIndex || 0, focusOrder: info.focusOrder || 0, focusable: info.focusable !== false, focused: false, focusTrap: info.focusTrap || false, skipLink: info.skipLink || false, ariaState: info.ariaState || {}, ariaProperty: info.ariaProperty || {} }; if (info.ariaLabel) { focusInfo.ariaLabel = info.ariaLabel; } if (info.ariaDescription) { focusInfo.ariaDescription = info.ariaDescription; } if (info.ariaRole) { focusInfo.ariaRole = info.ariaRole; } this.focusableElements.set(element, focusInfo); this.emit("elementRegistered", focusInfo); } /** * Create focus trap * * @param trapId - Focus trap ID * @param elements - Elements in the trap * @param options - Trap options */ createFocusTrap(trapId, elements, options = {}) { this.focusTraps.set(trapId, elements); elements.forEach((element, index) => { const focusInfo = this.focusableElements.get(element); if (focusInfo) { focusInfo.focusOrder = index; focusInfo.focusTrap = true; this.focusableElements.set(element, focusInfo); } }); if (options.initialFocus) { this.setFocus(options.initialFocus); } this.emit("focusTrapCreated", { trapId, elements, options }); } /** * Remove focus trap * * @param trapId - Focus trap ID * @param options - Removal options */ removeFocusTrap(trapId, options = {}) { const elements = this.focusTraps.get(trapId); if (elements) { elements.forEach((element) => { const focusInfo = this.focusableElements.get(element); if (focusInfo) { focusInfo.focusTrap = false; this.focusableElements.set(element, focusInfo); } }); this.focusTraps.delete(trapId); this.emit("focusTrapRemoved", { trapId, options }); } } /** * Set focus to element * * @param element - Element selector */ setFocus(element) { const focusInfo = this.focusableElements.get(element); if (focusInfo && focusInfo.focusable) { for (const info of this.focusableElements.values()) { info.focused = false; } focusInfo.focused = true; this.focusableElements.set(element, focusInfo); this.emit("focusChanged", focusInfo); } } /** * Move focus to next element * * @param currentElement - Current element * @returns Next focused element or undefined */ moveToNextElement(currentElement) { const current = this.focusableElements.get(currentElement); if (!current) return void 0; const focusableElements = Array.from(this.focusableElements.values()).filter((info) => info.focusable).sort((a, b) => a.focusOrder - b.focusOrder); const currentIndex = focusableElements.findIndex((info) => info.element === currentElement); if (currentIndex === -1) return void 0; const nextIndex = (currentIndex + 1) % focusableElements.length; const nextElement = focusableElements[nextIndex]; if (nextElement) { this.setFocus(nextElement.element); return nextElement; } return void 0; } /** * Move focus to previous element * * @param currentElement - Current element * @returns Previous focused element or undefined */ moveToPreviousElement(currentElement) { const current = this.focusableElements.get(currentElement); if (!current) return void 0; const focusableElements = Array.from(this.focusableElements.values()).filter((info) => info.focusable).sort((a, b) => a.focusOrder - b.focusOrder); const currentIndex = focusableElements.findIndex((info) => info.element === currentElement); if (currentIndex === -1) return void 0; const prevIndex = currentIndex === 0 ? focusableElements.length - 1 : currentIndex - 1; const prevElement = focusableElements[prevIndex]; if (prevElement) { this.setFocus(prevElement.element); return prevElement; } return void 0; } /** * Add skip link * * @param target - Target element * @param label - Skip link label * @param position - Skip link position */ addSkipLink(target, label, position = "top") { this.skipLinks.set(target, label); this.emit("skipLinkAdded", { target, label, position }); } /** * Remove skip link * * @param target - Target element */ removeSkipLink(target) { this.skipLinks.delete(target); this.emit("skipLinkRemoved", { target }); } /** * Handle keyboard navigation * * @param event - Keyboard event * @param currentElement - Current element * @returns Navigation result */ handleKeyboardNavigation(event, currentElement) { const navigation = { id: `nav_${Date.now()}`, type: "custom", key: event.key, keyCode: event.keyCode, target: currentElement, action: "", description: "", enabled: true, visible: true }; let handled = false; let action = ""; switch (event.key) { case "Tab": if (event.shiftKey) { const prevElement = this.moveToPreviousElement(currentElement); if (prevElement) { action = "moveToPrevious"; handled = true; } } else { const nextElement = this.moveToNextElement(currentElement); if (nextElement) { action = "moveToNext"; handled = true; } } break; case "Escape": action = "escape"; handled = true; break; case "Enter": case " ": action = "activate"; handled = true; break; case "ArrowUp": action = "navigateUp"; handled = true; break; case "ArrowDown": action = "navigateDown"; handled = true; break; case "ArrowLeft": action = "navigateLeft"; handled = true; break; case "ArrowRight": action = "navigateRight"; handled = true; break; case "Home": action = "moveToFirst"; handled = true; break; case "End": action = "moveToLast"; handled = true; break; } if (handled) { event.preventDefault(); event.stopPropagation(); } navigation.action = action; this.emit("keyboardNavigation", navigation); return { handled, action, target: currentElement }; } /** * Get focusable elements * * @returns Array of focusable elements */ getFocusableElements() { return Array.from(this.focusableElements.values()).filter((info) => info.focusable).sort((a, b) => a.focusOrder - b.focusOrder); } /** * Get focus trap elements * * @param trapId - Focus trap ID * @returns Array of trapped elements */ getFocusTrapElements(trapId) { const elements = this.focusTraps.get(trapId); if (!elements) return []; return elements.map((element) => this.focusableElements.get(element)).filter((info) => info !== void 0).sort((a, b) => a.focusOrder - b.focusOrder); } /** * Get skip links * * @returns Array of skip links */ getSkipLinks() { return Array.from(this.skipLinks.entries()).map(([target, label]) => ({ target, label })); } /** * Get currently focused element * * @returns Focused element or undefined */ getFocusedElement() { return Array.from(this.focusableElements.values()).find((info) => info.focused); } /** * Get focus manager statistics * * @returns Statistics */ getStats() { const totalElements = this.focusableElements.size; const focusableElements = Array.from(this.focusableElements.values()).filter((info) => info.focusable).length; const focusedElements = Array.from(this.focusableElements.values()).filter((info) => info.focused).length; return { totalElements, focusableElements, focusedElements, focusTraps: this.focusTraps.size, skipLinks: this.skipLinks.size }; } /** * Initialize focus management */ async initializeFocusManagement() { console.log("Initializing focus management..."); if (this.config.focusIndicators) { this.setupFocusIndicators(); } if (this.config.skipLinks) { this.setupSkipLinks(); } if (this.config.focusRestoration) { this.setupFocusRestoration(); } } /** * Setup focus indicators */ setupFocusIndicators() { console.log("Setting up focus indicators..."); } /** * Setup skip links */ setupSkipLinks() { console.log("Setting up skip links..."); } /** * Setup focus restoration */ setupFocusRestoration() { console.log("Setting up focus restoration..."); } }; export { FocusManager }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map