@ecl/mega-menu 5.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +296 -0
- package/README.md +105 -0
- package/mega-menu-item.html.twig +371 -0
- package/mega-menu-print.scss +8 -0
- package/mega-menu.html.twig +170 -0
- package/mega-menu.js +1703 -0
- package/mega-menu.scss +1402 -0
- package/package.json +36 -0
package/mega-menu.js
ADDED
|
@@ -0,0 +1,1703 @@
|
|
|
1
|
+
/* eslint-disable class-methods-use-this */
|
|
2
|
+
|
|
3
|
+
import { queryOne, queryAll } from '@ecl/dom-utils';
|
|
4
|
+
import EventManager from '@ecl/event-manager';
|
|
5
|
+
import isMobile from 'mobile-device-detect';
|
|
6
|
+
import { createFocusTrap } from 'focus-trap';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {HTMLElement} element DOM element for component instantiation and scope
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {String} options.openSelector Selector for the hamburger button
|
|
12
|
+
* @param {String} options.backSelector Selector for the back button
|
|
13
|
+
* @param {String} options.innerSelector Selector for the menu inner
|
|
14
|
+
* @param {String} options.itemSelector Selector for the menu item
|
|
15
|
+
* @param {String} options.linkSelector Selector for the menu link
|
|
16
|
+
* @param {String} options.subLinkSelector Selector for the menu sub link
|
|
17
|
+
* @param {String} options.megaSelector Selector for the mega menu
|
|
18
|
+
* @param {String} options.subItemSelector Selector for the menu sub items
|
|
19
|
+
* @param {String} options.labelOpenAttribute The data attribute for open label
|
|
20
|
+
* @param {String} options.labelCloseAttribute The data attribute for close label
|
|
21
|
+
* @param {Boolean} options.attachClickListener Whether or not to bind click events
|
|
22
|
+
* @param {Boolean} options.attachHoverListener Whether or not to bind hover events
|
|
23
|
+
* @param {Boolean} options.attachFocusListener Whether or not to bind focus events
|
|
24
|
+
* @param {Boolean} options.attachKeyListener Whether or not to bind keyboard events
|
|
25
|
+
* @param {Boolean} options.attachResizeListener Whether or not to bind resize events
|
|
26
|
+
*/
|
|
27
|
+
export class MegaMenu {
|
|
28
|
+
/**
|
|
29
|
+
* @static
|
|
30
|
+
* Shorthand for instance creation and initialisation.
|
|
31
|
+
*
|
|
32
|
+
* @param {HTMLElement} root DOM element for component instantiation and scope
|
|
33
|
+
*
|
|
34
|
+
* @return {Menu} An instance of Menu.
|
|
35
|
+
*/
|
|
36
|
+
static autoInit(root, { MEGA_MENU: defaultOptions = {} } = {}) {
|
|
37
|
+
const megaMenu = new MegaMenu(root, defaultOptions);
|
|
38
|
+
megaMenu.init();
|
|
39
|
+
root.ECLMegaMenu = megaMenu;
|
|
40
|
+
return megaMenu;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @event MegaMenu#onOpen
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* @event MegaMenu#onClose
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* @event MegaMenu#onOpenPanel
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* @event MegaMenu#onBack
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* @event MegaMenu#onItemClick
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* @event MegaMenu#onFocusTrapToggle
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* An array of supported events for this component.
|
|
64
|
+
*
|
|
65
|
+
* @type {Array<string>}
|
|
66
|
+
* @memberof MegaMenu
|
|
67
|
+
*/
|
|
68
|
+
supportedEvents = ['onOpen', 'onClose'];
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
element,
|
|
72
|
+
{
|
|
73
|
+
openSelector = '[data-ecl-mega-menu-open]',
|
|
74
|
+
backSelector = '[data-ecl-mega-menu-back]',
|
|
75
|
+
innerSelector = '[data-ecl-mega-menu-inner]',
|
|
76
|
+
itemSelector = '[data-ecl-mega-menu-item]',
|
|
77
|
+
linkSelector = '[data-ecl-mega-menu-link]',
|
|
78
|
+
subLinkSelector = '[data-ecl-mega-menu-sublink]',
|
|
79
|
+
megaSelector = '[data-ecl-mega-menu-mega]',
|
|
80
|
+
containerSelector = '[data-ecl-has-container]',
|
|
81
|
+
subItemSelector = '[data-ecl-mega-menu-subitem]',
|
|
82
|
+
featuredAttribute = '[data-ecl-mega-menu-featured]',
|
|
83
|
+
featuredLinkAttribute = '[data-ecl-mega-menu-featured-link]',
|
|
84
|
+
labelOpenAttribute = 'data-ecl-mega-menu-label-open',
|
|
85
|
+
labelCloseAttribute = 'data-ecl-mega-menu-label-close',
|
|
86
|
+
attachClickListener = true,
|
|
87
|
+
attachFocusListener = true,
|
|
88
|
+
attachKeyListener = true,
|
|
89
|
+
attachResizeListener = true,
|
|
90
|
+
} = {},
|
|
91
|
+
) {
|
|
92
|
+
// Check element
|
|
93
|
+
if (!element || element.nodeType !== Node.ELEMENT_NODE) {
|
|
94
|
+
throw new TypeError(
|
|
95
|
+
'DOM element should be given to initialize this widget.',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.element = element;
|
|
100
|
+
this.eventManager = new EventManager();
|
|
101
|
+
|
|
102
|
+
// Options
|
|
103
|
+
this.openSelector = openSelector;
|
|
104
|
+
this.backSelector = backSelector;
|
|
105
|
+
this.innerSelector = innerSelector;
|
|
106
|
+
this.itemSelector = itemSelector;
|
|
107
|
+
this.linkSelector = linkSelector;
|
|
108
|
+
this.subLinkSelector = subLinkSelector;
|
|
109
|
+
this.megaSelector = megaSelector;
|
|
110
|
+
this.subItemSelector = subItemSelector;
|
|
111
|
+
this.containerSelector = containerSelector;
|
|
112
|
+
this.labelOpenAttribute = labelOpenAttribute;
|
|
113
|
+
this.labelCloseAttribute = labelCloseAttribute;
|
|
114
|
+
this.attachClickListener = attachClickListener;
|
|
115
|
+
this.attachFocusListener = attachFocusListener;
|
|
116
|
+
this.attachKeyListener = attachKeyListener;
|
|
117
|
+
this.attachResizeListener = attachResizeListener;
|
|
118
|
+
this.featuredAttribute = featuredAttribute;
|
|
119
|
+
this.featuredLinkAttribute = featuredLinkAttribute;
|
|
120
|
+
|
|
121
|
+
// Private variables
|
|
122
|
+
this.direction = 'ltr';
|
|
123
|
+
this.open = null;
|
|
124
|
+
this.toggleLabel = null;
|
|
125
|
+
this.back = null;
|
|
126
|
+
this.backItemLevel1 = null;
|
|
127
|
+
this.backItemLevel2 = null;
|
|
128
|
+
this.inner = null;
|
|
129
|
+
this.items = null;
|
|
130
|
+
this.links = null;
|
|
131
|
+
this.isOpen = false;
|
|
132
|
+
this.resizeTimer = null;
|
|
133
|
+
this.wrappers = null;
|
|
134
|
+
this.isKeyEvent = false;
|
|
135
|
+
this.isDesktop = false;
|
|
136
|
+
this.isLarge = false;
|
|
137
|
+
this.lastVisibleItem = null;
|
|
138
|
+
this.menuOverlay = null;
|
|
139
|
+
this.currentItem = null;
|
|
140
|
+
this.totalItemsWidth = 0;
|
|
141
|
+
this.breakpointL = 996;
|
|
142
|
+
this.openPanel = { num: 0, item: {} };
|
|
143
|
+
this.infoLinks = null;
|
|
144
|
+
this.seeAllLinks = null;
|
|
145
|
+
this.featuredLinks = null;
|
|
146
|
+
|
|
147
|
+
// Bind `this` for use in callbacks
|
|
148
|
+
this.handleClickOnOpen = this.handleClickOnOpen.bind(this);
|
|
149
|
+
this.handleClickOnClose = this.handleClickOnClose.bind(this);
|
|
150
|
+
this.handleClickOnToggle = this.handleClickOnToggle.bind(this);
|
|
151
|
+
this.handleClickOnBack = this.handleClickOnBack.bind(this);
|
|
152
|
+
this.handleClickGlobal = this.handleClickGlobal.bind(this);
|
|
153
|
+
this.handleClickOnItem = this.handleClickOnItem.bind(this);
|
|
154
|
+
this.handleClickOnSubitem = this.handleClickOnSubitem.bind(this);
|
|
155
|
+
this.handleFocusOut = this.handleFocusOut.bind(this);
|
|
156
|
+
this.handleKeyboard = this.handleKeyboard.bind(this);
|
|
157
|
+
this.handleKeyboardGlobal = this.handleKeyboardGlobal.bind(this);
|
|
158
|
+
this.handleResize = this.handleResize.bind(this);
|
|
159
|
+
this.useDesktopDisplay = this.useDesktopDisplay.bind(this);
|
|
160
|
+
this.closeOpenDropdown = this.closeOpenDropdown.bind(this);
|
|
161
|
+
this.checkDropdownHeight = this.checkDropdownHeight.bind(this);
|
|
162
|
+
this.positionMenuOverlay = this.positionMenuOverlay.bind(this);
|
|
163
|
+
this.resetStyles = this.resetStyles.bind(this);
|
|
164
|
+
this.handleFirstPanel = this.handleFirstPanel.bind(this);
|
|
165
|
+
this.handleSecondPanel = this.handleSecondPanel.bind(this);
|
|
166
|
+
this.disableScroll = this.disableScroll.bind(this);
|
|
167
|
+
this.enableScroll = this.enableScroll.bind(this);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Initialise component.
|
|
172
|
+
*/
|
|
173
|
+
init() {
|
|
174
|
+
if (!ECL) {
|
|
175
|
+
throw new TypeError('Called init but ECL is not present');
|
|
176
|
+
}
|
|
177
|
+
ECL.components = ECL.components || new Map();
|
|
178
|
+
|
|
179
|
+
// Query elements
|
|
180
|
+
this.open = queryOne(this.openSelector, this.element);
|
|
181
|
+
this.back = queryOne(this.backSelector, this.element);
|
|
182
|
+
this.inner = queryOne(this.innerSelector, this.element);
|
|
183
|
+
this.btnPrevious = queryOne(this.buttonPreviousSelector, this.element);
|
|
184
|
+
this.btnNext = queryOne(this.buttonNextSelector, this.element);
|
|
185
|
+
this.items = queryAll(this.itemSelector, this.element);
|
|
186
|
+
this.subItems = queryAll(this.subItemSelector, this.element);
|
|
187
|
+
this.links = queryAll(this.linkSelector, this.element);
|
|
188
|
+
this.header = queryOne('.ecl-site-header', document);
|
|
189
|
+
this.headerBanner = queryOne('.ecl-site-header__banner', document);
|
|
190
|
+
this.wrappers = queryAll('.ecl-mega-menu__wrapper', this.element);
|
|
191
|
+
this.headerNotification = queryOne(
|
|
192
|
+
'.ecl-site-header__notification',
|
|
193
|
+
document,
|
|
194
|
+
);
|
|
195
|
+
this.toggleLabel = queryOne('.ecl-button__label', this.open);
|
|
196
|
+
this.menuOverlay = queryOne('.ecl-mega-menu__overlay', this.element);
|
|
197
|
+
|
|
198
|
+
// Check if we should use desktop display (it does not rely only on breakpoints)
|
|
199
|
+
this.isDesktop = this.useDesktopDisplay();
|
|
200
|
+
|
|
201
|
+
// Bind click events on buttons
|
|
202
|
+
if (this.attachClickListener) {
|
|
203
|
+
// Open
|
|
204
|
+
if (this.open) {
|
|
205
|
+
this.open.addEventListener('click', this.handleClickOnToggle);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Back
|
|
209
|
+
if (this.back) {
|
|
210
|
+
this.back.addEventListener('click', this.handleClickOnBack);
|
|
211
|
+
this.back.addEventListener('keyup', this.handleKeyboard);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Global click
|
|
215
|
+
if (this.attachClickListener) {
|
|
216
|
+
document.addEventListener('click', this.handleClickGlobal);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Bind event on menu links
|
|
221
|
+
if (this.links) {
|
|
222
|
+
this.links.forEach((link) => {
|
|
223
|
+
if (this.attachFocusListener) {
|
|
224
|
+
link.addEventListener('focusout', this.handleFocusOut);
|
|
225
|
+
}
|
|
226
|
+
if (this.attachKeyListener) {
|
|
227
|
+
link.addEventListener('keyup', this.handleKeyboard);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Bind event on sub menu links
|
|
233
|
+
if (this.subItems) {
|
|
234
|
+
this.subItems.forEach((subItem) => {
|
|
235
|
+
const subLink = queryOne('.ecl-mega-menu__sublink', subItem);
|
|
236
|
+
|
|
237
|
+
if (this.attachKeyListener && subLink) {
|
|
238
|
+
subLink.addEventListener('click', this.handleClickOnSubitem);
|
|
239
|
+
subLink.addEventListener('keyup', this.handleKeyboard);
|
|
240
|
+
}
|
|
241
|
+
if (this.attachFocusListener && subLink) {
|
|
242
|
+
subLink.addEventListener('focusout', this.handleFocusOut);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.infoLinks = queryAll('.ecl-mega-menu__info-link a', this.element);
|
|
248
|
+
if (this.infoLinks.length > 0) {
|
|
249
|
+
this.infoLinks.forEach((infoLink) => {
|
|
250
|
+
if (this.attachKeyListener) {
|
|
251
|
+
infoLink.addEventListener('keyup', this.handleKeyboard);
|
|
252
|
+
}
|
|
253
|
+
if (this.attachFocusListener) {
|
|
254
|
+
infoLink.addEventListener('blur', this.handleFocusOut);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.seeAllLinks = queryAll('.ecl-mega-menu__see-all a', this.element);
|
|
260
|
+
if (this.seeAllLinks.length > 0) {
|
|
261
|
+
this.seeAllLinks.forEach((seeAll) => {
|
|
262
|
+
if (this.attachKeyListener) {
|
|
263
|
+
seeAll.addEventListener('keyup', this.handleKeyboard);
|
|
264
|
+
}
|
|
265
|
+
if (this.attachFocusListener) {
|
|
266
|
+
seeAll.addEventListener('blur', this.handleFocusOut);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.featuredLinks = queryAll(this.featuredLinkAttribute, this.element);
|
|
272
|
+
if (this.featuredLinks.length > 0 && this.attachFocusListener) {
|
|
273
|
+
this.featuredLinks.forEach((featured) => {
|
|
274
|
+
featured.addEventListener('blur', this.handleFocusOut);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Bind global keyboard events
|
|
279
|
+
if (this.attachKeyListener) {
|
|
280
|
+
document.addEventListener('keyup', this.handleKeyboardGlobal);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Bind resize events
|
|
284
|
+
if (this.attachResizeListener) {
|
|
285
|
+
window.addEventListener('resize', this.handleResize);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Browse first level items
|
|
289
|
+
if (this.items) {
|
|
290
|
+
this.items.forEach((item) => {
|
|
291
|
+
// Check menu item display (right to left, full width, ...)
|
|
292
|
+
this.totalItemsWidth += item.offsetWidth;
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
item.hasAttribute('data-ecl-has-children') ||
|
|
296
|
+
item.hasAttribute('data-ecl-has-container')
|
|
297
|
+
) {
|
|
298
|
+
// Bind click event on menu links
|
|
299
|
+
const link = queryOne(this.linkSelector, item);
|
|
300
|
+
if (this.attachClickListener && link) {
|
|
301
|
+
link.addEventListener('click', this.handleClickOnItem);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Create a focus trap around the menu
|
|
308
|
+
this.focusTrap = createFocusTrap(this.element, {
|
|
309
|
+
onActivate: () =>
|
|
310
|
+
this.element.classList.add('ecl-mega-menu-trap-is-active'),
|
|
311
|
+
onDeactivate: () =>
|
|
312
|
+
this.element.classList.remove('ecl-mega-menu-trap-is-active'),
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
this.handleResize();
|
|
316
|
+
// Set ecl initialized attribute
|
|
317
|
+
this.element.setAttribute('data-ecl-auto-initialized', 'true');
|
|
318
|
+
ECL.components.set(this.element, this);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Register a callback function for a specific event.
|
|
323
|
+
*
|
|
324
|
+
* @param {string} eventName - The name of the event to listen for.
|
|
325
|
+
* @param {Function} callback - The callback function to be invoked when the event occurs.
|
|
326
|
+
* @returns {void}
|
|
327
|
+
* @memberof MegaMenu
|
|
328
|
+
* @instance
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* // Registering a callback for the 'onOpen' event
|
|
332
|
+
* megaMenu.on('onOpen', (event) => {
|
|
333
|
+
* console.log('Open event occurred!', event);
|
|
334
|
+
* });
|
|
335
|
+
*/
|
|
336
|
+
on(eventName, callback) {
|
|
337
|
+
this.eventManager.on(eventName, callback);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Trigger a component event.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} eventName - The name of the event to trigger.
|
|
344
|
+
* @param {any} eventData - Data associated with the event.
|
|
345
|
+
* @memberof MegaMenu
|
|
346
|
+
*/
|
|
347
|
+
trigger(eventName, eventData) {
|
|
348
|
+
this.eventManager.trigger(eventName, eventData);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Destroy component.
|
|
353
|
+
*/
|
|
354
|
+
destroy() {
|
|
355
|
+
if (this.attachClickListener) {
|
|
356
|
+
if (this.open) {
|
|
357
|
+
this.open.removeEventListener('click', this.handleClickOnToggle);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (this.back) {
|
|
361
|
+
this.back.removeEventListener('click', this.handleClickOnBack);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (this.attachClickListener) {
|
|
365
|
+
document.removeEventListener('click', this.handleClickGlobal);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (this.links) {
|
|
370
|
+
this.links.forEach((link) => {
|
|
371
|
+
if (this.attachClickListener) {
|
|
372
|
+
link.removeEventListener('click', this.handleClickOnItem);
|
|
373
|
+
}
|
|
374
|
+
if (this.attachFocusListener) {
|
|
375
|
+
link.removeEventListener('focusout', this.handleFocusOut);
|
|
376
|
+
}
|
|
377
|
+
if (this.attachKeyListener) {
|
|
378
|
+
link.removeEventListener('keyup', this.handleKeyboard);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (this.subItems) {
|
|
384
|
+
this.subItems.forEach((subItem) => {
|
|
385
|
+
const subLink = queryOne('.ecl-mega-menu__sublink', subItem);
|
|
386
|
+
if (this.attachKeyListener && subLink) {
|
|
387
|
+
subLink.removeEventListener('keyup', this.handleKeyboard);
|
|
388
|
+
}
|
|
389
|
+
if (this.attachClickListener && subLink) {
|
|
390
|
+
subLink.removeEventListener('click', this.handleClickOnSubitem);
|
|
391
|
+
}
|
|
392
|
+
if (this.attachFocusListener && subLink) {
|
|
393
|
+
subLink.removeEventListener('focusout', this.handleFocusOut);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (this.infoLinks) {
|
|
399
|
+
this.infoLinks.forEach((infoLink) => {
|
|
400
|
+
if (this.attachFocusListener) {
|
|
401
|
+
infoLink.removeEventListener('blur', this.handleFocusOut);
|
|
402
|
+
}
|
|
403
|
+
if (this.attachKeyListener) {
|
|
404
|
+
infoLink.removeEventListener('keyup', this.handleKeyboard);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (this.seeAllLinks) {
|
|
410
|
+
this.seeAllLinks.forEach((seeAll) => {
|
|
411
|
+
if (this.attachFocusListener) {
|
|
412
|
+
seeAll.removeEventListener('blur', this.handleFocusOut);
|
|
413
|
+
}
|
|
414
|
+
if (this.attachKeyListener) {
|
|
415
|
+
seeAll.removeEventListener('keyup', this.handleKeyboard);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (this.featuredLinks && this.attachFocusListener) {
|
|
421
|
+
this.featuredLinks.forEach((featuredLink) => {
|
|
422
|
+
featuredLink.removeEventListener('blur', this.handleFocusOut);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (this.attachKeyListener) {
|
|
427
|
+
document.removeEventListener('keyup', this.handleKeyboardGlobal);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this.attachResizeListener) {
|
|
431
|
+
window.removeEventListener('resize', this.handleResize);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this.closeOpenDropdown();
|
|
435
|
+
this.enableScroll();
|
|
436
|
+
|
|
437
|
+
if (this.element) {
|
|
438
|
+
this.element.removeAttribute('data-ecl-auto-initialized');
|
|
439
|
+
ECL.components.delete(this.element);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Disable page scrolling
|
|
445
|
+
*/
|
|
446
|
+
disableScroll() {
|
|
447
|
+
document.body.classList.add('ecl-mega-menu-prevent-scroll');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Enable page scrolling
|
|
452
|
+
*/
|
|
453
|
+
enableScroll() {
|
|
454
|
+
document.body.classList.remove('ecl-mega-menu-prevent-scroll');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Check if desktop display has to be used
|
|
459
|
+
* - not using a phone or tablet (whatever the screen size is)
|
|
460
|
+
* - not having hamburger menu on screen
|
|
461
|
+
*/
|
|
462
|
+
useDesktopDisplay() {
|
|
463
|
+
// Detect mobile devices
|
|
464
|
+
if (isMobile.isMobileOnly) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Force mobile display on tablet
|
|
469
|
+
if (isMobile.isTablet) {
|
|
470
|
+
this.element.classList.add('ecl-mega-menu--forced-mobile');
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// After all that, check if the hamburger button is displayed
|
|
475
|
+
if (window.innerWidth < this.breakpointL) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Everything is fine to use desktop display
|
|
480
|
+
this.element.classList.remove('ecl-mega-menu--forced-mobile');
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Reset the styles set by the script
|
|
486
|
+
*
|
|
487
|
+
* @param {string} desktop or mobile
|
|
488
|
+
*/
|
|
489
|
+
resetStyles(viewport, compact) {
|
|
490
|
+
const infoPanels = queryAll('.ecl-mega-menu__info', this.element);
|
|
491
|
+
const subLists = queryAll('.ecl-mega-menu__sublist', this.element);
|
|
492
|
+
|
|
493
|
+
// Remove display:none from the sublists
|
|
494
|
+
if (subLists && viewport === 'mobile') {
|
|
495
|
+
const megaMenus = queryAll(
|
|
496
|
+
'.ecl-mega-menu__item > .ecl-mega-menu__wrapper > .ecl-container > [data-ecl-mega-menu-mega]',
|
|
497
|
+
this.element,
|
|
498
|
+
);
|
|
499
|
+
megaMenus.forEach((menu) => {
|
|
500
|
+
menu.style.height = '';
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Reset top position and height of the wrappers
|
|
504
|
+
if (this.wrappers) {
|
|
505
|
+
this.wrappers.forEach((wrapper) => {
|
|
506
|
+
wrapper.style.top = '';
|
|
507
|
+
wrapper.style.height = '';
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this.openPanel.num > 0) {
|
|
512
|
+
if (this.header) {
|
|
513
|
+
if (this.headerBanner) {
|
|
514
|
+
this.headerBanner.style.display = 'none';
|
|
515
|
+
}
|
|
516
|
+
if (this.headerNotification) {
|
|
517
|
+
this.headerNotification.style.display = 'none';
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Two panels are opened
|
|
523
|
+
if (this.openPanel.num === 2) {
|
|
524
|
+
const subItemExpanded = queryOne(
|
|
525
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
526
|
+
this.element,
|
|
527
|
+
);
|
|
528
|
+
if (subItemExpanded) {
|
|
529
|
+
subItemExpanded.firstChild.classList.add(
|
|
530
|
+
'ecl-mega-menu__parent-link',
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
const menuItem = this.openPanel.item;
|
|
534
|
+
// Hide siblings
|
|
535
|
+
const siblings = menuItem.parentNode.childNodes;
|
|
536
|
+
siblings.forEach((sibling) => {
|
|
537
|
+
if (sibling !== menuItem) {
|
|
538
|
+
sibling.style.display = 'none';
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
} else if (subLists && viewport === 'desktop' && !compact) {
|
|
543
|
+
// Reset styles for the sublist and subitems
|
|
544
|
+
subLists.forEach((list) => {
|
|
545
|
+
list.classList.remove('ecl-mega-menu__sublist--scrollable');
|
|
546
|
+
list.childNodes.forEach((item) => {
|
|
547
|
+
item.style.display = '';
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
infoPanels.forEach((info) => {
|
|
552
|
+
info.style.top = '';
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Check if we have an open item, if we don't hide the overlay and enable scroll
|
|
556
|
+
const currentItems = [];
|
|
557
|
+
const currentItem = queryOne(
|
|
558
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
559
|
+
this.element,
|
|
560
|
+
);
|
|
561
|
+
if (currentItem) {
|
|
562
|
+
currentItem.firstElementChild.classList.remove(
|
|
563
|
+
'ecl-mega-menu__parent-link',
|
|
564
|
+
);
|
|
565
|
+
currentItems.push(currentItem);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const currentSubItem = queryOne(
|
|
569
|
+
'.ecl-mega-menu__item--expanded',
|
|
570
|
+
this.element,
|
|
571
|
+
);
|
|
572
|
+
if (currentSubItem) {
|
|
573
|
+
currentItems.push(currentSubItem);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (currentItems.length > 0) {
|
|
577
|
+
currentItems.forEach((current) => {
|
|
578
|
+
this.checkDropdownHeight(current);
|
|
579
|
+
});
|
|
580
|
+
} else {
|
|
581
|
+
this.element.setAttribute('aria-expanded', 'false');
|
|
582
|
+
this.element.removeAttribute('data-expanded');
|
|
583
|
+
this.open.setAttribute('aria-expanded', 'false');
|
|
584
|
+
this.enableScroll();
|
|
585
|
+
}
|
|
586
|
+
} else if (viewport === 'desktop' && compact) {
|
|
587
|
+
const currentSubItem = queryOne(
|
|
588
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
589
|
+
this.element,
|
|
590
|
+
);
|
|
591
|
+
if (currentSubItem) {
|
|
592
|
+
currentSubItem.firstElementChild.classList.remove(
|
|
593
|
+
'ecl-mega-menu__parent-link',
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
infoPanels.forEach((info) => {
|
|
597
|
+
info.style.height = '';
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (viewport === 'desktop' && this.header) {
|
|
602
|
+
if (this.headerBanner) {
|
|
603
|
+
this.headerBanner.style.display = 'flex';
|
|
604
|
+
}
|
|
605
|
+
if (this.headerNotification) {
|
|
606
|
+
this.headerNotification.style.display = 'flex';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Trigger events on resize
|
|
613
|
+
* Uses a debounce, for performance
|
|
614
|
+
*/
|
|
615
|
+
handleResize() {
|
|
616
|
+
clearTimeout(this.resizeTimer);
|
|
617
|
+
this.resizeTimer = setTimeout(() => {
|
|
618
|
+
const screenWidth = window.innerWidth;
|
|
619
|
+
if (this.prevScreenWidth !== undefined) {
|
|
620
|
+
// Check if the transition involves crossing the L breakpoint
|
|
621
|
+
const isTransition =
|
|
622
|
+
(this.prevScreenWidth <= this.breakpointL &&
|
|
623
|
+
screenWidth > this.breakpointL) ||
|
|
624
|
+
(this.prevScreenWidth > this.breakpointL &&
|
|
625
|
+
screenWidth <= this.breakpointL);
|
|
626
|
+
// If we are moving in or out the L breakpoint, reset the styles
|
|
627
|
+
if (isTransition) {
|
|
628
|
+
this.resetStyles(
|
|
629
|
+
screenWidth > this.breakpointL ? 'desktop' : 'mobile',
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
if (this.prevScreenWidth > 1140 && screenWidth > 996) {
|
|
633
|
+
this.resetStyles('desktop', true);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
this.isDesktop = this.useDesktopDisplay();
|
|
637
|
+
this.isLarge = window.innerWidth > 1140;
|
|
638
|
+
// Update previous screen width
|
|
639
|
+
this.prevScreenWidth = screenWidth;
|
|
640
|
+
this.element.classList.remove('ecl-mega-menu--forced-mobile');
|
|
641
|
+
// RTL
|
|
642
|
+
this.direction = getComputedStyle(this.element).direction;
|
|
643
|
+
if (this.direction === 'rtl') {
|
|
644
|
+
this.element.classList.add('ecl-mega-menu--rtl');
|
|
645
|
+
} else {
|
|
646
|
+
this.element.classList.remove('ecl-mega-menu--rtl');
|
|
647
|
+
}
|
|
648
|
+
// Check droopdown height if needed
|
|
649
|
+
const expanded = queryOne('.ecl-mega-menu__item--expanded', this.element);
|
|
650
|
+
if (expanded && this.isDesktop) {
|
|
651
|
+
this.checkDropdownHeight(expanded);
|
|
652
|
+
}
|
|
653
|
+
// Check the menu position
|
|
654
|
+
this.positionMenuOverlay();
|
|
655
|
+
}, 200);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Calculate dropdown height dynamically
|
|
660
|
+
*
|
|
661
|
+
* @param {Node} menuItem
|
|
662
|
+
*/
|
|
663
|
+
checkDropdownHeight(menuItem, hide = true) {
|
|
664
|
+
const infoPanel = queryOne('.ecl-mega-menu__info', menuItem);
|
|
665
|
+
const mainPanel = queryOne('.ecl-mega-menu__mega', menuItem);
|
|
666
|
+
// Hide the panels while calculating their heights
|
|
667
|
+
if (mainPanel && this.isDesktop && hide) {
|
|
668
|
+
mainPanel.style.opacity = 0;
|
|
669
|
+
}
|
|
670
|
+
if (infoPanel && this.isDesktop && hide) {
|
|
671
|
+
infoPanel.style.opacity = 0;
|
|
672
|
+
}
|
|
673
|
+
setTimeout(() => {
|
|
674
|
+
const viewportHeight = window.innerHeight;
|
|
675
|
+
let infoPanelHeight = 0;
|
|
676
|
+
|
|
677
|
+
if (this.isDesktop) {
|
|
678
|
+
const heights = [];
|
|
679
|
+
let height = 0;
|
|
680
|
+
let secondPanel = null;
|
|
681
|
+
let featuredPanel = null;
|
|
682
|
+
let itemsHeight = 0;
|
|
683
|
+
let subItemsHeight = 0;
|
|
684
|
+
let featuredHeight = 0;
|
|
685
|
+
|
|
686
|
+
if (infoPanel) {
|
|
687
|
+
infoPanel.style.height = '';
|
|
688
|
+
infoPanelHeight = infoPanel.scrollHeight + 16;
|
|
689
|
+
}
|
|
690
|
+
if (infoPanel && this.isLarge) {
|
|
691
|
+
heights.push(infoPanelHeight);
|
|
692
|
+
} else if (infoPanel && this.isDesktop) {
|
|
693
|
+
itemsHeight = infoPanelHeight;
|
|
694
|
+
subItemsHeight = infoPanelHeight;
|
|
695
|
+
featuredHeight = infoPanelHeight;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (mainPanel) {
|
|
699
|
+
mainPanel.style.height = '';
|
|
700
|
+
const mainTop = mainPanel.getBoundingClientRect().top;
|
|
701
|
+
const list = queryOne('.ecl-mega-menu__sublist', mainPanel);
|
|
702
|
+
if (!list) {
|
|
703
|
+
const isContainer = menuItem.classList.contains(
|
|
704
|
+
'ecl-mega-menu__item--has-container',
|
|
705
|
+
);
|
|
706
|
+
if (isContainer) {
|
|
707
|
+
const container = queryOne(
|
|
708
|
+
'.ecl-mega-menu__mega-container',
|
|
709
|
+
menuItem,
|
|
710
|
+
);
|
|
711
|
+
if (container) {
|
|
712
|
+
container.firstElementChild.style.height = `${viewportHeight - mainTop}px`;
|
|
713
|
+
mainPanel.style.opacity = 1;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
const items = list.children;
|
|
719
|
+
if (items.length > 0) {
|
|
720
|
+
Array.from(items).forEach((item) => {
|
|
721
|
+
itemsHeight += item.getBoundingClientRect().height;
|
|
722
|
+
});
|
|
723
|
+
heights.push(itemsHeight);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const expanded = queryOne(
|
|
728
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
729
|
+
menuItem,
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
if (expanded) {
|
|
733
|
+
secondPanel = queryOne('.ecl-mega-menu__mega--level-2', expanded);
|
|
734
|
+
if (secondPanel) {
|
|
735
|
+
secondPanel.style.height = '';
|
|
736
|
+
const subItems = queryAll(`${this.subItemSelector}`, secondPanel);
|
|
737
|
+
if (subItems.length > 0) {
|
|
738
|
+
subItems.forEach((item) => {
|
|
739
|
+
subItemsHeight += item.getBoundingClientRect().height;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
heights.push(subItemsHeight);
|
|
743
|
+
// Featured panel calculations.
|
|
744
|
+
featuredPanel = queryOne('.ecl-mega-menu__featured', expanded);
|
|
745
|
+
if (featuredPanel) {
|
|
746
|
+
// Get the elements inside the scrollable container and calculate their heights.
|
|
747
|
+
Array.from(featuredPanel.firstElementChild.children).forEach(
|
|
748
|
+
(child) => {
|
|
749
|
+
const elStyle = window.getComputedStyle(child);
|
|
750
|
+
const marginHeight =
|
|
751
|
+
parseFloat(elStyle.marginTop) +
|
|
752
|
+
parseFloat(elStyle.marginBottom);
|
|
753
|
+
featuredHeight += child.offsetHeight + marginHeight;
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
heights.push(featuredHeight);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const maxHeight = Math.max(...heights);
|
|
763
|
+
const containerBounding = this.inner.getBoundingClientRect();
|
|
764
|
+
const containerBottom = containerBounding.bottom;
|
|
765
|
+
// By requirements, limit the height to the 70% of the available space.
|
|
766
|
+
const availableHeight = (window.innerHeight - containerBottom) * 0.7;
|
|
767
|
+
|
|
768
|
+
if (maxHeight > availableHeight) {
|
|
769
|
+
height = availableHeight;
|
|
770
|
+
} else {
|
|
771
|
+
height = maxHeight;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const wrapper = queryOne('.ecl-mega-menu__wrapper', menuItem);
|
|
775
|
+
if (wrapper) {
|
|
776
|
+
wrapper.style.height = `${height}px`;
|
|
777
|
+
}
|
|
778
|
+
if (mainPanel && this.isLarge) {
|
|
779
|
+
mainPanel.style.height = `${height}px`;
|
|
780
|
+
} else if (mainPanel && infoPanel && this.isDesktop) {
|
|
781
|
+
mainPanel.style.height = `${height - infoPanelHeight}px`;
|
|
782
|
+
}
|
|
783
|
+
if (infoPanel && this.isLarge) {
|
|
784
|
+
infoPanel.style.height = `${height}px`;
|
|
785
|
+
}
|
|
786
|
+
if (secondPanel && this.isLarge) {
|
|
787
|
+
secondPanel.style.height = `${height}px`;
|
|
788
|
+
} else if (secondPanel && this.isDesktop) {
|
|
789
|
+
secondPanel.style.height = `${height - infoPanelHeight}px`;
|
|
790
|
+
}
|
|
791
|
+
if (featuredPanel && this.isLarge) {
|
|
792
|
+
featuredPanel.style.height = `${height}px`;
|
|
793
|
+
} else if (featuredPanel && this.isDesktop) {
|
|
794
|
+
featuredPanel.style.height = `${height - infoPanelHeight}px`;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (mainPanel && this.isDesktop) {
|
|
798
|
+
mainPanel.style.opacity = 1;
|
|
799
|
+
}
|
|
800
|
+
if (infoPanel && this.isDesktop) {
|
|
801
|
+
infoPanel.style.opacity = 1;
|
|
802
|
+
}
|
|
803
|
+
}, 100);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Dinamically set the position of the menu overlay
|
|
808
|
+
*/
|
|
809
|
+
positionMenuOverlay() {
|
|
810
|
+
let availableHeight = 0;
|
|
811
|
+
if (!this.isDesktop) {
|
|
812
|
+
// In mobile, we get the bottom position of the site header header
|
|
813
|
+
setTimeout(() => {
|
|
814
|
+
if (this.header) {
|
|
815
|
+
const position = this.header.getBoundingClientRect();
|
|
816
|
+
const bottomPosition = Math.round(position.bottom);
|
|
817
|
+
|
|
818
|
+
if (this.menuOverlay) {
|
|
819
|
+
this.menuOverlay.style.top = `${bottomPosition}px`;
|
|
820
|
+
}
|
|
821
|
+
if (this.inner) {
|
|
822
|
+
this.inner.style.top = `${bottomPosition}px`;
|
|
823
|
+
}
|
|
824
|
+
const item = queryOne('.ecl-mega-menu__item--expanded', this.element);
|
|
825
|
+
|
|
826
|
+
if (item) {
|
|
827
|
+
const subList = queryOne('.ecl-mega-menu__sublist', item);
|
|
828
|
+
if (subList && this.openPanel.num === 1) {
|
|
829
|
+
const info = queryOne('.ecl-mega-menu__info', item);
|
|
830
|
+
if (info) {
|
|
831
|
+
const bottomRect = info.getBoundingClientRect();
|
|
832
|
+
const bottomInfo = bottomRect.bottom;
|
|
833
|
+
availableHeight = window.innerHeight - bottomInfo - 16;
|
|
834
|
+
subList.classList.add('ecl-mega-menu__sublist--scrollable');
|
|
835
|
+
subList.style.height = `${availableHeight}px`;
|
|
836
|
+
}
|
|
837
|
+
} else if (subList) {
|
|
838
|
+
subList.classList.remove('ecl-mega-menu__sublist--scrollable');
|
|
839
|
+
subList.style.height = '';
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (this.openPanel.num === 2) {
|
|
844
|
+
const subItem = queryOne(
|
|
845
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
846
|
+
this.element,
|
|
847
|
+
);
|
|
848
|
+
if (subItem) {
|
|
849
|
+
const subMega = queryOne(
|
|
850
|
+
'.ecl-mega-menu__mega--level-2',
|
|
851
|
+
subItem,
|
|
852
|
+
);
|
|
853
|
+
if (subMega) {
|
|
854
|
+
const subMegaRect = subMega.getBoundingClientRect();
|
|
855
|
+
const subMegaTop = subMegaRect.top;
|
|
856
|
+
availableHeight = window.innerHeight - subMegaTop;
|
|
857
|
+
subMega.style.height = `${availableHeight}px`;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (this.wrappers) {
|
|
862
|
+
this.wrappers.forEach((wrapper) => {
|
|
863
|
+
wrapper.style.top = '';
|
|
864
|
+
wrapper.style.height = '';
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}, 0);
|
|
869
|
+
} else {
|
|
870
|
+
setTimeout(() => {
|
|
871
|
+
// In desktop we get the bottom position of the whole site header
|
|
872
|
+
const siteHeader = queryOne('.ecl-site-header', document);
|
|
873
|
+
if (siteHeader) {
|
|
874
|
+
const headerRect = siteHeader.getBoundingClientRect();
|
|
875
|
+
const headerBottom = headerRect.bottom;
|
|
876
|
+
const item = queryOne(this.itemSelector, this.element);
|
|
877
|
+
const rect = item.getBoundingClientRect();
|
|
878
|
+
const rectHeight = rect.height;
|
|
879
|
+
|
|
880
|
+
if (this.wrappers) {
|
|
881
|
+
this.wrappers.forEach((wrapper) => {
|
|
882
|
+
wrapper.style.top = `${rectHeight}px`;
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
if (this.menuOverlay) {
|
|
886
|
+
this.menuOverlay.style.top = `${headerBottom}px`;
|
|
887
|
+
}
|
|
888
|
+
} else {
|
|
889
|
+
const bottomPosition = this.element.getBoundingClientRect().bottom;
|
|
890
|
+
if (this.menuOverlay) {
|
|
891
|
+
this.menuOverlay.style.top = `${bottomPosition}px`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}, 0);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Handles keyboard events specific to the menu.
|
|
900
|
+
*
|
|
901
|
+
* @param {Event} e
|
|
902
|
+
*/
|
|
903
|
+
handleKeyboard(e) {
|
|
904
|
+
const element = e.target;
|
|
905
|
+
const cList = element.classList;
|
|
906
|
+
const menuExpanded = this.element.getAttribute('aria-expanded');
|
|
907
|
+
|
|
908
|
+
// Detect press on Escape
|
|
909
|
+
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
910
|
+
if (document.activeElement === element) {
|
|
911
|
+
element.blur();
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (menuExpanded === 'false') {
|
|
915
|
+
this.closeOpenDropdown();
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
// Handle Keyboard on the first panel
|
|
920
|
+
if (cList.contains('ecl-mega-menu__info-link')) {
|
|
921
|
+
if (e.key === 'ArrowUp') {
|
|
922
|
+
if (this.isDesktop) {
|
|
923
|
+
// Focus on the expanded nav item
|
|
924
|
+
queryOne(
|
|
925
|
+
'.ecl-mega-menu__item--expanded button',
|
|
926
|
+
this.element,
|
|
927
|
+
).focus();
|
|
928
|
+
} else if (this.back && !this.isDesktop) {
|
|
929
|
+
// focus on the back button
|
|
930
|
+
this.back.focus();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
934
|
+
// First item in the open dropdown.
|
|
935
|
+
element.parentElement.parentElement.nextSibling.firstChild.firstChild.firstChild.focus();
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (cList.contains('ecl-mega-menu__parent-link')) {
|
|
940
|
+
if (e.key === 'ArrowUp') {
|
|
941
|
+
const back = queryOne('.ecl-mega-menu__back', this.element);
|
|
942
|
+
back.focus();
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (e.key === 'ArrowDown') {
|
|
946
|
+
const mega = e.target.nextSibling;
|
|
947
|
+
mega.firstElementChild.firstElementChild.firstChild.focus();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Handle keyboard on the see all links
|
|
953
|
+
if (element.parentElement.classList.contains('ecl-mega-menu__see-all')) {
|
|
954
|
+
if (e.key === 'ArrowUp') {
|
|
955
|
+
// Focus on the last element of the sub-list
|
|
956
|
+
element.parentElement.previousSibling.firstChild.focus();
|
|
957
|
+
}
|
|
958
|
+
if (e.key === 'ArrowDown') {
|
|
959
|
+
// Focus on the fi
|
|
960
|
+
const featured = element.parentElement.parentElement.nextSibling;
|
|
961
|
+
if (featured) {
|
|
962
|
+
const focusableSelectors = [
|
|
963
|
+
'a[href]',
|
|
964
|
+
'button:not([disabled])',
|
|
965
|
+
'input:not([disabled])',
|
|
966
|
+
'select:not([disabled])',
|
|
967
|
+
'textarea:not([disabled])',
|
|
968
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
969
|
+
];
|
|
970
|
+
const focusableElements = queryAll(
|
|
971
|
+
focusableSelectors.join(', '),
|
|
972
|
+
featured,
|
|
973
|
+
);
|
|
974
|
+
if (focusableElements.length > 0) {
|
|
975
|
+
focusableElements[0].focus();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// Handle keyboard on the back button
|
|
981
|
+
if (cList.contains('ecl-mega-menu__back')) {
|
|
982
|
+
if (e.key === 'ArrowDown') {
|
|
983
|
+
e.preventDefault();
|
|
984
|
+
const expanded = queryOne(
|
|
985
|
+
'[aria-expanded="true"]',
|
|
986
|
+
element.parentElement.nextSibling,
|
|
987
|
+
);
|
|
988
|
+
// We have an opened list
|
|
989
|
+
if (expanded) {
|
|
990
|
+
const innerExpanded = queryOne(
|
|
991
|
+
'.ecl-mega-menu__subitem--expanded',
|
|
992
|
+
expanded.parentElement,
|
|
993
|
+
);
|
|
994
|
+
// We have an opened sub-list
|
|
995
|
+
if (innerExpanded) {
|
|
996
|
+
const parentLink = queryOne(
|
|
997
|
+
'.ecl-mega-menu__parent-link',
|
|
998
|
+
innerExpanded,
|
|
999
|
+
);
|
|
1000
|
+
if (parentLink) {
|
|
1001
|
+
parentLink.focus();
|
|
1002
|
+
}
|
|
1003
|
+
} else {
|
|
1004
|
+
const infoLink = queryOne(
|
|
1005
|
+
'.ecl-mega-menu__info-link',
|
|
1006
|
+
expanded.parentElement,
|
|
1007
|
+
);
|
|
1008
|
+
if (infoLink) {
|
|
1009
|
+
infoLink.focus();
|
|
1010
|
+
} else {
|
|
1011
|
+
queryOne(
|
|
1012
|
+
'.ecl-mega-menu__subitem:first-child .ecl-mega-menu__sublink',
|
|
1013
|
+
expanded.parentElement,
|
|
1014
|
+
).focus();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (e.key === 'ArrowUp') {
|
|
1020
|
+
// Focus on the open button
|
|
1021
|
+
this.open.focus();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
// Key actions to navigate between first level menu items
|
|
1025
|
+
if (cList.contains('ecl-mega-menu__link')) {
|
|
1026
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
1027
|
+
e.preventDefault();
|
|
1028
|
+
let prevItem = element.previousSibling;
|
|
1029
|
+
|
|
1030
|
+
if (prevItem && prevItem.classList.contains('ecl-mega-menu__link')) {
|
|
1031
|
+
prevItem.focus();
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
prevItem = element.parentElement.previousSibling;
|
|
1036
|
+
if (prevItem) {
|
|
1037
|
+
const prevLink = queryOne('.ecl-mega-menu__link', prevItem);
|
|
1038
|
+
|
|
1039
|
+
if (prevLink) {
|
|
1040
|
+
prevLink.focus();
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
1046
|
+
e.preventDefault();
|
|
1047
|
+
if (
|
|
1048
|
+
element.parentElement.getAttribute('aria-expanded') === 'true' &&
|
|
1049
|
+
e.key === 'ArrowDown'
|
|
1050
|
+
) {
|
|
1051
|
+
const infoLink = queryOne(
|
|
1052
|
+
'.ecl-mega-menu__info-link',
|
|
1053
|
+
element.parentElement,
|
|
1054
|
+
);
|
|
1055
|
+
if (infoLink) {
|
|
1056
|
+
infoLink.focus();
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
const nextItem = element.parentElement.nextSibling;
|
|
1061
|
+
if (nextItem) {
|
|
1062
|
+
const nextLink = queryOne('.ecl-mega-menu__link', nextItem);
|
|
1063
|
+
|
|
1064
|
+
if (nextLink) {
|
|
1065
|
+
nextLink.focus();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
// Key actions to navigate between the sub-links
|
|
1072
|
+
if (cList.contains('ecl-mega-menu__sublink')) {
|
|
1073
|
+
if (e.key === 'ArrowDown') {
|
|
1074
|
+
e.preventDefault();
|
|
1075
|
+
const nextItem = element.parentElement.nextSibling;
|
|
1076
|
+
let nextLink = '';
|
|
1077
|
+
if (nextItem) {
|
|
1078
|
+
nextLink = queryOne('.ecl-mega-menu__sublink', nextItem);
|
|
1079
|
+
if (
|
|
1080
|
+
!nextLink &&
|
|
1081
|
+
nextItem.classList.contains('ecl-mega-menu__spacer')
|
|
1082
|
+
) {
|
|
1083
|
+
nextLink = nextItem.nextSibling.firstElementChild;
|
|
1084
|
+
}
|
|
1085
|
+
if (nextLink) {
|
|
1086
|
+
nextLink.focus();
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (e.key === 'ArrowUp') {
|
|
1092
|
+
e.preventDefault();
|
|
1093
|
+
const prevItem = element.parentElement.previousSibling;
|
|
1094
|
+
if (prevItem) {
|
|
1095
|
+
const prevLink = queryOne('.ecl-mega-menu__sublink', prevItem);
|
|
1096
|
+
|
|
1097
|
+
if (prevLink) {
|
|
1098
|
+
prevLink.focus();
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
const moreLink = queryOne(
|
|
1102
|
+
'.ecl-mega-menu__info-link',
|
|
1103
|
+
element.parentElement.parentElement.parentElement.previousSibling,
|
|
1104
|
+
);
|
|
1105
|
+
if (moreLink) {
|
|
1106
|
+
moreLink.focus();
|
|
1107
|
+
} else if (this.openPanel.num === 2) {
|
|
1108
|
+
const parent = e.target.closest(
|
|
1109
|
+
'.ecl-mega-menu__mega',
|
|
1110
|
+
).previousSibling;
|
|
1111
|
+
if (parent) {
|
|
1112
|
+
parent.focus();
|
|
1113
|
+
}
|
|
1114
|
+
} else if (this.back) {
|
|
1115
|
+
this.back.focus();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (e.key === 'ArrowRight') {
|
|
1121
|
+
const expanded =
|
|
1122
|
+
element.parentElement.getAttribute('aria-expanded') === 'true';
|
|
1123
|
+
if (expanded) {
|
|
1124
|
+
e.preventDefault();
|
|
1125
|
+
// Focus on the first element in the second panel
|
|
1126
|
+
element.nextSibling.firstElementChild.firstChild.firstChild.focus();
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Handles global keyboard events, triggered outside of the menu.
|
|
1133
|
+
*
|
|
1134
|
+
* @param {Event} e
|
|
1135
|
+
*/
|
|
1136
|
+
handleKeyboardGlobal(e) {
|
|
1137
|
+
// Detect press on Escape
|
|
1138
|
+
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
1139
|
+
if (this.isOpen) {
|
|
1140
|
+
this.closeOpenDropdown(true);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Open menu list.
|
|
1147
|
+
*
|
|
1148
|
+
* @param {Event} e
|
|
1149
|
+
*
|
|
1150
|
+
* @fires MegaMenu#onOpen
|
|
1151
|
+
*/
|
|
1152
|
+
handleClickOnOpen(e) {
|
|
1153
|
+
if (this.isOpen) {
|
|
1154
|
+
this.handleClickOnClose(e);
|
|
1155
|
+
} else {
|
|
1156
|
+
e.preventDefault();
|
|
1157
|
+
this.disableScroll();
|
|
1158
|
+
this.element.setAttribute('aria-expanded', 'true');
|
|
1159
|
+
this.element.classList.add('ecl-mega-menu--start-panel');
|
|
1160
|
+
this.element.classList.remove(
|
|
1161
|
+
'ecl-mega-menu--one-panel',
|
|
1162
|
+
'ecl-mega-menu--two-panels',
|
|
1163
|
+
);
|
|
1164
|
+
this.open.setAttribute('aria-expanded', 'true');
|
|
1165
|
+
this.inner.setAttribute('aria-hidden', 'false');
|
|
1166
|
+
this.isOpen = true;
|
|
1167
|
+
|
|
1168
|
+
if (this.header) {
|
|
1169
|
+
this.header.classList.add(
|
|
1170
|
+
'ecl-site-header--open-menu',
|
|
1171
|
+
'ecl-site-header--open-menu-start',
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Update label
|
|
1176
|
+
const closeLabel = this.element.getAttribute(this.labelCloseAttribute);
|
|
1177
|
+
if (this.toggleLabel && closeLabel) {
|
|
1178
|
+
this.toggleLabel.innerHTML = closeLabel;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
this.positionMenuOverlay();
|
|
1182
|
+
|
|
1183
|
+
// Focus first element
|
|
1184
|
+
if (this.links.length > 0) {
|
|
1185
|
+
this.links[0].focus();
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
this.trigger('onOpen', e);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Close menu list.
|
|
1194
|
+
*
|
|
1195
|
+
* @param {Event} e
|
|
1196
|
+
*
|
|
1197
|
+
* @fires Menu#onClose
|
|
1198
|
+
*/
|
|
1199
|
+
handleClickOnClose(e) {
|
|
1200
|
+
if (this.element.getAttribute('aria-expanded') === 'true') {
|
|
1201
|
+
this.focusTrap.deactivate();
|
|
1202
|
+
this.closeOpenDropdown();
|
|
1203
|
+
this.trigger('onClose', e);
|
|
1204
|
+
} else {
|
|
1205
|
+
this.handleClickOnOpen(e);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/**
|
|
1210
|
+
* Toggle menu list.
|
|
1211
|
+
*
|
|
1212
|
+
* @param {Event} e
|
|
1213
|
+
*/
|
|
1214
|
+
handleClickOnToggle(e) {
|
|
1215
|
+
e.preventDefault();
|
|
1216
|
+
|
|
1217
|
+
if (this.isOpen) {
|
|
1218
|
+
this.handleClickOnClose(e);
|
|
1219
|
+
} else {
|
|
1220
|
+
this.handleClickOnOpen(e);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Get back to previous list (on mobile)
|
|
1226
|
+
*
|
|
1227
|
+
* @fires MegaMenu#onBack
|
|
1228
|
+
*/
|
|
1229
|
+
handleClickOnBack() {
|
|
1230
|
+
const infoPanels = queryAll('.ecl-mega-menu__info', this.element);
|
|
1231
|
+
infoPanels.forEach((info) => {
|
|
1232
|
+
info.style.top = '';
|
|
1233
|
+
});
|
|
1234
|
+
const level2 = queryOne('.ecl-mega-menu__subitem--expanded', this.element);
|
|
1235
|
+
if (level2) {
|
|
1236
|
+
this.element.classList.remove(
|
|
1237
|
+
'ecl-mega-menu--two-panels',
|
|
1238
|
+
'ecl-mega-menu--start-panel',
|
|
1239
|
+
);
|
|
1240
|
+
this.element.classList.add('ecl-mega-menu--one-panel');
|
|
1241
|
+
level2.setAttribute('aria-expanded', 'false');
|
|
1242
|
+
level2.classList.remove(
|
|
1243
|
+
'ecl-mega-menu__subitem--expanded',
|
|
1244
|
+
'ecl-mega-menu__subitem--current',
|
|
1245
|
+
);
|
|
1246
|
+
const itemLink = queryOne(this.subLinkSelector, level2);
|
|
1247
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1248
|
+
itemLink.classList.remove('ecl-mega-menu__parent-link');
|
|
1249
|
+
const siblings = level2.parentElement.childNodes;
|
|
1250
|
+
if (siblings) {
|
|
1251
|
+
siblings.forEach((sibling) => {
|
|
1252
|
+
sibling.style.display = '';
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
if (this.header) {
|
|
1256
|
+
this.header.classList.remove('ecl-site-header--open-menu-start');
|
|
1257
|
+
}
|
|
1258
|
+
// Move the focus to the previously selected item
|
|
1259
|
+
if (this.backItemLevel2) {
|
|
1260
|
+
this.backItemLevel2.firstElementChild.focus();
|
|
1261
|
+
}
|
|
1262
|
+
this.openPanel.num = 1;
|
|
1263
|
+
} else {
|
|
1264
|
+
if (this.header) {
|
|
1265
|
+
if (this.headerBanner) {
|
|
1266
|
+
this.headerBanner.style.display = 'flex';
|
|
1267
|
+
}
|
|
1268
|
+
if (this.headerNotification) {
|
|
1269
|
+
this.headerNotification.style.display = 'flex';
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// Remove expanded class from inner menu
|
|
1273
|
+
this.inner.classList.remove('ecl-mega-menu__inner--expanded');
|
|
1274
|
+
this.element.classList.remove('ecl-mega-menu--one-panel');
|
|
1275
|
+
// Remove css class and attribute from menu items
|
|
1276
|
+
this.items.forEach((item) => {
|
|
1277
|
+
item.classList.remove(
|
|
1278
|
+
'ecl-mega-menu__item--expanded',
|
|
1279
|
+
'ecl-mega-menu__item--current',
|
|
1280
|
+
);
|
|
1281
|
+
const itemLink = queryOne(this.linkSelector, item);
|
|
1282
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1283
|
+
});
|
|
1284
|
+
// Move the focus to the previously selected item
|
|
1285
|
+
if (this.backItemLevel1) {
|
|
1286
|
+
this.backItemLevel1.firstElementChild.focus();
|
|
1287
|
+
} else {
|
|
1288
|
+
this.items[0].firstElementChild.focus();
|
|
1289
|
+
}
|
|
1290
|
+
this.openPanel.num = 0;
|
|
1291
|
+
if (this.header) {
|
|
1292
|
+
this.header.classList.add('ecl-site-header--open-menu-start');
|
|
1293
|
+
}
|
|
1294
|
+
this.positionMenuOverlay();
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
this.trigger('onBack', { level: level2 ? 2 : 1 });
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Show/hide the first panel
|
|
1302
|
+
*
|
|
1303
|
+
* @param {Node} menuItem
|
|
1304
|
+
* @param {string} op (expand or collapse)
|
|
1305
|
+
*
|
|
1306
|
+
* @fires MegaMenu#onOpenPanel
|
|
1307
|
+
*/
|
|
1308
|
+
handleFirstPanel(menuItem, op) {
|
|
1309
|
+
switch (op) {
|
|
1310
|
+
case 'expand': {
|
|
1311
|
+
this.inner.classList.add('ecl-mega-menu__inner--expanded');
|
|
1312
|
+
this.positionMenuOverlay();
|
|
1313
|
+
this.checkDropdownHeight(menuItem);
|
|
1314
|
+
this.element.setAttribute('data-expanded', true);
|
|
1315
|
+
this.element.setAttribute('aria-expanded', 'true');
|
|
1316
|
+
this.element.classList.add('ecl-mega-menu--one-panel');
|
|
1317
|
+
this.element.classList.remove('ecl-mega-menu--start-panel');
|
|
1318
|
+
this.open.setAttribute('aria-expanded', 'true');
|
|
1319
|
+
if (this.header) {
|
|
1320
|
+
this.header.classList.add('ecl-site-header--open-menu');
|
|
1321
|
+
this.header.classList.remove('ecl-site-header--open-menu-start');
|
|
1322
|
+
if (!this.isDesktop) {
|
|
1323
|
+
if (this.headerBanner) {
|
|
1324
|
+
this.headerBanner.style.display = 'none';
|
|
1325
|
+
}
|
|
1326
|
+
if (this.headerNotification) {
|
|
1327
|
+
this.headerNotification.style.display = 'none';
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
this.disableScroll();
|
|
1332
|
+
this.isOpen = true;
|
|
1333
|
+
this.items.forEach((item) => {
|
|
1334
|
+
const itemLink = queryOne(this.linkSelector, item);
|
|
1335
|
+
if (itemLink && itemLink.hasAttribute('aria-expanded')) {
|
|
1336
|
+
if (item === menuItem) {
|
|
1337
|
+
item.classList.add(
|
|
1338
|
+
'ecl-mega-menu__item--expanded',
|
|
1339
|
+
'ecl-mega-menu__item--current',
|
|
1340
|
+
);
|
|
1341
|
+
itemLink.setAttribute('aria-expanded', 'true');
|
|
1342
|
+
this.backItemLevel1 = item;
|
|
1343
|
+
} else {
|
|
1344
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1345
|
+
item.classList.remove(
|
|
1346
|
+
'ecl-mega-menu__item--current',
|
|
1347
|
+
'ecl-mega-menu__item--expanded',
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
if (!this.isDesktop && this.back) {
|
|
1354
|
+
this.back.focus();
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
this.openPanel = {
|
|
1358
|
+
num: 1,
|
|
1359
|
+
item: menuItem,
|
|
1360
|
+
};
|
|
1361
|
+
const details = { panel: 1, item: menuItem };
|
|
1362
|
+
this.trigger('OnOpenPanel', details);
|
|
1363
|
+
if (this.isDesktop) {
|
|
1364
|
+
const list = queryOne('.ecl-mega-menu__sublist', menuItem);
|
|
1365
|
+
if (list) {
|
|
1366
|
+
// Expand the item in the sublist if it contains children.
|
|
1367
|
+
const firstExpandedChild = Array.from(list.children).find((child) =>
|
|
1368
|
+
child.firstElementChild?.hasAttribute('aria-expanded'),
|
|
1369
|
+
);
|
|
1370
|
+
|
|
1371
|
+
if (firstExpandedChild) {
|
|
1372
|
+
this.handleSecondPanel(firstExpandedChild, 'expand', true);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
case 'collapse':
|
|
1380
|
+
this.closeOpenDropdown();
|
|
1381
|
+
break;
|
|
1382
|
+
|
|
1383
|
+
default:
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* Show/hide the second panel
|
|
1389
|
+
*
|
|
1390
|
+
* @param {Node} menuItem
|
|
1391
|
+
* @param {string} op (expand or collapse)
|
|
1392
|
+
*
|
|
1393
|
+
* @fires MegaMenu#onOpenPanel
|
|
1394
|
+
*/
|
|
1395
|
+
handleSecondPanel(menuItem, op, noCheck = false) {
|
|
1396
|
+
const infoPanel = queryOne(
|
|
1397
|
+
'.ecl-mega-menu__info',
|
|
1398
|
+
menuItem.closest('.ecl-container'),
|
|
1399
|
+
);
|
|
1400
|
+
let siblings;
|
|
1401
|
+
switch (op) {
|
|
1402
|
+
case 'expand': {
|
|
1403
|
+
this.element.classList.remove(
|
|
1404
|
+
'ecl-mega-menu--one-panel',
|
|
1405
|
+
'ecl-mega-menu--start-panel',
|
|
1406
|
+
);
|
|
1407
|
+
this.element.classList.add('ecl-mega-menu--two-panels');
|
|
1408
|
+
this.subItems.forEach((item) => {
|
|
1409
|
+
const itemLink = queryOne(this.subLinkSelector, item);
|
|
1410
|
+
if (item === menuItem) {
|
|
1411
|
+
if (itemLink && itemLink.hasAttribute('aria-expanded')) {
|
|
1412
|
+
itemLink.setAttribute('aria-expanded', 'true');
|
|
1413
|
+
|
|
1414
|
+
if (!this.isDesktop) {
|
|
1415
|
+
// We use this class mainly to recover the default behavior of the link.
|
|
1416
|
+
itemLink.classList.add('ecl-mega-menu__parent-link');
|
|
1417
|
+
if (this.back) {
|
|
1418
|
+
this.back.focus();
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
item.classList.add('ecl-mega-menu__subitem--expanded');
|
|
1422
|
+
}
|
|
1423
|
+
item.classList.add('ecl-mega-menu__subitem--current');
|
|
1424
|
+
this.backItemLevel2 = item;
|
|
1425
|
+
} else {
|
|
1426
|
+
if (itemLink && itemLink.hasAttribute('aria-expanded')) {
|
|
1427
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1428
|
+
itemLink.classList.remove('ecl-mega-menu__parent-link');
|
|
1429
|
+
item.classList.remove('ecl-mega-menu__subitem--expanded');
|
|
1430
|
+
}
|
|
1431
|
+
item.classList.remove('ecl-mega-menu__subitem--current');
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
|
|
1435
|
+
this.openPanel = { num: 2, item: menuItem };
|
|
1436
|
+
siblings = menuItem.parentNode.childNodes;
|
|
1437
|
+
if (this.isDesktop) {
|
|
1438
|
+
// Reset style for the siblings, in case they were hidden
|
|
1439
|
+
siblings.forEach((sibling) => {
|
|
1440
|
+
if (sibling !== menuItem) {
|
|
1441
|
+
sibling.style.display = '';
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
} else {
|
|
1445
|
+
// Hide other items in the sublist
|
|
1446
|
+
siblings.forEach((sibling) => {
|
|
1447
|
+
if (sibling !== menuItem) {
|
|
1448
|
+
sibling.style.display = 'none';
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
this.positionMenuOverlay();
|
|
1453
|
+
if (!noCheck) {
|
|
1454
|
+
this.checkDropdownHeight(
|
|
1455
|
+
menuItem.closest('.ecl-mega-menu__item'),
|
|
1456
|
+
false,
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
const details = { panel: 2, item: menuItem };
|
|
1460
|
+
this.trigger('OnOpenPanel', details);
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
case 'collapse':
|
|
1465
|
+
this.element.classList.remove('ecl-mega-menu--two-panels');
|
|
1466
|
+
this.openPanel = { num: 1 };
|
|
1467
|
+
// eslint-disable-next-line no-case-declarations
|
|
1468
|
+
const itemLink = queryOne(this.subLinkSelector, menuItem);
|
|
1469
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1470
|
+
menuItem.classList.remove(
|
|
1471
|
+
'ecl-mega-menu__subitem--expanded',
|
|
1472
|
+
'ecl-mega-menu__subitem--current',
|
|
1473
|
+
);
|
|
1474
|
+
if (infoPanel) {
|
|
1475
|
+
infoPanel.style.top = '';
|
|
1476
|
+
}
|
|
1477
|
+
break;
|
|
1478
|
+
|
|
1479
|
+
default:
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Click on a menu item
|
|
1485
|
+
*
|
|
1486
|
+
* @param {Event} e
|
|
1487
|
+
*
|
|
1488
|
+
* @fires MegaMenu#onItemClick
|
|
1489
|
+
*/
|
|
1490
|
+
handleClickOnItem(e) {
|
|
1491
|
+
let isInTheContainer = false;
|
|
1492
|
+
const menuItem = e.target.closest('li');
|
|
1493
|
+
|
|
1494
|
+
const container = queryOne(
|
|
1495
|
+
'.ecl-mega-menu__mega-container-scrollable',
|
|
1496
|
+
menuItem,
|
|
1497
|
+
);
|
|
1498
|
+
if (container) {
|
|
1499
|
+
isInTheContainer = container.contains(e.target);
|
|
1500
|
+
}
|
|
1501
|
+
// We need to ensure that the click doesn't come from a parent link
|
|
1502
|
+
// or from an open container, in that case we do not act.
|
|
1503
|
+
if (
|
|
1504
|
+
!e.target.classList.contains(
|
|
1505
|
+
'ecl-mega-menu__mega-container-scrollable',
|
|
1506
|
+
) &&
|
|
1507
|
+
!isInTheContainer
|
|
1508
|
+
) {
|
|
1509
|
+
this.trigger('onItemClick', { item: menuItem, event: e });
|
|
1510
|
+
const hasChildren =
|
|
1511
|
+
menuItem.firstElementChild.getAttribute('aria-expanded');
|
|
1512
|
+
if (hasChildren && menuItem.classList.contains('ecl-mega-menu__item')) {
|
|
1513
|
+
e.preventDefault();
|
|
1514
|
+
e.stopPropagation();
|
|
1515
|
+
if (!this.isDesktop) {
|
|
1516
|
+
this.handleFirstPanel(menuItem, 'expand');
|
|
1517
|
+
} else {
|
|
1518
|
+
const isOpen = hasChildren === 'true';
|
|
1519
|
+
if (isOpen) {
|
|
1520
|
+
this.handleFirstPanel(menuItem, 'collapse');
|
|
1521
|
+
} else {
|
|
1522
|
+
this.closeOpenDropdown();
|
|
1523
|
+
this.handleFirstPanel(menuItem, 'expand');
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Click on a subitem
|
|
1532
|
+
*
|
|
1533
|
+
* @param {Event} e
|
|
1534
|
+
*/
|
|
1535
|
+
handleClickOnSubitem(e) {
|
|
1536
|
+
const menuItem = e.target.closest(this.subItemSelector);
|
|
1537
|
+
if (menuItem && menuItem.firstElementChild.hasAttribute('aria-expanded')) {
|
|
1538
|
+
const parentLink = queryOne('.ecl-mega-menu__parent-link', menuItem);
|
|
1539
|
+
if (parentLink) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
e.preventDefault();
|
|
1543
|
+
e.stopPropagation();
|
|
1544
|
+
const isExpanded =
|
|
1545
|
+
menuItem.firstElementChild.getAttribute('aria-expanded') === 'true';
|
|
1546
|
+
|
|
1547
|
+
if (isExpanded) {
|
|
1548
|
+
this.handleSecondPanel(menuItem, 'collapse');
|
|
1549
|
+
} else {
|
|
1550
|
+
this.handleSecondPanel(menuItem, 'expand');
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Deselect any opened menu item
|
|
1557
|
+
*
|
|
1558
|
+
* @param {boolean} esc, whether the call was originated by a press on Esc
|
|
1559
|
+
*
|
|
1560
|
+
* @fires MegaMenu#onFocusTrapToggle
|
|
1561
|
+
*/
|
|
1562
|
+
closeOpenDropdown(esc = false) {
|
|
1563
|
+
if (this.header) {
|
|
1564
|
+
this.header.classList.remove(
|
|
1565
|
+
'ecl-site-header--open-menu',
|
|
1566
|
+
'ecl-site-header--open-menu-start',
|
|
1567
|
+
);
|
|
1568
|
+
if (this.headerBanner) {
|
|
1569
|
+
this.headerBanner.style.display = 'flex';
|
|
1570
|
+
}
|
|
1571
|
+
if (this.headerNotification) {
|
|
1572
|
+
this.headerNotification.style.display = 'flex';
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
this.enableScroll();
|
|
1576
|
+
this.element.setAttribute('aria-expanded', 'false');
|
|
1577
|
+
this.element.removeAttribute('data-expanded');
|
|
1578
|
+
this.element.classList.remove(
|
|
1579
|
+
'ecl-mega-menu--start-panel',
|
|
1580
|
+
'ecl-mega-menu--two-panels',
|
|
1581
|
+
'ecl-mega-menu--one-panel',
|
|
1582
|
+
);
|
|
1583
|
+
this.open.setAttribute('aria-expanded', 'false');
|
|
1584
|
+
// Remove css class and attribute from inner menu
|
|
1585
|
+
this.inner.classList.remove('ecl-mega-menu__inner--expanded');
|
|
1586
|
+
|
|
1587
|
+
// Reset heights
|
|
1588
|
+
const megaMenus = queryAll('[data-ecl-mega-menu-mega]', this.element);
|
|
1589
|
+
megaMenus.forEach((mega) => {
|
|
1590
|
+
mega.style.height = '';
|
|
1591
|
+
mega.style.top = '';
|
|
1592
|
+
mega.style.opacity = '';
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
if (this.wrappers) {
|
|
1596
|
+
this.wrappers.forEach((wrapper) => {
|
|
1597
|
+
wrapper.style = '';
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
let currentItem = false;
|
|
1601
|
+
// Remove css class and attribute from menu items
|
|
1602
|
+
this.items.forEach((item) => {
|
|
1603
|
+
item.classList.remove('ecl-mega-menu__item--current');
|
|
1604
|
+
const itemLink = queryOne(this.linkSelector, item);
|
|
1605
|
+
if (itemLink.getAttribute('aria-expanded') === 'true') {
|
|
1606
|
+
item.classList.remove('ecl-mega-menu__item--expanded');
|
|
1607
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1608
|
+
currentItem = itemLink;
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
// Remove css class and attribute from menu subitems
|
|
1612
|
+
this.subItems.forEach((item) => {
|
|
1613
|
+
item.classList.remove('ecl-mega-menu__subitem--current');
|
|
1614
|
+
item.style.display = '';
|
|
1615
|
+
const itemLink = queryOne(this.subLinkSelector, item);
|
|
1616
|
+
if (itemLink && itemLink.hasAttribute('aria-expanded')) {
|
|
1617
|
+
item.classList.remove('ecl-mega-menu__subitem--expanded');
|
|
1618
|
+
item.style.display = '';
|
|
1619
|
+
itemLink.setAttribute('aria-expanded', 'false');
|
|
1620
|
+
itemLink.classList.remove('ecl-mega-menu__parent-link');
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
// Remove styles set for the sublists
|
|
1624
|
+
const sublists = queryAll('.ecl-mega-menu__sublist');
|
|
1625
|
+
if (sublists) {
|
|
1626
|
+
sublists.forEach((sublist) => {
|
|
1627
|
+
sublist.classList.remove(
|
|
1628
|
+
'ecl-mega-menu__sublist--no-border',
|
|
1629
|
+
'.ecl-mega-menu__sublist--scrollable',
|
|
1630
|
+
);
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
// Update label
|
|
1634
|
+
const openLabel = this.element.getAttribute(this.labelOpenAttribute);
|
|
1635
|
+
if (this.toggleLabel && openLabel) {
|
|
1636
|
+
this.toggleLabel.innerHTML = openLabel;
|
|
1637
|
+
}
|
|
1638
|
+
this.openPanel = {
|
|
1639
|
+
num: 0,
|
|
1640
|
+
item: false,
|
|
1641
|
+
};
|
|
1642
|
+
// If the focus trap is active, deactivate it
|
|
1643
|
+
this.focusTrap.deactivate();
|
|
1644
|
+
// Focus on the open button in mobile or on the formerly expanded item in desktop.
|
|
1645
|
+
if (!this.isDesktop && this.open && esc) {
|
|
1646
|
+
this.open.focus();
|
|
1647
|
+
} else if (this.isDesktop && currentItem && esc) {
|
|
1648
|
+
currentItem.focus();
|
|
1649
|
+
}
|
|
1650
|
+
this.trigger('onFocusTrapToggle', { active: false });
|
|
1651
|
+
this.isOpen = false;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Focus out of a menu link
|
|
1656
|
+
*
|
|
1657
|
+
* @param {Event} e
|
|
1658
|
+
*
|
|
1659
|
+
* @fires MegaMenu#onFocusTrapToggle
|
|
1660
|
+
*/
|
|
1661
|
+
handleFocusOut(e) {
|
|
1662
|
+
const element = e.target;
|
|
1663
|
+
const menuExpanded = this.element.getAttribute('aria-expanded');
|
|
1664
|
+
|
|
1665
|
+
// Specific focus action for mobile menu
|
|
1666
|
+
// Loop through the items and go back to close button
|
|
1667
|
+
if (menuExpanded === 'true' && !this.isDesktop) {
|
|
1668
|
+
const nextItem = element.parentElement.nextSibling;
|
|
1669
|
+
|
|
1670
|
+
if (!nextItem) {
|
|
1671
|
+
const nextFocusTarget = e.relatedTarget;
|
|
1672
|
+
if (!this.element.contains(nextFocusTarget)) {
|
|
1673
|
+
// This is the last item, go back to close button
|
|
1674
|
+
this.focusTrap.activate();
|
|
1675
|
+
this.trigger('onFocusTrapToggle', {
|
|
1676
|
+
active: true,
|
|
1677
|
+
lastFocusedEl: element.parentElement,
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Handles global click events, triggered outside of the menu.
|
|
1686
|
+
*
|
|
1687
|
+
* @param {Event} e
|
|
1688
|
+
*/
|
|
1689
|
+
handleClickGlobal(e) {
|
|
1690
|
+
if (
|
|
1691
|
+
!e.target.classList.contains(
|
|
1692
|
+
'ecl-mega-menu__mega-container-scrollable',
|
|
1693
|
+
) &&
|
|
1694
|
+
(e.target.classList.contains('ecl-mega-menu__overlay') ||
|
|
1695
|
+
!this.element.contains(e.target)) &&
|
|
1696
|
+
this.isOpen
|
|
1697
|
+
) {
|
|
1698
|
+
this.closeOpenDropdown();
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
export default MegaMenu;
|