355 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			355 lines
		
	
	
	
		
			9.2 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 ); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * 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 ); | ||
|  | 	} ); | ||
|  | 
 | ||
|  | })(); |