weg-shared-layout 0.0.41 → 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.
@@ -375,13 +375,19 @@ const LOGO_SRC = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAFcCAYAAACQ
375
375
 
376
376
  const sharedCss = () => `:host{--weg-breakpoint-sm:640px;--weg-breakpoint-md:960px;--weg-breakpoint-lg:1280px;--weg-breakpoint-xl:1440px}:root,:host{--weg-container-breakpoint-sm:40rem;--weg-container-breakpoint-md:48rem;--weg-container-breakpoint-lg:64rem;--weg-container-breakpoint-xl:80rem;--weg-container-breakpoint-2xl:86rem;--weg-container-max-vw:95vw;--weg-container-padding-inline:1rem}@media (width >= 48rem){:root,:host{--weg-container-padding-inline:2rem}}.container{width:100%;max-width:var(--weg-container-max-vw);margin-inline:auto;padding-inline:var(--weg-container-padding-inline)}@media (width >= 40rem){.container{max-width:min(var(--weg-container-breakpoint-sm), var(--weg-container-max-vw))}}@media (width >= 48rem){.container{max-width:min(var(--weg-container-breakpoint-md), var(--weg-container-max-vw))}}@media (width >= 64rem){.container{max-width:min(var(--weg-container-breakpoint-lg), var(--weg-container-max-vw))}}@media (width >= 80rem){.container{max-width:min(var(--weg-container-breakpoint-xl), var(--weg-container-max-vw))}}@media (width >= 86rem){.container{max-width:min(var(--weg-container-breakpoint-xl), var(--weg-container-max-vw))}}*,*::before,*::after{box-sizing:border-box}:host{display:block;--weg-font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji';--weg-color-accent:#948eeb;--weg-purple-200:#cdcff8}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}`;
377
377
 
