1function debounce(func, wait, immediate) { 2 var timeout; 3 4 return function executedFunction() { 5 var context = this; 6 var args = arguments; 7 8 var later = function () { 9 timeout = null; 10 if (!immediate) func.apply(context, args); 11 }; 12 13 var callNow = immediate && !timeout; 14 15 clearTimeout(timeout); 16 17 timeout = setTimeout(later, wait); 18 19 if (callNow) func.apply(context, args); 20 }; 21} 22 23function createCookie(name, value, nbDays) { 24 var today = new Date(), 25 expires = new Date(); 26 expires.setTime(today.getTime() + nbDays * 24 * 60 * 60 * 1000); 27 document.cookie = 28 name + 29 "=" + 30 encodeURIComponent(value) + 31 ";expires=" + 32 expires.toGMTString(); 33} 34function readCookie(name) { 35 var oRegex = new RegExp("(?:; )?" + name + "=([^;]*);?"); 36 if (oRegex.test(document.cookie)) { 37 return decodeURIComponent(RegExp["$1"]); 38 } else { 39 return null; 40 } 41} 42var target_href; 43function RecupParam(name) { 44 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 45 var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 46 results = regex.exec(location.search); 47 return results === null 48 ? "" 49 : decodeURIComponent(results[1].replace(/\+/g, " ")); 50} 51 52/* Affichage des cookies */ 53function readCookie(name) { 54 var nameEQ = name + "="; 55 var ca = document.cookie.split(";"); 56 for (var i = 0; i < ca.length; i++) { 57 var c = ca[i]; 58 while (c.charAt(0) == " ") c = c.substring(1, c.length); 59 if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); 60 } 61 return null; 62} 63 64function createCookieValidation() { 65 var dateJour = new Date(); 66 var date1an = new Date( 67 Date.UTC( 68 dateJour.getFullYear() + 1, 69 dateJour.getMonth(), 70 dateJour.getDate(), 71 0, 72 0, 73 0 74 ) 75 ); 76 document.cookie = "cookie=ok; expires=" + date1an + "; path=/"; 77 $("#cookieChoice").slideUp(); 78} 79 80function stopResponsiveTransition() { 81 var timer = null; 82 var viewportWidth = window.innerWidth; 83 window.addEventListener("resize", function () { 84 if (timer) { 85 clearTimeout(timer); 86 timer = null; 87 } else { 88 $(".navigationNiv3").addClass("stop-transition"); 89 $(".navigationNiv2 > .BigMenu").addClass("stop-transition"); 90 } 91 timer = setTimeout(function () { 92 $(".navigationNiv3").removeClass("stop-transition"); 93 $(".navigationNiv2 > .BigMenu").removeClass("stop-transition"); 94 timer = null; 95 }, 50); 96 97 // ACS 350 - Mutualisation header mobile et desktop 98 var header = document.querySelector("header"); 99 if ( 100 header && 101 ((viewportWidth < 1240 && window.innerWidth >= 1240) || 102 (viewportWidth >= 1240 && window.innerWidth < 1240)) 103 ) { 104 viewportWidth = window.innerWidth; 105 var overlay = document.querySelector("#overlay"); 106 if (header.classList && header.classList.contains("actif")) { 107 header.classList.add("inactif"); 108 header.classList.remove("actif"); 109 document.body.classList.remove("lockscroll"); 110 if (overlay) overlay.style.display = "none"; 111 } 112 var opennedMenu = document.querySelector( 113 "header nav#menuhamb .navigationNiv2 ul.actif" 114 ); 115 if (opennedMenu) { 116 opennedMenu.classList.remove("actif"); 117 if (overlay) overlay.style.display = "none"; 118 } 119 } 120 }); 121} 122 123/** 124 * ACCESSIBILITà : Ferme un sous-menu proprement (focus d'abord, puis aria-hidden) 125 */ 126 127/** 128 * Vérifie si un élément est visible (fonctionne avec position:fixed) 129 */ 130function isElementVisible(el) { 131 if (!el) return false; 132 133 var style = window.getComputedStyle(el); 134 if (style.display === 'none' || 135 style.visibility === 'hidden' || 136 style.opacity === '0') { 137 return false; 138 } 139 140 var rect = el.getBoundingClientRect(); 141 if (rect.width === 0 && rect.height === 0) { 142 return false; 143 } 144 145 var parent = el.parentElement; 146 while (parent) { 147 var parentStyle = window.getComputedStyle(parent); 148 if (parentStyle.display === 'none' || 149 parentStyle.visibility === 'hidden') { 150 return false; 151 } 152 parent = parent.parentElement; 153 } 154 155 return true; 156} 157 158/** 159 * Force le retrait du focus de tous les éléments d'un conteneur 160 * en déplaçant le focus sur un élément cible AVANT de cacher le conteneur 161 */ 162function forceFocusOut($container, $focusTarget) { 163 // Créer un élément temporaire invisible pour capturer le focus 164 var tempFocusTrap = document.createElement('span'); 165 tempFocusTrap.setAttribute('tabindex', '-1'); 166 tempFocusTrap.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none;'; 167 document.body.appendChild(tempFocusTrap); 168 tempFocusTrap.focus(); 169 170 // Nettoyer immédiatement 171 setTimeout(function() { 172 if (tempFocusTrap.parentNode) { 173 tempFocusTrap.parentNode.removeChild(tempFocusTrap); 174 } 175 if ($focusTarget && $focusTarget.length) {
176 $focusTarget.focus(); 177 } 178 }, 0); 179} 180 181function closeSubmenuAccessible($submenu, $parentButton) { 182 // 1. D'abord déplacer le focus HORS du sous-menu 183 if ($parentButton && $parentButton.length) { 184 $parentButton.focus(); 185 } else { 186 // Fallback : focus sur le body 187 document.body.focus(); 188 } 189 190 // 2. Ensuite seulement mettre aria-hidden (après un petit délai pour laisser le focus se déplacer) 191 setTimeout(function() { 192 $submenu.attr("aria-hidden", "true"); 193 }, 50); 194} 195 196function inactiveBigMenu(element) { 197 // ACCESSIBILITà : Trouver le bouton parent AVANT de fermer 198 var $navigationNiv3 = $(element).closest(".navigationNiv3"); 199 var menuId = $navigationNiv3.attr("id"); 200 var $parentButton = $("a[aria-controls='" + menuId + "']"); 201 202 $(element).parent().parent().removeClass("actif"); 203 $("body .navigationHeader > a").removeClass("actif"); 204 $("header").removeClass("bigMenuActif"); 205 $(".sousmenu").find(".actif").removeClass("actif"); 206 207 // ACCESSIBILITà : Mettre à jour aria-expanded sur tous les boutons BigMenu 208 $("a.BigMenu[role='button']").attr("aria-expanded", "false"); 209 $(".navigationHeader > a[role='button']").attr("aria-expanded", "false"); 210 211 // ACCESSIBILITà : Fermer proprement avec gestion du focus 212 if ($parentButton.length) { 213 $parentButton.focus(); 214 } 215 216 // FIX menu mobile et overlay 217 var header = document.querySelector("header"); 218 if (!(header && header.classList && header.classList.contains("actif"))) { 219 var overlay = document.querySelector("#overlay"); 220 if (overlay) overlay.style.display = "none"; 221 } 222} 223 224$(document).ready(function () { 225 226 // ============================================ 227 // ACCESSIBILITà NAVIGATION - RGAA 7.1 228 // ============================================ 229 initAccessibleNavigation(); 230 231 // MOBILE : Gestion des sous-menus 232 $("nav#menuhamb > ul > li > a[href='#']").on("click", function (e) { 233 e.preventDefault(); 234 e.stopPropagation(); 235 236 var $this = $(this); 237 var $parent = $this.parent(); 238 var wasSelected = $parent.hasClass("selected"); 239 240 // Fermer tous les autres sous-menus d'abord 241 var $otherItems = $("nav#menuhamb > ul > li").not($parent); 242 243 // ACCESSIBILITà : Forcer le focus hors des sous-menus AVANT de les fermer 244 $otherItems.find(".sousmenu").each(function() { 245 if ($.contains(this, document.activeElement)) { 246 forceFocusOut($(this), $this); 247 } 248 }); 249 250 $otherItems.removeClass("selected"); 251 $("nav#menuhamb > ul > li > a[href='#']").not(this).attr("aria-expanded", "false"); 252 253 // Fermer les sous-menus des autres items - avec délai pour que forceFocusOut ait le temps 254 setTimeout(function() { 255 $otherItems.find(".sousmenu").attr("aria-hidden", "true"); 256 $otherItems.find(".sousmenu").find('a[href], button:not([disabled])').attr('tabindex', '-1'); 257 $otherItems.find(".navigationNiv3").removeClass("actif").attr("aria-hidden", "true"); 258 }, 10); 259 260 if (!wasSelected) { 261 // Ouvrir ce sous-menu 262 $parent.addClass("selected"); 263 $this.attr("aria-expanded", "true"); 264 265 // ACCESSIBILITà : Mettre aria-hidden="false" sur le sous-menu ouvert 266 var submenuId = $this.attr("aria-controls"); 267 if (submenuId) { 268 $("#" + submenuId).attr("aria-hidden", "false"); 269 // ACCESSIBILITà : Initialiser les BigMenu du sous-menu qui vient de s'ouvrir 270 initBigMenuButtons(document.getElementById(submenuId)); 271 272 // ACCESSIBILITà : Mettre à jour le tabindex de TOUS les liens et focus le premier 273 setTimeout(function() { 274 var submenu = document.getElementById(submenuId); 275 if (submenu) { 276 // Rendre TOUS les liens/boutons du sous-menu focusables 277 submenu.querySelectorAll('a[href], button:not([disabled])').forEach(function(el) { 278 if (el.getAttribute('tabindex') === '-1') { 279 el.setAttribute('tabindex', '0'); 280 } 281 }); 282 283 var firstFocusable = submenu.querySelector('a[href]:not(.fermerSousMenu), a.BigMenu'); 284 if (firstFocusable) { 285 firstFocusable.focus(); 286 } 287 } 288 }, 150); 289 } 290 } else { 291 // Fermer ce sous-menu 292 $parent.removeClass("selected"); 293 $this.attr("aria-expanded", "false"); 294 295 // ACCESSIBILITà : Mettre aria-hidden="true" sur le sous-menu fermé 296 var submenuId = $this.attr("aria-controls"); 297 if (submenuId) { 298 var submenuEl = document.getElementById(submenuId); 299 if (submenuEl && $.contains(submenuEl, document.activeElement)) { 300 forceFocusOut($(submenuEl), $this); 301 } 302 setTimeout(function() { 303 $("#" + submenuId).attr("aria-hidden", "true"); 304 $("#" + submenuId).find('a[href], button:not([disabled])').attr('tabindex', '-1'); 305 }, 10); 306 } 307 } 308 309 return false; 310 }); 311 312 /* TODO : Gestion collapse footer à mutualiser dans JS base du GUI */ 313 (function () { 314 var collapsesFooter = document.querySelectorAll(".footer-collapse-trigger"); 315 collapsesFooter.forEach(function (button) { 316 button.addEventListener("click", function (event) { 317 event.preventDefault(); 318 var element = document.querySelector( 319 '[data-id="' + $(this).attr("data-triggerid") + '"]' 320 ); 321 var collapse = button.parentNode; 322 323 if (collapse && collapse.classList.contains("fieldsetPlus")) { 324 collapse.classList.remove("fieldsetPlus"); 325 collapse.classList.add("fieldsetMoins"); 326 button.setAttribute("aria-expanded", true); 327 button.setAttribute("title", button.dataset.tooltipless); 328 button.innerHTML = button.dataset.labeldisplayless; 329 } else { 330 collapse.classList.remove("fieldsetMoins"); 331 collapse.classList.add("fieldsetPlus"); 332 button.setAttribute("aria-expanded", false); 333 button.setAttribute("title", button.dataset.tooltipmore); 334 button.innerHTML = button.dataset.labeldisplaymore; 335 } 336 }); 337 });
338 })(); 339 340 /* Big Menu */ 341 // Permet d'activer le scroll du BigMenu en mobile si celui-ci est trop grand. 342 $(".navigationNiv3, .navigationNiv2 > .BigMenu").css( 343 "max-height", 344 $("body").height() - 345 $("#menuheader").height() - 346 $("#conteneurlogo").height() 347 ); 348 349 $("a.BigMenu").on("mouseover", function () { 350 if ($(window).width() > 1239) { 351 changeBigMenu($(this)); 352 } 353 }); 354 $("a.BigMenu").on("click", function (e) { 355 e.preventDefault(); 356 changeBigMenu($(this)); 357 return false; 358 }); 359 360 $( 361 "#overlay, body:not(.theme6) #menuheader, ul#sousmenu_auto > li > a:not('.BigMenu'), ul#sousmenu_santeprev > li > a:not('.BigMenu'), ul#sousmenu_habitation > li > a:not('.BigMenu')" 362 ).on("mouseover", function () { 363 if ($(".navigationHeader.focus").length) { 364 return; 365 } 366 $("a.BigMenu").next().removeClass("actif"); 367 $("header").removeClass("bigMenuActif"); 368 $(".navigationNiv3").attr("aria-hidden", true); 369 $(".sousmenu").find("li").removeClass("actif"); 370 371 // ACCESSIBILITà : Mettre à jour aria-expanded 372 $("a.BigMenu[role='button']").attr("aria-expanded", "false"); 373 374 if (!$("header.actif #menuhamb").length) $("#overlay").hide(); 375 }); 376 377 378 379 $("main").on("mouseenter", function () { 380 $("header").find(".actif").removeClass("actif"); 381 // ACCESSIBILITà : Mettre à jour aria-expanded 382 $("a.BigMenu[role='button']").attr("aria-expanded", "false"); 383 $(".navigationHeader > a[role='button']").attr("aria-expanded", "false"); 384 return false; 385 }); 386 387 $(".fermerMenu").off("click").on("click", function (e) { 388 e.preventDefault(); 389 e.stopImmediatePropagation(); 390 391 var $navigationNiv3 = $(this).closest(".navigationNiv3"); 392 var menuId = $navigationNiv3.attr("id"); 393 var $parentButton = $("a[aria-controls='" + menuId + "']"); 394 395 // Mettre à jour aria-expanded 396 if ($parentButton.length) { 397 $parentButton.attr("aria-expanded", "false"); 398 } 399 400 // Fermer UNIQUEMENT le niveau 3 401 $navigationNiv3.removeClass("actif"); 402 $navigationNiv3.attr("aria-hidden", "true"); 403 $("header").removeClass("bigMenuActif"); 404 405 // Focus direct sur le BigMenu parent (sans forceFocusOut) 406 if ($parentButton.length) { 407 $parentButton.attr("tabindex", "0"); 408 $parentButton[0].focus(); 409 } 410 411 return false; 412}); 413 414 $("#menuhamb .fermerSousMenu").on("click", function (e) { 415 e.preventDefault(); 416 417 var $item = $(this).closest(".navigationNiv2-item"); 418 var $sousmenu = $(this).closest(".sousmenu"); 419 var $parentButton = $item.find("> a[href='#']"); 420 421 // ACCESSIBILITà : Forcer le focus hors du sous-menu 422 if ($parentButton.length) { 423 $parentButton.attr("aria-expanded", "false"); 424 forceFocusOut($sousmenu, $parentButton); 425 } 426 427 setTimeout(function() { 428 $item.removeClass("selected"); 429 $sousmenu.attr("aria-hidden", "true"); 430 $sousmenu.find('a[href], button:not([disabled])').attr('tabindex', '-1'); 431 }, 10); 432 433 return false; 434 }); 435 436 $("body:not(.theme6) .navigationHeader > a[href='#']").on( 437 "click", 438 function () { 439 var $this = $(this); 440 var attr = $this.attr("id"); 441 var test = $this.hasClass("actif"); 442 443 $(".navigationHeader a, .navigationNiv2 ul").removeClass("actif"); 444 445 // ACCESSIBILITà : Réinitialiser tous les aria-expanded du header 446 $(".navigationHeader > a[role='button']").attr("aria-expanded", "false"); 447 448 function sousmenu() { 449 $(".navigationNiv2 ul").removeClass("actif"); 450 $("#sousmenu_" + attr).addClass("actif"); 451 } 452 453 if (!test) { 454 $this.addClass("actif"); 455 // ACCESSIBILITà : Mettre à jour aria-expanded 456 $this.attr("aria-expanded", "true"); 457 setTimeout(sousmenu, 300); 458 } 459 460 $(".navigationNiv2 ul") 461 .attr("aria-hidden", true) 462 .find(".BigMenu") 463 .attr("tabindex", "-1"); 464 465 $("#sousmenu_" + attr) 466 .attr("aria-hidden", false) 467 .find(".BigMenu") 468 .attr("tabindex", "0"); 469 470 // ACCESSIBILITà : Initialiser les BigMenu du sous-menu qui vient de s'ouvrir 471 var submenuIdDesktop = "sousmenu_" + attr; 472 initBigMenuButtons(document.getElementById(submenuIdDesktop)); 473 474 // ACCESSIBILITà : Focus automatique sur le premier élément du sous-menu 475 if (!test) { 476 (function(menuId) { 477 setTimeout(function() { 478 var submenu = document.getElementById(menuId); 479 if (submenu) { 480 var firstFocusable = submenu.querySelector('a.BigMenu[tabindex="0"], a.BigMenu, a[href]:not(.fermerSousMenu)'); 481 if (firstFocusable) { 482 firstFocusable.setAttribute('tabindex', '0'); 483 firstFocusable.focus(); 484 } 485 } 486 }, 400); 487 })(submenuIdDesktop); 488 } 489 490 return false; 491 } 492 ); 493 494 $("body.theme6 .navigationHeader > a").on("mouseover", function () { 495 var $this = $(this); 496 var attr = $this.attr("id"); 497 $("body.theme6 .navigationHeader > a").removeClass("actif"); 498 $("body.theme6 .navigationNiv2 .BigMenu + .navigationNiv3").removeClass( 499 "actif" 500 ); 501 502 // ACCESSIBILITà : Réinitialiser tous les aria-expanded 503 $("body.theme6 .navigationHeader > a[role='button']").attr("aria-expanded", "false"); 504 505 $this.addClass("actif"); 506 507 // ACCESSIBILITà : Mettre à jour aria-expanded si c'est un bouton 508 if ($this.attr("role") === "button") { 509 $this.attr("aria-expanded", "true"); 510 } 511 512 $("header").addClass("bigMenuActif"); 513 if ($this.parents(".navigationHeader").length) { 514 $("#sousmenu_" + attr) 515 .addClass("actif") 516 .attr("aria-hidden", false) 517 .siblings() 518 .attr("aria-hidden", true); 519 520 // ACCESSIBILITà : Initialiser les BigMenu du sous-menu qui vient de s'ouvrir 521 initBigMenuButtons(document.getElementById("sousmenu_" + attr)); 522 } 523 $("#overlay").show(); 524 return false; 525 }); 526 527 stopResponsiveTransition(); 528 529 /* Fin Big Menu */ 530 $("body header").addClass("inactif"); 531 $("header a#btnmenu").on("click", function () { 532 $("body header").removeClass("inactif").addClass("actif"); 533 $("body").addClass("lockscroll"); 534 $("#overlay").show(); 535 536 // Desactivate selected header button 537 var selectedTab = document.querySelector( 538 "#menuheader .navigationHeader a.actif" 539 ); 540 if (selectedTab) selectedTab.click(); 541 542 return false; 543 }); 544 $("header a#closemenu").on("click", function () { 545 $("body header").removeClass("actif"); 546 $("nav > div").removeClass("selected"); 547 $("#overlay").hide(); 548 549 // ACCESSIBILITà : Réinitialiser tous les aria-expanded 550 $("a[role='button']").attr("aria-expanded", "false"); 551 552 // ACCESSIBILITà : Fermer tous les sous-menus mobile 553 $("nav#menuhamb > ul > li").removeClass("selected"); 554 555 // ACCESSIBILITà : Forcer le focus hors des sous-menus avant aria-hidden 556 $("nav#menuhamb .sousmenu").each(function() { 557 if ($.contains(this, document.activeElement)) { 558 forceFocusOut($(this), $("header a#btnmenu")); 559 } 560 });
561 setTimeout(function() { 562 $("nav#menuhamb .sousmenu").attr("aria-hidden", "true"); 563 $("nav#menuhamb .sousmenu").find('a[href], button:not([disabled])').attr('tabindex', '-1'); 564 }, 10); 565 566 setTimeout(function () { 567 $("body header").addClass("inactif"); 568 $("body").removeClass("lockscroll"); 569 570 // On ferme le bigMenu encore actif pour le pro 571 $("body.theme6 .navigationNiv2 .BigMenu + .navigationNiv3") 572 .addClass("stop-transition") 573 .removeClass("actif"); 574 $("body.theme6 header").removeClass("bigMenuActif"); 575 setTimeout(function () { 576 $("body.theme6 .navigationNiv2 .BigMenu + .navigationNiv3").removeClass( 577 "stop-transition" 578 ); 579 }, 50); 580 }, 300); 581 return false; 582 }); 583 584 $("button.closeBandeauInfo").on("click", function () { 585 $(this).parents(".gm").hide(); 586 }); 587}); 588 589function changeBigMenu($element) { 590 $(".sousmenu").find("li").removeClass("actif"); 591 $("a.BigMenu").next().removeClass("actif"); 592 $("header").removeClass("bigMenuActif"); 593 594 // ACCESSIBILITà : Réinitialiser tous les aria-expanded des BigMenu 595 $("a.BigMenu[role='button']").attr("aria-expanded", "false"); 596 597 $element.next().addClass("actif"); 598 $("header").addClass("bigMenuActif"); 599 $("#overlay").show(); 600 601 // ACCESSIBILITà : Mettre à jour aria-expanded sur l'élément actif 602 if ($element.attr("role") === "button") { 603 $element.attr("aria-expanded", "true"); 604 } 605 606 if ($(".navigationNiv3").length) { 607 $($element).parent().addClass("actif").siblings().removeClass("actif"); 608 $(".navigationNiv3").attr("aria-hidden", true); 609 $("#" + $element.attr("aria-controls")) 610 .attr("aria-hidden", false); 611 612 // ACCESSIBILITà : Mettre à jour tabindex des items du sous-menu ouvert 613 updateSubmenuTabindex($("#" + $element.attr("aria-controls")), true); 614 } 615 return false; 616} 617 618/** 619 * ============================================ 620 * ACCESSIBILITà NAVIGATION - RGAA 7.1 621 * ============================================ 622 */ 623 624function initAccessibleNavigation() { 625 // Initialiser les boutons du menu desktop 626 document.querySelectorAll('.navigationHeader > a[aria-controls]').forEach(function(btn) { 627 setupMenuButton(btn); 628 }); 629 630 // Initialiser les boutons du menu mobile 631 document.querySelectorAll('#menuhamb .navigationNiv2-item > a[href="#"]').forEach(function(btn) { 632 setupMenuButton(btn); 633 }); 634 635 // Initialiser les boutons BigMenu (niveau 3) 636 document.querySelectorAll('a.BigMenu[aria-controls]').forEach(function(btn) { 637 setupMenuButton(btn); 638 }); 639 640 // Gestion globale de la touche Escape 641 document.addEventListener('keydown', handleGlobalEscape); 642 643 // Navigation au clavier dans les sous-menus 644 document.addEventListener('keydown', handleSubmenuKeyboardNavigation); 645 646 // Marquer la page courante avec aria-current 647 setAriaCurrentPage(); 648 649 // Tracking de aria-current sur l'élément actif 650 setupAriaCurrentTracking(); 651} 652 653/** 654 * Configure un bouton de menu avec les événements clavier 655 */ 656function setupMenuButton(button) { 657 // Ãviter de rebind plusieurs fois 658 if (button.dataset.a11yInitialized) return; 659 button.dataset.a11yInitialized = 'true'; 660 661 // Gestion clavier pour les boutons de menu 662 button.addEventListener('keydown', function(e) { 663 // Space : ouvrir et focus premier élément (les liens ne réagissent pas à Space par défaut) 664 if (e.key === ' ' || e.key === 'Spacebar') { 665 e.preventDefault(); 666 e.stopPropagation(); 667 openSubmenuAndFocus(button, 'first'); 668 } 669 670 // Entrée : laisser le click natif se produire, puis gérer le focus 671 if (e.key === 'Enter') { 672 // Ne PAS faire preventDefault ni stopPropagation 673 // Le click natif va ouvrir le menu, on gère juste le focus après 674 setTimeout(function() { 675 var submenuId = button.getAttribute('aria-controls'); 676 if (submenuId) { 677 var submenu = document.getElementById(submenuId); 678 if (submenu) { 679 var firstFocusable = submenu.querySelector('a[href]:not(.fermerSousMenu):not(.fermerMenu), a.BigMenu'); 680 if (firstFocusable) { 681 if (firstFocusable.getAttribute('tabindex') === '-1') { 682 firstFocusable.setAttribute('tabindex', '0'); 683 } 684 firstFocusable.focus(); 685 } 686 } 687 } 688 }, 400); 689 } 690 691 // Flèche bas : ouvrir et focus premier élément 692 if (e.key === 'ArrowDown') { 693 e.preventDefault(); 694 e.stopPropagation(); 695 openSubmenuAndFocus(button, 'first'); 696 } 697 698 // Flèche haut : ouvrir et focus dernier élément 699 if (e.key === 'ArrowUp') { 700 e.preventDefault(); 701 e.stopPropagation(); 702 openSubmenuAndFocus(button, 'last'); 703 } 704 }); 705} 706 707/** 708 * Initialise les boutons BigMenu dans un sous-menu qui vient de s'ouvrir 709 */ 710function initBigMenuButtons(container) { 711 if (!container) return; 712 container.querySelectorAll('a.BigMenu[aria-controls]').forEach(function(btn
712) { 713 setupMenuButton(btn); 714 }); 715} 716 717/** 718 * Ouvre le sous-menu et met le focus sur le premier ou dernier élément 719 */ 720function openSubmenuAndFocus(button, position) { 721 var submenuId = button.getAttribute('aria-controls'); 722 if (!submenuId) return; 723 724 var submenu = document.getElementById(submenuId); 725 if (!submenu) return; 726 727 // Déclencher l'ouverture 728 if (window.innerWidth >= 1240) { 729 // Desktop : simuler mouseover pour le BigMenu 730 var event = new MouseEvent('mouseover', { bubbles: true, cancelable: true }); 731 button.dispatchEvent(event); 732 } else { 733 // Mobile : simuler un clic 734 button.click(); 735 } 736 737 // Attendre que le menu soit ouvert puis focus 738 setTimeout(function() { 739 // Vérifier que le sous-menu est maintenant visible 740 var isVisible = submenu.classList.contains('actif') || 741 submenu.getAttribute('aria-hidden') === 'false' || 742 submenu.closest('.selected'); 743 744 if (!isVisible) return; 745 746 // Trouver tous les éléments focusables (exclure ceux avec tabindex="-1" sauf si le menu est ouvert) 747 var focusables = submenu.querySelectorAll('a[href], button:not([disabled])'); 748 if (focusables.length === 0) return; 749 750 // Rendre tous les éléments focusables
751 focusables.forEach(function(el) { 752 if (el.getAttribute('tabindex') === '-1') { 753 el.setAttribute('tabindex', '0'); 754 } 755 }); 756 757 var targetElement; 758 if (position === 'first') { 759 // Premier élément focusable (pas le bouton fermer) 760 for (var i = 0; i < focusables.length; i++) { 761 if (!focusables[i].classList.contains('fermerSousMenu') && 762 !focusables[i].classList.contains('fermerMenu')) { 763 targetElement = focusables[i]; 764 break; 765 } 766 } 767 // Fallback sur le premier si tous sont des boutons fermer 768 if (!targetElement) targetElement = focusables[0]; 769 } else { 770 targetElement = focusables[focusables.length - 1]; 771 } 772 773 if (targetElement) { 774 targetElement.focus(); 775 } 776 }, 200); 777} 778 779/** 780 * Gestion globale de la touche Escape 781 */ 782function handleGlobalEscape(e) { 783 if (e.key !== 'Escape' && e.key !== 'Esc') return; 784 785 // Chercher le sous-menu niveau 3 actif en premier 786 var activeNiv3 = document.querySelector('.navigationNiv3.actif'); 787 if (activeNiv3) { 788 e.preventDefault(); 789 var closeBtn = activeNiv3.querySelector('.fermerMenu'); 790 if (closeBtn) { 791 closeBtn.click(); 792 } 793 return; 794 } 795 // Sinon chercher un menu mobile ouvert (item avec classe "selected") 796 var selectedMobileItem = document.querySelector('#menuhamb .navigationNiv2-item.selected'); 797 if (selectedMobileItem) { 798 e.preventDefault(); 799 var closeBtn = selectedMobileItem.querySelector('.fermerSousMenu'); 800 if (closeBtn) { 801 closeBtn.click(); 802 } 803 return; 804 } 805 806 // Sinon chercher le sous-menu niveau 2 actif (desktop) 807 var activeSousmenu = document.querySelector('.navigationNiv2 .sousmenu.actif'); 808 if (activeSousmenu) { 809 e.preventDefault(); 810 // Trouver le bouton parent dans le header 811 var parentButton = document.querySelector('.navigationHeader > a.actif[aria-controls]'); 812 if (parentButton) { 813 // Simuler un clic pour fermer (toggle) 814 parentButton.click(); 815 parentButton.focus(); 816 } 817 return; 818 } 819 820 // Sinon fermer le menu hamburger si ouvert 821 var header = document.querySelector('header.actif'); 822 if (header) { 823 e.preventDefault(); 824 var closeMenuBtn = document.querySelector('#closemenu'); 825 if (closeMenuBtn) { 826 closeMenuBtn.click(); 827 } 828 } 829} 830 831/** 832 * Navigation au clavier dans les sous-menus (flèches haut/bas/gauche/droite + Tab amélioré) 833 */ 834function handleSubmenuKeyboardNavigation(e) { 835 if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp' && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight' && e.key !== 'Tab') return; 836 837 var activeElement = document.activeElement; 838 if (!activeElement) return; 839 840 // Vérifier si on est dans un sous-menu 841 var parentSubmenu = activeElement.closest('.sousmenu, .navigationNiv3'); 842 if (!parentSubmenu) return; 843 844 // Vérifier que le sous-menu est actif/visible 845 var isVisible = parentSubmenu.classList.contains('actif') || 846 parentSubmenu.getAttribute('aria-hidden') === 'false' || 847 parentSubmenu.closest('.selected'); 848 if (!isVisible) return; 849 850 // Pour Tab, gestion spéciale pour navigation circulaire 851 if (e.key === 'Tab') { 852 var allFocusables = parentSubmenu.querySelectorAll('a[href], button:not([disabled])');
853 allFocusables.forEach(function(item) { 854 if (item.getAttribute('tabindex') === '-1') { 855 item.setAttribute('tabindex', '0'); 856 } 857 }); 858 859 // Si on est dans un navigationNiv3 (sous-sous-menu) 860 var currentNiv3 = activeElement.closest('.navigationNiv3.actif'); 861 862 if (currentNiv3) { 863 var focusablesInNiv3 = Array.prototype.slice.call( 864 currentNiv3.querySelectorAll('a[href]:not(.fermerMenu):not(.fermerSousMenu), button:not([disabled]):not(.fermerMenu):not(.fermerSousMenu)') 865 ).filter(function(el) { 866 return isElementVisible(el); 867 }); 868 869 var currentIndex = focusablesInNiv3.indexOf(activeElement); 870 var isLastElement = currentIndex === focusablesInNiv3.length - 1; 871 var isFirstElement = currentIndex === 0; 872 873 if (!e.shiftKey && isLastElement) { 874 e.preventDefault(); 875 focusablesInNiv3[0].focus(); 876 return; 877 } 878 879 if (e.shiftKey && isFirstElement) { 880 e.preventDefault(); 881 focusablesInNiv3[focusablesInNiv3.length - 1].focus(); 882 return; 883 } 884 } 885 886 // Si on est dans un sous-menu niveau 2 SANS BigMenu 887 var currentSousmenu = activeElement.closest('.sousmenu:not(.BigMenuContainer)'); 888 889 if (currentSousmenu && !currentNiv3) { 890 var isSousmenuVisible = currentSousmenu.classList.contains('actif') || 891 currentSousmenu.getAttribute('aria-hidden') === 'false' || 892 currentSousmenu.closest('.selected') || 893 currentSousmenu.closest('.navigationNiv2-item.selected'); 894 895 if (!isSousmenuVisible) { 896 return; 897 } 898 899 var focusablesInSousmenu = Array.prototype.slice.call( 900 currentSousmenu.querySelectorAll('a[href]:not(.fermerSousMenu), button:not(.fermerSousMenu)') 901 ).filter(function(el) { 902 return isElementVisible(el) && !el.classList.contains('fermerSousMenu'); 903 }); 904 905 if (focusablesInSousmenu.length === 0) return; 906 907 var currentIndex = focusablesInSousmenu.indexOf(activeElement); 908 909 // Si l'élément actif n'est pas dans la liste (ex: bouton fermer), trouver le plus proche 910 if (currentIndex === -1) { 911 e.preventDefault(); 912 focusablesInSousmenu[0].focus(); 913 return; 914 } 915 916 var isLastElement = currentIndex === focusablesInSousmenu.length - 1; 917 var isFirstElement = currentIndex === 0; 918 919 if (!e.shiftKey && isLastElement) { 920 e.preventDefault(); 921 focusablesInSousmenu[0].focus(); 922 return; 923 } 924 925 if (e.shiftKey && isFirstElement) { 926 e.preventDefault(); 927 focusablesInSousmenu[focusablesInSousmenu.length - 1].focus(); 928 return; 929 } 930 931 // Empêcher le Tab de sortir du sous-menu dans tous les cas 932 e.preventDefault(); 933 var nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1; 934 focusablesInSousmenu[nextIndex].focus(); 935 return; 936 } 937 938 // Si on est dans un BigMenuContainer mais PAS dans un niveau 3 939 var currentBigMenuContainer = activeElement.closest('.sousmenu.BigMenuContainer'); 940 941 if (currentBigMenuContainer && !currentNiv3) { 942 var allBigMenus = Array.prototype.slice.call( 943 currentBigMenuContainer.querySelectorAll('a.BigMenu') 944 ); 945 var bigMenuIndex = allBigMenus.indexOf(activeElement); 946 947 if (!e.shiftKey && bigMenuIndex === allBigMenus.length - 1) { 948 e.preventDefault(); 949 allBigMenus[0].focus(); 950 return; 951 } 952 953 if (e.shiftKey && bigMenuIndex === 0) { 954 e.preventDefault(); 955 allBigMenus[allBigMenus.length - 1].focus(); 956 return; 957 } 958 } 959 960 // Laisser le comportement Tab par défaut dans les autres cas 961 return; 962 } 963 964 // FLÃCHE GAUCHE : Fermer le sous-menu actuel et revenir au parent 965if (e.key === 'ArrowLeft') { 966 e.preventDefault(); 967 968 // Si on est dans un navigationNiv3, fermer et revenir au BigMenu parent 969 var currentNiv3 = activeElement.closest('.navigationNiv3.actif'); 970 if (currentNiv3) { 971 var menuId = currentNiv3.getAttribute('id'); 972 var $parentButton = $("a[aria-controls='" + menuId + "']"); 973 974 // Fermer le niveau 3 975 $(currentNiv3).removeClass("actif"); 976 $(currentNiv3).attr("aria-hidden", "true"); 977 $("header").removeClass("bigMenuActif"); 978 979 // Mettre à jour aria-expanded 980 if ($parentButton.length) { 981 $parentButton.attr("aria-expanded", "false"); 982 983 // En mobile, s'assurer que le BigMenu parent est focusable 984 $parentButton.attr("tabindex", "0"); 985 $parentButton.focus(); 986 } 987 return; 988 } 989 990 // Si on est dans un sousmenu mobile (selected), fermer et revenir au menu principal 991 var selectedItem = activeElement.closest('.navigationNiv2-item.selected'); 992 if (selectedItem) { 993 var closeBtn = selectedItem.querySelector('.fermerSousMenu'); 994 if (closeBtn) { 995 closeBtn.click(); 996 } 997 return; 998 } 999 1000 // Si on est dans un sousmenu desktop, fermer via le bouton header 1001 var currentSousmenu = activeElement.closest('.sousmenu.actif, .sousmenu[aria-hidden="false"]'); 1002 if (currentSousmenu) { 1003 var parentButton = document.querySelector('.navigationHeader > a.actif[aria-controls]'); 1004 if (parentButton) { 1005 parentButton.click(); 1006 parentButton.focus(); 1007 } 1008 } 1009 return; 1010} 1011 1012 // FLÃCHE DROITE : Ouvrir le sous-menu de l'élément actif (si c'est un BigMenu) 1013 if (e.key === 'ArrowRight') { 1014 e.preventDefault(); 1015 1016 // Si l'élément actif est un BigMenu, l'ouvrir 1017 if (activeElement.classList.contains('BigMenu') && activeElement.hasAttribute('aria-controls')) { 1018 activeElement.click(); 1019 1020 // Focus sur le premier élément du sous-menu ouvert 1021 setTimeout(function() { 1022 var submenuId = activeElement.getAttribute('aria-controls'); 1023 var submenu = document.getElementById(submenuId); 1024 if (submenu) { 1025 var firstLink = submenu.querySelector('a[href]:not(.fermerMenu):not(.fermerSousMenu)'); 1026 if (firstLink) { 1027 if (firstLink.getAttribute('tabindex') === '-1') { 1028 firstLink.setAttribute('tabindex', '0'); 1029 } 1030 firstLink.focus(); 1031 } 1032 } 1033 }, 150); 1034 return; 1035 } 1036 1037 // Si on est dans le header sur un bouton avec sous-menu, l'ouvrir 1038 if (activeElement.hasAttribute('aria-controls') && activeElement.closest('.navigationHeader')) { 1039 activeElement.click(); 1040 setTimeout(function() { 1041 var submenuId = activeElement.getAttribute('aria-controls'); 1042 var submenu = document.getElementById(submenuId); 1043 if (submenu) { 1044 var firstLink = submenu.querySelector('a[href], a.BigMenu'); 1045 if (firstLink) { 1046 if (firstLink.getAttribute('tabindex') === '-1') { 1047 firstLink.setAttribute('tabindex', '0'); 1048 } 1049 firstLink.focus(); 1050 } 1051 } 1052 }, 150); 1053 return; 1054 } 1055 return; 1056 } 1057 1058 // Pour les flèches HAUT/BAS, navigation circulaire dans les items 1059 var focusables = Array.prototype.slice.call( 1060 parentSubmenu.querySelectorAll('a[href], button:not([disabled])') 1061 ).filter(function(el) { 1062 // Exclure les éléments cachés 1063 return isElementVisible(el); 1064 }); 1065 1066 if (focusables.length === 0) return; 1067 1068 var currentIndex = focusables.indexOf(activeElement); 1069 if (currentIndex === -1) return; 1070 1071 e.preventDefault(); 1072 1073 var nextIndex; 1074 if (e.key === 'ArrowDown') { 1075 nextIndex = (currentIndex + 1) % focusables.length; 1076 } else { // ArrowUp 1077 nextIndex = currentIndex - 1 < 0 ? focusables.length - 1 : currentIndex - 1; 1078 } 1079 1080 focusables[nextIndex].focus(); 1081} 1082 1083/** 1084 * Met à jour le tabindex des éléments du sous-menu 1085 */ 1086function updateSubmenuTabindex($submenu, isOpen) { 1087 if (!$submenu || !$submenu.length) return; 1088 1089 var tabindexValue = isOpen ? '0' : '-1'; 1090 $submenu.find('a[href], button:not(.fermerSousMenu):not(.fermerMenu)').attr('tabindex', tabindexValue); 1091} 1092 1093/** 1094 * Ferme le sous-menu et va au bouton header suivant 1095 */ 1096function goToNextHeaderButton(currentSousmenu) { 1097 // Trouver le bouton header actif 1098 var currentHeaderButton = document.querySelector('.navigationHeader > a.actif[aria-controls]'); 1099 1100 if (currentHeaderButton) { 1101 // Trouver tous les boutons/liens du header 1102 var allHeaderItems = Array.prototype.slice.call( 1103 document.querySelectorAll('.navigationHeader > a') 1104 ); 1105 var currentIndex = allHeaderItems.indexOf(currentHeaderButton); 1106 1107 // Fermer le sous-menu actuel 1108 currentHeaderButton.click(); // Toggle pour fermer 1109 1110 // Focus sur le bouton suivant 1111 if (currentIndex < allHeaderItems.length - 1) { 1112 var nextButton = allHeaderItems[currentIndex + 1]; 1113 setTimeout(function() { 1114 nextButton.focus(); 1115 }, 100); 1116 } else { 1117 // C'est le dernier, laisser le focus sortir du menu naturellement 1118 setTimeout(function() { 1119 currentHeaderButton.focus(); 1120 }, 100); 1121 } 1122 } 1123} 1124 1125/** 1126 * Ferme le sous-menu et va au bouton header précédent 1127 */ 1128function goToPreviousHeaderButton(currentSousmenu) { 1129 // Trouver le bouton header actif 1130 var currentHeaderButton = document.querySelector('.navigationHeader > a.actif[aria-controls]'); 1131 1132 if (currentHeaderButton) { 1133 // Trouver tous les boutons/liens du header 1134 var allHeaderItems = Array.prototype.slice.call( 1135 document.querySelectorAll('.navigationHeader > a') 1136 ); 1137 var currentIndex = allHeaderItems.indexOf(currentHeaderButton); 1138 1139 // Fermer le sous-menu actuel 1140 currentHeaderButton.click(); // Toggle pour fermer 1141 1142 // Focus sur le bouton précédent 1143 if (currentIndex > 0) { 1144 var prevButton = allHeaderItems[currentIndex - 1]; 1145 setTimeout(function() { 1146 prevButton.focus(); 1147 }, 100); 1148 } else { 1149 // C'est le premier, focus sur lui-même 1150 setTimeout(function() { 1151 currentHeaderButton.focus(); 1152 }, 100); 1153 } 1154 } 1155} 1156 1157/** 1158 * Marque la page courante avec aria-current="page" 1159 */ 1160function setAriaCurrentPage() { 1161 var currentPath = window.location.pathname; 1162 1163 // Nettoyer le path (enlever le slash final si présent) 1164 if (currentPath.length > 1 && currentPath.endsWith('/')) { 1165 currentPath = currentPath.slice(0, -1); 1166 } 1167 1168 // Chercher dans tous les liens de navigation 1169 var allNavLinks = document.querySelectorAll( 1170 '.navigationHeader a[href], #menuhamb a[href], .sousmenu a[href], .navigationNiv3 a[href]' 1171 ); 1172 1173 allNavLinks.forEach(function(link) { 1174 var href = link.getAttribute('href'); 1175 if (!href || href === '#') return; 1176 1177 // Nettoyer le href 1178 var linkPath = href; 1179 if (linkPath.length > 1 && linkPath.endsWith('/')) { 1180 linkPath = linkPath.slice(0, -1); 1181 } 1182 1183 // Vérifier si c'est la page courante ou une page parente 1184 if (currentPath === linkPath || (linkPath !== '/' && currentPath.startsWith(l
1184inkPath + '/'))) { 1185 link.setAttribute('aria-current', 'page'); 1186 } 1187 }); 1188} 1189 1190/** 1191 * Met à jour aria-current="true" sur l'élément actuellement focus dans le menu 1192 */ 1193function setupAriaCurrentTracking() { 1194 // Ãcouter les focus dans les menus 1195 document.addEventListener('focusin', function(e) { 1196 var target = e.target; 1197 1198 // Vérifier si on est dans un menu 1199 var isInMenu = target.closest('.sousmenu, .navigationNiv3, .navigationHeader, #menuhamb'); 1200 if (!isInMenu) return; 1201 1202 // Retirer aria-current="true" des autres éléments du même menu (pas aria-current="page") 1203 var parentMenu = target.closest('.sousmenu, .navigationNiv3, .navigationHeader, #menuhamb'); 1204 if (parentMenu) { 1205 parentMenu.querySelectorAll('[aria-current="true"]').forEach(function(el) { 1206 el.removeAttribute('aria-current'); 1207 }); 1208 } 1209 1210 // Ajouter aria-current="true" sur l'élément actif (sauf s'il a déjà aria-current="page") 1211 if (target.tagName === 'A' || target.tagName === 'BUTTON') { 1212 if (target.getAttribute('aria-current') !== 'page') { 1213 target.setAttribute('aria-current', 'true'); 1214 } 1215 } 1216 }); 1217 1218 // Retirer aria-current="true" quand on quitte le menu 1219 document.addEventListener('focusout', function(e) { 1220 var target = e.target; 1221 1222 // Si l'élément avait aria-current="true" (pas "page"), le retirer 1223 if (target.getAttribute('aria-current') === 'true') { 1224 // Petit délai pour vérifier si le nouveau focus est toujours dans le même menu 1225 setTimeout(function() { 1226 var newFocus = document.activeElement; 1227 var sameMenu = target.closest('.sousmenu, .navigationNiv3, .navigationHeader, #menuhamb'); 1228 var newFocusInSameMenu = sameMenu && sameMenu.contains(newFocus); 1229 1230 if (!newFocusInSameMenu) { 1231 target.removeAttribute('aria-current'); 1232 } 1233 }, 10); 1234 } 1235 }); 1236} 1237 1238function consentAAUserToken() { 1239 if (typeof aa === "undefined") { 1240 window.caReady = window.caReady || []; 1241 window.cact = function () { 1242 window.caReady.push(arguments); 1243 }; 1244 1245 cact("consent.get", function (result) { 1246 if (result.consent.categories["200"].status === "on") { 1247 initAAUserToken(); 1248 } else { 1249 document.cookie = "userTokenAlgolia = null; Max-Age = 0;"; 1250 } 1251 }); 1252 cact("consent.onUpdate", function (result) { 1253 if (result.consent.categories["200"].status === "on") { 1254 initAAUserToken(); 1255 } else { 1256 document.cookie = "userTokenAlgolia = null; Max-Age = 0;"; 1257 } 1258 }); 1259 } 1260} 1261 1262initSliders(); 1263 1264/******** SLIDERS ********/ 1265 1266function initSliders() { 1267 window.addEventListener( 1268 "resize", 1269 debounce(function () { 1270 refreshTnsSliders(); 1271 }, 50) 1272 ); 1273 window.dispatchEvent(new Event("resize")); 1274 1275 let $tnsOuters = document.querySelectorAll(".tns-outer"); 1276 let indexTnsOuters = $tnsOuters.length; 1277 if (indexTnsOuters < 1) { 1278 return; 1279 } 1280 while (indexTnsOuters) { 1281 addEventSlide($tnsOuters[indexTnsOuters - 1]); 1282 indexTnsOuters--; 1283 } 1284 1285 let $tnsBtnControls = document.querySelectorAll(".tns-controls-button"); 1286 let indexTnsBtnControls = $tnsBtnControls.length; 1287 if (indexTnsBtnControls < 1) { 1288 return; 1289 } 1290 while (indexTnsBtnControls) { 1291 let $btnControl = $tnsBtnControls[indexTnsBtnControls - 1]; 1292 1293 /* Au clic sur les boutons de contrôle, mise à jour de l'item actif */ 1294 $btnControl.addEventListener("click", () => { 1295 $btnControl.setAttribute("disabled", "disabled"); 1296 setTimeout(() => { 1297 $btnControl.removeAttribute("disabled"); 1298 }, 200); 1299 let $currentSlider = $btnControl.closest(".tns-outer"); 1300 doScrollSlider($currentSlider, $btnControl); 1301 1302 $currentSlider = null; 1303 }); 1304 indexTnsBtnControls--; 1305 } 1306 $tnsOuters = indexTnsBtnControls = null; 1307} 1308 1309/** 1310 * Anime le scroll du slider vers la gauche ou la droite 1311 * @$currentSlider Le slider sur lequel appliquer l'animation 1312 * @$btnControl Le bouton cliqué 1313 */ 1314function doScrollSlider($currentSlider, $btnControl) { 1315 let $slider = $currentSlider.querySelector(".tns-slider"); 1316 let newPosition = $slider.scrollLeft; 1317 let isForward = true; 1318 if ("prec" === $btnControl.getAttribute("data-controls")) { 1319 newPosition -= $currentSlider.querySelector( 1320 ".tns-item:first-child" 1321 ).offsetWidth; 1322 isForward = false; 1323 } else { 1324 newPosition += $currentSlider.querySelector( 1325 ".tns-item:first-child" 1326 ).offsetWidth; 1327 } 1328 $slider.scroll({ 1329 left: newPosition, 1330 behavior: "smooth", 1331 duration: 500, 1332 }); 1333 if ($currentSlider.closest("#offre-de-soins")) { 1334 let div = document.createElement("div"); 1335 if (isForward) { 1336 let $firstItem = $currentSlider.querySelector(".slide:first-child"); 1337 div.innerHTML = $firstItem.outerHTML.trim(); 1338 $currentSlider.querySelector(".slides").appendChild(div.firstChild); 1339 $firstItem.remove(); 1340 $firstItem = null; 1341 } else { 1342 let $lastItem = $currentSlider.querySelector(".slide:last-child"); 1343 div.innerHTML = $lastItem.outerHTML.trim(); 1344 $currentSlider.querySelector(".slides").prepend(div.firstChild); 1345 $lastItem.remove(); 1346 $lastItem = null; 1347 } 1348 div = null; 1349 } 1350 1351 $slider = null; 1352 newPosition = null; 1353} 1354 1355/** 1356 * Détermine s'il existe des sliders dans la page et affiche ou non les contrôles en fonction de la taille de la fenêtre et de la quantité d'items 1357 */ 1358function refreshTnsSliders() { 1359 let $tnsOuters = document.querySelectorAll(".tns-outer"); 1360 let indexTnsOuters = $tnsOuters.length; 1361 if (indexTnsOuters < 1) { 1362 return; 1363 } 1364 while (indexTnsOuters) { 1365 let $tnsOuter = $tnsOuters[indexTnsOuters - 1]; 1366 let totalItemsWidth = updateTnsControls( 1367 $tnsOuter, 1368 getTnsCurrentDisplayedItemIndex($tnsOuter) 1369 ); 1370 if ($tnsOuter.offsetWidth > totalItemsWidth) { 1371 $tnsOuter.querySelector(".tns-controls").style.display = "none"; 1372 } else { 1373 $tnsOuter.querySelector(".tns-controls").style.display = "block"; 1374 } 1375 1376 totalItemsWidth = $tnsOuter = null; 1377 indexTnsOuters--; 1378 } 1379 $tnsOuters = indexTnsOuters = null; 1380} 1381 1382/** 1383 * Retrouve l'index de l'item actuellement affiché dans un slider 1384 * @param {any} $currentSlider Le conteneur du slider ayant la classe "tns-outer" 1385 */ 1386function getTnsCurrentDisplayedItemIndex($currentSlider) { 1387 if (!$currentSlider) { 1388 return; 1389 } 1390 let value; 1391 let $tnsItems = $currentSlider.querySelectorAll(".tns-item");
1392 [].slice.call($tnsItems).forEach(function ($element, index) { 1393 if ( 1394 Math.abs( 1395 $element.getBoundingClientRect().left - 1396 $currentSlider.getBoundingClientRect().left 1397 ) < $tnsItems[0].offsetWidth 1398 ) { 1399 value = index; 1400 return; 1401 } 1402 }); 1403 return value; 1404} 1405 1406/** 1407 * Mets à jour l'affichage des boutons de contrôle du slider 1408 * @param {any} $currentSlider l'élément ayant la classe "tns-outer" 1409 * @param {any} nextIndex l'index de l'item que l'on veut afficher 1410 * @return {number} la largeur totale du slider en ajoutant tous ses éléments 1411 */ 1412function updateTnsControls($currentSlider, nextIndex) { 1413 let currentTotalItems = $currentSlider.querySelectorAll(".tns-item").length; 1414 const totalItemsWidth = 1415 currentTotalItems * 1416 $currentSlider.querySelector(".tns-item:first-child").offsetWidth; 1417 if ($currentSlider.offsetWidth < totalItemsWidth) { 1418 if (nextIndex < 1) { 1419 $currentSlider.querySelector( 1420 '.tns-controls-button[data-controls="prec"]' 1421 ).style.display = "none"; 1422 nextIndex = 0; 1423 } else { 1424 $currentSlider.querySelector( 1425 '.tns-controls-button[data-controls="prec"]' 1426 ).style.display = "block"; 1427 } 1428 if (nextIndex >= currentTotalItems - 1) { 1429 nextIndex = currentTotalItems - 1; 1430 $currentSlider.querySelector( 1431 '.tns-controls-button[data-controls="suiv"]' 1432 ).style.display = "none"; 1433 } else { 1434 $currentSlider.querySelector( 1435 '.tns-controls-button[data-controls="suiv"]' 1436 ).style.display = "block"; 1437 } 1438 } 1439 1440 currentTotalItem = null; 1441 return totalItemsWidth; 1442} 1443 1444/** 1445 * Ajoute le listeners scroll sur le slider 1446 * @param {any} $currentSlider l'élément ayant la classe "tns-outer" 1447 */ 1448function addEventSlide($currentSlider) { 1449 $currentSlider.querySelector(".tns-slider").addEventListener( 1450 "scroll", 1451 debounce(function () { 1452 setTimeout(() => { 1453 updateTnsControls( 1454 $currentSlider, 1455 getTnsCurrentDisplayedItemIndex($currentSlider) 1456 ); 1457 }, 100); 1458 }, 50) 1459 ); 1460} 1461 1462/******** FIN SLIDERS ********/
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.