1import {rgElement} from '../rgElement/rg-element.js'; 2 3const setup = function(nav) { 4 if(!this.offcanvasNav) { 5 let config = this.defaultConfig; 6 for(const [key, value] of Object.entries(this.defaultConfig)) { 7 let attribute = this.getAttribute('data-' + key); 8 if(attribute) { 9 config[key] = this.parseAttribute(attribute); 10 } 11 } 12 this.toggleButton.addEventListener('click', function() { 13 let evt = new KeyboardEvent('keydown', { 14 keyCode: 13 15 }); 16 this.toggleButton.dispatchEvent(evt); 17 }.bind(this)); 18 config.customToggle = this.toggleButton; 19 if(this.mode === 'megamenu') { 20 config.closeOnClick = false; 21 } 22 this.offcanvasNav = new hcOffcanvasNav(this.mode === 'dropdown' ? this.firstElementChild : this.wrapper.firstElementChild, config); 23 if(this.mode === 'offcanvas') { 24 this.offcanvasNav.setAttribute('inert', ''); 25 }
26 this.templates.forEach((tpl) => { 27 let target = document.querySelector(tpl.dataset.target), 28 dynamic = this.parseAttribute(tpl.dataset.dynamic); 29 if(target) { 30 const tag = document.createElement('div'), 31 operation = tpl.dataset.operation || 'prepend'; 32 tpl.dataset.wrapperclass && tag.classList.add(tpl.dataset.wrapperclass); 33 tag.append(tpl.content); 34 switch(operation) { 35 case 'append': 36 target.append(tag); 37 break; 38 case 'prepend': 39 default: 40 target.prepend(tag); 41 break; 42 } 43 if(dynamic) { 44 this.offcanvasNav.on('open.level', moveNode.bind(tpl, target, tag)); 45 this.offcanvasNav.on('close.level', moveNode.bind(tpl, target, tag)); 46 } 47 } 48 }); 49 if(config.closeOpenLevels === false) { 50 try { 51 let currentLink = this.offcanvasNav.querySelector('a[href^="' + document.location.pathname.split('/').filter(p => { 52 return !p.match('.html') 53 }).join('/') + '"]')?.closest('*[data-index]'), 54 dataLevel = Number(currentLink?.getAttribute('data-level')), 55 dataIndex = Number(currentLink?.getAttribute('data-index')); 56 if(dataLevel) { 57 //some external stylesheets may be animating things... give them some time 58 window.setTimeout(function() { 59 //if found... open it 60 this.offcanvasNav.style.display = 'none'; 61 this.offcanvasNav.open(dataLevel, dataIndex); 62 this.offcanvasNav.close(); 63 this.offcanvasNav.style.display = ''; 64 }.bind(this), 100); 65 } 66 } catch(ex) { 67 } 68 } 69 this.offcanvasNav.on('open', () => { 70 if(this.mode === 'offcanvas') { 71 this.offcanvasNav.removeAttribute('inert') 72 } 73 if(config.closeOpenLevels === false) { 74 this.offcanvasNav.querySelector('a[href="' + document.location.pathname + '"], a[href="' + document.location.pathname + 'index.html"]')?.focus() 75 } else { 76 this.offcanvasNav.querySelector('ul a')?.focus() 77 } 78 document.dispatchEvent(new CustomEvent('rgnav:open', { 79 detail: {el: this} 80 })); 81 }); 82 this.offcanvasNav.on('close', () => { 83 if(this.mode === 'offcanvas') { 84 this.offcanvasNav.setAttribute('inert', '') 85 } 86 }); 87 document.querySelectorAll('a[data-containeronly]').forEach($element => { 88 $element.addEventListener('click', $event => { 89 $event.preventDefault(); 90 let currentLink = $element.closest('li').querySelector('*[data-index]'), 91 dataLevel = Number(currentLink?.getAttribute('data-level')), 92 dataIndex = Number(currentLink?.getAttribute('data-index')); 93 if(dataLevel) { 94 //some external stylesheets may be animating things... give them some time 95 window.setTimeout(function() { 96 //if found... open it 97 this.offcanvasNav.open(dataLevel, dataIndex); 98 }.bind(this), 100); 99 } 100 }) 101 }) 102 document.dispatchEvent(new CustomEvent('rgnav:setupComplete', { 103 detail: {el: this} 104 })) 105 } 106 }, 107 moveNode = function(navEl, elemtag, ev, n) { 108 let panel = navEl.querySelector('div[data-level="' + ev.data.currentLevel + '"][data-index="' + ev.data.currentIndex + '"]'), 109 newTarget = panel ? panel.querySelector(this.dataset.target) : navEl; 110 switch(this.dataset.operation) { 111 case 'append': 112 newTarget.append(elemtag); 113 break; 114 case 'prepend': 115 newTarget.prepend(elemtag); 116 break; 117 } 118 }, 119 buildMegamenu = function() { 120 let $megamenu = document.createElement('nav'); 121 $megamenu.classList.add('megamenu'); 122 $megamenu.setAttribute('role', 'navigation'); 123 const $menuDataUl = this.querySelector('ul'); 124 $megamenu.append(buildSubmenu.call(this, $menuDataUl, 0)); 125 return $megamenu; 126 }, 127 buildSubmenu = function($menuDataUl, level, $overviewLink = null) { 128 const $ul = document.createElement('ul'); 129 $ul.classList.add('megamenu--level' + level); 130 if(level > 0) { 131 $ul.classList.add('megamenu__submenu'); 132 $ul.setAttribute('role', 'menu'); 133 $ul.setAttribute('aria-hidden', 'true'); 134 } else { 135 $ul.classList.add('megamenu__mainmenu'); 136 $ul.setAttribute('role', 'menubar'); 137 } 138 $ul.setAttribute('data-level', level); 139 if($overviewLink) { 140 const $li = document.createElement('li'); 141 $li.setAttribute('role', 'menuitem'); 142 const $element = $overviewLink.cloneNode(true); 143 $element.classList.add('megamenu__entry'); 144 $element.innerHTML = 'Ãbersicht'; 145 $li.append($element); 146 $ul.append($li); 147 }
148 Array.from($menuDataUl.children).forEach($menuDataLi => { 149 let $nextOverviewLink = null; 150 const $li = document.createElement('li'); 151 $li.setAttribute('role', 'menuitem'); 152 let hasSubmenu = ($menuDataLi.querySelector(':scope > ul') !== null); 153 Array.from($menuDataLi.children).forEach($menuDataElement => { 154 if($menuDataElement.tagName !== 'UL') { 155 let $element; 156 if(hasSubmenu) { 157 if(!$menuDataElement.dataset.containeronly) { 158 $nextOverviewLink = $menuDataElement; 159 } 160 $element = document.createElement('span'); 161 $element.innerHTML = $menuDataElement.innerHTML; 162 } else { 163 $element = $menuDataElement.cloneNode(true); 164 } 165 $element.setAttribute('tabindex', 0); 166 $element.classList.add('megamenu__entry'); 167 if($element.tagName === 'SPAN') { 168 $li.setAttribute('aria-haspopup', 'menu'); 169 $li.setAttribute('aria-expanded', 'false') 170 } 171 $li.append($element); 172 } else { 173 $li.classList.add('megamenu--has_submenu'); 174 hasSubmenu = true; 175 const $arrow = document.createElement('i'); 176 $arrow.classList.add('megamenu__arrow'); 177 $arrow.classList.add('fas'); 178 $arrow.setAttribute('tabindex', 0); 179 $li.append($arrow); 180 const $submenu = buildSubmenu.call(this, $menuDataElement, level + 1, $nextOverviewLink); 181 if(level === 0 && $menuDataLi.dataset.extracontentid) { 182 const $extracontent_template = this.querySelector('.megamenu-extracontent-template[data-extracontentid="' + $menuDataLi.dataset.extracontentid + '"]'); 183 if($extracontent_template) { 184 const $extracontent_li = document.createElement('li'); 185 $extracontent_li.classList.add('megamenu__extracontent-container'); 186 const extracontent_copy = $extracontent_template.cloneNode(true); 187 extracontent_copy.classList.remove('megamenu-extracontent-template'); 188 $extracontent_li.append(extracontent_copy); 189 $ul.append($extracontent_li); 190 $submenu.append($extracontent_li); 191 } 192 } 193 $li.append($submenu) 194 } 195 }); 196 if(hasSubmenu) { 197 $li.addEventListener('click', event => megamenuClickhandler.call(this, event)); 198 $li.addEventListener('keydown', event => { 199 if(event.code === 'Enter') { 200 megamenuClickhandler.call(this, event); 201 } 202 if(event.code === 'Escape') { 203 document.querySelectorAll('.megamenu--level0 > li.megamenu--open').forEach($element => { 204 $element.dispatchEvent(new PointerEvent('pointerdown', { 205 view: window, bubbles: false, cancelable: true 206 })) 207 }) 208 } 209 }); 210 } 211 $ul.append($li); 212 }); 213 return $ul; 214 }, 215 megamenuClickhandler = function(event) { 216 if(event.type === 'pointerdown' && event.pointerType === 'mouse' && event.buttons !== 1) { 217 return; 218 } 219 if(event.target.tagName === "A") { 220 return; 221 } 222 const $target = event.currentTarget; 223 let mode; 224 const $submenu = $target.querySelector('ul'); 225 if($target.classList.contains('megamenu--open')) { 226 mode = 'closing'; 227 megamenuCloseSubmenu.call(this, $target); 228 if($target.parentNode.classList.contains('megamenu--level0')) { 229 document.querySelectorAll('.megamenu__overlay').forEach($overlay
229=> $overlay.remove()); 230 } 231 } else { 232 mode = 'opening'; 233 const $siblings = $target && $target.parentNode && [...$target.parentNode.children].filter(node => node !== $target && node.classList.contains('megamenu--open')); 234 $siblings.forEach($element => { 235 megamenuCloseSubmenu.call(this, $element); 236 }) 237 $target.classList.add('megamenu--open'); 238 $target.setAttribute('aria-expanded', 'true'); 239 $submenu.setAttribute('aria-hidden', 'false'); 240 if($target.parentNode.classList.contains('megamenu--level0')) { 241 document.querySelectorAll('.megamenu__overlay').forEach($overlay => $overlay.remove()) 242 const $overlay = document.createElement('div'); 243 $overlay.classList.add('megamenu__overlay'); 244 $target.append($overlay); 245 } 246 } 247 if(!this.megamenuSubmenuSizes.has($submenu)) { 248 this.megamenuSubmenuSizes.set($submenu, $submenu.offsetHeight); 249 $submenu.classList.add('megamenu--expand-submenu'); 250 } 251 const megamenuPadding = parseInt(getComputedStyle($target).getPropertyValue('--megamenu-padding')); 252 const $extracontentContainer = $target.parentNode.classList.contains('megamenu--level0') ? $target.querySelector('.megamenu__extracontent-container') : $target.closest('.megamenu--level1')?.querySelector('.megamenu__extracontent-container'); 253 let extracontentContainerHeight = ''; 254 if($extracontentContainer) { 255 extracontentContainerHeight = $extracontentContainer.offsetHeight; 256 } 257 if(!$target.parentNode.classList.contains('megamenu--level0')) { 258 if(mode === 'opening') { 259 this.megamenuOpenSubmenus.set($submenu, $submenu); 260 } 261 let maxSize = extracontentContainerHeight; 262 this.megamenuOpenSubmenus.forEach($submenu => { 263 if(this.megamenuSubmenuSizes.get($submenu) > maxSize) { 264 maxSize = this.megamenuSubmenuSizes.get($submenu); 265 } 266 }) 267 if($target.closest('.megamenu--level1').offsetHeight < maxSize + 2 * megamenuPadding) { 268 $target.closest('.megamenu--level1').style.minHeight = (maxSize + 2 * megamenuPadding) + 'px'; 269 } else { 270 window.setTimeout(() => { 271 $target.closest('.megamenu--level1').style.minHeight = (maxSize + 2 * megamenuPadding) + 'px'; 272 }, 300) 273 } 274 } else { 275 $target.querySelector('.megamenu--level1').style.minHeight = (extracontentContainerHeight + 2 * megamenuPadding) + 'px'; 276 } 277 event.cancelBubble = true; 278 }, 279 megamenuCloseSubmenu = function($target) { 280 $target.classList.remove('megamenu--open'); 281 $target.querySelector('ul').setAttribute('aria-hidden', 'true'); 282 $target.setAttribute('aria-expanded', 'false'); 283 this.megamenuOpenSubmenus.delete($target.querySelector('ul')); 284 $target.querySelectorAll('.megamenu--open').forEach($element => { 285 $element.classList.remove('megamenu--open'); 286 $element.setAttribute('aria-expanded', 'false') 287 $element.querySelector('ul').setAttribute('aria-hidden', 'true'); 288 this.megamenuOpenSubmenus.delete($element.querySelector('ul')); 289 }) 290 }, 291 connected = function() { 292 if(!this.querySelectorAll('ul').length) { 293 return; 294 } 295 let adoptedCssCode = this.getStyles('.rg-nav', '.rg-nav-wrapper'); 296 const fontColor = adoptedCssCode.match(/color:(.*?);/) 297 if(fontColor) { 298 adoptedCssCode = adoptedCssCode + ' .hc-nav-trigger {--nav-trigger-color: ' + fontColor[1] + '}'; 299 } 300 const adoptedCSS = document.createElement('style'); 301 adoptedCSS.innerHTML = adoptedCssCode; 302 /** 303 * search for custom templates to append or prepend to navigation-html (see "setup") 304 */ 305 this.templates = this.querySelectorAll('template'); 306 /** 307 * create elements and load required files 308 */ 309 const offcanvasCss = document.createElement('link'); 310 offcanvasCss.setAttribute('rel', 'stylesheet'); 311 offcanvasCss.setAttribute('href', 'https://cdn.regiogate.net/hc-offcanvas-nav/hc-offcanvas-nav.css'); 312 let customCss = ''; 313 if(this.dataset.customcss) { 314 customCss = document.createElement('link'); 315 customCss.setAttribute('rel', 'stylesheet'); 316 customCss.setAttribute('href', this.dataset.customcss); 317 } 318 if(this.dataset.mode) { 319 this.mode = this.dataset.mode; 320 } else { 321 this.mode = 'offcanvas'; 322 } 323 const linkElem = document.createElement('link'); 324 linkElem.setAttribute('rel', 'stylesheet'); 325 linkElem.setAttribute('href', this.rootpath + '/css/rg-nav.css'); 326 const offCanvasScript = document.createElement('script'); 327 this.wrapper = document.createElement('div'); 328 const $slot = document.createElement('slot') 329 this.wrapper.append($slot); 330 this.wrapper.setAttribute('class', 'rg-nav-wrapper'); 331 this.classList.forEach(classname => { 332 if(classname !== 'rg-nav') { 333 this.wrapper.classList.add(classname); 334 } 335 }) 336 if(this.dataset.customtoggle !== undefined) { 337 this.toggleButton = document.querySelector(this.dataset.customtoggle) 338 } else { 339 this.toggleButton = document.createElement('a'); 340 this.toggleButton.setAttribute('href', '#'); 341 this.toggleButton.setAttribute('class', 'hc-nav-trigger fas fa-bars'); 342 this.toggleButton.append(document.createElement('span')); 343 } 344 document.head.append(offcanvasCss, customCss); 345 switch(this.mode) { 346 case 'dropdown': 347 //nav remains in light dom, slotted in. Should become default, but needs css adaptations... 348 break; 349 case 'megamenu': 350 this.wrapper.innerHTML = this.innerHTML + this.wrapper.innerHTML; 351 const $megamenu = buildMegamenu.call(this); 352 this.innerHTML = ''; 353 this.append($megamenu); 354 break; 355 default: 356 this.wrapper.innerHTML = this.innerHTML + this.wrapper.innerHTML; 357 this.innerHTML = ''; 358 } 359 if(this.dataset.customtoggle === undefined) { 360 this.wrapper.append(this.toggleButton); 361 } 362 this.shadowRoot.append(linkElem, adoptedCSS, offCanvasScript, this.wrapper); 363 /** 364 * as offcanvas-script is loaded run setup 365 */ 366 offCanvasScript.onload = function() { 367 setup.call(this); 368 this.style.display = ''; 369 if(this.toggleButton.querySelector('.menu-button__icon')) { 370 this.toggleButton.classList.remove('hc-nav-trigger'); 371 } 372 }.bind(this); 373 offCanvasScript.src = 'https://cdn.regiogate.net/hc-offcanvas-nav/hc-offcanvas-nav.js'; 374 this.setupComplete(); 375 } 376 377/* 378 * exports 379 */ 380export class rgNav extends rgElement { 381 defaultConfig = { 382 width: 280, 383 height: 'auto', 384 disableAt: false, 385 pushContent: null, 386 swipeGestures: true, 387 expanded: false, 388 position: 'right', // left, right, top, botto, 389 levelOpen: 'overlap', // overlap, expand, none/fals, 390 levelSpacing: 40, 391 levelTitles: true, 392 closeOpenLevels: false, 393 closeActiveLevel: false, 394 navTitle: null, 395 navClass: '', 396 disableBody: true, 397 closeOnClick: true, 398 closeOnEsc: true, 399 activeToggleClass: null,
400 bodyInsert: 'prepend', // prepend/appen, 401 keepClasses: true, 402 removeOriginalNav: false, 403 rtl: false, 404 insertClose: true, 405 insertBack: true, 406 levelTitleAsBack: true, 407 labelClose: '', 408 labelBack: 'Back', 409 }; 410 toggleButton = null; 411 wrapper = null; 412 templates = []; 413 mode = "default"; 414 megamenuSubmenuSizes = new Map(); 415 megamenuOpenSubmenus = new Map(); 416 417 constructor() { 418 super(); 419 } 420 421 connectedCallback() { 422 this.setUpElement(); 423 this.offcanvasNav = null; 424 !this.shadowRoot && this.attachShadow({mode: 'open'}); 425 this.style.display = 'none'; 426 if(document.readyState === 'complete') { 427 connected.call(this); 428 } else { 429 window.addEventListener('load', connected.bind(this)) 430 } 431 document.addEventListener('rgsearch:show', this.hideNav.bind(this)); 432 } 433 434 showNav() { 435 this.offcanvasNav && this.offcanvasNav.open(); 436 document.dispatchEvent(new CustomEvent('rgnav:open', { 437 detail: {el: this} 438 })); 439 } 440 441 hideNav() { 442 this.offcanvasNav && this.offcanvasNav.close(); 443 } 444 445 toggleNav() { 446 if(this.offcanvasNav) { 447 this.offcanvasNav.toggle(); 448 if(this.offcanvasNav.isOpen()) { 449 document.dispatchEvent(new CustomEvent('rgnav:open', { 450 detail: {el: this} 451 })); 452 } 453 } 454 } 455 456 isOpen() { 457 return this.offcanvasNav ? this.offcanvasNav.isOpen() : false; 458 } 459}
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.