378
- const wegHeaderCss = () => `:host{--weg-breakpoint-sm:640px;--weg-breakpoint-md:960px;--weg-breakpoint-lg:1280px;--weg-breakpoint-xl:1440px}.header{width:100%;border-bottom:2px solid var(--weg-color-accent);background:#ffffff;color:#0f172a;font-family:var(--weg-font-family)}@media (width >= 960px){.header{border-bottom-width:0.25rem}}:host{position:relative;z-index:20}.header--menu-open{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;overflow:hidden}.header--menu-open .header-inner{flex:1 1 auto;flex-wrap:wrap;align-content:flex-start;min-height:0}.header--menu-open .main-nav-panel{display:block;flex:1 1 100%;width:100%;min-height:0;overflow-y:auto;border-top:1px solid #d8d4f7}@media (width >= 960px){.header--menu-open{position:static;inset:auto;z-index:auto;display:block;overflow:visible}.header--menu-open .header-inner{flex:unset;flex-wrap:nowrap;align-content:unset;min-height:unset}}.logo-link{display:inline-flex;align-items:center;text-decoration:none;outline:none}.logo-link:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:4px;border-radius:4px}.logo{height:2.5rem;width:auto}@media (width >= 960px){.logo{height:3rem}}@media (width >= 1280px){.logo{height:5rem}}.header-inner{display:flex;width:100%;min-width:0;align-items:center;justify-content:space-between;gap:0.75rem;padding-block:0.75rem;padding-inline:var(--weg-container-padding-inline)}@media (width >= 960px){.header-inner{flex-wrap:nowrap;gap:1.5rem;padding-block:1.25rem}}@media (width >= 1440px){.header-inner{padding-block:1.75rem}}.header-inner .logo-link{flex:1 1 auto;min-width:0;justify-content:center}@media (width >= 960px){.header-inner .logo-link{flex:0 0 auto;justify-content:flex-start}}.header-inner .logo{max-width:100%}.header-actions{display:flex;align-items:center;flex-shrink:0}@media (width >= 960px){.header-actions{display:none}}.main-nav-panel{display:none;margin-inline:-0.5rem}@media (width >= 960px){.main-nav-panel{display:flex;flex:1 1 auto;width:auto;min-width:0;justify-content:flex-end;overflow:visible;border-top:none}}.main-nav{padding:0 1rem 1.5rem}@media (width >= 960px){.main-nav{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:1rem;padding:0}}.main-nav__list{list-style:none;margin:0;padding:0}@media (width >= 960px){.main-nav__list{display:flex;align-items:center;flex-wrap:wrap;gap:0.5rem}}@media (width >= 1280px){.main-nav__list{gap:1.5rem}}.main-nav__item{border-bottom:1px solid #d8d4f7}@media (width >= 960px){.main-nav__item{border-bottom:none}}.main-nav__item--dropdown{position:static}.nav-dropdown{position:relative}.nav-link{display:block;width:100%;padding:1rem 0;font-weight:600;color:inherit;text-decoration:none;outline:none}.nav-link:hover{opacity:0.85}.nav-link:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px;border-radius:4px}.nav-link--active{background:var(--weg-purple-200)}.nav-dropdown__trigger{display:flex;align-items:center;justify-content:space-between;gap:0.75rem;width:100%;padding:1rem 0;border:none;background:transparent;font:inherit;font-weight:600;color:inherit;text-align:left;cursor:pointer;outline:none}.nav-dropdown__trigger:hover{opacity:0.85}.nav-dropdown__trigger:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px;border-radius:4px}.nav-dropdown__trigger--open{background:transparent}.nav-dropdown__trigger--active{background:var(--weg-purple-200)}.toggle-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999rem;background:#312e81;color:#ffffff;flex-shrink:0}.nav-dropdown__panel{padding:0 0 0.75rem}@media (width >= 960px){.nav-dropdown__panel{position:absolute;top:calc(100% + 0.5rem);left:0;z-index:500;min-width:17.5rem;padding:0.75rem 0 0.75rem 0.75rem;border:1px solid #e2e8f0;border-radius:0.5rem;background:#f8fafc;box-shadow:0 0.25rem 1rem rgba(15, 23, 42, 0.08)}.nav-dropdown__panel::before{content:'';position:absolute;top:-0.5rem;left:0;right:0;height:0.5rem}}.nav-dropdown__accent{border-left:0.25rem solid var(--weg-color-accent);padding-left:0.75rem}.nav-dropdown__links{display:flex;flex-direction:column;gap:0.25rem}.nav-dropdown__link{display:block;padding:0.625rem 0;font-weight:400;color:inherit;text-decoration:none;outline:none}.nav-dropdown__link:hover{opacity:0.85}.nav-dropdown__link:focus-visible{outline:0.125rem solid var(--weg-color-accent);outline-offset:0.125rem;border-radius:0.25rem}.nav-dropdown__link--active{background:var(--weg-purple-200)}@media (width >= 960px){.nav-dropdown__link{padding:0.5rem;margin-right:0.75rem;border-radius:0.5rem;font-weight:600}.nav-dropdown__link:hover,.nav-dropdown__link:focus-visible{background:var(--weg-purple-200);opacity:1}.nav-dropdown__link--active{opacity:1}}@media (width >= 1280px){.nav-dropdown__link{padding:0.5rem 0.75rem}}.main-nav__item--auth>a{display:flex;align-items:center;gap:0.5rem;width:100%;padding:1rem 0}@media (width >= 960px){.nav-link,.nav-dropdown__trigger,.main-nav__item--auth>a{display:inline-flex;align-items:center;width:auto;padding:0.5rem;border-radius:0.5rem}.nav-link--active,.main-nav__item--auth>a.nav-link--active{background:var(--weg-purple-200)}.nav-dropdown__trigger{gap:0.5rem;cursor:pointer}.nav-dropdown__trigger--open{background:#f1f5f9}.nav-dropdown__trigger--active{background:var(--weg-purple-200)}}@media (width >= 1280px){.main-nav{font-size:1.25rem}.main-nav__item--auth>a{padding:0.5rem 0.75rem}}.sign-in-link,.sign-out-link,.auth-button{display:inline-flex;align-items:center;gap:0.5rem;font-weight:600;color:inherit;text-decoration:none;outline:none}.auth-button{padding:0;border:none;background:transparent;font:inherit;cursor:pointer}.sign-in-link:hover,.sign-out-link:hover,.auth-button:hover{opacity:0.85}.sign-in-link:focus-visible,.sign-out-link:focus-visible,.auth-button:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:4px;border-radius:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;padding:0;border:none;border-radius:0.5rem;background:transparent;color:inherit;cursor:pointer;text-decoration:none;outline:none}.icon-button:hover{background:#f1f5f9}.icon-button:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px}.menu-toggle{flex-shrink:0}@media (width >= 960px){.menu-toggle{display:none}}`;
378
+ const wegHeaderCss = () => `:host{--weg-breakpoint-sm:640px;--weg-breakpoint-md:960px;--weg-breakpoint-lg:1280px;--weg-breakpoint-xl:1440px}.header{width:100%;border-bottom:2px solid var(--weg-color-accent);background:#ffffff;color:#0f172a;font-family:var(--weg-font-family)}@media (width >= 960px){.header{border-bottom-width:0.25rem}}:host{position:relative;z-index:20;display:block;height:var(--weg-header-height, auto)}.header--fixed{position:fixed;top:0;left:0;right:0;z-index:20;transform:translateY(0);transition:transform 0.25s ease}.header--fixed.header--hidden{transform:translateY(-100%)}@media (prefers-reduced-motion: reduce){.header--fixed{transition:none}}.header--menu-open{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;overflow:hidden}.header--menu-open .header-inner{flex:1 1 auto;flex-wrap:wrap;align-content:flex-start;min-height:0}.header--menu-open .main-nav-panel{display:block;flex:1 1 100%;width:100%;min-height:0;overflow-y:auto;border-top:1px solid #d8d4f7}@media (width >= 960px){.header--menu-open{position:static;inset:auto;z-index:auto;display:block;overflow:visible}.header--menu-open .header-inner{flex:unset;flex-wrap:nowrap;align-content:unset;min-height:unset}}.logo-link{display:inline-flex;align-items:center;text-decoration:none;outline:none}.logo-link:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:4px;border-radius:4px}.logo{height:2.5rem;width:auto}@media (width >= 960px){.logo{height:3rem}}@media (width >= 1280px){.logo{height:5rem}}.header-inner{display:flex;width:100%;min-width:0;align-items:center;justify-content:space-between;gap:0.75rem;padding-block:0.75rem;padding-inline:var(--weg-container-padding-inline)}@media (width >= 960px){.header-inner{flex-wrap:nowrap;gap:1.5rem;padding-block:1.25rem}}@media (width >= 1440px){.header-inner{padding-block:1.75rem}}.header-inner .logo-link{flex:1 1 auto;min-width:0;justify-content:center}@media (width >= 960px){.header-inner .logo-link{flex:0 0 auto;justify-content:flex-start}}.header-inner .logo{max-width:100%}.header-actions{display:flex;align-items:center;flex-shrink:0}@media (width >= 960px){.header-actions{display:none}}.main-nav-panel{display:none;margin-inline:-0.5rem}@media (width >= 960px){.main-nav-panel{display:flex;flex:1 1 auto;width:auto;min-width:0;justify-content:flex-end;overflow:visible;border-top:none}}.main-nav{padding:0 1rem 1.5rem}@media (width >= 960px){.main-nav{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:1rem;padding:0}}.main-nav__list{list-style:none;margin:0;padding:0}@media (width >= 960px){.main-nav__list{display:flex;align-items:center;flex-wrap:wrap;gap:0.5rem}}@media (width >= 1280px){.main-nav__list{gap:1.5rem}}.main-nav__item{border-bottom:1px solid #d8d4f7}@media (width >= 960px){.main-nav__item{border-bottom:none}}.main-nav__item--dropdown{position:static}.nav-dropdown{position:relative}.nav-link{display:block;width:100%;padding:1rem 0;font-weight:600;color:inherit;text-decoration:none;outline:none}.nav-link:hover{opacity:0.85}.nav-link:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px;border-radius:4px}.nav-link--active{background:var(--weg-purple-200)}.nav-dropdown__trigger{display:flex;align-items:center;justify-content:space-between;gap:0.75rem;width:100%;padding:1rem 0;border:none;background:transparent;font:inherit;font-weight:600;color:inherit;text-align:left;cursor:pointer;outline:none}.nav-dropdown__trigger:hover{opacity:0.85}.nav-dropdown__trigger:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px;border-radius:4px}.nav-dropdown__trigger--open{background:transparent}.nav-dropdown__trigger--active{background:var(--weg-purple-200)}.toggle-icon{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:999rem;background:#312e81;color:#ffffff;flex-shrink:0}.nav-dropdown__panel{padding:0 0 0.75rem}@media (width >= 960px){.nav-dropdown__panel{position:absolute;top:calc(100% + 0.5rem);left:0;z-index:500;min-width:17.5rem;padding:0.75rem 0 0.75rem 0.75rem;border:1px solid #e2e8f0;border-radius:0.5rem;background:#f8fafc;box-shadow:0 0.25rem 1rem rgba(15, 23, 42, 0.08)}.nav-dropdown__panel::before{content:'';position:absolute;top:-0.5rem;left:0;right:0;height:0.5rem}}.nav-dropdown__accent{border-left:0.25rem solid var(--weg-color-accent);padding-left:0.75rem}.nav-dropdown__links{display:flex;flex-direction:column;gap:0.25rem;list-style:none;margin:0;padding:0}.nav-dropdown__link{display:block;padding:0.625rem 0;font-weight:400;color:inherit;text-decoration:none;outline:none}.nav-dropdown__link:hover{opacity:0.85}.nav-dropdown__link:focus-visible{outline:0.125rem solid var(--weg-color-accent);outline-offset:0.125rem;border-radius:0.25rem}.nav-dropdown__link--active{background:var(--weg-purple-200)}@media (width >= 960px){.nav-dropdown__link{padding:0.5rem;margin-right:0.75rem;border-radius:0.5rem;font-weight:600}.nav-dropdown__link:hover,.nav-dropdown__link:focus-visible{background:var(--weg-purple-200);opacity:1}.nav-dropdown__link--active{opacity:1}}@media (width >= 1280px){.nav-dropdown__link{padding:0.5rem 0.75rem}}.main-nav__item--auth>a{display:flex;align-items:center;gap:0.5rem;width:100%;padding:1rem 0}@media (width >= 960px){.nav-link,.nav-dropdown__trigger,.main-nav__item--auth>a{display:inline-flex;align-items:center;width:auto;padding:0.5rem;border-radius:0.5rem}.nav-link--active,.main-nav__item--auth>a.nav-link--active{background:var(--weg-purple-200)}.nav-dropdown__trigger{gap:0.5rem;cursor:pointer}.nav-dropdown__trigger--open{background:#f1f5f9}.nav-dropdown__trigger--active{background:var(--weg-purple-200)}}@media (width >= 1280px){.main-nav{font-size:1.25rem}.main-nav__item--auth>a{padding:0.5rem 0.75rem}}.sign-in-link,.sign-out-link,.auth-button{display:inline-flex;align-items:center;gap:0.5rem;font-weight:600;color:inherit;text-decoration:none;outline:none}.auth-button{padding:0;border:none;background:transparent;font:inherit;cursor:pointer}.sign-in-link:hover,.sign-out-link:hover,.auth-button:hover{opacity:0.85}.sign-in-link:focus-visible,.sign-out-link:focus-visible,.auth-button:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:4px;border-radius:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;padding:0;border:none;border-radius:0.5rem;background:transparent;color:inherit;cursor:pointer;text-decoration:none;outline:none}.icon-button:hover{background:#f1f5f9}.icon-button:focus-visible{outline:2px solid var(--weg-color-accent);outline-offset:2px}.menu-toggle{flex-shrink:0}@media (width >= 960px){.menu-toggle{display:none}}`;
379
379
 
