Skip to content

Commit 2abd09d

Browse files
committed
LYNX-760: initial implementation
1 parent 3df82c1 commit 2abd09d

File tree

5 files changed

+224
-114
lines changed

5 files changed

+224
-114
lines changed

.eslintrc.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ module.exports = {
1515
'linebreak-style': ['error', 'unix'], // enforce unix linebreaks
1616
'no-param-reassign': [2, { props: false }], // allow modifying properties of param
1717
'no-use-before-define': [2, { functions: false }],
18-
'no-console': [
19-
'error',
20-
{
21-
allow: ['warn', 'error', 'info', 'debug'],
22-
},
23-
],
18+
// 'no-console': [
19+
// 'error',
20+
// {
21+
// allow: ['warn', 'error', 'info', 'debug'],
22+
// },
23+
// ],
2424
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
2525
},
2626
};

blocks/header/header.js

Lines changed: 13 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -12,109 +12,12 @@ import { loadFragment } from '../fragment/fragment.js';
1212
import renderAuthCombine from './renderAuthCombine.js';
1313
import { renderAuthDropdown } from './renderAuthDropdown.js';
1414

15-
// media query match that indicates mobile/tablet width
16-
const isDesktop = window.matchMedia('(min-width: 900px)');
17-
18-
function closeOnEscape(e) {
19-
if (e.code === 'Escape') {
20-
const nav = document.getElementById('nav');
21-
const navSections = nav.querySelector('.nav-sections');
22-
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
23-
if (navSectionExpanded && isDesktop.matches) {
24-
// eslint-disable-next-line no-use-before-define
25-
toggleAllNavSections(navSections);
26-
navSectionExpanded.focus();
27-
} else if (!isDesktop.matches) {
28-
// eslint-disable-next-line no-use-before-define
29-
toggleMenu(nav, navSections);
30-
nav.querySelector('button').focus();
31-
}
32-
}
33-
}
34-
35-
function closeOnFocusLost(e) {
36-
const nav = e.currentTarget;
37-
if (!nav.contains(e.relatedTarget)) {
38-
const navSections = nav.querySelector('.nav-sections');
39-
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
40-
if (navSectionExpanded && isDesktop.matches) {
41-
// eslint-disable-next-line no-use-before-define
42-
toggleAllNavSections(navSections, false);
43-
} else if (!isDesktop.matches) {
44-
// eslint-disable-next-line no-use-before-define
45-
toggleMenu(nav, navSections, false);
46-
}
47-
}
48-
}
49-
50-
function openOnKeydown(e) {
51-
const focused = document.activeElement;
52-
const isNavDrop = focused.className === 'nav-drop';
53-
if (isNavDrop && (e.code === 'Enter' || e.code === 'Space')) {
54-
const dropExpanded = focused.getAttribute('aria-expanded') === 'true';
55-
// eslint-disable-next-line no-use-before-define
56-
toggleAllNavSections(focused.closest('.nav-sections'));
57-
focused.setAttribute('aria-expanded', dropExpanded ? 'false' : 'true');
58-
}
59-
}
60-
61-
function focusNavSection() {
62-
document.activeElement.addEventListener('keydown', openOnKeydown);
63-
}
64-
65-
/**
66-
* Toggles all nav sections
67-
* @param {Element} sections The container element
68-
* @param {Boolean} expanded Whether the element should be expanded or collapsed
69-
*/
70-
function toggleAllNavSections(sections, expanded = false) {
71-
sections
72-
.querySelectorAll('.nav-sections .default-content-wrapper > ul > li')
73-
.forEach((section) => {
74-
section.setAttribute('aria-expanded', expanded);
75-
});
76-
}
77-
78-
/**
79-
* Toggles the entire nav
80-
* @param {Element} nav The container element
81-
* @param {Element} navSections The nav sections within the container element
82-
* @param {*} forceExpanded Optional param to force nav expand behavior when not null
83-
*/
84-
function toggleMenu(nav, navSections, forceExpanded = null) {
85-
const expanded = forceExpanded !== null ? !forceExpanded : nav.getAttribute('aria-expanded') === 'true';
86-
const button = nav.querySelector('.nav-hamburger button');
87-
document.body.style.overflowY = expanded || isDesktop.matches ? '' : 'hidden';
88-
nav.setAttribute('aria-expanded', expanded ? 'false' : 'true');
89-
toggleAllNavSections(navSections, expanded || isDesktop.matches ? 'false' : 'true');
90-
button.setAttribute('aria-label', expanded ? 'Open navigation' : 'Close navigation');
91-
// enable nav dropdown keyboard accessibility
92-
const navDrops = navSections.querySelectorAll('.nav-drop');
93-
if (isDesktop.matches) {
94-
navDrops.forEach((drop) => {
95-
if (!drop.hasAttribute('tabindex')) {
96-
drop.setAttribute('tabindex', 0);
97-
drop.addEventListener('focus', focusNavSection);
98-
}
99-
});
100-
} else {
101-
navDrops.forEach((drop) => {
102-
drop.removeAttribute('tabindex');
103-
drop.removeEventListener('focus', focusNavSection);
104-
});
105-
}
106-
107-
// enable menu collapse on escape keypress
108-
if (!expanded || isDesktop.matches) {
109-
// collapse menu on escape press
110-
window.addEventListener('keydown', closeOnEscape);
111-
// collapse menu on focus lost
112-
nav.addEventListener('focusout', closeOnFocusLost);
113-
} else {
114-
window.removeEventListener('keydown', closeOnEscape);
115-
nav.removeEventListener('focusout', closeOnFocusLost);
116-
}
117-
}
15+
import {
16+
isDesktop,
17+
parseNavSections,
18+
toggleAllNavSections,
19+
toggleMenu,
20+
} from './menu/menu.js';
11821

