ng-hub-ui-nav 22.11.2 → 22.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  A flexible, accessible, and highly customizable navigation component for Angular 21+. It supports horizontal menus, vertical sidebars, mobile collapse modes, stacked drill-down panels, projected start/end slots, and scroll-spy integration.
9
9
 
10
10
  > [!IMPORTANT]
11
- > Version `21.1.1` targets Angular 21 and follows the signal-first architecture used across `ng-hub-ui`.
11
+ > Version `22.12.0` targets Angular 21 and follows the signal-first architecture used across `ng-hub-ui`.
12
12
 
13
13
  ## Documentation and Live Examples
14
14
 
@@ -69,8 +69,8 @@ This library is part of the **ng-hub-ui** ecosystem:
69
69
  - Scroll-spy helpers for documentation pages and one-page layouts.
70
70
  - Sticky vertical navigation support.
71
71
  - **Desktop icon rail** — the two-way `rail` input collapses a vertical nav to `--hub-nav-rail-width` (4rem) showing icons only: labels surface as tooltips, accordion groups open as click-triggered overlay flyouts, and the offcanvas behavior still wins below `collapseBreakpoint`. A built-in edge toggle ships by default (`config.railToggle: false` to bring your own) and is fully themeable through `--hub-nav-rail-toggle-*`, including a replaceable SVG arrow. The library persists nothing; `railChange` lets the app store the preference.
72
- - **Tooltip on truncated labels** — long item labels are clipped with an ellipsis (the standard sidebar behaviour) and reveal their full text on hover, automatically and only when truncated (via `ng-hub-ui-utils`' `[hubOverflowTooltip]`). The tooltip is **agnostic** — it defaults to the hub-ui tooltip but is swappable with `provideHubTooltip(...)`. Requires `ng-hub-ui-utils >= 22.8.1` (the library's peer floor) and the tooltip styles (`@use 'ng-hub-ui-utils/styles/tooltip';`). Tip: control the sidebar width with `--hub-nav-panel-width`.
73
- - Semantic `variant` accent system (`primary` / `success` / `danger` / `warning` / `info`, plus any custom accent) recolouring the hover/active affordances — mirrors `<hub-panels>`.
72
+ - **Tooltip on truncated labels** — long item labels are clipped with an ellipsis (the standard sidebar behaviour) and reveal their full text on hover, automatically and only when truncated (via `ng-hub-ui-utils`' `[hubOverflowTooltip]`). The tooltip is **agnostic** — it defaults to the hub-ui tooltip but is swappable with `provideHubTooltip(...)`. Requires `ng-hub-ui-utils >= 22.13.0` (the library's peer floor) and the tooltip styles (`@use 'ng-hub-ui-utils/styles/tooltip';`). Tip: control the sidebar width with `--hub-nav-panel-width`.
73
+ - Semantic `color` accent system (`primary` / `success` / `danger` / `warning` / `info`, plus any custom accent or literal colour) recolouring the hover/active affordances — mirrors `<hub-panels>`.
74
74
  - Full CSS variable theming via `--hub-nav-*` tokens.
75
75
 
76
76
  ## Installation
@@ -187,9 +187,9 @@ A toggle button ships on the outer edge of the primary column: an arrow inside a
187
187
  | `config` | `Partial<HubNavConfig>` | `{}` | Per-instance config merged with global defaults. |
188
188
  | `navClass` | `string` | `''` | Additional class applied to the internal `<nav>`. |
189
189
  | `itemTemplate` | `TemplateRef<unknown> \| null` | `null` | Optional custom item template. |
190
- | `autoOpenFromRoute` | `boolean` | `false` | Opens matching dropdowns/panels from the current route. It also re-derives the stack when the viewport comes back above `collapseBreakpoint`; with the input off, a stack opened by hand survives that round trip. |
190
+ | `autoOpenFromRoute` | `boolean` | `false` | Opens matching dropdowns/panels from the current route, and leaves exactly one section open when roots expand differently: arriving at an accordion root drops the panel of the panel root you left, and the other way round. It also re-derives the stack when the viewport comes back above `collapseBreakpoint`; with the input off, a stack opened by hand survives that round trip. |
191
191
  | `rail` | `boolean` (two-way `model`) | `false` | Desktop-only icon rail for vertical navs. Ignored below `collapseBreakpoint`. Bind with `[(rail)]`. |
192
- | `variant` | `'primary' \| 'success' \| 'danger' \| 'warning' \| 'info' \| string` | `'primary'` | Semantic accent for the hover/active affordances. Built-in values render with the design-system tints; any custom string resolves through `--hub-sys-color-<variant>`. |
192
+ | `color` | `'primary' \| 'success' \| 'danger' \| 'warning' \| 'info' \| string \| undefined` | `undefined` (reads as `primary`) | Semantic accent for the hover/active affordances. A bareword — semantic name, registered accent or CSS named colour — resolves through `--hub-sys-color-<name>`; a literal `#hex` / `rgb()` / `oklch()` / `var()` is passed through unchanged. |
193
193
 
194
194
  #### Outputs
195
195
 
