weg-shared-layout 0.0.40 → 0.0.42

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.
Files changed (37) hide show
  1. package/LICENSE +21 -21
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/weg-footer_2.cjs.entry.js +449 -32
  4. package/dist/cjs/weg-shared-layout.cjs.js +1 -1
  5. package/dist/collection/components/weg-footer/icons/instagram.svg +5 -5
  6. package/dist/collection/components/weg-footer/icons/linkedin.svg +3 -3
  7. package/dist/collection/components/weg-footer/icons/tiktok.svg +3 -3
  8. package/dist/collection/components/weg-footer/icons/youtube.svg +4 -4
  9. package/dist/collection/components/weg-footer/weg-footer.css +263 -263
  10. package/dist/collection/components/weg-footer/weg-footer.js +2 -2
  11. package/dist/collection/components/weg-header/weg-header.css +533 -483
  12. package/dist/collection/components/weg-header/weg-header.js +348 -32
  13. package/dist/collection/styles/shared.css +104 -103
  14. package/dist/collection/utils/nav-active.js +121 -0
  15. package/dist/components/weg-footer.js +1 -1
  16. package/dist/components/weg-header.js +1 -1
  17. package/dist/esm/loader.js +1 -1
  18. package/dist/esm/weg-footer_2.entry.js +449 -32
  19. package/dist/esm/weg-shared-layout.js +1 -1
  20. package/dist/types/components/weg-header/weg-header.d.ts +37 -0
  21. package/dist/types/components.d.ts +9 -0
  22. package/dist/types/utils/nav-active.d.ts +20 -0
  23. package/dist/weg-shared-layout/p-877ccc54.entry.js +1 -0
  24. package/dist/weg-shared-layout/weg-shared-layout.esm.js +1 -1
  25. package/docs/README.md +51 -49
  26. package/docs/angular.md +451 -400
  27. package/docs/nextjs.md +312 -302
  28. package/docs/publishing.md +65 -65
  29. package/docs/react.md +287 -257
  30. package/docs/vanilla.md +155 -124
  31. package/package.json +81 -81
  32. package/readme.md +108 -97
  33. package/src/constants/breakpoints.ts +7 -7
  34. package/src/styles/container.css +60 -60
  35. package/dist/weg-shared-layout/p-6f83b24a.entry.js +0 -1
  36. /package/dist/types/Users/{tan/Documents/code → rakin/Documents/WEG/repos}/weg-shared-layout/.stencil/test-utils/axe.d.ts +0 -0
  37. /package/dist/types/Users/{tan/Documents/code → rakin/Documents/WEG/repos}/weg-shared-layout/.stencil/test-utils/viewport.d.ts +0 -0
@@ -1,12 +1,19 @@
1
1
  import { h } from "@stencil/core";
2
2
  import { findAuthMenuLink, isMenuGroup, isNonEmptyString, normalizeHeaderMenu, parseJsonProp, } from "../../utils/layout";
3
3
  import { WEG_HOVER_CAPABLE_MEDIA_QUERY, WEG_MD_MEDIA_QUERY } from "../../constants/breakpoints";
4
+ import { isAriaCurrentNavHref, isNavLinkActive, resolveAriaCurrentNavKey, } from "../../utils/nav-active";
4
5
  import { LOGO_SRC } from "./logo-data";
5
6
  const WEG_HOME = 'https://www.warwickemploymentgroup.com';
6
7
  const DEFAULT_ACCOUNT_HOME = 'https://account.warwickemploymentgroup.com';
8
+ /** Stay visible while scrolling down through the first half of the viewport. */
9
+ const HEADER_SCROLL_HIDE_THRESHOLD_VH = 0.5;
10
+ const HEADER_SCROLL_DIRECTION_DELTA = 5;
7
11
  const DEFAULT_LOGO_HREF = `${WEG_HOME}/`;
8
12
  /** Placeholder href — sign-out is handled via `wegAuthClick`, not navigation. */
9
13
  const SIGN_OUT_HREF = '#';
14
+ const MAIN_NAV_LABEL = 'Main navigation';
15
+ const OPEN_MAIN_NAV_LABEL = 'Open main navigation';
16
+ const CLOSE_MAIN_NAV_LABEL = 'Close main navigation';
10
17
  function normalizeAccountBase(input) {
11
18
  const base = input?.trim() || DEFAULT_ACCOUNT_HOME;
12
19
  return base.replace(/\/$/, '');
@@ -91,7 +98,7 @@ function ToggleIcon({ expanded }) {
91
98
  return (h("span", { class: "toggle-icon", "aria-hidden": "true" }, expanded ? (h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none" }, h("path", { d: "M6 12h12", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round" }))) : (h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none" }, h("path", { d: "M12 6v12M6 12h12", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round" })))));
