1// Landing-page side popover menu - open/close, submenus, ESC, overlay, link-follow. 2// jQuery is provided globally by scripts-combined.min.js (jQuery 3.6). 3(function ($) { 4 if (!$) { return; } 5 6 $(function () { 7 var $menu = $('[data-landing-menu]'); 8 if (!$menu.length || $menu.data('landingMenuBound')) { return; } 9 $menu.data('landingMenuBound', true); 10 11 var $body = $('body'); 12 var OPEN_CLASS = '-is-open'; 13 var BODY_CLASS = '-mobile-nav-is-open'; 14 var HAS_SUBMENU_CLASS = '-has-active-submenu'; 15 var ACTIVE_CLASS = '-is-active'; 16 var ANIMATING_CLASS = '-is-animating'; 17 var VISIBLE_CLASS = '-is-visible'; 18 var DESKTOP_QUERY = '(min-width: 992px)'; 19 var REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)'; 20 var INLINE_ANIMATION_MS = 320; 21 var desktopMedia = window.matchMedia ? window.matchMedia(DESKTOP_QUERY) : null; 22 var reducedMotionMedia = window.matchMedia ? window.matchMedia(REDUCED_MOTION_QUERY) : null; 23 var activeSubmenuKey = null; 24 25 var $submenuPanel = $menu.find('[data-landing-menu-submenu-panel]'); 26 var $submenuToggles = $menu.find('[data-landing-menu-submenu-toggle]'); 27 var $inlineSubmenus = $menu.find('[data-landing-menu-submenu]'); 28 var $desktopSubmenuLists = $menu.find('[data-landing-menu-submenu-list]'); 29 var $groupToggles = $menu.find('[data-submenu-group-toggle]'); // NEW 30 31 function isOpen() { return $menu.hasClass(OPEN_CLASS); } 32 function isDesktop() { return desktopMedia ? desktopMedia.matches : window.innerWidth >= 992; } 33 function hasReducedMotion() { return reducedMotionMedia ? reducedMotionMedia.matches : false; } 34 35 function setHidden($element, hidden) { 36 if (!$element.length) { return; } 37 $element.prop('hidden', hidden); 38 $element.attr('aria-hidden', hidden ? 'true' : 'false'); 39 } 40 41 function setFocusable($element, isFocusable) { 42 $element.find('a, button, input, select, textarea, [tabindex]').each(function () { 43 var $focusable = $(this); 44 45 if (isFocusable) { 46 var previousTabIndex = $focusable.attr('data-landing-menu-prev-tabindex'); 47 if (previousTabIndex) { 48 $focusable.attr('tabindex', previousTabIndex); 49 } else { 50 $focusable.removeAttr('tabindex'); 51 } 52 $focusable.removeAttr('data-landing-menu-prev-tabindex'); 53 return; 54 } 55 56 if (!$focusable.is('[data-landing-menu-prev-tabindex]') && $focusable.is('[tabindex]')) { 57 $focusable.attr('data-landing-menu-prev-tabindex', $focusable.attr('tabindex')); 58 } 59 $focusable.attr('tabindex', '-1'); 60 }); 61 } 62 63 function getSubmenuKey($element) { 64 return $element.attr('data-submenu-key') || $element.attr('data-landing-menu-submenu-toggle'); 65 } 66 67 function updateToggleState(key) { 68 $submenuToggles.each(function () { 69 var $toggle = $(this); 70 var isActive = getSubmenuKey($toggle) === key; 71 72 $toggle.attr('aria-expanded', isActive ? 'true' : 'false'); 73 $toggle.toggleClass(ACTIVE_CLASS, isActive); 74 $toggle.closest('[data-landing-menu-submenu-item]').toggleClass(ACTIVE_CLASS, isActive); 75 }); 76 } 77 78 // NEW: mirrors updateToggleState(), but for the nested group toggles 79 // (Special Interest / Seasonal Holidays) inside the Holiday Types panel. 80 function resetGroupToggles() { 81 $groupToggles.each(function () { 82 var $toggle = $(this); 83 var $target = $('#' + $toggle.attr('aria-controls')); 84 $toggle.attr('aria-expanded', 'false').removeClass(ACTIVE_CLASS); 85 $target.prop('hidden', true); 86 }); 87 } 88 89 function hideAllSubmenus() { 90 setHidden($submenuPanel, true); 91 setHidden($inlineSubmenus, true); 92 setHidden($desktopSubmenuLists, true); 93 $inlineSubmenus 94 .removeClass(VISIBLE_CLASS + ' ' + ANIMATING_CLASS) 95 .off('transitionend.landingMenuSubmenu') 96 .css('height', ''); 97 setFocusable($inlineSubmenus, false); 98 $menu.removeClass(HAS_SUBMENU_CLASS); 99 resetGroupToggles(); // NEW 100 } 101 102 function openInlineSubmenu($submenu) { 103 if (!$submenu.length) { return; } 104 105 window.clearTimeout($submenu.data('landingMenuTimer')); 106 $submenu.off('transitionend.landingMenuSubmenu'); 107 setHidden($submenu, false); 108 setFocusable($submenu, true); 109 110 if (hasReducedMotion()) { 111 $submenu.addClass(VISIBLE_CLASS).css('height', ''); 112 return; 113 } 114 115 $submenu 116 .removeClass(VISIBLE_CLASS) 117 .addClass(ANIMATING_CLASS) 118 .css('height', '0px'); 119 120 // Commit the collapsed height before moving to the measured open height. 121 $submenu[0].offsetHeight; 122 123 window.requestAnimationFrame(function () { 124 window.requestAnimationFrame(function () { 125 if ($submenu.prop('hidden')) { return; } 126 127 $submenu.addClass(VISIBLE_CLASS).css('height', $submenu[0].scrollHeight + 'px'); 128 $submenu.one('transitionend.landingMenuSubmenu', function (e) { 129 if (e.originalEvent && e.originalEvent.propertyName !== 'height') { return; } 130 if ($submenu.hasClass(VISIBLE_CLASS)) { 131 $submenu.removeClass(ANIMATING_CLASS).css('height', 'auto'); 132 } 133 }); 134 135 $submenu.data('landingMenuTimer', window.setTimeout(function () { 136 if ($submenu.hasClass(VISIBLE_CLASS)) { 137 $submenu.removeClass(ANIMATING_CLASS).css('height', 'auto'); 138 } 139 }, INLINE_ANIMATION_MS + 80)); 140 }); 141 }); 142 } 143 144 function closeInlineSubmenu($submenu, animate) { 145 if (!$submenu.length || $submenu.prop('hidden')) { return; } 146 147 window.clearTimeout($submenu.data('landingMenuTimer')); 148 $submenu.off('transitionend.landingMenuSubmenu'); 149 $submenu.attr('aria-hidden', 'true'); 150 setFocusable($submenu, false); 151 152 if (!animate || hasReducedMotion()) { 153 $submenu.removeClass(VISIBLE_CLASS + ' ' + ANIMATING_CLASS).css('height', ''); 154 setHidden($submenu, true); 155 return; 156 } 157 158 $submenu 159 .addClass(ANIMATING_CLASS) 160 .css('height', $submenu[0].scrollHeight + 'px'); 161 162 // Commit the current expanded height before collapsing to zero. 163 $submenu[0].offsetHeight; 164 165 window.requestAnimationFrame(function () { 166 $submenu.removeClass(VISIBLE_CLASS).css('height', '0px'); 167 }); 168 169 $submenu.one('transitionend.landingMenuSubmenu', function (e) { 170 if (e.originalEvent && e.originalEvent.propertyName !== 'height') { return; } 171 if (!$submenu.hasClass(VISIBLE_CLASS)) { 172 $submenu.removeClass(ANIMATING_CLASS).css('height', ''); 173 setHidden($submenu, true); 174 } 175 }); 176 177 $submenu.data('landingMenuTimer', window.setTimeout(function () { 178 if (!$submenu.hasClass(VISIBLE_CLASS)) { 179 $submenu.removeClass(ANIMATING_CLASS).css('height', ''); 180 setHidden($submenu, true); 181 } 182 }, INLINE_ANIMATION_MS + 60)); 183 } 184 185 function resetSubmenus() { 186 activeSubmenuKey = null; 187 $submenuToggles.attr('aria-expanded', 'false').removeClass(ACTIVE_CLASS); 188 $menu.find('[data-landing-menu-submenu-item]').removeClass(ACTIVE_CLASS); 189 resetGroupToggles(); // NEW 190 191 if (!isDesktop() && isOpen()) { 192 setHidden($submenuPanel, true); 193 setHidden($desktopSubmenuLists, true); 194 $inlineSubmenus.each(function () { 195 closeInlineSubmenu($(this), true); 196 }); 197 $menu.removeClass(HAS_SUBMENU_CLASS); 198 return; 199 } 200 201 hideAllSubmenus(); 202 } 203 204 function openSubmenu(key) { 205 activeSubmenuKey = key; 206 updateToggleState(key); 207 resetGroupToggles(); // NEW 208 209 if (isDesktop()) { 210 $inlineSubmenus.each(function () { 211 closeInlineSubmenu($(this), false); 212 }); 213 $desktopSubmenuLists.each(function () { 214 var $list = $(this); 215 setHidden($list, getSubmenuKey($list) !== key); 216 }); 217 setHidden($submenuPanel, false); 218 $menu.addClass(HAS_SUBMENU_CLASS); 219 return; 220 } 221 222 setHidden($submenuPanel, true); 223 setHidden($desktopSubmenuLists, true); 224 $inlineSubmenus.each(function () { 225 var $submenu = $(this); 226 if (getSubmenuKey($submenu) === key) { 227 openInlineSubmenu($submenu); 228 } else { 229 closeInlineSubmenu($submenu, true); 230 } 231 }); 232 $menu.removeClass(HAS_SUBMENU_CLASS); 233 } 234 235 function syncSubmenuLayout() { 236 if (activeSubmenuKey) { 237 openSubmenu(activeSubmenuKey); 238 return; 239 } 240 241 resetSubmenus(); 242 } 243 244 function openMenu() { 245 if (isOpen()) { return; } 246 $menu.addClass(OPEN_CLASS); 247 $body.addClass(BODY_CLASS); 248 window.setTimeout(function () { 249 $menu.find('[data-landing-menu-close]').first().trigger('focus'); 250 }, 200); 251 } 252 253 function closeMenu() { 254 if (!isOpen()) { return; } 255 $menu.removeClass(OPEN_CLASS); 256 $body.removeClass(BODY_CLASS); 257 resetSubmenus(); 258 } 259 260 // Hamburger trigger 261 $(document).on('click.landingMenu', '.l-landing-header__btn-nav', function (e) { 262 e.preventDefault(); 263 if (isOpen()) { closeMenu(); } else { openMenu(); } 264 }); 265 266 // Close button + overlay (delegated on the nav root so both work) 267 $menu.on('click.landingMenu', '[data-landing-menu-close]', function (e) { 268 e.preventDefault(); 269 closeMenu(); 270 }); 271 272 // Desktop opens a right-hand submenu panel. Mobile/tablet opens one accordion at a time. 273 $menu.on('click.landingMenu', '[data-landing-menu-submenu-toggle]', function (e) { 274 var key = getSubmenuKey($(this)); 275 276 e.preventDefault(); 277 if (!key) { return; } 278 279 if (!isDesktop() && activeSubmenuKey === key) { 280 resetSubmenus(); 281 return; 282 } 283 284 openSubmenu(key); 285 }); 286 287 // NEW: Special Interest / Seasonal Holidays group toggles - now wired into 288 // the same ACTIVE_CLASS / aria-expanded pattern as the top-level toggles, 289 // and behaves as an accordion (opening one closes the other). 290 $menu.on('click.landingMenu', '[data-submenu-group-toggle]', function (e) { 291 e.preventDefault(); 292 293 var $toggle = $(this); 294 var willExpand = $toggle.attr('aria-expanded') !== 'true'; 295 var $target = $('#' + $toggle.attr('aria-controls')); 296 297 $groupToggles.not($toggle).each(function () { 298 var $other = $(this); 299 $other.attr('aria-expanded', 'false').removeClass(ACTIVE_CLASS); 300 $('#' + $other.attr('aria-controls')).prop('hidden', true); 301 }); 302 303 $toggle.attr('aria-expanded', String(willExpand)).toggleClass(ACTIVE_CLASS, willExpand); 304 $target.prop('hidden', !willExpand); 305 }); 306 307 // ESC closes 308 $(document).on('keydown.landingMenu', function (e) { 309 if (e.key === 'Escape' && isOpen()) { closeMenu(); } 310 }); 311 312 if (desktopMedia) { 313 if (desktopMedia.addEventListener) { 314 desktopMedia.addEventListener('change', syncSubmenuLayout); 315 } else if (desktopMedia.addListener) { 316 desktopMedia.addListener(syncSubmenuLayout); 317 } 318 } else { 319 $(window).on('resize.landingMenu', syncSubmenuLayout); 320 } 321 322 // Auto-close when following an internal link 323 $menu.on('click.landingMenu', 'a[href]:not([href^="#"])', function () { 324 closeMenu(); 325 }); 326 });
327})(window.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.