@@ -219,11 +219,20 @@ interface HubNavConfig {
219
219
  sidebarSide: 'left' | 'right';
220
220
  panelWidth: string;
221
221
  dropdownRenderMode: 'inline' | 'overlay';
222
+ railToggle?: boolean;
223
+ activeIndicator?: boolean;
224
+ followReplacedUrls?: boolean | number;
222
225
  labels?: Partial<HubNavLabels>;
223
226
  }
224
227
  ```
225
228
 
226
- `labels` overrides the built-in accessible strings (`toggleNavigation`, `closeNavigation`, `goBack`, `closePanel`, `toggleSection` — the latter supports a `{label}` placeholder) per instance. Without an override, each label resolves from the shared `HUBUI.NAV.*` dictionary keys (`provideHubTranslationAdapter()` in `ng-hub-ui-utils`) and finally falls back to English.
229
+ `railToggle` (default `true`) draws the built-in rail toggle on the outer edge of a vertical desktop nav; set it to `false` to supply your own control.
230
+
231
+ `activeIndicator` (default `false`) moves the active mark to a single element shared by the list so it travels between siblings instead of appearing in place. It is opt-in because the mark stops being painted by each item, so a rule targeting `.hub-nav-item__link--active` no longer applies. It honours `prefers-reduced-motion`.
232
+
233
+ `followReplacedUrls` (default `true`) decides how eagerly the nav follows a URL that was **replaced** rather than pushed — which is what a scroll spy does while the reader scrolls. `true` follows every report; a **number** follows only once the reports have been quiet for that many milliseconds, so the mark lands where the reader stopped instead of walking down the menu; `false` never follows, marking only where the reader chose to go. Deep links are unaffected either way.
234
+
235
+ `labels` overrides the built-in accessible strings (`toggleNavigation`, `closeNavigation`, `collapseNavigation`, `expandNavigation`, `goBack`, `closePanel`, `toggleSection` — the last one supports a `{label}` placeholder) per instance. Without an override, each label resolves from the shared `HUBUI.NAV.*` dictionary keys (`provideHubTranslationAdapter()` in `ng-hub-ui-utils`) and finally falls back to English.
227
236
 
228
237
 
229
238
  ### `HubNavItem`
@@ -285,7 +294,7 @@ The component exposes a complete set of `--hub-nav-*` tokens. See the full refer
285
294
 
286
295
  ### Recolour the whole nav from a single accent
287
296
 
288
- The hover/active affordances and the nav surface all derive from one accent hook. Set it (or use the `variant` input) to re-theme the entire nav:
297
+ The hover/active affordances and the nav surface all derive from one accent hook. Set it (or use the `color` input) to re-theme the entire nav:
289
298
 
290
299
  ```css
