diff --git a/package.json b/package.json index 3b61461..7701cf2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ -{ - "name": "accessibility-widgets", +{ + "name": "@invisi/accessibility-widgets", "version": "2.0.14", "description": "A comprehensive, lightweight accessibility widget that enhances web accessibility for all users. Provides multiple accessibility features including screen reader support, voice control, high contrast mode, and more.", "main": "widget.js", @@ -32,26 +32,25 @@ "assistive-technology" ], "author": { - "name": "sinanisler", - "email": "sinan@sinan.im", - "url": "https://sinan.im" + "name": "Surya Handika Putratama", + "email": "ubunteroz@gmail.com" }, - "license": "GPL", + "license": "GPL-3.0-or-later", "repository": { "type": "git", - "url": "git+https://github.com/sinanisler/accessibility-widgets.git" + "url": "git+https://git.invisi.co.id/invisi/accessibility-widgets" }, "bugs": { - "url": "https://github.com/sinanisler/accessibility-widgets/issues" + "url": "https://git.invisi.co.id/invisi/accessibility-widgets/issues" }, - "homepage": "https://sinan.im", + "homepage": "https://git.invisi.co.id/invisi/accessibility-widgets", "files": [ "widget.js", "README.md", "LICENSE" ], "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "browserslist": [ "> 1%", @@ -59,9 +58,6 @@ "not dead", "not ie 11" ], - "devDependencies": {}, - "dependencies": {}, - "peerDependencies": {}, "funding": { "type": "github", "url": "https://github.com/sponsors/sinanisler" @@ -69,4 +65,4 @@ "jsdelivr": "widget.js", "unpkg": "widget.js", "cdn": "widget.js" -} +} \ No newline at end of file diff --git a/widget.js b/widget.js index c6f23b6..8453d4e 100644 --- a/widget.js +++ b/widget.js @@ -1154,7 +1154,10 @@ const savedLanguage = localStorage.getItem('accessibilityWidgetLanguage'); if (savedLanguage && TRANSLATIONS[savedLanguage]) { currentLanguage = savedLanguage; } else { - currentLanguage = detectBrowserLanguage(); + // Honor configured default language (window.ACCESSIBILITY_WIDGET_CONFIG.defaultLanguage) + // over browser detection when no saved preference exists. + var configDefault = window?.ACCESSIBILITY_WIDGET_CONFIG?.defaultLanguage; + currentLanguage = (configDefault && TRANSLATIONS[configDefault]) ? configDefault : detectBrowserLanguage(); localStorage.setItem('accessibilityWidgetLanguage', currentLanguage); } @@ -1234,6 +1237,9 @@ const DEFAULT_WIDGET_CONFIG = { hoverScale: '1.05' }, + // Applied on first visit when there is no saved preference; overrides browser-language detection. + defaultLanguage: null, + // Language/Text Configuration lang: { accessibilityMenu: 'Accessibility Menu', @@ -1390,7 +1396,7 @@ const DEFAULT_WIDGET_CONFIG = { colorFilter: ['renk filtresi', 'renk körü', 'filtre'], screenReader: ['ekran okuyucu', 'sesli oku', 'ses okuyucu'], voiceControl: ['sesli komut', 'sesli kontrol', 'sesli komutlar'], - resetAll: ['hepsini sıfırla', 'tümünü sıfırla', 'hepsini temizle', 'ayarları sıfırla','sıfırla'] + resetAll: ['hepsini sıfırla', 'tümünü sıfırla', 'hepsini temizle', 'ayarları sıfırla', 'sıfırla'] }, ar: { showMenu: ['إظهار القائمة', 'فتح القائمة', 'قائمة إمكانية الوصول', 'قائمة الوصول'], @@ -2312,10 +2318,10 @@ const pageStyles = ` /* Dyslexia Font */ .snn-dyslexia-font { - font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important; + font-family: 'OpenDyslexic', 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important; } .snn-dyslexia-font * { - font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important; + font-family: 'OpenDyslexic', 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important; } /* Line Height - 3 Options */ @@ -3282,6 +3288,48 @@ function handleLineHeight() { // ACCESSIBILITY FEATURES // =========================================== +// Resolve the BCP-47 speech-synthesis locale for the active UI language. +function getSpeechLang() { + switch (currentLanguage) { + case 'de': return 'de-DE'; + case 'es': return 'es-ES'; + case 'it': return 'it-IT'; + case 'fr': return 'fr-FR'; + case 'ru': return 'ru-RU'; + case 'tr': return 'tr-TR'; + case 'ar': return 'ar-SA'; + case 'hi': return 'hi-IN'; + case 'zh-cn': return 'zh-CN'; + case 'jp': return 'ja-JP'; + case 'pt': return 'pt-PT'; + case 'bn': return 'bn-IN'; + case 'ko': return 'ko-KR'; + case 'vi': return 'vi-VN'; + case 'id': return 'id-ID'; + case 'th': return 'th-TH'; + case 'pl': return 'pl-PL'; + case 'nl': return 'nl-NL'; + case 'el': return 'el-GR'; + case 'sv': return 'sv-SE'; + case 'no': return 'no-NO'; + case 'da': return 'da-DK'; + case 'fi': return 'fi-FI'; + case 'cs': return 'cs-CZ'; + case 'hu': return 'hu-HU'; + case 'ro': return 'ro-RO'; + case 'he': return 'he-IL'; + case 'fa': return 'fa-IR'; + case 'ur': return 'ur-PK'; + case 'pa': return 'pa-IN'; + case 'mr': return 'mr-IN'; + case 'te': return 'te-IN'; + case 'ta': return 'ta-IN'; + case 'ms': return 'ms-MY'; + case 'tl': return 'fil-PH'; + default: return 'en-US'; + } +} + // Screen reader functionality const screenReader = { active: localStorage.getItem('screenReader') === 'true', @@ -3289,52 +3337,27 @@ const screenReader = { handleFocus: function (event) { if (screenReader.active && screenReader.isSupported) { try { - const content = event.target.innerText || event.target.alt || event.target.title || ''; + // WAI-ARIA accessible name computation order: aria-labelledby -> aria-label -> contents -> alt -> title + var target = event.target; + var labelledBy = target.getAttribute && target.getAttribute('aria-labelledby'); + var labelledByText = ''; + if (labelledBy) { + labelledByText = labelledBy.split(/\s+/).map(function (id) { + var el = document.getElementById(id); + return el ? (el.innerText || el.textContent) : ''; + }).join(' ').trim(); + } + const content = labelledByText + || (target.getAttribute && target.getAttribute('aria-label')) + || target.innerText + || target.alt + || target.title + || ''; if (content.trim() !== '') { window.speechSynthesis.cancel(); const speech = new SpeechSynthesisUtterance(content); - // Set language based on current interface language - let speechLang = 'en-US'; // default - switch (currentLanguage) { - case 'de': speechLang = 'de-DE'; break; - case 'es': speechLang = 'es-ES'; break; - case 'it': speechLang = 'it-IT'; break; - case 'fr': speechLang = 'fr-FR'; break; - case 'ru': speechLang = 'ru-RU'; break; - case 'tr': speechLang = 'tr-TR'; break; - case 'ar': speechLang = 'ar-SA'; break; - case 'hi': speechLang = 'hi-IN'; break; - case 'zh-cn': speechLang = 'zh-CN'; break; - case 'jp': speechLang = 'ja-JP'; break; - case 'pt': speechLang = 'pt-PT'; break; - case 'bn': speechLang = 'bn-IN'; break; - case 'ko': speechLang = 'ko-KR'; break; - case 'vi': speechLang = 'vi-VN'; break; - case 'id': speechLang = 'id-ID'; break; - case 'th': speechLang = 'th-TH'; break; - case 'pl': speechLang = 'pl-PL'; break; - case 'nl': speechLang = 'nl-NL'; break; - case 'el': speechLang = 'el-GR'; break; - case 'sv': speechLang = 'sv-SE'; break; - case 'no': speechLang = 'no-NO'; break; - case 'da': speechLang = 'da-DK'; break; - case 'fi': speechLang = 'fi-FI'; break; - case 'cs': speechLang = 'cs-CZ'; break; - case 'hu': speechLang = 'hu-HU'; break; - case 'ro': speechLang = 'ro-RO'; break; - case 'he': speechLang = 'he-IL'; break; - case 'fa': speechLang = 'fa-IR'; break; - case 'ur': speechLang = 'ur-PK'; break; - case 'pa': speechLang = 'pa-IN'; break; - case 'mr': speechLang = 'mr-IN'; break; - case 'te': speechLang = 'te-IN'; break; - case 'ta': speechLang = 'ta-IN'; break; - case 'ms': speechLang = 'ms-MY'; break; - case 'tl': speechLang = 'fil-PH'; break; - default: speechLang = 'en-US'; - } - speech.lang = speechLang; + speech.lang = getSpeechLang(); speech.onerror = function (event) { console.warn('Speech synthesis error:', event.error); @@ -3356,21 +3379,7 @@ const screenReader = { localStorage.setItem('screenReader', isActive); try { - // Set language based on current interface language - let speechLang = 'en-US'; // default - switch (currentLanguage) { - case 'de': speechLang = 'de-DE'; break; - case 'es': speechLang = 'es-ES'; break; - case 'it': speechLang = 'it-IT'; break; - case 'fr': speechLang = 'fr-FR'; break; - case 'ru': speechLang = 'ru-RU'; break; - case 'tr': speechLang = 'tr-TR'; break; - case 'ar': speechLang = 'ar-SA'; break; - case 'hi': speechLang = 'hi-IN'; break; - case 'zh-cn': speechLang = 'zh-CN'; break; - case 'jp': speechLang = 'ja-JP'; break; - default: speechLang = 'en-US'; - } + let speechLang = getSpeechLang(); if (isActive) { document.addEventListener('focusin', screenReader.handleFocus);