// Accessibility utility functions for ensuring proper button labels /** * Checks if all IconButtons on the page have proper aria-labels * This function can be called in development to identify missing labels */ export const validateIconButtonAccessibility = () => { if (process.env.NODE_ENV !== 'development') return; // Find all IconButton elements const iconButtons = document.querySelectorAll('[class*="MuiIconButton"]'); const missingLabels = []; iconButtons.forEach((button, index) => { const hasAriaLabel = button.hasAttribute('aria-label'); const hasAriaLabelledBy = button.hasAttribute('aria-labelledby'); const hasTitle = button.hasAttribute('title'); const isInTooltip = button.closest('[role="tooltip"]') !== null; // Check if button has any form of accessible name if (!hasAriaLabel && !hasAriaLabelledBy && !hasTitle && !isInTooltip) { missingLabels.push({ element: button, index, classes: button.className, parentElement: button.parentElement?.tagName, parentClasses: button.parentElement?.className }); } }); if (missingLabels.length > 0) { console.warn('IconButtons missing accessibility labels:', missingLabels); return missingLabels; } console.log('✅ All IconButtons have proper accessibility labels'); return []; }; /** * Checks if all images on the page have proper alt attributes * This function can be called in development to identify missing alt text */ export const validateImageAccessibility = () => { if (process.env.NODE_ENV !== 'development') return; // Find all img elements const images = document.querySelectorAll('img'); const missingAltText = []; images.forEach((img, index) => { const hasAlt = img.hasAttribute('alt'); const altValue = img.getAttribute('alt'); const isDecorative = altValue === ''; const src = img.src; // Check if image has alt attribute if (!hasAlt) { missingAltText.push({ element: img, index, src: src, classes: img.className, parentElement: img.parentElement?.tagName, parentClasses: img.parentElement?.className, issue: 'Missing alt attribute' }); } else if (altValue && altValue.trim() === '' && !isDecorative) { // Empty alt text for non-decorative images missingAltText.push({ element: img, index, src: src, classes: img.className, parentElement: img.parentElement?.tagName, parentClasses: img.parentElement?.className, issue: 'Empty alt text for informative image' }); } }); if (missingAltText.length > 0) { console.warn('Images missing accessibility alt text:', missingAltText); return missingAltText; } console.log('✅ All images have proper alt attributes'); return []; }; /** * Automatically adds alt attributes to images that are missing them * This is a fallback solution for development */ export const addFallbackAltText = () => { if (process.env.NODE_ENV !== 'development') return; const images = document.querySelectorAll('img:not([alt])'); images.forEach((img) => { const src = img.src || ''; let fallbackAlt = 'Bild'; // Try to determine alt text from src or context if (src.includes('prod')) { fallbackAlt = 'Produktbild'; } else if (src.includes('cat')) { fallbackAlt = 'Kategoriebild'; } else if (src.includes('logo')) { fallbackAlt = 'Logo'; } else if (src.includes('nopicture')) { fallbackAlt = 'Kein Bild verfügbar'; } else if (src.includes('404')) { fallbackAlt = '404 - Seite nicht gefunden'; } else if (src.includes('seeds')) { fallbackAlt = 'Seeds'; } else if (src.includes('cutlings')) { fallbackAlt = 'Stecklinge'; } else if (src.includes('filiale')) { fallbackAlt = 'Filiale'; } else if (src.includes('presse')) { fallbackAlt = 'Presse'; } img.setAttribute('alt', fallbackAlt); console.warn(`Added fallback alt text "${fallbackAlt}" to image:`, img); }); }; /** * Automatically adds aria-labels to IconButtons that are missing them * This is a fallback solution for development */ export const addFallbackAriaLabels = () => { if (process.env.NODE_ENV !== 'development') return; const iconButtons = document.querySelectorAll('[class*="MuiIconButton"]:not([aria-label]):not([aria-labelledby]):not([title])'); iconButtons.forEach((button) => { // Try to determine button function from context const icon = button.querySelector('[class*="MuiSvgIcon"]'); const iconClass = icon?.className || ''; let fallbackLabel = 'Button'; // Determine label based on icon type or context if (iconClass.includes('Close')) { fallbackLabel = 'Schließen'; } else if (iconClass.includes('Search')) { fallbackLabel = 'Suchen'; } else if (iconClass.includes('Add')) { fallbackLabel = 'Hinzufügen'; } else if (iconClass.includes('Remove')) { fallbackLabel = 'Entfernen'; } else if (iconClass.includes('Delete')) { fallbackLabel = 'Löschen'; } else if (iconClass.includes('Edit')) { fallbackLabel = 'Bearbeiten'; } else if (iconClass.includes('Expand')) { fallbackLabel = 'Erweitern'; } else if (iconClass.includes('ShoppingCart')) { fallbackLabel = 'Warenkorb'; } else if (iconClass.includes('Zoom')) { fallbackLabel = 'Vergrößern'; } button.setAttribute('aria-label', fallbackLabel); console.warn(`Added fallback aria-label "${fallbackLabel}" to IconButton:`, button); }); }; /** * Comprehensive accessibility validation */ export const validateAllAccessibility = () => { if (process.env.NODE_ENV !== 'development') return; console.log('🔍 Running comprehensive accessibility validation...'); const iconButtonIssues = validateIconButtonAccessibility(); const imageIssues = validateImageAccessibility(); if (iconButtonIssues.length === 0 && imageIssues.length === 0) { console.log('✅ All accessibility checks passed!'); } else { console.warn(`❌ Found ${iconButtonIssues.length + imageIssues.length} accessibility issues`); // Auto-fix issues in development if (iconButtonIssues.length > 0) { addFallbackAriaLabels(); } if (imageIssues.length > 0) { addFallbackAltText(); } } }; /** * Initialize accessibility checking on page load */ export const initializeAccessibilityChecking = () => { if (process.env.NODE_ENV !== 'development') return; // Check after initial render setTimeout(() => { validateAllAccessibility(); }, 1000); // Check after dynamic content loads const observer = new MutationObserver(() => { setTimeout(() => { validateAllAccessibility(); }, 100); }); observer.observe(document.body, { childList: true, subtree: true }); };