1/** 2 * Header navigation with all the possible states 3 * 4 * @requires $us.canvas 5 */ 6! function( $, _undefined ) { 7 "use strict"; 8 9 window.$us = window.$us || {}; 10 11 $us.mobileNavOpened = 0; 12 13 const SLIDE_DURATION = 250; 14 15 function usNav( container ) { 16 const self = this; 17 18 self.$container = $( container ); 19 if ( self.$container.length === 0 ) { 20 return; 21 } 22 23 // Elements 24 self.$items = $( '.menu-item', self.$container ); 25 self.$list = $( '.w-nav-list.level_1', self.$container ); 26 self.$anchors = $( '.w-nav-anchor', self.$container ); 27 self.$arrows = $( '.w-nav-arrow', self.$container ); 28 self.$mobileMenu = $( '.w-nav-control', self.$container ); 29 self.$itemsHasChildren = $( '.menu-item-has-children', self.$list ); 30 self.$childLists = $( '.menu-item-has-children > .w-nav-list', self.$list ); 31 self.$reusableBlocksLinks = $( '.menu-item-object-us_page_block a', self.$container ); 32 33 // Private "Variables" 34 self.type = self.$container.usMod( 'type' ); 35 self.layout = self.$container.usMod( 'layout' ); 36 self.openDropdownOnClick = self.$container.hasClass( 'open_on_click' ); 37 self.mobileNavOpened = false; 38 self.keyboardNavEvent = false; 39 self.opts = {}; 40 41 // Bindable events 42 self._events = { 43 closeMobileMenu: self.closeMobileMenu.bind( self ), 44 closeMobileMenuOnClick: self.closeMobileMenuOnClick.bind( self ), 45 closeMobileMenuOnFocusIn: self.closeMobileMenuOnFocusIn.bind( self ), 46 closeOnClickOutside: self.closeOnClickOutside.bind( self ), 47 handleKeyboardNav: self.handleKeyboardNav.bind( self ), 48 handleMobileClick: self.handleMobileClick.bind( self ), 49 resize: self.resize.bind( self ), 50 toggleMenuOnClick: self.toggleMenuOnClick.bind( self ), 51 toggleMobileMenu: self.toggleMobileMenu.bind( self ), 52 } 53 54 // Load nav options 55 const $opts = $( '.w-nav-options:first', self.$container ); 56 if ( $opts.is( '[onclick]' ) ) { 57 self.opts = $opts[0].onclick() || {}; 58 $opts.remove(); 59 } 60 61 // Events 62 self.$container 63 // Detach animation after transition 64 .on( 'transitionend', () => self.$container.removeClass( 'us_animate_this' ) ) 65 // Keyboard navigation accessibility 66 .on( 'keydown.upsolution', self._events.handleKeyboardNav ) 67 .on( 'click', '.w-nav-close', self._events.closeMobileMenu ) 68 .on( 'click', '.w-nav-anchor, .w-hwrapper-link', self._events.closeMobileMenuOnClick ) 69 .on( 'click', '.w-nav-control', self._events.toggleMobileMenu ) 70 .on( 'closeMobileMenu', self._events.closeMobileMenu ); 71 72 // Open dropdown on click 73 if ( self.openDropdownOnClick ) { 74 self.$container.on( 'click', '.menu-item.togglable > .w-nav-anchor', self._events.toggleMenuOnClick ); 75 76 // Close on outside menu click 77 $us.$document.on( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside ); 78 } 79 80 // Triggered in `$us.header.setView()` 81 $us.$document 82 .on( 'usHeader.update_view', self._events.resize ); 83 84 $us.$window 85 .on( 'resize', $ush.debounce( self._events.resize, 5 ) ); 86 87 // Fix "Safari outline webkit bug #243289" 88 if ( $ush.isSafari ) { 89 self.$mobileMenu.on( 'mouseup', () => { 90 self.$mobileMenu.attr( 'style', 'outline: none' ); 91 } ); 92 } 93 94 // Add anchors from reusable blocks 95 self.$reusableBlocksLinks.each( ( index, anchor ) => { 96 if ( $( anchor ).parents( '.w-popup-wrap' ).length === 0 ) { 97 self.$anchors.push( anchor ); 98 } 99 } ); 100
101 // Close the main menu on tablets after a submenu click (since tablets donât support hover) 102 if ( $.isMobile && self.type === 'desktop' ) { 103 self.$list.on( 'click', '.w-nav-anchor[class*="level_"]', ( e ) => { 104 const $target = $( e.currentTarget ); 105 const $menuItem = $target.closest( '.menu-item' ); 106 if ( $target.usMod('level') > 1 && ! $menuItem.hasClass( 'menu-item-has-children' ) ) { 107 $target.parents( '.menu-item.opened' ).removeClass( 'opened' ); 108 } 109 } ); 110 } 111 112 self.$itemsHasChildren.each( ( _, menuItem ) => { 113 const $menuItem = $( menuItem ); 114 115 // Mark toggleable items (skip descendants of mega menus) 116 const isInMegaMenu = $menuItem.parents( '.menu-item.has_cols, .menu-item.has_side_panel' ).length > 0; 117 if ( ! isInMegaMenu ) { 118 $menuItem.addClass( 'togglable' ); 119 } 120 121 // Behavior of menu items on mobile 122 const $arrow = $( '.w-nav-arrow:first', $menuItem ); 123 const $subAnchor = $( '.w-nav-anchor:first', $menuItem ); 124 const dropByLabel = $menuItem.hasClass( 'mobile-drop-by_label' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_label' ); 125 const dropByArrow = $menuItem.hasClass( 'mobile-drop-by_arrow' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_arrow' ); 126 127 if ( dropByLabel || ( self.opts.mobileBehavior && ! dropByArrow ) ) { 128 $subAnchor.on( 'click', self._events.handleMobileClick ); 129 } else { 130 $arrow.on( 'click', self._events.handleMobileClick ); 131 $arrow.on( 'click', self._events.handleKeyboardNav ); 132 } 133 } ); 134 135 // Touchscreen handling on desktop devices 136 if ( ! $us.$html.hasClass( 'no-touch' ) ) { 137 self.$list.on( 'click', '.menu-item-has-children.togglable > .w-nav-anchor', ( e ) => { 138 if ( self.type === 'mobile' ) { 139 return; 140 } 141 e.preventDefault(); 142 const $target = $( e.currentTarget ); 143 const $menuItem = $target.parent(); 144 if ( $menuItem.hasClass( 'opened' ) ) { 145 return location.assign( $target.attr( 'href' ) ); 146 } 147 $menuItem.addClass( 'opened' ); 148 const onOutsideClick = ( e ) => { 149 if ( $.contains( $menuItem[0], e.target ) ) { 150 return; 151 } 152 $menuItem.removeClass( 'opened' ); 153 $us.$body.off( 'touchstart', onOutsideClick ); 154 }; 155 $us.$body.on( 'touchstart.noPreventDefault', onOutsideClick ); 156 } ); 157 } 158 159 $us.$document.on( 'keydown', ( e ) => { 160 161 // Fix Safari outline webkit bug #243289 162 if ( $ush.isSafari ) { 163 self.$mobileMenu.removeAttr( 'style' ); 164 } 165 166 // Close mobile menu on ESC 167 if ( e.keyCode === $ush.ESC_KEYCODE && self.type === 'mobile' && self.mobileNavOpened ) { 168 self.closeMobileMenu(); 169 } 170 171 // Close submenus when navigating outside the menu 172 if ( e.keyCode === $ush.TAB_KEYCODE && self.type === 'desktop' && ! $( e.target ).closest( '.w-nav' ).length ) { 173 self.$items.removeClass( 'opened' ); 174 } 175 } ); 176 177 $ush.timeout( () => { 178 self.resize(); 179 $us.header.$container.trigger( 'contentChange' ); 180 }, 50 ); 181 }; 182 183 // Mobile navigation API. 184 $.extend( usNav.prototype, { 185 186 /** 187 * Mobile menu toggle button. 188 * 189 * @param {Event} e 190 */ 191 toggleMobileMenu: function( e ) { 192 const self = this; 193 194 e.preventDefault(); 195 196 self.mobileNavOpened = ! self.mobileNavOpened; 197 198 // Close on outside menu click 199 $us.$document.on( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside ); 200 201 // Make empty links focusable and clickable to open dropdown 202 self.$anchors.each( ( _, node ) => { node.href = node.href || 'javascript:void(0)'; } ); 203 204 if ( self.mobileNavOpened ) { 205 206 // Close other opened menus 207 $( '.l-header .w-nav' ).not( self.$container ).each( ( _, node ) => { 208 $( node ).trigger( 'closeMobileMenu' ); 209 } ); 210 211 // Close opened sublists 212 self.$mobileMenu.addClass( 'active' ).focus(); 213 self.$items.filter( '.opened' ).removeClass( 'opened' ); 214 self.$childLists.resetInlineCSS( 'display', 'height', 'opacity' ); 215 if ( self.layout === 'dropdown' ) { 216 self.$list.slideDownCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) ); 217 } 218 $us.$html.addClass( 'w-nav-open' ); 219 self.$mobileMenu.attr( 'aria-expanded', 'true' ); 220 $us.mobileNavOpened++; 221
222 // Close mobile menu when focus is outside the menu 223 $us.$document.on( 'focusin', self._events.closeMobileMenuOnFocusIn ); 224 225 } else { 226 self.closeMobileMenu(); 227 } 228 229 $us.$canvas.trigger( 'contentChange' ); 230 }, 231 232 /** 233 * Handle click on mobile. 234 * 235 * @param {Event} e 236 */ 237 handleMobileClick: function( e ) { 238 const self = this; 239 if ( self.type === 'mobile' ) { 240 e.stopPropagation(); 241 e.preventDefault(); 242 const $menuItem = $( e.currentTarget ).closest( '.menu-item' ); 243 self.switchMobileSubMenu( $menuItem, ! $menuItem.hasClass( 'opened' ) ); 244 } 245 }, 246 247 /** 248 * Switch mobile submenu items. 249 * 250 * @param {Node} $menuItem 251 * @param {Boolean} opened 252 */ 253 switchMobileSubMenu: function( $menuItem, opened ) { 254 const self = this; 255 if ( self.type !== 'mobile' ) { 256 return; 257 } 258 const $subMenu = $menuItem.children( '.w-nav-list' ); 259 if ( opened ) { 260 $menuItem.addClass( 'opened' ); 261 $subMenu.slideDownCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) ); 262 } else { 263 $menuItem.removeClass( 'opened' ); 264 $subMenu.slideUpCSS( SLIDE_DURATION, () => $us.header.$container.trigger( 'contentChange' ) ); 265 } 266 }, 267 268 /** 269 * Close mobile menu on focus leave. 270 * 271 * @event handler 272 */ 273 closeMobileMenuOnFocusIn: function() { 274 const self = this; 275 if ( ! $.contains( self.$container[0], document.activeElement ) ) { 276 self.closeMobileMenu(); 277 } 278 }, 279 280 /** 281 * Close mobile menu. 282 */ 283 closeMobileMenu: function() { 284 const self = this; 285 if ( self.type !== 'mobile' ) { 286 return; 287 } 288 self.mobileNavOpened = false; 289 self.$mobileMenu.removeClass( 'active' ); 290 $us.$html.removeClass( 'w-nav-open' ); 291 self.$mobileMenu.attr( 'aria-expanded', 'false' ); 292 293 if ( self.$list && self.layout === 'dropdown' ) { 294 self.$list.slideUpCSS( SLIDE_DURATION ); 295 } 296 297 $us.mobileNavOpened--; 298 self.$mobileMenu[0].focus(); 299 $us.$canvas.trigger( 'contentChange' ); 300 301 $us.$document 302 .off( 'focusin', self._events.closeMobileMenuOnFocusIn ) 303 .off( 'mouseup touchend.noPreventDefault', self._events.closeOnClickOutside ); 304 }, 305 306 /** 307 * Close on anchor click (mobile). 308 * 309 * @param {Event} e 310 */ 311 closeMobileMenuOnClick: function( e ) { 312 const self = this; 313 const $menuItem = $( e.currentTarget ).closest( '.menu-item' ); 314 const dropByLabel = $menuItem.hasClass( 'mobile-drop-by_label' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_label' ); 315 const dropByArrow = $menuItem.hasClass( 'mobile-drop-by_arrow' ) || $menuItem.parents( '.menu-item' ).hasClass( 'mobile-drop-by_arrow' ); 316 317 if ( self.type !== 'mobile' || $us.header.isVertical() ) { 318 return; 319 } 320 if ( 321 dropByLabel 322 || ( self.opts.mobileBehavior && $menuItem.hasClass( 'menu-item-has-children' ) && ! dropByArrow ) 323 ) { 324 return; 325 } 326 327 self.closeMobileMenu(); 328 }, 329 330 } ); 331 332 // Global navigation API 333 $.extend( usNav.prototype, { 334 335 /** 336 * Open/close dropdown on click. 337 * 338 * @param {Event} e 339 */ 340 toggleMenuOnClick: function( e ) { 341 const self = this; 342 343 if ( self.type === 'mobile' ) { 344 return; 345 } 346 347 const $menuItem = $( e.currentTarget ).closest( '.menu-item' ); 348 const isOpened = $menuItem.hasClass( 'opened' ); 349 350 if ( ! self.keyboardNavEvent ) { 351 e.preventDefault(); 352 e.stopPropagation(); 353 } else { 354 return; 355 } 356 357 $menuItem.toggleClass( 'opened', ! isOpened ); 358 359 // Events for open items 360 if ( ! isOpened ) { 361 self.closeOnMouseOver( e ); 362 363 } else { 364 $( '.menu-item-has-children.opened', $menuItem ).removeClass( 'opened' ); 365 } 366 }, 367 368 /** 369 * Close menu on click outside. 370 * 371 * @param {Event} e 372 */ 373 closeOnClickOutside: function( e ) { 374 const self = this; 375 376 if ( self.mobileNavOpened && self.type === 'mobile' ) { 377 if ( 378 ! self.$mobileMenu.is( e.target ) 379 && ! self.$mobileMenu.has( e.target ).length 380 && ! self.$list.is( e.target ) 381 && ! self.$list.has( e.target ).length 382 ) { 383 self.closeMobileMenu(); 384 } 385 386 } else if ( 387 self.$itemsHasChildren.hasClass( 'opened' ) 388 && ! $.contains( self.$container[0], e.target ) 389 ) { 390 self.$itemsHasChildren.removeClass( 'opened' ); 391 } 392 }, 393 394 /** 395 * Close tab-opened items on hover of other items. 396 * 397 * @param {Event} e 398 */ 399 closeOnMouseOver: function( e ) { 400 const self = this; 401 if ( self.type === 'mobile' ) { 402 return; 403 } 404 const $target = $( e.target );
405 const $itemHasChildren = $target.closest( '.menu-item-has-children' ); 406 const $menuItemLevel1 = $target.closest( '.menu-item.level_1' ); 407 self.$itemsHasChildren 408 .not( $itemHasChildren ) 409 .not( $itemHasChildren.parents( '.menu-item-has-children' ) ) 410 .not( $menuItemLevel1 ) 411 .removeClass( 'opened' ); 412 }, 413 414 /** 415 * Handle keydown events. 416 * 417 * @param {Event} e 418 */ 419 handleKeyboardNav: function( e ) { 420 const self = this; 421 const keyCode = e.keyCode || e.which; 422 const $target = $( e.target ); 423 const $menuItem = $target.closest( '.menu-item' ); 424 const $menuItemLevel1 = $target.closest( '.menu-item.level_1' ); 425 const $itemHasChildren = $target.closest( '.menu-item-has-children' ); 426 427 // Mobile menu handlers 428 if ( self.type === 'mobile' ) { 429 if ( [ $ush.ENTER_KEYCODE, $ush.SPACE_KEYCODE ].includes( keyCode ) && $target.is( self.$arrows ) ) { 430 e.stopPropagation(); 431 e.preventDefault(); 432 self.switchMobileSubMenu( $menuItem, ! $menuItem.hasClass( 'opened' ) ); 433 } 434 if ( keyCode === $ush.TAB_KEYCODE ) { 435 if ( e.shiftKey && self.$anchors.index( $target ) === 0 ) { 436 self.closeMobileMenu(); 437 } 438 } 439 } 440 441 // Desktop menu handlers 442 if ( self.type === 'desktop' ) { 443 if ( [ $ush.ENTER_KEYCODE, $ush.SPACE_KEYCODE ].includes( keyCode ) && $target.is( self.$arrows ) ) { 444 e.preventDefault(); 445 446 // Close items opened via tab on hover over other items 447 self.$itemsHasChildren 448 .off( 'mouseover', self._events.closeOnMouseOver ) 449 .one( 'mouseover', self._events.closeOnMouseOver ); 450 451 if ( ! $itemHasChildren.hasClass( 'opened' ) ) { 452 $itemHasChildren 453 .addClass( 'opened' ) 454 .siblings() 455 .removeClass( 'opened' ); 456 457 self.$itemsHasChildren.not( $itemHasChildren ).not( $menuItemLevel1 ).removeClass( 'opened' ); 458 459 self.$arrows.attr( 'aria-expanded', 'false' ); 460 $target.attr( 'aria-expanded', 'true' ); 461 462 } else { 463 $itemHasChildren.removeClass( 'opened' ); 464 $target.attr( 'aria-expanded', 'false' ); 465 } 466 } 467 if ( keyCode === $ush.ESC_KEYCODE ) { 468 if ( $menuItemLevel1.hasClass( 'opened' ) ) { 469 $( '.w-nav-arrow:first', $menuItemLevel1 ).focus(); 470 } 471 self.$items.removeClass( 'opened' ); 472 self.$arrows.attr( 'aria-expanded', 'false' ); 473 } 474 475 self.keyboardNavEvent = true; 476 $ush.timeout( () => { self.keyboardNavEvent = false; }, 1 ); 477 } 478 }, 479 480 /** 481 * Resize handler. 482 */ 483 resize: function() { 484 const self = this; 485 486 if ( self.$container.length === 0 ) { 487 return; 488 } 489 490 const newType = ( window.innerWidth < self.opts.mobileWidth ) ? 'mobile' : 'desktop'; 491 492 if ( $us.header.orientation !== self.headerOrientation || newType !== self.type ) { 493 494 self.$childLists.resetInlineCSS( 'display', 'height', 'opacity' ); 495 496 if ( self.headerOrientation === 'hor' && self.type === 'mobile' ) { 497 self.$list.resetInlineCSS( 'display', 'height', 'opacity' ); 498 } 499 500 self.$items.removeClass( 'opened' ); 501 self.headerOrientation = $us.header.orientation; 502 self.type = newType; 503 self.$container.usMod( 'type', newType ); 504 } 505 506 self.$list.removeClass( 'hide_for_mobiles' ); 507 }, 508 509 } ); 510 511 $.fn.usNav = function() { 512 return this.each( function() { 513 $( this ).data( 'usNav', new usNav( this ) ); 514 } ); 515 }; 516 517 $( '.l-header .w-nav' ).usNav(); 518 519}( 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.