291
300
  .my-sidebar {
@@ -396,8 +396,17 @@ class HubNavStateService {
396
396
  this.openDropdown(id);
397
397
  return true;
398
398
  }
399
- /** Closes all open dropdowns. */
399
+ /**
400
+ * Closes all open dropdowns.
401
+ *
402
+ * Writing an empty set over an empty set is not free: the signal has no value equality, so
403
+ * every reader re-runs. Route synchronisation calls this on each navigation, and this nav
404
+ * has already been observed generating state churn by the million in a single SSR pass.
405
+ */
400
406
  closeAllDropdowns() {
407
+ if (this._openDropdowns().size === 0) {
408
+ return;
409
+ }
401
410
  this._openDropdowns.set(new Set());
402
411
  }
403
412
  /**
@@ -519,6 +528,12 @@ class HubNavStateService {
519
528
  /**
520
529
  * Finds the ordered list of dropdown ancestor IDs for the active route.
521
530
  *
531
+ * The walk commits to the first branch it accepts, so which item it accepts has to be the
532
+ * most specific one at each level, not merely the first that matches: a menu whose opening
533
+ * entry is parked on the language prefix (`/en/`) matches every URL in the site, and being
534
+ * childless it ended the walk with an empty trail — every section stayed shut, whichever
535
+ * page you were on.
536
+ *
522
537
  * @param items - Items to search recursively.
523
538
  * @param activeRoute - Current router URL.
524
539
  * @param trail - Accumulated ancestor trail.
@@ -526,7 +541,7 @@ class HubNavStateService {
526
541
  */
527
542
  findActiveDropdownTrail(items, activeRoute, trail = []) {
528
543
  for (const item of items) {
529
- if (!this.isItemOrDescendantActive(item, activeRoute)) {
544
+ if (!this.isItemActiveAmongSiblings(item, items, activeRoute)) {
530
545
  continue;
531
546
  }
532
547
  const nextTrail = item.children?.length ? [...trail, item.id] : trail;
@@ -557,8 +572,11 @@ class HubNavStateService {
557
572
  closePanelsFromIndex(index) {
558
573
  this._panelStack.update((stack) => stack.slice(0, index));
559
574
  }
560
- /** Closes all open panels. */
575
+ /** Closes all open panels. Skips the write when the stack is already empty, as {@link closeAllDropdowns} does. */
561
576
  closeAllPanels() {
577
+ if (this._panelStack().length === 0) {
578
+ return;
579
+ }
562
580
  this._panelStack.set([]);
563
581
  }
564
582
  /**
@@ -849,8 +867,14 @@ class HubNavScrollSpySectionDirective {
849
867
  /** Optional section id. When omitted, host `id` is used. */
850
868
  sectionId = input(null, { ...(ngDevMode ? { debugName: "sectionId" } : /* istanbul ignore next */ {}), alias: 'hubNavScrollSpySection' });
851
869
  el = inject((ElementRef));
852
- /** Effective section id exposed to the container directive. */
853
- resolvedSectionId = computed(() => this.sectionId() ?? this.el.nativeElement.id ?? null, /* @ts-ignore */
870
+ /**
871
+ * Effective section id exposed to the container directive.
872
+ *
873
+ * Falsy rather than nullish on purpose: the documented bare form, `<section id="x"
874
+ * hubNavScrollSpySection>`, reaches the input as the empty string, which `??` would keep —
875
+ * stripping the host marker attribute and leaving the container with nothing to observe.
876
+ */
877
+ resolvedSectionId = computed(() => this.sectionId() || this.el.nativeElement.id || null, /* @ts-ignore */
854
878
  ...(ngDevMode ? [{ debugName: "resolvedSectionId" }] : /* istanbul ignore next */ []));
855
879
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubNavScrollSpySectionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
856
880
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubNavScrollSpySectionDirective, isStandalone: true, selector: "[hubNavScrollSpySection]", inputs: { sectionId: { classPropertyName: "sectionId", publicName: "hubNavScrollSpySection", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-hub-nav-scroll-spy-section": "resolvedSectionId() || null" } }, ngImport: i0 });
@@ -1325,7 +1349,7 @@ class HubNavItemComponent {
1325
1349
  this.toggleDropdown.emit(item);
1326
1350
  }
1327
1351
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubNavItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1328
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubNavItemComponent, isStandalone: true, selector: "hub-nav-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isExpanded: { classPropertyName: "isExpanded", publicName: "isExpanded", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, forceAccordionMode: { classPropertyName: "forceAccordionMode", publicName: "forceAccordionMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", toggleDropdown: "toggleDropdown" }, host: { attributes: { "role": "none" }, properties: { "class.hub-nav-item--active": "isActive()", "class.hub-nav-item--disabled": "item().disabled", "class.hub-nav-item--has-children": "hasChildren()", "class.hub-nav-item--expanded": "isExpanded()", "class.hub-nav-item--header": "isHeader()", "attr.data-item-id": "item().id" }, classAttribute: "hub-nav-item" }, ngImport: i0, template: "@if (itemTemplate()) {\n\t<!-- Custom template rendering -->\n\t<ng-container [ngTemplateOutlet]=\"itemTemplate()!\" [ngTemplateOutletContext]=\"templateContext()\" />\n} @else if (isHeader()) {\n\t<!-- Header item: non-interactive section label -->\n\t<span class=\"hub-nav-item__header\" role=\"presentation\">{{ item().label }}</span>\n} @else if (isLink() && resolvedRoute()) {\n\t<!-- Link item: routable anchor -->\n\t<a\n\t\tclass=\"hub-nav-item__link\"\n\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\trole=\"menuitem\"\n\t\t[routerLink]=\"resolvedRoute()\"\n\t\t[queryParams]=\"item().queryParams\"\n\t\t[fragment]=\"item().fragment\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[attr.aria-current]=\"isActive() ? 'page' : null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t</a>\n} @else if ((isDropdown() || hasChildren()) && resolvedRoute()) {\n\t<!-- Routable dropdown: link area navigates, caret area toggles submenu -->\n\t<div class=\"hub-nav-item__compound\">\n\t\t<a\n\t\t\tclass=\"hub-nav-item__link\"\n\t\t\t[class.hub-nav-item__link--split]=\"showCaret()\"\n\t\t\trole=\"menuitem\"\n\t\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\t\t[routerLink]=\"resolvedRoute()\"\n\t\t\t[queryParams]=\"item().queryParams\"\n\t\t\t[fragment]=\"item().fragment\"\n\t\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t\t[attr.aria-current]=\"isActive() ? 'page' : null\"\n\t\t\t[attr.aria-expanded]=\"showCaret() ? null : isExpanded()\"\n\t\t\t[attr.aria-haspopup]=\"showCaret() ? null : true\"\n\t\t\t[tooltip]=\"railTooltip()\"\n\t\t\t[placement]=\"railTooltipPlacement()\"\n\t\t\t(click)=\"onRouteItemClick($event)\"\n\t\t>\n\t\t\t@if (item().icon) {\n\t\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t\t} @else if (railInitial()) {\n\t\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t\t}\n\t\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t\t@if (item().badge) {\n\t\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t\t}\n\t\t</a>\n\n\t\t@if (showCaret()) {\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"hub-nav-item__caret-button\"\n\t\t\t\t[class.hub-nav-item__caret-button--active]=\"isActive()\"\n\t\t\t\t[class.hub-nav-item__caret-button--expanded]=\"isExpanded()\"\n\t\t\t\t[class.hub-nav-item__caret-button--disabled]=\"item().disabled\"\n\t\t\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t\t\t[attr.aria-label]=\"caretAriaLabel()\"\n\t\t\t\t[attr.aria-expanded]=\"isExpanded()\"\n\t\t\t\t[attr.aria-haspopup]=\"true\"\n\t\t\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t\t\t(click)=\"onCaretClick($event)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-nav-item__caret\" aria-hidden=\"true\"></span>\n\t\t\t</button>\n\t\t}\n\t</div>\n} @else if (isDropdown() || hasChildren()) {\n\t<!-- Dropdown trigger: button that toggles a submenu -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"hub-nav-item__link hub-nav-item__dropdown-toggle\"\n\t\trole=\"menuitem\"\n\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.aria-expanded]=\"isExpanded()\"\n\t\t[attr.aria-haspopup]=\"true\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t\t@if (showCaret()) {\n\t\t\t<span class=\"hub-nav-item__caret\" aria-hidden=\"true\"></span>\n\t\t}\n\t</button>\n} @else {\n\t<!-- Fallback: non-routable item (custom or link without route) -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"hub-nav-item__link\"\n\t\trole=\"menuitem\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t</button>\n}\n", styles: [":host{display:block;position:relative}:host-context(.hub-nav-item-list--force-accordion){display:block;width:100%}.hub-nav-item__link{display:flex;align-items:center;gap:var(--hub-nav-icon-gap, .5rem);width:100%;padding:var(--hub-nav-item-padding-y, .5rem) var(--hub-nav-item-padding-x, 1rem);font-size:var(--hub-nav-item-font-size, 1rem);font-weight:var(--hub-nav-item-font-weight, 400);color:var(--hub-nav-item-color, #212529);text-decoration:none;background:none;border:0;border-radius:var(--hub-nav-item-border-radius, .25rem);cursor:pointer;transition:var(--hub-nav-item-transition, all .15s ease-in-out);white-space:nowrap;font-family:inherit;line-height:var(--hub-ref-line-height-base, 1.5);text-align:start}.hub-nav-item__link:hover:not(.hub-nav-item__link--disabled){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__link--active{background-color:var(--hub-nav-item-active-bg);color:var(--hub-nav-item-active-color);font-weight:var(--hub-nav-item-active-font-weight, 600)}.hub-nav-item__link--disabled{color:var(--hub-nav-item-disabled-color, #6c757d);opacity:var(--hub-nav-item-disabled-opacity, .65);pointer-events:none;cursor:default}.hub-nav-item__link:focus-visible{outline:2px solid var(--hub-sys-focus-ring-color, rgba(13, 110, 253, .25));outline-offset:-2px}:host-context(.hub-nav--horizontal) .hub-nav-item__link--active{box-shadow:inset 0 calc(var(--hub-nav-item-active-indicator-size, 3px) * -1) 0 var(--hub-nav-item-active-indicator-color)}:host-context(.hub-nav-item-list--animated-indicator) .hub-nav-item__link--active{background-color:transparent;box-shadow:none}.hub-nav-item__compound{display:flex;align-items:stretch;width:100%}.hub-nav-item__link--split{flex:1 1 auto;min-width:0;border-start-end-radius:0;border-end-end-radius:0}.hub-nav-item__caret-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;min-width:calc(var(--hub-nav-item-font-size, 1rem) * 2.5);padding:0 var(--hub-nav-item-padding-x, 1rem);color:var(--hub-nav-item-color, #212529);background:none;border:0;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:var(--hub-nav-item-border-radius, .25rem);border-end-end-radius:var(--hub-nav-item-border-radius, .25rem);cursor:pointer;transition:var(--hub-nav-item-transition, all .15s ease-in-out)}.hub-nav-item__caret-button:hover:not(.hub-nav-item__caret-button--disabled){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__caret-button--active{background-color:var(--hub-nav-item-active-bg, #0d6efd);color:var(--hub-nav-item-active-color, #fff)}.hub-nav-item__caret-button--disabled{color:var(--hub-nav-item-disabled-color, #6c757d);opacity:var(--hub-nav-item-disabled-opacity, .65);cursor:default}.hub-nav-item__caret-button:focus-visible{outline:2px solid var(--hub-sys-focus-ring-color, rgba(13, 110, 253, .25));outline-offset:-2px}.hub-nav-item__caret-button--expanded:not(.hub-nav-item__caret-button--active){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__icon{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-nav-icon-size, 1em);height:var(--hub-nav-icon-size, 1em);flex-shrink:0}.hub-nav-item__label{flex:1;overflow:hidden;text-overflow:ellipsis}.hub-nav-item__badge{display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-nav-badge-padding-y, .25em) var(--hub-nav-badge-padding-x, .5em);font-size:var(--hub-nav-badge-font-size, .75rem);font-weight:var(--hub-ref-font-weight-semibold, 600);line-height:1;color:var(--hub-nav-badge-color, #fff);background-color:var(--hub-nav-badge-bg, #dc3545);border-radius:var(--hub-nav-badge-border-radius, 50rem);flex-shrink:0}.hub-nav-item__caret{display:inline-block;width:0;height:0;vertical-align:middle;border-top:var(--hub-nav-caret-size, .3rem) solid var(--hub-nav-caret-color, currentColor);border-right:var(--hub-nav-caret-size, .3rem) solid transparent;border-left:var(--hub-nav-caret-size, .3rem) solid transparent;transition:var(--hub-nav-item-transition, all .15s ease-in-out);flex-shrink:0}:host(.hub-nav-item--expanded) .hub-nav-item__caret{transform:rotate(180deg)}:host(.hub-nav-item--expanded) .hub-nav-item__link:not(.hub-nav-item__link--active){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd);font-weight:var(--hub-nav-item-active-font-weight, 600)}.hub-nav-item__header{display:block;padding:var(--hub-nav-header-padding-y, .25rem) var(--hub-nav-header-padding-x, 1rem);margin-top:var(--hub-nav-header-margin-top, .75rem);font-size:var(--hub-nav-header-font-size, .875rem);font-weight:var(--hub-nav-header-font-weight, 600);color:var(--hub-nav-header-color, #6c757d);text-transform:uppercase;letter-spacing:.05em;cursor:default;white-space:nowrap}:host-context(.hub-nav--horizontal) .hub-nav-item__header{display:inline-flex;align-items:center;margin-top:0;margin-inline-start:0;padding-block:var(--hub-nav-header-padding-y, .25rem);padding-inline-end:var(--hub-nav-header-padding-x, 1rem);padding-inline-start:var(--hub-nav-header-horizontal-padding-inline-start, 2rem)}:host-context(.hub-nav-dropdown) .hub-nav-item__header,:host-context(.hub-nav-accordion) .hub-nav-item__header,:host-context(.hub-nav-panel__body) .hub-nav-item__header,:host-context(.hub-nav-mobile-panel__body) .hub-nav-item__header{display:block;margin-top:var(--hub-nav-header-margin-top, .75rem);margin-inline-start:0}:host-context(.hub-nav--horizontal){display:inline-flex}:host-context(.hub-nav--horizontal) .hub-nav-item__link{white-space:nowrap}:host-context(.hub-nav--horizontal):host-context(.hub-nav-dropdown) .hub-nav-item__caret{border-top:var(--hub-nav-caret-size, .3rem) solid transparent;border-bottom:var(--hub-nav-caret-size, .3rem) solid transparent;border-left:var(--hub-nav-caret-size, .3rem) solid var(--hub-nav-caret-color, currentColor);border-right:0}:host-context(.hub-nav-dropdown){display:block}:host-context(.hub-nav-item-list--force-accordion) .hub-nav-item__link{width:100%}.hub-nav-item__initial{display:none}:host-context(.hub-nav-item-list--rail) .hub-nav-item__link{justify-content:center;gap:0;padding-inline:0}:host-context(.hub-nav-item-list--rail) .hub-nav-item__label{flex:0 0 auto;inline-size:0;overflow:hidden}:host-context(.hub-nav-item-list--rail) .hub-nav-item__badge,:host-context(.hub-nav-item-list--rail) .hub-nav-item__caret,:host-context(.hub-nav-item-list--rail) .hub-nav-item__header{display:none}:host-context(.hub-nav-item-list--rail) .hub-nav-item__initial{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-nav-icon-size, 1em);height:var(--hub-nav-icon-size, 1em);font-weight:var(--hub-nav-item-active-font-weight, 600);text-transform:uppercase;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: HubOverflowTooltipDirective, selector: "[hubOverflowTooltip]", inputs: ["hubOverflowTooltip", "placement"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip]", inputs: ["tooltip", "placement", "delay", "offset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1352
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubNavItemComponent, isStandalone: true, selector: "hub-nav-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, isActive: { classPropertyName: "isActive", publicName: "isActive", isSignal: true, isRequired: false, transformFunction: null }, isExpanded: { classPropertyName: "isExpanded", publicName: "isExpanded", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, forceAccordionMode: { classPropertyName: "forceAccordionMode", publicName: "forceAccordionMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", toggleDropdown: "toggleDropdown" }, host: { attributes: { "role": "none" }, properties: { "class.hub-nav-item--active": "isActive()", "class.hub-nav-item--disabled": "item().disabled", "class.hub-nav-item--has-children": "hasChildren()", "class.hub-nav-item--expanded": "isExpanded()", "class.hub-nav-item--header": "isHeader()", "attr.data-item-id": "item().id" }, classAttribute: "hub-nav-item" }, ngImport: i0, template: "@if (itemTemplate()) {\n\t<!-- Custom template rendering -->\n\t<ng-container [ngTemplateOutlet]=\"itemTemplate()!\" [ngTemplateOutletContext]=\"templateContext()\" />\n} @else if (isHeader()) {\n\t<!-- Header item: non-interactive section label -->\n\t<span class=\"hub-nav-item__header\" role=\"presentation\">{{ item().label }}</span>\n} @else if (isLink() && resolvedRoute()) {\n\t<!-- Link item: routable anchor -->\n\t<a\n\t\tclass=\"hub-nav-item__link\"\n\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\trole=\"menuitem\"\n\t\t[routerLink]=\"resolvedRoute()\"\n\t\t[queryParams]=\"item().queryParams\"\n\t\t[fragment]=\"item().fragment\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[attr.aria-current]=\"isActive() ? 'page' : null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t</a>\n} @else if ((isDropdown() || hasChildren()) && resolvedRoute()) {\n\t<!-- Routable dropdown: link area navigates, caret area toggles submenu -->\n\t<div class=\"hub-nav-item__compound\">\n\t\t<a\n\t\t\tclass=\"hub-nav-item__link\"\n\t\t\t[class.hub-nav-item__link--split]=\"showCaret()\"\n\t\t\trole=\"menuitem\"\n\t\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\t\t[routerLink]=\"resolvedRoute()\"\n\t\t\t[queryParams]=\"item().queryParams\"\n\t\t\t[fragment]=\"item().fragment\"\n\t\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t\t[attr.aria-current]=\"isActive() ? 'page' : null\"\n\t\t\t[attr.aria-expanded]=\"showCaret() ? null : isExpanded()\"\n\t\t\t[attr.aria-haspopup]=\"showCaret() ? null : true\"\n\t\t\t[tooltip]=\"railTooltip()\"\n\t\t\t[placement]=\"railTooltipPlacement()\"\n\t\t\t(click)=\"onRouteItemClick($event)\"\n\t\t>\n\t\t\t@if (item().icon) {\n\t\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t\t} @else if (railInitial()) {\n\t\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t\t}\n\t\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t\t@if (item().badge) {\n\t\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t\t}\n\t\t</a>\n\n\t\t@if (showCaret()) {\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"hub-nav-item__caret-button\"\n\t\t\t\t[class.hub-nav-item__caret-button--active]=\"isActive()\"\n\t\t\t\t[class.hub-nav-item__caret-button--expanded]=\"isExpanded()\"\n\t\t\t\t[class.hub-nav-item__caret-button--disabled]=\"item().disabled\"\n\t\t\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t\t\t[attr.aria-label]=\"caretAriaLabel()\"\n\t\t\t\t[attr.aria-expanded]=\"isExpanded()\"\n\t\t\t\t[attr.aria-haspopup]=\"true\"\n\t\t\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t\t\t(click)=\"onCaretClick($event)\"\n\t\t\t>\n\t\t\t\t<span class=\"hub-nav-item__caret\" aria-hidden=\"true\"></span>\n\t\t\t</button>\n\t\t}\n\t</div>\n} @else if (isDropdown() || hasChildren()) {\n\t<!-- Dropdown trigger: button that toggles a submenu -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"hub-nav-item__link hub-nav-item__dropdown-toggle\"\n\t\trole=\"menuitem\"\n\t\t[class.hub-nav-item__link--active]=\"isActive()\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.aria-expanded]=\"isExpanded()\"\n\t\t[attr.aria-haspopup]=\"true\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t\t@if (showCaret()) {\n\t\t\t<span class=\"hub-nav-item__caret\" aria-hidden=\"true\"></span>\n\t\t}\n\t</button>\n} @else {\n\t<!-- Fallback: non-routable item (custom or link without route) -->\n\t<button\n\t\ttype=\"button\"\n\t\tclass=\"hub-nav-item__link\"\n\t\trole=\"menuitem\"\n\t\t[class.hub-nav-item__link--disabled]=\"item().disabled\"\n\t\t[attr.tabindex]=\"item().disabled ? -1 : 0\"\n\t\t[attr.aria-disabled]=\"item().disabled || null\"\n\t\t[tooltip]=\"railTooltip()\"\n\t\t[placement]=\"railTooltipPlacement()\"\n\t\t(click)=\"onItemClick($event)\"\n\t>\n\t\t@if (item().icon) {\n\t\t\t<span class=\"hub-nav-item__icon\" [class]=\"item().icon\" aria-hidden=\"true\"></span>\n\t\t} @else if (railInitial()) {\n\t\t\t<span class=\"hub-nav-item__initial\" aria-hidden=\"true\">{{ railInitial() }}</span>\n\t\t}\n\t\t<span class=\"hub-nav-item__label\" [hubOverflowTooltip]=\"item().label\">{{ item().label }}</span>\n\t\t@if (item().badge) {\n\t\t\t<span class=\"hub-nav-item__badge\" [class]=\"item().badgeClass\">{{ item().badge }}</span>\n\t\t}\n\t</button>\n}\n", styles: [":host{display:block;position:relative}:host-context(.hub-nav-item-list--force-accordion){display:block;width:100%}.hub-nav-item__link{display:flex;align-items:center;gap:var(--hub-nav-icon-gap, .5rem);width:100%;padding:var(--hub-nav-item-padding-y, .5rem) var(--hub-nav-item-padding-x, 1rem);font-size:var(--hub-nav-item-font-size, 1rem);font-weight:var(--hub-nav-item-font-weight, 400);color:var(--hub-nav-item-color, #212529);text-decoration:none;background:none;border:0;border-radius:var(--hub-nav-item-border-radius, .25rem);cursor:pointer;transition:var(--hub-nav-item-transition, all .15s ease-in-out);white-space:nowrap;font-family:inherit;line-height:var(--hub-ref-line-height-base, 1.5);text-align:start}.hub-nav-item__link:hover:not(.hub-nav-item__link--disabled){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__link--active{background-color:var(--hub-nav-item-active-bg);color:var(--hub-nav-item-active-color);font-weight:var(--hub-nav-item-active-font-weight, 600)}.hub-nav-item__link--disabled{color:var(--hub-nav-item-disabled-color, #6c757d);opacity:var(--hub-nav-item-disabled-opacity, .65);pointer-events:none;cursor:default}.hub-nav-item__link:focus-visible{outline:2px solid var(--hub-sys-focus-ring-color, rgba(13, 110, 253, .25));outline-offset:-2px}:host-context(.hub-nav--horizontal) .hub-nav-item__link--active{box-shadow:inset 0 calc(var(--hub-nav-item-active-indicator-size, 3px) * -1) 0 var(--hub-nav-item-active-indicator-color)}:host-context(.hub-nav-item-list--animated-indicator) .hub-nav-item__link--active{background-color:transparent;box-shadow:none}.hub-nav-item__compound{display:flex;align-items:stretch;width:100%}.hub-nav-item__link--split{flex:1 1 auto;min-width:0;border-start-end-radius:0;border-end-end-radius:0}.hub-nav-item__caret-button{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;min-width:calc(var(--hub-nav-item-font-size, 1rem) * 2.5);padding:0 var(--hub-nav-item-padding-x, 1rem);color:var(--hub-nav-item-color, #212529);background:none;border:0;border-start-start-radius:0;border-end-start-radius:0;border-start-end-radius:var(--hub-nav-item-border-radius, .25rem);border-end-end-radius:var(--hub-nav-item-border-radius, .25rem);cursor:pointer;transition:var(--hub-nav-item-transition, all .15s ease-in-out)}.hub-nav-item__caret-button:hover:not(.hub-nav-item__caret-button--disabled){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__caret-button--active{background-color:var(--hub-nav-item-active-bg, #0d6efd);color:var(--hub-nav-item-active-color, #fff)}.hub-nav-item__caret-button--disabled{color:var(--hub-nav-item-disabled-color, #6c757d);opacity:var(--hub-nav-item-disabled-opacity, .65);cursor:default}.hub-nav-item__caret-button:focus-visible{outline:2px solid var(--hub-sys-focus-ring-color, rgba(13, 110, 253, .25));outline-offset:-2px}.hub-nav-item__caret-button--expanded:not(.hub-nav-item__caret-button--active){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd)}.hub-nav-item__icon{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-nav-icon-size, 1em);height:var(--hub-nav-icon-size, 1em);flex-shrink:0}.hub-nav-item__label{flex:1;overflow:hidden;text-overflow:ellipsis}.hub-nav-item__badge{display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-nav-badge-padding-y, .25em) var(--hub-nav-badge-padding-x, .5em);font-size:var(--hub-nav-badge-font-size, .75rem);font-weight:var(--hub-ref-font-weight-semibold, 600);line-height:1;color:var(--hub-nav-badge-color, #fff);background-color:var(--hub-nav-badge-bg, #dc3545);border-radius:var(--hub-nav-badge-border-radius, 50rem);flex-shrink:0}.hub-nav-item__caret{display:inline-block;width:0;height:0;vertical-align:middle;border-top:var(--hub-nav-caret-size, .3rem) solid var(--hub-nav-caret-color, currentColor);border-right:var(--hub-nav-caret-size, .3rem) solid transparent;border-left:var(--hub-nav-caret-size, .3rem) solid transparent;transition:var(--hub-nav-item-transition, all .15s ease-in-out);flex-shrink:0}:host(.hub-nav-item--expanded) .hub-nav-item__caret{transform:rotate(180deg)}:host(.hub-nav-item--expanded) .hub-nav-item__link:not(.hub-nav-item__link--active){background-color:var(--hub-nav-item-hover-bg, rgba(0, 0, 0, .04));color:var(--hub-nav-item-hover-color, #0d6efd);font-weight:var(--hub-nav-item-active-font-weight, 600)}.hub-nav-item__header{display:block;padding:var(--hub-nav-header-padding-y, .25rem) var(--hub-nav-header-padding-x, 1rem);margin-top:var(--hub-nav-header-margin-top, .75rem);font-size:var(--hub-nav-header-font-size, .875rem);font-weight:var(--hub-nav-header-font-weight, 600);color:var(--hub-nav-header-color, #6c757d);text-transform:uppercase;letter-spacing:.05em;cursor:default;white-space:nowrap}:host-context(.hub-nav--horizontal) .hub-nav-item__header{display:inline-flex;align-items:center;margin-top:0;margin-inline-start:0;padding-block:var(--hub-nav-header-padding-y, .25rem);padding-inline-end:var(--hub-nav-header-padding-x, 1rem);padding-inline-start:var(--hub-nav-header-horizontal-padding-inline-start, 2rem)}:host-context(.hub-nav-dropdown) .hub-nav-item__header,:host-context(.hub-nav-accordion) .hub-nav-item__header,:host-context(.hub-nav-panel__body) .hub-nav-item__header,:host-context(.hub-nav-mobile-panel__body) .hub-nav-item__header{display:block;margin-top:var(--hub-nav-header-margin-top, .75rem);margin-inline-start:0}:host-context(.hub-nav--horizontal){display:inline-flex}:host-context(.hub-nav--horizontal) .hub-nav-item__link{white-space:nowrap}:host-context(.hub-nav--horizontal):host-context(.hub-nav-dropdown) .hub-nav-item__caret{border-top:var(--hub-nav-caret-size, .3rem) solid transparent;border-bottom:var(--hub-nav-caret-size, .3rem) solid transparent;border-left:var(--hub-nav-caret-size, .3rem) solid var(--hub-nav-caret-color, currentColor);border-right:0}:host-context(.hub-nav-dropdown){display:block}:host-context(.hub-nav-item-list--force-accordion) .hub-nav-item__link{width:100%}.hub-nav-item__initial{display:none}:host-context(.hub-nav-item-list--rail) .hub-nav-item__link{justify-content:center;gap:0;padding-inline:0}:host-context(.hub-nav-item-list--rail) .hub-nav-item__label{flex:0 0 auto;inline-size:0;overflow:hidden}:host-context(.hub-nav-item-list--rail) .hub-nav-item__badge,:host-context(.hub-nav-item-list--rail) .hub-nav-item__caret,:host-context(.hub-nav-item-list--rail) .hub-nav-item__header{display:none}:host-context(.hub-nav-item-list--rail) .hub-nav-item__initial{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-nav-icon-size, 1em);height:var(--hub-nav-icon-size, 1em);font-weight:var(--hub-nav-item-active-font-weight, 600);text-transform:uppercase;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: HubOverflowTooltipDirective, selector: "[hubOverflowTooltip]", inputs: ["hubOverflowTooltip", "placement", "hubOverflowTooltipMeasure"] }, { kind: "directive", type: TooltipDirective, selector: "[tooltip]", inputs: ["tooltip", "placement", "delay", "offset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1329
1353
  }
1330
1354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubNavItemComponent, decorators: [{
1331
1355
  type: Component,
@@ -2895,18 +2919,39 @@ class HubNavComponent {
2895
2919
  this._lastSyncedPath = currentPath;
2896
2920
  return;
2897
2921
  }
2922
+ // Which root the reader is now inside, resolved by the most specific match rather than
2923
+ // the first one — the same rule {@link openPanelsFromRoute} uses. A root parked on the
2924
+ // language prefix (`/en/`) matches every URL in the site, and taking it as the answer
2925
+ // handed an accordion section's navigation to the panel stack.
2926
+ const activeRootItem = items.find((item) => this.state.isItemActiveAmongSiblings(item, items, currentUrl));
2927
+ // Nowhere in the menu — a settings screen, a 404. Whatever is open describes the page
2928
+ // just left, so both mechanisms are cleared rather than only the panels.
2929
+ if (!activeRootItem) {
2930
+ this.state.closeAllDropdowns();
2931
+ this.state.closeAllPanels();
2932
+ this._lastSyncedPath = currentPath;
2933
+ return;
2934
+ }
2898
2935
  // An accordion opens in place, so what the route has to open is its
2899
2936
  // dropdown state — not the panel stack, which is the drill-down mechanism
2900
2937
  // and is not rendered in this mode. Deciding by collapsed-or-not alone
2901
2938
  // left an expanded accordion with nothing opened from the route: the
2902
2939
  // section somebody had just navigated into stayed shut, while a panel
2903
2940
  // nobody could see was opened behind the page.
2904
- const activeRootItem = items.find((item) => this.state.isItemOrDescendantActive(item, currentUrl));
2905
- if (activeRootItem && this.state.getEffectiveExpandMode(activeRootItem) === 'accordion') {
2941
+ if (this.state.getEffectiveExpandMode(activeRootItem) === 'accordion') {
2906
2942
  this.state.syncDropdownsWithRoute(items, currentUrl);
2943
+ // In a mixed nav the reader can arrive here from a root that drills down into
2944
+ // panels, and that panel now has no owner: an accordion expands in place and
2945
+ // never touches the stack, so nothing else would ever close it. Two sections
2946
+ // stayed open at once, one of them the one just walked out of.
2947
+ this.state.closeAllPanels();
2907
2948
  this._lastSyncedPath = currentPath;
2908
2949
  return;
2909
2950
  }
2951
+ // The mirror of the branch above. This root does not expand in place, so any dropdown
2952
+ // still open belongs to the section left behind — including the case of a root with no
2953
+ // children at all, which opens neither mechanism and must therefore leave neither.
2954
+ this.state.closeAllDropdowns();
2910
2955
  if (currentPath === this._lastSyncedPath && this.state.panelCount() > 0) {
2911
2956
  return;
2912
2957
  }