-
Notifications
You must be signed in to change notification settings - Fork 109
/
Copy pathheader.js
247 lines (224 loc) · 6.54 KB
/
header.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
* Header component
*
*/
class Header {
constructor() {
this.menuIsOpen = false
this.navigation = document.querySelector('.nhsuk-navigation')
this.navigationList = document.querySelector(
'.nhsuk-header__navigation-list'
)
this.mobileMenu = document.createElement('ul')
this.mobileMenuToggleButton = document.querySelector(
'.nhsuk-header__menu-toggle'
)
this.mobileMenuCloseButton = document.createElement('button')
this.mobileMenuContainer = document.querySelector(
'.nhsuk-mobile-menu-container'
)
this.breakpoints = []
this.width = document.body.offsetWidth
}
init() {
if (
!this.navigation ||
!this.navigationList ||
!this.mobileMenuToggleButton ||
!this.mobileMenuContainer
) {
return
}
this.setupMobileMenu()
this.calculateBreakpoints()
this.updateNavigation()
this.doOnOrientationChange()
this.handleResize = this.debounce(() => {
this.calculateBreakpoints()
this.updateNavigation()
})
this.mobileMenuToggleButton.addEventListener(
'click',
this.toggleMobileMenu.bind(this)
)
window.addEventListener('resize', this.handleResize)
window.addEventListener('orientationchange', this.doOnOrientationChange())
}
debounce(func, timeout = 100) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, timeout)
}
}
/**
* Calculate breakpoints.
*
* Calculate the breakpoints by summing the widths of
* each navigation item.
*
*/
calculateBreakpoints() {
// Get the width of the gap between each navigation item
const navigationListStyles = window.getComputedStyle(this.navigationList)
const gapPixels = navigationListStyles.getPropertyValue('gap')
const gap = Number(gapPixels.replace('px', ''))
let childrenWidth = 0
for (let i = 0; i < this.navigationList.children.length; i++) {
childrenWidth += this.navigationList.children[i].offsetWidth + gap
this.breakpoints[i] = childrenWidth
}
}
// Add the mobile menu to the DOM
setupMobileMenu() {
this.mobileMenuContainer.appendChild(this.mobileMenu)
this.mobileMenu.classList.add(
'nhsuk-header__drop-down',
'nhsuk-header__drop-down--hidden'
)
}
/**
* Close the mobile menu
*
* Closes the mobile menu and updates accessibility state.
*
* Removes the margin-bottom from the navigation
*/
closeMobileMenu() {
this.menuIsOpen = false
this.mobileMenu.classList.add('nhsuk-header__drop-down--hidden')
this.navigation.style.marginBottom = 0
this.mobileMenuToggleButton.setAttribute('aria-expanded', 'false')
this.mobileMenuToggleButton.focus()
this.mobileMenuCloseButton.removeEventListener(
'click',
this.closeMobileMenu.bind(this)
)
document.removeEventListener('keydown', this.handleEscapeKey.bind(this))
}
/**
* Escape key handler
*
* This function is called when the user
* presses the escape key to close the mobile menu.
*
*/
handleEscapeKey(e) {
if (e.key === 'Escape') {
this.closeMobileMenu()
}
}
/**
* Open the mobile menu
*
* Opens the mobile menu and updates accessibility state.
*
* The mobile menu is absolutely positioned, so it adds a margin
* to the bottom of the navigation to prevent it from overlapping
*
* Adds event listeners for the close button,
*/
openMobileMenu() {
this.menuIsOpen = true
this.mobileMenu.classList.remove('nhsuk-header__drop-down--hidden')
const marginBody = this.mobileMenu.offsetHeight
this.navigation.style.marginBottom = `${marginBody}px`
this.mobileMenuToggleButton.setAttribute('aria-expanded', 'true')
// add event listener for esc key to close menu
document.addEventListener('keydown', this.handleEscapeKey.bind(this))
// add event listener for close icon to close menu
this.mobileMenuCloseButton.addEventListener(
'click',
this.closeMobileMenu.bind(this)
)
}
/**
* Handle menu button click
*
* Toggles the mobile menu between open and closed
*/
toggleMobileMenu() {
if (this.menuIsOpen) {
this.closeMobileMenu()
} else {
this.openMobileMenu()
}
}
/**
* Update nav for the available space
*
* If the available space is less than the current breakpoint,
* add the mobile menu toggle button and move the last
* item in the list to the drop-down list.
*
* If the available space is greater than the current breakpoint,
* remove the mobile menu toggle button and move the first item in the
*
* Additionally will close the mobile menu if the window gets resized
* and the menu is open.
*/
updateNavigation() {
const availableSpace = this.navigationList.offsetWidth
let itemsVisible = this.navigationList.children.length
if (availableSpace < this.breakpoints[itemsVisible - 1]) {
this.mobileMenuToggleButton.classList.add(
'nhsuk-header__menu-toggle--visible'
)
this.mobileMenuContainer.classList.add(
'nhsuk-mobile-menu-container--visible'
)
if (itemsVisible === 2) {
return
}
while (availableSpace < this.breakpoints[itemsVisible - 1]) {
this.mobileMenu.insertBefore(
this.navigationList.children[itemsVisible - 2],
this.mobileMenu.firstChild
)
itemsVisible -= 1
}
} else if (availableSpace > this.breakpoints[itemsVisible]) {
while (availableSpace > this.breakpoints[itemsVisible]) {
this.navigationList.insertBefore(
this.mobileMenu.removeChild(this.mobileMenu.firstChild),
this.mobileMenuContainer
)
itemsVisible += 1
}
}
if (!this.mobileMenu.children.length) {
this.mobileMenuToggleButton.classList.remove(
'nhsuk-header__menu-toggle--visible'
)
this.mobileMenuContainer.classList.remove(
'nhsuk-mobile-menu-container--visible'
)
}
if (document.body.offsetWidth !== this.width && this.menuIsOpen) {
this.closeMobileMenu()
}
}
/**
* Orientation change
*
* Check the orientation of the device, if changed it will trigger a
* update to the breakpoints and navigation.
*/
doOnOrientationChange() {
switch (window.orientation) {
case 90:
setTimeout(() => {
this.calculateBreakpoints()
this.updateNavigation()
}, 200)
break
default:
break
}
}
}
export default () => {
new Header().init()
}