11922
/**
12023
* loads and decorates the header, mainly the nav
@@ -147,10 +50,14 @@ export default async function decorate(block) {
14750

14851
const navSections = nav.querySelector('.nav-sections');
14952
if (navSections) {
150-
navSections
151-
.querySelectorAll(':scope .default-content-wrapper > ul > li')
53+
const visibleSections = parseNavSections(navSections);
54+
55+
visibleSections
15256
.forEach((navSection) => {
153-
if (navSection.querySelector('ul')) navSection.classList.add('nav-drop');
57+
if (navSection.querySelector('ul')) {
58+
navSection.classList.add('nav-drop');
59+
}
60+
15461
navSection.addEventListener('click', () => {
15562
if (isDesktop.matches) {
15663
const expanded = navSection.getAttribute('aria-expanded') === 'true';

blocks/header/menu/hashTagParser.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Parses href hash
3+
*
4+
* @param urlHash
5+
* @returns {*[]}
6+
*/
7+
const namespaces = [
8+
'display_for_',
9+
'apply_style_',
10+
];
11+
12+
function parseUrlHashTag(href) {
13+
if (!href.indexOf('#') > 0) {
14+
return;
15+
}
16+
17+
const parsed = [];
18+
const hashTags = href.split('#').slice(1);
19+
20+
if (hashTags) {
21+
namespaces.forEach((ns) => {
22+
hashTags.forEach((tag) => {
23+
const value = tag.split(ns);
24+
if (value && value.length === 2) {
25+
parsed.push({
26+
namespace: ns,
27+
value: value[1],
28+
});
29+
}
30+
});
31+
});
32+
}
33+
34+
return parsed;
35+
}
36+
37+
export {
38+
parseUrlHashTag,
39+
};

