mirror of
https://github.com/WordPress/WordPress.git
synced 2024-12-26 19:18:57 +01:00
5432b8e055
Merges [43808,43821,43842,43860,43892,43904,43909,43926-43929,43956,43961-43963] from the 5.0 branch to trunk. Props allancole, karmatosed, kjellr, yingling017, mrasharirfan, milana_cap, fabiankaegy, westonruter, jorbin, netweb, b-07, khleomix, audrasjb, nielslange, mmaumio, richsalvucci, littlebigthing, dimadin, joyously, anevins, peterwilsoncc, dannycooper, iCaleb, siriokun, technosiren, travel_girl, azchughtai, ianbelanger, nadim1992, ismailelkorchi, nativeinside, chetan200891, grapplerulrich, ocean90, joshfeck, frankew, AbdulWahab610, mendezcode, eliorivero, melchoyce, joen, laurelfulford, mdawaffe, kraftbj, dsmart, nao, mayukojpn, enodekciw, ketuchetan, atanasangelovdev, poena, sharaz, artisticasad, mukesh27, burhandodhy, crunnells, aprakasa, themeroots, imonly_ik, tlxo, youthkee, brentswisher, smyoon315, mrahmadawais, desideveloper, Kau-Boy, mor10, mikeyarce, dingo_bastard, xkon, twoabove. Fixes #45424. Built from https://develop.svn.wordpress.org/trunk@44149 git-svn-id: http://core.svn.wordpress.org/trunk@43979 1a063a9b-81f0-0310-95a4-ce76da25c4cd
363 lines
9.4 KiB
JavaScript
363 lines
9.4 KiB
JavaScript
/**
|
||
* 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.previousSibling );
|
||
}
|
||
|
||
/**
|
||
* 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' ) : '';
|
||
|
||
// If there’s a link, go to it on touchend
|
||
if ( '#' !== url && '' !== url ) {
|
||
window.location = url;
|
||
|
||
// Open submenu if url is #
|
||
} else if ( '#' === url && event.target.nextSibling.matches('.submenu-expand') ) {
|
||
|
||
openSubMenu( event.target );
|
||
|
||
// Prevent default touch events
|
||
} else {
|
||
|
||
event.preventDefault();
|
||
}
|
||
}
|
||
|
||
// 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 );
|
||
} );
|
||
|
||
})();
|