weg-shared-layout 0.0.41 → 0.0.44

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,28 @@ 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}.weg-header-shell{display:block}.skip-to-content{position:fixed;top:-100%;left:0;z-index:10001;padding:0.75rem 1rem;background:#ffffff;color:#0f172a;font-family:var(--weg-font-family);font-weight:600;text-decoration:none;border:2px solid #312e81;border-radius:0 0 0.25rem 0;box-shadow:0 0.25rem 0.75rem rgba(17, 24, 39, 0.15)}.skip-to-content:focus,.skip-to-content:focus-visible{top:0;outline:2px solid var(--weg-color-accent);outline-offset:2px}.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';
391
+ const SKIP_TO_CONTENT_LABEL = 'Skip to main content';
392
+ const SKIP_TO_CONTENT_TARGET_ID = 'main-content';
393
+ function focusScrollTarget(element) {
394
+ if (!element.hasAttribute('tabindex')) {
395
+ element.setAttribute('tabindex', '-1');
396
+ }
397
+ element.focus({ preventScroll: true });
398
+ element.scrollIntoView();
399
+ }
385
400
  function normalizeAccountBase(input) {
386
401
  const base = input?.trim() || DEFAULT_ACCOUNT_HOME;
387
402
  return base.replace(/\/$/, '');
@@ -517,9 +532,16 @@ const WegHeader = class {
517
532
  resolved = EMPTY_HEADER;
518
533
  menuOpen = false;
519
534
  openDropdown = null;
535
+ headerHidden = false;
520
536
  boundHandleDocumentClick = this.handleDocumentClick.bind(this);
521
537
  boundHandleViewportChange = this.handleViewportChange.bind(this);
522
538
  boundHandleHoverCapabilityChange = this.handleHoverCapabilityChange.bind(this);
539
+ boundHandleScroll = this.handleScroll.bind(this);
540
+ headerResizeObserver;
541
+ lastScrollY = 0;
542
+ scrollTicking = false;
543
+ bodyScrollLocked = false;
544
+ bodyScrollLockY = 0;
523
545
  hoverCloseTimer = null;
524
546
  hoverOpenEnabled = false;
525
547
  desktopMediaQuery;
@@ -540,15 +562,69 @@ const WegHeader = class {
540
562
  this.updateHoverDropdownEnabled();
541
563
  this.handleViewportChange();
542
564
  }
565
+ componentDidLoad() {
566
+ const header = this.el.shadowRoot?.querySelector('.header');
567
+ if (!header)
568
+ return;
569
+ this.headerResizeObserver = new ResizeObserver(() => this.syncHeaderHeight());
570
+ this.headerResizeObserver.observe(header);
571
+ this.syncHeaderHeight();
572
+ this.lastScrollY = window.scrollY;
573
+ this.updateHeaderVisibility();
574
+ }
543
575
  connectedCallback() {
544
576
  document.addEventListener('click', this.boundHandleDocumentClick);
577
+ window.addEventListener('scroll', this.boundHandleScroll, { passive: true });
545
578
  }
546
579
  disconnectedCallback() {
547
580
  document.removeEventListener('click', this.boundHandleDocumentClick);
581
+ window.removeEventListener('scroll', this.boundHandleScroll);
582
+ this.unlockBodyScroll();
583
+ this.headerResizeObserver?.disconnect();
548
584
  this.desktopMediaQuery?.removeEventListener('change', this.boundHandleViewportChange);
549
585
  this.hoverCapableMediaQuery?.removeEventListener('change', this.boundHandleHoverCapabilityChange);
550
586
  this.cancelScheduledDropdownClose();
551
587
  }
588
+ syncHeaderHeight() {
589
+ if (this.isMobileMenuActive())
590
+ return;
591
+ const header = this.el.shadowRoot?.querySelector('.header');
592
+ if (!header)
593
+ return;
594
+ this.el.style.setProperty('--weg-header-height', `${header.offsetHeight}px`);
595
+ }
596
+ handleScroll() {
597
+ if (this.scrollTicking)
598
+ return;
599
+ this.scrollTicking = true;
600
+ requestAnimationFrame(() => {
601
+ this.scrollTicking = false;
602
+ this.updateHeaderVisibility();
603
+ });
604
+ }
605
+ updateHeaderVisibility() {
606
+ if (this.isMobileMenuActive()) {
607
+ if (this.headerHidden)
608
+ this.headerHidden = false;
609
+ return;
610
+ }
611
+ const scrollY = window.scrollY;
612
+ const delta = scrollY - this.lastScrollY;
613
+ const threshold = window.innerHeight * HEADER_SCROLL_HIDE_THRESHOLD_VH;
614
+ let nextHidden = this.headerHidden;
615
+ if (scrollY <= threshold) {
616
+ nextHidden = false;
617
+ }
618
+ else if (Math.abs(delta) >= HEADER_SCROLL_DIRECTION_DELTA) {
619
+ nextHidden = delta > 0;
620
+ }
621
+ this.lastScrollY = scrollY;
622
+ if (nextHidden === this.headerHidden)
623
+ return;
624
+ if (nextHidden)
625
+ this.closeDropdown();
626
+ this.headerHidden = nextHidden;
627
+ }
552
628
  /**
553
629
  * Desktop dropdowns open on hover when viewport is md+ and the device reports hover support.
554
630
  * Uses (hover: hover) only — not (pointer: fine), which breaks in DevTools and hybrid devices.
@@ -566,10 +642,74 @@ const WegHeader = class {
566
642
  }
567
643
  handleViewportChange() {
568
644
  if (this.desktopMediaQuery?.matches) {
569
- this.closeMenu();
645
+ this.closeMenu({ announce: false, focusToggle: false });
570
646
  }
571
647
  this.updateHoverDropdownEnabled();
572
648
  }
649
+ announceToScreenReader(message) {
650
+ const region = this.el.shadowRoot?.querySelector('[data-weg-sr-live]');
651
+ if (!region)
652
+ return;
653
+ region.textContent = '';
654
+ requestAnimationFrame(() => {
655
+ region.textContent = message;
656
+ });
657
+ }
658
+ getMenuToggleButton() {
659
+ return this.el.shadowRoot?.querySelector('.menu-toggle');
660
+ }
661
+ getFirstNavFocusable() {
662
+ const panel = this.el.shadowRoot?.querySelector('#weg-header-main-nav');
663
+ if (!panel)
664
+ return null;
665
+ const focusables = panel.querySelectorAll('a[href], button:not([disabled])');
666
+ return focusables[0] ?? null;
667
+ }
668
+ focusFirstNavItem() {
669
+ setTimeout(() => {
670
+ const attemptFocus = (retries = 8) => {
671
+ const target = this.getFirstNavFocusable();
672
+ if (target) {
673
+ target.focus();
674
+ return;
675
+ }
676
+ if (retries > 0) {
677
+ requestAnimationFrame(() => attemptFocus(retries - 1));
678
+ }
679
+ };
680
+ attemptFocus();
681
+ }, 0);
682
+ }
683
+ focusMenuToggle() {
684
+ const attemptFocus = (retries = 8) => {
685
+ const toggle = this.getMenuToggleButton();
686
+ if (toggle) {
687
+ toggle.focus();
688
+ return;
689
+ }
690
+ if (retries > 0) {
691
+ requestAnimationFrame(() => attemptFocus(retries - 1));
692
+ }
693
+ };
694
+ attemptFocus();
695
+ }
696
+ getMenuGroupLinkCount(label) {
697
+ const group = this.getActiveMenu().find((item) => isMenuGroup(item) && item.label === label);
698
+ return group?.items?.length ?? 0;
699
+ }
700
+ formatLinkCount(count) {
701
+ return count === 1 ? '1 link' : `${count} links`;
702
+ }
703
+ formatItemCount(count) {
704
+ return count === 1 ? '1 item' : `${count} items`;
705
+ }
706
+ announceSubmenuExpanded(label) {
707
+ const linkCount = this.getMenuGroupLinkCount(label);
708
+ this.announceToScreenReader(`${label} submenu expanded. ${this.formatLinkCount(linkCount)}.`);
709
+ }
710
+ announceSubmenuCollapsed(label) {
711
+ this.announceToScreenReader(`${label} submenu collapsed.`);
712
+ }
573
713
  isMobileMenuActive() {
574
714
  return this.menuOpen && !this.desktopMediaQuery?.matches;
575
715
  }
@@ -579,8 +719,39 @@ const WegHeader = class {
579
719
  handleKeyDown(event) {
580
720
  if (event.key !== 'Escape')
581
721
  return;
582
- this.menuOpen = false;
583
- this.closeDropdown();
722
+ if (this.openDropdown) {
723
+ event.preventDefault();
724
+ this.closeDropdownAndFocusTrigger(this.openDropdown);
725
+ return;
726
+ }
727
+ if (this.menuOpen) {
728
+ event.preventDefault();
729
+ this.closeMenu({ focusToggle: true });
730
+ }
731
+ }
732
+ lockBodyScroll() {
733
+ if (this.bodyScrollLocked)
734
+ return;
735
+ this.bodyScrollLockY = window.scrollY;
736
+ document.body.style.overflow = 'hidden';
737
+ document.body.style.position = 'fixed';
738
+ document.body.style.top = `-${this.bodyScrollLockY}px`;
739
+ document.body.style.left = '0';
740
+ document.body.style.right = '0';
741
+ document.body.style.width = '100%';
742
+ this.bodyScrollLocked = true;
743
+ }
744
+ unlockBodyScroll() {
745
+ if (!this.bodyScrollLocked)
746
+ return;
747
+ document.body.style.overflow = '';
748
+ document.body.style.position = '';
749
+ document.body.style.top = '';
750
+ document.body.style.left = '';
751
+ document.body.style.right = '';
752
+ document.body.style.width = '';
753
+ window.scrollTo(0, this.bodyScrollLockY);
754
+ this.bodyScrollLocked = false;
584
755
  }
585
756
  handleDocumentClick(event) {
586
757
  const target = event.target;
@@ -611,10 +782,12 @@ const WegHeader = class {
611
782
  }
612
783
  toggleDropdown(label) {
613
784
  if (this.openDropdown === label) {
785
+ this.announceSubmenuCollapsed(label);
614
786
  this.closeDropdown();
615
787
  return;
616
788
  }
617
789
  this.openDropdownMenu(label);
790
+ this.announceSubmenuExpanded(label);
618
791
  }
619
792
  getDropdownPanelId(label) {
620
793
  return `weg-header-dropdown-${label.trim().toLowerCase().replace(/\s+/g, '-')}`;
@@ -667,29 +840,111 @@ const WegHeader = class {
667
840
  if (event.key !== 'ArrowDown')
668
841
  return;
669
842
  event.preventDefault();
670
- this.openDropdownMenu(label);
843
+ if (this.openDropdown !== label) {
844
+ this.openDropdownMenu(label);
845
+ this.announceSubmenuExpanded(label);
846
+ }
671
847
  this.focusFirstDropdownLink(itemEl);
672
848
  }
849
+ getDropdownLinks(container) {
850
+ return Array.from(container.querySelectorAll('.nav-dropdown__link'));
851
+ }
852
+ focusDropdownLink(container, index) {
853
+ const attemptFocus = (retries = 8) => {
854
+ const links = this.getDropdownLinks(container);
855
+ if (links[index]) {
856
+ links[index].focus();
857
+ return;
858
+ }
859
+ if (retries > 0) {
860
+ requestAnimationFrame(() => attemptFocus(retries - 1));
861
+ }
862
+ };
863
+ attemptFocus();
864
+ }
673
865
  focusFirstDropdownLink(itemEl) {
674
- setTimeout(() => {
675
- const firstLink = itemEl.querySelector('.nav-dropdown__link');
676
- firstLink?.focus();
677
- }, 0);
866
+ this.focusDropdownLink(itemEl, 0);
867
+ }
868
+ getDropdownContainer(label) {
869
+ const panelId = this.getDropdownPanelId(label);
870
+ return this.el.shadowRoot?.querySelector(`#${panelId}-trigger`)?.closest('.nav-dropdown') ?? null;
871
+ }
872
+ closeDropdownAndFocusTrigger(labelOrContainer) {
873
+ const container = typeof labelOrContainer === 'string'
874
+ ? this.getDropdownContainer(labelOrContainer)
875
+ : labelOrContainer;
876
+ const trigger = container?.querySelector('.nav-dropdown__trigger');
877
+ const label = typeof labelOrContainer === 'string'
878
+ ? labelOrContainer
879
+ : (trigger?.querySelector('.nav-dropdown__label')?.textContent?.trim() ?? '');
880
+ if (label && this.openDropdown === label) {
881
+ this.announceSubmenuCollapsed(label);
882
+ }
883
+ this.closeDropdown();
884
+ const attemptFocus = (retries = 8) => {
885
+ if (trigger && this.el.shadowRoot?.contains(trigger)) {
886
+ trigger.focus();
887
+ return;
888
+ }
889
+ if (retries > 0) {
890
+ requestAnimationFrame(() => attemptFocus(retries - 1));
891
+ }
892
+ };
893
+ attemptFocus();
894
+ }
895
+ handleDropdownLinkKeyDown(event, container) {
896
+ const links = this.getDropdownLinks(container);
897
+ const currentIndex = links.indexOf(event.currentTarget);
898
+ if (currentIndex === -1)
899
+ return;
900
+ if (event.key === 'ArrowDown') {
901
+ event.preventDefault();
902
+ links[(currentIndex + 1) % links.length]?.focus();
903
+ return;
904
+ }
905
+ if (event.key === 'ArrowUp') {
906
+ event.preventDefault();
907
+ links[(currentIndex - 1 + links.length) % links.length]?.focus();
908
+ return;
909
+ }
910
+ if (event.key === 'Escape') {
911
+ event.preventDefault();
912
+ event.stopPropagation();
913
+ this.closeDropdownAndFocusTrigger(container);
914
+ }
678
915
  }
679
916
  toggleMenu() {
680
917
  if (this.menuOpen) {
681
- this.closeMenu();
918
+ this.closeMenu({ focusToggle: false });
682
919
  return;
683
920
  }
684
921
  this.openMenu();
685
922
  }
686
923
  openMenu() {
687
924
  this.menuOpen = true;
925
+ this.headerHidden = false;
688
926
  this.closeDropdown();
927
+ if (!this.desktopMediaQuery?.matches) {
928
+ this.lockBodyScroll();
929
+ const itemCount = this.getActiveMenu().length;
930
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} opened. ${this.formatItemCount(itemCount)}.`);
931
+ this.focusFirstNavItem();
932
+ }
689
933
  }
690
- closeMenu() {
934
+ closeMenu(options = {}) {
935
+ const { announce = true, focusToggle = false } = options;
936
+ const wasMobileActive = this.isMobileMenuActive();
691
937
  this.menuOpen = false;
692
938
  this.closeDropdown();
939
+ this.unlockBodyScroll();
940
+ this.syncHeaderHeight();
941
+ this.updateHeaderVisibility();
942
+ if (announce && wasMobileActive) {
943
+ this.announceToScreenReader(`${MAIN_NAV_LABEL} closed.`);
944
+ }
945
+ if (focusToggle) {
946
+ this.focusMenuToggle();
947
+ }
693
948
  }
694
949
  getAccountHome() {
695
950
  return normalizeAccountBase(this.accountBaseUrl);
@@ -760,13 +1015,17 @@ const WegHeader = class {
760
1015
  }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => this.handleDropdownTriggerClick(event, item.label), onKeyDown: (event) => {
761
1016
  const container = event.currentTarget.closest('.nav-dropdown');
762
1017
  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) => {
1018
+ } }, 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
1019
  const childActive = this.isNavItemActive(child.href, 'exact');
765
1020
  const ariaCurrent = this.isAriaCurrentHref(child.href, ariaCurrentHref);
766
- return (h("a", { class: {
1021
+ const positionLabel = `${child.label}, link ${index + 1} of ${items.length}`;
1022
+ return (h("li", { key: `${item.label}:${child.label}` }, h("a", { class: {
767
1023
  'nav-dropdown__link': true,
768
1024
  'nav-dropdown__link--active': childActive,
769
- }, href: child.href, key: `${item.label}:${child.label}`, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.() }, child.label));
1025
+ }, href: child.href, "aria-label": positionLabel, "aria-current": ariaCurrent ? 'page' : undefined, onClick: () => onNavigate?.(), onKeyDown: (event) => {
1026
+ const dropdownContainer = event.currentTarget.closest('.nav-dropdown');
1027
+ this.handleDropdownLinkKeyDown(event, dropdownContainer);
1028
+ } }, child.label)));
770
1029
  }))))) : null)));
771
1030
  }
772
1031
  handleAuthClick(event, action, _href, onNavigate) {
@@ -829,7 +1088,7 @@ const WegHeader = class {
829
1088
  }
830
1089
  renderNav(onNavigate) {
831
1090
  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)))));
1091
+ 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
1092
  }
834
1093
  renderCompactAuth(onNavigate) {
835
1094
  if (this.signedIn) {
@@ -845,15 +1104,28 @@ const WegHeader = class {
845
1104
  onNavigate,
846
1105
  });
847
1106
  }
1107
+ handleSkipToContent(event) {
1108
+ event.preventDefault();
1109
+ const mainContent = document.querySelector('main') ?? document.getElementById(SKIP_TO_CONTENT_TARGET_ID);
1110
+ if (mainContent instanceof HTMLElement) {
1111
+ focusScrollTarget(mainContent);
1112
+ }
1113
+ const headings = document.querySelectorAll('h1');
1114
+ if (headings.length > 0 && headings[0] instanceof HTMLElement) {
1115
+ focusScrollTarget(headings[0]);
1116
+ }
1117
+ }
848
1118
  render() {
849
1119
  const logoHref = this.getLogoHref();
850
1120
  const logoSrc = this.getLogoSrc();
851
1121
  const closeMenu = () => this.closeMenu();
852
1122
  const mobileMenuActive = this.isMobileMenuActive();
853
- return (h("header", { key: '6b932d0c3dc20b53292b61fa3403e26e35182d05', class: {
1123
+ return (h("div", { key: 'a769ab7a466cd90019570b8c3cf8e669b3df62c6', class: "weg-header-shell" }, h("a", { key: '43b11ec58387350df5ccf53c5a97cb94296357e5', class: "skip-to-content", href: `#${SKIP_TO_CONTENT_TARGET_ID}`, title: SKIP_TO_CONTENT_LABEL, onClick: (event) => this.handleSkipToContent(event) }, SKIP_TO_CONTENT_LABEL), h("header", { key: '84d893626e535525b235adc5997bac474d785732', class: {
854
1124
  header: true,
1125
+ 'header--fixed': !mobileMenuActive,
1126
+ 'header--hidden': this.headerHidden && !mobileMenuActive,
855
1127
  '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)))));
1128
+ } }, h("div", { key: '4cfaaf18575bf56d9c09ab6fe0db4383cc9cee94', class: "header-inner" }, h("div", { key: 'b9e2d18284db956c944a4e0669a6927586b6ee32', class: "sr-only", "aria-live": "polite", "aria-atomic": "true", "data-weg-sr-live": true }), h("button", { key: 'f019b74b76c22311c97ee3fc539f396e24ec98fd', 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: 'fe6d460021d059287b487fafb96be2a79a3253c8', href: logoHref, src: logoSrc }), h("div", { key: '2e59fe4e86e183aa7ad41402baba2ff6467967c1', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: '90c8861c45b344862c03a3e2d5c2b6b6f37a6174', 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
1129
  }
858
1130
  static get watchers() { return {
859
1131
  "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;
@@ -105,5 +133,6 @@ export declare class WegHeader {
105
133
  private renderNavItem;
106
134
  private renderNav;
107
135
  private renderCompactAuth;
136
+ private handleSkipToContent;
108
137
  render(): any;
109
138
  }