PageSourceSearch

https://onphi.org/assets/js/accessibility.js?v=1785288865

js onphi.org collected 2026-10-02 21:20:20 UTC 6,410 bytes, 197 lines download raw bytes

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.