1(function () { 2 'use strict'; 3 4 // --- Burger ------------------------------------------------------------------- 5 const initBurger = () => { 6 const burger = document.getElementById('mainmenu-mobile'); 7 const nav = document.getElementById('lns-nav'); 8 if (!burger || !nav) return; 9 10 const open = () => { 11 burger.setAttribute('aria-expanded', 'true'); 12 document.body.classList.add('mainmenu-isopen'); 13 nav.querySelector('a, button')?.focus(); 14 }; 15 16 const close = () => { 17 burger.setAttribute('aria-expanded', 'false'); 18 document.body.classList.remove('mainmenu-isopen'); 19 closeAllSubmenus(); 20 }; 21 22 burger.addEventListener('click', () => { 23 burger.getAttribute('aria-expanded') === 'true' ? close() : open(); 24 }); 25 26 document.addEventListener('keydown', (e) => { 27 if (e.key === 'Escape' && burger.getAttribute('aria-expanded') === 'true') { 28 close(); 29 burger.focus(); 30 } 31 }); 32 }; 33 34 // --- Submenu open/close ------------------------------------------------------- 35 const openSubmenu = (btn) => { 36 const li = btn.closest('li'); 37 const panel = btn.nextElementSibling; 38 if (!li || !panel) return; 39 40 btn.setAttribute('aria-expanded', 'true'); 41 // Static label â aria-expanded conveys the state 42 li.classList.add('submenuopen'); 43 panel.setAttribute('aria-hidden', 'false'); 44 }; 45 46 const closeSubmenu = (btn) => { 47 const li = btn.closest('li'); 48 const panel = btn.nextElementSibling; 49 if (!li || !panel) return; 50 51 btn.setAttribute('aria-expanded', 'false'); 52 li.classList.remove('submenuopen'); 53 panel.setAttribute('aria-hidden', 'true'); 54 }; 55 56 const closeAllSubmenus = () => { 57 document.querySelectorAll('nav.main-menu .menu-sub[aria-expanded="true"]') 58 .forEach(closeSubmenu); 59 }; 60 61 const bindSubmenuButtons = () => { 62 document.querySelectorAll('nav.main-menu .menu-sub').forEach(btn => { 63 // Set initial ARIA state 64 btn.setAttribute('aria-expanded', 'false'); 65 btn.nextElementSibling?.setAttribute('aria-hidden', 'true'); 66 // Label names the target, aria-expanded delivers the state 67 btn.setAttribute('aria-label', 68 'Untermenü: ' + btn.closest('li')?.querySelector(':scope > a')?.textContent?.trim() 69 ); 70 71 btn.addEventListener('click', () => { 72 btn.getAttribute('aria-expanded') === 'true' 73 ? closeSubmenu(btn) 74 : openSubmenu(btn); 75 }); 76 }); 77 }; 78 79 // --- Desktop keyboard navigation ---------------------------------------------- 80 const initDesktopKeyboard = () => { 81 const topItems = document.querySelectorAll('nav.main-menu > ul > li'); 82 83 topItems.forEach((li, index) => { 84 const topLink = li.querySelector(':scope > a'); 85 const subBtn = li.querySelector(':scope > .menu-sub'); 86 const lvl1 = li.querySelector(':scope > .lvl-1'); 87 88 if (topLink) { 89 topLink.addEventListener('keydown', (e) => { 90 switch (e.key) { 91 case 'ArrowRight': 92 topItems[(index + 1) % topItems.length]?.querySelector(':scope > a')?.focus(); 93 e.preventDefault(); break; 94 case 'ArrowLeft': 95 topItems[index - 1]?.querySelector(':scope > a')?.focus(); 96 e.preventDefault(); break; 97 case 'ArrowDown': 98 if (subBtn) { 99 openSubmenu(subBtn); 100 lvl1?.querySelector('.container .navi-column li > a')?.focus(); 101 } 102 e.preventDefault(); break; 103 case 'Escape': 104 if (subBtn) closeSubmenu(subBtn); 105 e.preventDefault(); break; 106 } 107 }); 108 } 109 110 if (lvl1) { 111 const getColumns = () => [...lvl1.querySelectorAll('.container .navi-column')]; 112 113 // --- lvl-1 column headers ----------------------------------------------- 114 lvl1.addEventListener('keydown', (e) => { 115 const current = document.activeElement; 116 const columns = getColumns(); 117 const currentColumn = current.closest('.navi-column'); 118 if (!currentColumn) return; 119 120 const colIndex = columns.indexOf(currentColumn); 121 const lvl1Link = currentColumn.querySelector(':scope > li > a'); 122 123 switch (e.key) { 124 case 'ArrowRight': 125 columns[colIndex + 1]?.querySelector('li > a')?.focus(); 126 e.preventDefault(); break; 127 128 case 'ArrowLeft': 129 columns[colIndex - 1]?.querySelector('li > a')?.focus(); 130 e.preventDefault(); break; 131 132 case 'ArrowDown':
133 // Only handle ArrowDown when focus is on the lvl-1 column header link 134 if (current === lvl1Link) { 135 const firstLvl2Link = currentColumn.querySelector('.lvl-2 li > a'); 136 if (firstLvl2Link) { 137 // Column has lvl-2 items â move into them 138 firstLvl2Link.focus(); 139 } else { 140 // No lvl-2 items â jump to next column header 141 columns[colIndex + 1]?.querySelector('li > a')?.focus(); 142 } 143 e.preventDefault(); 144 } 145 break; 146 147 case 'Escape': 148 if (subBtn) { closeSubmenu(subBtn); topLink?.focus(); } 149 e.preventDefault(); break; 150 } 151 }); 152 153 // --- lvl-2 items -------------------------------------------------------- 154 lvl1.querySelectorAll('.lvl-2').forEach((lvl2) => { 155 lvl2.addEventListener('keydown', (e) => { 156 const links = [...lvl2.querySelectorAll('li > a')]; 157 const pos = links.indexOf(document.activeElement); 158 const columns = getColumns(); 159 const currentColumn = lvl2.closest('.navi-column'); 160 const colIndex = columns.indexOf(currentColumn); 161 162 switch (e.key) { 163 case 'ArrowDown': { 164 const nextLink = links[pos + 1]; 165 if (nextLink) { 166 // Next item within the same lvl-2 167 nextLink.focus(); 168 } else { 169 // End of lvl-2 â jump to next column's lvl-1 header 170 columns[colIndex + 1]?.querySelector(':scope > li > a')?.focus(); 171 } 172 e.preventDefault(); 173 e.stopPropagation(); // Prevent lvl-1 handler from firing again 174 break; 175 } 176 177 case 'ArrowUp': { 178 const prevLink = links[pos - 1]; 179 if (prevLink) { 180 // Previous item within the same lvl-2 181 prevLink.focus(); 182 } else { 183 // Top of lvl-2 â back to this column's lvl-1 header 184 currentColumn.querySelector(':scope > li > a')?.focus(); 185 } 186 e.preventDefault(); 187 e.stopPropagation(); // Prevent lvl-1 handler from firing again 188 break; 189 } 190 191 case 'ArrowLeft': 192 // Jump back to this column's lvl-1 header 193 currentColumn.querySelector(':scope > li > a')?.focus(); 194 e.preventDefault(); 195 e.stopPropagation(); // Prevent lvl-1 handler from firing again 196 break; 197 198 case 'Escape': 199 if (subBtn) { closeSubmenu(subBtn); topLink?.focus(); } 200 e.preventDefault(); 201 e.stopPropagation(); 202 break; 203 } 204 }); 205 }); 206 207 // Close submenu when focus leaves the top-level item entirely 208 li.addEventListener('focusout', (e) => { 209 if (!li.contains(e.relatedTarget) && subBtn) closeSubmenu(subBtn); 210 }); 211 } 212 }); 213 }; 214 215 // --- Init -------------------------------------------------------------------- 216 document.addEventListener('DOMContentLoaded', () => { 217 initBurger(); 218 bindSubmenuButtons(); 219 initDesktopKeyboard(); 220 }); 221 222})();
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.