[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-content/themes/twentytwentyone/assets/js/ -> primary-navigation.js (source)

   1  /**
   2   * File primary-navigation.js.
   3   *
   4   * Required to open and close the mobile navigation.
   5   */
   6  
   7  /**
   8   * Toggle an attribute's value
   9   *
  10   * @since Twenty Twenty-One 1.0
  11   *
  12   * @param {Element} el - The element.
  13   * @param {boolean} withListeners - Whether we want to add/remove listeners or not.
  14   */
  15  function twentytwentyoneToggleAriaExpanded( el, withListeners ) {
  16      if ( 'true' !== el.getAttribute( 'aria-expanded' ) ) {
  17          el.setAttribute( 'aria-expanded', 'true' );
  18          twentytwentyoneSubmenuPosition( el.parentElement );
  19          if ( withListeners ) {
  20              document.addEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside );
  21          }
  22      } else {
  23          el.setAttribute( 'aria-expanded', 'false' );
  24          if ( withListeners ) {
  25              document.removeEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside );
  26          }
  27      }
  28  }
  29  
  30  function twentytwentyoneCollapseMenuOnClickOutside( event ) {
  31      if ( ! document.getElementById( 'site-navigation' ).contains( event.target ) ) {
  32          document.getElementById( 'site-navigation' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) {
  33              button.setAttribute( 'aria-expanded', 'false' );
  34          } );
  35      }
  36  }
  37  
  38  /**
  39   * Changes the position of submenus so they always fit the screen horizontally.
  40   *
  41   * @since Twenty Twenty-One 1.0
  42   *
  43   * @param {Element} li - The li element.
  44   */
  45  function twentytwentyoneSubmenuPosition( li ) {
  46      var subMenu = li.querySelector( 'ul.sub-menu' ),
  47          rect,
  48          right,
  49          left,
  50          windowWidth;
  51  
  52      if ( ! subMenu ) {
  53          return;
  54      }
  55  
  56      rect = subMenu.getBoundingClientRect();
  57      right = Math.round( rect.right );
  58      left = Math.round( rect.left );
  59      windowWidth = Math.round( window.innerWidth );
  60  
  61      if ( right > windowWidth ) {
  62          subMenu.classList.add( 'submenu-reposition-right' );
  63      } else if ( document.body.classList.contains( 'rtl' ) && left < 0 ) {
  64          subMenu.classList.add( 'submenu-reposition-left' );
  65      }
  66  }
  67  
  68  /**
  69   * Handle clicks on submenu toggles.
  70   *
  71   * @since Twenty Twenty-One 1.0
  72   *
  73   * @param {Element} el - The element.
  74   */
  75  function twentytwentyoneExpandSubMenu( el ) { // jshint ignore:line
  76      // Close other expanded items.
  77      el.closest( 'nav' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) {
  78          if ( button !== el ) {
  79              button.setAttribute( 'aria-expanded', 'false' );
  80          }
  81      } );
  82  
  83      // Toggle aria-expanded on the button.
  84      twentytwentyoneToggleAriaExpanded( el, true );
  85  
  86      // On tab-away collapse the menu.
  87      el.parentNode.querySelectorAll( 'ul > li:last-child > a' ).forEach( function( linkEl ) {
  88          linkEl.addEventListener( 'blur', function( event ) {
  89              if ( ! el.parentNode.contains( event.relatedTarget ) ) {
  90                  el.setAttribute( 'aria-expanded', 'false' );
  91              }
  92          } );
  93      } );
  94  }
  95  
  96  ( function() {
  97      /**
  98       * Menu Toggle Behaviors
  99       *
 100       * @since Twenty Twenty-One 1.0
 101       *
 102       * @param {string} id - The ID.
 103       */
 104      var navMenu = function( id ) {
 105          var wrapper = document.body, // this is the element to which a CSS class is added when a mobile nav menu is open
 106              mobileButton = document.getElementById( id + '-mobile-menu' ),
 107              navMenuEl = document.getElementById( 'site-navigation' );
 108  
 109          // If there's no nav menu, none of this is necessary.
 110          if ( ! navMenuEl ) {
 111              return;
 112          }
 113  
 114          if ( mobileButton ) {
 115              mobileButton.onclick = function() {
 116                  wrapper.classList.toggle( id + '-navigation-open' );
 117                  wrapper.classList.toggle( 'lock-scrolling' );
 118                  twentytwentyoneToggleAriaExpanded( mobileButton );
 119                  mobileButton.focus();
 120              };
 121          }
 122  
 123          // Add aria-controls attributes to primary sub-menu.
 124          var subMenus = document.querySelectorAll( '.primary-menu-container .sub-menu' );
 125          subMenus.forEach( function( subMenu, index ) {
 126              var parentLi = subMenu.closest( 'li.menu-item-has-children' );
 127              subMenu.id = 'sub-menu-' + ( index + 1 );
 128              if ( parentLi ) {
 129                  var parentLink = parentLi.querySelector( 'button' );
 130                  if ( parentLink ) {
 131                      parentLink.setAttribute( 'aria-controls', subMenu.id );
 132                  }
 133              }
 134          } );
 135  
 136          /**
 137           * Trap keyboard navigation in the menu modal.
 138           * Adapted from Twenty Twenty.
 139           *
 140           * @since Twenty Twenty-One 1.0
 141           */
 142          document.addEventListener( 'keydown', function( event ) {
 143              var modal, elements, selectors, lastEl, firstEl, activeEl, tabKey, shiftKey, escKey;
 144              if ( ! wrapper.classList.contains( id + '-navigation-open' ) ) {
 145                  return;
 146              }
 147  
 148              modal = document.querySelector( '.' + id + '-navigation' );
 149              selectors = 'input, a, button';
 150              elements = modal.querySelectorAll( selectors );
 151              elements = Array.prototype.slice.call( elements );
 152              tabKey = event.keyCode === 9;
 153              shiftKey = event.shiftKey;
 154              escKey = event.keyCode === 27;
 155              activeEl = document.activeElement; // eslint-disable-line @wordpress/no-global-active-element
 156              lastEl = elements[ elements.length - 1 ];
 157              firstEl = elements[0];
 158  
 159              if ( escKey ) {
 160                  event.preventDefault();
 161                  wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' );
 162                  twentytwentyoneToggleAriaExpanded( mobileButton );
 163                  mobileButton.focus();
 164              }
 165  
 166              if ( ! shiftKey && tabKey && lastEl === activeEl ) {
 167                  event.preventDefault();
 168                  firstEl.focus();
 169              }
 170  
 171              if ( shiftKey && tabKey && firstEl === activeEl ) {
 172                  event.preventDefault();
 173                  lastEl.focus();
 174              }
 175  
 176              // If there are no elements in the menu, don't move the focus
 177              if ( tabKey && firstEl === lastEl ) {
 178                  event.preventDefault();
 179              }
 180          } );
 181  
 182          /**
 183           * Close menu and scroll to anchor when an anchor link is clicked.
 184           * Adapted from Twenty Twenty.
 185           *
 186           * @since Twenty Twenty-One 1.1
 187           */
 188          document.getElementById( 'site-navigation' ).addEventListener( 'click', function( event ) {
 189              // If target onclick is <a> with # within the href attribute
 190              if ( event.target.hash ) {
 191                  wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' );
 192                  twentytwentyoneToggleAriaExpanded( mobileButton );
 193                  // Wait 550 and scroll to the anchor.
 194                  setTimeout(function () {
 195                      var anchor = document.getElementById(event.target.hash.slice(1));
 196                      if ( anchor ) {
 197                          anchor.scrollIntoView();
 198                      }
 199                  }, 550);
 200              }
 201          } );
 202  
 203          navMenuEl.querySelectorAll( '.menu-wrapper > .menu-item-has-children' ).forEach( function( li ) {
 204              li.addEventListener( 'mouseenter', function() {
 205                  this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'true' );
 206                  twentytwentyoneSubmenuPosition( li );
 207              } );
 208              li.addEventListener( 'mouseleave', function() {
 209                  this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'false' );
 210              } );
 211          } );
 212      };
 213  
 214      window.addEventListener( 'load', function() {
 215          new navMenu( 'primary' );
 216      } );
 217  }() );


Generated : Sat Feb 22 08:20:01 2025 Cross-referenced by PHPXref