PageSourceSearch

https://www.brighthorizons.com/dist/js/globalNavBar.js?v1.0

js brighthorizons.com collected 2026-09-24 08:28:52 UTC 6,816 bytes, 211 lines download raw bytes

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.