1// TODO: * add/remove tabIndex for focusable elements. 2// TODO: * add focusable elements function. 3// TODO: * replace children accordion anchors in panels. 4 5class NavigationBar { 6 constructor(el) { 7 this.dropdowns = el.querySelectorAll('.nav-item.-hasSubnav') 8 this.subtriggers = el.querySelectorAll('.navBar__subitemTrigger') 9 this.subnavs = el.querySelectorAll('.navBar__subitemPanel') 10 this.navItems = [] 11 this.activeNav = null 12 this.setup() 13 this.bindEvents(el) 14 this.init() 15 } 16 17 setup() { 18 this.buildNavObjects() 19 } 20 21 bindEvents(el) { 22 this.navItems.forEach((item) => { 23 if (item.trigger) { 24 item.trigger.addEventListener('click', () => this.toggleNav(item)) 25 } 26 if (item.closer) { 27 item.closer.addEventListener('click', () => this.toggleNav(item)); 28 } 29 }) 30 this.subtriggers.forEach((subitem) => { 31 const subPanel = el.querySelector('#' + subitem.getAttribute('aria-controls')) 32 subitem.addEventListener('click', () => this.toggleSubNav(subitem, subPanel)) 33 }) 34 document.addEventListener('click', (e) => { 35 if (!el.contains(e.target)) { 36 this.closeAllNavs() 37 } 38 }) 39 } 40 41 init() { 42 this.closeAllNavs() 43 } 44 45 buildNavObjects() { 46 Array.from(this.dropdowns).forEach((node) => { 47 const item = { 48 node, 49 isActive: false, 50 trigger: node.querySelector('.navBar__itemTrigger'), 51 closer: node.querySelector('.close-x'), 52 panel: node.querySelector('.navBar__itemPanel'), 53 anchors: node.querySelectorAll('a') 54 } 55 this.navItems = [...this.navItems, item] 56 }) 57 } 58 59 toggleSubNav(subitem, subpanel) { 60 if (subpanel.classList.contains('-active')) { 61 subitem.setAttribute('aria-expanded', false) 62 subitem.classList.add('collapsed') 63 subpanel.setAttribute('aria-expanded', false) 64 subpanel.classList.remove('-active') 65 this.onStart(subpanel, () => { 66 subpanel.classList.remove('-open') 67 subpanel.classList.add('-closing') 68 }) 69 this.onComplete(subpanel, () => { 70 subpanel.classList.remove('-closing') 71 subpanel.classList.add('collapse') 72 }) 73 subpanel.style.height = '0px' 74 } else { 75 subitem.setAttribute('aria-expanded', true) 76 subitem.classList.remove('collapsed') 77 subpanel.setAttribute('aria-expanded', true) 78 subpanel.classList.add('-active') 79 this.onStart(subpanel, () => { 80 subpanel.classList.remove('collapse') 81 subpanel.classList.add('-opening') 82 }) 83 this.onComplete(subpanel, () => { 84 subpanel.classList.remove('-opening') 85 subpanel.classList.add('-open') 86 }) 87 subpanel.style.height = 'auto' 88 const maxHeight = subpanel.clientHeight + 'px' 89 subpanel.style.height = '0px' 90 setTimeout(() => { 91 subpanel.style.height = maxHeight 92 }, 0); 93 } 94 } 95 96 toggleNav(item) { 97 if (item.isActive) { 98 this.activeNav = null 99 this.closeNavItem(item) 100 } else { 101 if (this.activeNav) { 102 this.closeNavItem(this.activeNav) 103 } 104 this.activeNav = item 105 this.activateNavItem(item) 106 } 107 } 108 109 closeAllNavs() { 110 this.navItems.forEach((item) => { 111 item.isActive = false 112 this.resetTrigger(item.trigger) 113 this.resetPanel(item.panel) 114 this.removeTabindex(item) 115 }) 116 this.activeNav = null 117 this.subtriggers.forEach((subitem) => { 118 subitem.setAttribute('aria-expanded', false) 119 subitem.classList.add('collapsed') 120 })
121 this.subnavs.forEach((subpanel) => { 122 subpanel.setAttribute('aria-expanded', false) 123 subpanel.classList.remove('-active') 124 subpanel.style.height = '0px' 125 }) 126 } 127 128 activateNavItem(item) { 129 item.isActive = true 130 this.activateTrigger(item.trigger) 131 this.activatePanel(item.panel) 132 this.addTabIndex(item) 133 } 134 135 closeNavItem(item) { 136 item.isActive = false 137 this.resetTrigger(item.trigger) 138 this.resetPanel(item.panel) 139 this.removeTabindex(item) 140 } 141 142 activateTrigger(trigger) { 143 trigger.setAttribute('aria-expanded', true) 144 trigger.classList.remove('collapsed') 145 } 146 147 activatePanel(panel) { 148 panel.setAttribute('aria-expanded', true) 149 this.onStart(panel, () => { 150 panel.classList.remove('collapse') 151 panel.classList.add('-opening') 152 }) 153 this.onComplete(panel, () => { 154 panel.classList.remove('-opening') 155 panel.classList.add('-open') 156 }) 157 panel.style.height = 'auto' 158 159 if (window.innerWidth > 1200) { 160 const maxHeight = panel.clientHeight + 'px' 161 panel.style.height = '0px' 162 setTimeout(() => { 163 panel.style.height = maxHeight 164 }, 0); 165 } 166 } 167 168 resetTrigger(trigger) { 169 trigger.setAttribute('aria-expanded', false) 170 trigger.classList.add('collapsed') 171 } 172 173 resetPanel(panel) { 174 panel.setAttribute('aria-expanded', false) 175 this.onStart(panel, () => { 176 panel.classList.remove('-open') 177 panel.classList.add('-closing') 178 }) 179 this.onComplete(panel, () => { 180 panel.classList.remove('-closing') 181 panel.classList.add('collapse') 182 }) 183 panel.style.height = '0px' 184 } 185 186 onStart(target, callback) { 187 target.addEventListener('transitionrun', callback, { once: true }) 188 } 189 190 onComplete(target, callback) { 191 target.addEventListener('transitionend', callback, { once: true }) 192 } 193 194 removeTabindex(item) { 195 item.anchors.forEach((anchor) => { 196 anchor.tabIndex = -1 197 }) 198 } 199 addTabIndex(item) { 200 item.anchors.forEach((anchor) => { 201 anchor.tabIndex = 0 202 }) 203 } 204} 205 206// export default NavigationBar 207 208(() => { 209 // Global Navigation Bar Functions 210 document.querySelectorAll('.js-navBar').forEach((el) => new NavigationBar(el)) 211})();
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.