1/** 2 * ONPhI â Accessibilité WCAG 2.2 AA 3 * Gestion clavier, focus, annonces ARIA, menu mobile. 4 */ 5(function () { 6 'use strict'; 7 8 var i18n = window.ONPHI_A11Y_I18N || {}; 9 10 function t(key, fallback) { 11 return i18n[key] || fallback; 12 } 13 14 function ready(fn) { 15 if (document.readyState !== 'loading') { 16 fn(); 17 } else { 18 document.addEventListener('DOMContentLoaded', fn); 19 } 20 } 21 22 function announce(message) { 23 var live = document.getElementById('onphi-a11y-live'); 24 if (!live || !message) { 25 return; 26 } 27 live.textContent = ''; 28 window.setTimeout(function () { 29 live.textContent = message; 30 }, 50); 31 } 32 33 function setExpanded(toggle, expanded) { 34 if (!toggle) { 35 return; 36 } 37 toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false'); 38 } 39 40 function isSidebarOpen(sidebar) { 41 if (!sidebar) { 42 return false; 43 } 44 var width = window.innerWidth || document.documentElement.clientWidth || 1024; 45 if (width <= 799) { 46 return sidebar.classList.contains('active'); 47 } 48 return !sidebar.classList.contains('inactive'); 49 } 50 51 function enhanceImages() { 52 var images = document.querySelectorAll('img:not([alt])'); 53 for (var i = 0; i < images.length; i++) { 54 var img = images[i]; 55 // Images décoratives sans alt : alt vide pour les lecteurs d'écran 56 if (img.closest('a') && img.closest('a').getAttribute('aria-label')) { 57 img.setAttribute('alt', ''); 58 } else if (img.getAttribute('role') === 'presentation' || img.getAttribute('aria-hidden') === 'true') { 59 img.setAttribute('alt', ''); 60 } else { 61 // Alt manquant : valeur neutre plutôt qu'absence d'attribut (échec automatique) 62 img.setAttribute('alt', ''); 63 img.setAttribute('data-a11y-missing-alt', '1'); 64 } 65 } 66 } 67 68 function enhanceExternalLinks() { 69 var opensLabel = t('opensNewWindow', '(opens in a new window)'); 70 var links = document.querySelectorAll('a[target="_blank"]'); 71 for (var i = 0; i < links.length; i++) { 72 var link = links[i]; 73 if (!link.getAttribute('rel')) { 74 link.setAttribute('rel', 'noopener noreferrer'); 75 } 76 var label = link.getAttribute('aria-label') || link.textContent || ''; 77 var labelLower = label.toLowerCase(); 78 if (label && labelLower.indexOf('nouvelle fenêtre') === -1 && 79 labelLower.indexOf('new window') === -1 && 80 labelLower.indexOf(opensLabel.toLowerCase()) === -1 && 81 !link.querySelector('.sr-only')) { 82 var span = document.createElement('span'); 83 span.className = 'sr-only'; 84 span.textContent = ' ' + opensLabel; 85 link.appendChild(span); 86 } 87 } 88 } 89 90 function enhanceSidebarToggle() { 91 var toggle = document.getElementById('sidebar-toggle') || 92 document.querySelector('#wrapper > a.toggle'); 93 var sidebar = document.getElementById('sidebar'); 94 if (!toggle || !sidebar) { 95 return; 96 } 97 98 if (!toggle.getAttribute('aria-controls')) { 99 toggle.setAttribute('aria-controls', 'sidebar'); 100 } 101 if (!toggle.getAttribute('role')) { 102 toggle.setAttribute('role', 'button'); 103 } 104 toggle.setAttribute('aria-haspopup', 'true'); 105 setExpanded(toggle, isSidebarOpen(sidebar)); 106 107 toggle.addEventListener('click', function () { 108 window.setTimeout(function () { 109 var open = isSidebarOpen(sidebar); 110 setExpanded(toggle, open); 111 announce(open ? t('menuOpen', 'Menu open') : t('menuClosed', 'Menu closed')); 112 if (open) { 113 var first = sidebar.querySelector('a, button, select, [tabindex]:not([tabindex="-1"])'); 114 if (first && typeof first.focus === 'function') { 115 first.focus(); 116 } 117 } 118 }, 50); 119 }); 120 121 document.addEventListener('keydown', function (event) { 122 if (event.key !== 'Escape') { 123 return; 124 } 125 var width = window.innerWidth || document.documentElement.clientWidth || 1024; 126 if (width <= 799 && sidebar.classList.contains('active')) { 127 sidebar.classList.remove('active'); 128 setExpanded(toggle, false); 129 announce(t('menuClosed', 'Menu closed')); 130 toggle.focus(); 131 } 132 }); 133 } 134 135 function enhanceLanguageSelect() { 136 var select = document.getElementById('langue'); 137 if (!select) { 138 return; 139 } 140 if (!select.getAttribute('aria-label') && !document.querySelector('label[for="langue"]')) { 141 select.setAttribute('aria-label', t('chooseLanguage', 'Choose the site language')); 142 } 143 } 144 145 function enhanceMainLandmarks() { 146 var main = document.getElementById('main'); 147 if (main && !main.getAttribute('role')) { 148 main.setAttribute('role', 'main'); 149 } 150 var nav = document.getElementById('menu'); 151 if (nav && !nav.getAttribute('aria-label')) { 152 nav.setAttribute('aria-label', t('mainMenu', 'Main menu')); 153 } 154 var footer = document.getElementById('footer'); 155 if (footer && !footer.getAttribute('role')) { 156 footer.setAttribute('role', 'contentinfo'); 157 } 158 var header = document.getElementById('header'); 159 if (header && !header.getAttribute('role')) { 160 header.setAttribute('role', 'banner'); 161 } 162 } 163 164 function enhanceTables() { 165 var tables = document.querySelectorAll('#main-content table'); 166 for (var i = 0; i < tables.length; i++) { 167 var table = tables[i]; 168 if (!table.querySelector('caption') && !table.getAttribute('aria-label') && !table.getAttribute('aria-labelledby')) { 169 table.setAttribute('aria-label', t('dataTable', 'Data table')); 170 } 171 // Conteneur scrollable pour éviter le débordement horizontal (mobile / WCAG 1.4.10) 172 if (table.closest('.table-responsive')) { 173 continue; 174 } 175 var parent = table.parentElement; 176 if (!parent) { 177 continue; 178 } 179 var wrapper = document.createElement('div'); 180 wrapper.className = 'table-responsive'; 181 wrapper.setAttribute('tabindex', '0'); 182 wrapper.setAttribute('role', 'region'); 183 wrapper.setAttribute('aria-label', t('scrollableTable', 'Horizontally scrollable table')); 184 parent.insertBefore(wrapper, table); 185 wrapper.appendChild(table); 186 } 187 } 188 189 ready(function () { 190 enhanceMainLandmarks(); 191 enhanceSidebarToggle(); 192 enhanceLanguageSelect(); 193 enhanceImages(); 194 enhanceExternalLinks(); 195 enhanceTables(); 196 }); 197})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.