-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheader.js
More file actions
166 lines (151 loc) · 6.17 KB
/
header.js
File metadata and controls
166 lines (151 loc) · 6.17 KB
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
import { getMetadata } from '../../scripts/aem.js';
import { loadFragment } from '../fragment/fragment.js';
// media query match that indicates mobile/tablet width
const isDesktop = window.matchMedia('(min-width: 900px)');
function closeOnEscape(e) {
if (e.code === 'Escape') {
const nav = document.getElementById('nav');
const navSections = nav.querySelector('.nav-sections');
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
if (navSectionExpanded && isDesktop.matches) {
// eslint-disable-next-line no-use-before-define
toggleAllNavSections(navSections);
navSectionExpanded.focus();
} else if (!isDesktop.matches) {
// eslint-disable-next-line no-use-before-define
toggleMenu(nav, navSections);
nav.querySelector('button').focus();
}
}
}
function closeOnFocusLost(e) {
const nav = e.currentTarget;
if (!nav.contains(e.relatedTarget)) {
const navSections = nav.querySelector('.nav-sections');
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
if (navSectionExpanded && isDesktop.matches) {
// eslint-disable-next-line no-use-before-define
toggleAllNavSections(navSections, false);
} else if (!isDesktop.matches) {
// eslint-disable-next-line no-use-before-define
toggleMenu(nav, navSections, false);
}
}
}
function openOnKeydown(e) {
const focused = document.activeElement;
const isNavDrop = focused.className === 'nav-drop';
if (isNavDrop && (e.code === 'Enter' || e.code === 'Space')) {
const dropExpanded = focused.getAttribute('aria-expanded') === 'true';
// eslint-disable-next-line no-use-before-define
toggleAllNavSections(focused.closest('.nav-sections'));
focused.setAttribute('aria-expanded', dropExpanded ? 'false' : 'true');
}
}
function focusNavSection() {
document.activeElement.addEventListener('keydown', openOnKeydown);
}
/**
* Toggles all nav sections
* @param {Element} sections The container element
* @param {Boolean} expanded Whether the element should be expanded or collapsed
*/
function toggleAllNavSections(sections, expanded = false) {
sections.querySelectorAll('.nav-sections .default-content-wrapper > ul > li').forEach((section) => {
section.setAttribute('aria-expanded', expanded);
});
}
/**
* Toggles the entire nav
* @param {Element} nav The container element
* @param {Element} navSections The nav sections within the container element
* @param {*} forceExpanded Optional param to force nav expand behavior when not null
*/
function toggleMenu(nav, navSections, forceExpanded = null) {
const expanded = forceExpanded !== null ? !forceExpanded : nav.getAttribute('aria-expanded') === 'true';
const button = nav.querySelector('.nav-hamburger button');
document.body.style.overflowY = (expanded || isDesktop.matches) ? '' : 'hidden';
nav.setAttribute('aria-expanded', expanded ? 'false' : 'true');
toggleAllNavSections(navSections, expanded || isDesktop.matches ? 'false' : 'true');
button.setAttribute('aria-label', expanded ? 'Open navigation' : 'Close navigation');
// enable nav dropdown keyboard accessibility
const navDrops = navSections.querySelectorAll('.nav-drop');
if (isDesktop.matches) {
navDrops.forEach((drop) => {
if (!drop.hasAttribute('tabindex')) {
drop.setAttribute('tabindex', 0);
drop.addEventListener('focus', focusNavSection);
}
});
} else {
navDrops.forEach((drop) => {
drop.removeAttribute('tabindex');
drop.removeEventListener('focus', focusNavSection);
});
}
// enable menu collapse on escape keypress
if (!expanded || isDesktop.matches) {
// collapse menu on escape press
window.addEventListener('keydown', closeOnEscape);
// collapse menu on focus lost
nav.addEventListener('focusout', closeOnFocusLost);
} else {
window.removeEventListener('keydown', closeOnEscape);
nav.removeEventListener('focusout', closeOnFocusLost);
}
}
/**
* loads and decorates the header, mainly the nav
* @param {Element} block The header block element
*/
export default async function decorate(block) {
// load nav as fragment
const navMeta = getMetadata('nav');
const navPath = navMeta ? new URL(navMeta, window.location).pathname : '/nav';
const fragment = await loadFragment(navPath);
// decorate nav DOM
block.textContent = '';
const nav = document.createElement('nav');
nav.id = 'nav';
while (fragment.firstElementChild) nav.append(fragment.firstElementChild);
const classes = ['brand', 'sections', 'tools'];
classes.forEach((c, i) => {
const section = nav.children[i];
if (section) section.classList.add(`nav-${c}`);
});
const navBrand = nav.querySelector('.nav-brand');
const brandLink = navBrand.querySelector('.button');
if (brandLink) {
brandLink.className = '';
brandLink.closest('.button-container').className = '';
}
const navSections = nav.querySelector('.nav-sections');
if (navSections) {
navSections.querySelectorAll(':scope .default-content-wrapper > ul > li').forEach((navSection) => {
if (navSection.querySelector('ul')) navSection.classList.add('nav-drop');
navSection.addEventListener('click', () => {
if (isDesktop.matches) {
const expanded = navSection.getAttribute('aria-expanded') === 'true';
toggleAllNavSections(navSections);
navSection.setAttribute('aria-expanded', expanded ? 'false' : 'true');
}
});
});
}
// hamburger for mobile
const hamburger = document.createElement('div');
hamburger.classList.add('nav-hamburger');
hamburger.innerHTML = `<button type="button" aria-controls="nav" aria-label="Open navigation">
<span class="nav-hamburger-icon"></span>
</button>`;
hamburger.addEventListener('click', () => toggleMenu(nav, navSections));
nav.prepend(hamburger);
nav.setAttribute('aria-expanded', 'false');
// prevent mobile nav behavior on window resize
toggleMenu(nav, navSections, isDesktop.matches);
isDesktop.addEventListener('change', () => toggleMenu(nav, navSections, isDesktop.matches));
const navWrapper = document.createElement('div');
navWrapper.className = 'nav-wrapper';
navWrapper.append(nav);
block.append(navWrapper);
}