accessify-widget 0.3.99 → 0.3.100
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.
- package/dist/accessify.min.js +1 -1
- package/dist/accessify.min.js.map +1 -1
- package/dist/accessify.mjs +1 -1
- package/dist/{index-CUmLkmwP.js → index-BCnhsRbl.js} +4 -4
- package/dist/{index-CUmLkmwP.js.map → index-BCnhsRbl.js.map} +1 -1
- package/dist/{keyboard-nav--ZFLwRfu.js → keyboard-nav-yfjbY6i4.js} +2 -2
- package/dist/{keyboard-nav--ZFLwRfu.js.map → keyboard-nav-yfjbY6i4.js.map} +1 -1
- package/dist/{page-structure-C78Y3t4E.js → page-structure-sIAqugcR.js} +2 -2
- package/dist/{page-structure-C78Y3t4E.js.map → page-structure-sIAqugcR.js.map} +1 -1
- package/dist/{text-simplify-DUdShGEx.js → text-simplify-QxPVLWca.js} +65 -11
- package/dist/text-simplify-QxPVLWca.js.map +1 -0
- package/dist/widget.js +1 -1
- package/dist/widget.js.map +1 -1
- package/package.json +1 -1
- package/dist/text-simplify-DUdShGEx.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t, g as getCurrentWidgetLang } from "./index-
|
|
1
|
+
import { t, g as getCurrentWidgetLang } from "./index-BCnhsRbl.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
|
|
378
|
+
//# sourceMappingURL=keyboard-nav-yfjbY6i4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-nav--ZFLwRfu.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-yfjbY6i4.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-
|
|
1
|
+
import { t, g as getCurrentWidgetLang } from "./index-BCnhsRbl.js";
|
|
2
2
|
function createPageStructureModule() {
|
|
3
3
|
let enabled = false;
|
|
4
4
|
let panelEl = null;
|
|
@@ -240,4 +240,4 @@ function createPageStructureModule() {
|
|
|
240
240
|
export {
|
|
241
241
|
createPageStructureModule as default
|
|
242
242
|
};
|
|
243
|
-
//# sourceMappingURL=page-structure-
|
|
243
|
+
//# sourceMappingURL=page-structure-sIAqugcR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-structure-C78Y3t4E.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-width: calc(100vw - 32px); 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 { 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 { 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 { background: #2a2a2e; }\n #${PANEL_ID} .ps-close { color: #aaa; }\n #${PANEL_ID} .ps-close:hover { background: #2a2a2e; }\n }\n </style>\n <button class=\"ps-close\" aria-label=\"${t('widget.close', lang())}\">×</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, '&').replace(/</g, '<').replace(/>/g, '>');\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-sIAqugcR.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-width: calc(100vw - 32px); 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 { 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 { 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 { background: #2a2a2e; }\n #${PANEL_ID} .ps-close { color: #aaa; }\n #${PANEL_ID} .ps-close:hover { background: #2a2a2e; }\n }\n </style>\n <button class=\"ps-close\" aria-label=\"${t('widget.close', lang())}\">×</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, '&').replace(/</g, '<').replace(/>/g, '>');\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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"}
|
|
@@ -557,14 +557,22 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
557
557
|
savedParagraphs = savedParagraphs.filter((sp) => sp.el !== saved.el);
|
|
558
558
|
}
|
|
559
559
|
const manifestCache = /* @__PURE__ */ new Map();
|
|
560
|
-
|
|
560
|
+
const MANIFEST_FETCH_TIMEOUT_MS = 8e3;
|
|
561
|
+
async function fetchManifest(siteKey, proxyUrl, externalSignal) {
|
|
561
562
|
const cacheKey = `${siteKey}:${level}:${window.location.pathname}`;
|
|
562
563
|
if (manifestCache.has(cacheKey)) return manifestCache.get(cacheKey);
|
|
563
564
|
const base = proxyUrl || "https://accessify-api.accessify.workers.dev";
|
|
564
565
|
const pageUrl = encodeURIComponent(window.location.origin + window.location.pathname);
|
|
565
566
|
const url = `${base}/v1/manifest?siteKey=${siteKey}&url=${pageUrl}&feature=simplify&variant=${level}`;
|
|
567
|
+
const timeoutController = new AbortController();
|
|
568
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), MANIFEST_FETCH_TIMEOUT_MS);
|
|
569
|
+
const externalAbortHandler = () => timeoutController.abort();
|
|
570
|
+
if (externalSignal) {
|
|
571
|
+
if (externalSignal.aborted) timeoutController.abort();
|
|
572
|
+
else externalSignal.addEventListener("abort", externalAbortHandler);
|
|
573
|
+
}
|
|
566
574
|
try {
|
|
567
|
-
const res = await fetch(url, { cache: "no-cache" });
|
|
575
|
+
const res = await fetch(url, { cache: "no-cache", signal: timeoutController.signal });
|
|
568
576
|
if (!res.ok) {
|
|
569
577
|
manifestCache.set(cacheKey, null);
|
|
570
578
|
return null;
|
|
@@ -575,8 +583,10 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
575
583
|
manifestCache.set(cacheKey, result);
|
|
576
584
|
return result;
|
|
577
585
|
} catch {
|
|
578
|
-
manifestCache.set(cacheKey, null);
|
|
579
586
|
return null;
|
|
587
|
+
} finally {
|
|
588
|
+
clearTimeout(timeoutId);
|
|
589
|
+
if (externalSignal) externalSignal.removeEventListener("abort", externalAbortHandler);
|
|
580
590
|
}
|
|
581
591
|
}
|
|
582
592
|
function applyManifestBlocks(manifest, elements) {
|
|
@@ -835,7 +845,11 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
835
845
|
async function simplifyPage() {
|
|
836
846
|
const elements = getTextElements();
|
|
837
847
|
if (elements.length === 0) return;
|
|
838
|
-
abortController
|
|
848
|
+
abortController?.abort();
|
|
849
|
+
const runController = new AbortController();
|
|
850
|
+
abortController = runController;
|
|
851
|
+
const runSignal = runController.signal;
|
|
852
|
+
const isAborted = () => runSignal.aborted || !enabled;
|
|
839
853
|
savedParagraphs = [];
|
|
840
854
|
skippedBlocks = 0;
|
|
841
855
|
setupResizeObserver();
|
|
@@ -850,15 +864,23 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
850
864
|
if (diagRun) diagRun.elementsFound = elements.length;
|
|
851
865
|
if (siteKey) {
|
|
852
866
|
showProgress(0, elements.length);
|
|
853
|
-
const manifest = await fetchManifest(siteKey, proxyUrl);
|
|
867
|
+
const manifest = await fetchManifest(siteKey, proxyUrl, runSignal);
|
|
854
868
|
if (DEBUG) console.log(`[Accessify] Manifest: ${manifest?.blocks?.length ?? 0} blocks for ${elements.length} elements`);
|
|
855
869
|
if (diagRun) {
|
|
856
870
|
diagRun.manifestLoaded = !!manifest?.blocks?.length;
|
|
857
871
|
diagRun.manifestBlocks = manifest?.blocks?.length ?? 0;
|
|
858
872
|
diagRun.siteMode = currentSiteMode;
|
|
859
873
|
}
|
|
874
|
+
if (isAborted()) {
|
|
875
|
+
if (DIAG()) diagFinish();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
860
878
|
if (manifest?.blocks?.length) {
|
|
861
879
|
await clearClientCacheForPage();
|
|
880
|
+
if (isAborted()) {
|
|
881
|
+
if (DIAG()) diagFinish();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
862
884
|
const result = applyManifestBlocks(manifest, elements);
|
|
863
885
|
fromCache = result.applied.length;
|
|
864
886
|
appliedFromManifest = result.applied;
|
|
@@ -884,6 +906,7 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
884
906
|
{
|
|
885
907
|
const stillRemaining = [];
|
|
886
908
|
for (const el of remaining) {
|
|
909
|
+
if (isAborted()) return;
|
|
887
910
|
const text = el.dataset.accessifyOriginal || el.textContent?.trim() || "";
|
|
888
911
|
if (text.length < 20) {
|
|
889
912
|
stillRemaining.push(el);
|
|
@@ -891,6 +914,7 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
891
914
|
}
|
|
892
915
|
const blockHash = hashText(text);
|
|
893
916
|
const cached = await getClientCached(clientCacheKey(blockHash));
|
|
917
|
+
if (isAborted()) return;
|
|
894
918
|
if (cached) {
|
|
895
919
|
const success = safeReplace(el, cached, blockHash);
|
|
896
920
|
if (success) {
|
|
@@ -996,7 +1020,7 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
996
1020
|
let totalSimplified = fromCache;
|
|
997
1021
|
const isStale = (el) => staleBlocks.has(el);
|
|
998
1022
|
for (const el of remaining) {
|
|
999
|
-
if (
|
|
1023
|
+
if (isAborted()) break;
|
|
1000
1024
|
const text = el.dataset.accessifyOriginal || el.textContent?.trim() || "";
|
|
1001
1025
|
if (text.length < 20) {
|
|
1002
1026
|
clearLoading(el);
|
|
@@ -1015,7 +1039,7 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
1015
1039
|
}
|
|
1016
1040
|
try {
|
|
1017
1041
|
const simplified = await aiService.simplifyText(text, level, lang);
|
|
1018
|
-
if (
|
|
1042
|
+
if (isAborted()) return;
|
|
1019
1043
|
if (simplified && simplified !== text) {
|
|
1020
1044
|
const success = safeReplace(el, simplified, blockHash);
|
|
1021
1045
|
if (success) {
|
|
@@ -1053,9 +1077,10 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
1053
1077
|
}
|
|
1054
1078
|
if (err?.message?.includes("429")) {
|
|
1055
1079
|
await new Promise((r) => setTimeout(r, 8e3));
|
|
1080
|
+
if (isAborted()) return;
|
|
1056
1081
|
try {
|
|
1057
1082
|
const retry = await aiService.simplifyText(text, level, lang);
|
|
1058
|
-
if (
|
|
1083
|
+
if (isAborted()) return;
|
|
1059
1084
|
if (retry && retry !== text) {
|
|
1060
1085
|
const success = safeReplace(el, retry, blockHash);
|
|
1061
1086
|
if (success) {
|
|
@@ -1086,15 +1111,16 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
1086
1111
|
}
|
|
1087
1112
|
} finally {
|
|
1088
1113
|
clearLoading(el);
|
|
1089
|
-
if (!
|
|
1114
|
+
if (!isAborted()) {
|
|
1090
1115
|
await new Promise((r) => setTimeout(r, 600));
|
|
1091
1116
|
}
|
|
1092
1117
|
}
|
|
1118
|
+
if (isAborted()) break;
|
|
1093
1119
|
}
|
|
1094
1120
|
for (const el of remaining) {
|
|
1095
1121
|
clearLoading(el);
|
|
1096
1122
|
}
|
|
1097
|
-
if (!
|
|
1123
|
+
if (!isAborted()) {
|
|
1098
1124
|
showDone();
|
|
1099
1125
|
if (totalSimplified > 0 || fromCache > 0) {
|
|
1100
1126
|
showDisclaimer();
|
|
@@ -1105,6 +1131,34 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
1105
1131
|
function restorePage() {
|
|
1106
1132
|
disconnectObservers();
|
|
1107
1133
|
const restoreReason = abortController?.signal.aborted ? "abort(navigation/toggle)" : "toggle-off";
|
|
1134
|
+
const trackedEls = new Set(savedParagraphs.map((s) => s.el));
|
|
1135
|
+
document.querySelectorAll(`[${SIMPLIFIED_ATTR}]`).forEach((node) => {
|
|
1136
|
+
const el = node;
|
|
1137
|
+
if (trackedEls.has(el)) return;
|
|
1138
|
+
const originalText = el.dataset.accessifyOriginal;
|
|
1139
|
+
if (originalText && el.textContent !== originalText) {
|
|
1140
|
+
el.textContent = originalText;
|
|
1141
|
+
}
|
|
1142
|
+
el.removeAttribute(SIMPLIFIED_ATTR);
|
|
1143
|
+
el.removeAttribute(ORIGINAL_ATTR);
|
|
1144
|
+
el.removeAttribute(BLOCK_HASH_ATTR);
|
|
1145
|
+
el.removeAttribute("data-accessify-replacing");
|
|
1146
|
+
clearLoading(el);
|
|
1147
|
+
if (el.dataset.accessifyOrigTransform) {
|
|
1148
|
+
el.style.textTransform = el.dataset.accessifyOrigTransform;
|
|
1149
|
+
delete el.dataset.accessifyOrigTransform;
|
|
1150
|
+
}
|
|
1151
|
+
if (DIAG() && diagRun) {
|
|
1152
|
+
diagRun.restoreLog.push({
|
|
1153
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1154
|
+
hash: el.getAttribute(BLOCK_HASH_ATTR) || "orphan",
|
|
1155
|
+
text: originalText?.slice(0, 60) || "",
|
|
1156
|
+
textNodeRestore: false,
|
|
1157
|
+
innerHtmlFallback: false,
|
|
1158
|
+
reason: `${restoreReason}+orphan-sweep`
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1108
1162
|
for (const { el, originalHtml, savedTextNodes, ancestorPatches } of savedParagraphs) {
|
|
1109
1163
|
const blockHash = el.getAttribute(BLOCK_HASH_ATTR) || "unknown";
|
|
1110
1164
|
let textNodeRestore = false;
|
|
@@ -1253,4 +1307,4 @@ function createTextSimplifyModule(aiService, lang = "de", options) {
|
|
|
1253
1307
|
export {
|
|
1254
1308
|
createTextSimplifyModule as default
|
|
1255
1309
|
};
|
|
1256
|
-
//# sourceMappingURL=text-simplify-
|
|
1310
|
+
//# sourceMappingURL=text-simplify-QxPVLWca.js.map
|