1(function (Drupal, $) { 2 Drupal.behaviors.mainMenuA11y = { 3 attach: function (context) { 4 function getFocusableElements($container) { 5 return $container 6 .find('a[href]:not([tabindex="-1"]), button:not([disabled]), input, select, textarea, [tabindex="0"]') 7 .filter(':visible'); 8 } 9 function trapFocus($container) { 10 $container.on('keydown.focusTrap', function (e) { 11 // 1. Filter for Tab key immediately 12 if (e.key !== 'Tab') return; 13 14 const $focusable = getFocusableElements($(this)); 15 16 if ($focusable.length === 0) return; 17 18 const $first = $focusable.first(); 19 const $last = $focusable.last(); 20 21 if (e.shiftKey) { 22 if ($(document.activeElement).is($first)) { 23 e.preventDefault(); 24 $last.focus(); 25 } 26 } 27 else { 28 if ($(document.activeElement).is($last)) { 29 e.preventDefault(); 30 $first.focus(); 31 } 32 } 33 }); 34 } 35 function releaseFocus($container) { 36 $container.off('keydown.focusTrap'); 37 } 38 // Mobile menu accordion L1 39 if (!window.matchMedia('(min-width: 1200px)').matches) { 40 /* ========================================================= 41 * Helpers 42 * ======================================================= */ 43 44 function toggleSubmenu($submenu, open) { 45 $submenu.toggleClass('mobile-menu--active', open).attr('aria-hidden', String(!open)).prop('hidden', !open); 46 } 47 48 function closeAllLevel3() { 49 $('.first-child-level-link').removeClass('js-active'); 50 $('.sub-menu--level3.mobile').css('height', 0) 51 .attr('aria-hidden', 'true') 52 .prop('hidden', true); 53 54 } 55 56 /* ========================================================= 57 * L1 â Mobile Menu Toggle 58 * ======================================================= */ 59 60 $(once('main-header__mobile-toggle', '.main-header__mobile-toggle', context)) 61 .each(function () { 62 const $toggle = $(this); 63 const $mainContent = $('.main-content'); 64 65 $toggle.on('click', function () { 66 const isOpen = !$toggle.hasClass('js-active'); 67 68 $toggle 69 .toggleClass('js-active', isOpen) 70 .attr('aria-expanded', isOpen); 71 72 $('body').toggleClass('js-no-scroll', isOpen); 73 $mainContent.toggleClass('menu-hover', isOpen); 74 75 const $navWrap = $toggle.parents('.main-header__main-nav-wrap'); 76 const $nav = $navWrap.find('.main-nav'); 77 78 $navWrap.toggleClass('js-active', isOpen); 79 80 $nav.slideToggle(300, function () { 81 if (isOpen) { 82 // trapFocus($nav); 83 getFocusableElements($nav).first().focus(); 84 } else { 85 // releaseFocus($nav); 86 $toggle.focus(); 87 } 88 }); 89 }); 90 }); 91 92 /* ========================================================= 93 * L2 â Second Level Submenu 94 * ======================================================= */ 95 96 $(once('main-menu-level2', '.main-menu--first > li > button', context)) 97 .each(function () { 98 99 $(this).on('click keydown', function (e) { 100 if (e.type === 'keydown' && e.key !== 'Escape') return; 101 102 const $this = $(this); 103 const $submenu = $this.siblings('.main-menu--second'); 104 const willOpen = !$submenu.hasClass('mobile-menu--active'); 105 // ESC â close L2 only 106 if (e.key === 'Escape') { 107 e.preventDefault(); 108 e.stopPropagation(); 109 110 $this.attr('aria-expanded', 'false'); 111 toggleSubmenu($submenu, false); 112 $this.parent().removeClass('js-active'); 113 114 closeAllLevel3(); 115 $this.focus(); 116 if (willOpen && $('.main-header__mobile-toggle').hasClass('js-active')) { 117 const $l1Toggle = $('.main-header__mobile-toggle'); 118 $l1Toggle.trigger('click').focus(); 119 } 120 return; 121 } 122 123 124 $this.attr('aria-expanded', willOpen); 125 toggleSubmenu($submenu, willOpen); 126 $this.parent().toggleClass('js-active', willOpen); 127 128 closeAllLevel3(); 129 }); 130 }); 131 132 /* ========================================================= 133 * L3 â Third Level Submenu 134 * ======================================================= */ 135 136 $(once('main-menu-level3', '.first-child-level-link .sub-child', context)) 137 .each(function () { 138 139 $(this).on('click keydown', function (e) { 140 141 if (e.type === 'keydown' && e.key !== 'Escape') return; 142 143 if (e.type === 'click' && window.matchMedia('(max-width: 1199px)').matches) { 144 e.preventDefault(); 145 } 146 147 const $toggle = $(this); 148 const $parentLi = $toggle.parent('.first-child-level-link'); 149 const $parentL1 = $toggle.closest('li.main-link');
150 const $submenu = $parentLi.children('.sub-menu--level3.mobile'); 151 152 const willOpen = !$parentLi.hasClass('js-active'); 153 154 // ESC â close L3 only 155 if (e.key === 'Escape') { 156 e.preventDefault(); 157 e.stopPropagation(); 158 159 $parentLi.removeClass('js-active'); 160 toggleSubmenu($submenu, false); 161 $submenu.css('height', 0); 162 163 $toggle.attr('aria-expanded', 'false').focus(); 164 if (willOpen) { 165 $parentL1.children('.main-link-trigger').trigger('click').focus(); 166 } 167 168 return; 169 } 170 171 172 closeAllLevel3(); 173 174 if (willOpen) { 175 $parentLi.addClass('js-active'); 176 toggleSubmenu($submenu, true); 177 178 let totalHeight = 0; 179 $submenu.find('.last-link').each(function () { 180 totalHeight += $(this).outerHeight(true); 181 }); 182 183 $submenu.css('height', totalHeight); 184 } else { 185 $parentLi.removeClass('js-active'); 186 toggleSubmenu($submenu, false); 187 $submenu.css('height', 0); 188 } 189 190 $toggle.attr('aria-expanded', willOpen); 191 }); 192 }); 193 194 $(once('main-menu-level3-2', '.main-menu--second .subsection-single-link-wrapper .last-link > a', context)) 195 .each(function () { 196 $(this).on('keydown', function (e) { 197 if (e.type === 'keydown' && e.key !== 'Escape') return; 198 e.preventDefault(); 199 e.stopPropagation(); 200 $(this).closest('li.main-link').children('.main-link-trigger').trigger('click').focus(); 201 }); 202 }); 203 $(once('main-menu-level3-3','#main-navigation .main-menu--second .my123-menu-icon ul.dropdown li > a', context)) 204 .each(function () { 205 $(this).on('keydown', function (e) { 206 if (e.type === 'keydown' && e.key !== 'Escape') return; 207 e.preventDefault(); 208 e.stopPropagation(); 209 $(this).closest('li.header-right--mymenu.main-link').children('.mobile-trigger').trigger('click').focus(); 210 }); 211 }); 212 213 $(once('main-menu-level3-4','#search-block-form input', context)) 214 .each(function () { 215 $(this).on('keydown', function (e) { 216 if (e.type === 'keydown' && e.key !== 'Escape') return; 217 e.preventDefault(); 218 e.stopPropagation(); 219 $(this).closest('li.header-right--search.main-link').children('.mobile-trigger').trigger('click').focus(); 220 }); 221 }); 222 223 /* ========================================================= 224 * L4 â Fourth Level Submenu Toggle 225 * ======================================================= */ 226 227 $(once('main-menu-level4', '.sub-menu--level3.mobile .last-link', context)) 228 .each(function () { 229 230 $(this).on('keydown', function (e) { 231 if (e.type === 'keydown' && e.key !== 'Escape') return; 232 e.preventDefault(); 233 e.stopPropagation(); 234 const $toggle = $(this); 235 const $parentMenu = $toggle.parent('.sub-menu--level3.mobile'); 236 const $parentLi = $parentMenu.parent('.first-child-level-link'); 237 $parentLi.children('.sub-child').attr('aria-expanded', 'false').focus(); 238 $parentLi.removeClass('js-active'); 239 toggleSubmenu($parentMenu, false); 240 $parentMenu.css('height', 0); 241 return; 242 }); 243 }); 244 } 245 else { 246 const link = $('.first-child-level-link'); 247 const mainContent = $('.main-content'); 248 const icon = $('.triangle-icon'); 249 let previousLinkData = null; 250 251 const windowHeight = window.innerHeight; 252 const isMobile = navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/); 253 254 if (windowHeight < 800 && !isMobile) { 255 $('.subsection-first-level-wrapper').addClass('small-screens'); 256 } 257 258 const resetHoverState = () => { 259 link.removeClass('is-hover'); 260 $('.main-image').show(); 261 $('.subsection-menu-image-wrapper .sub-image div').removeClass('is-hover'); 262 $('.subsection-second-level-links > ul').removeClass('is-hover'); 263 }; 264 $(once('main-menu-level2-desktop', '.first-child-level-link', context)) 265 .each(function () { 266 $(this).on('mouseenter keydown click', function(e) { 267 268 if (e.type === 'click') { 269 let anchortag = $(this).children('button.sub-child').data('target') 270 let anchorLink = "" 271 const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Windows Phone|webOS/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); 272 const hasDesktopAnchor = $(this).children('a').length > 0; 273 if (!isMobile && !hasDesktopAnchor) { 274 anchortag = $(this).children('button.sub-child').attr('aria-controls') 275 anchorLink = $(`#${anchortag}`).find('a').first().attr('href') 276 } 277 else { 278 anchorLink = $(`#${anchortag}`).attr('href') 279 } 280 if(anchorLink && anchorLink !== "#") { 281 window.location.href = anchorLink; 282 } 283 return; 284 } 285 286 const linkData = $(this).data('target'); 287 if (e.type === 'keydown') { 288 if (e.key === 'Tab') { 289 if(!$(`#${linkData}`).hasClass('is-hover'))return; 290 e.preventDefault(); 291 e.stopPropagation(); 292 $(`#${linkData}, li.${linkData}`).addClass('is-hover'); 293 $(`#${linkData}`).find('a') 294 $(`#${linkData}`).find('a').eq(1).focus() 295 trapFocus($(`#${linkData}`)) 296 } 297 if (e.key === 'Tab') { 298 } 299 if (e.key !== 'Enter' && e.key !== ' ') return; 300 e.preventDefault(); 301 e.stopPropagation(); 302 releaseFocus($(this)) 303 $(`li.${linkData}`).children('.sub-child').attr('aria-expanded', 'true') 304 // $(this).blur(); 305 setTimeout(() => { 306 $(this).children('.sub-child').focus(); 307 }, 50); 308 if (previousLinkData === linkData) { 309 resetHoverState(); 310 previousLinkData = null; 311 $(`li.${linkData}`).children('.sub-child').attr('aria-expanded', 'false') 312 setTimeout(() => { 313 $(this).children('.sub-child').focus(); 314 }, 50); 315 return; 316 } 317 } 318 319 if (!linkData) return; 320 321 if (previousLinkData && previousLinkData !== linkData) { 322 $(`#${previousLinkData}-image`).removeClass('is-hover'); 323 $(`#${previousLinkData}, .${previousLinkData}`).removeClass('is-hover'); 324 } 325 326 previousLinkData = linkData; 327 328 $('.main-image').hide(); 329 $(`#${linkData}-image`).addClass('is-hover'); 330 $(`#${linkData}, li.${linkData}`).addClass('is-hover'); 331 $(`#${linkData}`).attr('aria-hidden', 'false'); 332 }); 333 }); 334 335 $('.subsection-links-container').on('mouseleave', () => { 336 resetHoverState(); 337 previousLinkData = null; 338 });
339 $(once('main-menu-level3-desktop', '.sub-menu--level3.desktop', context)) 340 .each(function () { 341 $(this).on('keydown', function(e) { 342 if (e.type === 'keydown' && e.key !== 'Escape') return; 343 e.preventDefault(); 344 e.stopPropagation(); 345 releaseFocus($(this)) 346 resetHoverState(); 347 previousLinkData = null; 348 const id = $(this).prop('id') 349 const parentMenu = $(this).closest('.subsection-links-container').children('ul') 350 trapFocus(parentMenu) 351 352 $(`li.${id}`).children('.sub-child').attr('aria-expanded', 'false').focus() 353 }); 354 }); 355 356 357 $('.main-menu.main-menu--first').hover( 358 () => mainContent.attr('aria-expanded', 'true'), 359 () => mainContent.attr('aria-expanded', 'false') 360 ); 361 // Adding support for focus as well 362 const $menu = $('.main-menu--first', context); 363 364 var mainHeader = $('.main-header'); 365 366 $menu.on('focusin', () => { 367 mainContent.attr('aria-expanded', 'true'); 368 mainHeader.addClass('is-hover'); 369 }); 370 371 $menu.on('focusout', () => { 372 mainContent.attr('aria-expanded', 'false'), 373 mainHeader.removeClass('is-hover'); 374 }); 375 376 $('.header-menu-list').hover( 377 function() { 378 379 $('.main-link').hover(function() { 380 const linkData = $(this).data('target'); 381 if (previousLinkData !== linkData) { 382 if (previousLinkData) { 383 $(`.${previousLinkData}`).removeClass('is-hover'); 384 } 385 previousLinkData = linkData; 386 } 387 $(`.${linkData}`).addClass('is-hover'); 388 mainHeader.addClass('is-hover'); 389 390 }); 391 }, 392 function() { 393 $('.main-link li').removeClass('is-hover'); 394 } 395 ); 396 397 function moveIcon() { 398 const link = $(this); 399 const position = link.position(); 400 const width = link.width(); 401 402 icon.css({ 403 left: position.left + (width / 2) - (icon.width() / 2) 404 }); 405 } 406 407 $('.main-link').on('mouseenter focusin', moveIcon); 408 const triggers = $menu.find('.main-link-trigger'); 409 410 /* ---------------------------- 411 * Utilities 412 * ---------------------------- */ 413 414 function closeAll() { 415 416 $menu.find('[aria-hidden="false"]').attr('aria-hidden', 'true'); 417 $menu.find('.is-hover').removeClass('is-hover'); 418 triggers.attr('aria-expanded', 'false'); 419 $menu.find('li[aria-expanded="true"]').attr('aria-expanded', 'false'); 420 } 421 422 function openMenu($btn) { 423 const panelId = $btn.attr('aria-controls'); 424 if (!panelId) return; 425 426 closeAll(); 427 428 $btn.attr('aria-expanded', 'true'); 429 $btn.closest('li').attr('aria-expanded', 'true'); 430 $('#' + panelId).attr('aria-hidden', 'false'); 431 const targetMenuList = $('#' + panelId).find('.subsection-links-container').children('ul') 432 trapFocus(targetMenuList) 433 } 434 435 function toggleMenu($btn) { 436 const panelId = $btn.attr('aria-controls'); 437 if (!panelId) return; 438 439 const expanded = $btn.attr('aria-expanded') === 'true'; 440 $('#' + panelId).prop('hidden', expanded); 441 expanded ? closeAll() : openMenu($btn); 442 } 443 444 /* ---------------------------- 445 * Top-level keyboard 446 * ---------------------------- */ 447 448 triggers.on('keydown', function (e) { 449 const $current = $(this); 450 const index = triggers.index(this); 451 if (e.code === 'Space' || e.key === ' ') { 452 e.preventDefault(); 453 toggleMenu($current); 454 } 455 switch (e.key) { 456 case 'Enter': 457 e.preventDefault(); 458 toggleMenu($current); 459 break; 460 case 'ArrowDown': 461 e.preventDefault(); 462 openMenu($current); 463 break; 464 465 case 'ArrowRight': 466 e.preventDefault(); 467 closeAll(); 468 triggers.eq((index + 1) % triggers.length).focus(); 469 break; 470 471 case 'ArrowLeft': 472 e.preventDefault(); 473 closeAll(); 474 triggers.eq((index - 1 + triggers.length) % triggers.length).focus(); 475 break; 476 477 case 'Escape': 478 e.preventDefault(); 479 closeAll(); 480 $current.focus(); 481 break; 482 } 483 }); 484 485 /* ---------------------------- 486 * ESC anywhere inside menu 487 * ---------------------------- */ 488 $menu.on('keydown', function (e) { 489 if (e.key === 'Escape') { 490 console.log("escape") 491 triggers.filter('[aria-expanded="true"]').focus(); 492 closeAll(); 493 } 494 }); 495 } 496 } 497 }; 498 499})(Drupal, jQuery);
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.