@ecl/site-header 5.0.0-alpha.2 → 5.0.0-alpha.21
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/README.md +25 -4
- package/{_site-header-language-switcher.scss → _site-header-dropdown.scss} +34 -20
- package/package.json +15 -14
- package/site-header-ec.scss +135 -105
- package/site-header-eu.scss +92 -51
- package/site-header-language-switcher.html.twig +5 -7
- package/site-header-print.scss +4 -17
- package/site-header.html.twig +245 -151
- package/site-header.js +326 -172
package/site-header.js
CHANGED
|
@@ -53,6 +53,9 @@ export class SiteHeader {
|
|
|
53
53
|
attachKeyListener = true,
|
|
54
54
|
attachResizeListener = true,
|
|
55
55
|
tabletBreakpoint = 768,
|
|
56
|
+
customActionToggleSelector = '[data-ecl-custom-action]',
|
|
57
|
+
customActionOverlaySelector = '[data-ecl-custom-action-overlay]',
|
|
58
|
+
customActionCloseSelector = '[data-ecl-custom-action-close]',
|
|
56
59
|
} = {},
|
|
57
60
|
) {
|
|
58
61
|
// Check element
|
|
@@ -81,6 +84,9 @@ export class SiteHeader {
|
|
|
81
84
|
this.attachKeyListener = attachKeyListener;
|
|
82
85
|
this.attachResizeListener = attachResizeListener;
|
|
83
86
|
this.tabletBreakpoint = tabletBreakpoint;
|
|
87
|
+
this.customActionToggleSelector = customActionToggleSelector;
|
|
88
|
+
this.customActionOverlaySelector = customActionOverlaySelector;
|
|
89
|
+
this.customActionCloseSelector = customActionCloseSelector;
|
|
84
90
|
|
|
85
91
|
// Private variables
|
|
86
92
|
this.languageMaxColumnItems = 8;
|
|
@@ -98,6 +104,10 @@ export class SiteHeader {
|
|
|
98
104
|
this.resizeTimer = null;
|
|
99
105
|
this.direction = null;
|
|
100
106
|
this.notificationContainer = null;
|
|
107
|
+
this.customActionToggle = null;
|
|
108
|
+
this.customActionOverlay = null;
|
|
109
|
+
this.customActionClose = null;
|
|
110
|
+
this.customActionFocusTrap = null;
|
|
101
111
|
|
|
102
112
|
// Bind `this` for use in callbacks
|
|
103
113
|
this.openOverlay = this.openOverlay.bind(this);
|
|
@@ -113,6 +123,12 @@ export class SiteHeader {
|
|
|
113
123
|
this.handleResize = this.handleResize.bind(this);
|
|
114
124
|
this.setLanguageListHeight = this.setLanguageListHeight.bind(this);
|
|
115
125
|
this.handleNotificationClose = this.handleNotificationClose.bind(this);
|
|
126
|
+
|
|
127
|
+
this.openCustomAction = this.openCustomAction.bind(this);
|
|
128
|
+
this.closeCustomAction = this.closeCustomAction.bind(this);
|
|
129
|
+
this.toggleCustomAction = this.toggleCustomAction.bind(this);
|
|
130
|
+
this.handleKeyboardCustomAction =
|
|
131
|
+
this.handleKeyboardCustomAction.bind(this);
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
/**
|
|
@@ -154,10 +170,12 @@ export class SiteHeader {
|
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
// Create focus trap
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
173
|
+
if (this.languageListOverlay) {
|
|
174
|
+
this.focusTrap = createFocusTrap(this.languageListOverlay, {
|
|
175
|
+
onDeactivate: this.closeOverlay,
|
|
176
|
+
allowOutsideClick: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
161
179
|
|
|
162
180
|
if (this.attachClickListener && this.languageLink) {
|
|
163
181
|
this.languageLink.addEventListener('click', this.toggleOverlay);
|
|
@@ -188,6 +206,35 @@ export class SiteHeader {
|
|
|
188
206
|
this.loginToggle.addEventListener('click', this.toggleLogin);
|
|
189
207
|
}
|
|
190
208
|
|
|
209
|
+
// Custom action management
|
|
210
|
+
this.customActionToggle = queryOne(this.customActionToggleSelector);
|
|
211
|
+
this.customActionOverlay = queryOne(this.customActionOverlaySelector);
|
|
212
|
+
this.customActionClose = queryOne(this.customActionCloseSelector);
|
|
213
|
+
|
|
214
|
+
if (this.customActionOverlay) {
|
|
215
|
+
// Create a separate focus trap for the custom action overlay
|
|
216
|
+
this.customActionFocusTrap = createFocusTrap(this.customActionOverlay, {
|
|
217
|
+
onDeactivate: this.closeCustomAction,
|
|
218
|
+
allowOutsideClick: true,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.attachClickListener && this.customActionToggle) {
|
|
223
|
+
this.customActionToggle.addEventListener(
|
|
224
|
+
'click',
|
|
225
|
+
this.toggleCustomAction,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (this.attachClickListener && this.customActionClose) {
|
|
229
|
+
this.customActionClose.addEventListener('click', this.toggleCustomAction);
|
|
230
|
+
}
|
|
231
|
+
if (this.attachKeyListener && this.customActionToggle) {
|
|
232
|
+
this.customActionToggle.addEventListener(
|
|
233
|
+
'keydown',
|
|
234
|
+
this.handleKeyboardCustomAction,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
191
238
|
// Set ecl initialized attribute
|
|
192
239
|
this.element.setAttribute('data-ecl-auto-initialized', 'true');
|
|
193
240
|
ECL.components.set(this.element, this);
|
|
@@ -237,6 +284,28 @@ export class SiteHeader {
|
|
|
237
284
|
this.loginToggle.removeEventListener('click', this.toggleLogin);
|
|
238
285
|
}
|
|
239
286
|
|
|
287
|
+
if (this.attachClickListener && this.customActionToggle) {
|
|
288
|
+
this.customActionToggle.removeEventListener(
|
|
289
|
+
'click',
|
|
290
|
+
this.toggleCustomAction,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (this.customActionFocusTrap) {
|
|
294
|
+
this.customActionFocusTrap.deactivate();
|
|
295
|
+
}
|
|
296
|
+
if (this.attachKeyListener && this.customActionToggle) {
|
|
297
|
+
this.customActionToggle.removeEventListener(
|
|
298
|
+
'keydown',
|
|
299
|
+
this.handleKeyboardCustomAction,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (this.attachClickListener && this.customActionClose) {
|
|
303
|
+
this.customActionClose.removeEventListener(
|
|
304
|
+
'click',
|
|
305
|
+
this.toggleCustomAction,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
240
309
|
if (this.attachKeyListener) {
|
|
241
310
|
document.removeEventListener('keyup', this.handleKeyboardGlobal);
|
|
242
311
|
}
|
|
@@ -257,168 +326,148 @@ export class SiteHeader {
|
|
|
257
326
|
}
|
|
258
327
|
|
|
259
328
|
/**
|
|
260
|
-
*
|
|
329
|
+
* Method repositions a popover overlay in the viewport.
|
|
330
|
+
* It also (optionally) handles language-list–specific logic such as columns.
|
|
331
|
+
*
|
|
332
|
+
* @param {HTMLElement} overlay The overlay element to be positioned
|
|
333
|
+
* @param {HTMLElement} toggle The toggle button/link that opens the overlay
|
|
334
|
+
* @param {String} arrowCssVar The CSS variable name for arrow positioning
|
|
261
335
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const itemsEu = queryAll(
|
|
269
|
-
'.ecl-site-header__language-item',
|
|
270
|
-
this.languageListEu,
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
// Calculate number of columns
|
|
274
|
-
columnsEu = Math.ceil(itemsEu.length / this.languageMaxColumnItems);
|
|
336
|
+
updateOverlayPosition(
|
|
337
|
+
overlay,
|
|
338
|
+
toggle,
|
|
339
|
+
arrowCssVar = '--ecl-overlay-arrow-position',
|
|
340
|
+
) {
|
|
341
|
+
if (!overlay || !toggle || !this.container) return;
|
|
275
342
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
343
|
+
// If this overlay is the language overlay, handle columns (EU vs Non-EU items)
|
|
344
|
+
if (overlay === this.languageListOverlay) {
|
|
345
|
+
// Calculate columns for the EU language list
|
|
346
|
+
if (this.languageListEu) {
|
|
347
|
+
const itemsEu = queryAll(
|
|
348
|
+
'.ecl-site-header__language-item',
|
|
349
|
+
this.languageListEu,
|
|
350
|
+
);
|
|
351
|
+
const columnsEu = Math.ceil(
|
|
352
|
+
itemsEu.length / this.languageMaxColumnItems,
|
|
280
353
|
);
|
|
354
|
+
if (columnsEu > 1) {
|
|
355
|
+
this.languageListEu.classList.add(
|
|
356
|
+
`ecl-site-header__language-category--${columnsEu}-col`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
281
359
|
}
|
|
282
|
-
}
|
|
283
|
-
if (this.languageListNonEu) {
|
|
284
|
-
// Get all non-Eu languages
|
|
285
|
-
const itemsNonEu = queryAll(
|
|
286
|
-
'.ecl-site-header__language-item',
|
|
287
|
-
this.languageListNonEu,
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
// Calculate number of columns
|
|
291
|
-
columnsNonEu = Math.ceil(itemsNonEu.length / this.languageMaxColumnItems);
|
|
292
360
|
|
|
293
|
-
//
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
361
|
+
// Calculate columns for the Non-EU language list
|
|
362
|
+
if (this.languageListNonEu) {
|
|
363
|
+
const itemsNonEu = queryAll(
|
|
364
|
+
'.ecl-site-header__language-item',
|
|
365
|
+
this.languageListNonEu,
|
|
366
|
+
);
|
|
367
|
+
const columnsNonEu = Math.ceil(
|
|
368
|
+
itemsNonEu.length / this.languageMaxColumnItems,
|
|
297
369
|
);
|
|
370
|
+
if (columnsNonEu > 1) {
|
|
371
|
+
this.languageListNonEu.classList.add(
|
|
372
|
+
`ecl-site-header__language-category--${columnsNonEu}-col`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
298
375
|
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Check total width, and change display if needed
|
|
302
|
-
if (this.languageListEu) {
|
|
303
|
-
this.languageListEu.parentNode.classList.remove(
|
|
304
|
-
'ecl-site-header__language-content--stack',
|
|
305
|
-
);
|
|
306
|
-
} else if (this.languageListNonEu) {
|
|
307
|
-
this.languageListNonEu.parentNode.classList.remove(
|
|
308
|
-
'ecl-site-header__language-content--stack',
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
let popoverRect = this.languageListOverlay.getBoundingClientRect();
|
|
312
|
-
const containerRect = this.container.getBoundingClientRect();
|
|
313
376
|
|
|
314
|
-
|
|
315
|
-
// Stack elements
|
|
377
|
+
// Remove stacked classes first
|
|
316
378
|
if (this.languageListEu) {
|
|
317
|
-
this.languageListEu.parentNode.classList.
|
|
379
|
+
this.languageListEu.parentNode.classList.remove(
|
|
318
380
|
'ecl-site-header__language-content--stack',
|
|
319
381
|
);
|
|
320
382
|
} else if (this.languageListNonEu) {
|
|
321
|
-
this.languageListNonEu.parentNode.classList.
|
|
383
|
+
this.languageListNonEu.parentNode.classList.remove(
|
|
322
384
|
'ecl-site-header__language-content--stack',
|
|
323
385
|
);
|
|
324
386
|
}
|
|
325
|
-
|
|
326
|
-
// Adapt column display
|
|
327
|
-
if (this.languageListNonEu) {
|
|
328
|
-
this.languageListNonEu.classList.remove(
|
|
329
|
-
`ecl-site-header__language-category--${columnsNonEu}-col`,
|
|
330
|
-
);
|
|
331
|
-
this.languageListNonEu.classList.add(
|
|
332
|
-
`ecl-site-header__language-category--${Math.max(
|
|
333
|
-
columnsEu,
|
|
334
|
-
columnsNonEu,
|
|
335
|
-
)}-col`,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
387
|
}
|
|
339
388
|
|
|
340
|
-
//
|
|
341
|
-
|
|
389
|
+
// Clear leftover classes/inline styles
|
|
390
|
+
overlay.classList.remove(
|
|
342
391
|
'ecl-site-header__language-container--push-right',
|
|
343
392
|
'ecl-site-header__language-container--push-left',
|
|
344
|
-
);
|
|
345
|
-
this.languageListOverlay.classList.remove(
|
|
346
393
|
'ecl-site-header__language-container--full',
|
|
347
394
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
);
|
|
351
|
-
this.languageListOverlay.style.removeProperty('right');
|
|
352
|
-
this.languageListOverlay.style.removeProperty('left');
|
|
395
|
+
overlay.style.removeProperty(arrowCssVar);
|
|
396
|
+
overlay.style.removeProperty('right');
|
|
397
|
+
overlay.style.removeProperty('left');
|
|
353
398
|
|
|
354
|
-
|
|
399
|
+
// Calculate bounding rects
|
|
400
|
+
let popoverRect = overlay.getBoundingClientRect();
|
|
401
|
+
const containerRect = this.container.getBoundingClientRect();
|
|
355
402
|
const screenWidth = window.innerWidth;
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
`calc(${arrowPosition}px - ${this.arrowSize})`,
|
|
373
|
-
);
|
|
374
|
-
} else if (this.direction === 'rtl' && popoverRect.left < 0) {
|
|
375
|
-
this.languageListOverlay.classList.add(
|
|
376
|
-
'ecl-site-header__language-container--push-left',
|
|
377
|
-
);
|
|
378
|
-
this.languageListOverlay.style.setProperty(
|
|
379
|
-
'left',
|
|
380
|
-
`calc(-${linkRect.left}px + ${containerRect.left}px)`,
|
|
381
|
-
);
|
|
382
|
-
// Adapt arrow position
|
|
383
|
-
const arrowPosition =
|
|
384
|
-
linkRect.right - containerRect.left - linkRect.width / 2;
|
|
385
|
-
this.languageListOverlay.style.setProperty(
|
|
386
|
-
'--ecl-language-arrow-position',
|
|
387
|
-
`${arrowPosition}px`,
|
|
388
|
-
);
|
|
403
|
+
const toggleRect = toggle.getBoundingClientRect();
|
|
404
|
+
|
|
405
|
+
// If this is the language overlay, handle “too wide” scenario
|
|
406
|
+
if (overlay === this.languageListOverlay) {
|
|
407
|
+
if (popoverRect.width > containerRect.width) {
|
|
408
|
+
// Stack elements
|
|
409
|
+
if (this.languageListEu) {
|
|
410
|
+
this.languageListEu.parentNode.classList.add(
|
|
411
|
+
'ecl-site-header__language-content--stack',
|
|
412
|
+
);
|
|
413
|
+
} else if (this.languageListNonEu) {
|
|
414
|
+
this.languageListNonEu.parentNode.classList.add(
|
|
415
|
+
'ecl-site-header__language-content--stack',
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
389
419
|
}
|
|
390
420
|
|
|
391
|
-
// Mobile
|
|
421
|
+
// Mobile: full width if below tablet breakpoint
|
|
392
422
|
if (window.innerWidth < this.tabletBreakpoint) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.languageListOverlay.style.removeProperty('right');
|
|
398
|
-
|
|
399
|
-
// Adapt arrow position
|
|
423
|
+
overlay.classList.add('ecl-site-header__language-container--full');
|
|
424
|
+
// Recompute popoverRect after applying the class
|
|
425
|
+
popoverRect = overlay.getBoundingClientRect();
|
|
426
|
+
// Position arrow
|
|
400
427
|
const arrowPosition =
|
|
401
|
-
popoverRect.right -
|
|
402
|
-
|
|
403
|
-
|
|
428
|
+
popoverRect.right - toggleRect.right + toggleRect.width / 2;
|
|
429
|
+
overlay.style.setProperty(
|
|
430
|
+
arrowCssVar,
|
|
404
431
|
`calc(${arrowPosition}px - ${this.arrowSize})`,
|
|
405
432
|
);
|
|
433
|
+
return;
|
|
406
434
|
}
|
|
407
435
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
436
|
+
// If popover extends beyond right edge in LTR
|
|
437
|
+
if (this.direction === 'ltr' && popoverRect.right > screenWidth) {
|
|
438
|
+
overlay.classList.add('ecl-site-header__language-container--push-right');
|
|
439
|
+
overlay.style.setProperty(
|
|
440
|
+
'right',
|
|
441
|
+
`calc(-${containerRect.right}px + ${toggleRect.right}px)`,
|
|
442
|
+
);
|
|
443
|
+
const arrowPos =
|
|
444
|
+
containerRect.right - toggleRect.right + toggleRect.width / 2;
|
|
445
|
+
overlay.style.setProperty(
|
|
446
|
+
arrowCssVar,
|
|
447
|
+
`calc(${arrowPos}px - ${this.arrowSize})`,
|
|
448
|
+
);
|
|
449
|
+
} else if (this.direction === 'rtl' && popoverRect.left < 0) {
|
|
450
|
+
// If popover extends beyond left edge in RTL
|
|
451
|
+
overlay.classList.add('ecl-site-header__language-container--push-left');
|
|
452
|
+
overlay.style.setProperty(
|
|
453
|
+
'left',
|
|
454
|
+
`calc(-${toggleRect.left}px + ${containerRect.left}px)`,
|
|
455
|
+
);
|
|
456
|
+
const arrowPos =
|
|
457
|
+
toggleRect.right - containerRect.left - toggleRect.width / 2;
|
|
458
|
+
overlay.style.setProperty(arrowCssVar, `${arrowPos}px`);
|
|
419
459
|
}
|
|
420
460
|
}
|
|
421
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Wrapper: Update display of the modal language list overlay
|
|
464
|
+
* (Calls the new `updateOverlayPosition` for the language popover).
|
|
465
|
+
*/
|
|
466
|
+
updateOverlay() {
|
|
467
|
+
if (!this.languageListOverlay || !this.languageLink) return;
|
|
468
|
+
this.updateOverlayPosition(this.languageListOverlay, this.languageLink);
|
|
469
|
+
}
|
|
470
|
+
|
|
422
471
|
/**
|
|
423
472
|
* Removes the containers of the notification element
|
|
424
473
|
*/
|
|
@@ -476,50 +525,13 @@ export class SiteHeader {
|
|
|
476
525
|
|
|
477
526
|
if (this.languageListOverlay.hasAttribute('hidden')) {
|
|
478
527
|
this.openOverlay();
|
|
479
|
-
this.
|
|
528
|
+
this.updateOverlayPosition(this.languageListOverlay, this.languageLink);
|
|
480
529
|
this.focusTrap.activate();
|
|
481
530
|
} else {
|
|
482
531
|
this.focusTrap.deactivate();
|
|
483
532
|
}
|
|
484
533
|
}
|
|
485
534
|
|
|
486
|
-
/**
|
|
487
|
-
* Trigger events on resize
|
|
488
|
-
* Uses a debounce, for performance
|
|
489
|
-
*/
|
|
490
|
-
handleResize() {
|
|
491
|
-
if (
|
|
492
|
-
!this.languageListOverlay ||
|
|
493
|
-
this.languageListOverlay.hasAttribute('hidden')
|
|
494
|
-
)
|
|
495
|
-
return;
|
|
496
|
-
if (
|
|
497
|
-
(this.loginBox &&
|
|
498
|
-
this.loginBox.classList.contains(
|
|
499
|
-
'ecl-site-header__login-box--active',
|
|
500
|
-
)) ||
|
|
501
|
-
(this.searchForm &&
|
|
502
|
-
this.searchForm.classList.contains('ecl-site-header__search--active'))
|
|
503
|
-
) {
|
|
504
|
-
clearTimeout(this.resizeTimer);
|
|
505
|
-
this.resizeTimer = setTimeout(() => {
|
|
506
|
-
this.updateOverlay();
|
|
507
|
-
}, 200);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Handles keyboard events specific to the language list.
|
|
513
|
-
*
|
|
514
|
-
* @param {Event} e
|
|
515
|
-
*/
|
|
516
|
-
handleKeyboardLanguage(e) {
|
|
517
|
-
// Open the menu with space and enter
|
|
518
|
-
if (e.keyCode === 32 || e.key === 'Enter') {
|
|
519
|
-
this.toggleOverlay(e);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
535
|
/**
|
|
524
536
|
* Toggles the search form.
|
|
525
537
|
*
|
|
@@ -561,7 +573,9 @@ export class SiteHeader {
|
|
|
561
573
|
if (loginRect.x === 0) {
|
|
562
574
|
const loginToggleRect = this.loginToggle.getBoundingClientRect();
|
|
563
575
|
const arrowPosition =
|
|
564
|
-
|
|
576
|
+
document.documentElement.clientWidth -
|
|
577
|
+
loginToggleRect.right +
|
|
578
|
+
loginToggleRect.width / 2;
|
|
565
579
|
|
|
566
580
|
this.loginBox.style.setProperty(
|
|
567
581
|
'--ecl-login-arrow-position',
|
|
@@ -575,7 +589,9 @@ export class SiteHeader {
|
|
|
575
589
|
if (searchRect.x === 0) {
|
|
576
590
|
const searchToggleRect = this.searchToggle.getBoundingClientRect();
|
|
577
591
|
const arrowPosition =
|
|
578
|
-
|
|
592
|
+
document.documentElement.clientWidth -
|
|
593
|
+
searchToggleRect.right +
|
|
594
|
+
searchToggleRect.width / 2;
|
|
579
595
|
|
|
580
596
|
this.searchForm.style.setProperty(
|
|
581
597
|
'--ecl-search-arrow-position',
|
|
@@ -619,6 +635,18 @@ export class SiteHeader {
|
|
|
619
635
|
}
|
|
620
636
|
}
|
|
621
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Handles keyboard events specific to the language list.
|
|
640
|
+
*
|
|
641
|
+
* @param {Event} e
|
|
642
|
+
*/
|
|
643
|
+
handleKeyboardLanguage(e) {
|
|
644
|
+
// Open the menu with space and enter
|
|
645
|
+
if (e.keyCode === 32 || e.key === 'Enter') {
|
|
646
|
+
this.toggleOverlay(e);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
622
650
|
/**
|
|
623
651
|
* Handles global keyboard events, triggered outside of the site header.
|
|
624
652
|
*
|
|
@@ -633,6 +661,12 @@ export class SiteHeader {
|
|
|
633
661
|
if (listExpanded === 'true') {
|
|
634
662
|
this.toggleOverlay(e);
|
|
635
663
|
}
|
|
664
|
+
if (
|
|
665
|
+
this.customActionToggle &&
|
|
666
|
+
this.customActionToggle.getAttribute('aria-expanded') === 'true'
|
|
667
|
+
) {
|
|
668
|
+
this.toggleCustomAction(e);
|
|
669
|
+
}
|
|
636
670
|
}
|
|
637
671
|
}
|
|
638
672
|
|
|
@@ -677,6 +711,126 @@ export class SiteHeader {
|
|
|
677
711
|
this.toggleSearch(e);
|
|
678
712
|
}
|
|
679
713
|
}
|
|
714
|
+
|
|
715
|
+
// Custom action
|
|
716
|
+
const customActionExpanded =
|
|
717
|
+
this.customActionToggle &&
|
|
718
|
+
this.customActionToggle.getAttribute('aria-expanded') === 'true';
|
|
719
|
+
if (customActionExpanded) {
|
|
720
|
+
if (
|
|
721
|
+
!this.customActionOverlay.contains(e.target) &&
|
|
722
|
+
!this.customActionToggle.contains(e.target)
|
|
723
|
+
) {
|
|
724
|
+
this.toggleCustomAction(e);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Trigger events on resize
|
|
731
|
+
* Uses a debounce, for performance
|
|
732
|
+
*/
|
|
733
|
+
handleResize() {
|
|
734
|
+
if (this.resizeTimer) {
|
|
735
|
+
clearTimeout(this.resizeTimer);
|
|
736
|
+
}
|
|
737
|
+
this.resizeTimer = setTimeout(() => {
|
|
738
|
+
// If language overlay is open, reposition
|
|
739
|
+
if (
|
|
740
|
+
this.languageListOverlay &&
|
|
741
|
+
!this.languageListOverlay.hasAttribute('hidden')
|
|
742
|
+
) {
|
|
743
|
+
this.updateOverlayPosition(this.languageListOverlay, this.languageLink);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// If custom action overlay is open, reposition
|
|
747
|
+
if (
|
|
748
|
+
this.customActionOverlay &&
|
|
749
|
+
!this.customActionOverlay.hasAttribute('hidden')
|
|
750
|
+
) {
|
|
751
|
+
this.updateOverlayPosition(
|
|
752
|
+
this.customActionOverlay,
|
|
753
|
+
this.customActionToggle,
|
|
754
|
+
'--ecl-overlay-arrow-position',
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// If the login box is open, re-position arrow
|
|
759
|
+
if (
|
|
760
|
+
this.loginBox &&
|
|
761
|
+
this.loginBox.classList.contains('ecl-site-header__login-box--active')
|
|
762
|
+
) {
|
|
763
|
+
this.setLoginArrow();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// If the search box is open, re-position arrow
|
|
767
|
+
if (
|
|
768
|
+
this.searchForm &&
|
|
769
|
+
this.searchForm.classList.contains('ecl-site-header__search--active')
|
|
770
|
+
) {
|
|
771
|
+
this.setSearchArrow();
|
|
772
|
+
}
|
|
773
|
+
}, 200);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Shows the custom action overlay.
|
|
778
|
+
*/
|
|
779
|
+
openCustomAction() {
|
|
780
|
+
if (!this.customActionOverlay) return;
|
|
781
|
+
this.customActionOverlay.hidden = false;
|
|
782
|
+
this.customActionOverlay.setAttribute('aria-modal', 'true');
|
|
783
|
+
this.customActionToggle.setAttribute('aria-expanded', 'true');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Hides the custom action overlay.
|
|
788
|
+
*/
|
|
789
|
+
closeCustomAction() {
|
|
790
|
+
if (!this.customActionOverlay) return;
|
|
791
|
+
this.customActionOverlay.hidden = true;
|
|
792
|
+
this.customActionOverlay.removeAttribute('aria-modal');
|
|
793
|
+
this.customActionToggle.setAttribute('aria-expanded', 'false');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Toggles the custom action overlay.
|
|
798
|
+
*
|
|
799
|
+
* @param {Event} e
|
|
800
|
+
*/
|
|
801
|
+
toggleCustomAction(e) {
|
|
802
|
+
if (!this.customActionOverlay || !this.customActionFocusTrap) return;
|
|
803
|
+
|
|
804
|
+
e.preventDefault();
|
|
805
|
+
|
|
806
|
+
// Check current state
|
|
807
|
+
const isHidden = this.customActionOverlay.hasAttribute('hidden');
|
|
808
|
+
|
|
809
|
+
if (isHidden) {
|
|
810
|
+
this.openCustomAction();
|
|
811
|
+
// Reuse the same overlay positioning logic,
|
|
812
|
+
// but use a different CSS arrow var for custom action if you like.
|
|
813
|
+
this.updateOverlayPosition(
|
|
814
|
+
this.customActionOverlay,
|
|
815
|
+
this.customActionToggle,
|
|
816
|
+
'--ecl-overlay-arrow-position',
|
|
817
|
+
);
|
|
818
|
+
this.customActionFocusTrap.activate();
|
|
819
|
+
} else {
|
|
820
|
+
this.customActionFocusTrap.deactivate();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Handles keyboard events specific to the custom action toggle.
|
|
826
|
+
*
|
|
827
|
+
* @param {Event} e
|
|
828
|
+
*/
|
|
829
|
+
handleKeyboardCustomAction(e) {
|
|
830
|
+
// Open the custom action with space and enter
|
|
831
|
+
if (e.keyCode === 32 || e.key === 'Enter') {
|
|
832
|
+
this.toggleCustomAction(e);
|
|
833
|
+
}
|
|
680
834
|
}
|
|
681
835
|
}
|
|
682
836
|
|