class Navigation { constructor() { this.counter = 0; this.initElements(); if (this.navs.size === 0) { return; } this.openNav = null; this.initListeners(); } initElements() { this.navs = new Map(); document.querySelectorAll('nav:has(.submenu), nav:has(.toggle)').forEach(nav => { let navID = nav.id; if (navID === '') { navID = `nav-${this.counter}`; nav.id = navID; this.counter++; } if (nav.querySelector('.submenu')) { nav.addEventListener('mouseenter', this.hoverOnListener); nav.addEventListener('mouseleave', this.hoverOffListener); } let [ toggles, submenus, submenuToggles ] = [ nav.querySelectorAll('nav .toggle'), nav.querySelectorAll('.has-submenu'), nav.querySelectorAll('.toggle:not(.main)') ]; let elements = { nav: nav, toggles: toggles, submenus: submenus, submenuToggles: submenuToggles } this.navs.set(navID, elements); this.counter++; }); } initListeners() { this.clickListener = this.handleClick.bind(this); this.escapeListener = this.handleEscape.bind(this); this.hoverOnListener = this.handleHoverOn.bind(this); this.hoverOffListener = this.handleHoverOff.bind(this); document.addEventListener('click', this.clickListener); } handleClick(e) { if (this.navs.size === 0) { return; } if (this.openNav && e.target.closest(`#${this.openNav}`) === null) { console.log('Closing nav', this.openNav); this.toggleNav(false, this.openNav); } // if (!e.target.closest(this.openNav)) { // console.log('Not closest nav ids'); // console.log(this.navIDs()); // return; // } let toggle = e.target.closest('.toggle.main'); if (toggle) { let nav = toggle.closest('nav'); this.toggleNav(!nav.classList.contains('open'), nav.id); } let submenuToggle = e.target.closest('[data-action="toggle-submenu"], .has-submenu .a') if (submenuToggle) { let li = submenuToggle.closest('li'); this.toggleSubmenu(!li.classList.contains('open'), li); } } handleHoverOn(e) { let nav = e.target.closest('nav'); if (nav) { this.toggleNav(true, nav.id); } let submenu = e.target.closest('.has-submenu'); if (submenu) { this.toggleSubmenu(true, submenu); } } handleHoverOff(e) { let nav = e.target.closest('nav'); if (nav) { this.toggleNav(false, nav.id); } // let submenu = e.target.closest('.has-submenu'); // if (submenu) { // this.toggleSubmenu(false, submenu); // } } handleEscape(e) { if (this.openNav && e.key === 'Escape') { this.toggleNav(false, this.openNav); } } toggleNav(on, id) { let nav = this.navs.get(id); if (!nav) { return; } if (on && id !== this.openNav) { this.toggleNav(false, this.openNav); } if (on) { this.openNav = id; document.addEventListener('keydown', this.escapeListener); } else { if (this.openNav === id) { this.openNav = null; } document.removeEventListener('keydown', this.escapeListener); if (!nav.nav.classList.contains('sidebar')) { Array.from(nav.submenus).forEach(submenu => { if(submenu.classList.contains('open')) { this.toggleSubmenu(false, submenu); } }); } } nav.nav.ariaExpanded = on; nav.nav.classList.toggle('open', on); nav.ariaHidden = !on; if (on) { nav.nav.querySelector('a:not(.skip-to-content)')?.focus(); } } toggleSubmenu(on, submenu) { let [ toggle, firstLink ] = [ submenu.querySelector('.toggle'), submenu.querySelector('a') ]; submenu.classList.toggle('open', on); submenu.ariaHidden = !on; toggle.ariaExpanded = on; if (on && firstLink) { firstLink.focus(); } } } document.addEventListener('DOMContentLoaded', function() { window.jvbNav = new Navigation(); });