From a5c23f1773ca10d1abd0ceb0adcdf99f462e5aec Mon Sep 17 00:00:00 2001 From: sinanisler Date: Mon, 15 Jun 2026 21:48:04 +0300 Subject: [PATCH] feat: add screen reader announcements, improve translation fallback, and fix voice recognition restart --- widget.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/widget.js b/widget.js index b7984fd..c6f23b6 100644 --- a/widget.js +++ b/widget.js @@ -1133,7 +1133,20 @@ function setLanguage(lang) { } function getTranslation(key) { - return TRANSLATIONS[currentLanguage][key] || TRANSLATIONS['en'][key] || key; + // Check current language translations first + if (TRANSLATIONS[currentLanguage] && TRANSLATIONS[currentLanguage][key]) { + return TRANSLATIONS[currentLanguage][key]; + } + // Fallback to user-configured lang overrides + if (WIDGET_CONFIG.lang && WIDGET_CONFIG.lang[key]) { + return WIDGET_CONFIG.lang[key]; + } + // Fallback to English translations + if (TRANSLATIONS['en'] && TRANSLATIONS['en'][key]) { + return TRANSLATIONS['en'][key]; + } + // Last resort: return the key itself + return key; } // Initialize language from localStorage or detect from browser @@ -2211,6 +2224,19 @@ const widgetStyles = ` word-spacing: 2px !important; text-align: left; } + + /* Screen-reader-only utility for live announcements */ + .snn-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } `; // Page accessibility styles (will go in main document - these affect the page, NOT the widget) @@ -2470,6 +2496,14 @@ function createShadowContainer() { styleElement.textContent = widgetStyles; shadowRoot.appendChild(styleElement); + // Create aria-live region for screen reader announcements + const liveRegion = document.createElement('div'); + liveRegion.id = 'snn-live-region'; + liveRegion.setAttribute('aria-live', 'polite'); + liveRegion.setAttribute('aria-atomic', 'true'); + liveRegion.classList.add('snn-sr-only'); + shadowRoot.appendChild(liveRegion); + return shadowRoot; } @@ -2477,6 +2511,19 @@ function createShadowContainer() { // CORE UTILITY FUNCTIONS // =========================================== +// Announce a change to screen reader users via the live region +function announceChange(message) { + if (!shadowRoot) return; + const liveRegion = shadowRoot.getElementById('snn-live-region'); + if (!liveRegion) return; + // Clear then set to ensure announcement even for repeated messages + liveRegion.textContent = ''; + // Use requestAnimationFrame to ensure DOM update before setting new text + requestAnimationFrame(() => { + liveRegion.textContent = message; + }); +} + // Cache for DOM elements to improve performance const domCache = { get body() { @@ -2800,6 +2847,9 @@ function resetAccessibilitySettings() { const steps = button.querySelectorAll('.snn-option-step'); steps.forEach(step => step.classList.remove('active')); }); + + // Announce reset to screen reader users + announceChange(getTranslation('resetAllSettings')); } // Create toggle buttons for accessibility options @@ -2877,6 +2927,9 @@ function createToggleButton( targetElement.classList.remove(className); } } + + // Announce change to screen reader users + announceChange(buttonText); } return button; @@ -2916,6 +2969,7 @@ function createActionButton(buttonText, actionFunction, iconSVG, optionsConfig = const result = actionFunction(); if (result) { updateActionButtonStatus(button, optionId, optionsConfig); + announceChange(buttonText + ': ' + result); } }); @@ -2925,6 +2979,7 @@ function createActionButton(buttonText, actionFunction, iconSVG, optionsConfig = const result = actionFunction(); if (result) { updateActionButtonStatus(button, optionId, optionsConfig); + announceChange(buttonText + ': ' + result); } } }); @@ -3383,6 +3438,11 @@ const voiceControl = { } try { + // Abort any existing recognition instance before creating a new one + if (voiceControl.recognition) { + try { voiceControl.recognition.abort(); } catch (e) { /* ignore */ } + voiceControl.recognition = null; + } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; voiceControl.recognition = new SpeechRecognition(); voiceControl.recognition.interimResults = false;