feat: add screen reader announcements, improve translation fallback, and fix voice recognition restart
This commit is contained in:
@@ -1133,7 +1133,20 @@ function setLanguage(lang) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTranslation(key) {
|
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
|
// Initialize language from localStorage or detect from browser
|
||||||
@@ -2211,6 +2224,19 @@ const widgetStyles = `
|
|||||||
word-spacing: 2px !important;
|
word-spacing: 2px !important;
|
||||||
text-align: left;
|
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)
|
// Page accessibility styles (will go in main document - these affect the page, NOT the widget)
|
||||||
@@ -2470,6 +2496,14 @@ function createShadowContainer() {
|
|||||||
styleElement.textContent = widgetStyles;
|
styleElement.textContent = widgetStyles;
|
||||||
shadowRoot.appendChild(styleElement);
|
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;
|
return shadowRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2477,6 +2511,19 @@ function createShadowContainer() {
|
|||||||
// CORE UTILITY FUNCTIONS
|
// 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
|
// Cache for DOM elements to improve performance
|
||||||
const domCache = {
|
const domCache = {
|
||||||
get body() {
|
get body() {
|
||||||
@@ -2800,6 +2847,9 @@ function resetAccessibilitySettings() {
|
|||||||
const steps = button.querySelectorAll('.snn-option-step');
|
const steps = button.querySelectorAll('.snn-option-step');
|
||||||
steps.forEach(step => step.classList.remove('active'));
|
steps.forEach(step => step.classList.remove('active'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Announce reset to screen reader users
|
||||||
|
announceChange(getTranslation('resetAllSettings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create toggle buttons for accessibility options
|
// Create toggle buttons for accessibility options
|
||||||
@@ -2877,6 +2927,9 @@ function createToggleButton(
|
|||||||
targetElement.classList.remove(className);
|
targetElement.classList.remove(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Announce change to screen reader users
|
||||||
|
announceChange(buttonText);
|
||||||
}
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
@@ -2916,6 +2969,7 @@ function createActionButton(buttonText, actionFunction, iconSVG, optionsConfig =
|
|||||||
const result = actionFunction();
|
const result = actionFunction();
|
||||||
if (result) {
|
if (result) {
|
||||||
updateActionButtonStatus(button, optionId, optionsConfig);
|
updateActionButtonStatus(button, optionId, optionsConfig);
|
||||||
|
announceChange(buttonText + ': ' + result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2925,6 +2979,7 @@ function createActionButton(buttonText, actionFunction, iconSVG, optionsConfig =
|
|||||||
const result = actionFunction();
|
const result = actionFunction();
|
||||||
if (result) {
|
if (result) {
|
||||||
updateActionButtonStatus(button, optionId, optionsConfig);
|
updateActionButtonStatus(button, optionId, optionsConfig);
|
||||||
|
announceChange(buttonText + ': ' + result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3383,6 +3438,11 @@ const voiceControl = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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;
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
voiceControl.recognition = new SpeechRecognition();
|
voiceControl.recognition = new SpeechRecognition();
|
||||||
voiceControl.recognition.interimResults = false;
|
voiceControl.recognition.interimResults = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user