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


Generated: Sat Apr 4 01:00:03 2020 Cross-referenced by PHPXref 0.7.1