diff --git a/widget.js b/widget.js
index 83cbb5f..f8f48b3 100644
--- a/widget.js
+++ b/widget.js
@@ -21,14 +21,14 @@ const DEFAULT_WIDGET_CONFIG = {
enableBiggerCursor: true,
enableLineHeight: true,
enableTextAlign: true,
-
+
// Advanced Features
enableScreenReader: true,
enableVoiceControl: true,
enableReducedMotion: true,
enableFontSelection: true,
enableColorFilter: true,
-
+
// Widget Styling
widgetWidth: '440px',
widgetPosition: {
@@ -37,7 +37,7 @@ const DEFAULT_WIDGET_CONFIG = {
left: '20px',
bottom: '20px'
},
-
+
// Colors
colors: {
primary: '#000000',
@@ -51,7 +51,7 @@ const DEFAULT_WIDGET_CONFIG = {
focus: '#ff6b35',
focusGlow: 'rgba(255, 107, 53, 0.3)'
},
-
+
// Button styling
button: {
size: '55px',
@@ -59,7 +59,7 @@ const DEFAULT_WIDGET_CONFIG = {
iconSize: '40px',
shadow: '0 4px 8px rgba(0, 0, 0, 0.2)'
},
-
+
// Menu styling
menu: {
headerHeight: '55px',
@@ -71,7 +71,7 @@ const DEFAULT_WIDGET_CONFIG = {
titleFontSize: '22px',
closeButtonSize: '44px'
},
-
+
// Typography
typography: {
fontFamily: 'Arial, sans-serif',
@@ -80,13 +80,13 @@ const DEFAULT_WIDGET_CONFIG = {
titleFontWeight: '500',
lineHeight: '1'
},
-
+
// Animation
animation: {
transition: '0.2s',
hoverScale: '1.05'
},
-
+
// Language/Text Configuration
lang: {
accessibilityMenu: 'Accessibility Menu',
@@ -144,9 +144,9 @@ const DEFAULT_WIDGET_CONFIG = {
// Function to deep merge user configuration with defaults
function mergeConfigs(defaultConfig, userConfig) {
const result = { ...defaultConfig };
-
+
if (!userConfig) return result;
-
+
for (const key in userConfig) {
if (userConfig.hasOwnProperty(key)) {
if (typeof userConfig[key] === 'object' && userConfig[key] !== null && !Array.isArray(userConfig[key])) {
@@ -156,7 +156,7 @@ function mergeConfigs(defaultConfig, userConfig) {
}
}
}
-
+
return result;
}
@@ -491,7 +491,7 @@ function injectStyles() {
const styleSheet = document.createElement('style');
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
-
+
// Add SVG color blindness filters
const svgFilters = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgFilters.style.position = 'absolute';
@@ -523,7 +523,7 @@ const domCache = {
documentElement: document.documentElement,
images: null,
lastImageUpdate: 0,
- getImages: function() {
+ getImages: function () {
const now = Date.now();
if (!this.images || now - this.lastImageUpdate > 5000) {
this.images = document.querySelectorAll('img');
@@ -659,7 +659,7 @@ function createAccessibilityButton() {
button.addEventListener('click', function () {
toggleMenu();
});
-
+
button.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
@@ -769,17 +769,17 @@ function createToggleButton(
button.addEventListener('click', function () {
handleToggle();
});
-
+
button.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleToggle();
}
});
-
+
function handleToggle() {
const newIsActive = localStorage.getItem(localStorageKey) !== 'true';
-
+
// If there's a custom toggle function, call it and check if it succeeded
if (customToggleFunction) {
const success = customToggleFunction(newIsActive);
@@ -788,7 +788,7 @@ function createToggleButton(
return;
}
}
-
+
localStorage.setItem(localStorageKey, newIsActive);
button.setAttribute('aria-pressed', newIsActive);
@@ -814,10 +814,10 @@ function createActionButton(buttonText, actionFunction, iconSVG) {
button.innerHTML = `${iconSVG}${buttonText}: ${WIDGET_CONFIG.lang.default}`;
button.setAttribute('aria-label', buttonText);
button.classList.add('snn-accessibility-option');
-
+
// Update initial status
updateActionButtonStatus(button, buttonText, actionFunction);
-
+
button.addEventListener('click', function () {
const result = actionFunction();
if (result) {
@@ -825,7 +825,7 @@ function createActionButton(buttonText, actionFunction, iconSVG) {
statusSpan.textContent = result;
}
});
-
+
button.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
@@ -836,14 +836,14 @@ function createActionButton(buttonText, actionFunction, iconSVG) {
}
}
});
-
+
return button;
}
// Update action button status on page load
function updateActionButtonStatus(button, buttonText, actionFunction) {
const statusSpan = button.querySelector('.snn-status');
-
+
if (buttonText.includes('Font')) {
const currentFont = localStorage.getItem('fontSelection');
statusSpan.textContent = currentFont ? currentFont.charAt(0).toUpperCase() + currentFont.slice(1) : WIDGET_CONFIG.lang.default;
@@ -880,11 +880,11 @@ function handleFontSelection() {
const currentFont = localStorage.getItem('fontSelection') || 'default';
const currentIndex = fonts.indexOf(currentFont);
const nextIndex = (currentIndex + 1) % (fonts.length + 1); // +1 for default
-
+
// Remove all font classes in one operation
const fontClasses = ['snn-font-arial', 'snn-font-times', 'snn-font-verdana'];
domCache.body.classList.remove(...fontClasses);
-
+
if (nextIndex === fonts.length) {
// Default font
localStorage.removeItem('fontSelection');
@@ -903,11 +903,11 @@ function handleColorFilter() {
const currentFilter = localStorage.getItem('colorFilter') || 'none';
const currentIndex = filters.indexOf(currentFilter);
const nextIndex = (currentIndex + 1) % (filters.length + 1); // +1 for none
-
+
// Remove all filter classes in one operation
const filterClasses = ['snn-filter-protanopia', 'snn-filter-deuteranopia', 'snn-filter-tritanopia', 'snn-filter-grayscale'];
domCache.documentElement.classList.remove(...filterClasses);
-
+
if (nextIndex === filters.length) {
// No filter
localStorage.removeItem('colorFilter');
@@ -926,11 +926,11 @@ function handleTextAlign() {
const currentAlign = localStorage.getItem('textAlign') || 'none';
const currentIndex = alignments.indexOf(currentAlign);
const nextIndex = (currentIndex + 1) % (alignments.length + 1); // +1 for none
-
+
// Remove all alignment classes
const alignClasses = ['snn-text-align-left', 'snn-text-align-center', 'snn-text-align-right'];
domCache.body.classList.remove(...alignClasses);
-
+
if (nextIndex === alignments.length) {
// Default alignment
localStorage.removeItem('textAlign');
@@ -949,11 +949,11 @@ function handleBiggerText() {
const currentSize = localStorage.getItem('biggerText') || 'none';
const currentIndex = textSizes.indexOf(currentSize);
const nextIndex = (currentIndex + 1) % (textSizes.length + 1); // +1 for none
-
+
// Remove all text size classes
const textClasses = ['snn-bigger-text-medium', 'snn-bigger-text-large', 'snn-bigger-text-xlarge'];
domCache.body.classList.remove(...textClasses);
-
+
if (nextIndex === textSizes.length) {
// Default text size
localStorage.removeItem('biggerText');
@@ -972,11 +972,11 @@ function handleHighContrast() {
const currentContrast = localStorage.getItem('highContrast') || 'none';
const currentIndex = contrastLevels.indexOf(currentContrast);
const nextIndex = (currentIndex + 1) % (contrastLevels.length + 1); // +1 for none
-
+
// Remove all contrast classes
const contrastClasses = ['snn-high-contrast-medium', 'snn-high-contrast-high', 'snn-high-contrast-ultra'];
domCache.documentElement.classList.remove(...contrastClasses);
-
+
if (nextIndex === contrastLevels.length) {
// Default contrast
localStorage.removeItem('highContrast');
@@ -1005,7 +1005,7 @@ const screenReader = {
window.speechSynthesis.cancel();
const speech = new SpeechSynthesisUtterance(content);
speech.lang = 'en-US';
- speech.onerror = function(event) {
+ speech.onerror = function (event) {
console.warn('Speech synthesis error:', event.error);
};
window.speechSynthesis.speak(speech);
@@ -1020,16 +1020,16 @@ const screenReader = {
console.warn(`Speech synthesis ${WIDGET_CONFIG.lang.notSupportedBrowser}`);
return false;
}
-
+
screenReader.active = isActive;
localStorage.setItem('screenReader', isActive);
-
+
try {
if (isActive) {
document.addEventListener('focusin', screenReader.handleFocus);
const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOn);
feedbackSpeech.lang = 'en-US';
- feedbackSpeech.onerror = function(event) {
+ feedbackSpeech.onerror = function (event) {
console.warn('Speech synthesis feedback error:', event.error);
};
window.speechSynthesis.speak(feedbackSpeech);
@@ -1038,7 +1038,7 @@ const screenReader = {
window.speechSynthesis.cancel();
const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOff);
feedbackSpeech.lang = 'en-US';
- feedbackSpeech.onerror = function(event) {
+ feedbackSpeech.onerror = function (event) {
console.warn('Speech synthesis feedback error:', event.error);
};
window.speechSynthesis.speak(feedbackSpeech);
@@ -1047,7 +1047,7 @@ const screenReader = {
console.warn('Screen reader toggle error:', error);
return false;
}
-
+
return true;
},
};
@@ -1064,10 +1064,10 @@ const voiceControl = {
console.warn(`Speech Recognition API ${WIDGET_CONFIG.lang.notSupportedBrowser}`);
return false;
}
-
+
voiceControl.isActive = isActive;
localStorage.setItem('voiceControl', isActive);
-
+
try {
if (isActive) {
voiceControl.startListening();
@@ -1082,14 +1082,14 @@ const voiceControl = {
console.warn('Voice control toggle error:', error);
return false;
}
-
+
return true;
},
startListening: function () {
if (!voiceControl.isSupported) {
return;
}
-
+
try {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
voiceControl.recognition = new SpeechRecognition();
@@ -1140,7 +1140,7 @@ const voiceControl = {
},
handleVoiceCommand: function (command) {
console.log(`Received command: ${command}`);
-
+
try {
// Check for show menu commands
if (WIDGET_CONFIG.voiceCommands.showMenu.includes(command)) {
@@ -1159,7 +1159,7 @@ const voiceControl = {
// Build dynamic command map based on configuration
let localStorageKey = null;
-
+
// Check each command group
if (WIDGET_CONFIG.voiceCommands.highContrast.includes(command)) {
localStorageKey = 'highContrast';
@@ -1230,7 +1230,7 @@ function createAccessibilityMenu() {
closeButton.addEventListener('click', function () {
closeMenu();
});
-
+
closeButton.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
@@ -1310,7 +1310,7 @@ function createAccessibilityMenu() {
enabled: WIDGET_CONFIG.enableReducedMotion,
},
];
-
+
// Add enabled toggle options to grid
options.forEach((option) => {
if (option.enabled) {
@@ -1326,33 +1326,33 @@ function createAccessibilityMenu() {
optionsGrid.appendChild(button);
}
});
-
+
// Add action buttons (font selection and color filters) to grid if enabled
if (WIDGET_CONFIG.enableFontSelection) {
const fontButton = createActionButton(WIDGET_CONFIG.lang.fontSelection, handleFontSelection, icons.fontSelection);
optionsGrid.appendChild(fontButton);
}
-
+
if (WIDGET_CONFIG.enableColorFilter) {
const colorButton = createActionButton(WIDGET_CONFIG.lang.colorFilter, handleColorFilter, icons.colorFilter);
optionsGrid.appendChild(colorButton);
}
-
+
if (WIDGET_CONFIG.enableTextAlign) {
const textAlignButton = createActionButton(WIDGET_CONFIG.lang.textAlign, handleTextAlign, icons.textAlign);
optionsGrid.appendChild(textAlignButton);
}
-
+
if (WIDGET_CONFIG.enableBiggerText) {
const biggerTextButton = createActionButton(WIDGET_CONFIG.lang.textSize, handleBiggerText, icons.biggerText);
optionsGrid.appendChild(biggerTextButton);
}
-
+
if (WIDGET_CONFIG.enableHighContrast) {
const highContrastButton = createActionButton(WIDGET_CONFIG.lang.highContrast, handleHighContrast, icons.highContrast);
optionsGrid.appendChild(highContrastButton);
}
-
+
// Add Screen Reader and Voice Command as the LAST two buttons
if (WIDGET_CONFIG.enableScreenReader) {
const screenReaderButton = createToggleButton(
@@ -1366,7 +1366,7 @@ function createAccessibilityMenu() {
);
optionsGrid.appendChild(screenReaderButton);
}
-
+
if (WIDGET_CONFIG.enableVoiceControl) {
const voiceControlButton = createToggleButton(
WIDGET_CONFIG.lang.voiceCommand,
@@ -1382,7 +1382,7 @@ function createAccessibilityMenu() {
// Add grid to content
content.appendChild(optionsGrid);
-
+
// Add content to menu
menu.appendChild(content);
@@ -1398,7 +1398,7 @@ const menuCache = {
menu: null,
button: null,
closeButton: null,
- init: function() {
+ init: function () {
this.menu = document.getElementById('snn-accessibility-menu');
this.button = document.getElementById('snn-accessibility-button');
this.closeButton = this.menu?.querySelector('.snn-close');
@@ -1409,7 +1409,7 @@ const menuCache = {
function toggleMenu() {
if (!menuCache.menu) menuCache.init();
const isOpen = menuCache.menu.style.display === 'block';
-
+
if (isOpen) {
closeMenu();
} else {
@@ -1421,11 +1421,11 @@ function openMenu() {
if (!menuCache.menu) menuCache.init();
menuCache.menu.style.display = 'block';
menuCache.menu.setAttribute('aria-hidden', 'false');
-
+
if (menuCache.closeButton) {
menuCache.closeButton.focus();
}
-
+
// Add keyboard navigation
document.addEventListener('keydown', handleMenuKeyboard);
}
@@ -1434,11 +1434,11 @@ function closeMenu() {
if (!menuCache.menu) menuCache.init();
menuCache.menu.style.display = 'none';
menuCache.menu.setAttribute('aria-hidden', 'true');
-
+
if (menuCache.button) {
menuCache.button.focus();
}
-
+
// Remove keyboard navigation
document.removeEventListener('keydown', handleMenuKeyboard);
}
@@ -1447,7 +1447,7 @@ function closeMenu() {
let keyboardCache = {
focusableElements: null,
lastUpdate: 0,
- getFocusableElements: function() {
+ getFocusableElements: function () {
const now = Date.now();
if (!this.focusableElements || now - this.lastUpdate > 1000) {
if (menuCache.menu) {
@@ -1464,20 +1464,20 @@ let keyboardCache = {
function handleMenuKeyboard(e) {
if (!menuCache.menu || menuCache.menu.style.display !== 'block') return;
-
+
if (e.key === 'Escape') {
e.preventDefault();
closeMenu();
return;
}
-
+
const elements = keyboardCache.getFocusableElements();
if (!elements) return;
-
+
if (e.key === 'Tab') {
const firstElement = elements.all[0];
const lastElement = elements.all[elements.all.length - 1];
-
+
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
@@ -1490,18 +1490,18 @@ function handleMenuKeyboard(e) {
}
}
}
-
+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
const currentIndex = elements.options.indexOf(document.activeElement);
let nextIndex;
-
+
if (e.key === 'ArrowDown') {
nextIndex = currentIndex === elements.options.length - 1 ? 0 : currentIndex + 1;
} else {
nextIndex = currentIndex === 0 ? elements.options.length - 1 : currentIndex - 1;
}
-
+
elements.options[nextIndex].focus();
}
}