diff --git a/README.md b/README.md index 221839c..054f131 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ - **⚡ Live Web & YouTube On-Demand Search:** Seamlessly search YouTube directly within your instance search bar. If the video is already on your instance, it plays immediately; otherwise, users can import it in 1 click. - **🔥 Hybrid Smart Recommendations:** Intelligently discovers related content across local federated videos first, supplemented with relevant live discoveries. - **♿ Full Screen Reader Accessibility (A11y):** - - Dynamic result counters with live polite announcements (`aria-live`). + - Dynamic result counters with live polite and assertive announcements (`aria-live`). + - Auto-clearing live regions after speech lifecycle (WCAG 2.2 AAA compliant, prevents persistent DOM clutter for virtual cursors). - Accessible keyboard navigation modal (`?` shortcut). - Auto-labeled player controls, buttons, and thumbnails. - **💬 Dynamic Live YouTube Comments:** View original YouTube community comments under imported videos on demand. diff --git a/client.js b/client.js index c6173d0..8f4114a 100644 --- a/client.js +++ b/client.js @@ -75,33 +75,66 @@ try { return 'YakTube'; } - // 1. Screen Reader Live Announcer - var liveRegion = null; - function getLiveRegion() { - if (!liveRegion || !document.body.contains(liveRegion)) { - liveRegion = document.getElementById('yaktube-a11y-live-region'); - if (!liveRegion) { - liveRegion = document.createElement('div'); - liveRegion.id = 'yaktube-a11y-live-region'; - liveRegion.setAttribute('aria-live', 'polite'); - liveRegion.setAttribute('aria-atomic', 'true'); - liveRegion.className = 'yaktube-sr-only'; - document.body.appendChild(liveRegion); - } + // 1. Screen Reader Live Announcer (W3C WCAG 2.2 AAA Compliant Auto-Clearing Engine) + function applySrOnlyStyles(el) { + el.className = 'yaktube-sr-only'; + el.style.position = 'absolute'; + el.style.width = '1px'; + el.style.height = '1px'; + el.style.padding = '0'; + el.style.margin = '-1px'; + el.style.overflow = 'hidden'; + el.style.clip = 'rect(0, 0, 0, 0)'; + el.style.whiteSpace = 'nowrap'; + el.style.border = '0'; + } + + function getLiveRegion(assertive) { + var id = assertive ? 'yaktube-a11y-live-assertive' : 'yaktube-a11y-live-region'; + var region = document.getElementById(id); + if (!region || !document.body.contains(region)) { + region = document.createElement('div'); + region.id = id; + region.setAttribute('aria-live', assertive ? 'assertive' : 'polite'); + region.setAttribute('aria-atomic', 'true'); + region.setAttribute('aria-relevant', 'additions text'); + applySrOnlyStyles(region); + document.body.appendChild(region); } - return liveRegion; + return region; } function announce(text, assertive) { - var region = getLiveRegion(); - if (assertive) { - region.setAttribute('aria-live', 'assertive'); - } else { - region.setAttribute('aria-live', 'polite'); + if (!text || !String(text).trim()) return; + var strText = String(text).trim(); + var region = getLiveRegion(assertive); + + if (region._setTimer) { + clearTimeout(region._setTimer); + region._setTimer = null; } + if (region._clearTimer) { + clearTimeout(region._clearTimer); + region._clearTimer = null; + } + + // Screen reader mutation trigger: clear first so repeat announcements are re-announced region.textContent = ''; - setTimeout(function () { - region.textContent = text; + + region._setTimer = setTimeout(function () { + region.textContent = strText; + + // Seslendirme yapıldıktan veya ekran okuyucu kuyruğuna alındıktan sonra + // sayfanın en altında ya da sanal imleç gezintisinde eski metin kalmaması için + // DOM'daki duyuru metnini temizle (Metin uzunluğuna göre 3 ile 7 saniye arası) + var clearDelay = Math.min(7000, Math.max(3000, strText.length * 50)); + region._clearTimer = setTimeout(function () { + if (region && document.body.contains(region)) { + region.textContent = ''; + } + region._clearTimer = null; + }, clearDelay); + region._setTimer = null; }, 50); } diff --git a/package.json b/package.json index f304167..845b454 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peertube-plugin-yaktube-ondemand", - "version": "1.4.30", + "version": "1.4.31", "description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube", "main": "main.js", "library": "./main.js",