accessify-widget 0.3.71 → 0.3.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { t, g as getCurrentWidgetLang } from "./index-DLo9u9kS.js";
1
+ import { t, g as getCurrentWidgetLang } from "./index-DCvvWewP.js";
2
2
  function createKeyboardNavModule() {
3
3
  let enabled = false;
4
4
  const STYLE_ID = "accessify-keyboard-nav";
@@ -375,4 +375,4 @@ function createKeyboardNavModule() {
375
375
  export {
376
376
  createKeyboardNavModule as default
377
377
  };
378
- //# sourceMappingURL=keyboard-nav-C9qVZ3e8.js.map
378
+ //# sourceMappingURL=keyboard-nav-BMIfQKyz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-nav-C9qVZ3e8.js","sources":["../src/features/keyboard-nav.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\nimport { getCurrentWidgetLang, t } from '../i18n/index';\n\nexport default function createKeyboardNavModule(): FeatureModule {\n let enabled = false;\n const STYLE_ID = 'accessify-keyboard-nav';\n const SKIP_LINK_ID = 'accessify-skip-link';\n const OVERLAY_ID = 'accessify-keyboard-help';\n const STORAGE_KEY = 'accessify-keyboard-nav';\n\n let headingIndex = -1;\n let landmarkIndex = -1;\n let headings: HTMLElement[] = [];\n let landmarks: HTMLElement[] = [];\n let helpOverlayVisible = false;\n let mutationObserver: MutationObserver | null = null;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let originalPushState: typeof history.pushState | null = null;\n let originalReplaceState: typeof history.replaceState | null = null;\n const boundPopstateHandler = () => scheduleDynamicRescan();\n\n function lang(): string {\n return getCurrentWidgetLang();\n }\n\n // --- Skip-to-content link ---\n\n function findMainContent(): HTMLElement | null {\n return (\n document.querySelector('main') ||\n document.getElementById('content') ||\n document.getElementById('main-content') ||\n document.querySelector('[role=\"main\"]') ||\n document.querySelector('h1')\n );\n }\n\n function hasSkipLink(): boolean {\n const links = document.querySelectorAll('a[href^=\"#\"]');\n for (const link of links) {\n const text = (link as HTMLElement).textContent?.toLowerCase() || '';\n if (text.includes('skip') || text.includes('main content') || text.includes('navigation')) {\n return true;\n }\n }\n return false;\n }\n\n function injectSkipLink() {\n if (hasSkipLink()) return;\n const target = findMainContent();\n if (!target) return;\n\n // Ensure target has an ID for the skip link to reference\n if (!target.id) {\n target.id = 'accessify-main-content';\n }\n\n const skipLink = document.createElement('a');\n skipLink.id = SKIP_LINK_ID;\n skipLink.href = `#${target.id}`;\n skipLink.textContent = t('keyboard.skipToContent', lang());\n skipLink.addEventListener('click', (e) => {\n e.preventDefault();\n target.setAttribute('tabindex', '-1');\n target.focus();\n target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n });\n\n document.body.insertBefore(skipLink, document.body.firstChild);\n }\n\n function removeSkipLink() {\n const skipLink = document.getElementById(SKIP_LINK_ID);\n skipLink?.remove();\n }\n\n // --- Heading navigation (Alt+H) ---\n\n function collectHeadings() {\n headings = Array.from(\n document.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6')\n ).filter((el) => {\n // Only visible headings\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextHeading() {\n collectHeadings();\n if (headings.length === 0) return;\n headingIndex = (headingIndex + 1) % headings.length;\n const heading = headings[headingIndex];\n heading.setAttribute('tabindex', '-1');\n heading.focus();\n heading.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Landmark navigation (Alt+L) ---\n\n function collectLandmarks() {\n const selectors = [\n 'header, [role=\"banner\"]',\n 'nav, [role=\"navigation\"]',\n 'main, [role=\"main\"]',\n 'aside, [role=\"complementary\"]',\n '[role=\"search\"]',\n '[role=\"form\"]',\n 'footer, [role=\"contentinfo\"]',\n '[role=\"region\"][aria-label]',\n ];\n landmarks = Array.from(\n document.querySelectorAll<HTMLElement>(selectors.join(', '))\n ).filter((el) => {\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextLandmark() {\n collectLandmarks();\n if (landmarks.length === 0) return;\n landmarkIndex = (landmarkIndex + 1) % landmarks.length;\n const landmark = landmarks[landmarkIndex];\n landmark.setAttribute('tabindex', '-1');\n landmark.focus();\n landmark.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Keyboard shortcut help overlay (Alt+K) ---\n\n function toggleHelpOverlay() {\n const existing = document.getElementById(OVERLAY_ID);\n if (existing) {\n existing.remove();\n helpOverlayVisible = false;\n return;\n }\n\n helpOverlayVisible = true;\n const overlay = document.createElement('div');\n overlay.id = OVERLAY_ID;\n overlay.setAttribute('role', 'dialog');\n overlay.setAttribute('aria-label', t('keyboard.shortcuts', lang()));\n overlay.setAttribute('aria-modal', 'true');\n\n overlay.innerHTML = `\n <div style=\"\n position: fixed; inset: 0; background: rgba(0,0,0,0.6);\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483647;\n \">\n <div style=\"\n background: #fff; color: #222; border-radius: 12px; padding: 32px;\n max-width: 480px; width: 90%; max-height: 80vh; overflow-y: auto;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: system-ui, sans-serif;\n \" role=\"document\">\n <h2 style=\"margin: 0 0 16px; font-size: 20px; color: #1a73e8;\">${t('keyboard.shortcuts', lang())}</h2>\n <table style=\"width: 100%; border-collapse: collapse; font-size: 14px;\">\n <thead>\n <tr style=\"border-bottom: 2px solid #e0e0e0;\">\n <th style=\"text-align: left; padding: 8px 12px;\">${t('keyboard.shortcut', lang())}</th>\n <th style=\"text-align: left; padding: 8px 12px;\">${t('keyboard.action', lang())}</th>\n </tr>\n </thead>\n <tbody>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + H</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextHeading', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + L</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextLandmark', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + K</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.toggleHelp', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Tab</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextFocusable', lang())}</td>\n </tr>\n <tr>\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Escape</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.closeOverlay', lang())}</td>\n </tr>\n </tbody>\n </table>\n <button style=\"\n margin-top: 20px; padding: 8px 24px; background: #1a73e8; color: #fff;\n border: none; border-radius: 6px; cursor: pointer; font-size: 14px;\n \" id=\"accessify-keyboard-help-close\">${t('widget.close', lang())}</button>\n </div>\n </div>\n `;\n\n document.body.appendChild(overlay);\n\n // Focus management for the dialog\n const closeBtn = document.getElementById('accessify-keyboard-help-close');\n closeBtn?.focus();\n closeBtn?.addEventListener('click', () => {\n overlay.remove();\n helpOverlayVisible = false;\n });\n }\n\n // --- Tabindex injection for interactive elements ---\n\n function injectTabindex() {\n const selectors = [\n 'a[href]',\n 'button',\n 'input',\n 'select',\n 'textarea',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"switch\"]',\n '[contenteditable=\"true\"]',\n 'summary',\n ];\n\n const elements = document.querySelectorAll<HTMLElement>(selectors.join(', '));\n elements.forEach((el) => {\n // Only add tabindex if the element is not already focusable via native mechanism\n // and does not already have an explicit tabindex\n if (!el.hasAttribute('tabindex') && !isNativelyFocusable(el)) {\n el.setAttribute('tabindex', '0');\n el.dataset.accessifyTabindex = 'true';\n }\n });\n }\n\n function removeInjectedTabindex() {\n const elements = document.querySelectorAll<HTMLElement>('[data-accessify-tabindex=\"true\"]');\n elements.forEach((el) => {\n el.removeAttribute('tabindex');\n delete el.dataset.accessifyTabindex;\n });\n }\n\n function isNativelyFocusable(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if (['a', 'button', 'input', 'select', 'textarea'].includes(tag)) {\n return true;\n }\n if (tag === 'a' && !(el as HTMLAnchorElement).href) {\n return false;\n }\n return false;\n }\n\n // --- Keyboard event handler ---\n\n function handleKeyDown(e: KeyboardEvent) {\n if (!enabled) return;\n\n // Close help overlay on Escape\n if (e.key === 'Escape' && helpOverlayVisible) {\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n helpOverlayVisible = false;\n return;\n }\n\n if (!e.altKey) return;\n\n switch (e.key.toLowerCase()) {\n case 'h':\n e.preventDefault();\n navigateToNextHeading();\n break;\n case 'l':\n e.preventDefault();\n navigateToNextLandmark();\n break;\n case 'k':\n e.preventDefault();\n toggleHelpOverlay();\n break;\n }\n }\n\n // --- Styles for skip link ---\n\n function getStyles(): string {\n return `\n /* accessify keyboard navigation */\n #${SKIP_LINK_ID} {\n position: fixed;\n top: -100px;\n left: 50%;\n transform: translateX(-50%);\n background: #1a73e8;\n color: #fff;\n padding: 12px 24px;\n border-radius: 0 0 8px 8px;\n font-size: 16px;\n font-family: system-ui, sans-serif;\n text-decoration: none;\n z-index: 2147483647;\n transition: top 0.2s ease;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n }\n #${SKIP_LINK_ID}:focus {\n top: 0;\n outline: 3px solid #ffdd00;\n outline-offset: 2px;\n }\n `;\n }\n\n function injectStyles() {\n let styleEl = document.getElementById(STYLE_ID);\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = getStyles();\n }\n\n function removeStyles() {\n const styleEl = document.getElementById(STYLE_ID);\n styleEl?.remove();\n }\n\n // --- Dynamic re-scan for SPA navigation + lazy content ---\n\n function scheduleDynamicRescan() {\n if (!enabled) return;\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n // Re-collect headings and landmarks, reset indices\n collectHeadings();\n collectLandmarks();\n headingIndex = -1;\n landmarkIndex = -1;\n // Re-inject tabindex for newly added interactive elements\n injectTabindex();\n }, 300);\n }\n\n function setupDynamicObserver() {\n if (mutationObserver) return;\n mutationObserver = new MutationObserver(() => scheduleDynamicRescan());\n const target = document.querySelector('main') || document.body;\n mutationObserver.observe(target, { childList: true, subtree: true });\n }\n\n function teardownDynamicObserver() {\n mutationObserver?.disconnect();\n mutationObserver = null;\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n }\n\n function setupSPAListeners() {\n if (!originalPushState) {\n originalPushState = history.pushState;\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\n originalPushState!.apply(this, args);\n scheduleDynamicRescan();\n };\n }\n if (!originalReplaceState) {\n originalReplaceState = history.replaceState;\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\n originalReplaceState!.apply(this, args);\n scheduleDynamicRescan();\n };\n }\n window.addEventListener('popstate', boundPopstateHandler);\n }\n\n function teardownSPAListeners() {\n if (originalPushState) {\n history.pushState = originalPushState;\n originalPushState = null;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n originalReplaceState = null;\n }\n window.removeEventListener('popstate', boundPopstateHandler);\n }\n\n // --- Module lifecycle ---\n\n function activate() {\n enabled = true;\n headingIndex = -1;\n landmarkIndex = -1;\n\n injectStyles();\n injectSkipLink();\n injectTabindex();\n document.addEventListener('keydown', handleKeyDown);\n setupDynamicObserver();\n setupSPAListeners();\n localStorage.setItem(STORAGE_KEY, 'true');\n }\n\n function deactivate() {\n enabled = false;\n helpOverlayVisible = false;\n\n teardownDynamicObserver();\n teardownSPAListeners();\n document.removeEventListener('keydown', handleKeyDown);\n removeSkipLink();\n removeInjectedTabindex();\n removeStyles();\n\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'keyboard-nav',\n name: () => 'Keyboard Navigation',\n description: 'Enhanced keyboard navigation with skip links, heading/landmark jumping, and shortcut help',\n icon: 'keyboard-nav',\n category: 'motor',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'keyboard-nav',\n enabled,\n }),\n setState: (state: { enabled: boolean }) => {\n if (state.enabled) {\n activate();\n } else {\n deactivate();\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keyboard-nav-BMIfQKyz.js","sources":["../src/features/keyboard-nav.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\nimport { getCurrentWidgetLang, t } from '../i18n/index';\n\nexport default function createKeyboardNavModule(): FeatureModule {\n let enabled = false;\n const STYLE_ID = 'accessify-keyboard-nav';\n const SKIP_LINK_ID = 'accessify-skip-link';\n const OVERLAY_ID = 'accessify-keyboard-help';\n const STORAGE_KEY = 'accessify-keyboard-nav';\n\n let headingIndex = -1;\n let landmarkIndex = -1;\n let headings: HTMLElement[] = [];\n let landmarks: HTMLElement[] = [];\n let helpOverlayVisible = false;\n let mutationObserver: MutationObserver | null = null;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let originalPushState: typeof history.pushState | null = null;\n let originalReplaceState: typeof history.replaceState | null = null;\n const boundPopstateHandler = () => scheduleDynamicRescan();\n\n function lang(): string {\n return getCurrentWidgetLang();\n }\n\n // --- Skip-to-content link ---\n\n function findMainContent(): HTMLElement | null {\n return (\n document.querySelector('main') ||\n document.getElementById('content') ||\n document.getElementById('main-content') ||\n document.querySelector('[role=\"main\"]') ||\n document.querySelector('h1')\n );\n }\n\n function hasSkipLink(): boolean {\n const links = document.querySelectorAll('a[href^=\"#\"]');\n for (const link of links) {\n const text = (link as HTMLElement).textContent?.toLowerCase() || '';\n if (text.includes('skip') || text.includes('main content') || text.includes('navigation')) {\n return true;\n }\n }\n return false;\n }\n\n function injectSkipLink() {\n if (hasSkipLink()) return;\n const target = findMainContent();\n if (!target) return;\n\n // Ensure target has an ID for the skip link to reference\n if (!target.id) {\n target.id = 'accessify-main-content';\n }\n\n const skipLink = document.createElement('a');\n skipLink.id = SKIP_LINK_ID;\n skipLink.href = `#${target.id}`;\n skipLink.textContent = t('keyboard.skipToContent', lang());\n skipLink.addEventListener('click', (e) => {\n e.preventDefault();\n target.setAttribute('tabindex', '-1');\n target.focus();\n target.scrollIntoView({ behavior: 'smooth', block: 'start' });\n });\n\n document.body.insertBefore(skipLink, document.body.firstChild);\n }\n\n function removeSkipLink() {\n const skipLink = document.getElementById(SKIP_LINK_ID);\n skipLink?.remove();\n }\n\n // --- Heading navigation (Alt+H) ---\n\n function collectHeadings() {\n headings = Array.from(\n document.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6')\n ).filter((el) => {\n // Only visible headings\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextHeading() {\n collectHeadings();\n if (headings.length === 0) return;\n headingIndex = (headingIndex + 1) % headings.length;\n const heading = headings[headingIndex];\n heading.setAttribute('tabindex', '-1');\n heading.focus();\n heading.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Landmark navigation (Alt+L) ---\n\n function collectLandmarks() {\n const selectors = [\n 'header, [role=\"banner\"]',\n 'nav, [role=\"navigation\"]',\n 'main, [role=\"main\"]',\n 'aside, [role=\"complementary\"]',\n '[role=\"search\"]',\n '[role=\"form\"]',\n 'footer, [role=\"contentinfo\"]',\n '[role=\"region\"][aria-label]',\n ];\n landmarks = Array.from(\n document.querySelectorAll<HTMLElement>(selectors.join(', '))\n ).filter((el) => {\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n function navigateToNextLandmark() {\n collectLandmarks();\n if (landmarks.length === 0) return;\n landmarkIndex = (landmarkIndex + 1) % landmarks.length;\n const landmark = landmarks[landmarkIndex];\n landmark.setAttribute('tabindex', '-1');\n landmark.focus();\n landmark.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }\n\n // --- Keyboard shortcut help overlay (Alt+K) ---\n\n function toggleHelpOverlay() {\n const existing = document.getElementById(OVERLAY_ID);\n if (existing) {\n existing.remove();\n helpOverlayVisible = false;\n return;\n }\n\n helpOverlayVisible = true;\n const overlay = document.createElement('div');\n overlay.id = OVERLAY_ID;\n overlay.setAttribute('role', 'dialog');\n overlay.setAttribute('aria-label', t('keyboard.shortcuts', lang()));\n overlay.setAttribute('aria-modal', 'true');\n\n overlay.innerHTML = `\n <div style=\"\n position: fixed; inset: 0; background: rgba(0,0,0,0.6);\n display: flex; align-items: center; justify-content: center;\n z-index: 2147483647;\n \">\n <div style=\"\n background: #fff; color: #222; border-radius: 12px; padding: 32px;\n max-width: 480px; width: 90%; max-height: 80vh; overflow-y: auto;\n box-shadow: 0 8px 32px rgba(0,0,0,0.3); font-family: system-ui, sans-serif;\n \" role=\"document\">\n <h2 style=\"margin: 0 0 16px; font-size: 20px; color: #1a73e8;\">${t('keyboard.shortcuts', lang())}</h2>\n <table style=\"width: 100%; border-collapse: collapse; font-size: 14px;\">\n <thead>\n <tr style=\"border-bottom: 2px solid #e0e0e0;\">\n <th style=\"text-align: left; padding: 8px 12px;\">${t('keyboard.shortcut', lang())}</th>\n <th style=\"text-align: left; padding: 8px 12px;\">${t('keyboard.action', lang())}</th>\n </tr>\n </thead>\n <tbody>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + H</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextHeading', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + L</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextLandmark', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Alt + K</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.toggleHelp', lang())}</td>\n </tr>\n <tr style=\"border-bottom: 1px solid #f0f0f0;\">\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Tab</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.nextFocusable', lang())}</td>\n </tr>\n <tr>\n <td style=\"padding: 8px 12px;\"><kbd style=\"background:#f5f5f5;padding:2px 8px;border-radius:4px;border:1px solid #ccc;font-family:monospace;\">Escape</kbd></td>\n <td style=\"padding: 8px 12px;\">${t('keyboard.closeOverlay', lang())}</td>\n </tr>\n </tbody>\n </table>\n <button style=\"\n margin-top: 20px; padding: 8px 24px; background: #1a73e8; color: #fff;\n border: none; border-radius: 6px; cursor: pointer; font-size: 14px;\n \" id=\"accessify-keyboard-help-close\">${t('widget.close', lang())}</button>\n </div>\n </div>\n `;\n\n document.body.appendChild(overlay);\n\n // Focus management for the dialog\n const closeBtn = document.getElementById('accessify-keyboard-help-close');\n closeBtn?.focus();\n closeBtn?.addEventListener('click', () => {\n overlay.remove();\n helpOverlayVisible = false;\n });\n }\n\n // --- Tabindex injection for interactive elements ---\n\n function injectTabindex() {\n const selectors = [\n 'a[href]',\n 'button',\n 'input',\n 'select',\n 'textarea',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"switch\"]',\n '[contenteditable=\"true\"]',\n 'summary',\n ];\n\n const elements = document.querySelectorAll<HTMLElement>(selectors.join(', '));\n elements.forEach((el) => {\n // Only add tabindex if the element is not already focusable via native mechanism\n // and does not already have an explicit tabindex\n if (!el.hasAttribute('tabindex') && !isNativelyFocusable(el)) {\n el.setAttribute('tabindex', '0');\n el.dataset.accessifyTabindex = 'true';\n }\n });\n }\n\n function removeInjectedTabindex() {\n const elements = document.querySelectorAll<HTMLElement>('[data-accessify-tabindex=\"true\"]');\n elements.forEach((el) => {\n el.removeAttribute('tabindex');\n delete el.dataset.accessifyTabindex;\n });\n }\n\n function isNativelyFocusable(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if (['a', 'button', 'input', 'select', 'textarea'].includes(tag)) {\n return true;\n }\n if (tag === 'a' && !(el as HTMLAnchorElement).href) {\n return false;\n }\n return false;\n }\n\n // --- Keyboard event handler ---\n\n function handleKeyDown(e: KeyboardEvent) {\n if (!enabled) return;\n\n // Close help overlay on Escape\n if (e.key === 'Escape' && helpOverlayVisible) {\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n helpOverlayVisible = false;\n return;\n }\n\n if (!e.altKey) return;\n\n switch (e.key.toLowerCase()) {\n case 'h':\n e.preventDefault();\n navigateToNextHeading();\n break;\n case 'l':\n e.preventDefault();\n navigateToNextLandmark();\n break;\n case 'k':\n e.preventDefault();\n toggleHelpOverlay();\n break;\n }\n }\n\n // --- Styles for skip link ---\n\n function getStyles(): string {\n return `\n /* accessify keyboard navigation */\n #${SKIP_LINK_ID} {\n position: fixed;\n top: -100px;\n left: 50%;\n transform: translateX(-50%);\n background: #1a73e8;\n color: #fff;\n padding: 12px 24px;\n border-radius: 0 0 8px 8px;\n font-size: 16px;\n font-family: system-ui, sans-serif;\n text-decoration: none;\n z-index: 2147483647;\n transition: top 0.2s ease;\n box-shadow: 0 2px 8px rgba(0,0,0,0.3);\n }\n #${SKIP_LINK_ID}:focus {\n top: 0;\n outline: 3px solid #ffdd00;\n outline-offset: 2px;\n }\n `;\n }\n\n function injectStyles() {\n let styleEl = document.getElementById(STYLE_ID);\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = getStyles();\n }\n\n function removeStyles() {\n const styleEl = document.getElementById(STYLE_ID);\n styleEl?.remove();\n }\n\n // --- Dynamic re-scan for SPA navigation + lazy content ---\n\n function scheduleDynamicRescan() {\n if (!enabled) return;\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n // Re-collect headings and landmarks, reset indices\n collectHeadings();\n collectLandmarks();\n headingIndex = -1;\n landmarkIndex = -1;\n // Re-inject tabindex for newly added interactive elements\n injectTabindex();\n }, 300);\n }\n\n function setupDynamicObserver() {\n if (mutationObserver) return;\n mutationObserver = new MutationObserver(() => scheduleDynamicRescan());\n const target = document.querySelector('main') || document.body;\n mutationObserver.observe(target, { childList: true, subtree: true });\n }\n\n function teardownDynamicObserver() {\n mutationObserver?.disconnect();\n mutationObserver = null;\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n }\n\n function setupSPAListeners() {\n if (!originalPushState) {\n originalPushState = history.pushState;\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\n originalPushState!.apply(this, args);\n scheduleDynamicRescan();\n };\n }\n if (!originalReplaceState) {\n originalReplaceState = history.replaceState;\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\n originalReplaceState!.apply(this, args);\n scheduleDynamicRescan();\n };\n }\n window.addEventListener('popstate', boundPopstateHandler);\n }\n\n function teardownSPAListeners() {\n if (originalPushState) {\n history.pushState = originalPushState;\n originalPushState = null;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n originalReplaceState = null;\n }\n window.removeEventListener('popstate', boundPopstateHandler);\n }\n\n // --- Module lifecycle ---\n\n function activate() {\n enabled = true;\n headingIndex = -1;\n landmarkIndex = -1;\n\n injectStyles();\n injectSkipLink();\n injectTabindex();\n document.addEventListener('keydown', handleKeyDown);\n setupDynamicObserver();\n setupSPAListeners();\n localStorage.setItem(STORAGE_KEY, 'true');\n }\n\n function deactivate() {\n enabled = false;\n helpOverlayVisible = false;\n\n teardownDynamicObserver();\n teardownSPAListeners();\n document.removeEventListener('keydown', handleKeyDown);\n removeSkipLink();\n removeInjectedTabindex();\n removeStyles();\n\n const overlay = document.getElementById(OVERLAY_ID);\n overlay?.remove();\n\n localStorage.removeItem(STORAGE_KEY);\n }\n\n return {\n id: 'keyboard-nav',\n name: () => 'Keyboard Navigation',\n description: 'Enhanced keyboard navigation with skip links, heading/landmark jumping, and shortcut help',\n icon: 'keyboard-nav',\n category: 'motor',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'keyboard-nav',\n enabled,\n }),\n setState: (state: { enabled: boolean }) => {\n if (state.enabled) {\n activate();\n } else {\n deactivate();\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t, g as getCurrentWidgetLang } from "./index-DLo9u9kS.js";
1
+ import { t, g as getCurrentWidgetLang } from "./index-DCvvWewP.js";
2
2
  function createPageStructureModule() {
3
3
  let enabled = false;
4
4
  let panelEl = null;
@@ -243,4 +243,4 @@ function createPageStructureModule() {
243
243
  export {
244
244
  createPageStructureModule as default
245
245
  };
246
- //# sourceMappingURL=page-structure-DL3Xdzlm.js.map
246
+ //# sourceMappingURL=page-structure-BimA9vQz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-structure-DL3Xdzlm.js","sources":["../src/features/page-structure.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\nimport { getCurrentWidgetLang, t } from '../i18n/index';\n\nexport default function createPageStructureModule(): FeatureModule {\n let enabled = false;\n let panelEl: HTMLElement | null = null;\n const PANEL_ID = 'accessify-page-structure';\n let mutationObserver: MutationObserver | null = null;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let originalPushState: typeof history.pushState | null = null;\n let originalReplaceState: typeof history.replaceState | null = null;\n const boundPopstateHandler = () => scheduleRefresh();\n let lastContentHash = '';\n\n function lang(): string {\n return getCurrentWidgetLang();\n }\n\n function collectHeadings(): Array<{ tag: string; text: string; el: HTMLElement }> {\n const results: Array<{ tag: string; text: string; el: HTMLElement }> = [];\n const headings = document.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6');\n headings.forEach((el) => {\n if (el.closest('#accessify-root')) return;\n const text = el.textContent?.trim() || '';\n if (text) results.push({ tag: el.tagName.toLowerCase(), text, el });\n });\n return results;\n }\n\n function collectLandmarks(): Array<{ role: string; label: string; el: HTMLElement }> {\n const results: Array<{ role: string; label: string; el: HTMLElement }> = [];\n const selectors = [\n 'main', 'nav', 'aside', 'header', 'footer', 'section[aria-label]',\n 'section[aria-labelledby]', '[role=\"main\"]', '[role=\"navigation\"]',\n '[role=\"complementary\"]', '[role=\"banner\"]', '[role=\"contentinfo\"]',\n '[role=\"search\"]',\n ];\n const els = document.querySelectorAll<HTMLElement>(selectors.join(','));\n els.forEach((el) => {\n if (el.closest('#accessify-root')) return;\n const role = el.getAttribute('role') || el.tagName.toLowerCase();\n const label = el.getAttribute('aria-label') || el.getAttribute('aria-labelledby') || role;\n results.push({ role, label, el });\n });\n return results;\n }\n\n function buildPanel() {\n panelEl = document.createElement('div');\n panelEl.id = PANEL_ID;\n panelEl.setAttribute('role', 'dialog');\n panelEl.setAttribute('aria-label', t('pageStructure.title', lang()));\n\n const headings = collectHeadings();\n const landmarks = collectLandmarks();\n\n const indent: Record<string, string> = {\n h1: '0', h2: '12px', h3: '24px', h4: '36px', h5: '48px', h6: '60px',\n };\n\n let html = `\n <style>\n #${PANEL_ID} {\n position: fixed; top: 50%; left: 50%;\n transform: translate(-50%, -50%);\n z-index: 999998;\n background: #fff; color: #1a1a1a;\n border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.2);\n padding: 24px; width: 400px; max-height: 80vh;\n overflow-y: auto; font-family: system-ui, sans-serif;\n font-size: 14px; line-height: 1.5;\n }\n #${PANEL_ID} h3 { margin: 0 0 12px; font-size: 16px; }\n #${PANEL_ID} .ps-section { margin-bottom: 16px; }\n #${PANEL_ID} .ps-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 6px; }\n #${PANEL_ID} .ps-item {\n display: block; width: 100%; text-align: left;\n padding: 6px 8px; border: none; background: none;\n cursor: pointer; border-radius: 6px; font-size: 13px;\n color: #1a1a1a; font-family: inherit;\n }\n #${PANEL_ID} .ps-item:hover, #${PANEL_ID} .ps-item:active { background: #f0f0f2; }\n #${PANEL_ID} .ps-item:focus-visible { outline: 2px solid #0055CC; outline-offset: 1px; }\n #${PANEL_ID} .ps-tag { color: #888; font-size: 11px; margin-right: 6px; }\n #${PANEL_ID} .ps-close {\n position: absolute; top: 12px; right: 12px;\n background: none; border: none; cursor: pointer;\n font-size: 18px; color: #888; padding: 4px 8px; border-radius: 4px;\n }\n #${PANEL_ID} .ps-close:hover, #${PANEL_ID} .ps-close:active { background: #f0f0f2; }\n @media (prefers-color-scheme: dark) {\n #${PANEL_ID} { background: #1a1a1a; color: #f0f0f0; }\n #${PANEL_ID} .ps-item { color: #f0f0f0; }\n #${PANEL_ID} .ps-item:hover, #${PANEL_ID} .ps-item:active { background: #2a2a2e; }\n #${PANEL_ID} .ps-close { color: #aaa; }\n #${PANEL_ID} .ps-close:hover, #${PANEL_ID} .ps-close:active { background: #2a2a2e; }\n }\n @media (max-width: 640px) {\n #${PANEL_ID} { width: calc(100vw - 32px); max-width: 400px; }\n }\n </style>\n <button class=\"ps-close\" aria-label=\"${t('widget.close', lang())}\">&times;</button>\n <h3>${t('pageStructure.title', lang())}</h3>\n `;\n\n if (headings.length > 0) {\n html += `<div class=\"ps-section\"><div class=\"ps-label\">${t('pageStructure.headings', lang())}</div>`;\n headings.forEach((h, i) => {\n html += `<button class=\"ps-item\" data-type=\"heading\" data-index=\"${i}\" style=\"padding-left:${indent[h.tag] || '0'}\">\n <span class=\"ps-tag\">${h.tag}</span>${escapeHtml(h.text.slice(0, 80))}\n </button>`;\n });\n html += '</div>';\n }\n\n if (landmarks.length > 0) {\n html += `<div class=\"ps-section\"><div class=\"ps-label\">${t('pageStructure.landmarks', lang())}</div>`;\n landmarks.forEach((lm, i) => {\n html += `<button class=\"ps-item\" data-type=\"landmark\" data-index=\"${i}\">\n <span class=\"ps-tag\">${escapeHtml(lm.role)}</span>${escapeHtml(lm.label.slice(0, 60))}\n </button>`;\n });\n html += '</div>';\n }\n\n if (headings.length === 0 && landmarks.length === 0) {\n html += `<p style=\"color:#888\">${t('pageStructure.empty', lang())}</p>`;\n }\n\n panelEl.innerHTML = html;\n\n // Event delegation\n panelEl.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest<HTMLElement>('.ps-item');\n if (btn) {\n const type = btn.dataset.type;\n const idx = parseInt(btn.dataset.index || '0', 10);\n let target: HTMLElement | undefined;\n if (type === 'heading') target = headings[idx]?.el;\n else if (type === 'landmark') target = landmarks[idx]?.el;\n if (target) {\n target.scrollIntoView({ behavior: 'smooth', block: 'center' });\n target.focus({ preventScroll: true });\n }\n }\n if ((e.target as HTMLElement).closest('.ps-close')) {\n deactivate();\n }\n });\n\n document.body.appendChild(panelEl);\n const firstBtn = panelEl.querySelector<HTMLElement>('.ps-item, .ps-close');\n firstBtn?.focus();\n }\n\n function escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n }\n\n // --- Content hash for change detection ---\n function contentHash(): string {\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n let hash = '';\n headings.forEach((el) => { hash += el.textContent?.trim().slice(0, 20) || ''; });\n return hash;\n }\n\n function scheduleRefresh() {\n if (!enabled || !panelEl) return;\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n const newHash = contentHash();\n if (newHash !== lastContentHash) {\n lastContentHash = newHash;\n panelEl?.remove();\n buildPanel();\n }\n }, 300);\n }\n\n // --- SPA navigation detection ---\n function setupSPAListeners() {\n // Override pushState/replaceState to detect SPA navigation\n if (!originalPushState) {\n originalPushState = history.pushState;\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\n originalPushState!.apply(this, args);\n scheduleRefresh();\n };\n }\n if (!originalReplaceState) {\n originalReplaceState = history.replaceState;\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\n originalReplaceState!.apply(this, args);\n scheduleRefresh();\n };\n }\n window.addEventListener('popstate', boundPopstateHandler);\n }\n\n function teardownSPAListeners() {\n if (originalPushState) {\n history.pushState = originalPushState;\n originalPushState = null;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n originalReplaceState = null;\n }\n window.removeEventListener('popstate', boundPopstateHandler);\n }\n\n // --- MutationObserver for dynamic content ---\n function setupObserver() {\n if (mutationObserver) return;\n mutationObserver = new MutationObserver(() => scheduleRefresh());\n const target = document.querySelector('main') || document.body;\n mutationObserver.observe(target, { childList: true, subtree: true });\n }\n\n function teardownObserver() {\n mutationObserver?.disconnect();\n mutationObserver = null;\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n }\n\n function activate() {\n enabled = true;\n lastContentHash = contentHash();\n buildPanel();\n setupObserver();\n setupSPAListeners();\n }\n\n function deactivate() {\n enabled = false;\n teardownObserver();\n teardownSPAListeners();\n panelEl?.remove();\n panelEl = null;\n }\n\n return {\n id: 'page-structure',\n name: () => 'Page Structure',\n description: 'View headings and landmarks for quick navigation',\n icon: 'page-structure',\n category: 'motor',\n activate,\n deactivate,\n getState: (): FeatureState => ({ id: 'page-structure', enabled }),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"page-structure-BimA9vQz.js","sources":["../src/features/page-structure.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\nimport { getCurrentWidgetLang, t } from '../i18n/index';\n\nexport default function createPageStructureModule(): FeatureModule {\n let enabled = false;\n let panelEl: HTMLElement | null = null;\n const PANEL_ID = 'accessify-page-structure';\n let mutationObserver: MutationObserver | null = null;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n let originalPushState: typeof history.pushState | null = null;\n let originalReplaceState: typeof history.replaceState | null = null;\n const boundPopstateHandler = () => scheduleRefresh();\n let lastContentHash = '';\n\n function lang(): string {\n return getCurrentWidgetLang();\n }\n\n function collectHeadings(): Array<{ tag: string; text: string; el: HTMLElement }> {\n const results: Array<{ tag: string; text: string; el: HTMLElement }> = [];\n const headings = document.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6');\n headings.forEach((el) => {\n if (el.closest('#accessify-root')) return;\n const text = el.textContent?.trim() || '';\n if (text) results.push({ tag: el.tagName.toLowerCase(), text, el });\n });\n return results;\n }\n\n function collectLandmarks(): Array<{ role: string; label: string; el: HTMLElement }> {\n const results: Array<{ role: string; label: string; el: HTMLElement }> = [];\n const selectors = [\n 'main', 'nav', 'aside', 'header', 'footer', 'section[aria-label]',\n 'section[aria-labelledby]', '[role=\"main\"]', '[role=\"navigation\"]',\n '[role=\"complementary\"]', '[role=\"banner\"]', '[role=\"contentinfo\"]',\n '[role=\"search\"]',\n ];\n const els = document.querySelectorAll<HTMLElement>(selectors.join(','));\n els.forEach((el) => {\n if (el.closest('#accessify-root')) return;\n const role = el.getAttribute('role') || el.tagName.toLowerCase();\n const label = el.getAttribute('aria-label') || el.getAttribute('aria-labelledby') || role;\n results.push({ role, label, el });\n });\n return results;\n }\n\n function buildPanel() {\n panelEl = document.createElement('div');\n panelEl.id = PANEL_ID;\n panelEl.setAttribute('role', 'dialog');\n panelEl.setAttribute('aria-label', t('pageStructure.title', lang()));\n\n const headings = collectHeadings();\n const landmarks = collectLandmarks();\n\n const indent: Record<string, string> = {\n h1: '0', h2: '12px', h3: '24px', h4: '36px', h5: '48px', h6: '60px',\n };\n\n let html = `\n <style>\n #${PANEL_ID} {\n position: fixed; top: 50%; left: 50%;\n transform: translate(-50%, -50%);\n z-index: 999998;\n background: #fff; color: #1a1a1a;\n border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.2);\n padding: 24px; width: 400px; max-height: 80vh;\n overflow-y: auto; font-family: system-ui, sans-serif;\n font-size: 14px; line-height: 1.5;\n }\n #${PANEL_ID} h3 { margin: 0 0 12px; font-size: 16px; }\n #${PANEL_ID} .ps-section { margin-bottom: 16px; }\n #${PANEL_ID} .ps-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #888; margin-bottom: 6px; }\n #${PANEL_ID} .ps-item {\n display: block; width: 100%; text-align: left;\n padding: 6px 8px; border: none; background: none;\n cursor: pointer; border-radius: 6px; font-size: 13px;\n color: #1a1a1a; font-family: inherit;\n }\n #${PANEL_ID} .ps-item:hover, #${PANEL_ID} .ps-item:active { background: #f0f0f2; }\n #${PANEL_ID} .ps-item:focus-visible { outline: 2px solid #0055CC; outline-offset: 1px; }\n #${PANEL_ID} .ps-tag { color: #888; font-size: 11px; margin-right: 6px; }\n #${PANEL_ID} .ps-close {\n position: absolute; top: 12px; right: 12px;\n background: none; border: none; cursor: pointer;\n font-size: 18px; color: #888; padding: 4px 8px; border-radius: 4px;\n }\n #${PANEL_ID} .ps-close:hover, #${PANEL_ID} .ps-close:active { background: #f0f0f2; }\n @media (prefers-color-scheme: dark) {\n #${PANEL_ID} { background: #1a1a1a; color: #f0f0f0; }\n #${PANEL_ID} .ps-item { color: #f0f0f0; }\n #${PANEL_ID} .ps-item:hover, #${PANEL_ID} .ps-item:active { background: #2a2a2e; }\n #${PANEL_ID} .ps-close { color: #aaa; }\n #${PANEL_ID} .ps-close:hover, #${PANEL_ID} .ps-close:active { background: #2a2a2e; }\n }\n @media (max-width: 640px) {\n #${PANEL_ID} { width: calc(100vw - 32px); max-width: 400px; }\n }\n </style>\n <button class=\"ps-close\" aria-label=\"${t('widget.close', lang())}\">&times;</button>\n <h3>${t('pageStructure.title', lang())}</h3>\n `;\n\n if (headings.length > 0) {\n html += `<div class=\"ps-section\"><div class=\"ps-label\">${t('pageStructure.headings', lang())}</div>`;\n headings.forEach((h, i) => {\n html += `<button class=\"ps-item\" data-type=\"heading\" data-index=\"${i}\" style=\"padding-left:${indent[h.tag] || '0'}\">\n <span class=\"ps-tag\">${h.tag}</span>${escapeHtml(h.text.slice(0, 80))}\n </button>`;\n });\n html += '</div>';\n }\n\n if (landmarks.length > 0) {\n html += `<div class=\"ps-section\"><div class=\"ps-label\">${t('pageStructure.landmarks', lang())}</div>`;\n landmarks.forEach((lm, i) => {\n html += `<button class=\"ps-item\" data-type=\"landmark\" data-index=\"${i}\">\n <span class=\"ps-tag\">${escapeHtml(lm.role)}</span>${escapeHtml(lm.label.slice(0, 60))}\n </button>`;\n });\n html += '</div>';\n }\n\n if (headings.length === 0 && landmarks.length === 0) {\n html += `<p style=\"color:#888\">${t('pageStructure.empty', lang())}</p>`;\n }\n\n panelEl.innerHTML = html;\n\n // Event delegation\n panelEl.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest<HTMLElement>('.ps-item');\n if (btn) {\n const type = btn.dataset.type;\n const idx = parseInt(btn.dataset.index || '0', 10);\n let target: HTMLElement | undefined;\n if (type === 'heading') target = headings[idx]?.el;\n else if (type === 'landmark') target = landmarks[idx]?.el;\n if (target) {\n target.scrollIntoView({ behavior: 'smooth', block: 'center' });\n target.focus({ preventScroll: true });\n }\n }\n if ((e.target as HTMLElement).closest('.ps-close')) {\n deactivate();\n }\n });\n\n document.body.appendChild(panelEl);\n const firstBtn = panelEl.querySelector<HTMLElement>('.ps-item, .ps-close');\n firstBtn?.focus();\n }\n\n function escapeHtml(str: string): string {\n return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n }\n\n // --- Content hash for change detection ---\n function contentHash(): string {\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n let hash = '';\n headings.forEach((el) => { hash += el.textContent?.trim().slice(0, 20) || ''; });\n return hash;\n }\n\n function scheduleRefresh() {\n if (!enabled || !panelEl) return;\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n const newHash = contentHash();\n if (newHash !== lastContentHash) {\n lastContentHash = newHash;\n panelEl?.remove();\n buildPanel();\n }\n }, 300);\n }\n\n // --- SPA navigation detection ---\n function setupSPAListeners() {\n // Override pushState/replaceState to detect SPA navigation\n if (!originalPushState) {\n originalPushState = history.pushState;\n history.pushState = function (...args: Parameters<typeof history.pushState>) {\n originalPushState!.apply(this, args);\n scheduleRefresh();\n };\n }\n if (!originalReplaceState) {\n originalReplaceState = history.replaceState;\n history.replaceState = function (...args: Parameters<typeof history.replaceState>) {\n originalReplaceState!.apply(this, args);\n scheduleRefresh();\n };\n }\n window.addEventListener('popstate', boundPopstateHandler);\n }\n\n function teardownSPAListeners() {\n if (originalPushState) {\n history.pushState = originalPushState;\n originalPushState = null;\n }\n if (originalReplaceState) {\n history.replaceState = originalReplaceState;\n originalReplaceState = null;\n }\n window.removeEventListener('popstate', boundPopstateHandler);\n }\n\n // --- MutationObserver for dynamic content ---\n function setupObserver() {\n if (mutationObserver) return;\n mutationObserver = new MutationObserver(() => scheduleRefresh());\n const target = document.querySelector('main') || document.body;\n mutationObserver.observe(target, { childList: true, subtree: true });\n }\n\n function teardownObserver() {\n mutationObserver?.disconnect();\n mutationObserver = null;\n if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }\n }\n\n function activate() {\n enabled = true;\n lastContentHash = contentHash();\n buildPanel();\n setupObserver();\n setupSPAListeners();\n }\n\n function deactivate() {\n enabled = false;\n teardownObserver();\n teardownSPAListeners();\n panelEl?.remove();\n panelEl = null;\n }\n\n return {\n id: 'page-structure',\n name: () => 'Page Structure',\n description: 'View headings and landmarks for quick navigation',\n icon: 'page-structure',\n category: 'motor',\n activate,\n deactivate,\n getState: (): FeatureState => ({ id: 'page-structure', enabled }),\n };\n}\n"],"names":[],"mappings":"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"}
@@ -97,7 +97,21 @@ function createTTSModule() {
97
97
  }
98
98
  return sentences.length > 0 ? sentences : [text];
99
99
  }
100
+ const activeTimeouts = /* @__PURE__ */ new Set();
101
+ function trackTimeout(cb, ms) {
102
+ const id = setTimeout(() => {
103
+ activeTimeouts.delete(id);
104
+ cb();
105
+ }, ms);
106
+ activeTimeouts.add(id);
107
+ return id;
108
+ }
109
+ function clearAllTimeouts() {
110
+ for (const id of activeTimeouts) clearTimeout(id);
111
+ activeTimeouts.clear();
112
+ }
100
113
  function stopSpeech() {
114
+ clearAllTimeouts();
101
115
  speechSynthesis.cancel();
102
116
  isPlaying = false;
103
117
  isPaused = false;
@@ -144,16 +158,19 @@ function createTTSModule() {
144
158
  settled = true;
145
159
  if (timeoutId) {
146
160
  clearTimeout(timeoutId);
161
+ activeTimeouts.delete(timeoutId);
147
162
  timeoutId = null;
148
163
  }
149
164
  }
150
- timeoutId = setTimeout(() => {
151
- if (settled) return;
165
+ timeoutId = trackTimeout(() => {
166
+ if (settled || !isPlaying) return;
152
167
  settle();
153
168
  speechSynthesis.cancel();
154
169
  currentUtteranceIndex++;
155
170
  if (currentUtteranceIndex < currentUtterances.length && isPlaying) {
156
- setTimeout(() => speakNextUtterance(), 100);
171
+ trackTimeout(() => {
172
+ if (isPlaying) speakNextUtterance();
173
+ }, 100);
157
174
  } else {
158
175
  isPlaying = false;
159
176
  isPaused = false;
@@ -162,10 +179,13 @@ function createTTSModule() {
162
179
  }
163
180
  }, 2e4);
164
181
  utterance.onend = () => {
182
+ if (!isPlaying) return;
165
183
  settle();
166
184
  currentUtteranceIndex++;
167
185
  if (currentUtteranceIndex < currentUtterances.length && isPlaying) {
168
- setTimeout(() => speakNextUtterance(), 50);
186
+ trackTimeout(() => {
187
+ if (isPlaying) speakNextUtterance();
188
+ }, 50);
169
189
  } else {
170
190
  isPlaying = false;
171
191
  isPaused = false;
@@ -174,12 +194,15 @@ function createTTSModule() {
174
194
  }
175
195
  };
176
196
  utterance.onerror = (e) => {
197
+ if (!isPlaying) return;
177
198
  settle();
178
199
  if (e.error !== "interrupted" && e.error !== "canceled") {
179
200
  console.warn("[Accessify TTS] Speech error:", e.error);
180
201
  currentUtteranceIndex++;
181
202
  if (currentUtteranceIndex < currentUtterances.length && isPlaying) {
182
- setTimeout(() => speakNextUtterance(), 100);
203
+ trackTimeout(() => {
204
+ if (isPlaying) speakNextUtterance();
205
+ }, 100);
183
206
  return;
184
207
  }
185
208
  }
@@ -548,4 +571,4 @@ function createTTSModule() {
548
571
  export {
549
572
  createTTSModule as default
550
573
  };
551
- //# sourceMappingURL=tts-CjszLRnb.js.map
574
+ //# sourceMappingURL=tts-zrXtEd07.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts-zrXtEd07.js","sources":["../src/features/tts.ts"],"sourcesContent":["import type { FeatureModule, FeatureState } from '../types';\n\ninterface TTSOptions {\n speed: number;\n}\n\nconst DEFAULT_OPTIONS: TTSOptions = {\n speed: 1.0,\n};\n\nexport default function createTTSModule(): FeatureModule {\n let enabled = false;\n let options: TTSOptions = { ...DEFAULT_OPTIONS };\n let controlBar: HTMLDivElement | null = null;\n let highlightedEl: HTMLElement | null = null;\n let isPlaying = false;\n let isPaused = false;\n let clickToReadActive = true;\n let currentUtterances: SpeechSynthesisUtterance[] = [];\n let currentUtteranceIndex = 0;\n let selectedVoice: SpeechSynthesisVoice | null = null;\n let detectedLang = 'en';\n\n const STORAGE_KEY = 'accessify-tts';\n const CONTROL_BAR_ID = 'accessify-tts-controls';\n const STYLE_ID = 'accessify-tts-styles';\n const HIGHLIGHT_CLASS = 'accessify-tts-highlight';\n\n // ---------------------------------------------------------------------------\n // Language detection\n // ---------------------------------------------------------------------------\n\n function detectPageLanguage(): string {\n const htmlLang = document.documentElement.lang;\n if (htmlLang) {\n return htmlLang.toLowerCase();\n }\n // Fallback: check <meta> content-language\n const meta = document.querySelector<HTMLMetaElement>(\n 'meta[http-equiv=\"content-language\"]'\n );\n if (meta?.content) {\n return meta.content.toLowerCase();\n }\n return 'en';\n }\n\n // ---------------------------------------------------------------------------\n // Voice selection\n // ---------------------------------------------------------------------------\n\n function selectBestVoice(lang: string): SpeechSynthesisVoice | null {\n const voices = speechSynthesis.getVoices();\n if (voices.length === 0) return null;\n\n const langBase = lang.split('-')[0]; // e.g. 'de' from 'de-DE'\n\n // For German, strongly prioritize native German voices\n if (langBase === 'de') {\n // Prefer high-quality (non-local) de-DE voices first\n const deDENetwork = voices.find(\n (v) => v.lang.toLowerCase().startsWith('de-de') && v.localService === false\n );\n if (deDENetwork) return deDENetwork;\n\n // Then any de-DE voice\n const deDE = voices.find(\n (v) => v.lang.toLowerCase().startsWith('de-de')\n );\n if (deDE) return deDE;\n\n // Then any German voice\n const deAny = voices.find((v) =>\n v.lang.toLowerCase().startsWith('de')\n );\n if (deAny) return deAny;\n }\n\n // Exact match (e.g. 'en-US' matches 'en-US')\n const exact = voices.find(\n (v) => v.lang.toLowerCase() === lang\n );\n if (exact) return exact;\n\n // Base language match (e.g. 'en' matches 'en-US', 'en-GB')\n const baseMatch = voices.find(\n (v) => v.lang.toLowerCase().startsWith(langBase)\n );\n if (baseMatch) return baseMatch;\n\n // Ultimate fallback: first available voice\n return voices[0] || null;\n }\n\n function refreshVoice() {\n selectedVoice = selectBestVoice(detectedLang);\n }\n\n /** Wait for voices to be available (Chrome loads them async). */\n function waitForVoices(timeoutMs = 3000): Promise<void> {\n return new Promise((resolve) => {\n const voices = speechSynthesis.getVoices();\n if (voices.length > 0) { resolve(); return; }\n let resolved = false;\n const handler = () => {\n if (resolved) return;\n resolved = true;\n resolve();\n };\n speechSynthesis.addEventListener('voiceschanged', handler, { once: true });\n setTimeout(handler, timeoutMs); // don't block forever\n });\n }\n\n // ---------------------------------------------------------------------------\n // Sentence splitting (avoids Chrome's ~15s synthesis cutoff)\n // ---------------------------------------------------------------------------\n\n function splitIntoSentences(text: string): string[] {\n // Split on sentence-ending punctuation followed by whitespace or end\n const raw = text.match(/[^.!?]*[.!?]+[\\s]?|[^.!?]+$/g);\n if (!raw) return [text];\n\n const sentences: string[] = [];\n let buffer = '';\n\n for (const segment of raw) {\n const trimmed = segment.trim();\n if (!trimmed) continue;\n\n buffer += (buffer ? ' ' : '') + trimmed;\n\n // Flush buffer if it's reasonably long (> 80 chars) or is a complete sentence\n if (buffer.length > 80 || /[.!?]$/.test(buffer)) {\n sentences.push(buffer);\n buffer = '';\n }\n }\n\n if (buffer.trim()) {\n sentences.push(buffer.trim());\n }\n\n return sentences.length > 0 ? sentences : [text];\n }\n\n // ---------------------------------------------------------------------------\n // Speech engine\n // ---------------------------------------------------------------------------\n\n // All pending watchdog + advance timeouts. Cleared on every stopSpeech\n // so a stalled timeout can't resurrect speech after the user stops.\n const activeTimeouts = new Set<ReturnType<typeof setTimeout>>();\n function trackTimeout(cb: () => void, ms: number): ReturnType<typeof setTimeout> {\n const id = setTimeout(() => {\n activeTimeouts.delete(id);\n cb();\n }, ms);\n activeTimeouts.add(id);\n return id;\n }\n function clearAllTimeouts() {\n for (const id of activeTimeouts) clearTimeout(id);\n activeTimeouts.clear();\n }\n\n function stopSpeech() {\n clearAllTimeouts();\n speechSynthesis.cancel();\n isPlaying = false;\n isPaused = false;\n currentUtterances = [];\n currentUtteranceIndex = 0;\n removeHighlight();\n updateControlBarState();\n }\n\n function speakText(text: string, sourceEl?: HTMLElement) {\n stopSpeech();\n\n if (!text.trim()) return;\n\n if (sourceEl) {\n applyHighlight(sourceEl);\n }\n\n const sentences = splitIntoSentences(text).slice(0, 50); // cap at 50 sentences\n currentUtterances = sentences.map((sentence) => {\n const utterance = new SpeechSynthesisUtterance(sentence);\n utterance.rate = options.speed;\n utterance.lang = detectedLang;\n if (selectedVoice) {\n utterance.voice = selectedVoice;\n }\n return utterance;\n });\n\n currentUtteranceIndex = 0;\n isPlaying = true;\n isPaused = false;\n updateControlBarState();\n\n speakNextUtterance();\n }\n\n function speakNextUtterance() {\n if (currentUtteranceIndex >= currentUtterances.length) {\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n return;\n }\n\n const utterance = currentUtterances[currentUtteranceIndex];\n\n // Safety timeout: Chrome can stall on long utterances (>15s).\n // If onend/onerror don't fire within 20s, force-advance.\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let settled = false;\n\n function settle() {\n if (settled) return;\n settled = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n activeTimeouts.delete(timeoutId);\n timeoutId = null;\n }\n }\n\n timeoutId = trackTimeout(() => {\n if (settled || !isPlaying) return;\n settle();\n speechSynthesis.cancel();\n currentUtteranceIndex++;\n if (currentUtteranceIndex < currentUtterances.length && isPlaying) {\n trackTimeout(() => { if (isPlaying) speakNextUtterance(); }, 100);\n } else {\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n }\n }, 20_000);\n\n utterance.onend = () => {\n if (!isPlaying) return;\n settle();\n currentUtteranceIndex++;\n if (currentUtteranceIndex < currentUtterances.length && isPlaying) {\n trackTimeout(() => { if (isPlaying) speakNextUtterance(); }, 50);\n } else {\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n }\n };\n\n utterance.onerror = (e) => {\n if (!isPlaying) return;\n settle();\n if (e.error !== 'interrupted' && e.error !== 'canceled') {\n console.warn('[Accessify TTS] Speech error:', e.error);\n // Skip to next utterance on real errors instead of stopping entirely\n currentUtteranceIndex++;\n if (currentUtteranceIndex < currentUtterances.length && isPlaying) {\n trackTimeout(() => { if (isPlaying) speakNextUtterance(); }, 100);\n return;\n }\n }\n isPlaying = false;\n isPaused = false;\n removeHighlight();\n updateControlBarState();\n };\n\n speechSynthesis.speak(utterance);\n }\n\n function togglePause() {\n if (!isPlaying) return;\n\n if (isPaused) {\n speechSynthesis.resume();\n isPaused = false;\n } else {\n speechSynthesis.pause();\n isPaused = true;\n }\n updateControlBarState();\n }\n\n // ---------------------------------------------------------------------------\n // Highlight management\n // ---------------------------------------------------------------------------\n\n function applyHighlight(el: HTMLElement) {\n removeHighlight();\n el.classList.add(HIGHLIGHT_CLASS);\n highlightedEl = el;\n }\n\n function removeHighlight() {\n if (highlightedEl) {\n highlightedEl.classList.remove(HIGHLIGHT_CLASS);\n highlightedEl = null;\n }\n // Safety: remove from any stale elements\n document\n .querySelectorAll(`.${HIGHLIGHT_CLASS}`)\n .forEach((el) => el.classList.remove(HIGHLIGHT_CLASS));\n }\n\n // ---------------------------------------------------------------------------\n // Click-to-read handler\n // ---------------------------------------------------------------------------\n\n function handleClick(e: MouseEvent) {\n if (!enabled || !clickToReadActive) return;\n\n const target = e.target as HTMLElement;\n if (!target) return;\n\n // Ignore clicks on the control bar itself\n if (controlBar?.contains(target)) return;\n\n // Ignore clicks inside the Accessify widget (Shadow DOM host)\n const widgetRoot = document.getElementById('accessify-root');\n if (widgetRoot && (widgetRoot === target || widgetRoot.contains(target))) return;\n\n // Don't block clicks on interactive elements (links, buttons, inputs)\n const tag = target.tagName;\n if (tag === 'A' || tag === 'BUTTON' || tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA' ||\n target.closest('a, button, input, select, textarea, [role=\"button\"]')) {\n return;\n }\n\n e.preventDefault();\n\n // Walk up to find the most meaningful text container\n const textEl = findTextElement(target);\n if (!textEl) return;\n\n const text = extractTextContent(textEl);\n if (text.trim()) {\n speakText(text, textEl);\n }\n }\n\n function findTextElement(el: HTMLElement): HTMLElement | null {\n // If the element itself has substantial text, use it\n const directText = extractTextContent(el);\n if (directText.trim().length > 0) return el;\n\n // Walk up to find a parent with text\n let current: HTMLElement | null = el;\n while (current && current !== document.body) {\n const text = extractTextContent(current);\n if (text.trim().length > 0) return current;\n current = current.parentElement;\n }\n return null;\n }\n\n function extractTextContent(el: HTMLElement): string {\n // Use innerText rather than textContent to get rendered text with spacing\n return (el.innerText || el.textContent || '').trim();\n }\n\n // ---------------------------------------------------------------------------\n // Control bar UI\n // ---------------------------------------------------------------------------\n\n function createControlBar(): HTMLDivElement {\n const bar = document.createElement('div');\n bar.id = CONTROL_BAR_ID;\n bar.setAttribute('role', 'toolbar');\n bar.setAttribute('aria-label', 'Text-to-Speech controls');\n\n Object.assign(bar.style, {\n position: 'fixed',\n top: '0',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: '2147483646',\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n padding: '6px 14px',\n background: '#1a1a2e',\n color: '#f0f0f0',\n borderRadius: '0 0 10px 10px',\n boxShadow: '0 4px 16px rgba(0,0,0,0.3)',\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: '13px',\n lineHeight: '1',\n userSelect: 'none',\n transition: 'opacity 0.2s ease',\n });\n\n // Play/Pause button\n const playPauseBtn = document.createElement('button');\n playPauseBtn.className = 'accessify-tts-btn accessify-tts-play';\n playPauseBtn.setAttribute('aria-label', 'Play / Pause');\n playPauseBtn.title = 'Play / Pause';\n playPauseBtn.innerHTML = iconPlay();\n playPauseBtn.addEventListener('click', () => {\n if (isPlaying) {\n togglePause();\n }\n // If nothing is playing, the user should click an element to start\n });\n\n // Stop button\n const stopBtn = document.createElement('button');\n stopBtn.className = 'accessify-tts-btn accessify-tts-stop';\n stopBtn.setAttribute('aria-label', 'Stop');\n stopBtn.title = 'Stop';\n stopBtn.innerHTML = iconStop();\n stopBtn.addEventListener('click', () => {\n stopSpeech();\n });\n\n // Speed control\n const speedContainer = document.createElement('div');\n Object.assign(speedContainer.style, {\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n marginLeft: '4px',\n });\n\n const speedLabel = document.createElement('label');\n speedLabel.textContent = 'Speed';\n speedLabel.setAttribute('for', 'accessify-tts-speed');\n Object.assign(speedLabel.style, {\n fontSize: '11px',\n opacity: '0.8',\n whiteSpace: 'nowrap',\n });\n\n const speedSlider = document.createElement('input');\n speedSlider.type = 'range';\n speedSlider.id = 'accessify-tts-speed';\n speedSlider.min = '0.5';\n speedSlider.max = '2';\n speedSlider.step = '0.1';\n speedSlider.value = String(options.speed);\n speedSlider.setAttribute('aria-label', 'Speech speed');\n Object.assign(speedSlider.style, {\n width: '70px',\n accentColor: '#4ea8de',\n cursor: 'pointer',\n });\n\n const speedValue = document.createElement('span');\n speedValue.className = 'accessify-tts-speed-val';\n speedValue.textContent = `${options.speed.toFixed(1)}x`;\n Object.assign(speedValue.style, {\n fontSize: '11px',\n minWidth: '30px',\n textAlign: 'center',\n opacity: '0.8',\n });\n\n speedSlider.addEventListener('input', () => {\n const newSpeed = parseFloat(speedSlider.value);\n options.speed = newSpeed;\n speedValue.textContent = `${newSpeed.toFixed(1)}x`;\n savePreferences();\n\n // Update rate on any currently queued utterances\n for (const u of currentUtterances) {\n u.rate = newSpeed;\n }\n });\n\n speedContainer.appendChild(speedLabel);\n speedContainer.appendChild(speedSlider);\n speedContainer.appendChild(speedValue);\n\n // Separator\n const sep = document.createElement('div');\n Object.assign(sep.style, {\n width: '1px',\n height: '18px',\n background: 'rgba(255,255,255,0.2)',\n margin: '0 4px',\n });\n\n // Hint text\n const hint = document.createElement('span');\n hint.className = 'accessify-tts-hint';\n hint.textContent = 'Click any text to read it aloud';\n Object.assign(hint.style, {\n fontSize: '11px',\n opacity: '0.6',\n whiteSpace: 'nowrap',\n });\n\n // Screen reader note (visually hidden, available to AT)\n const srNote = document.createElement('span');\n Object.assign(srNote.style, {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0,0,0,0)',\n whiteSpace: 'nowrap',\n border: '0',\n });\n srNote.textContent =\n 'For full screen reader support, use NVDA, JAWS, or VoiceOver';\n\n bar.appendChild(playPauseBtn);\n bar.appendChild(stopBtn);\n bar.appendChild(sep);\n bar.appendChild(speedContainer);\n bar.appendChild(sep.cloneNode(true));\n bar.appendChild(hint);\n bar.appendChild(srNote);\n\n return bar;\n }\n\n function updateControlBarState() {\n if (!controlBar) return;\n\n const playBtn = controlBar.querySelector<HTMLButtonElement>(\n '.accessify-tts-play'\n );\n const hintEl = controlBar.querySelector<HTMLSpanElement>(\n '.accessify-tts-hint'\n );\n\n if (playBtn) {\n if (isPlaying && !isPaused) {\n playBtn.innerHTML = iconPause();\n playBtn.setAttribute('aria-label', 'Pause');\n playBtn.title = 'Pause';\n } else if (isPlaying && isPaused) {\n playBtn.innerHTML = iconPlay();\n playBtn.setAttribute('aria-label', 'Resume');\n playBtn.title = 'Resume';\n } else {\n playBtn.innerHTML = iconPlay();\n playBtn.setAttribute('aria-label', 'Play / Pause');\n playBtn.title = 'Play / Pause';\n }\n }\n\n if (hintEl) {\n if (isPlaying && !isPaused) {\n hintEl.textContent = 'Speaking...';\n } else if (isPaused) {\n hintEl.textContent = 'Paused';\n } else {\n hintEl.textContent = 'Click any text to read it aloud';\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Inline SVG icons for control buttons\n // ---------------------------------------------------------------------------\n\n function iconPlay(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><polygon points=\"6,3 20,12 6,21\"/></svg>';\n }\n\n function iconPause(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><rect x=\"5\" y=\"3\" width=\"4\" height=\"18\"/><rect x=\"15\" y=\"3\" width=\"4\" height=\"18\"/></svg>';\n }\n\n function iconStop(): string {\n return '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\" aria-hidden=\"true\"><rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/></svg>';\n }\n\n // ---------------------------------------------------------------------------\n // Styles (injected into <head>, not Shadow DOM)\n // ---------------------------------------------------------------------------\n\n function getStyles(): string {\n return `\n /* accessify TTS styles */\n .${HIGHLIGHT_CLASS} {\n background-color: rgba(78, 168, 222, 0.15) !important;\n outline: 2px solid rgba(78, 168, 222, 0.5) !important;\n outline-offset: 2px !important;\n border-radius: 3px !important;\n transition: background-color 0.2s ease, outline-color 0.2s ease !important;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n margin: 0;\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n background: rgba(255,255,255,0.08);\n color: #f0f0f0;\n cursor: pointer;\n transition: background 0.15s ease, border-color 0.15s ease;\n line-height: 1;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:hover {\n background: rgba(255,255,255,0.18);\n border-color: rgba(255,255,255,0.3);\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:focus-visible {\n outline: 2px solid #4ea8de;\n outline-offset: 1px;\n }\n\n #${CONTROL_BAR_ID} .accessify-tts-btn:active {\n background: rgba(255,255,255,0.25);\n }\n `;\n }\n\n function injectStyles() {\n let styleEl = document.getElementById(STYLE_ID);\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ID;\n document.head.appendChild(styleEl);\n }\n styleEl.textContent = getStyles();\n }\n\n function removeStyles() {\n const styleEl = document.getElementById(STYLE_ID);\n styleEl?.remove();\n }\n\n // ---------------------------------------------------------------------------\n // Preferences persistence\n // ---------------------------------------------------------------------------\n\n function loadPreferences() {\n const saved = localStorage.getItem(STORAGE_KEY);\n if (saved) {\n try {\n const parsed = JSON.parse(saved);\n options.speed = Math.min(2, Math.max(0.5, parsed.speed ?? DEFAULT_OPTIONS.speed));\n } catch {\n options = { ...DEFAULT_OPTIONS };\n }\n }\n }\n\n function savePreferences() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify({ speed: options.speed }));\n }\n\n // ---------------------------------------------------------------------------\n // Module lifecycle\n // ---------------------------------------------------------------------------\n\n function activate() {\n if (enabled) return;\n enabled = true;\n\n loadPreferences();\n detectedLang = detectPageLanguage();\n\n // Load voices async — don't block activation\n waitForVoices().then(() => refreshVoice());\n\n // Inject styles and control bar\n injectStyles();\n controlBar = createControlBar();\n document.documentElement.appendChild(controlBar);\n\n // Enable click-to-read\n clickToReadActive = true;\n document.addEventListener('click', handleClick, true);\n }\n\n function deactivate() {\n enabled = false;\n\n // Stop all speech\n stopSpeech();\n\n // Remove click handler\n document.removeEventListener('click', handleClick, true);\n clickToReadActive = false;\n\n // Remove control bar\n if (controlBar) {\n controlBar.remove();\n controlBar = null;\n }\n\n // Remove highlights\n removeHighlight();\n\n // Remove styles\n removeStyles();\n\n selectedVoice = null;\n }\n\n // ---------------------------------------------------------------------------\n // FeatureModule interface\n // ---------------------------------------------------------------------------\n\n return {\n id: 'tts',\n name: () => 'Text-to-Speech',\n description: 'Click any text to hear it read aloud with adjustable speed',\n icon: 'tts',\n category: 'cognitive',\n activate,\n deactivate,\n getState: (): FeatureState => ({\n id: 'tts',\n enabled,\n value: { speed: options.speed },\n }),\n setState: (newState: Partial<TTSOptions>) => {\n if (newState.speed !== undefined) {\n options.speed = Math.min(2, Math.max(0.5, newState.speed));\n savePreferences();\n\n // Update the slider in the control bar if present\n const slider = document.getElementById(\n 'accessify-tts-speed'\n ) as HTMLInputElement | null;\n if (slider) {\n slider.value = String(options.speed);\n }\n const valDisplay = controlBar?.querySelector<HTMLSpanElement>(\n '.accessify-tts-speed-val'\n );\n if (valDisplay) {\n valDisplay.textContent = `${options.speed.toFixed(1)}x`;\n }\n }\n },\n };\n}\n"],"names":[],"mappings":"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"}