1/** 2 * File primary-navigation.js. 3 * 4 * Required to open and close the mobile navigation. 5 */ 6 7/** 8 * Toggles 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 */ 15function 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 30function 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 */ 45function 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 * Handles clicks on submenu toggles. 70 * 71 * @since Twenty Twenty-One 1.0 72 * 73 * @param {Element} el - The element. 74 */ 75function 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 * Traps 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, shift
143Key, 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 * Closes menu and scrolls 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 var toggle = this.querySelector('.sub-menu-toggle'); 206 if (toggle && typeof toggle.setAttribute === 'function') { 207 toggle.setAttribute('aria-expanded', 'true'); 208 } 209 twentytwentyoneSubmenuPosition(li); 210 }); 211 li.addEventListener('mouseleave', function () { 212 var toggle = this.querySelector('.sub-menu-toggle'); 213 if (toggle && typeof toggle.setAttribute === 'function') { 214 toggle.setAttribute('aria-expanded', 'false'); 215 } 216 }); 217 }); 218 }; 219 220 document.addEventListener('DOMContentLoaded', function () { 221 new navMenu('primary'); 222 }); 223}());
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.