1/** 2 * Touch & Keyboard navigation. 3 * 4 * Contains handlers for touch devices and keyboard navigation. 5 */ 6 7(function() { 8 9 /** 10 * Debounce 11 * 12 * @param {Function} func 13 * @param {number} wait 14 * @param {boolean} immediate 15 */ 16 function debounce(func, wait, immediate) { 17 'use strict'; 18 19 var timeout; 20 wait = (typeof wait !== 'undefined') ? wait : 20; 21 immediate = (typeof immediate !== 'undefined') ? immediate : true; 22 23 return function() { 24 25 var context = this, args = arguments; 26 var later = function() { 27 timeout = null; 28 29 if (!immediate) { 30 func.apply(context, args); 31 } 32 }; 33 34 var callNow = immediate && !timeout; 35 36 clearTimeout(timeout); 37 timeout = setTimeout(later, wait); 38 39 if (callNow) { 40 func.apply(context, args); 41 } 42 }; 43 } 44 45 /** 46 * Add class 47 * 48 * @param {Object} el 49 * @param {string} cls 50 */ 51 function addClass(el, cls) { 52 if ( ! el.className.match( '(?:^|\\s)' + cls + '(?!\\S)') ) { 53 el.className += ' ' + cls; 54 } 55 } 56 57 /** 58 * Delete class 59 * 60 * @param {Object} el 61 * @param {string} cls 62 */ 63 function deleteClass(el, cls) { 64 el.className = el.className.replace( new RegExp( '(?:^|\\s)' + cls + '(?!\\S)' ),'' ); 65 } 66 67 /** 68 * Has class? 69 * 70 * @param {Object} el 71 * @param {string} cls 72 * 73 * @returns {boolean} Has class 74 */ 75 function hasClass(el, cls) { 76 77 if ( el.className.match( '(?:^|\\s)' + cls + '(?!\\S)' ) ) { 78 return true; 79 } 80 } 81 82 /** 83 * Toggle Aria Expanded state for screenreaders 84 * 85 * @param {Object} ariaItem 86 */ 87 function toggleAriaExpandedState( ariaItem ) { 88 'use strict'; 89 90 var ariaState = ariaItem.getAttribute('aria-expanded'); 91 92 if ( ariaState === 'true' ) { 93 ariaState = 'false'; 94 } else { 95 ariaState = 'true'; 96 } 97 98 ariaItem.setAttribute('aria-expanded', ariaState); 99 } 100 101 /** 102 * Open sub-menu 103 * 104 * @param {Object} currentSubMenu 105 */ 106 function openSubMenu( currentSubMenu ) { 107 'use strict'; 108 109 // Update classes 110 // classList.add is not supported in IE11 111 currentSubMenu.parentElement.className += ' off-canvas'; 112 currentSubMenu.parentElement.lastElementChild.className += ' expanded-true'; 113 114 // Update aria-expanded state 115 toggleAriaExpandedState( currentSubMenu ); 116 } 117 118 /** 119 * Close sub-menu 120 * 121 * @param {Object} currentSubMenu 122 */ 123 function closeSubMenu( currentSubMenu ) { 124 'use strict'; 125 126 var menuItem = getCurrentParent( currentSubMenu, '.menu-item' ); // this.parentNode 127 var menuItemAria = menuItem.querySelector('a[aria-expanded]'); 128 var subMenu = currentSubMenu.closest('.sub-menu'); 129 130 // If this is in a sub-sub-menu, go back to parent sub-menu 131 if ( getCurrentParent( currentSubMenu, 'ul' ).classList.contains( 'sub-menu' ) ) { 132 133 // Update classes 134 // classList.remove is not supported in IE11 135 menuItem.className = menuItem.className.replace( 'off-canvas', '' ); 136 subMenu.className = subMenu.className.replace( 'expanded-true', '' ); 137 138 // Update aria-expanded and :focus states 139 toggleAriaExpandedState( menuItemAria ); 140 141 // Or else close all sub-menus 142 } else { 143 144 // Update classes 145 // classList.remove is not supported in IE11 146 menuItem.className = menuItem.className.replace( 'off-canvas', '' ); 147 menuItem.lastElementChild.className = menuItem.lastElementChild.className.replace( 'expanded-true', '' ); 148 149 // Update aria-expanded and :focus states 150 toggleAriaExpandedState( menuItemAria ); 151 } 152 } 153 154 /** 155 * Find first ancestor of an element by selector 156 * 157 * @param {Object} child 158 * @param {String} selector 159 * @param {String} stopSelector 160 */ 161 function getCurrentParent( child, selector, stopSelector ) { 162 163 var currentParent = null; 164 165 while ( child ) { 166 167 if ( child.matches(selector) ) { 168 169 currentParent = child; 170 break; 171 172 } else if ( stopSelector && child.matches(stopSelector) ) { 173 174 break; 175 } 176 177 child = child.parentElement; 178 } 179 180 return currentParent; 181 } 182 183 /** 184 * Remove all off-canvas states 185 */ 186 function removeAllFocusStates() { 187 'use strict'; 188 189 var siteBranding = document.getElementsByClassName( 'site-branding' )[0]; 190 191 if ( ! siteBranding ) { 192 return; 193 } 194 195 var getFocusedElements = siteBranding.querySelectorAll(':hover, :focus, :focus-within'); 196 var getFocusedClassElements = siteBranding.querySelectorAll('.is-focused'); 197 var i; 198 var o; 199 200 for ( i = 0; i < getFocusedElements.length; i++) { 201 getFocusedElements[i].blur(); 202 } 203 204 for ( o = 0; o < getFocusedClassElements.length; o++) { 205 deleteClass( getFocusedClassElements[o], 'is-focused' ); 206 } 207 } 208
209 /** 210 * Matches polyfill for IE11 211 */ 212 if (!Element.prototype.matches) { 213 Element.prototype.matches = Element.prototype.msMatchesSelector; 214 } 215 216 /** 217 * Toggle `focus` class to allow sub-menu access on touch screens. 218 */ 219 function toggleSubmenuDisplay() { 220 221 document.addEventListener('touchstart', function(event) { 222 223 if ( event.target.matches('a') ) { 224 225 var url = event.target.getAttribute( 'href' ) ? event.target.getAttribute( 'href' ) : ''; 226 227 // Open submenu if url is # 228 if ( '#' === url && event.target.nextSibling.matches('.submenu-expand') ) { 229 openSubMenu( event.target ); 230 } 231 } 232 233 // Check if .submenu-expand is touched 234 if ( event.target.matches('.submenu-expand') ) { 235 openSubMenu(event.target); 236 237 // Check if child of .submenu-expand is touched 238 } else if ( null != getCurrentParent( event.target, '.submenu-expand' ) && 239 getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ) { 240 openSubMenu( getCurrentParent( event.target, '.submenu-expand' ) ); 241 242 // Check if .menu-item-link-return is touched 243 } else if ( event.target.matches('.menu-item-link-return') ) { 244 closeSubMenu( event.target ); 245 246 // Check if child of .menu-item-link-return is touched 247 } else if ( null != getCurrentParent( event.target, '.menu-item-link-return' ) && getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) { 248 closeSubMenu( event.target ); 249 } 250 251 // Prevent default mouse/focus events 252 removeAllFocusStates(); 253 254 }, false); 255 256 document.addEventListener('touchend', function(event) { 257 258 var mainNav = getCurrentParent( event.target, '.main-navigation' ); 259 260 if ( null != mainNav && hasClass( mainNav, '.main-navigation' ) ) { 261 // Prevent default mouse events 262 event.preventDefault(); 263 264 } else if ( 265 event.target.matches('.submenu-expand') || 266 null != getCurrentParent( event.target, '.submenu-expand' ) && 267 getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) || 268 event.target.matches('.menu-item-link-return') || 269 null != getCurrentParent( event.target, '.menu-item-link-return' ) && 270 getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) { 271 // Prevent default mouse events 272 event.preventDefault(); 273 } 274 275 // Prevent default mouse/focus events 276 removeAllFocusStates(); 277 278 }, false); 279 280 document.addEventListener('focus', function(event) { 281 282 if ( event.target.matches('.main-navigation > div > ul > li a') ) { 283 284 // Remove Focused elements in sibling div 285 var currentDiv = getCurrentParent( event.target, 'div', '.main-navigation' ); 286 var currentDivSibling = currentDiv.previousElementSibling === null ? currentDiv.nextElementSibling : currentDiv.previousElementSibling; 287 var focusedElement = currentDivSibling.querySelector( '.is-focused' ); 288 var focusedClass = 'is-focused'; 289 var prevLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).previousElementSibling; 290 var nextLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).nextElementSibling; 291 292 if ( null !== focusedElement && null !== hasClass( focusedElement, focusedClass ) ) { 293 deleteClass( focusedElement, focusedClass ); 294 } 295 296 // Add .is-focused class to top-level li 297 if ( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ) ) { 298 addClass( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ), focusedClass ); 299 } 300 301 // Check for previous li 302 if ( prevLi && hasClass( prevLi, focusedClass ) ) { 303 deleteClass( prevLi, focusedClass ); 304 } 305 306 // Check for next li 307 if ( nextLi && hasClass( nextLi, focusedClass ) ) { 308 deleteClass( nextLi, focusedClass ); 309 } 310 } 311 312 }, true); 313 314 document.addEventListener('click', function(event) { 315 316 // Remove all focused menu states when clicking outside site branding 317 if ( event.target !== document.getElementsByClassName( 'site-branding' )[0] ) { 318 removeAllFocusStates(); 319 } else { 320 // nothing 321 } 322 323 }, false); 324 } 325 326 /** 327 * Run our sub-menu function as soon as the document is `ready` 328 */ 329 document.addEventListener( 'DOMContentLoaded', function() { 330 toggleSubmenuDisplay(); 331 }); 332 333 /** 334 * Run our sub-menu function on selective refresh in the customizer 335 */ 336 document.addEventListener( 'customize-preview-menu-refreshed', function( e, params ) { 337 if ( 'menu-1' === params.wpNavMenuArgs.theme_location ) { 338 toggleSubmenuDisplay(); 339 } 340 }); 341 342 /** 343 * Run our sub-menu function every time the window resizes 344 */ 345 var isResizing = false;
346 window.addEventListener( 'resize', function() { 347 isResizing = true; 348 debounce( function() { 349 if ( isResizing ) { 350 return; 351 } 352 353 toggleSubmenuDisplay(); 354 isResizing = false; 355 356 }, 150 ); 357 } ); 358 359})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.