[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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


Generated: Sat Nov 23 20:47:33 2019 Cross-referenced by PHPXref 0.7