[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-content/themes/twentytwenty/assets/js/ -> index.js (source)

   1  /*    -----------------------------------------------------------------------------------------------
   2      Namespace
   3  --------------------------------------------------------------------------------------------------- */
   4  
   5  var twentytwenty = twentytwenty || {};
   6  
   7  // Set a default value for scrolled.
   8  twentytwenty.scrolled = 0;
   9  
  10  // polyfill closest
  11  // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
  12  if ( ! Element.prototype.closest ) {
  13      Element.prototype.closest = function( s ) {
  14          var el = this;
  15  
  16          do {
  17              if ( el.matches( s ) ) {
  18                  return el;
  19              }
  20  
  21              el = el.parentElement || el.parentNode;
  22          } while ( el !== null && el.nodeType === 1 );
  23  
  24          return null;
  25      };
  26  }
  27  
  28  // polyfill forEach
  29  // https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
  30  if ( window.NodeList && ! NodeList.prototype.forEach ) {
  31      NodeList.prototype.forEach = function( callback, thisArg ) {
  32          var i;
  33          var len = this.length;
  34  
  35          thisArg = thisArg || window;
  36  
  37          for ( i = 0; i < len; i++ ) {
  38              callback.call( thisArg, this[ i ], i, this );
  39          }
  40      };
  41  }
  42  
  43  // event "polyfill"
  44  twentytwenty.createEvent = function( eventName ) {
  45      var event;
  46      if ( typeof window.Event === 'function' ) {
  47          event = new Event( eventName );
  48      } else {
  49          event = document.createEvent( 'Event' );
  50          event.initEvent( eventName, true, false );
  51      }
  52      return event;
  53  };
  54  
  55  // matches "polyfill"
  56  // https://developer.mozilla.org/es/docs/Web/API/Element/matches
  57  if ( ! Element.prototype.matches ) {
  58      Element.prototype.matches =
  59          Element.prototype.matchesSelector ||
  60          Element.prototype.mozMatchesSelector ||
  61          Element.prototype.msMatchesSelector ||
  62          Element.prototype.oMatchesSelector ||
  63          Element.prototype.webkitMatchesSelector ||
  64          function( s ) {
  65              var matches = ( this.document || this.ownerDocument ).querySelectorAll( s ),
  66                  i = matches.length;
  67              while ( --i >= 0 && matches.item( i ) !== this ) {}
  68              return i > -1;
  69          };
  70  }
  71  
  72  // Add a class to the body for when touch is enabled for browsers that don't support media queries
  73  // for interaction media features. Adapted from <https://codepen.io/Ferie/pen/vQOMmO>.
  74  twentytwenty.touchEnabled = {
  75  
  76      init: function() {
  77          var matchMedia = function() {
  78              // Include the 'heartz' as a way to have a non-matching MQ to help terminate the join. See <https://git.io/vznFH>.
  79              var prefixes = [ '-webkit-', '-moz-', '-o-', '-ms-' ];
  80              var query = [ '(', prefixes.join( 'touch-enabled),(' ), 'heartz', ')' ].join( '' );
  81              return window.matchMedia && window.matchMedia( query ).matches;
  82          };
  83  
  84          if ( ( 'ontouchstart' in window ) || ( window.DocumentTouch && document instanceof window.DocumentTouch ) || matchMedia() ) {
  85              document.body.classList.add( 'touch-enabled' );
  86          }
  87      }
  88  }; // twentytwenty.touchEnabled
  89  
  90  /*    -----------------------------------------------------------------------------------------------
  91      Cover Modals
  92  --------------------------------------------------------------------------------------------------- */
  93  
  94  twentytwenty.coverModals = {
  95  
  96      init: function() {
  97          if ( document.querySelector( '.cover-modal' ) ) {
  98              // Handle cover modals when they're toggled.
  99              this.onToggle();
 100  
 101              // When toggled, untoggle if visitor clicks on the wrapping element of the modal.
 102              this.outsideUntoggle();
 103  
 104              // Close on escape key press.
 105              this.closeOnEscape();
 106  
 107              // Hide and show modals before and after their animations have played out.
 108              this.hideAndShowModals();
 109          }
 110      },
 111  
 112      // Handle cover modals when they're toggled.
 113      onToggle: function() {
 114          document.querySelectorAll( '.cover-modal' ).forEach( function( element ) {
 115              element.addEventListener( 'toggled', function( event ) {
 116                  var modal = event.target,
 117                      body = document.body;
 118  
 119                  if ( modal.classList.contains( 'active' ) ) {
 120                      body.classList.add( 'showing-modal' );
 121                  } else {
 122                      body.classList.remove( 'showing-modal' );
 123                      body.classList.add( 'hiding-modal' );
 124  
 125                      // Remove the hiding class after a delay, when animations have been run.
 126                      setTimeout( function() {
 127                          body.classList.remove( 'hiding-modal' );
 128                      }, 500 );
 129                  }
 130              } );
 131          } );
 132      },
 133  
 134      // Close modal on outside click.
 135      outsideUntoggle: function() {
 136          document.addEventListener( 'click', function( event ) {
 137              var target = event.target;
 138              var modal = document.querySelector( '.cover-modal.active' );
 139  
 140              // if target onclick is <a> with # within the href attribute
 141              if ( event.target.tagName.toLowerCase() === 'a' && event.target.hash.includes( '#' ) && modal !== null ) {
 142                  // untoggle the modal
 143                  this.untoggleModal( modal );
 144                  // wait 550 and scroll to the anchor
 145                  setTimeout( function() {
 146                      var anchor = document.getElementById( event.target.hash.slice( 1 ) );
 147                      anchor.scrollIntoView();
 148                  }, 550 );
 149              }
 150  
 151              if ( target === modal ) {
 152                  this.untoggleModal( target );
 153              }
 154          }.bind( this ) );
 155      },
 156  
 157      // Close modal on escape key press.
 158      closeOnEscape: function() {
 159          document.addEventListener( 'keydown', function( event ) {
 160              if ( event.keyCode === 27 ) {
 161                  event.preventDefault();
 162                  document.querySelectorAll( '.cover-modal.active' ).forEach( function( element ) {
 163                      this.untoggleModal( element );
 164                  }.bind( this ) );
 165              }
 166          }.bind( this ) );
 167      },
 168  
 169      // Hide and show modals before and after their animations have played out.
 170      hideAndShowModals: function() {
 171          var _doc = document,
 172              _win = window,
 173              modals = _doc.querySelectorAll( '.cover-modal' ),
 174              htmlStyle = _doc.documentElement.style,
 175              adminBar = _doc.querySelector( '#wpadminbar' );
 176  
 177  		function getAdminBarHeight( negativeValue ) {
 178              var height,
 179                  currentScroll = _win.pageYOffset;
 180  
 181              if ( adminBar ) {
 182                  height = currentScroll + adminBar.getBoundingClientRect().height;
 183  
 184                  return negativeValue ? -height : height;
 185              }
 186  
 187              return currentScroll === 0 ? 0 : -currentScroll;
 188          }
 189  
 190  		function htmlStyles() {
 191              var overflow = _win.innerHeight > _doc.documentElement.getBoundingClientRect().height;
 192  
 193              return {
 194                  'overflow-y': overflow ? 'hidden' : 'scroll',
 195                  position: 'fixed',
 196                  width: '100%',
 197                  top: getAdminBarHeight( true ) + 'px',
 198                  left: 0
 199              };
 200          }
 201  
 202          // Show the modal.
 203          modals.forEach( function( modal ) {
 204              modal.addEventListener( 'toggle-target-before-inactive', function( event ) {
 205                  var styles = htmlStyles(),
 206                      offsetY = _win.pageYOffset,
 207                      paddingTop = ( Math.abs( getAdminBarHeight() ) - offsetY ) + 'px',
 208                      mQuery = _win.matchMedia( '(max-width: 600px)' );
 209  
 210                  if ( event.target !== modal ) {
 211                      return;
 212                  }
 213  
 214                  Object.keys( styles ).forEach( function( styleKey ) {
 215                      htmlStyle.setProperty( styleKey, styles[ styleKey ] );
 216                  } );
 217  
 218                  _win.twentytwenty.scrolled = parseInt( styles.top, 10 );
 219  
 220                  if ( adminBar ) {
 221                      _doc.body.style.setProperty( 'padding-top', paddingTop );
 222  
 223                      if ( mQuery.matches ) {
 224                          if ( offsetY >= getAdminBarHeight() ) {
 225                              modal.style.setProperty( 'top', 0 );
 226                          } else {
 227                              modal.style.setProperty( 'top', ( getAdminBarHeight() - offsetY ) + 'px' );
 228                          }
 229                      }
 230                  }
 231  
 232                  modal.classList.add( 'show-modal' );
 233              } );
 234  
 235              // Hide the modal after a delay, so animations have time to play out.
 236              modal.addEventListener( 'toggle-target-after-inactive', function( event ) {
 237                  if ( event.target !== modal ) {
 238                      return;
 239                  }
 240  
 241                  setTimeout( function() {
 242                      var clickedEl = twentytwenty.toggles.clickedEl;
 243  
 244                      modal.classList.remove( 'show-modal' );
 245  
 246                      Object.keys( htmlStyles() ).forEach( function( styleKey ) {
 247                          htmlStyle.removeProperty( styleKey );
 248                      } );
 249  
 250                      if ( adminBar ) {
 251                          _doc.body.style.removeProperty( 'padding-top' );
 252                          modal.style.removeProperty( 'top' );
 253                      }
 254  
 255                      if ( clickedEl !== false ) {
 256                          clickedEl.focus();
 257                          clickedEl = false;
 258                      }
 259  
 260                      _win.scrollTo( 0, Math.abs( _win.twentytwenty.scrolled + getAdminBarHeight() ) );
 261  
 262                      _win.twentytwenty.scrolled = 0;
 263                  }, 500 );
 264              } );
 265          } );
 266      },
 267  
 268      // Untoggle a modal.
 269      untoggleModal: function( modal ) {
 270          var modalTargetClass,
 271              modalToggle = false;
 272  
 273          // If the modal has specified the string (ID or class) used by toggles to target it, untoggle the toggles with that target string.
 274          // The modal-target-string must match the string toggles use to target the modal.
 275          if ( modal.dataset.modalTargetString ) {
 276              modalTargetClass = modal.dataset.modalTargetString;
 277  
 278              modalToggle = document.querySelector( '*[data-toggle-target="' + modalTargetClass + '"]' );
 279          }
 280  
 281          // If a modal toggle exists, trigger it so all of the toggle options are included.
 282          if ( modalToggle ) {
 283              modalToggle.click();
 284  
 285              // If one doesn't exist, just hide the modal.
 286          } else {
 287              modal.classList.remove( 'active' );
 288          }
 289      }
 290  
 291  }; // twentytwenty.coverModals
 292  
 293  /*    -----------------------------------------------------------------------------------------------
 294      Intrinsic Ratio Embeds
 295  --------------------------------------------------------------------------------------------------- */
 296  
 297  twentytwenty.intrinsicRatioVideos = {
 298  
 299      init: function() {
 300          this.makeFit();
 301  
 302          window.addEventListener( 'resize', function() {
 303              this.makeFit();
 304          }.bind( this ) );
 305      },
 306  
 307      makeFit: function() {
 308          document.querySelectorAll( 'iframe, object, video' ).forEach( function( video ) {
 309              var ratio, iTargetWidth,
 310                  container = video.parentNode;
 311  
 312              // Skip videos we want to ignore.
 313              if ( video.classList.contains( 'intrinsic-ignore' ) || video.parentNode.classList.contains( 'intrinsic-ignore' ) ) {
 314                  return true;
 315              }
 316  
 317              if ( ! video.dataset.origwidth ) {
 318                  // Get the video element proportions.
 319                  video.setAttribute( 'data-origwidth', video.width );
 320                  video.setAttribute( 'data-origheight', video.height );
 321              }
 322  
 323              iTargetWidth = container.offsetWidth;
 324  
 325              // Get ratio from proportions.
 326              ratio = iTargetWidth / video.dataset.origwidth;
 327  
 328              // Scale based on ratio, thus retaining proportions.
 329              video.style.width = iTargetWidth + 'px';
 330              video.style.height = ( video.dataset.origheight * ratio ) + 'px';
 331          } );
 332      }
 333  
 334  }; // twentytwenty.instrinsicRatioVideos
 335  
 336  /*    -----------------------------------------------------------------------------------------------
 337      Modal Menu
 338  --------------------------------------------------------------------------------------------------- */
 339  twentytwenty.modalMenu = {
 340  
 341      init: function() {
 342          // If the current menu item is in a sub level, expand all the levels higher up on load.
 343          this.expandLevel();
 344          this.keepFocusInModal();
 345      },
 346  
 347      expandLevel: function() {
 348          var modalMenus = document.querySelectorAll( '.modal-menu' );
 349  
 350          modalMenus.forEach( function( modalMenu ) {
 351              var activeMenuItem = modalMenu.querySelector( '.current-menu-item' );
 352  
 353              if ( activeMenuItem ) {
 354                  twentytwentyFindParents( activeMenuItem, 'li' ).forEach( function( element ) {
 355                      var subMenuToggle = element.querySelector( '.sub-menu-toggle' );
 356                      if ( subMenuToggle ) {
 357                          twentytwenty.toggles.performToggle( subMenuToggle, true );
 358                      }
 359                  } );
 360              }
 361          } );
 362      },
 363  
 364      keepFocusInModal: function() {
 365          var _doc = document;
 366  
 367          _doc.addEventListener( 'keydown', function( event ) {
 368              var toggleTarget, modal, selectors, elements, menuType, bottomMenu, activeEl, lastEl, firstEl, tabKey, shiftKey,
 369                  clickedEl = twentytwenty.toggles.clickedEl;
 370  
 371              if ( clickedEl && _doc.body.classList.contains( 'showing-modal' ) ) {
 372                  toggleTarget = clickedEl.dataset.toggleTarget;
 373                  selectors = 'input, a, button';
 374                  modal = _doc.querySelector( toggleTarget );
 375  
 376                  elements = modal.querySelectorAll( selectors );
 377                  elements = Array.prototype.slice.call( elements );
 378  
 379                  if ( '.menu-modal' === toggleTarget ) {
 380                      menuType = window.matchMedia( '(min-width: 1000px)' ).matches;
 381                      menuType = menuType ? '.expanded-menu' : '.mobile-menu';
 382  
 383                      elements = elements.filter( function( element ) {
 384                          return null !== element.closest( menuType ) && null !== element.offsetParent;
 385                      } );
 386  
 387                      elements.unshift( _doc.querySelector( '.close-nav-toggle' ) );
 388  
 389                      bottomMenu = _doc.querySelector( '.menu-bottom > nav' );
 390  
 391                      if ( bottomMenu ) {
 392                          bottomMenu.querySelectorAll( selectors ).forEach( function( element ) {
 393                              elements.push( element );
 394                          } );
 395                      }
 396                  }
 397  
 398                  lastEl = elements[ elements.length - 1 ];
 399                  firstEl = elements[0];
 400                  activeEl = _doc.activeElement;
 401                  tabKey = event.keyCode === 9;
 402                  shiftKey = event.shiftKey;
 403  
 404                  if ( ! shiftKey && tabKey && lastEl === activeEl ) {
 405                      event.preventDefault();
 406                      firstEl.focus();
 407                  }
 408  
 409                  if ( shiftKey && tabKey && firstEl === activeEl ) {
 410                      event.preventDefault();
 411                      lastEl.focus();
 412                  }
 413              }
 414          } );
 415      }
 416  }; // twentytwenty.modalMenu
 417  
 418  /*    -----------------------------------------------------------------------------------------------
 419      Primary Menu
 420  --------------------------------------------------------------------------------------------------- */
 421  
 422  twentytwenty.primaryMenu = {
 423  
 424      init: function() {
 425          this.focusMenuWithChildren();
 426      },
 427  
 428      // The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu
 429      // by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element.
 430      focusMenuWithChildren: function() {
 431          // Get all the link elements within the primary menu.
 432          var links, i, len,
 433              menu = document.querySelector( '.primary-menu-wrapper' );
 434  
 435          if ( ! menu ) {
 436              return false;
 437          }
 438  
 439          links = menu.getElementsByTagName( 'a' );
 440  
 441          // Each time a menu link is focused or blurred, toggle focus.
 442          for ( i = 0, len = links.length; i < len; i++ ) {
 443              links[i].addEventListener( 'focus', toggleFocus, true );
 444              links[i].addEventListener( 'blur', toggleFocus, true );
 445          }
 446  
 447          //Sets or removes the .focus class on an element.
 448  		function toggleFocus() {
 449              var self = this;
 450  
 451              // Move up through the ancestors of the current link until we hit .primary-menu.
 452              while ( -1 === self.className.indexOf( 'primary-menu' ) ) {
 453                  // On li elements toggle the class .focus.
 454                  if ( 'li' === self.tagName.toLowerCase() ) {
 455                      if ( -1 !== self.className.indexOf( 'focus' ) ) {
 456                          self.className = self.className.replace( ' focus', '' );
 457                      } else {
 458                          self.className += ' focus';
 459                      }
 460                  }
 461                  self = self.parentElement;
 462              }
 463          }
 464      }
 465  }; // twentytwenty.primaryMenu
 466  
 467  /*    -----------------------------------------------------------------------------------------------
 468      Toggles
 469  --------------------------------------------------------------------------------------------------- */
 470  
 471  twentytwenty.toggles = {
 472  
 473      clickedEl: false,
 474  
 475      init: function() {
 476          // Do the toggle.
 477          this.toggle();
 478  
 479          // Check for toggle/untoggle on resize.
 480          this.resizeCheck();
 481  
 482          // Check for untoggle on escape key press.
 483          this.untoggleOnEscapeKeyPress();
 484      },
 485  
 486      performToggle: function( element, instantly ) {
 487          var target, timeOutTime, classToToggle,
 488              self = this,
 489              _doc = document,
 490              // Get our targets.
 491              toggle = element,
 492              targetString = toggle.dataset.toggleTarget,
 493              activeClass = 'active';
 494  
 495          // Elements to focus after modals are closed.
 496          if ( ! _doc.querySelectorAll( '.show-modal' ).length ) {
 497              self.clickedEl = _doc.activeElement;
 498          }
 499  
 500          if ( targetString === 'next' ) {
 501              target = toggle.nextSibling;
 502          } else {
 503              target = _doc.querySelector( targetString );
 504          }
 505  
 506          // Trigger events on the toggle targets before they are toggled.
 507          if ( target.classList.contains( activeClass ) ) {
 508              target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-before-active' ) );
 509          } else {
 510              target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-before-inactive' ) );
 511          }
 512  
 513          // Get the class to toggle, if specified.
 514          classToToggle = toggle.dataset.classToToggle ? toggle.dataset.classToToggle : activeClass;
 515  
 516          // For cover modals, set a short timeout duration so the class animations have time to play out.
 517          timeOutTime = 0;
 518  
 519          if ( target.classList.contains( 'cover-modal' ) ) {
 520              timeOutTime = 10;
 521          }
 522  
 523          setTimeout( function() {
 524              var focusElement,
 525                  subMenued = target.classList.contains( 'sub-menu' ),
 526                  newTarget = subMenued ? toggle.closest( '.menu-item' ).querySelector( '.sub-menu' ) : target,
 527                  duration = toggle.dataset.toggleDuration;
 528  
 529              // Toggle the target of the clicked toggle.
 530              if ( toggle.dataset.toggleType === 'slidetoggle' && ! instantly && duration !== '0' ) {
 531                  twentytwentyMenuToggle( newTarget, duration );
 532              } else {
 533                  newTarget.classList.toggle( classToToggle );
 534              }
 535  
 536              // If the toggle target is 'next', only give the clicked toggle the active class.
 537              if ( targetString === 'next' ) {
 538                  toggle.classList.toggle( activeClass );
 539              } else if ( target.classList.contains( 'sub-menu' ) ) {
 540                  toggle.classList.toggle( activeClass );
 541              } else {
 542                  // If not, toggle all toggles with this toggle target.
 543                  _doc.querySelector( '*[data-toggle-target="' + targetString + '"]' ).classList.toggle( activeClass );
 544              }
 545  
 546              // Toggle aria-expanded on the toggle.
 547              twentytwentyToggleAttribute( toggle, 'aria-expanded', 'true', 'false' );
 548  
 549              if ( self.clickedEl && -1 !== toggle.getAttribute( 'class' ).indexOf( 'close-' ) ) {
 550                  twentytwentyToggleAttribute( self.clickedEl, 'aria-expanded', 'true', 'false' );
 551              }
 552  
 553              // Toggle body class.
 554              if ( toggle.dataset.toggleBodyClass ) {
 555                  _doc.body.classList.toggle( toggle.dataset.toggleBodyClass );
 556              }
 557  
 558              // Check whether to set focus.
 559              if ( toggle.dataset.setFocus ) {
 560                  focusElement = _doc.querySelector( toggle.dataset.setFocus );
 561  
 562                  if ( focusElement ) {
 563                      if ( target.classList.contains( activeClass ) ) {
 564                          focusElement.focus();
 565                      } else {
 566                          focusElement.blur();
 567                      }
 568                  }
 569              }
 570  
 571              // Trigger the toggled event on the toggle target.
 572              target.dispatchEvent( twentytwenty.createEvent( 'toggled' ) );
 573  
 574              // Trigger events on the toggle targets after they are toggled.
 575              if ( target.classList.contains( activeClass ) ) {
 576                  target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-after-active' ) );
 577              } else {
 578                  target.dispatchEvent( twentytwenty.createEvent( 'toggle-target-after-inactive' ) );
 579              }
 580          }, timeOutTime );
 581      },
 582  
 583      // Do the toggle.
 584      toggle: function() {
 585          var self = this;
 586  
 587          document.querySelectorAll( '*[data-toggle-target]' ).forEach( function( element ) {
 588              element.addEventListener( 'click', function( event ) {
 589                  event.preventDefault();
 590                  self.performToggle( element );
 591              } );
 592          } );
 593      },
 594  
 595      // Check for toggle/untoggle on screen resize.
 596      resizeCheck: function() {
 597          if ( document.querySelectorAll( '*[data-untoggle-above], *[data-untoggle-below], *[data-toggle-above], *[data-toggle-below]' ).length ) {
 598              window.addEventListener( 'resize', function() {
 599                  var winWidth = window.innerWidth,
 600                      toggles = document.querySelectorAll( '.toggle' );
 601  
 602                  toggles.forEach( function( toggle ) {
 603                      var unToggleAbove = toggle.dataset.untoggleAbove,
 604                          unToggleBelow = toggle.dataset.untoggleBelow,
 605                          toggleAbove = toggle.dataset.toggleAbove,
 606                          toggleBelow = toggle.dataset.toggleBelow;
 607  
 608                      // If no width comparison is set, continue.
 609                      if ( ! unToggleAbove && ! unToggleBelow && ! toggleAbove && ! toggleBelow ) {
 610                          return;
 611                      }
 612  
 613                      // If the toggle width comparison is true, toggle the toggle.
 614                      if (
 615                          ( ( ( unToggleAbove && winWidth > unToggleAbove ) ||
 616                              ( unToggleBelow && winWidth < unToggleBelow ) ) &&
 617                              toggle.classList.contains( 'active' ) ) ||
 618                          ( ( ( toggleAbove && winWidth > toggleAbove ) ||
 619                              ( toggleBelow && winWidth < toggleBelow ) ) &&
 620                              ! toggle.classList.contains( 'active' ) )
 621                      ) {
 622                          toggle.click();
 623                      }
 624                  } );
 625              } );
 626          }
 627      },
 628  
 629      // Close toggle on escape key press.
 630      untoggleOnEscapeKeyPress: function() {
 631          document.addEventListener( 'keyup', function( event ) {
 632              if ( event.key === 'Escape' ) {
 633                  document.querySelectorAll( '*[data-untoggle-on-escape].active' ).forEach( function( element ) {
 634                      if ( element.classList.contains( 'active' ) ) {
 635                          element.click();
 636                      }
 637                  } );
 638              }
 639          } );
 640      }
 641  
 642  }; // twentytwenty.toggles
 643  
 644  /**
 645   * Is the DOM ready?
 646   *
 647   * This implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/
 648   *
 649   * @since Twenty Twenty 1.0
 650   *
 651   * @param {Function} fn Callback function to run.
 652   */
 653  function twentytwentyDomReady( fn ) {
 654      if ( typeof fn !== 'function' ) {
 655          return;
 656      }
 657  
 658      if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
 659          return fn();
 660      }
 661  
 662      document.addEventListener( 'DOMContentLoaded', fn, false );
 663  }
 664  
 665  twentytwentyDomReady( function() {
 666      twentytwenty.toggles.init();              // Handle toggles.
 667      twentytwenty.coverModals.init();          // Handle cover modals.
 668      twentytwenty.intrinsicRatioVideos.init(); // Retain aspect ratio of videos on window resize.
 669      twentytwenty.modalMenu.init();            // Modal Menu.
 670      twentytwenty.primaryMenu.init();          // Primary Menu.
 671      twentytwenty.touchEnabled.init();         // Add class to body if device is touch-enabled.
 672  } );
 673  
 674  /*    -----------------------------------------------------------------------------------------------
 675      Helper functions
 676  --------------------------------------------------------------------------------------------------- */
 677  
 678  /* Toggle an attribute ----------------------- */
 679  
 680  function twentytwentyToggleAttribute( element, attribute, trueVal, falseVal ) {
 681      var toggles;
 682  
 683      if ( ! element.hasAttribute( attribute ) ) {
 684          return;
 685      }
 686  
 687      if ( trueVal === undefined ) {
 688          trueVal = true;
 689      }
 690      if ( falseVal === undefined ) {
 691          falseVal = false;
 692      }
 693  
 694      /*
 695       * Take into account multiple toggle elements that need their state to be
 696       * synced. For example: the Search toggle buttons for desktop and mobile.
 697       */
 698      toggles = document.querySelectorAll( '[data-toggle-target="' + element.dataset.toggleTarget + '"]' );
 699  
 700      toggles.forEach( function( toggle ) {
 701          if ( ! toggle.hasAttribute( attribute ) ) {
 702              return;
 703          }
 704  
 705          if ( toggle.getAttribute( attribute ) !== trueVal ) {
 706              toggle.setAttribute( attribute, trueVal );
 707          } else {
 708              toggle.setAttribute( attribute, falseVal );
 709          }
 710      } );
 711  }
 712  
 713  /**
 714   * Toggle a menu item on or off.
 715   *
 716   * @since Twenty Twenty 1.0
 717   *
 718   * @param {HTMLElement} target
 719   * @param {number} duration
 720   */
 721  function twentytwentyMenuToggle( target, duration ) {
 722      var initialParentHeight, finalParentHeight, menu, menuItems, transitionListener,
 723          initialPositions = [],
 724          finalPositions = [];
 725  
 726      if ( ! target ) {
 727          return;
 728      }
 729  
 730      menu = target.closest( '.menu-wrapper' );
 731  
 732      // Step 1: look at the initial positions of every menu item.
 733      menuItems = menu.querySelectorAll( '.menu-item' );
 734  
 735      menuItems.forEach( function( menuItem, index ) {
 736          initialPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
 737      } );
 738      initialParentHeight = target.parentElement.offsetHeight;
 739  
 740      target.classList.add( 'toggling-target' );
 741  
 742      // Step 2: toggle target menu item and look at the final positions of every menu item.
 743      target.classList.toggle( 'active' );
 744  
 745      menuItems.forEach( function( menuItem, index ) {
 746          finalPositions[ index ] = { x: menuItem.offsetLeft, y: menuItem.offsetTop };
 747      } );
 748      finalParentHeight = target.parentElement.offsetHeight;
 749  
 750      // Step 3: close target menu item again.
 751      // The whole process happens without giving the browser a chance to render, so it's invisible.
 752      target.classList.toggle( 'active' );
 753  
 754      /*
 755       * Step 4: prepare animation.
 756       * Position all the items with absolute offsets, at the same starting position.
 757       * Shouldn't result in any visual changes if done right.
 758       */
 759      menu.classList.add( 'is-toggling' );
 760      target.classList.toggle( 'active' );
 761      menuItems.forEach( function( menuItem, index ) {
 762          var initialPosition = initialPositions[ index ];
 763          if ( initialPosition.y === 0 && menuItem.parentElement === target ) {
 764              initialPosition.y = initialParentHeight;
 765          }
 766          menuItem.style.transform = 'translate(' + initialPosition.x + 'px, ' + initialPosition.y + 'px)';
 767      } );
 768  
 769      /*
 770       * The double rAF is unfortunately needed, since we're toggling CSS classes, and
 771       * the only way to ensure layout completion here across browsers is to wait twice.
 772       * This just delays the start of the animation by 2 frames and is thus not an issue.
 773       */
 774      requestAnimationFrame( function() {
 775          requestAnimationFrame( function() {
 776              /*
 777               * Step 5: start animation by moving everything to final position.
 778               * All the layout work has already happened, while we were preparing for the animation.
 779               * The animation now runs entirely in CSS, using cheap CSS properties (opacity and transform)
 780               * that don't trigger the layout or paint stages.
 781               */
 782              menu.classList.add( 'is-animating' );
 783              menuItems.forEach( function( menuItem, index ) {
 784                  var finalPosition = finalPositions[ index ];
 785                  if ( finalPosition.y === 0 && menuItem.parentElement === target ) {
 786                      finalPosition.y = finalParentHeight;
 787                  }
 788                  if ( duration !== undefined ) {
 789                      menuItem.style.transitionDuration = duration + 'ms';
 790                  }
 791                  menuItem.style.transform = 'translate(' + finalPosition.x + 'px, ' + finalPosition.y + 'px)';
 792              } );
 793              if ( duration !== undefined ) {
 794                  target.style.transitionDuration = duration + 'ms';
 795              }
 796          } );
 797  
 798          // Step 6: finish toggling.
 799          // Remove all transient classes when the animation ends.
 800          transitionListener = function() {
 801              menu.classList.remove( 'is-animating' );
 802              menu.classList.remove( 'is-toggling' );
 803              target.classList.remove( 'toggling-target' );
 804              menuItems.forEach( function( menuItem ) {
 805                  menuItem.style.transform = '';
 806                  menuItem.style.transitionDuration = '';
 807              } );
 808              target.style.transitionDuration = '';
 809              target.removeEventListener( 'transitionend', transitionListener );
 810          };
 811  
 812          target.addEventListener( 'transitionend', transitionListener );
 813      } );
 814  }
 815  
 816  /**
 817   * Traverses the DOM up to find elements matching the query.
 818   *
 819   * @since Twenty Twenty 1.0
 820   *
 821   * @param {HTMLElement} target
 822   * @param {string} query
 823   * @return {NodeList} parents matching query
 824   */
 825  function twentytwentyFindParents( target, query ) {
 826      var parents = [];
 827  
 828      // Recursively go up the DOM adding matches to the parents array.
 829  	function traverse( item ) {
 830          var parent = item.parentNode;
 831          if ( parent instanceof HTMLElement ) {
 832              if ( parent.matches( query ) ) {
 833                  parents.push( parent );
 834              }
 835              traverse( parent );
 836          }
 837      }
 838  
 839      traverse( target );
 840  
 841      return parents;
 842  }


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