blocks/header/menu/menu.js

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { parseUrlHashTag } from './hashTagParser.js';
2+
3+
// media query match that indicates mobile/tablet width
4+
const isDesktop = window.matchMedia('(min-width: 900px)');
5+
6+
function closeOnEscape(e) {
7+
if (e.code === 'Escape') {
8+
const nav = document.getElementById('nav');
9+
const navSections = nav.querySelector('.nav-sections');
10+
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
11+
if (navSectionExpanded && isDesktop.matches) {
12+
// eslint-disable-next-line no-use-before-define
13+
toggleAllNavSections(navSections);
14+
navSectionExpanded.focus();
15+
} else if (!isDesktop.matches) {
16+
// eslint-disable-next-line no-use-before-define
17+
toggleMenu(nav, navSections);
18+
nav.querySelector('button').focus();
19+
}
20+
}
21+
}
22+
23+
function closeOnFocusLost(e) {
24+
const nav = e.currentTarget;
25+
if (!nav.contains(e.relatedTarget)) {
26+
const navSections = nav.querySelector('.nav-sections');
27+
const navSectionExpanded = navSections.querySelector('[aria-expanded="true"]');
28+
if (navSectionExpanded && isDesktop.matches) {
29+
// eslint-disable-next-line no-use-before-define
30+
toggleAllNavSections(navSections, false);
31+
} else if (!isDesktop.matches) {
32+
// eslint-disable-next-line no-use-before-define
33+
toggleMenu(nav, navSections, false);
34+
}
35+
}
36+
}
37+
38+
function openOnKeydown(e) {
39+
const focused = document.activeElement;
40+
const isNavDrop = focused.className === 'nav-drop';
41+
if (isNavDrop && (e.code === 'Enter' || e.code === 'Space')) {
42+
const dropExpanded = focused.getAttribute('aria-expanded') === 'true';
43+
// eslint-disable-next-line no-use-before-define
44+
toggleAllNavSections(focused.closest('.nav-sections'));
45+
focused.setAttribute('aria-expanded', dropExpanded ? 'false' : 'true');
46+
}
47+
}
48+
49+
function focusNavSection() {
50+
document.activeElement.addEventListener('keydown', openOnKeydown);
51+
}
52+
53+
/**
54+
* Recursively gets all LI elements from menu section
55+
*
56+
* @param menu
57+
* @param elements
58+
* @returns {*[]}
59+
*/
60+
function getLiElements(menu, elements = []) {
61+
const uls = menu.querySelectorAll('ul');
62+
63+
/*
64+
@TODO: handle nested ul > li > ul; makes sure it wont get into infinite loop; optimization (?);
65+
*/
66+
uls.forEach((ul) => {
67+
ul.childNodes.forEach((n) => {
68+
if (n.nodeName === 'ul') {
69+
getLiElements(n, elements);
70+
}
71+
// if (n.nodeName === 'li' && n.querySelectorAll('ul').length > 0) {
72+
// getLiElements(n.querySelectorAll('ul'), elements);
73+
// }
74+
if (n.nodeType !== 3) {
75+
elements.push(n);
76+
}
77+
});
78+
});
79+
return elements;
80+
}
81+
82+
/**
83+
* Parses nav fragment; disable visible elements based on hash tags
84+
*
85+
* @param navSections
86+
* @returns {*}
87+
*/
88+
function parseNavSections(navSections) {
89+
const visibleMenuItems = navSections.querySelectorAll(':scope .default-content-wrapper > ul > li');
90+
91+
visibleMenuItems.forEach((section) => {
92+
const liElements = getLiElements(section);
93+
liElements.forEach((liElement) => {
94+
const link = liElement.querySelector('a') && liElement.querySelector('a').href;
95+
if (link) {
96+
const hashTags = parseUrlHashTag(link);
97+
console.table(`Hash tags for ${link}`, hashTags);
98+
// liElement.parentNode.removeChild(liElement);
99+
}
100+
});
101+
});
102+
return visibleMenuItems;
103+
}
104+
105+
/**
106+
* Toggles all nav sections
107+
* @param {Element} sections The container element
108+
* @param {Boolean} expanded Whether the element should be expanded or collapsed
109+
*/
110+
function toggleAllNavSections(sections, expanded = false) {
111+
sections
112+
.querySelectorAll('.nav-sections .default-content-wrapper > ul > li')
113+
.forEach((section) => {
114+
section.setAttribute('aria-expanded', expanded);
115+
});
116+
}
117+
118+
/**
119+
* Toggles the entire nav
120+
* @param {Element} nav The container element
121+
* @param {Element} navSections The nav sections within the container element
122+
* @param {*} forceExpanded Optional param to force nav expand behavior when not null
123+
*/
124+
function toggleMenu(nav, navSections, forceExpanded = null) {
125+
const expanded = forceExpanded !== null ? !forceExpanded : nav.getAttribute('aria-expanded') === 'true';
126+
const button = nav.querySelector('.nav-hamburger button');
127+
document.body.style.overflowY = expanded || isDesktop.matches ? '' : 'hidden';
128+
nav.setAttribute('aria-expanded', expanded ? 'false' : 'true');
129+
toggleAllNavSections(navSections, expanded || isDesktop.matches ? 'false' : 'true');
130+
button.setAttribute('aria-label', expanded ? 'Open navigation' : 'Close navigation');
131+
// enable nav dropdown keyboard accessibility
132+
const navDrops = navSections.querySelectorAll('.nav-drop');
133+
if (isDesktop.matches) {
134+
navDrops.forEach((drop) => {
135+
if (!drop.hasAttribute('tabindex')) {
136+
drop.setAttribute('tabindex', 0);
137+
drop.addEventListener('focus', focusNavSection);
138+
}
139+
});
140+
} else {
141+
navDrops.forEach((drop) => {
142+
drop.removeAttribute('tabindex');
143+
drop.removeEventListener('focus', focusNavSection);
144+
});
145+
}
146+
147+
// enable menu collapse on escape keypress
148+
if (!expanded || isDesktop.matches) {
149+
// collapse menu on escape press
150+
window.addEventListener('keydown', closeOnEscape);
151+
// collapse menu on focus lost
152+
nav.addEventListener('focusout', closeOnFocusLost);
153+
} else {
154+
window.removeEventListener('keydown', closeOnEscape);
155+
nav.removeEventListener('focusout', closeOnFocusLost);
156+
}
157+
}
158+
159+
export {
160+
isDesktop,
161+
parseNavSections,
162+
toggleAllNavSections,
163+
toggleMenu,
164+
};

fstab.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mountpoints:
2-
/: https://adobe.sharepoint.com/:f:/r/sites/HelixProjects/Shared%20Documents/sites/starter-content-commerce
2+
/: https://da.live/#/sirugh/rafals-storefront-repo
33

44
folders:
5-
/products/: /products/default
5+
/products/: /products/default

0 commit comments

Comments
 (0)