1function _extends() { 2 return _extends = Object.assign ? Object.assign.bind() : function (n) { 3 for (var e = 1; e < arguments.length; e++) { 4 var t = arguments[e]; 5 for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); 6 } 7 return n; 8 }, _extends.apply(null, arguments); 9} 10 11/** 12 * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> 13 * @license GNU General Public License version 2 or later; see LICENSE.txt 14 */ 15 16(() => { 17 18 /** 19 * Navigation menu 20 * 21 * Example usage: 22 * // Default behavior (uses menuHoverClass = 'show-menu', dir = 'ltr') 23 * new Nav(document.querySelector('.nav')); 24 * 25 * // Override defaults (e.g. custom open-class and RTL support) 26 * new Nav(document.querySelector('.nav'), { 27 * menuHoverClass: 'my-open-class', 28 * dir: 'rtl' 29 * }); 30 * 31 * @param {HTMLElement} nav The root <ul class="nav"> element 32 * @param {Object} [settings] Optional overrides for defaultSettings 33 * @param {string} [settings.menuHoverClass='show-menu'] CSS class to toggle on open submenus 34 * @param {string} [settings.dir='ltr'] Text direction for keyboard nav ('ltr'|'rtl') 35 */ 36 class Nav { 37 constructor(nav, settings = {}) { 38 var _settings$dir, _this$nav$id, _this$nav; 39 this.nav = nav; 40 41 // read the HTML dir attribute or computed style, or fall back to defaultSettings.dir 42 const browserDir = document.documentElement.getAttribute('dir') || 43 // <html dir="â¦"> 44 getComputedStyle(document.documentElement).direction || 45 // CSS direction 46 47 Nav.defaultSettings.dir; 48 this.settings = _extends({}, Nav.defaultSettings, settings); 49 50 // merge defaults, browserâdetected dir, and any explicit overrides in `settings` 51 this.settings = _extends({}, Nav.defaultSettings, { 52 dir: (_settings$dir = settings.dir) != null ? _settings$dir : browserDir 53 }, settings); 54 55 // Unique prefix for this nav instance - needed for the id of submenus and aria-controls 56 this.idPrefix = (_this$nav$id = (_this$nav = this.nav) == null ? void 0 : _this$nav.id) != null ? _this$nav$id : `nav-${Math.floor(Math.random() * 100000)}`; 57 this.topLevelNodes = this.nav.querySelectorAll(':scope > li'); 58 this.topLevelNodes.forEach(topLevelEl => { 59 // get submenu ul elements within topLevelEl 60 const levelChildUls = topLevelEl.querySelectorAll('ul'); 61 const ariaControls = []; 62 levelChildUls.forEach(childUl => { 63 childUl.setAttribute('aria-hidden', 'true'); 64 childUl.classList.remove(this.settings.menuHoverClass); 65 childUl.id = `${this.idPrefix}-submenu${Nav.idCounter}`; 66 Nav.idCounter += 1; 67 ariaControls.push(childUl.id); 68 }); 69 if (levelChildUls.length > 0) { 70 const togglebtn = topLevelEl.querySelector('[aria-expanded]'); 71 togglebtn == null || togglebtn.setAttribute('aria-controls', ariaControls.join(' ')); 72 } 73 }); 74 nav.addEventListener('keydown', this.onMenuKeyDown.bind(this)); 75 nav.addEventListener('click', this.onClick.bind(this)); 76 if (this.nav.classList.contains(this.settings.preventSubmenuOpenOnload)) { 77 this.toggleAllForCurrentActive(); 78 } 79 } 80 onMenuKeyDown(event) { 81 const target = event.target.closest('li'); 82 if (!target) { 83 return; 84 } 85 const subLists = target.querySelectorAll('ul'); 86 switch (event.key) { 87 case 'ArrowUp': 88 event.preventDefault(); 89 this.tabPrev(); 90 break; 91 case 'ArrowLeft': 92 event.preventDefault(); 93 if (this.settings.dir === 'rtl') { 94 this.tabNext(); 95 } else { 96 this.tabPrev(); 97 } 98 break; 99 case 'ArrowDown': 100 event.preventDefault(); 101 this.tabNext(); 102 break; 103 case 'ArrowRight': 104 event.preventDefault(); 105 if (this.settings.dir === 'rtl') { 106 this.tabPrev(); 107 } else { 108 this.tabNext(); 109 } 110 break; 111 case 'Enter': 112 if (event.target.nodeName === 'SPAN' && event.target.parentNode.nodeName !== 'A' && subLists.length > 0) { 113 var _subLists$; 114 event.preventDefault(); 115 this.toggleSubMenu(target, subLists, ((_subLists$ = subLists[0]) == null ? void 0 : _subLists$.getAttribute('aria-hidden')) === 'true'); 116 } 117 break; 118 case ' ': 119 case 'Spacebar': 120 if (subLists.length > 0) { 121 var _subLists$2; 122 event.preventDefault(); 123 this.toggleSubMenu(target, subLists, ((_subLists$2 = subLists[0]) == null ? void 0 : _subLists$2.getAttribute('aria-hidden')) === 'true'); 124 } 125 break; 126 case 'Escape': 127 { 128 event.preventDefault(); 129 const currentTopLevelLi = this.getTopLevelParentLi(event.target); 130 if (!currentTopLevelLi) { 131 break; 132 } 133 const allChildListsFromTopLevelLi = currentTopLevelLi.querySelectorAll('ul'); 134 if (allChildListsFromTopLevelLi.length > 0) { 135 this.toggleSubMenu(currentTopLevelLi, allChildListsFromTopLevelLi, false); 136 } 137 // set focus on the top level li child with tabindex 138 currentTopLevelLi.querySelectorAll(':scope >
138 [tabindex]:not([tabindex="-1"]), a, button').forEach(tabElement => { 139 if (tabElement.hasAttribute(['aria-expanded'])) { 140 tabElement.focus(); 141 } 142 }); 143 break; 144 } 145 case 'End': 146 { 147 var _target$closest; 148 event.preventDefault(); 149 const currentLiList = (_target$closest = target.closest('ul')) == null ? void 0 : _target$closest.querySelectorAll(':scope > li'); 150 for (let index = currentLiList.length - 1; index >= 0; index -= 1) { 151 const lastTabbable = currentLiList[index].querySelector(':scope > [tabindex]:not([tabindex="-1"]), a, button'); 152 if (lastTabbable) { 153 lastTabbable.focus(); 154 return; 155 } 156 } 157 break; 158 } 159 case 'Home': 160 { 161 var _target$closest2; 162 event.preventDefault(); 163 const firstLi = (_target$closest2 = target.closest('ul')) == null ? void 0 : _target$closest2.querySelector(':scope > li:first-child'); 164 if (firstLi) { 165 var _firstLi$querySelecto; 166 // set focus on first li child with tabindex within current list 167 (_firstLi$querySelecto = firstLi.querySelector(':scope > [tabindex]:not([tabindex="-1"]), a, button')) == null || _firstLi$querySelecto.focus(); 168 } 169 break; 170 } 171 } 172 } 173 onClick(event) { 174 var _event$target, _event$target2, _event$target3, _event$target4; 175 if (!((_event$target = event.target) != null && _event$target.hasAttribute('aria-expanded')) && !((_event$target2 = event.target) != null && _event$target2.closest('[aria-expanded'))) { 176 return; 177 } 178 if (((_event$target3 = event.target) == null ? void 0 : _event$target3.nodeName) === 'A') { 179 return; 180 } 181 if (((_event$target4 = event.target) == null ? void 0 : _event$target4.nodeName) === 'SPAN' && event.target.parentNode.nodeName === 'A') { 182 return; 183 } 184 const target = event.target.closest('li'); 185 const subLists = target == null ? void 0 : target.querySelectorAll('ul'); 186 if (subLists && subLists.length > 0) { 187 var _subLists$3; 188 event.preventDefault(); 189 this.toggleSubMenu(target, subLists, ((_subLists$3 = subLists[0]) == null ? void 0 : _subLists$3.getAttribute('aria-hidden')) === 'true'); 190 } 191 } 192 toggleSubMenu(target, subLists, open = false) { 193 var _target$querySelector; 194 if (open) { 195 // close all opened submenus before opening the new one 196 const allSubMenus = this.nav.querySelectorAll('ul[aria-hidden="false"]'); 197 allSubMenus.forEach(ulChild => { 198 var _this$getTopLevelPare; 199 ulChild.setAttribute('aria-hidden', 'true'); 200 ulChild.classList.remove(this.settings.menuHoverClass); 201 (_this$getTopLevelPare = this.getTopLevelParentLi(ulChild)) == null || (_this$getTopLevelPare = _this$getTopLevelPare.querySelector(':scope > [aria-expanded]')) == null || _this$getTopLevelPare.setAttribute('aria-expanded', 'false'); 202 }); 203 } 204 subLists.forEach(ulChild => { 205 ulChild.setAttribute('aria-hidden', open ? 'false' : 'true'); 206 ulChild.classList.toggle(this.settings.menuHoverClass, open); 207 }); 208 (_target$querySelector = target.querySelector(':scope > [aria-expanded]')) == null || _target$querySelector.setAttribute('aria-expanded', open ? 'true' : 'false'); 209 } 210 focusTabbable(direction = 1) { 211 const tabbables = Array.from(this.nav.querySelectorAll('[tabindex]:not([tabindex="-1"]), a, button')).filter(el => !el.disabled && el.tabIndex >= 0 && el.offsetParent !== null); 212 const currentIndex = tabbables.indexOf(document.activeElement); 213 if (tabbables.length === 0) return; 214 const nextIndex = (currentIndex + direction + tabbables.length) % tabbables.length; 215 tabbables[nextIndex].focus(); 216 } 217 tabNext() { 218 this.focusTabbable(1); 219 } 220 tabPrev() { 221 this.focusTabbable(-1); 222 } 223 getTopLevelParentLi(element) { 224 let currentLi = element.closest('li'); 225 // this.topLevelNodes is a NodeList of top-level li elements in this nav 226 while (currentLi && !Array.from(this.topLevelNodes).includes(currentLi)) { 227 const parentUl = currentLi.parentElement.closest('ul'); 228 currentLi = parentUl ? parentUl.closest('li') : null; 229 } 230 return currentLi; // top-level li or null if not found, or the 231 } 232 toggleAllForCurrentActive() { 233 const active = this.nav.querySelector('.current.active'); 234 if (active) { 235 this.getTopLevelParentLi(active); 236 let currentLi = active; 237 while (currentLi && !Array.from(this.topLevelNodes).includes(currentLi)) { 238 const parentUl = currentLi.parentElement.closest('ul'); 239 currentLi = parentUl ? parentUl.closest('li') : null; 240 if (currentLi) { 241 var _subLists$4; 242 const subLists = currentLi.querySelectorAll('ul'); 243 this.toggleSubMenu(currentLi, subLists, ((_subLists$4 = subLists[0]) == null ? void 0 : _subLists$4.getAttribute('aria-hidden')) === 'true'); 244 } 245 } 246 } 247 } 248 } 249 250 // static idCounter for unique id generation of submenus 251 // Default settings for the Nav class 252 Nav.defaultSettings = { 253 menuHoverClass: 'show-menu', 254 dir: 'ltr', 255 preventSubmenuOpenOnload: 'nav-active-open' 256 }; 257 Nav.idCounter = 0; 258 259 // Initialize Nav instances for all nav elements on the page 260 document.addEventListener('DOMContentLoaded', () => { 261 document.querySelectorAll('.nav').forEach(nav =>
261 new Nav(nav)); 262 }); 263})();
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.