[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-content/themes/twentynineteen/js/ -> touch-keyboard-navigation.js (source)

   1  /**
   2   * Touch & Keyboard navigation.
   3   *
   4   * Contains handlers for touch devices and keyboard navigation.
   5   */
   6  
   7  (function() {
   8  
   9      /**
  10       * Debounce.
  11       *
  12       * @param {Function} func
  13       * @param {number} wait
  14       * @param {boolean} immediate
  15       */
  16  	function debounce(func, wait, immediate) {
  17          'use strict';
  18  
  19          var timeout;
  20          wait      = (typeof wait !== 'undefined') ? wait : 20;
  21          immediate = (typeof immediate !== 'undefined') ? immediate : true;
  22  
  23          return function() {
  24  
  25              var context = this, args = arguments;
  26              var later = function() {
  27                  timeout = null;
  28  
  29                  if (!immediate) {
  30                      func.apply(context, args);
  31                  }
  32              };
  33  
  34              var callNow = immediate && !timeout;
  35  
  36              clearTimeout(timeout);
  37              timeout = setTimeout(later, wait);
  38  
  39              if (callNow) {
  40                  func.apply(context, args);
  41              }
  42          };
  43      }
  44  
  45      /**
  46       * Add class.
  47       *
  48       * @param {Object} el
  49       * @param {string} cls
  50       */
  51  	function addClass(el, cls) {
  52          if ( ! el.className.match( '(?:^|\\s)' + cls + '(?!\\S)') ) {
  53              el.className += ' ' + cls;
  54          }
  55      }
  56  
  57      /**
  58       * Delete class.
  59       *
  60       * @param {Object} el
  61       * @param {string} cls
  62       */
  63  	function deleteClass(el, cls) {
  64          el.className = el.className.replace( new RegExp( '(?:^|\\s)' + cls + '(?!\\S)' ),'' );
  65      }
  66  
  67      /**
  68       * Has class?
  69       *
  70       * @param {Object} el
  71       * @param {string} cls
  72       *
  73       * @returns {boolean} Has class
  74       */
  75  	function hasClass(el, cls) {
  76  
  77          if ( el.className.match( '(?:^|\\s)' + cls + '(?!\\S)' ) ) {
  78              return true;
  79          }
  80      }
  81  
  82      /**
  83       * Toggle Aria Expanded state for screenreaders.
  84       *
  85       * @param {Object} ariaItem
  86       */
  87  	function toggleAriaExpandedState( ariaItem ) {
  88          'use strict';
  89  
  90          var ariaState = ariaItem.getAttribute('aria-expanded');
  91  
  92          if ( ariaState === 'true' ) {
  93              ariaState = 'false';
  94          } else {
  95              ariaState = 'true';
  96          }
  97  
  98          ariaItem.setAttribute('aria-expanded', ariaState);
  99      }
 100  
 101      /**
 102       * Open sub-menu.
 103       *
 104       * @param {Object} currentSubMenu
 105       */
 106  	function openSubMenu( currentSubMenu ) {
 107          'use strict';
 108  
 109          // Update classes.
 110          // classList.add is not supported in IE11.
 111          currentSubMenu.parentElement.className += ' off-canvas';
 112          currentSubMenu.parentElement.lastElementChild.className += ' expanded-true';
 113  
 114          // Update aria-expanded state.
 115          toggleAriaExpandedState( currentSubMenu );
 116      }
 117  
 118      /**
 119       * Close sub-menu.
 120       *
 121       * @param {Object} currentSubMenu
 122       */
 123  	function closeSubMenu( currentSubMenu ) {
 124          'use strict';
 125  
 126          var menuItem     = getCurrentParent( currentSubMenu, '.menu-item' ); // this.parentNode
 127          var menuItemAria = menuItem.querySelector('a[aria-expanded]');
 128          var subMenu      = currentSubMenu.closest('.sub-menu');
 129  
 130          // If this is in a sub-sub-menu, go back to parent sub-menu.
 131          if ( getCurrentParent( currentSubMenu, 'ul' ).classList.contains( 'sub-menu' ) ) {
 132  
 133              // Update classes.
 134              // classList.remove is not supported in IE11.
 135              menuItem.className = menuItem.className.replace( 'off-canvas', '' );
 136              subMenu.className  = subMenu.className.replace( 'expanded-true', '' );
 137  
 138              // Update aria-expanded and :focus states.
 139              toggleAriaExpandedState( menuItemAria );
 140  
 141          // Or else close all sub-menus.
 142          } else {
 143  
 144              // Update classes.
 145              // classList.remove is not supported in IE11.
 146              menuItem.className = menuItem.className.replace( 'off-canvas', '' );
 147              menuItem.lastElementChild.className = menuItem.lastElementChild.className.replace( 'expanded-true', '' );
 148  
 149              // Update aria-expanded and :focus states.
 150              toggleAriaExpandedState( menuItemAria );
 151          }
 152      }
 153  
 154      /**
 155       * Find first ancestor of an element by selector.
 156       *
 157       * @param {Object} child
 158       * @param {String} selector
 159       * @param {String} stopSelector
 160       */
 161  	function getCurrentParent( child, selector, stopSelector ) {
 162  
 163          var currentParent = null;
 164  
 165          while ( child ) {
 166  
 167              if ( child.matches(selector) ) {
 168  
 169                  currentParent = child;
 170                  break;
 171  
 172              } else if ( stopSelector && child.matches(stopSelector) ) {
 173  
 174                  break;
 175              }
 176  
 177              child = child.parentElement;
 178          }
 179  
 180          return currentParent;
 181      }
 182  
 183      /**
 184       * Remove all off-canvas states.
 185       */
 186  	function removeAllFocusStates() {
 187          'use strict';
 188  
 189          var siteBranding            = document.getElementsByClassName( 'site-branding' )[0];
 190          var getFocusedElements      = siteBranding.querySelectorAll(':hover, :focus, :focus-within');
 191          var getFocusedClassElements = siteBranding.querySelectorAll('.is-focused');
 192          var i;
 193          var o;
 194  
 195          for ( i = 0; i < getFocusedElements.length; i++) {
 196              getFocusedElements[i].blur();
 197          }
 198  
 199          for ( o = 0; o < getFocusedClassElements.length; o++) {
 200              deleteClass( getFocusedClassElements[o], 'is-focused' );
 201          }
 202      }
 203  
 204      /**
 205       * Matches polyfill for IE11.
 206       */
 207      if (!Element.prototype.matches) {
 208          Element.prototype.matches = Element.prototype.msMatchesSelector;
 209      }
 210  
 211      /**
 212       * Toggle `focus` class to allow sub-menu access on touch screens.
 213       */
 214  	function toggleSubmenuDisplay() {
 215  
 216          document.addEventListener('touchstart', function(event) {
 217  
 218              if ( event.target.matches('a') ) {
 219  
 220                  var url = event.target.getAttribute( 'href' ) ? event.target.getAttribute( 'href' ) : '';
 221  
 222                  // Open submenu if URL is #.
 223                  if ( '#' === url && event.target.nextSibling.matches('.submenu-expand') ) {
 224                      openSubMenu( event.target );
 225                  }
 226              }
 227  
 228              // Check if .submenu-expand is touched.
 229              if ( event.target.matches('.submenu-expand') ) {
 230                  openSubMenu(event.target);
 231  
 232              // Check if child of .submenu-expand is touched.
 233              } else if ( null != getCurrentParent( event.target, '.submenu-expand' ) &&
 234                                  getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ) {
 235                  openSubMenu( getCurrentParent( event.target, '.submenu-expand' ) );
 236  
 237              // Check if .menu-item-link-return is touched.
 238              } else if ( event.target.matches('.menu-item-link-return') ) {
 239                  closeSubMenu( event.target );
 240  
 241              // Check if child of .menu-item-link-return is touched.
 242              } else if ( null != getCurrentParent( event.target, '.menu-item-link-return' ) && getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) {
 243                  closeSubMenu( event.target );
 244              }
 245  
 246              // Prevent default mouse/focus events.
 247              removeAllFocusStates();
 248  
 249          }, false);
 250  
 251          document.addEventListener('touchend', function(event) {
 252  
 253              var mainNav = getCurrentParent( event.target, '.main-navigation' );
 254  
 255              if ( null != mainNav && hasClass( mainNav, '.main-navigation' ) ) {
 256                  // Prevent default mouse events.
 257                  event.preventDefault();
 258  
 259              } else if (
 260                  event.target.matches('.submenu-expand') ||
 261                  null != getCurrentParent( event.target, '.submenu-expand' ) &&
 262                  getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ||
 263                  event.target.matches('.menu-item-link-return') ||
 264                  null != getCurrentParent( event.target, '.menu-item-link-return' ) &&
 265                  getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) {
 266                      // Prevent default mouse events.
 267                      event.preventDefault();
 268              }
 269  
 270              // Prevent default mouse/focus events.
 271              removeAllFocusStates();
 272  
 273          }, false);
 274  
 275          document.addEventListener('focus', function(event) {
 276  
 277              if ( event.target.matches('.main-navigation > div > ul > li a') ) {
 278  
 279                  // Remove Focused elements in sibling div.
 280                  var currentDiv        = getCurrentParent( event.target, 'div', '.main-navigation' );
 281                  var currentDivSibling = currentDiv.previousElementSibling === null ? currentDiv.nextElementSibling : currentDiv.previousElementSibling;
 282                  var focusedElement    = currentDivSibling.querySelector( '.is-focused' );
 283                  var focusedClass      = 'is-focused';
 284                  var prevLi            = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).previousElementSibling;
 285                  var nextLi            = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).nextElementSibling;
 286  
 287                  if ( null !== focusedElement && null !== hasClass( focusedElement, focusedClass ) ) {
 288                      deleteClass( focusedElement, focusedClass );
 289                  }
 290  
 291                  // Add .is-focused class to top-level li.
 292                  if ( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ) ) {
 293                      addClass( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ), focusedClass );
 294                  }
 295  
 296                  // Check for previous li.
 297                  if ( prevLi && hasClass( prevLi, focusedClass ) ) {
 298                      deleteClass( prevLi, focusedClass );
 299                  }
 300  
 301                  // Check for next li.
 302                  if ( nextLi && hasClass( nextLi, focusedClass ) ) {
 303                      deleteClass( nextLi, focusedClass );
 304                  }
 305              }
 306  
 307          }, true);
 308  
 309          document.addEventListener('click', function(event) {
 310  
 311              // Remove all focused menu states when clicking outside site branding.
 312              if ( event.target !== document.getElementsByClassName( 'site-branding' )[0] ) {
 313                  removeAllFocusStates();
 314              } else {
 315                  // Nothing.
 316              }
 317  
 318          }, false);
 319      }
 320  
 321      /**
 322       * Run our sub-menu function as soon as the document is `ready`.
 323       */
 324      document.addEventListener( 'DOMContentLoaded', function() {
 325          toggleSubmenuDisplay();
 326      });
 327  
 328      /**
 329       * Run our sub-menu function on selective refresh in the customizer.
 330       */
 331      document.addEventListener( 'customize-preview-menu-refreshed', function( e, params ) {
 332          if ( 'menu-1' === params.wpNavMenuArgs.theme_location ) {
 333              toggleSubmenuDisplay();
 334          }
 335      });
 336  
 337      /**
 338       * Run our sub-menu function every time the window resizes.
 339       */
 340      var isResizing = false;
 341      window.addEventListener( 'resize', function() {
 342          isResizing = true;
 343          debounce( function() {
 344              if ( isResizing ) {
 345                  return;
 346              }
 347  
 348              toggleSubmenuDisplay();
 349              isResizing = false;
 350  
 351          }, 150 );
 352      } );
 353  
 354  })();


Generated: Thu Nov 21 01:00:03 2024 Cross-referenced by PHPXref 0.7.1