summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'themes/twentynineteen/js')
-rw-r--r--themes/twentynineteen/js/customize-controls.js30
-rw-r--r--themes/twentynineteen/js/customize-preview.js60
-rw-r--r--themes/twentynineteen/js/priority-menu.js216
-rw-r--r--themes/twentynineteen/js/skip-link-focus-fix.js33
-rw-r--r--themes/twentynineteen/js/touch-keyboard-navigation.js354
5 files changed, 693 insertions, 0 deletions
diff --git a/themes/twentynineteen/js/customize-controls.js b/themes/twentynineteen/js/customize-controls.js
new file mode 100644
index 00000000..e717e909
--- /dev/null
+++ b/themes/twentynineteen/js/customize-controls.js
@@ -0,0 +1,30 @@
+/**
+ * File customizer.js.
+ *
+ * Theme Customizer enhancements for a better user experience.
+ *
+ * Contains handlers to make Theme Customizer preview reload changes asynchronously.
+ */
+
+(function() {
+
+ wp.customize.bind( 'ready', function() {
+
+ // Only show the color hue control when there's a custom primary color.
+ wp.customize( 'primary_color', function( setting ) {
+ wp.customize.control( 'primary_color_hue', function( control ) {
+ var visibility = function() {
+ if ( 'custom' === setting.get() ) {
+ control.container.slideDown( 180 );
+ } else {
+ control.container.slideUp( 180 );
+ }
+ };
+
+ visibility();
+ setting.bind( visibility );
+ });
+ });
+ });
+
+})( jQuery );
diff --git a/themes/twentynineteen/js/customize-preview.js b/themes/twentynineteen/js/customize-preview.js
new file mode 100644
index 00000000..60dfa6fc
--- /dev/null
+++ b/themes/twentynineteen/js/customize-preview.js
@@ -0,0 +1,60 @@
+/**
+ * File customizer.js.
+ *
+ * Theme Customizer enhancements for a better user experience.
+ *
+ * Contains handlers to make Theme Customizer preview reload changes asynchronously.
+ */
+
+(function( $ ) {
+
+ // Primary color.
+ wp.customize( 'primary_color', function( value ) {
+ value.bind( function( to ) {
+ // Update custom color CSS.
+ var style = $( '#custom-theme-colors' ),
+ hue = style.data( 'hue' ),
+ css = style.html(),
+ color;
+
+ if( 'custom' === to ){
+ // If a custom primary color is selected, use the currently set primary_color_hue
+ color = wp.customize.get().primary_color_hue;
+ } else {
+ // If the "default" option is selected, get the default primary_color_hue
+ color = _TwentyNineteenPreviewData.default_hue;
+ }
+
+ // Equivalent to css.replaceAll, with hue followed by comma to prevent values with units from being changed.
+ css = css.split( hue + ',' ).join( color + ',' );
+ style.html( css ).data( 'hue', color );
+ });
+ });
+
+ // Primary color hue.
+ wp.customize( 'primary_color_hue', function( value ) {
+ value.bind( function( to ) {
+
+ // Update custom color CSS.
+ var style = $( '#custom-theme-colors' ),
+ hue = style.data( 'hue' ),
+ css = style.html();
+
+ // Equivalent to css.replaceAll, with hue followed by comma to prevent values with units from being changed.
+ css = css.split( hue + ',' ).join( to + ',' );
+ style.html( css ).data( 'hue', to );
+ });
+ });
+
+ // Image filter.
+ wp.customize( 'image_filter', function( value ) {
+ value.bind( function( to ) {
+ if ( to ) {
+ $( 'body' ).addClass( 'image-filters-enabled' );
+ } else {
+ $( 'body' ).removeClass( 'image-filters-enabled' );
+ }
+ } );
+ } );
+
+})( jQuery );
diff --git a/themes/twentynineteen/js/priority-menu.js b/themes/twentynineteen/js/priority-menu.js
new file mode 100644
index 00000000..7cd6bb06
--- /dev/null
+++ b/themes/twentynineteen/js/priority-menu.js
@@ -0,0 +1,216 @@
+(function() {
+
+ /**
+ * Debounce
+ *
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ */
+ function debounce(func, wait, immediate) {
+ 'use strict';
+
+ var timeout;
+ wait = (typeof wait !== 'undefined') ? wait : 20;
+ immediate = (typeof immediate !== 'undefined') ? immediate : true;
+
+ return function() {
+
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+
+ if (!immediate) {
+ func.apply(context, args);
+ }
+ };
+
+ var callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+
+ if (callNow) {
+ func.apply(context, args);
+ }
+ };
+ }
+
+ /**
+ * Prepends an element to a container.
+ *
+ * @param {Element} container
+ * @param {Element} element
+ */
+ function prependElement(container, element) {
+ if (container.firstChild.nextSibling) {
+ return container.insertBefore(element, container.firstChild.nextSibling);
+ } else {
+ return container.appendChild(element);
+ }
+ }
+
+ /**
+ * Shows an element by adding a hidden className.
+ *
+ * @param {Element} element
+ */
+ function showButton(element) {
+ // classList.remove is not supported in IE11
+ element.className = element.className.replace('is-empty', '');
+ }
+
+ /**
+ * Hides an element by removing the hidden className.
+ *
+ * @param {Element} element
+ */
+ function hideButton(element) {
+ // classList.add is not supported in IE11
+ if (!element.classList.contains('is-empty')) {
+ element.className += ' is-empty';
+ }
+ }
+
+ /**
+ * Returns the currently available space in the menu container.
+ *
+ * @returns {number} Available space
+ */
+ function getAvailableSpace( button, container ) {
+ return container.offsetWidth - button.offsetWidth - 22;
+ }
+
+ /**
+ * Returns whether the current menu is overflowing or not.
+ *
+ * @returns {boolean} Is overflowing
+ */
+ function isOverflowingNavivation( list, button, container ) {
+ return list.offsetWidth > getAvailableSpace( button, container );
+ }
+
+ /**
+ * Set menu container variable
+ */
+ var navContainer = document.querySelector('.main-navigation');
+ var breaks = [];
+
+ /**
+ * Let’s bail if we our menu doesn't exist
+ */
+ if ( ! navContainer ) {
+ return;
+ }
+
+ /**
+ * Refreshes the list item from the menu depending on the menu size
+ */
+ function updateNavigationMenu( container ) {
+
+ /**
+ * Let’s bail if our menu is empty
+ */
+ if ( ! container.parentNode.querySelector('.main-menu[id]') ) {
+ return;
+ }
+
+ // Adds the necessary UI to operate the menu.
+ var visibleList = container.parentNode.querySelector('.main-menu[id]');
+ var hiddenList = visibleList.parentNode.nextElementSibling.querySelector('.hidden-links');
+ var toggleButton = visibleList.parentNode.nextElementSibling.querySelector('.main-menu-more-toggle');
+
+ if ( isOverflowingNavivation( visibleList, toggleButton, container ) ) {
+
+ // Record the width of the list
+ breaks.push( visibleList.offsetWidth );
+ // Move last item to the hidden list
+ prependElement( hiddenList, ! visibleList.lastChild || null === visibleList.lastChild ? visibleList.previousElementSibling : visibleList.lastChild );
+ // Show the toggle button
+ showButton( toggleButton );
+
+ } else {
+
+ // There is space for another item in the nav
+ if ( getAvailableSpace( toggleButton, container ) > breaks[breaks.length - 1] ) {
+ // Move the item to the visible list
+ visibleList.appendChild( hiddenList.firstChild.nextSibling );
+ breaks.pop();
+ }
+
+ // Hide the dropdown btn if hidden list is empty
+ if (breaks.length < 2) {
+ hideButton( toggleButton );
+ }
+ }
+
+ // Recur if the visible list is still overflowing the nav
+ if ( isOverflowingNavivation( visibleList, toggleButton, container ) ) {
+ updateNavigationMenu( container );
+ }
+ }
+
+ /**
+ * Run our priority+ function as soon as the document is `ready`
+ */
+ document.addEventListener( 'DOMContentLoaded', function() {
+
+ updateNavigationMenu( navContainer );
+
+ // Also, run our priority+ function on selective refresh in the customizer
+ var hasSelectiveRefresh = (
+ 'undefined' !== typeof wp &&
+ wp.customize &&
+ wp.customize.selectiveRefresh &&
+ wp.customize.navMenusPreview.NavMenuInstancePartial
+ );
+
+ if ( hasSelectiveRefresh ) {
+ // Re-run our priority+ function on Nav Menu partial refreshes
+ wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function ( placement ) {
+
+ var isNewNavMenu = (
+ placement &&
+ placement.partial.id.includes( 'nav_menu_instance' ) &&
+ 'null' !== placement.container[0].parentNode &&
+ placement.container[0].parentNode.classList.contains( 'main-navigation' )
+ );
+
+ if ( isNewNavMenu ) {
+ updateNavigationMenu( placement.container[0].parentNode );
+ }
+ });
+ }
+ });
+
+ /**
+ * Run our priority+ function on load
+ */
+ window.addEventListener( 'load', function() {
+ updateNavigationMenu( navContainer );
+ });
+
+ /**
+ * Run our priority+ function every time the window resizes
+ */
+ var isResizing = false;
+ window.addEventListener( 'resize',
+ debounce( function() {
+ if ( isResizing ) {
+ return;
+ }
+
+ isResizing = true;
+ setTimeout( function() {
+ updateNavigationMenu( navContainer );
+ isResizing = false;
+ }, 150 );
+ } )
+ );
+
+ /**
+ * Run our priority+ function
+ */
+ updateNavigationMenu( navContainer );
+
+})();
diff --git a/themes/twentynineteen/js/skip-link-focus-fix.js b/themes/twentynineteen/js/skip-link-focus-fix.js
new file mode 100644
index 00000000..32ba80cc
--- /dev/null
+++ b/themes/twentynineteen/js/skip-link-focus-fix.js
@@ -0,0 +1,33 @@
+/**
+ * File skip-link-focus-fix.js.
+ *
+ * Helps with accessibility for keyboard only users.
+ *
+ * This is the source file for what is minified in the twentynineteen_skip_link_focus_fix() PHP function.
+ *
+ * Learn more: https://git.io/vWdr2
+ */
+( function() {
+ var isIe = /(trident|msie)/i.test( navigator.userAgent );
+
+ if ( isIe && document.getElementById && window.addEventListener ) {
+ window.addEventListener( 'hashchange', function() {
+ var id = location.hash.substring( 1 ),
+ element;
+
+ if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) {
+ return;
+ }
+
+ element = document.getElementById( id );
+
+ if ( element ) {
+ if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) {
+ element.tabIndex = -1;
+ }
+
+ element.focus();
+ }
+ }, false );
+ }
+} )();
diff --git a/themes/twentynineteen/js/touch-keyboard-navigation.js b/themes/twentynineteen/js/touch-keyboard-navigation.js
new file mode 100644
index 00000000..2fa19056
--- /dev/null
+++ b/themes/twentynineteen/js/touch-keyboard-navigation.js
@@ -0,0 +1,354 @@
+/**
+ * Touch & Keyboard navigation.
+ *
+ * Contains handlers for touch devices and keyboard navigation.
+ */
+
+(function() {
+
+ /**
+ * Debounce
+ *
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ */
+ function debounce(func, wait, immediate) {
+ 'use strict';
+
+ var timeout;
+ wait = (typeof wait !== 'undefined') ? wait : 20;
+ immediate = (typeof immediate !== 'undefined') ? immediate : true;
+
+ return function() {
+
+ var context = this, args = arguments;
+ var later = function() {
+ timeout = null;
+
+ if (!immediate) {
+ func.apply(context, args);
+ }
+ };
+
+ var callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+
+ if (callNow) {
+ func.apply(context, args);
+ }
+ };
+ }
+
+ /**
+ * Add class
+ *
+ * @param {Object} el
+ * @param {string} cls
+ */
+ function addClass(el, cls) {
+ if ( ! el.className.match( '(?:^|\\s)' + cls + '(?!\\S)') ) {
+ el.className += ' ' + cls;
+ }
+ }
+
+ /**
+ * Delete class
+ *
+ * @param {Object} el
+ * @param {string} cls
+ */
+ function deleteClass(el, cls) {
+ el.className = el.className.replace( new RegExp( '(?:^|\\s)' + cls + '(?!\\S)' ),'' );
+ }
+
+ /**
+ * Has class?
+ *
+ * @param {Object} el
+ * @param {string} cls
+ *
+ * @returns {boolean} Has class
+ */
+ function hasClass(el, cls) {
+
+ if ( el.className.match( '(?:^|\\s)' + cls + '(?!\\S)' ) ) {
+ return true;
+ }
+ }
+
+ /**
+ * Toggle Aria Expanded state for screenreaders
+ *
+ * @param {Object} ariaItem
+ */
+ function toggleAriaExpandedState( ariaItem ) {
+ 'use strict';
+
+ var ariaState = ariaItem.getAttribute('aria-expanded');
+
+ if ( ariaState === 'true' ) {
+ ariaState = 'false';
+ } else {
+ ariaState = 'true';
+ }
+
+ ariaItem.setAttribute('aria-expanded', ariaState);
+ }
+
+ /**
+ * Open sub-menu
+ *
+ * @param {Object} currentSubMenu
+ */
+ function openSubMenu( currentSubMenu ) {
+ 'use strict';
+
+ // Update classes
+ // classList.add is not supported in IE11
+ currentSubMenu.parentElement.className += ' off-canvas';
+ currentSubMenu.parentElement.lastElementChild.className += ' expanded-true';
+
+ // Update aria-expanded state
+ toggleAriaExpandedState( currentSubMenu );
+ }
+
+ /**
+ * Close sub-menu
+ *
+ * @param {Object} currentSubMenu
+ */
+ function closeSubMenu( currentSubMenu ) {
+ 'use strict';
+
+ var menuItem = getCurrentParent( currentSubMenu, '.menu-item' ); // this.parentNode
+ var menuItemAria = menuItem.querySelector('a[aria-expanded]');
+ var subMenu = currentSubMenu.closest('.sub-menu');
+
+ // If this is in a sub-sub-menu, go back to parent sub-menu
+ if ( getCurrentParent( currentSubMenu, 'ul' ).classList.contains( 'sub-menu' ) ) {
+
+ // Update classes
+ // classList.remove is not supported in IE11
+ menuItem.className = menuItem.className.replace( 'off-canvas', '' );
+ subMenu.className = subMenu.className.replace( 'expanded-true', '' );
+
+ // Update aria-expanded and :focus states
+ toggleAriaExpandedState( menuItemAria );
+
+ // Or else close all sub-menus
+ } else {
+
+ // Update classes
+ // classList.remove is not supported in IE11
+ menuItem.className = menuItem.className.replace( 'off-canvas', '' );
+ menuItem.lastElementChild.className = menuItem.lastElementChild.className.replace( 'expanded-true', '' );
+
+ // Update aria-expanded and :focus states
+ toggleAriaExpandedState( menuItemAria );
+ }
+ }
+
+ /**
+ * Find first ancestor of an element by selector
+ *
+ * @param {Object} child
+ * @param {String} selector
+ * @param {String} stopSelector
+ */
+ function getCurrentParent( child, selector, stopSelector ) {
+
+ var currentParent = null;
+
+ while ( child ) {
+
+ if ( child.matches(selector) ) {
+
+ currentParent = child;
+ break;
+
+ } else if ( stopSelector && child.matches(stopSelector) ) {
+
+ break;
+ }
+
+ child = child.parentElement;
+ }
+
+ return currentParent;
+ }
+
+ /**
+ * Remove all off-canvas states
+ */
+ function removeAllFocusStates() {
+ 'use strict';
+
+ var siteBranding = document.getElementsByClassName( 'site-branding' )[0];
+ var getFocusedElements = siteBranding.querySelectorAll(':hover, :focus, :focus-within');
+ var getFocusedClassElements = siteBranding.querySelectorAll('.is-focused');
+ var i;
+ var o;
+
+ for ( i = 0; i < getFocusedElements.length; i++) {
+ getFocusedElements[i].blur();
+ }
+
+ for ( o = 0; o < getFocusedClassElements.length; o++) {
+ deleteClass( getFocusedClassElements[o], 'is-focused' );
+ }
+ }
+
+ /**
+ * Matches polyfill for IE11
+ */
+ if (!Element.prototype.matches) {
+ Element.prototype.matches = Element.prototype.msMatchesSelector;
+ }
+
+ /**
+ * Toggle `focus` class to allow sub-menu access on touch screens.
+ */
+ function toggleSubmenuDisplay() {
+
+ document.addEventListener('touchstart', function(event) {
+
+ if ( event.target.matches('a') ) {
+
+ var url = event.target.getAttribute( 'href' ) ? event.target.getAttribute( 'href' ) : '';
+
+ // Open submenu if url is #
+ if ( '#' === url && event.target.nextSibling.matches('.submenu-expand') ) {
+ openSubMenu( event.target );
+ }
+ }
+
+ // Check if .submenu-expand is touched
+ if ( event.target.matches('.submenu-expand') ) {
+ openSubMenu(event.target);
+
+ // Check if child of .submenu-expand is touched
+ } else if ( null != getCurrentParent( event.target, '.submenu-expand' ) &&
+ getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ) {
+ openSubMenu( getCurrentParent( event.target, '.submenu-expand' ) );
+
+ // Check if .menu-item-link-return is touched
+ } else if ( event.target.matches('.menu-item-link-return') ) {
+ closeSubMenu( event.target );
+
+ // Check if child of .menu-item-link-return is touched
+ } else if ( null != getCurrentParent( event.target, '.menu-item-link-return' ) && getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) {
+ closeSubMenu( event.target );
+ }
+
+ // Prevent default mouse/focus events
+ removeAllFocusStates();
+
+ }, false);
+
+ document.addEventListener('touchend', function(event) {
+
+ var mainNav = getCurrentParent( event.target, '.main-navigation' );
+
+ if ( null != mainNav && hasClass( mainNav, '.main-navigation' ) ) {
+ // Prevent default mouse events
+ event.preventDefault();
+
+ } else if (
+ event.target.matches('.submenu-expand') ||
+ null != getCurrentParent( event.target, '.submenu-expand' ) &&
+ getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ||
+ event.target.matches('.menu-item-link-return') ||
+ null != getCurrentParent( event.target, '.menu-item-link-return' ) &&
+ getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) {
+ // Prevent default mouse events
+ event.preventDefault();
+ }
+
+ // Prevent default mouse/focus events
+ removeAllFocusStates();
+
+ }, false);
+
+ document.addEventListener('focus', function(event) {
+
+ if ( event.target.matches('.main-navigation > div > ul > li a') ) {
+
+ // Remove Focused elements in sibling div
+ var currentDiv = getCurrentParent( event.target, 'div', '.main-navigation' );
+ var currentDivSibling = currentDiv.previousElementSibling === null ? currentDiv.nextElementSibling : currentDiv.previousElementSibling;
+ var focusedElement = currentDivSibling.querySelector( '.is-focused' );
+ var focusedClass = 'is-focused';
+ var prevLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).previousElementSibling;
+ var nextLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).nextElementSibling;
+
+ if ( null !== focusedElement && null !== hasClass( focusedElement, focusedClass ) ) {
+ deleteClass( focusedElement, focusedClass );
+ }
+
+ // Add .is-focused class to top-level li
+ if ( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ) ) {
+ addClass( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ), focusedClass );
+ }
+
+ // Check for previous li
+ if ( prevLi && hasClass( prevLi, focusedClass ) ) {
+ deleteClass( prevLi, focusedClass );
+ }
+
+ // Check for next li
+ if ( nextLi && hasClass( nextLi, focusedClass ) ) {
+ deleteClass( nextLi, focusedClass );
+ }
+ }
+
+ }, true);
+
+ document.addEventListener('click', function(event) {
+
+ // Remove all focused menu states when clicking outside site branding
+ if ( event.target !== document.getElementsByClassName( 'site-branding' )[0] ) {
+ removeAllFocusStates();
+ } else {
+ // nothing
+ }
+
+ }, false);
+ }
+
+ /**
+ * Run our sub-menu function as soon as the document is `ready`
+ */
+ document.addEventListener( 'DOMContentLoaded', function() {
+ toggleSubmenuDisplay();
+ });
+
+ /**
+ * Run our sub-menu function on selective refresh in the customizer
+ */
+ document.addEventListener( 'customize-preview-menu-refreshed', function( e, params ) {
+ if ( 'menu-1' === params.wpNavMenuArgs.theme_location ) {
+ toggleSubmenuDisplay();
+ }
+ });
+
+ /**
+ * Run our sub-menu function every time the window resizes
+ */
+ var isResizing = false;
+ window.addEventListener( 'resize', function() {
+ isResizing = true;
+ debounce( function() {
+ if ( isResizing ) {
+ return;
+ }
+
+ toggleSubmenuDisplay();
+ isResizing = false;
+
+ }, 150 );
+ } );
+
+})();