380
380
  const WEG_HOME = 'https://www.warwickemploymentgroup.com';
381
381
  const DEFAULT_ACCOUNT_HOME = 'https://account.warwickemploymentgroup.com';
382
+ /** Stay visible while scrolling down through the first half of the viewport. */
383
+ const HEADER_SCROLL_HIDE_THRESHOLD_VH = 0.5;
384
+ const HEADER_SCROLL_DIRECTION_DELTA = 5;
382
385
  const DEFAULT_LOGO_HREF = `${WEG_HOME}/`;
383
386
  /** Placeholder href — sign-out is handled via `wegAuthClick`, not navigation. */
384
387
  const SIGN_OUT_HREF = '#';
388
+ const MAIN_NAV_LABEL = 'Main navigation';
389
+ const OPEN_MAIN_NAV_LABEL = 'Open main navigation';
390
+ const CLOSE_MAIN_NAV_LABEL = 'Close main navigation';
385
391
  function normalizeAccountBase(input) {
386
392
  const base = input?.trim() || DEFAULT_ACCOUNT_HOME;
387
393
  return base.replace(/\/$/, '');
@@ -517,9 +523,16 @@ const WegHeader = class {
517
523
  resolved = EMPTY_HEADER;
518
524
  menuOpen = false;
519
525
  openDropdown = null;
526
+ headerHidden = false;
520
527
  boundHandleDocumentClick = this.handleDocumentClick.bind(this);
521
528
  boundHandleViewportChange = this.handleViewportChange.bind(this);
522
529
  boundHandleHoverCapabilityChange = this.handleHoverCapabilityChange.bind(this);
530
+ boundHandleScroll = this.handleScroll.bind(this);
531
+ headerResizeObserver;
532
+ lastScrollY = 0;
533
+ scrollTicking = false;
534
+ bodyScrollLocked = false;
535
+ bodyScrollLockY = 0;
523
536
  hoverCloseTimer = null;
524
537
  hoverOpenEnabled = false;
525
538
  desktopMediaQuery;
@@ -540,15 +553,69 @@ const WegHeader = class {
540
553
  this.updateHoverDropdownEnabled();
541
554
  this.handleViewportChange();
542
555
  }
556
+ componentDidLoad() {
557
+ const header = this.el.shadowRoot?.querySelector('.header');
558
+ if (!header)
559
+ return;
560
+ this.headerResizeObserver = new ResizeObserver(() => this.syncHeaderHeight());
561
+ this.headerResizeObserver.observe(header);
562
+ this.syncHeaderHeight();
563
+ this.lastScrollY = window.scrollY;
564
+ this.updateHeaderVisibility();
565
+ }
543
566
  connectedCallback() {
544
567
  document.addEventListener('click', this.boundHandleDocumentClick);
568
+ window.addEventListener('scroll', this.boundHandleScroll, { passive: true });
545
569
  }
546
570
  disconnectedCallback() {
547
571
  document.removeEventListener('click', this.boundHandleDocumentClick);
572
+ window.removeEventListener('scroll', this.boundHandleScroll);
573
+ this.unlockBodyScroll();
574
+ this.headerResizeObserver?.disconnect();
548
575
  this.desktopMediaQuery?.removeEventListener('change', this.boundHandleViewportChange);
549
576
  this.hoverCapableMediaQuery?.removeEventListener('change', this.boundHandleHoverCapabilityChange);
550
577
  this.cancelScheduledDropdownClose();
551
578
  }
579
+ syncHeaderHeight() {
580
+ if (this.isMobileMenuActive())
581
+ return;
582
+ const header = this.el.shadowRoot?.querySelector('.header');
583
+ if (!header)
584
+ return;
585
+ this.el.style.setProperty('--weg-header-height', `${header.offsetHeight}px`);
586
+ }
587
+ handleScroll() {
588
+ if (this.scrollTicking)
589
+ return;
590
+ this.scrollTicking = true;
591
+ requestAnimationFrame(() => {
592
+ this.scrollTicking = false;
593
+ this.updateHeaderVisibility();
594
+ });
595
+ }
596
+ updateHeaderVisibility() {
597
+ if (this.isMobileMenuActive()) {
598
+ if (this.headerHidden)
599
+ this.headerHidden = false;
600
+ return;
601
+ }
602
+ const scrollY = window.scrollY;
603
+ const delta = scrollY - this.lastScrollY;
604
+ const threshold = window.innerHeight * HEADER_SCROLL_HIDE_THRESHOLD_VH;
605
+ let nextHidden = this.headerHidden;
606
+ if (scrollY <= threshold) {
607
+ nextHidden = false;
608
+ }
609
+ else if (Math.abs(delta) >= HEADER_SCROLL_DIRECTION_DELTA) {
610
+ nextHidden = delta > 0;
611
+ }
612
+ this.lastScrollY = scrollY;
613
+ if (nextHidden === this.headerHidden)
614
+ return;
615
+ if (nextHidden)
616
+ this.closeDropdown();
617
+ this.headerHidden = nextHidden;
618
+ }
552
619
  /**
553
620
  * Desktop dropdowns open on hover when viewport is md+ and the device reports hover support.
554
621
  * Uses (hover: hover) only — not (pointer: fine), which breaks in DevTools and hybrid devices.
@@ -566,10 +633,74 @@ const WegHeader = class {
566
633
  }
567
634
  handleViewportChange() {
568
635
  if (this.desktopMediaQuery?.matches) {
569
- this.closeMenu();
636
+ this.closeMenu({ announce: false, focusToggle: false });
570
637
  }
571
638
  this.updateHoverDropdownEnabled();
572
639
  }
640
+ announceToScreenReader(message) {
641
+ const region = this.el.shadowRoot?.querySelector('[data-weg-sr-live]');
642
+ if (!region)
643
+ return;
644
+ region.textContent = '';
645
+ requestAnimationFrame(() => {
646
+ region.textContent = message;
647
+ });
648
+ }
649
+ getMenuToggleButton() {
650
+ return this.el.shadowRoot?.querySelector('.menu-toggle');
651
+ }
652
+ getFirstNavFocusable() {
653
+ const panel = this.el.shadowRoot?.querySelector('#weg-header-main-nav');
654
+ if (!panel)
655
+ return null;
656
+ const focusables = panel.querySelectorAll('a[href], button:not([disabled])');
657
+ return focusables[0] ?? null;
658
+ }
659
+ focusFirstNavItem() {
660
+ setTimeout(() => {
661
+ const attemptFocus = (retries = 8) => {
662
+ const target = this.getFirstNavFocusable();
663
+ if (target) {
664
+ target.focus();
665
+ return;
666
+ }
667
+ if (retries > 0) {
668
+ requestAnimationFrame(() => attemptFocus(retries - 1));
669
+ }
670
+ };
671
+ attemptFocus();
672
+ }, 0);
673
+ }
674
+ focusMenuToggle() {
675
+ const attemptFocus = (retries = 8) => {
676
+ const toggle = this.getMenuToggleButton();
677
+ if (toggle) {
678
+ toggle.focus();
679
+ return;
680
+ }
681
+ if (retries > 0) {
682
+ requestAnimationFrame(() => attemptFocus(retries - 1));
683
+ }
684
+ };
685
+ attemptFocus();
686
+ }
687
+ getMenuGroupLinkCount(label) {
688
+ const group = this.getActiveMenu().find((item) => isMenuGroup(item) && item.label === label);
689
+ return group?.items?.length ?? 0;
690
+ }
691
+ formatLinkCount(count) {
692
+ return count === 1 ? '1 link' : `${count} links`;
693
+ }
694
+ formatItemCount(count) {
695
+ return count === 1 ? '1 item' : `${count} items`;
696
+ }
697
+ announceSubmenuExpanded(label) {
698
+ const linkCount = this.getMenuGroupLinkCount(label);
699
+ this.announceToScreenReader(`${label} submenu expanded. ${this.formatLinkCount(linkCount)}.`);
700
+ }
701
+ announceSubmenuCollapsed(label) {
702
+ this.announceToScreenReader(`${label} submenu collapsed.`);
703
+ }
573
704
  isMobileMenuActive() {
574
705
  return this.menuOpen && !this.desktopMediaQuery?.matches;
575
706
  }
@@ -579,8 +710,39 @@ const WegHeader = class {
579
710
  handleKeyDown(event) {
580
711
  if (event.key !== 'Escape')
581
712
  return;
582
- this.menuOpen = false;
583
- this.closeDropdown();
713
+ if (this.openDropdown) {
714
+ event.preventDefault();
715
+ this.closeDropdownAndFocusTrigger(this.openDropdown);
716
+ return;
717
+ }
718
+ if (this.menuOpen) {
719
+ event.preventDefault();
720
+ this.closeMenu({ focusToggle: true });
721
+ }
722
+ }
723
+ lockBodyScroll() {
724
+ if (this.bodyScrollLocked)
725
+ return;
726
+ this.bodyScrollLockY = window.scrollY;
727
+ document.body.style.overflow = 'hidden';
728
+ document.body.style.position = 'fixed';
729
+ document.body.style.top = `-${this.bodyScrollLockY}px`;
730
+ document.body.style.left = '0';
731
+ document.body.style.right = '0';
732
+ document.body.style.width = '100%';
733
+ this.bodyScrollLocked = true;
734
+ }
735
+ unlockBodyScroll() {
736
+ if (!this.bodyScrollLocked)
737
+ return;
738
+ document.body.style.overflow = '';
739
+ document.body.style.position = '';
740
+ document.body.style.top = '';
741
+ document.body.style.left = '';
742
+ document.body.style.right = '';
743
+ document.body.style.width = '';
744
+ window.scrollTo(0, this.bodyScrollLockY);
745
+ this.bodyScrollLocked = false;
584
746
  }
585
747
  handleDocumentClick(event) {
586
748
  const target = event.target;
@@ -611,10 +773,12 @@ const WegHeader = class {
611
773
  }
612
774
  toggleDropdown(label) {
613
775
  if (this.openDropdown === label) {
776
+ this.announceSubmenuCollapsed(label);
614
777
  this.closeDropdown();
615
778
  return;
616
779
  }
617
780
  this.openDropdownMenu(label);
781
+ this.announceSubmenuExpanded(label);
618
782
  }
619
783
  getDropdownPanelId(label) {
620
784
  return `weg-header-dropdown-${label.trim().toLowerCase().replace(/\s+/g, '-')}`;
@@ -667,29 +831,111 @@ const WegHeader = class {
667
831
  if (event.key !== 'ArrowDown')
668
832
  return;
669
833
  event.preventDefault();
670
- this.openDropdownMenu(label);
834
+ if (this.openDropdown !== label) {
835
+ this.openDropdownMenu(label);
836
+ this.announceSubmenuExpanded(label);
837
+ }
671
838
  this.focusFirstDropdownLink(itemEl);
672
839
  }
840
+ getDropdownLinks(container) {
841
+ return Array.from(container.querySelectorAll('.nav-dropdown__link'));
842
+ }
843
+ focusDropdownLink(container, index) {
844
+ const attemptFocus = (retries = 8) => {
845
+ const links = this.getDropdownLinks(container);
846
+ if (links[index]) {
847
+ links[index].focus();
848
+ return;
849
+ }
850
+ if (retries > 0) {
851
+ requestAnimationFrame(() => attemptFocus(retries - 1));
852
+ }
853
+ };
854
+ attemptFocus();
855
+ }
673
856
  focusFirstDropdownLink(itemEl) {
674
- setTimeout(() => {
675
- const firstLink = itemEl.querySelector('.nav-dropdown__link');
676
- firstLink?.focus();
677
- }, 0);
857
+ this.focusDropdownLink(itemEl, 0);
858
+ }
859
+ getDropdownContainer(label) {
860
+ const panelId = this.getDropdownPanelId(label);
861
+ return this.el.shadowRoot?.querySelector(`#${panelId}-trigger`)?.closest('.nav-dropdown') ?? null;
862
+ }
863
+ closeDropdownAndFocusTrigger(labelOrContainer) {
864
+ const container = typeof labelOrContainer === 'string'
865
+ ? this.getDropdownContainer(labelOrContainer)
866
+ : labelOrContainer;
867
+ const trigger = container?.querySelector('.nav-dropdown__trigger');
868
+ const label = typeof labelOrContainer === 'string'
869
+ ? labelOrContainer
870
+ : (trigger?.querySelector('.nav-dropdown__label')?.textContent?.trim() ?? '');
871
+ if (label && this.openDropdown === label) {
872
+ this.announceSubmenuCollapsed(label);
873
+ }
874
+ this.closeDropdown();
875
+ const attemptFocus = (retries = 8) => {
876
+ if (trigger && this.el.shadowRoot?.contains(trigger)) {
877
+ trigger.focus();
878
+ return;
879
+ }
880
+ if (retries > 0) {
881
+ requestAnimationFrame(() => attemptFocus(retries - 1));
882
+ }
883
+ };
884
+ attemptFocus();
885
+ }
886
+ handleDropdownLinkKeyDown(event, container) {
887
+ const links = this.getDropdownLinks(container);
888
+ const currentIndex = links.indexOf(event.currentTarget);
889
+ if (currentIndex === -1)
890
+ return;
891
+ if (event.key === 'ArrowDown') {
892
+ event.preventDefault();
893
+ links[(currentIndex + 1) % links.length]?.focus();
894
+ return;
895
+ }
896
+ if (event.key === 'ArrowUp') {
897
+ event.preventDefault();
898
+ links[(currentIndex - 1 + links.length) % links.length]?.focus();
899
+ return;
900
+ }
901
+ if (event.key === 'Escape') {
902
+ event.preventDefault();
903
+ event.stopPropagation();
904
+ this.closeDropdownAndFocusTrigger(container);
905
+ }
678
906
  }
679
907
  toggleMenu() {
680
908
  if (this.menuOpen) {
681
- this.closeMenu();
909
+ this.closeMenu({ focusToggle: false });
682
910
  return;
683
911
  }
684
912
  this.openMenu();
685
913
  }
686
914
  openMenu() {
687
915
  this.menuOpen = true;
916
+ this.headerHidden = false;
688
917
  this.closeDropdown();
918
+ if (!this.desktopMediaQuery?.matches) {
919
+ this.lockBodyScroll();
920
+ const itemCount = this.getActiveMenu().length;
921
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} opened. ${this.formatItemCount(itemCount)}.`);
922
+ this.focusFirstNavItem();
923
+ }
689
924
  }
690
- closeMenu() {
925
+ closeMenu(options = {}) {
926
+ const { announce = true, focusToggle = false } = options;
927
+ const wasMobileActive = this.isMobileMenuActive();
691
928
  this.menuOpen = false;
692
929
  this.closeDropdown();
930
+ this.unlockBodyScroll();
931
+ this.syncHeaderHeight();
932
+ this.updateHeaderVisibility();
933
+ if (announce && wasMobileActive) {
934
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} closed.`);
935
+ }
936
+ if (focusToggle) {
937
+ this.focusMenuToggle();
938
+ }
693
939
  }
694
940
  getAccountHome() {
695
941
  return normalizeAccountBase(this.accountBaseUrl);
@@ -760,13 +1006,17 @@ const WegHeader = class {
760
1006
  }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => this.handleDropdownTriggerClick(event, item.label), onKeyDown: (event) => {
761
1007
  const container = event.currentTarget.closest('.nav-dropdown');
762
1008
  this.handleDropdownTriggerKeyDown(event, item.label, container);
763
- } }, 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) => {
1009
+ } }, 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) => {
764
1010
  const childActive = this.isNavItemActive(child.href, 'exact');
765
1011
  const ariaCurrent = this.isAriaCurrentHref(child.href, ariaCurrentHref);
766
- return (h("a", { class: {
1012
+ const positionLabel = `${child.label}, link ${index + 1} of ${items.length}`;
1013
+ return (h("li", { key: `${item.label}:${child.label}` }, h("a", { class: {
767
1014
  'nav-dropdown__link': true,
768
1015
  'nav-dropdown__link--active': childActive,
769
- }, href: child.href, key: `${item.label}:${child.label}`, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.() }, child.label));
1016
+ }, href: child.href, "aria-label": positionLabel, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.(), onKeyDown: (event) => {
1017
+ const dropdownContainer = event.currentTarget.closest('.nav-dropdown');
1018
+ this.handleDropdownLinkKeyDown(event, dropdownContainer);
1019
+ } }, child.label)));
770
1020
  }))))) : null)));
771
1021
  }
772
1022
  handleAuthClick(event, action, _href, onNavigate) {
@@ -829,7 +1079,7 @@ const WegHeader = class {
829
1079
  }
830
1080
  renderNav(onNavigate) {
831
1081
  const ariaCurrentHref = this.resolveAriaCurrentHref();
832
- return (h("nav", { class: "main-nav", "aria-label": "Main" }, h("ul", { class: "main-nav__list" }, this.getActiveMenu().map((item) => this.renderNavItem(item, onNavigate, ariaCurrentHref)))));
1082
+ 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)))));
833
1083
  }
834
1084
  renderCompactAuth(onNavigate) {
835
1085
  if (this.signedIn) {
@@ -850,10 +1100,12 @@ const WegHeader = class {
850
1100
  const logoSrc = this.getLogoSrc();
851
1101
  const closeMenu = () => this.closeMenu();
852
1102
  const mobileMenuActive = this.isMobileMenuActive();
853
- return (h("header", { key: '6b932d0c3dc20b53292b61fa3403e26e35182d05', class: {
1103
+ return (h("header", { key: '429d9af0996fa37484ebeea36f1e071fe0ba0789', class: {
854
1104
  header: true,
1105
+ 'header--fixed': !mobileMenuActive,
1106
+ 'header--hidden': this.headerHidden && !mobileMenuActive,
855
1107
  'header--menu-open': mobileMenuActive,
856
- } }, h("div", { key: '426395d6e1245188bc103c8a2f72ccfc3819ba7d', class: "header-inner" }, h("button", { key: 'fe6128407500f5b1a1899aa9e7fdd00222f177b3', 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: 'bb19f9430217686834c4aec615bd7306d276a969', href: logoHref, src: logoSrc }), h("div", { key: 'cebab248840b8412479a90a83c5fc3d0a601476b', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: 'c029db0bcd71a9940d93bef5b32e59625f94b76c', 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)))));
1108
+ } }, 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)))));
857
1109
  }
858
1110
  static get watchers() { return {
859
1111
  "layout": [{
@@ -17,5 +17,5 @@ var patchBrowser = () => {
17
17
 
18
18
  patchBrowser().then(async (options) => {
19
19
  await globalScripts();
20
- return bootstrapLazy([["weg-footer_2",[[513,"weg-footer",{"layout":[1],"resolved":[32]},null,{"layout":[{"watchLayout":0}]}],[513,"weg-header",{"layout":[1],"signedIn":[516,"signed-in"],"userName":[1,"user-name"],"accountBaseUrl":[1,"account-base-url"],"currentPath":[1,"current-path"],"resolved":[32],"menuOpen":[32],"openDropdown":[32]},[[8,"keydown","handleKeyDown"]],{"layout":[{"watchLayout":0}]}]]]], options);
20
+ return bootstrapLazy([["weg-footer_2",[[513,"weg-footer",{"layout":[1],"resolved":[32]},null,{"layout":[{"watchLayout":0}]}],[513,"weg-header",{"layout":[1],"signedIn":[516,"signed-in"],"userName":[1,"user-name"],"accountBaseUrl":[1,"account-base-url"],"currentPath":[1,"current-path"],"resolved":[32],"menuOpen":[32],"openDropdown":[32],"headerHidden":[32]},[[8,"keydown","handleKeyDown"]],{"layout":[{"watchLayout":0}]}]]]], options);
21
21
  });
@@ -47,17 +47,28 @@ export declare class WegHeader {
47
47
  private resolved;
48
48
  private menuOpen;
49
49
  private openDropdown;
50
+ private headerHidden;
50
51
  private boundHandleDocumentClick;
51
52
  private boundHandleViewportChange;
52
53
  private boundHandleHoverCapabilityChange;
54
+ private boundHandleScroll;
55
+ private headerResizeObserver?;
56
+ private lastScrollY;
57
+ private scrollTicking;
58
+ private bodyScrollLocked;
59
+ private bodyScrollLockY;
53
60
  private hoverCloseTimer;
54
61
  private hoverOpenEnabled;
55
62
  private desktopMediaQuery?;
56
63
  private hoverCapableMediaQuery?;
57
64
  private resolve;
58
65
  componentWillLoad(): void;
66
+ componentDidLoad(): void;
59
67
  connectedCallback(): void;
60
68
  disconnectedCallback(): void;
69
+ private syncHeaderHeight;
70
+ private handleScroll;
71
+ private updateHeaderVisibility;
61
72
  /**
62
73
  * Desktop dropdowns open on hover when viewport is md+ and the device reports hover support.
63
74
  * Uses (hover: hover) only — not (pointer: fine), which breaks in DevTools and hybrid devices.
@@ -65,9 +76,21 @@ export declare class WegHeader {
65
76
  private updateHoverDropdownEnabled;
66
77
  private handleHoverCapabilityChange;
67
78
  private handleViewportChange;
79
+ private announceToScreenReader;
80
+ private getMenuToggleButton;
81
+ private getFirstNavFocusable;
82
+ private focusFirstNavItem;
83
+ private focusMenuToggle;
84
+ private getMenuGroupLinkCount;
85
+ private formatLinkCount;
86
+ private formatItemCount;
87
+ private announceSubmenuExpanded;
88
+ private announceSubmenuCollapsed;
68
89
  private isMobileMenuActive;
69
90
  protected watchLayout(): void;
70
91
  handleKeyDown(event: KeyboardEvent): void;
92
+ private lockBodyScroll;
93
+ private unlockBodyScroll;
71
94
  private handleDocumentClick;
72
95
  private cancelScheduledDropdownClose;
73
96
  private openDropdownMenu;
@@ -82,7 +105,12 @@ export declare class WegHeader {
82
105
  private handleDropdownTriggerClick;
83
106
  private handleDropdownFocusOut;
84
107
  private handleDropdownTriggerKeyDown;
108
+ private getDropdownLinks;
109
+ private focusDropdownLink;
85
110
  private focusFirstDropdownLink;
111
+ private getDropdownContainer;
112
+ private closeDropdownAndFocusTrigger;
113
+ private handleDropdownLinkKeyDown;
86
114
  private toggleMenu;
87
115
  private openMenu;
88
116
  private closeMenu;