[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * File primary-navigation.js. 3 * 4 * Required to open and close the mobile navigation. 5 */ 6 7 /** 8 * Toggle an attribute's value 9 * 10 * @since Twenty Twenty-One 1.0 11 * 12 * @param {Element} el - The element. 13 * @param {boolean} withListeners - Whether we want to add/remove listeners or not. 14 */ 15 function twentytwentyoneToggleAriaExpanded( el, withListeners ) { 16 if ( 'true' !== el.getAttribute( 'aria-expanded' ) ) { 17 el.setAttribute( 'aria-expanded', 'true' ); 18 twentytwentyoneSubmenuPosition( el.parentElement ); 19 if ( withListeners ) { 20 document.addEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside ); 21 } 22 } else { 23 el.setAttribute( 'aria-expanded', 'false' ); 24 if ( withListeners ) { 25 document.removeEventListener( 'click', twentytwentyoneCollapseMenuOnClickOutside ); 26 } 27 } 28 } 29 30 function twentytwentyoneCollapseMenuOnClickOutside( event ) { 31 if ( ! document.getElementById( 'site-navigation' ).contains( event.target ) ) { 32 document.getElementById( 'site-navigation' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) { 33 button.setAttribute( 'aria-expanded', 'false' ); 34 } ); 35 } 36 } 37 38 /** 39 * Changes the position of submenus so they always fit the screen horizontally. 40 * 41 * @since Twenty Twenty-One 1.0 42 * 43 * @param {Element} li - The li element. 44 */ 45 function twentytwentyoneSubmenuPosition( li ) { 46 var subMenu = li.querySelector( 'ul.sub-menu' ), 47 rect, 48 right, 49 left, 50 windowWidth; 51 52 if ( ! subMenu ) { 53 return; 54 } 55 56 rect = subMenu.getBoundingClientRect(); 57 right = Math.round( rect.right ); 58 left = Math.round( rect.left ); 59 windowWidth = Math.round( window.innerWidth ); 60 61 if ( right > windowWidth ) { 62 subMenu.classList.add( 'submenu-reposition-right' ); 63 } else if ( document.body.classList.contains( 'rtl' ) && left < 0 ) { 64 subMenu.classList.add( 'submenu-reposition-left' ); 65 } 66 } 67 68 /** 69 * Handle clicks on submenu toggles. 70 * 71 * @since Twenty Twenty-One 1.0 72 * 73 * @param {Element} el - The element. 74 */ 75 function twentytwentyoneExpandSubMenu( el ) { // jshint ignore:line 76 // Close other expanded items. 77 el.closest( 'nav' ).querySelectorAll( '.sub-menu-toggle' ).forEach( function( button ) { 78 if ( button !== el ) { 79 button.setAttribute( 'aria-expanded', 'false' ); 80 } 81 } ); 82 83 // Toggle aria-expanded on the button. 84 twentytwentyoneToggleAriaExpanded( el, true ); 85 86 // On tab-away collapse the menu. 87 el.parentNode.querySelectorAll( 'ul > li:last-child > a' ).forEach( function( linkEl ) { 88 linkEl.addEventListener( 'blur', function( event ) { 89 if ( ! el.parentNode.contains( event.relatedTarget ) ) { 90 el.setAttribute( 'aria-expanded', 'false' ); 91 } 92 } ); 93 } ); 94 } 95 96 ( function() { 97 /** 98 * Menu Toggle Behaviors 99 * 100 * @since Twenty Twenty-One 1.0 101 * 102 * @param {string} id - The ID. 103 */ 104 var navMenu = function( id ) { 105 var wrapper = document.body, // this is the element to which a CSS class is added when a mobile nav menu is open 106 mobileButton = document.getElementById( id + '-mobile-menu' ), 107 navMenuEl = document.getElementById( 'site-navigation' ); 108 109 // If there's no nav menu, none of this is necessary. 110 if ( ! navMenuEl ) { 111 return; 112 } 113 114 if ( mobileButton ) { 115 mobileButton.onclick = function() { 116 wrapper.classList.toggle( id + '-navigation-open' ); 117 wrapper.classList.toggle( 'lock-scrolling' ); 118 twentytwentyoneToggleAriaExpanded( mobileButton ); 119 mobileButton.focus(); 120 }; 121 } 122 123 // Add aria-controls attributes to primary sub-menu. 124 var subMenus = document.querySelectorAll( '.primary-menu-container .sub-menu' ); 125 subMenus.forEach( function( subMenu, index ) { 126 var parentLi = subMenu.closest( 'li.menu-item-has-children' ); 127 subMenu.id = 'sub-menu-' + ( index + 1 ); 128 if ( parentLi ) { 129 var parentLink = parentLi.querySelector( 'button' ); 130 if ( parentLink ) { 131 parentLink.setAttribute( 'aria-controls', subMenu.id ); 132 } 133 } 134 } ); 135 136 /** 137 * Trap keyboard navigation in the menu modal. 138 * Adapted from Twenty Twenty. 139 * 140 * @since Twenty Twenty-One 1.0 141 */ 142 document.addEventListener( 'keydown', function( event ) { 143 var modal, elements, selectors, lastEl, firstEl, activeEl, tabKey, shiftKey, escKey; 144 if ( ! wrapper.classList.contains( id + '-navigation-open' ) ) { 145 return; 146 } 147 148 modal = document.querySelector( '.' + id + '-navigation' ); 149 selectors = 'input, a, button'; 150 elements = modal.querySelectorAll( selectors ); 151 elements = Array.prototype.slice.call( elements ); 152 tabKey = event.keyCode === 9; 153 shiftKey = event.shiftKey; 154 escKey = event.keyCode === 27; 155 activeEl = document.activeElement; // eslint-disable-line @wordpress/no-global-active-element 156 lastEl = elements[ elements.length - 1 ]; 157 firstEl = elements[0]; 158 159 if ( escKey ) { 160 event.preventDefault(); 161 wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' ); 162 twentytwentyoneToggleAriaExpanded( mobileButton ); 163 mobileButton.focus(); 164 } 165 166 if ( ! shiftKey && tabKey && lastEl === activeEl ) { 167 event.preventDefault(); 168 firstEl.focus(); 169 } 170 171 if ( shiftKey && tabKey && firstEl === activeEl ) { 172 event.preventDefault(); 173 lastEl.focus(); 174 } 175 176 // If there are no elements in the menu, don't move the focus 177 if ( tabKey && firstEl === lastEl ) { 178 event.preventDefault(); 179 } 180 } ); 181 182 /** 183 * Close menu and scroll to anchor when an anchor link is clicked. 184 * Adapted from Twenty Twenty. 185 * 186 * @since Twenty Twenty-One 1.1 187 */ 188 document.getElementById( 'site-navigation' ).addEventListener( 'click', function( event ) { 189 // If target onclick is <a> with # within the href attribute 190 if ( event.target.hash ) { 191 wrapper.classList.remove( id + '-navigation-open', 'lock-scrolling' ); 192 twentytwentyoneToggleAriaExpanded( mobileButton ); 193 // Wait 550 and scroll to the anchor. 194 setTimeout(function () { 195 var anchor = document.getElementById(event.target.hash.slice(1)); 196 if ( anchor ) { 197 anchor.scrollIntoView(); 198 } 199 }, 550); 200 } 201 } ); 202 203 navMenuEl.querySelectorAll( '.menu-wrapper > .menu-item-has-children' ).forEach( function( li ) { 204 li.addEventListener( 'mouseenter', function() { 205 this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'true' ); 206 twentytwentyoneSubmenuPosition( li ); 207 } ); 208 li.addEventListener( 'mouseleave', function() { 209 this.querySelector( '.sub-menu-toggle' ).setAttribute( 'aria-expanded', 'false' ); 210 } ); 211 } ); 212 }; 213 214 window.addEventListener( 'load', function() { 215 new navMenu( 'primary' ); 216 } ); 217 }() );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Feb 22 08:20:01 2025 | Cross-referenced by PHPXref |