92
99
  }
93
100
  function Logo({ href, src }) {
94
- return (h("a", { class: "logo-link", href: href, "aria-label": "Home" }, h("img", { class: "logo", src: src, alt: "WEG", width: "225", height: "83" })));
101
+ return (h("a", { class: "logo-link", href: href, "aria-label": "Home" }, h("img", { class: "logo", src: src, alt: "Warwick Employment Group logo", width: "225", height: "83" })));
95
102
  }
96
103
  export class WegHeader {
97
104
  /**
@@ -124,6 +131,11 @@ export class WegHeader {
124
131
  * Defaults to production when omitted.
125
132
  */
126
133
  accountBaseUrl;
134
+ /**
135
+ * Current page path from the host application (e.g. `/career-advice/my-article`).
136
+ * Include a query string when matching dropdown links that use search params.
137
+ */
138
+ currentPath;
127
139
  /**
128
140
  * Fired when the user clicks Sign in or Sign out.
129
141
  * Call `event.preventDefault()` in the host to handle navigation/logout yourself.
@@ -133,9 +145,16 @@ export class WegHeader {
133
145
  resolved = EMPTY_HEADER;
134
146
  menuOpen = false;
135
147
  openDropdown = null;
148
+ headerHidden = false;
136
149
  boundHandleDocumentClick = this.handleDocumentClick.bind(this);
137
150
  boundHandleViewportChange = this.handleViewportChange.bind(this);
138
151
  boundHandleHoverCapabilityChange = this.handleHoverCapabilityChange.bind(this);
152
+ boundHandleScroll = this.handleScroll.bind(this);
153
+ headerResizeObserver;
154
+ lastScrollY = 0;
155
+ scrollTicking = false;
156
+ bodyScrollLocked = false;
157
+ bodyScrollLockY = 0;
139
158
  hoverCloseTimer = null;
140
159
  hoverOpenEnabled = false;
141
160
  desktopMediaQuery;
@@ -156,15 +175,69 @@ export class WegHeader {
156
175
  this.updateHoverDropdownEnabled();
157
176
  this.handleViewportChange();
158
177
  }
178
+ componentDidLoad() {
179
+ const header = this.el.shadowRoot?.querySelector('.header');
180
+ if (!header)
181
+ return;
182
+ this.headerResizeObserver = new ResizeObserver(() => this.syncHeaderHeight());
183
+ this.headerResizeObserver.observe(header);
184
+ this.syncHeaderHeight();
185
+ this.lastScrollY = window.scrollY;
186
+ this.updateHeaderVisibility();
187
+ }
159
188
  connectedCallback() {
160
189
  document.addEventListener('click', this.boundHandleDocumentClick);
190
+ window.addEventListener('scroll', this.boundHandleScroll, { passive: true });
161
191
  }
162
192
  disconnectedCallback() {
163
193
  document.removeEventListener('click', this.boundHandleDocumentClick);
194
+ window.removeEventListener('scroll', this.boundHandleScroll);
195
+ this.unlockBodyScroll();
196
+ this.headerResizeObserver?.disconnect();
164
197
  this.desktopMediaQuery?.removeEventListener('change', this.boundHandleViewportChange);
165
198
  this.hoverCapableMediaQuery?.removeEventListener('change', this.boundHandleHoverCapabilityChange);
166
199
  this.cancelScheduledDropdownClose();
167
200
  }
201
+ syncHeaderHeight() {
202
+ if (this.isMobileMenuActive())
203
+ return;
204
+ const header = this.el.shadowRoot?.querySelector('.header');
205
+ if (!header)
206
+ return;
207
+ this.el.style.setProperty('--weg-header-height', `${header.offsetHeight}px`);
208
+ }
209
+ handleScroll() {
210
+ if (this.scrollTicking)
211
+ return;
212
+ this.scrollTicking = true;
213
+ requestAnimationFrame(() => {
214
+ this.scrollTicking = false;
215
+ this.updateHeaderVisibility();
216
+ });
217
+ }
218
+ updateHeaderVisibility() {
219
+ if (this.isMobileMenuActive()) {
220
+ if (this.headerHidden)
221
+ this.headerHidden = false;
222
+ return;
223
+ }
224
+ const scrollY = window.scrollY;
225
+ const delta = scrollY - this.lastScrollY;
226
+ const threshold = window.innerHeight * HEADER_SCROLL_HIDE_THRESHOLD_VH;
227
+ let nextHidden = this.headerHidden;
228
+ if (scrollY <= threshold) {
229
+ nextHidden = false;
230
+ }
231
+ else if (Math.abs(delta) >= HEADER_SCROLL_DIRECTION_DELTA) {
232
+ nextHidden = delta > 0;
233
+ }
234
+ this.lastScrollY = scrollY;
235
+ if (nextHidden === this.headerHidden)
236
+ return;
237
+ if (nextHidden)
238
+ this.closeDropdown();
239
+ this.headerHidden = nextHidden;
240
+ }
168
241
  /**
169
242
  * Desktop dropdowns open on hover when viewport is md+ and the device reports hover support.
170
243
  * Uses (hover: hover) only — not (pointer: fine), which breaks in DevTools and hybrid devices.
@@ -182,10 +255,74 @@ export class WegHeader {
182
255
  }
183
256
  handleViewportChange() {
184
257
  if (this.desktopMediaQuery?.matches) {
185
- this.closeMenu();
258
+ this.closeMenu({ announce: false, focusToggle: false });
186
259
  }
187
260
  this.updateHoverDropdownEnabled();
188
261
  }
262
+ announceToScreenReader(message) {
263
+ const region = this.el.shadowRoot?.querySelector('[data-weg-sr-live]');
264
+ if (!region)
265
+ return;
266
+ region.textContent = '';
267
+ requestAnimationFrame(() => {
268
+ region.textContent = message;
269
+ });
270
+ }
271
+ getMenuToggleButton() {
272
+ return this.el.shadowRoot?.querySelector('.menu-toggle');
273
+ }
274
+ getFirstNavFocusable() {
275
+ const panel = this.el.shadowRoot?.querySelector('#weg-header-main-nav');
276
+ if (!panel)
277
+ return null;
278
+ const focusables = panel.querySelectorAll('a[href], button:not([disabled])');
279
+ return focusables[0] ?? null;
280
+ }
281
+ focusFirstNavItem() {
282
+ setTimeout(() => {
283
+ const attemptFocus = (retries = 8) => {
284
+ const target = this.getFirstNavFocusable();
285
+ if (target) {
286
+ target.focus();
287
+ return;
288
+ }
289
+ if (retries > 0) {
290
+ requestAnimationFrame(() => attemptFocus(retries - 1));
291
+ }
292
+ };
293
+ attemptFocus();
294
+ }, 0);
295
+ }
296
+ focusMenuToggle() {
297
+ const attemptFocus = (retries = 8) => {
298
+ const toggle = this.getMenuToggleButton();
299
+ if (toggle) {
300
+ toggle.focus();
301
+ return;
302
+ }
303
+ if (retries > 0) {
304
+ requestAnimationFrame(() => attemptFocus(retries - 1));
305
+ }
306
+ };
307
+ attemptFocus();
308
+ }
309
+ getMenuGroupLinkCount(label) {
310
+ const group = this.getActiveMenu().find((item) => isMenuGroup(item) && item.label === label);
311
+ return group?.items?.length ?? 0;
312
+ }
313
+ formatLinkCount(count) {
314
+ return count === 1 ? '1 link' : `${count} links`;
315
+ }
316
+ formatItemCount(count) {
317
+ return count === 1 ? '1 item' : `${count} items`;
318
+ }
319
+ announceSubmenuExpanded(label) {
320
+ const linkCount = this.getMenuGroupLinkCount(label);
321
+ this.announceToScreenReader(`${label} submenu expanded. ${this.formatLinkCount(linkCount)}.`);
322
+ }
323
+ announceSubmenuCollapsed(label) {
324
+ this.announceToScreenReader(`${label} submenu collapsed.`);
325
+ }
189
326
  isMobileMenuActive() {
190
327
  return this.menuOpen && !this.desktopMediaQuery?.matches;
191
328
  }
@@ -195,8 +332,39 @@ export class WegHeader {
195
332
  handleKeyDown(event) {
196
333
  if (event.key !== 'Escape')
197
334
  return;
198
- this.menuOpen = false;
199
- this.closeDropdown();
335
+ if (this.openDropdown) {
336
+ event.preventDefault();
337
+ this.closeDropdownAndFocusTrigger(this.openDropdown);
338
+ return;
339
+ }
340
+ if (this.menuOpen) {
341
+ event.preventDefault();
342
+ this.closeMenu({ focusToggle: true });
343
+ }
344
+ }
345
+ lockBodyScroll() {
346
+ if (this.bodyScrollLocked)
347
+ return;
348
+ this.bodyScrollLockY = window.scrollY;
349
+ document.body.style.overflow = 'hidden';
350
+ document.body.style.position = 'fixed';
351
+ document.body.style.top = `-${this.bodyScrollLockY}px`;
352
+ document.body.style.left = '0';
353
+ document.body.style.right = '0';
354
+ document.body.style.width = '100%';
355
+ this.bodyScrollLocked = true;
356
+ }
357
+ unlockBodyScroll() {
358
+ if (!this.bodyScrollLocked)
359
+ return;
360
+ document.body.style.overflow = '';
361
+ document.body.style.position = '';
362
+ document.body.style.top = '';
363
+ document.body.style.left = '';
364
+ document.body.style.right = '';
365
+ document.body.style.width = '';
366
+ window.scrollTo(0, this.bodyScrollLockY);
367
+ this.bodyScrollLocked = false;
200
368
  }
201
369
  handleDocumentClick(event) {
202
370
  const target = event.target;
@@ -227,10 +395,12 @@ export class WegHeader {
227
395
  }
228
396
  toggleDropdown(label) {
229
397
  if (this.openDropdown === label) {
398
+ this.announceSubmenuCollapsed(label);
230
399
  this.closeDropdown();
231
400
  return;
232
401
  }
233
402
  this.openDropdownMenu(label);
403
+ this.announceSubmenuExpanded(label);
234
404
  }
235
405
  getDropdownPanelId(label) {
236
406
  return `weg-header-dropdown-${label.trim().toLowerCase().replace(/\s+/g, '-')}`;
@@ -283,29 +453,111 @@ export class WegHeader {
283
453
  if (event.key !== 'ArrowDown')
284
454
  return;
285
455
  event.preventDefault();
286
- this.openDropdownMenu(label);
456
+ if (this.openDropdown !== label) {
457
+ this.openDropdownMenu(label);
458
+ this.announceSubmenuExpanded(label);
459
+ }
287
460
  this.focusFirstDropdownLink(itemEl);
288
461
  }
462
+ getDropdownLinks(container) {
463
+ return Array.from(container.querySelectorAll('.nav-dropdown__link'));
464
+ }
465
+ focusDropdownLink(container, index) {
466
+ const attemptFocus = (retries = 8) => {
467
+ const links = this.getDropdownLinks(container);
468
+ if (links[index]) {
469
+ links[index].focus();
470
+ return;
471
+ }
472
+ if (retries > 0) {
473
+ requestAnimationFrame(() => attemptFocus(retries - 1));
474
+ }
475
+ };
476
+ attemptFocus();
477
+ }
289
478
  focusFirstDropdownLink(itemEl) {
290
- setTimeout(() => {
291
- const firstLink = itemEl.querySelector('.nav-dropdown__link');
292
- firstLink?.focus();
293
- }, 0);
479
+ this.focusDropdownLink(itemEl, 0);
480
+ }
481
+ getDropdownContainer(label) {
482
+ const panelId = this.getDropdownPanelId(label);
483
+ return this.el.shadowRoot?.querySelector(`#${panelId}-trigger`)?.closest('.nav-dropdown') ?? null;
484
+ }
485
+ closeDropdownAndFocusTrigger(labelOrContainer) {
486
+ const container = typeof labelOrContainer === 'string'
487
+ ? this.getDropdownContainer(labelOrContainer)
488
+ : labelOrContainer;
489
+ const trigger = container?.querySelector('.nav-dropdown__trigger');
490
+ const label = typeof labelOrContainer === 'string'
491
+ ? labelOrContainer
492
+ : (trigger?.querySelector('.nav-dropdown__label')?.textContent?.trim() ?? '');
493
+ if (label && this.openDropdown === label) {
494
+ this.announceSubmenuCollapsed(label);
495
+ }
496
+ this.closeDropdown();
497
+ const attemptFocus = (retries = 8) => {
498
+ if (trigger && this.el.shadowRoot?.contains(trigger)) {
499
+ trigger.focus();
500
+ return;
501
+ }
502
+ if (retries > 0) {
503
+ requestAnimationFrame(() => attemptFocus(retries - 1));
504
+ }
505
+ };
506
+ attemptFocus();
507
+ }
508
+ handleDropdownLinkKeyDown(event, container) {
509
+ const links = this.getDropdownLinks(container);
510
+ const currentIndex = links.indexOf(event.currentTarget);
511
+ if (currentIndex === -1)
512
+ return;
513
+ if (event.key === 'ArrowDown') {
514
+ event.preventDefault();
515
+ links[(currentIndex + 1) % links.length]?.focus();
516
+ return;
517
+ }
518
+ if (event.key === 'ArrowUp') {
519
+ event.preventDefault();
520
+ links[(currentIndex - 1 + links.length) % links.length]?.focus();
521
+ return;
522
+ }
523
+ if (event.key === 'Escape') {
524
+ event.preventDefault();
525
+ event.stopPropagation();
526
+ this.closeDropdownAndFocusTrigger(container);
527
+ }
294
528
  }
295
529
  toggleMenu() {
296
530
  if (this.menuOpen) {
297
- this.closeMenu();
531
+ this.closeMenu({ focusToggle: false });
298
532
  return;
299
533
  }
300
534
  this.openMenu();
301
535
  }
302
536
  openMenu() {
303
537
  this.menuOpen = true;
538
+ this.headerHidden = false;
304
539
  this.closeDropdown();
540
+ if (!this.desktopMediaQuery?.matches) {
541
+ this.lockBodyScroll();
542
+ const itemCount = this.getActiveMenu().length;
543
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} opened. ${this.formatItemCount(itemCount)}.`);
544
+ this.focusFirstNavItem();
545
+ }
305
546
  }
306
- closeMenu() {
547
+ closeMenu(options = {}) {
548
+ const { announce = true, focusToggle = false } = options;
549
+ const wasMobileActive = this.isMobileMenuActive();
307
550
  this.menuOpen = false;
308
551
  this.closeDropdown();
552
+ this.unlockBodyScroll();
553
+ this.syncHeaderHeight();
554
+ this.updateHeaderVisibility();
555
+ if (announce && wasMobileActive) {
556
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} closed.`);
557
+ }
558
+ if (focusToggle) {
559
+ this.focusMenuToggle();
560
+ }
309
561
  }
310
562
  getAccountHome() {
311
563
  return normalizeAccountBase(this.accountBaseUrl);
@@ -334,23 +586,60 @@ export class WegHeader {
334
586
  const name = this.userName?.trim();
335
587
  return name || 'Manage Account';
336
588
  }
337
- renderIconNavLink(link, Icon, className, onNavigate) {
338
- return (h("li", { class: "main-nav__item main-nav__item--auth", key: link.label }, h("a", { class: { 'sign-in-link': true, [className]: true }, href: link.href, onClick: () => onNavigate?.() }, h(Icon, null), link.label)));
589
+ shouldExcludeFromNavActive(item, accountHome) {
590
+ return isSignOutLink(item) || isSignInLink(item, accountHome);
339
591
  }
340
- renderFlatNavLink(link, onNavigate) {
341
- return (h("li", { class: "main-nav__item", key: link.label }, h("a", { class: "nav-link", href: link.href, onClick: () => onNavigate?.() }, link.label)));
592
+ isNavItemActive(href, mode) {
593
+ return isNavLinkActive(this.currentPath ?? '', href, mode);
342
594
  }
343
- renderMenuGroup(item, onNavigate) {
595
+ resolveAriaCurrentHref() {
596
+ const accountHome = this.getAccountHome();
597
+ return resolveAriaCurrentNavKey(this.getActiveMenu(), this.currentPath ?? '', (item) => this.shouldExcludeFromNavActive(item, accountHome));
598
+ }
599
+ isAriaCurrentHref(href, ariaCurrentKey) {
600
+ return isAriaCurrentNavHref(href, ariaCurrentKey);
601
+ }
602
+ renderIconNavLink(link, Icon, className, onNavigate, ariaCurrentHref = null) {
603
+ const active = this.isNavItemActive(link.href, 'prefix');
604
+ const ariaCurrent = this.isAriaCurrentHref(link.href, ariaCurrentHref);
605
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: link.label }, h("a", { class: {
606
+ 'sign-in-link': true,
607
+ [className]: true,
608
+ 'nav-link--active': active,
609
+ }, href: link.href, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.() }, h(Icon, null), link.label)));
610
+ }
611
+ renderFlatNavLink(link, onNavigate, ariaCurrentHref = null) {
612
+ const active = this.isNavItemActive(link.href, 'prefix');
613
+ const ariaCurrent = this.isAriaCurrentHref(link.href, ariaCurrentHref);
614
+ return (h("li", { class: "main-nav__item", key: link.label }, h("a", { class: {
615
+ 'nav-link': true,
616
+ 'nav-link--active': active,
617
+ }, href: link.href, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.() }, link.label)));
618
+ }
619
+ renderMenuGroup(item, onNavigate, ariaCurrentHref = null) {
344
620
  const items = item.items ?? [];
345
621
  const isOpen = this.openDropdown === item.label;
346
622
  const panelId = this.getDropdownPanelId(item.label);
623
+ const groupActive = items.some((child) => this.isNavItemActive(child.href, 'exact'));
347
624
  return (h("li", { class: "main-nav__item main-nav__item--dropdown", key: item.label }, h("div", { class: "nav-dropdown", onMouseEnter: () => this.handleDropdownPointerEnter(item.label), onMouseLeave: (event) => this.handleDropdownPointerLeave(event), onFocusout: (event) => this.handleDropdownFocusOut(event, item.label) }, h("button", { type: "button", class: {
348
625
  'nav-dropdown__trigger': true,
349
626
  'nav-dropdown__trigger--open': isOpen,
627
+ 'nav-dropdown__trigger--active': groupActive,
350
628
  }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => this.handleDropdownTriggerClick(event, item.label), onKeyDown: (event) => {
351
629
  const container = event.currentTarget.closest('.nav-dropdown');
352
630
  this.handleDropdownTriggerKeyDown(event, item.label, container);
353
- } }, h("span", { class: "nav-dropdown__label" }, item.label), h(ToggleIcon, { expanded: isOpen })), isOpen ? (h("div", { class: "nav-dropdown__panel", id: panelId, role: "region", "aria-labelledby": `${panelId}-trigger`, onMouseEnter: () => this.handleDropdownPanelPointerEnter(item.label), onMouseLeave: (event) => this.handleDropdownPanelPointerLeave(event) }, h("div", { class: "nav-dropdown__accent" }, h("div", { class: "nav-dropdown__links" }, items.map((child) => (h("a", { class: "nav-dropdown__link", href: child.href, key: `${item.label}:${child.label}`, onClick: () => onNavigate?.() }, child.label))))))) : null)));
631
+ } }, h("span", { class: "nav-dropdown__label" }, item.label), h(ToggleIcon, { expanded: isOpen })), isOpen ? (h("div", { class: "nav-dropdown__panel", id: panelId, role: "region", "aria-labelledby": `${panelId}-trigger`, onMouseEnter: () => this.handleDropdownPanelPointerEnter(item.label), onMouseLeave: (event) => this.handleDropdownPanelPointerLeave(event) }, h("div", { class: "nav-dropdown__accent" }, h("ul", { class: "nav-dropdown__links" }, items.map((child, index) => {
632
+ const childActive = this.isNavItemActive(child.href, 'exact');
633
+ const ariaCurrent = this.isAriaCurrentHref(child.href, ariaCurrentHref);
634
+ const positionLabel = `${child.label}, link ${index + 1} of ${items.length}`;
635
+ return (h("li", { key: `${item.label}:${child.label}` }, h("a", { class: {
636
+ 'nav-dropdown__link': true,
637
+ 'nav-dropdown__link--active': childActive,
638
+ }, href: child.href, "aria-label": positionLabel, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.(), onKeyDown: (event) => {
639
+ const dropdownContainer = event.currentTarget.closest('.nav-dropdown');
640
+ this.handleDropdownLinkKeyDown(event, dropdownContainer);
641
+ } }, child.label)));
642
+ }))))) : null)));
354
643
  }
355
644
  handleAuthClick(event, action, _href, onNavigate) {
356
645
  const emitted = this.wegAuthClick.emit({ action });
@@ -376,18 +665,22 @@ export class WegHeader {
376
665
  [options.className || '']: !!options.className,
377
666
  }, href: link.href, "aria-label": link.label, onClick: (event) => this.handleAuthClick(event, action, link.href, onNavigate) }, h(Icon, null), !iconOnly ? link.label : null, iconOnly ? h("span", { class: "sr-only" }, link.label) : null));
378
667
  }
379
- renderManageAccountAnchor(onNavigate, iconOnly = false) {
668
+ renderManageAccountAnchor(onNavigate, iconOnly = false, ariaCurrentHref = null) {
380
669
  const label = this.getManageAccountLabel();
670
+ const href = accountPath(this.getAccountHome(), '/account/manage');
671
+ const active = this.isNavItemActive(href, 'prefix');
672
+ const ariaCurrent = this.isAriaCurrentHref(href, ariaCurrentHref);
381
673
  return (h("a", { class: {
382
674
  'sign-in-link': !iconOnly,
383
675
  'manage-account-link': true,
384
676
  'icon-button': iconOnly,
385
- }, href: accountPath(this.getAccountHome(), '/account/manage'), "aria-label": label, onClick: () => onNavigate?.() }, h(SignInIcon, null), !iconOnly ? label : null, iconOnly ? h("span", { class: "sr-only" }, label) : null));
677
+ 'nav-link--active': active,
678
+ }, href: href, "aria-label": label, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.() }, h(SignInIcon, null), !iconOnly ? label : null, iconOnly ? h("span", { class: "sr-only" }, label) : null));
386
679
  }
387
- renderNavItem(item, onNavigate) {
680
+ renderNavItem(item, onNavigate, ariaCurrentHref = null) {
388
681
  const accountHome = this.getAccountHome();
389
682
  if (isMenuGroup(item)) {
390
- return this.renderMenuGroup(item, onNavigate);
683
+ return this.renderMenuGroup(item, onNavigate, ariaCurrentHref);
391
684
  }
392
685
  if (isSignOutLink(item)) {
393
686
  return (h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-out" }, this.renderAuthControl({ link: item, action: 'sign-out', onNavigate })));
@@ -396,18 +689,19 @@ export class WegHeader {
396
689
  return (h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-in" }, this.renderAuthControl({ link: item, action: 'sign-in', onNavigate })));
397
690
  }
398
691
  if (isManageAccountLink(item, accountHome)) {
399
- return (h("li", { class: "main-nav__item main-nav__item--auth", key: "manage-account" }, this.renderManageAccountAnchor(onNavigate)));
692
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: "manage-account" }, this.renderManageAccountAnchor(onNavigate, false, ariaCurrentHref)));
400
693
  }
401
694
  if (this.signedIn && isFindJobLink(item)) {
402
- return this.renderIconNavLink(item, FindJobIcon, 'find-a-job-link', onNavigate);
695
+ return this.renderIconNavLink(item, FindJobIcon, 'find-a-job-link', onNavigate, ariaCurrentHref);
403
696
  }
404
697
  if (this.signedIn && isDashboardLink(item, accountHome)) {
405
- return this.renderIconNavLink(item, DashboardIcon, 'dashboard-link', onNavigate);
698
+ return this.renderIconNavLink(item, DashboardIcon, 'dashboard-link', onNavigate, ariaCurrentHref);
406
699
  }
407
- return this.renderFlatNavLink(item, onNavigate);
700
+ return this.renderFlatNavLink(item, onNavigate, ariaCurrentHref);
408
701
  }
409
702
  renderNav(onNavigate) {
410
- return (h("nav", { class: "main-nav", "aria-label": "Main" }, h("ul", { class: "main-nav__list" }, this.getActiveMenu().map((item) => this.renderNavItem(item, onNavigate)))));
703
+ const ariaCurrentHref = this.resolveAriaCurrentHref();
704
+ return (h("nav", { class: "main-nav", "aria-label": MAIN_NAV_LABEL }, h("ul", { class: "main-nav__list" }, this.getActiveMenu().map((item) => this.renderNavItem(item, onNavigate, ariaCurrentHref)))));
411
705
  }
412
706
  renderCompactAuth(onNavigate) {
413
707
  if (this.signedIn) {
@@ -428,10 +722,12 @@ export class WegHeader {
428
722
  const logoSrc = this.getLogoSrc();
429
723
  const closeMenu = () => this.closeMenu();
430
724
  const mobileMenuActive = this.isMobileMenuActive();
431
- return (h("header", { key: '64736cfe4328d1ab78ae99cca6721c492f1531e9', class: {
725
+ return (h("header", { key: '429d9af0996fa37484ebeea36f1e071fe0ba0789', class: {
432
726
  header: true,
727
+ 'header--fixed': !mobileMenuActive,
728
+ 'header--hidden': this.headerHidden && !mobileMenuActive,
433
729
  'header--menu-open': mobileMenuActive,
434
- } }, h("div", { key: '0db29810a8772023f5b50b7f7074ce1a41e65e83', class: "header-inner" }, h("button", { key: '75c2f7ecd64bfcc7279798efad80af9502807f6e', type: "button", class: "menu-toggle icon-button", "aria-label": mobileMenuActive ? 'Close menu' : 'Open menu', "aria-expanded": mobileMenuActive ? 'true' : 'false', "aria-controls": "weg-header-main-nav", onClick: () => this.toggleMenu() }, mobileMenuActive ? h(CloseIcon, null) : h(HamburgerIcon, null)), h(Logo, { key: 'd05be8ac2d8be06bc5a005a70c1fe25bd6259243', href: logoHref, src: logoSrc }), h("div", { key: '3a125651b08eeb7906444a056fe871873807ef73', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: '4e36a06336d834c418511a89eb1d3fe6278957ad', class: "main-nav-panel", id: "weg-header-main-nav", role: mobileMenuActive ? 'dialog' : undefined, "aria-modal": mobileMenuActive ? 'true' : undefined, "aria-label": mobileMenuActive ? 'Menu' : undefined }, this.renderNav(mobileMenuActive ? closeMenu : undefined)))));
730
+ } }, h("div", { key: '2c088a3e602ac2c8751d21376872536e35a0ab7d', class: "header-inner" }, h("div", { key: '91089bfc45b2b3274df00b032cad0adaba79b937', class: "sr-only", "aria-live": "polite", "aria-atomic": "true", "data-weg-sr-live": true }), h("button", { key: 'a66e0f0564014f5041a9fa80b39f74ce177f7125', type: "button", class: "menu-toggle icon-button", "aria-label": mobileMenuActive ? CLOSE_MAIN_NAV_LABEL : OPEN_MAIN_NAV_LABEL, "aria-expanded": mobileMenuActive ? 'true' : 'false', "aria-controls": "weg-header-main-nav", onClick: () => this.toggleMenu() }, mobileMenuActive ? h(CloseIcon, null) : h(HamburgerIcon, null)), h(Logo, { key: '917d134565c899e9a25557bafd62bf8b0de75e32', href: logoHref, src: logoSrc }), h("div", { key: '7b6bcd11271e00ddf0ca7d34bc1c8d2ad8342ed4', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: '1a6779e99be4704b6f9093501dd230a891b81c17', class: "main-nav-panel", id: "weg-header-main-nav", role: mobileMenuActive ? 'dialog' : undefined, "aria-modal": mobileMenuActive ? 'true' : undefined, "aria-label": mobileMenuActive ? MAIN_NAV_LABEL : undefined }, this.renderNav(mobileMenuActive ? closeMenu : undefined)))));
435
731
  }
436
732
  static get is() { return "weg-header"; }
437
733
  static get encapsulation() { return "shadow"; }
@@ -466,7 +762,7 @@ export class WegHeader {
466
762
  "optional": true,
467
763
  "docs": {
468
764
  "tags": [],
469
- "text": "Layout payload, supplied by the host application.\n\nExpected shape:\n```json\n{\n \"header\": {\n \"menu\": [\n { \"label\": \"Find a job\", \"items\": [{ \"label\": \"Graduates\", \"href\": \"...\" }] },\n { \"label\": \"Career advice\", \"href\": \"/career-advice\" },\n { \"label\": \"Sign in\", \"href\": \"/account/login\" }\n ]\n }\n}\n```"
765
+ "text": "Layout payload, supplied by the host application.\r\n\r\nExpected shape:\r\n```json\r\n{\r\n \"header\": {\r\n \"menu\": [\r\n { \"label\": \"Find a job\", \"items\": [{ \"label\": \"Graduates\", \"href\": \"...\" }] },\r\n { \"label\": \"Career advice\", \"href\": \"/career-advice\" },\r\n { \"label\": \"Sign in\", \"href\": \"/account/login\" }\r\n ]\r\n }\r\n}\r\n```"
470
766
  },
471
767
  "getter": false,
472
768
  "setter": false,
@@ -524,12 +820,31 @@ export class WegHeader {
524
820
  "optional": true,
525
821
  "docs": {
526
822
  "tags": [],
527
- "text": "Account portal origin for sign-in, register, dashboard, and manage-account links.\nDefaults to production when omitted."
823
+ "text": "Account portal origin for sign-in, register, dashboard, and manage-account links.\r\nDefaults to production when omitted."
528
824
  },
529
825
  "getter": false,
530
826
  "setter": false,
531
827
  "reflect": false,
532
828
  "attribute": "account-base-url"
829
+ },
830
+ "currentPath": {
831
+ "type": "string",
832
+ "mutable": false,
833
+ "complexType": {
834
+ "original": "string",
835
+ "resolved": "string",
836
+ "references": {}
837
+ },
838
+ "required": false,
839
+ "optional": true,
840
+ "docs": {
841
+ "tags": [],
842
+ "text": "Current page path from the host application (e.g. `/career-advice/my-article`).\r\nInclude a query string when matching dropdown links that use search params."
843
+ },
844
+ "getter": false,
845
+ "setter": false,
846
+ "reflect": false,
847
+ "attribute": "current-path"
533
848
  }
534
849
  };
535
850
  }
@@ -537,7 +852,8 @@ export class WegHeader {
537
852
  return {
538
853
  "resolved": {},
539
854
  "menuOpen": {},
540
- "openDropdown": {}
855
+ "openDropdown": {},
856
+ "headerHidden": {}
541
857
  };
542
858
  }
543
859
  static get events() {
@@ -549,7 +865,7 @@ export class WegHeader {
549
865
  "composed": true,
550
866
  "docs": {
551
867
  "tags": [],
552
- "text": "Fired when the user clicks Sign in or Sign out.\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
868
+ "text": "Fired when the user clicks Sign in or Sign out.\r\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
553
869
  },
554
870
  "complexType": {
555
871
  "original": "{ action: LayoutHeaderAuthAction }",