[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 var getFocusedElements = siteBranding.querySelectorAll(':hover, :focus, :focus-within'); 191 var getFocusedClassElements = siteBranding.querySelectorAll('.is-focused'); 192 var i; 193 var o; 194 195 for ( i = 0; i < getFocusedElements.length; i++) { 196 getFocusedElements[i].blur(); 197 } 198 199 for ( o = 0; o < getFocusedClassElements.length; o++) { 200 deleteClass( getFocusedClassElements[o], 'is-focused' ); 201 } 202 } 203 204 /** 205 * Matches polyfill for IE11. 206 */ 207 if (!Element.prototype.matches) { 208 Element.prototype.matches = Element.prototype.msMatchesSelector; 209 } 210 211 /** 212 * Toggle `focus` class to allow sub-menu access on touch screens. 213 */ 214 function toggleSubmenuDisplay() { 215 216 document.addEventListener('touchstart', function(event) { 217 218 if ( event.target.matches('a') ) { 219 220 var url = event.target.getAttribute( 'href' ) ? event.target.getAttribute( 'href' ) : ''; 221 222 // Open submenu if URL is #. 223 if ( '#' === url && event.target.nextSibling.matches('.submenu-expand') ) { 224 openSubMenu( event.target ); 225 } 226 } 227 228 // Check if .submenu-expand is touched. 229 if ( event.target.matches('.submenu-expand') ) { 230 openSubMenu(event.target); 231 232 // Check if child of .submenu-expand is touched. 233 } else if ( null != getCurrentParent( event.target, '.submenu-expand' ) && 234 getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) ) { 235 openSubMenu( getCurrentParent( event.target, '.submenu-expand' ) ); 236 237 // Check if .menu-item-link-return is touched. 238 } else if ( event.target.matches('.menu-item-link-return') ) { 239 closeSubMenu( event.target ); 240 241 // Check if child of .menu-item-link-return is touched. 242 } else if ( null != getCurrentParent( event.target, '.menu-item-link-return' ) && getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) { 243 closeSubMenu( event.target ); 244 } 245 246 // Prevent default mouse/focus events. 247 removeAllFocusStates(); 248 249 }, false); 250 251 document.addEventListener('touchend', function(event) { 252 253 var mainNav = getCurrentParent( event.target, '.main-navigation' ); 254 255 if ( null != mainNav && hasClass( mainNav, '.main-navigation' ) ) { 256 // Prevent default mouse events. 257 event.preventDefault(); 258 259 } else if ( 260 event.target.matches('.submenu-expand') || 261 null != getCurrentParent( event.target, '.submenu-expand' ) && 262 getCurrentParent( event.target, '.submenu-expand' ).matches( '.submenu-expand' ) || 263 event.target.matches('.menu-item-link-return') || 264 null != getCurrentParent( event.target, '.menu-item-link-return' ) && 265 getCurrentParent( event.target, '.menu-item-link-return' ).matches( '.menu-item-link-return' ) ) { 266 // Prevent default mouse events. 267 event.preventDefault(); 268 } 269 270 // Prevent default mouse/focus events. 271 removeAllFocusStates(); 272 273 }, false); 274 275 document.addEventListener('focus', function(event) { 276 277 if ( event.target !== window.document && event.target.matches( '.main-navigation > div > ul > li a' ) ) { 278 279 // Remove Focused elements in sibling div. 280 var currentDiv = getCurrentParent( event.target, 'div', '.main-navigation' ); 281 var currentDivSibling = currentDiv.previousElementSibling === null ? currentDiv.nextElementSibling : currentDiv.previousElementSibling; 282 var focusedElement = currentDivSibling.querySelector( '.is-focused' ); 283 var focusedClass = 'is-focused'; 284 var prevLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).previousElementSibling; 285 var nextLi = getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ).nextElementSibling; 286 287 if ( null !== focusedElement && null !== hasClass( focusedElement, focusedClass ) ) { 288 deleteClass( focusedElement, focusedClass ); 289 } 290 291 // Add .is-focused class to top-level li. 292 if ( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ) ) { 293 addClass( getCurrentParent( event.target, '.main-navigation > div > ul > li', '.main-navigation' ), focusedClass ); 294 } 295 296 // Check for previous li. 297 if ( prevLi && hasClass( prevLi, focusedClass ) ) { 298 deleteClass( prevLi, focusedClass ); 299 } 300 301 // Check for next li. 302 if ( nextLi && hasClass( nextLi, focusedClass ) ) { 303 deleteClass( nextLi, focusedClass ); 304 } 305 } 306 307 }, true); 308 309 document.addEventListener('click', function(event) { 310 311 // Remove all focused menu states when clicking outside site branding. 312 if ( event.target !== document.getElementsByClassName( 'site-branding' )[0] ) { 313 removeAllFocusStates(); 314 } else { 315 // Nothing. 316 } 317 318 }, false); 319 } 320 321 /** 322 * Run our sub-menu function as soon as the document is `ready`. 323 */ 324 document.addEventListener( 'DOMContentLoaded', function() { 325 toggleSubmenuDisplay(); 326 }); 327 328 /** 329 * Run our sub-menu function on selective refresh in the customizer. 330 */ 331 document.addEventListener( 'customize-preview-menu-refreshed', function( e, params ) { 332 if ( 'menu-1' === params.wpNavMenuArgs.theme_location ) { 333 toggleSubmenuDisplay(); 334 } 335 }); 336 337 /** 338 * Run our sub-menu function every time the window resizes. 339 */ 340 var isResizing = false; 341 window.addEventListener( 'resize', function() { 342 isResizing = true; 343 debounce( function() { 344 if ( isResizing ) { 345 return; 346 } 347 348 toggleSubmenuDisplay(); 349 isResizing = false; 350 351 }, 150 ); 352 } ); 353 354 })();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |