fold-ng 0.2.1 → 0.4.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.
@@ -1,6 +1,7 @@
1
1
  import { DOCUMENT, NgTemplateOutlet, NgClass, DatePipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, ViewContainerRef, Injector } from '@angular/core';
3
+ import { input, inject, ElementRef, effect, Directive, Injectable, signal, DestroyRef, model, computed, HostListener, Component, InjectionToken, Service, booleanAttribute, linkedSignal, numberAttribute, ViewEncapsulation, output, viewChild, afterNextRender, contentChildren, TemplateRef, isDevMode, contentChild, viewChildren, ViewContainerRef, Injector } from '@angular/core';
4
+ import { RouterLink, RouterLinkActive } from '@angular/router';
4
5
 
5
6
  /**
6
7
  * A matched element is only truly focusable if it's rendered — `display:none`
@@ -637,32 +638,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
637
638
  type: Service
638
639
  }] });
639
640
 
640
- /** Ink for text on a light vs dark categorical fill. */
641
- const DARK_INK = "#1a202c";
642
- const LIGHT_INK = "#ffffff";
643
641
  /**
644
- * Pick a readable ink for text drawn on `fill`, from its relative luminance —
645
- * so a custom (dark) palette supplied via `provideFoldPalette` still gets legible
646
- * initials instead of the fixed dark ink. Non-hex fills fall back to dark ink.
642
+ * Small WCAG colour-contrast helpers — used to pick a legible ink for text on a
643
+ * categorical fill (avatar initials), and lockable by a contrast test.
644
+ *
645
+ * These operate on plain hex strings (`#rgb` / `#rrggbb`); a categorical palette
646
+ * is qualitative *data* (hex is allowed there — rule 5.3), so contrast can't be
647
+ * a token concern and lives here.
647
648
  */
648
- function readableInk(fill) {
649
- const hex = fill.trim().replace(/^#/, "");
650
- const full = hex.length === 3
651
- ? hex
649
+ /** Parse `#rgb` / `#rrggbb` to `[r, g, b]` (0–255), or `null` if not a hex. */
650
+ function parseHex(hex) {
651
+ const h = hex.trim().replace(/^#/, "");
652
+ const full = h.length === 3
653
+ ? h
652
654
  .split("")
653
655
  .map((c) => c + c)
654
656
  .join("")
655
- : hex;
657
+ : h;
656
658
  if (!/^[0-9a-fA-F]{6}$/.test(full)) {
657
- return DARK_INK;
659
+ return null;
658
660
  }
659
- const channel = (i) => {
660
- const c = parseInt(full.slice(i, i + 2), 16) / 255;
661
- return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
661
+ return [
662
+ parseInt(full.slice(0, 2), 16),
663
+ parseInt(full.slice(2, 4), 16),
664
+ parseInt(full.slice(4, 6), 16),
665
+ ];
666
+ }
667
+ /** WCAG relative luminance of a hex colour, or `null` if it isn't a hex. */
668
+ function foldLuminance(hex) {
669
+ const rgb = parseHex(hex);
670
+ if (!rgb) {
671
+ return null;
672
+ }
673
+ const lin = (c) => {
674
+ const s = c / 255;
675
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
662
676
  };
663
- const luminance = 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
664
- return luminance > 0.4 ? DARK_INK : LIGHT_INK;
677
+ return 0.2126 * lin(rgb[0]) + 0.7152 * lin(rgb[1]) + 0.0722 * lin(rgb[2]);
665
678
  }
679
+ /** WCAG contrast ratio between two hex colours (1–21), or `null` if either
680
+ * isn't a hex. */
681
+ function foldContrast(a, b) {
682
+ const la = foldLuminance(a);
683
+ const lb = foldLuminance(b);
684
+ if (la === null || lb === null) {
685
+ return null;
686
+ }
687
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
688
+ return (hi + 0.05) / (lo + 0.05);
689
+ }
690
+ /**
691
+ * The **more legible** of two inks for text drawn on `fill` — whichever has the
692
+ * higher WCAG contrast. Picking the max (rather than thresholding luminance)
693
+ * guarantees the best of the pair for *any* fill: at the black/white crossover
694
+ * both inks already clear ~4.5:1, so every fill lands at or above it. A non-hex
695
+ * `fill` falls back to `dark`.
696
+ */
697
+ function foldReadableInk(fill, dark, light) {
698
+ const withDark = foldContrast(fill, dark);
699
+ const withLight = foldContrast(fill, light);
700
+ if (withDark === null || withLight === null) {
701
+ return dark;
702
+ }
703
+ return withDark >= withLight ? dark : light;
704
+ }
705
+
706
+ /** Ink for text on a light vs dark categorical fill. */
707
+ const DARK_INK = "#1a202c";
708
+ const LIGHT_INK = "#ffffff";
666
709
  /**
667
710
  * `<fold-avatar>` — a user/entity avatar with initials or an image.
668
711
  *
@@ -671,11 +714,17 @@ function readableInk(fill) {
671
714
  * (so the same seed is the same colour everywhere, and one `registry.use(...)`
672
715
  * recolours every avatar).
673
716
  *
717
+ * A broken `imageUrl` falls back to the initials rather than the browser's
718
+ * broken-image glyph (and retries when the URL changes).
719
+ *
674
720
  * Three orthogonal state axes compose: `variant` (fill), `muted` (presence —
675
721
  * dim for an absence/inactive person), and `ring` + `ringStyle` (a status
676
722
  * outline). The component stays domain-agnostic: a screen maps its own states
677
723
  * (e.g. absent → `[muted]`, a scheduled arrival → `ring="accent"`
678
- * `ringStyle="dotted"`), the avatar just draws the primitives.
724
+ * `ringStyle="dotted"`), the avatar just draws the primitives. The ring is a
725
+ * redundant emphasis cue — never the sole carrier of a state — because on a
726
+ * light page a soft status hue (amber/green) can't clear WCAG 3:1 as a thin
727
+ * line (status-ring-contrast.spec.ts).
679
728
  *
680
729
  * ## Inputs
681
730
  *
@@ -686,12 +735,19 @@ function readableInk(fill) {
686
735
  * | `colorSeed` | `string` | `name` | String used to pick the deterministic colour. |
687
736
  * | `variant` | `'solid' \| 'ghost'` | `'solid'` | `'ghost'` renders a dashed border (for guests). |
688
737
  * | `square` | `boolean` | `false` | Square shape with a small radius (for orgs). |
689
- * | `imageUrl` | `string` | — | Image/logo URL. Replaces initials when set. |
738
+ * | `imageUrl` | `string` | — | Image/logo URL. Replaces initials when set; falls back to initials if it fails to load. |
690
739
  * | `muted` | `boolean` | `false` | Dim the avatar (same hue, less intense) — absence / inactive. |
691
740
  * | `ring` | `FoldAvatarRing` | `'none'` | A status outline (`accent`/`info`/`warning`/`alert`/`success`). |
692
741
  * | `ringStyle` | `'solid' \| 'dotted'` | `'solid'` | Ring line style — `dotted` for scheduled/tentative states. |
693
742
  *
694
743
  * @selector `fold-avatar`
744
+ *
745
+ * @example
746
+ * ```html
747
+ * <fold-avatar name="Clément Aubry" size="lg" />
748
+ * <fold-avatar name="Léa Petit" ring="accent" ringStyle="dotted" />
749
+ * <fold-avatar name="Acme Corp" square imageUrl="/logo.svg" />
750
+ * ```
695
751
  */
696
752
  class FoldAvatarComponent {
697
753
  palette = inject(FoldPaletteRegistry);
@@ -714,8 +770,20 @@ class FoldAvatarComponent {
714
770
  /** Ring line style — `'dotted'` for scheduled / tentative states. */
715
771
  ringStyle = input("solid", /* @ts-ignore */
716
772
  ...(ngDevMode ? [{ debugName: "ringStyle" }] : /* istanbul ignore next */ []));
717
- /** Readable ink for the initials, derived from the fill's luminance. */
718
- onColor = computed(() => readableInk(this.color()), /* @ts-ignore */
773
+ /** True once the `<img>` fails to load. Resets whenever `imageUrl` changes,
774
+ * so a corrected URL is retried rather than staying on the fallback. */
775
+ imageFailed = linkedSignal(() => {
776
+ this.imageUrl();
777
+ return false;
778
+ }, /* @ts-ignore */
779
+ ...(ngDevMode ? [{ debugName: "imageFailed" }] : /* istanbul ignore next */ []));
780
+ /** Show the image only when a URL is set AND it has not failed — a broken URL
781
+ * falls back to the initials instead of the browser's broken-image glyph. */
782
+ showImage = computed(() => Boolean(this.imageUrl()) && !this.imageFailed(), /* @ts-ignore */
783
+ ...(ngDevMode ? [{ debugName: "showImage" }] : /* istanbul ignore next */ []));
784
+ /** Readable ink for the initials — the higher-contrast of dark/light ink on
785
+ * the fill, so a custom palette stays legible. */
786
+ onColor = computed(() => foldReadableInk(this.color(), DARK_INK, LIGHT_INK), /* @ts-ignore */
719
787
  ...(ngDevMode ? [{ debugName: "onColor" }] : /* istanbul ignore next */ []));
720
788
  initials = computed(() => {
721
789
  const n = this.name().trim();
@@ -733,11 +801,11 @@ class FoldAvatarComponent {
733
801
  color = computed(() => this.palette.colorFor(this.colorSeed() ?? this.name()), /* @ts-ignore */
734
802
  ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
735
803
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
736
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAvatarComponent, isStandalone: true, selector: "fold-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorSeed: { classPropertyName: "colorSeed", publicName: "colorSeed", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, square: { classPropertyName: "square", publicName: "square", isSignal: true, isRequired: false, transformFunction: null }, imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, ring: { classPropertyName: "ring", publicName: "ring", isSignal: true, isRequired: false, transformFunction: null }, ringStyle: { classPropertyName: "ringStyle", publicName: "ringStyle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (imageUrl()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img [src]=\"imageUrl()\" [alt]=\"name()\" class=\"avatar-img\" />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] });
804
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldAvatarComponent, isStandalone: true, selector: "fold-avatar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, colorSeed: { classPropertyName: "colorSeed", publicName: "colorSeed", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, square: { classPropertyName: "square", publicName: "square", isSignal: true, isRequired: false, transformFunction: null }, imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, ring: { classPropertyName: "ring", publicName: "ring", isSignal: true, isRequired: false, transformFunction: null }, ringStyle: { classPropertyName: "ringStyle", publicName: "ringStyle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (showImage()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img\n [src]=\"imageUrl()\"\n [alt]=\"name()\"\n class=\"avatar-img\"\n (error)=\"imageFailed.set(true)\"\n />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar.has-image.variant-ghost{border-style:dashed}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] });
737
805
  }
738
806
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAvatarComponent, decorators: [{
739
807
  type: Component,
740
- args: [{ selector: "fold-avatar", standalone: true, template: "@if (imageUrl()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img [src]=\"imageUrl()\" [alt]=\"name()\" class=\"avatar-img\" />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] }]
808
+ args: [{ selector: "fold-avatar", standalone: true, template: "@if (showImage()) {\n <div\n class=\"avatar has-image\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [attr.title]=\"name()\"\n >\n <img\n [src]=\"imageUrl()\"\n [alt]=\"name()\"\n class=\"avatar-img\"\n (error)=\"imageFailed.set(true)\"\n />\n </div>\n} @else {\n <div\n class=\"avatar\"\n [class.size-sm]=\"size() === 'sm'\"\n [class.size-md]=\"size() === 'md'\"\n [class.size-lg]=\"size() === 'lg'\"\n [class.variant-ghost]=\"variant() === 'ghost'\"\n [class.shape-square]=\"square()\"\n [class.is-muted]=\"muted()\"\n [attr.data-ring]=\"ring() === 'none' ? null : ring()\"\n [attr.data-ring-style]=\"ringStyle()\"\n [style.background]=\"variant() === 'solid' ? color() : ''\"\n [style.color]=\"variant() === 'solid' ? onColor() : ''\"\n [attr.title]=\"name()\"\n >\n {{ initials() }}\n </div>\n}\n", styles: ["@charset \"UTF-8\";.avatar{display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-radius:var(--fold-radius-round)}.avatar.size-sm{width:20px;height:20px;font-size:7px}.avatar.size-md{width:32px;height:32px;font-size:11px}.avatar.size-lg{width:44px;height:44px;font-size:15px}.avatar.variant-ghost{background:var(--fold-color-surface-raised);color:var(--fold-color-text-secondary);border:1px dashed var(--fold-color-border)}.avatar.shape-square{border-radius:var(--fold-radius-sm)}.avatar.has-image{overflow:hidden;background:var(--fold-color-surface-raised);border:1px solid var(--fold-color-border)}.avatar.has-image.variant-ghost{border-style:dashed}.avatar-img{width:100%;height:100%;object-fit:cover}.avatar.is-muted{opacity:.45}.avatar[data-ring]{outline:2px solid var(--avatar-ring, transparent);outline-offset:2px}.avatar[data-ring-style=dotted]{outline-style:dotted}.avatar[data-ring=accent]{--avatar-ring: var(--fold-color-primary)}.avatar[data-ring=info]{--avatar-ring: var(--fold-color-info)}.avatar[data-ring=warning]{--avatar-ring: var(--fold-color-warning)}.avatar[data-ring=alert]{--avatar-ring: var(--fold-color-alert)}.avatar[data-ring=success]{--avatar-ring: var(--fold-color-success)}\n"] }]
741
809
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], colorSeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorSeed", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], square: [{ type: i0.Input, args: [{ isSignal: true, alias: "square", required: false }] }], imageUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUrl", required: false }] }], muted: [{ type: i0.Input, args: [{ isSignal: true, alias: "muted", required: false }] }], ring: [{ type: i0.Input, args: [{ isSignal: true, alias: "ring", required: false }] }], ringStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "ringStyle", required: false }] }] } });
742
810
 
743
811
  /**
@@ -765,6 +833,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
765
833
  * | `ringStyle` | `'solid' \| 'dotted'` | `'solid'` | Ring line style (`dotted` = scheduled). |
766
834
  *
767
835
  * @selector `fold-avatar-detail`
836
+ *
837
+ * @example
838
+ * ```html
839
+ * <fold-avatar-detail primary="Alex Rivers" secondary="alex@sh3pherd.dev" />
840
+ * <fold-avatar-detail primary="Acme Corp" secondary="Organisation" square />
841
+ * ```
768
842
  */
769
843
  class FoldAvatarDetailComponent {
770
844
  primary = input.required(/* @ts-ignore */
@@ -1389,7 +1463,7 @@ const FOLD_SEMANTIC_COLOR_TOKENS = [
1389
1463
  "bg-rail-primary",
1390
1464
  /** Rail 2 — the workspace menu. */
1391
1465
  "bg-rail-secondary",
1392
- /** Rail 3 — tertiary nav (e.g. a `fold-tab-nav` section sidebar). */
1466
+ /** Rail 3 — tertiary nav (e.g. a `fold-view-nav` section sidebar). */
1393
1467
  "bg-rail-tertiary",
1394
1468
  /* ── Primary / accent ─────────────────────────────────────── */
1395
1469
  /** Primary / accent — brand teal (solid). */
@@ -1472,6 +1546,10 @@ const FOLD_TEXT_TOKENS = ["xs", "sm", "md", "lg", "xl"];
1472
1546
  const FOLD_ICON_SIZE_TOKENS = ["xs", "sm", "md", "lg", "xl"];
1473
1547
  /** Space (gap / padding / margin) scale. */
1474
1548
  const FOLD_SPACE_TOKENS = ["xs", "sm", "md", "lg", "xl"];
1549
+ /** Rail-width scale — the nav-rail hierarchy's shared widths, named to pair with
1550
+ * the `--fold-color-bg-rail-*` colours (primary app menu → secondary workspace /
1551
+ * aside → tertiary in-page nav). */
1552
+ const FOLD_RAIL_TOKENS = ["primary", "secondary", "tertiary"];
1475
1553
  /** Motion scale — `transition` shorthands (duration + easing). */
1476
1554
  const FOLD_MOTION_TOKENS = ["fast", "base", "slow"];
1477
1555
  /** Backdrop-blur radii (`--fold-blur-*`). */
@@ -1863,14 +1941,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1863
1941
  *
1864
1942
  * - `surface` — `card` (the raised card tint, default) · `sunken` (a deeper
1865
1943
  * surface for large containers, below the card — the ref's second card tint;
1866
- * pairs with a fainter `border-subtle` hairline).
1944
+ * pairs with a fainter `border-subtle` hairline) · `accent` (an accent-filled
1945
+ * card whose content sub-tree auto-inverts to a compatible on-accent palette).
1867
1946
  * - `radius` — `lg` (14px, default) · `md` · `sm`.
1868
1947
  * - `padding` — `md` (16px, default) · `none` · `sm` · `lg`. Sets the *body*
1869
1948
  * padding; override with a custom value via `--fold-card-padding`.
1870
- * - `interactive` — adds a hover lift (for clickable cards).
1871
- * - `separators` — draw a hairline between a projected header/footer and the body.
1872
- * - `raisedBands` — tint the header/footer bands a step above the surface
1873
- * (fainter on `sunken`).
1949
+ * - `interactive` — makes the whole card a clickable control (see below).
1950
+ * - `separators` — which bands get a hairline against the body:
1951
+ * `none` (default) · `header` · `footer` · `both`.
1952
+ * - `raisedBands` — which bands are tinted a step above the surface (fainter on
1953
+ * `sunken`): `none` (default) · `header` · `footer` · `both`.
1954
+ *
1955
+ * `separators` and `raisedBands` are per-band and independent axes, so a card can
1956
+ * have (say) a raised, un-separated header over a flush footer.
1874
1957
  *
1875
1958
  * Content projection:
1876
1959
  * - default slot → the card body.
@@ -1882,10 +1965,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1882
1965
  * never pads. So the content padding is identical whether or not the bands show —
1883
1966
  * toggling a header/footer never shifts the body.
1884
1967
  *
1968
+ * **Interactive cards.** `interactive` turns the whole card into a real control:
1969
+ * the host gains `role="button"`, `tabindex="0"`, a focus ring and a hover lift,
1970
+ * and Enter / Space (or a click) emit `(activated)`. Give it an accessible name
1971
+ * with `ariaLabel` when the card's text isn't a sufficient label.
1972
+ * A `role="button"` must not wrap other interactive controls — so an interactive
1973
+ * card must NOT contain buttons/links. For a card with its own inner actions,
1974
+ * keep it non-interactive and put a single primary `<a>`/`<button>` inside.
1975
+ *
1885
1976
  * ```html
1886
1977
  * <fold-card>Static content</fold-card>
1887
1978
  * <fold-card surface="sunken" padding="lg">Deep container</fold-card>
1888
- * <fold-card separators>
1979
+ * <fold-card separators="both">
1889
1980
  * <h3 cardHeader>Title</h3>
1890
1981
  * Body content
1891
1982
  * <div cardFooter>Actions</div>
@@ -1897,9 +1988,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1897
1988
  * (default `clip`) lets content escape the rounded corners.
1898
1989
  *
1899
1990
  * @selector `fold-card`
1991
+ *
1992
+ * @example
1993
+ * ```html
1994
+ * <fold-card interactive ariaLabel="Open Acme Records" (activated)="open()">
1995
+ * <h3>Acme Records</h3>
1996
+ * <p>128 contracts · 96 active</p>
1997
+ * </fold-card>
1998
+ * ```
1900
1999
  */
1901
2000
  class FoldCardComponent {
1902
- /** Surface tint — `card` (raised, default) or `sunken` (deeper container). */
2001
+ /** Surface tint — `card` (raised, default), `sunken` (deeper container), or
2002
+ * `accent` (an **accent-filled** card: the accent colour as ground, with the
2003
+ * whole content sub-tree auto-inverted to a compatible on-accent role-set —
2004
+ * text, borders, band gradation, and even nested buttons/links/icon-tiles
2005
+ * convert automatically). Use it to make a card in a grid stand out. */
1903
2006
  surface = input("card", /* @ts-ignore */
1904
2007
  ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
1905
2008
  /** Corner radius — `lg` (default), `md`, or `sm`. */
@@ -1909,31 +2012,67 @@ class FoldCardComponent {
1909
2012
  * set with the `--fold-card-padding` CSS variable. */
1910
2013
  padding = input("md", /* @ts-ignore */
1911
2014
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
1912
- /** Add a hover lift for clickable cards. */
1913
- interactive = input(false, /* @ts-ignore */
1914
- ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
1915
- /** Draw a hairline between a projected header/footer and the body. */
1916
- separators = input(false, { ...(ngDevMode ? { debugName: "separators" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1917
- /** Lift the header/footer bands with a subtle raised tint over the card
1918
- * surface (fainter on `sunken`), to set the chrome apart from the body. */
1919
- raisedBands = input(false, { ...(ngDevMode ? { debugName: "raisedBands" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2015
+ /** Make the whole card a clickable control: `role="button"` + `tabindex`, a
2016
+ * focus ring, a hover lift, and Enter/Space/click → `(activated)`. Must not
2017
+ * wrap other interactive controls (see the class docs). */
2018
+ interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2019
+ /** Accessible name for the interactive card, when its content isn't enough. */
2020
+ ariaLabel = input(/* @ts-ignore */
2021
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2022
+ /** Which bands get a hairline against the body — `none` (default), `header`,
2023
+ * `footer`, or `both`. */
2024
+ separators = input("none", /* @ts-ignore */
2025
+ ...(ngDevMode ? [{ debugName: "separators" }] : /* istanbul ignore next */ []));
2026
+ /** Which bands are lifted with a subtle raised tint over the card surface
2027
+ * (fainter on `sunken`) — `none` (default), `header`, `footer`, or `both`. */
2028
+ raisedBands = input("none", /* @ts-ignore */
2029
+ ...(ngDevMode ? [{ debugName: "raisedBands" }] : /* istanbul ignore next */ []));
2030
+ /** Fires when an `interactive` card is activated (click, Enter, or Space). */
2031
+ activated = output();
2032
+ /** Whether a per-band chrome value applies to the given band. */
2033
+ hasBand(value, band) {
2034
+ return value === band || value === "both";
2035
+ }
2036
+ onActivate(event) {
2037
+ if (this.interactive()) {
2038
+ this.activated.emit(event);
2039
+ }
2040
+ }
2041
+ /** Enter/Space activate the card, matching the native button keyboard contract
2042
+ * (Space is prevented from scrolling the page). */
2043
+ onKeydown(event) {
2044
+ if (!this.interactive() || (event.key !== "Enter" && event.key !== " ")) {
2045
+ return;
2046
+ }
2047
+ event.preventDefault();
2048
+ this.activated.emit(event);
2049
+ }
1920
2050
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1921
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldCardComponent, isStandalone: true, selector: "fold-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, raisedBands: { classPropertyName: "raisedBands", publicName: "raisedBands", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.r-sm": "radius() === 'sm'", "class.r-md": "radius() === 'md'", "class.p-none": "padding() === 'none'", "class.p-sm": "padding() === 'sm'", "class.p-lg": "padding() === 'lg'", "class.is-interactive": "interactive()", "class.has-sep": "separators()", "class.raised-bands": "raisedBands()" } }, ngImport: i0, template: "<header class=\"card-header\"><ng-content select=\"[cardHeader]\" /></header>\n<div class=\"card-body\"><ng-content /></div>\n<footer class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></footer>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raised-bands) .card-header:not(:empty),:host(.raised-bands) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.has-sep) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.has-sep) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .1s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}\n"] });
2051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldCardComponent, isStandalone: true, selector: "fold-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, radius: { classPropertyName: "radius", publicName: "radius", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, raisedBands: { classPropertyName: "raisedBands", publicName: "raisedBands", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activated: "activated" }, host: { listeners: { "click": "onActivate($event)", "keydown": "onKeydown($event)" }, properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-accent": "surface() === 'accent'", "attr.data-surface": "surface() === 'accent' ? 'accent' : null", "class.r-sm": "radius() === 'sm'", "class.r-md": "radius() === 'md'", "class.p-none": "padding() === 'none'", "class.p-sm": "padding() === 'sm'", "class.p-lg": "padding() === 'lg'", "class.is-interactive": "interactive()", "class.sep-header": "hasBand(separators(), 'header')", "class.sep-footer": "hasBand(separators(), 'footer')", "class.raise-header": "hasBand(raisedBands(), 'header')", "class.raise-footer": "hasBand(raisedBands(), 'footer')", "attr.role": "interactive() ? 'button' : null", "attr.tabindex": "interactive() ? 0 : null", "attr.aria-label": "interactive() ? (ariaLabel() ?? null) : null" } }, ngImport: i0, template: "<div class=\"card-header\"><ng-content select=\"[cardHeader]\" /></div>\n<div class=\"card-body\"><ng-content /></div>\n<div class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></div>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raise-header) .card-header:not(:empty),:host(.raise-footer) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.sep-header) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.sep-footer) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}:host(.is-interactive:focus-visible){outline:2px solid var(--fold-color-primary);outline-offset:2px}@media(prefers-reduced-motion:reduce){:host(.is-interactive){transition:none}}:host(.s-accent){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary);--_band-raise: color-mix( in srgb, var(--_accent-ink) 16%, var(--_accent-fill) );--_sep-line: color-mix(in srgb, var(--_accent-ink) 24%, transparent)}:host(.s-accent.is-interactive:focus-visible){outline-color:var(--fold-color-on-primary)}\n"] });
1922
2052
  }
1923
2053
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldCardComponent, decorators: [{
1924
2054
  type: Component,
1925
2055
  args: [{ selector: "fold-card", standalone: true, host: {
1926
2056
  "[class.s-sunken]": "surface() === 'sunken'",
2057
+ "[class.s-accent]": "surface() === 'accent'",
2058
+ "[attr.data-surface]": "surface() === 'accent' ? 'accent' : null",
1927
2059
  "[class.r-sm]": "radius() === 'sm'",
1928
2060
  "[class.r-md]": "radius() === 'md'",
1929
2061
  "[class.p-none]": "padding() === 'none'",
1930
2062
  "[class.p-sm]": "padding() === 'sm'",
1931
2063
  "[class.p-lg]": "padding() === 'lg'",
1932
2064
  "[class.is-interactive]": "interactive()",
1933
- "[class.has-sep]": "separators()",
1934
- "[class.raised-bands]": "raisedBands()",
1935
- }, template: "<header class=\"card-header\"><ng-content select=\"[cardHeader]\" /></header>\n<div class=\"card-body\"><ng-content /></div>\n<footer class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></footer>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raised-bands) .card-header:not(:empty),:host(.raised-bands) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.has-sep) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.has-sep) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease,border-color .1s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}\n"] }]
1936
- }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], raisedBands: [{ type: i0.Input, args: [{ isSignal: true, alias: "raisedBands", required: false }] }] } });
2065
+ "[class.sep-header]": "hasBand(separators(), 'header')",
2066
+ "[class.sep-footer]": "hasBand(separators(), 'footer')",
2067
+ "[class.raise-header]": "hasBand(raisedBands(), 'header')",
2068
+ "[class.raise-footer]": "hasBand(raisedBands(), 'footer')",
2069
+ "[attr.role]": "interactive() ? 'button' : null",
2070
+ "[attr.tabindex]": "interactive() ? 0 : null",
2071
+ "[attr.aria-label]": "interactive() ? (ariaLabel() ?? null) : null",
2072
+ "(click)": "onActivate($event)",
2073
+ "(keydown)": "onKeydown($event)",
2074
+ }, template: "<div class=\"card-header\"><ng-content select=\"[cardHeader]\" /></div>\n<div class=\"card-body\"><ng-content /></div>\n<div class=\"card-footer\"><ng-content select=\"[cardFooter]\" /></div>\n", styles: ["@charset \"UTF-8\";:host{--_pad: 16px;--_chrome: 12px 16px;--_radius: var(--fold-radius-lg);--_band-raise: var(--fold-color-surface-hover);--_sep-line: var(--fold-color-border-subtle);display:flex;flex-direction:column;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--_radius);overflow:var(--fold-card-overflow, clip)}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle);--_band-raise: var(--fold-color-surface-card)}:host(.r-md){--_radius: var(--fold-radius-md)}:host(.r-sm){--_radius: var(--fold-radius-sm)}:host(.p-none){--_pad: 0}:host(.p-sm){--_pad: 10px}:host(.p-lg){--_pad: 20px}.card-header,.card-footer{position:relative;padding:var(--_chrome)}.card-header:empty,.card-footer:empty{display:none}.card-header{border-radius:var(--_radius) var(--_radius) 0 0}.card-footer{border-radius:0 0 var(--_radius) var(--_radius)}.card-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;padding:var(--fold-card-padding, var(--_pad))}:host(.raise-header) .card-header:not(:empty),:host(.raise-footer) .card-footer:not(:empty){background-color:var(--_band-raise)}:host(.sep-header) .card-header:not(:empty){border-bottom:1px solid var(--_sep-line)}:host(.sep-footer) .card-footer:not(:empty){border-top:1px solid var(--_sep-line)}:host(.is-interactive){cursor:pointer;transition:transform .18s ease,box-shadow .18s ease}:host(.is-interactive:hover){transform:translateY(-2px);box-shadow:var(--fold-shadow-md)}:host(.is-interactive:focus-visible){outline:2px solid var(--fold-color-primary);outline-offset:2px}@media(prefers-reduced-motion:reduce){:host(.is-interactive){transition:none}}:host(.s-accent){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary);--_band-raise: color-mix( in srgb, var(--_accent-ink) 16%, var(--_accent-fill) );--_sep-line: color-mix(in srgb, var(--_accent-ink) 24%, transparent)}:host(.s-accent.is-interactive:focus-visible){outline-color:var(--fold-color-on-primary)}\n"] }]
2075
+ }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], radius: [{ type: i0.Input, args: [{ isSignal: true, alias: "radius", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], raisedBands: [{ type: i0.Input, args: [{ isSignal: true, alias: "raisedBands", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
1937
2076
 
1938
2077
  /**
1939
2078
  * `<fold-element-title>` — the label that heads a section, card or panel. Fully
@@ -2034,7 +2173,7 @@ class FoldContextCardComponent {
2034
2173
  subtitle = input(/* @ts-ignore */
2035
2174
  ...(ngDevMode ? [undefined, { debugName: "subtitle" }] : /* istanbul ignore next */ []));
2036
2175
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldContextCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2037
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldContextCardComponent, isStandalone: true, selector: "fold-context-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<fold-card padding=\"none\">\n <div class=\"cc-head\">\n <fold-element-title\n variant=\"title\"\n [level]=\"3\"\n [icon]=\"icon()\"\n [iconTone]=\"iconTone()\"\n [title]=\"title()\"\n [subtitle]=\"subtitle()\"\n />\n </div>\n <div class=\"cc-body\"><ng-content /></div>\n <div class=\"cc-foot\"><ng-content select=\"[footer]\" /></div>\n</fold-card>\n", styles: ["@charset \"UTF-8\";:host{display:block}.cc-head{padding:16px;border-bottom:1px solid var(--fold-color-border-subtle)}.cc-body{padding:2px 16px}.cc-foot{display:flex;justify-content:center;padding:14px 16px;border-top:1px solid var(--fold-color-border-subtle)}.cc-foot:empty{display:none}\n"], dependencies: [{ kind: "component", type: FoldCardComponent, selector: "fold-card", inputs: ["surface", "radius", "padding", "interactive", "separators", "raisedBands"] }, { kind: "component", type: FoldElementTitleComponent, selector: "fold-element-title", inputs: ["icon", "iconTone", "title", "subtitle", "variant", "level", "headingId"] }] });
2176
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldContextCardComponent, isStandalone: true, selector: "fold-context-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<fold-card padding=\"none\">\n <div class=\"cc-head\">\n <fold-element-title\n variant=\"title\"\n [level]=\"3\"\n [icon]=\"icon()\"\n [iconTone]=\"iconTone()\"\n [title]=\"title()\"\n [subtitle]=\"subtitle()\"\n />\n </div>\n <div class=\"cc-body\"><ng-content /></div>\n <div class=\"cc-foot\"><ng-content select=\"[footer]\" /></div>\n</fold-card>\n", styles: ["@charset \"UTF-8\";:host{display:block}.cc-head{padding:16px;border-bottom:1px solid var(--fold-color-border-subtle)}.cc-body{padding:2px 16px}.cc-foot{display:flex;justify-content:center;padding:14px 16px;border-top:1px solid var(--fold-color-border-subtle)}.cc-foot:empty{display:none}\n"], dependencies: [{ kind: "component", type: FoldCardComponent, selector: "fold-card", inputs: ["surface", "radius", "padding", "interactive", "ariaLabel", "separators", "raisedBands"], outputs: ["activated"] }, { kind: "component", type: FoldElementTitleComponent, selector: "fold-element-title", inputs: ["icon", "iconTone", "title", "subtitle", "variant", "level", "headingId"] }] });
2038
2177
  }
2039
2178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldContextCardComponent, decorators: [{
2040
2179
  type: Component,
@@ -2283,6 +2422,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2283
2422
  * - `tone` — `accent` (default, brand link) · `muted` (secondary).
2284
2423
  * - `icon` / `trailingIcon` — optional glyphs around the label.
2285
2424
  * - `disabled` — button mode only.
2425
+ * - `target` / `rel` — anchor mode only. Setting `target="_blank"` auto-applies
2426
+ * `rel="noopener noreferrer"` (safe by default); override with an explicit
2427
+ * `rel`.
2286
2428
  *
2287
2429
  * @selector `fold-link`
2288
2430
  *
@@ -2291,7 +2433,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2291
2433
  * <fold-link icon="company" trailingIcon="chevron-right" (clicked)="openOrg()">
2292
2434
  * Voir l'organigramme
2293
2435
  * </fold-link>
2294
- * <fold-link href="https://sh3pherd.dev/docs" tone="muted">Documentation</fold-link>
2436
+ * <fold-link href="https://sh3pherd.dev/docs" target="_blank" tone="muted">
2437
+ * Documentation
2438
+ * </fold-link>
2295
2439
  * ```
2296
2440
  */
2297
2441
  class FoldLinkComponent {
@@ -2307,17 +2451,27 @@ class FoldLinkComponent {
2307
2451
  /** When set, the link renders as an `<a href>` instead of a button. */
2308
2452
  href = input(/* @ts-ignore */
2309
2453
  ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
2454
+ /** Anchor target (e.g. `_blank`). Anchor mode only. */
2455
+ target = input(/* @ts-ignore */
2456
+ ...(ngDevMode ? [undefined, { debugName: "target" }] : /* istanbul ignore next */ []));
2457
+ /** Anchor `rel`. Anchor mode only. Defaults to `noopener noreferrer` when
2458
+ * `target="_blank"`; set explicitly to override. */
2459
+ rel = input(/* @ts-ignore */
2460
+ ...(ngDevMode ? [undefined, { debugName: "rel" }] : /* istanbul ignore next */ []));
2310
2461
  /** Disable the button form (no effect on the `href` form). */
2311
2462
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2312
- /** Fires on click in the button form. */
2463
+ /** Fires on click in the button form; carries the `MouseEvent`. */
2313
2464
  clicked = output();
2465
+ /** Resolved `rel` — the explicit value, or a safe default for a new tab. */
2466
+ resolvedRel = computed(() => this.rel() ?? (this.target() === "_blank" ? "noopener noreferrer" : null), /* @ts-ignore */
2467
+ ...(ngDevMode ? [{ debugName: "resolvedRel" }] : /* istanbul ignore next */ []));
2314
2468
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2315
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldLinkComponent, isStandalone: true, selector: "fold-link", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { properties: { "class.tone-muted": "tone() === 'muted'" } }, ngImport: i0, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a class=\"lnk\" [href]=\"h\">\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
2469
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldLinkComponent, isStandalone: true, selector: "fold-link", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, rel: { classPropertyName: "rel", publicName: "rel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, host: { properties: { "class.tone-muted": "tone() === 'muted'" } }, ngImport: i0, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a\n class=\"lnk\"\n [href]=\"h\"\n [attr.target]=\"target() ?? null\"\n [attr.rel]=\"resolvedRel()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit($event)\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
2316
2470
  }
2317
2471
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldLinkComponent, decorators: [{
2318
2472
  type: Component,
2319
- args: [{ selector: "fold-link", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], host: { "[class.tone-muted]": "tone() === 'muted'" }, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a class=\"lnk\" [href]=\"h\">\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
2320
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
2473
+ args: [{ selector: "fold-link", standalone: true, imports: [NgTemplateOutlet, FoldIconComponent], host: { "[class.tone-muted]": "tone() === 'muted'" }, template: "<ng-template #inner>\n @if (icon(); as i) {\n <fold-icon [name]=\"i\" size=\"sm\" />\n }\n <span class=\"lnk-label\"><ng-content /></span>\n @if (trailingIcon(); as t) {\n <fold-icon [name]=\"t\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as h) {\n <a\n class=\"lnk\"\n [href]=\"h\"\n [attr.target]=\"target() ?? null\"\n [attr.rel]=\"resolvedRel()\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"lnk\"\n [disabled]=\"disabled()\"\n (click)=\"clicked.emit($event)\"\n >\n <ng-container [ngTemplateOutlet]=\"inner\" />\n </button>\n}\n", styles: [":host{display:inline-flex;min-width:0}.lnk{display:inline-flex;align-items:center;gap:6px;min-width:0;padding:0;border:0;background:none;font-family:inherit;font-size:var(--fold-text-xs);font-weight:600;color:var(--fold-color-primary);text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}:host(.tone-muted) .lnk{color:var(--fold-color-text-secondary)}.lnk:hover .lnk-label{text-decoration:underline}.lnk:disabled{color:var(--fold-color-text-muted);cursor:not-allowed}.lnk:disabled .lnk-label{text-decoration:none}.lnk-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
2474
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], trailingIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailingIcon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], rel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
2321
2475
 
2322
2476
  /**
2323
2477
  * `<fold-menu>` — a vertical icon navigation rail (the app's primary menu shell).
@@ -2421,7 +2575,7 @@ class FoldMenuComponent {
2421
2575
  this.expanded.update((v) => !v);
2422
2576
  }
2423
2577
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2424
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldMenuComponent, isStandalone: true, selector: "fold-menu", inputs: { collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, tint: { classPropertyName: "tint", publicName: "tint", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, togglePlacement: { classPropertyName: "togglePlacement", publicName: "togglePlacement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, host: { properties: { "class.expanded": "expanded()", "attr.data-tint": "tint()", "attr.data-level": "level()" } }, viewQueries: [{ propertyName: "headRef", first: true, predicate: ["head"], descendants: true, isSignal: true }, { propertyName: "footRef", first: true, predicate: ["foot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, 64px);height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2578
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldMenuComponent, isStandalone: true, selector: "fold-menu", inputs: { collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, tint: { classPropertyName: "tint", publicName: "tint", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, togglePlacement: { classPropertyName: "togglePlacement", publicName: "togglePlacement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expanded: "expandedChange" }, host: { properties: { "class.expanded": "expanded()", "attr.data-tint": "tint()", "attr.data-level": "level()" } }, viewQueries: [{ propertyName: "headRef", first: true, predicate: ["head"], descendants: true, isSignal: true }, { propertyName: "footRef", first: true, predicate: ["foot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, var(--fold-rail-primary, 64px));height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
2425
2579
  }
2426
2580
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldMenuComponent, decorators: [{
2427
2581
  type: Component,
@@ -2429,7 +2583,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2429
2583
  "[class.expanded]": "expanded()",
2430
2584
  "[attr.data-tint]": "tint()",
2431
2585
  "[attr.data-level]": "level()",
2432
- }, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, 64px);height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"] }]
2586
+ }, template: "<div\n class=\"menu-head\"\n [class.head-has-toggle]=\"collapsible() && resolvedPlacement() === 'header'\"\n #head\n>\n <ng-content select=\"[header]\" />\n @if (collapsible() && resolvedPlacement() === \"header\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</div>\n\n<nav class=\"menu-body\">\n <ng-content />\n @if (collapsible() && resolvedPlacement() === \"body\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n</nav>\n\n<div\n class=\"menu-foot\"\n [class.foot-has-toggle]=\"collapsible() && resolvedPlacement() === 'footer'\"\n #foot\n>\n @if (collapsible() && resolvedPlacement() === \"footer\") {\n <ng-container [ngTemplateOutlet]=\"toggleTpl\" />\n }\n <ng-content select=\"[footer]\" />\n</div>\n\n<ng-template #toggleTpl>\n <button\n type=\"button\"\n class=\"menu-toggle\"\n [attr.aria-label]=\"expanded() ? 'Collapse menu' : 'Expand menu'\"\n (click)=\"toggle()\"\n >\n <fold-icon\n [name]=\"expanded() ? 'chevron-left' : 'chevron-right'\"\n [size]=\"14\"\n />\n </button>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;align-items:center;width:var(--fold-shell-rail-width, var(--fold-rail-primary, 64px));height:100%;box-sizing:border-box;padding:10px 0;gap:4px;background:var(--fold-color-bg-rail-primary);border-right:1px solid var(--fold-color-border);overflow:visible;-webkit-user-select:none;user-select:none;transition:width var(--fold-motion-base)}:host([data-level=secondary]){background:var(--fold-color-bg-rail-secondary)}:host([data-level=tertiary]){background:var(--fold-color-bg-rail-tertiary)}:host([data-elevated]){border-right:none}:host(.expanded){width:max-content;min-width:190px;max-width:260px;align-items:stretch}.menu-head,.menu-foot{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}.menu-head:empty,.menu-foot:empty{display:none}.menu-head{padding-bottom:8px;border-bottom:1px solid var(--fold-color-border-subtle)}.menu-foot{padding-top:8px;border-top:1px solid var(--fold-color-border-subtle)}:host(.expanded) .menu-head,:host(.expanded) .menu-foot{align-items:stretch;width:auto;padding-left:8px;padding-right:8px}.menu-body{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;width:100%}:host(.expanded) .menu-body{align-items:stretch;width:auto;padding:0 8px;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.menu-toggle{flex-shrink:0;width:28px;height:28px;display:grid;place-items:center;border:none;border-radius:var(--fold-radius-sm);background:none;color:var(--fold-color-text-muted);cursor:pointer;transition:background var(--fold-motion-fast),color var(--fold-motion-fast)}.menu-toggle:hover{background:var(--fold-color-surface-hover);color:var(--fold-color-text-secondary)}:host(.expanded) .menu-toggle{align-self:flex-end;margin-right:8px}:host(.expanded) .menu-head.head-has-toggle,:host(.expanded) .menu-foot.foot-has-toggle{position:relative;padding-right:40px}:host(.expanded) .menu-head.head-has-toggle .menu-toggle,:host(.expanded) .menu-foot.foot-has-toggle .menu-toggle{position:absolute;right:8px;bottom:8px;margin:0}\n"] }]
2433
2587
  }], ctorParameters: () => [], propDecorators: { collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], tint: [{ type: i0.Input, args: [{ isSignal: true, alias: "tint", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], togglePlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "togglePlacement", required: false }] }], headRef: [{ type: i0.ViewChild, args: ["head", { isSignal: true }] }], footRef: [{ type: i0.ViewChild, args: ["foot", { isSignal: true }] }] } });
2434
2588
 
2435
2589
  /** Solid accent colour per semantic tone (drives the dot + follow-pill). */
@@ -2861,13 +3015,15 @@ class FoldHeroCardComponent {
2861
3015
  /** Add a primary accent bar down the left edge (composable with any surface). */
2862
3016
  accentBar = input(false, { ...(ngDevMode ? { debugName: "accentBar" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2863
3017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldHeroCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2864
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldHeroCardComponent, isStandalone: true, selector: "fold-hero-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, accentBar: { classPropertyName: "accentBar", publicName: "accentBar", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-primary": "surface() === 'primary'", "class.a-subtle": "accent() === 'subtle'", "class.a-gradient": "accent() === 'gradient'", "class.has-bar": "accentBar()", "class.p-sm": "padding() === 'sm'", "class.p-md": "padding() === 'md'" } }, ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;overflow:hidden;isolation:isolate;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);padding:20px}:host(.p-sm){padding:10px}:host(.p-md){padding:16px}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle)}:host(.s-primary){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary)}:host(.a-subtle):after,:host(.a-gradient):after{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none}:host(.a-subtle):after{background:radial-gradient(120% 140% at 100% 0%,color-mix(in srgb,var(--fold-color-primary) 10%,transparent),transparent 45%),linear-gradient(160deg,transparent 30%,color-mix(in srgb,var(--fold-color-surface-sunken) 50%,transparent))}:host(.a-gradient){border-color:var(--fold-color-primary-border)}:host(.a-gradient):after{background:radial-gradient(260px circle at 90% -10%,color-mix(in srgb,var(--fold-color-primary) 22%,transparent),transparent 68%),linear-gradient(180deg,color-mix(in srgb,var(--fold-color-primary) 8%,transparent),transparent 60%)}:host(.has-bar):before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(var(--fold-color-primary),color-mix(in srgb,var(--fold-color-primary) 30%,transparent))}\n"] });
3018
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldHeroCardComponent, isStandalone: true, selector: "fold-hero-card", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, accentBar: { classPropertyName: "accentBar", publicName: "accentBar", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.s-sunken": "surface() === 'sunken'", "class.s-primary": "surface() === 'primary'", "attr.data-surface": "surface() === 'primary' ? 'accent' : null", "class.a-subtle": "accent() === 'subtle'", "class.a-gradient": "accent() === 'gradient'", "class.has-bar": "accentBar()", "class.p-sm": "padding() === 'sm'", "class.p-md": "padding() === 'md'" } }, ngImport: i0, template: "<ng-content />\n", styles: ["@charset \"UTF-8\";:host{display:block;position:relative;overflow:hidden;isolation:isolate;background:var(--fold-color-surface-card);border:1px solid var(--fold-color-border);border-radius:var(--fold-radius-lg);padding:20px}:host(.p-sm){padding:10px}:host(.p-md){padding:16px}:host(.s-sunken){background:var(--fold-color-surface-sunken);border-color:var(--fold-color-border-subtle)}:host(.s-primary){background:var(--fold-color-primary);border-color:var(--fold-color-primary);color:var(--fold-color-on-primary)}:host(.a-subtle):after,:host(.a-gradient):after{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none}:host(.a-subtle):after{background:radial-gradient(120% 140% at 100% 0%,color-mix(in srgb,var(--fold-color-primary) 10%,transparent),transparent 45%),linear-gradient(160deg,transparent 30%,color-mix(in srgb,var(--fold-color-surface-sunken) 50%,transparent))}:host(.a-gradient){border-color:var(--fold-color-primary-border)}:host(.a-gradient):after{background:radial-gradient(260px circle at 90% -10%,color-mix(in srgb,var(--fold-color-primary) 22%,transparent),transparent 68%),linear-gradient(180deg,color-mix(in srgb,var(--fold-color-primary) 8%,transparent),transparent 60%)}:host(.has-bar):before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(var(--fold-color-primary),color-mix(in srgb,var(--fold-color-primary) 30%,transparent))}\n"] });
2865
3019
  }
2866
3020
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldHeroCardComponent, decorators: [{
2867
3021
  type: Component,
2868
3022
  args: [{ selector: "fold-hero-card", standalone: true, host: {
2869
3023
  "[class.s-sunken]": "surface() === 'sunken'",
2870
3024
  "[class.s-primary]": "surface() === 'primary'",
3025
+ // a solid primary ground is an accent surface: auto-invert the content too.
3026
+ "[attr.data-surface]": "surface() === 'primary' ? 'accent' : null",
2871
3027
  "[class.a-subtle]": "accent() === 'subtle'",
2872
3028
  "[class.a-gradient]": "accent() === 'gradient'",
2873
3029
  "[class.has-bar]": "accentBar()",
@@ -4698,23 +4854,26 @@ class FoldAsideLayoutComponent {
4698
4854
  }, /* @ts-ignore */
4699
4855
  ...(ngDevMode ? [{ debugName: "topOffsetCss" }] : /* istanbul ignore next */ []));
4700
4856
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAsideLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4701
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldAsideLayoutComponent, isStandalone: true, selector: "fold-aside-layout", inputs: { stackLeftFirst: { classPropertyName: "stackLeftFirst", publicName: "stackLeftFirst", isSignal: true, isRequired: false, transformFunction: null }, asideLeftLabel: { classPropertyName: "asideLeftLabel", publicName: "asideLeftLabel", isSignal: true, isRequired: false, transformFunction: null }, asideRightLabel: { classPropertyName: "asideRightLabel", publicName: "asideRightLabel", isSignal: true, isRequired: false, transformFunction: null }, topOffset: { classPropertyName: "topOffset", publicName: "topOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--fold-aside-layout-top": "topOffsetCss()", "class.stack-left-first": "stackLeftFirst()" } }, ngImport: i0, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] });
4857
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldAsideLayoutComponent, isStandalone: true, selector: "fold-aside-layout", inputs: { stackLeftFirst: { classPropertyName: "stackLeftFirst", publicName: "stackLeftFirst", isSignal: true, isRequired: false, transformFunction: null }, asideLeftLabel: { classPropertyName: "asideLeftLabel", publicName: "asideLeftLabel", isSignal: true, isRequired: false, transformFunction: null }, asideRightLabel: { classPropertyName: "asideRightLabel", publicName: "asideRightLabel", isSignal: true, isRequired: false, transformFunction: null }, topOffset: { classPropertyName: "topOffset", publicName: "topOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--fold-aside-layout-top": "topOffsetCss()", "class.stack-left-first": "stackLeftFirst()" } }, ngImport: i0, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] });
4702
4858
  }
4703
4859
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldAsideLayoutComponent, decorators: [{
4704
4860
  type: Component,
4705
4861
  args: [{ selector: "fold-aside-layout", standalone: true, host: {
4706
4862
  "[style.--fold-aside-layout-top]": "topOffsetCss()",
4707
4863
  "[class.stack-left-first]": "stackLeftFirst()",
4708
- }, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, 220px) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] }]
4864
+ }, template: "<div class=\"al-grid\">\n <div\n class=\"al-aside al-aside-left\"\n [attr.role]=\"asideLeftLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideLeftLabel() || null\"\n >\n <ng-content select=\"[asideLeft]\" />\n </div>\n <div class=\"al-center\"><ng-content /></div>\n <div\n class=\"al-aside al-aside-right\"\n [attr.role]=\"asideRightLabel() ? 'complementary' : null\"\n [attr.aria-label]=\"asideRightLabel() || null\"\n >\n <ng-content select=\"[asideRight]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;container-type:inline-size}.al-grid{display:grid;grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr));gap:var(--fold-aside-layout-gap, 28px);max-width:var(--fold-aside-layout-max, 1240px);margin-inline:auto;padding:var(--fold-aside-layout-pad, 28px 28px 64px)}:host:has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:has([asideleft]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr))}:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:var(--fold-aside-layout-rail-width, var(--fold-rail-secondary, 220px)) var(--fold-aside-layout-center-width, minmax(0, 1fr)) var(--fold-aside-layout-side-width, 300px)}:host:not(:has([asideleft])) .al-aside-left{display:none}:host:not(:has([asideright])) .al-aside-right{display:none}.al-center{min-width:0;display:flex;flex-direction:column;gap:var(--fold-aside-layout-stack, 18px)}.al-aside{--al-rail-top: var(--fold-aside-layout-top, 24px);align-self:start;position:sticky;top:var(--al-rail-top);display:flex;flex-direction:column;gap:var(--fold-aside-layout-rail-gap, 14px);min-width:0;max-height:var(--fold-aside-layout-rail-max, calc(100dvh - var(--al-rail-top) - 2rem));overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}.al-aside-left{--al-rail-top: var( --fold-aside-layout-left-top, var(--fold-aside-layout-top, 24px) )}.al-aside-right{--al-rail-top: var( --fold-aside-layout-right-top, var(--fold-aside-layout-top, 24px) )}@container (max-width: 1040px){.al-grid,:host:has([asideright]) .al-grid,:host:has([asideleft]) .al-grid,:host:has([asideleft]):has([asideright]) .al-grid{grid-template-columns:minmax(0,1fr)}.al-aside{position:static;max-height:none;overflow:visible}.al-center{order:1}.al-aside-left{order:2}.al-aside-right{order:3}:host(.stack-left-first) .al-aside-left{order:0}}@container (max-width: 700px){.al-grid{padding:var(--fold-aside-layout-pad-sm, 16px 14px 48px);gap:var(--fold-aside-layout-gap-sm, 16px)}}\n"] }]
4709
4865
  }], propDecorators: { stackLeftFirst: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackLeftFirst", required: false }] }], asideLeftLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "asideLeftLabel", required: false }] }], asideRightLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "asideRightLabel", required: false }] }], topOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "topOffset", required: false }] }] } });
4710
4866
 
4867
+ /** DI handle for the nearest {@link FoldNavLayoutComponent}, if any. */
4868
+ const FOLD_NAV_LAYOUT = new InjectionToken("FOLD_NAV_LAYOUT");
4869
+
4711
4870
  /**
4712
4871
  * Dead band (px) between folding and unfolding. Wider than any scrollbar, so the
4713
4872
  * width a fold gives back can never flip the layout straight back.
4714
4873
  */
4715
4874
  const HYSTERESIS = 32;
4716
4875
  /**
4717
- * `<fold-tab-layout>` — pairs a tab bar with the content it drives, and owns the
4876
+ * `<fold-nav-layout>` — pairs a tab bar with the content it drives, and owns the
4718
4877
  * one thing every tabbed page hand-rolls: where the nav sits.
4719
4878
  *
4720
4879
  * - `placement="top"` (default) — the nav above the content.
@@ -4722,39 +4881,62 @@ const HYSTERESIS = 32;
4722
4881
  * top when the layout gets narrower than {@link foldAt} (so the menu always
4723
4882
  * precedes the content it drives, never below it).
4724
4883
  *
4725
- * Content projection — the nav stays yours (tabs, active key, events):
4726
- * - `[tabNav]` → the tab bar (an `fold-tab-nav`, or anything else).
4727
- * - default slot → the content for the active tab.
4884
+ * Content projection — the bar stays yours (tabs, active key, events):
4885
+ * - `[tabNav]` → the bar. Project a {@link FoldViewNavComponent} if the tabs
4886
+ * **navigate** (route between views) or a {@link FoldTabsComponent} if they
4887
+ * **switch panels in place** — this layout is agnostic, it only places the bar.
4888
+ * - default slot → the content (routed views, or the tabs' `fold-tab-panel`s).
4728
4889
  *
4729
- * **A11y — this owns placement, not the ARIA tabs pattern.** The projected bar
4730
- * carries the `tablist`/`tab` roles; the content is yours. If the tabs switch
4731
- * in-page panels, give that content `role="tabpanel"` named by the active tab;
4732
- * if they navigate between routes, keep plain nav semantics and add no tab
4733
- * roles (the tabs pattern is for panel-switching, not navigation).
4890
+ * **A11y lives in the bar, not here.** `fold-view-nav` brings `<nav>` +
4891
+ * `aria-current`; `fold-tabs` brings the full `role="tablist"` widget with its
4892
+ * `fold-tab-panel`s. This component adds no tab semantics — it only decides where
4893
+ * the bar sits and folds it responsively.
4734
4894
  *
4735
- * A side rail needs a *vertical* bar, a folded one a *horizontal* bar. Rather
4736
- * than guess, the layout exposes {@link stacked} (`exportAs`) so the projected
4737
- * nav follows the layout in one binding:
4895
+ * A side rail needs a *vertical* bar, a folded one a *horizontal* bar. The
4896
+ * projected bar handles that itself with `direction="auto"` (its default) — it
4897
+ * reads this layout through DI ({@link FoldNavLayoutContext}), no wiring:
4738
4898
  *
4739
4899
  * ```html
4740
- * <fold-tab-layout placement="side" #tl="foldTabLayout">
4741
- * <fold-tab-nav
4742
- * tabNav
4743
- * [direction]="tl.stacked() ? 'horizontal' : 'vertical'"
4744
- * [tabs]="tabs"
4745
- * [activeKey]="tab()"
4746
- * (tabChange)="tab.set($event)"
4747
- * />
4748
- * <app-tab-content />
4749
- * </fold-tab-layout>
4900
+ * <fold-nav-layout placement="side">
4901
+ * <fold-view-nav tabNav [items]="items" />
4902
+ * <router-outlet />
4903
+ * </fold-nav-layout>
4750
4904
  * ```
4751
4905
  *
4752
- * Sizing is CSS custom properties: `--fold-tab-layout-gap` (16px) and
4753
- * `--fold-tab-layout-nav-width` (200px, the side rail track).
4906
+ * The layout also exposes {@link stacked} via `exportAs` for consumers that want
4907
+ * to read the folded state themselves.
4908
+ *
4909
+ * **Two roles — same component, composed differently.**
4910
+ * - **Page scaffold** — the tab rail *is* the page's primary structure: use it
4911
+ * directly (usually `placement="side"`), the folded bar leading the content.
4912
+ * - **Tabbed section** — a tabbed block *inside* a page, among other sections.
4913
+ * Don't reach for a new mode — wrap it in a {@link FoldPageSectionComponent}.
4914
+ * The section owns the title, the `<section>` + heading semantics, the vertical
4915
+ * rhythm and the optional `bleed`; tab-layout stays pure placement. It paints
4916
+ * nothing (no card), so it sits **flat** — add `bleed` on the section for an
4917
+ * edge-to-edge band. The rail folds on the **section's** width (it measures
4918
+ * itself), not the viewport, so nested tabs collapse on their own.
4754
4919
  *
4755
- * @selector `fold-tab-layout`
4920
+ * ```html
4921
+ * <!-- a tabbed section: flat structure (page-section) + tab placement -->
4922
+ * <fold-page-section title="Settings" bleed>
4923
+ * <fold-nav-layout placement="side">
4924
+ * <fold-view-nav tabNav [items]="items" />
4925
+ * <app-settings-panel />
4926
+ * </fold-nav-layout>
4927
+ * </fold-page-section>
4928
+ * ```
4929
+ *
4930
+ * Sizing is CSS custom properties: `--fold-nav-layout-gap` (16px) and
4931
+ * `--fold-nav-layout-rail-width` — the side-rail track, defaulting to the shared
4932
+ * `--fold-rail-tertiary` (200px). That's the **tertiary** step of the rail
4933
+ * hierarchy — the same level the `--fold-color-bg-rail-tertiary` colour names
4934
+ * (app menu → workspace → in-page nav) — so every rail in the app stays on one
4935
+ * scale. Override the local var per instance.
4936
+ *
4937
+ * @selector `fold-nav-layout`
4756
4938
  */
4757
- class FoldTabLayoutComponent {
4939
+ class FoldNavLayoutComponent {
4758
4940
  /** Where the nav sits: above the content, or as a rail beside it. */
4759
4941
  placement = input("top", /* @ts-ignore */
4760
4942
  ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
@@ -4795,14 +4977,40 @@ class FoldTabLayoutComponent {
4795
4977
  this.folded.set(false);
4796
4978
  }
4797
4979
  }
4798
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4799
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldTabLayoutComponent, isStandalone: true, selector: "fold-tab-layout", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, foldAt: { classPropertyName: "foldAt", publicName: "foldAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-row": "!stacked()" } }, exportAs: ["foldTabLayout"], ngImport: i0, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-tab-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-tab-layout-nav-width, 200px)}.tl-body{flex:1 1 auto;min-width:0}\n"] });
4980
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4981
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldNavLayoutComponent, isStandalone: true, selector: "fold-nav-layout", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, foldAt: { classPropertyName: "foldAt", publicName: "foldAt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-row": "!stacked()" } }, providers: [
4982
+ { provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
4983
+ ], exportAs: ["foldNavLayout"], ngImport: i0, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] });
4800
4984
  }
4801
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabLayoutComponent, decorators: [{
4985
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, decorators: [{
4802
4986
  type: Component,
4803
- args: [{ selector: "fold-tab-layout", standalone: true, exportAs: "foldTabLayout", host: { "[class.is-row]": "!stacked()" }, template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-tab-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-tab-layout-nav-width, 200px)}.tl-body{flex:1 1 auto;min-width:0}\n"] }]
4987
+ args: [{ selector: "fold-nav-layout", standalone: true, exportAs: "foldNavLayout", host: { "[class.is-row]": "!stacked()" }, providers: [
4988
+ { provide: FOLD_NAV_LAYOUT, useExisting: FoldNavLayoutComponent },
4989
+ ], template: "<div class=\"tl-nav\"><ng-content select=\"[tabNav]\" /></div>\n<div class=\"tl-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-nav-layout-gap, 16px);min-width:0}:host(.is-row){flex-direction:row}.tl-nav{display:grid;min-width:0}:host(.is-row) .tl-nav{flex:0 0 var(--fold-nav-layout-rail-width, var(--fold-rail-tertiary, 200px))}.tl-body{flex:1 1 auto;min-width:0}\n"] }]
4804
4990
  }], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], foldAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "foldAt", required: false }] }] } });
4805
4991
 
4992
+ /**
4993
+ * Marks a custom page title projected into {@link FoldPageLayoutComponent} — put
4994
+ * it on inline heading content when the plain `icon` + `title` inputs aren't
4995
+ * enough (an avatar, a two-tone title, a live status). It renders inside the
4996
+ * page's `<h1>`, so keep it inline and text-like. Its presence switches the
4997
+ * header on in place of the input-driven title.
4998
+ *
4999
+ * @example
5000
+ * ```html
5001
+ * <fold-page-layout>
5002
+ * <span pageTitle><fold-avatar … /> Acme Records</span>
5003
+ * </fold-page-layout>
5004
+ * ```
5005
+ */
5006
+ class FoldPageTitleDirective {
5007
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5008
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: FoldPageTitleDirective, isStandalone: true, selector: "[pageTitle]", ngImport: i0 });
5009
+ }
5010
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageTitleDirective, decorators: [{
5011
+ type: Directive,
5012
+ args: [{ selector: "[pageTitle]", standalone: true }]
5013
+ }] });
4806
5014
  /**
4807
5015
  * `<fold-page-layout>` — the vertical scaffold for a settings/admin-style page:
4808
5016
  * an optional `icon` + `title` header with a description slot, an optional
@@ -4824,6 +5032,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
4824
5032
  *
4825
5033
  * Content projection:
4826
5034
  * - default slot → the page body (sections, cards, banners…).
5035
+ * - `[pageTitle]` ({@link FoldPageTitleDirective}) → a custom title, rendered
5036
+ * inside the `<h1>` in place of the `icon` + `title` inputs, for when a plain
5037
+ * string won't do (an avatar, a two-tone title). Its presence alone switches
5038
+ * the header on.
4827
5039
  * - `[titleBadge]` → an inline pill beside the title (e.g. a status/kind badge).
4828
5040
  * - `p[description]` → the intro under the title. A **slot**, not a string
4829
5041
  * input: a description that needs a `<code>`, a link or a second sentence is
@@ -4854,13 +5066,17 @@ class FoldPageLayoutComponent {
4854
5066
  /** An optional leading icon shown beside the title. */
4855
5067
  icon = input(/* @ts-ignore */
4856
5068
  ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
5069
+ /** A projected `[pageTitle]`, if any — switches the header on for a custom
5070
+ * title even without the `title` input. */
5071
+ customTitle = contentChild(FoldPageTitleDirective, /* @ts-ignore */
5072
+ ...(ngDevMode ? [{ debugName: "customTitle" }] : /* istanbul ignore next */ []));
4857
5073
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4858
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldPageLayoutComponent, isStandalone: true, selector: "fold-page-layout", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (title()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n @if (title()) {\n <h1 class=\"page-title\">\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ title() }}</span>\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n }\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
5074
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldPageLayoutComponent, isStandalone: true, selector: "fold-page-layout", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "customTitle", first: true, predicate: FoldPageTitleDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@if (title() || customTitle()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n <h1 class=\"page-title\">\n @if (title(); as t) {\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ t }}</span>\n }\n <ng-content select=\"[pageTitle]\" />\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }] });
4859
5075
  }
4860
5076
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, decorators: [{
4861
5077
  type: Component,
4862
- args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], template: "@if (title()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n @if (title()) {\n <h1 class=\"page-title\">\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ title() }}</span>\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n }\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"] }]
4863
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
5078
+ args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], template: "@if (title() || customTitle()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n <h1 class=\"page-title\">\n @if (title(); as t) {\n @if (icon(); as ic) {\n <fold-icon class=\"page-icon\" [name]=\"ic\" [size]=\"22\" />\n }\n <span>{{ t }}</span>\n }\n <ng-content select=\"[pageTitle]\" />\n <span class=\"page-title-badge\"\n ><ng-content select=\"[titleBadge]\"\n /></span>\n </h1>\n <div class=\"page-desc\"><ng-content select=\"p[description]\" /></div>\n </div>\n <div class=\"page-actions\"><ng-content select=\"[pageActions]\" /></div>\n </header>\n}\n<div class=\"page-body\"><ng-content /></div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px);box-sizing:border-box;padding-block:var(--fold-page-pad-top, 28px) var(--fold-page-pad-bottom, 40px);padding-inline:var(--fold-page-gutter, 32px);flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.page-head-text{display:flex;flex-direction:column;gap:5px;min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:10px;font-size:var(--fold-text-xl);font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--fold-color-text)}.page-icon{flex:none;color:var(--fold-color-primary-text)}.page-title-badge{display:inline-flex;align-items:center;gap:6px}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:1.5;max-width:var(--fold-page-desc-measure, none);color:var(--fold-color-text-muted)}.page-desc:empty{display:none}.page-desc>*{margin:0}.page-actions{flex:none;display:inline-flex;align-items:center;gap:8px}.page-actions:empty{display:none}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"] }]
5079
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], customTitle: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldPageTitleDirective), { isSignal: true }] }] } });
4864
5080
 
4865
5081
  /**
4866
5082
  * `<fold-page-section>` — a titled, semantic **`<section>`** grouping of page
@@ -5128,64 +5344,285 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
5128
5344
  }], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: true }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], siblingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "siblingCount", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }] } });
5129
5345
 
5130
5346
  /**
5131
- * `<fold-tab-nav>` — a tab navigation bar (buttons only; the parent renders the
5132
- * content for the active key).
5347
+ * `<fold-view-nav>` — a **navigation** bar styled as tabs: a `<nav>` whose items
5348
+ * *go somewhere*. Give each item a `link` (or `href`) and it renders a real
5349
+ * `<a>` — so cmd/middle-click opens a new tab, the URL is a deep link, and the
5350
+ * active item is driven automatically by `routerLinkActive` +
5351
+ * `aria-current="page"`. Without a link an item is a `<button>` that emits
5352
+ * `activeChange` and reads its active state from `activeKey` (view switching
5353
+ * with no route).
5354
+ *
5355
+ * **Navigation, not the tabs widget.** Reach for this when a tab routes / switches
5356
+ * views. When tabs toggle **layered panels in place** (same URL), use
5357
+ * {@link FoldTabsComponent} — same look, but the ARIA Tabs pattern
5358
+ * (`role="tablist"`, arrow keys, `tabpanel`).
5133
5359
  *
5134
- * - `activeStyle` — `underline` (accent border on the active tab) or `fill`
5135
- * (accent background pill, good for sidebars).
5136
- * - `direction` — `horizontal` (row, equal-width) or `vertical` (stacked
5137
- * sidebar; auto-collapses to a horizontal icon-accordion at ≤768px).
5360
+ * - `activeStyle` — `underline` (accent border) or `fill` (accent pill).
5361
+ * - `direction` — `auto` (default: follows a wrapping `fold-nav-layout`, else
5362
+ * vertical), `vertical`, or `horizontal`.
5138
5363
  *
5139
- * @selector `fold-tab-nav`
5364
+ * @selector `fold-view-nav`
5140
5365
  *
5141
5366
  * @example
5142
5367
  * ```html
5143
- * <fold-tab-nav
5144
- * [tabs]="[{ key: 'members', label: 'Members', badge: 3 }, { key: 'settings', label: 'Settings' }]"
5145
- * [activeKey]="tab()"
5146
- * activeStyle="underline"
5147
- * direction="horizontal"
5148
- * (tabChange)="tab.set($event)"
5149
- * />
5368
+ * <!-- real navigation: link items, active state automatic -->
5369
+ * <fold-view-nav [items]="[
5370
+ * { key: 'members', label: 'Members', link: 'members', badge: 3 },
5371
+ * { key: 'settings', label: 'Settings', link: 'settings' },
5372
+ * ]" />
5373
+ * <router-outlet />
5374
+ * ```
5375
+ */
5376
+ class FoldViewNavComponent {
5377
+ /** The items to render, in order. */
5378
+ items = input.required(/* @ts-ignore */
5379
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
5380
+ /**
5381
+ * The active item's `key` — for **button** items (no `link`). Link items ignore
5382
+ * it: their active state comes from the URL via `routerLinkActive`.
5383
+ */
5384
+ activeKey = input("", /* @ts-ignore */
5385
+ ...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
5386
+ /** How the active item reads: accent underline, or accent fill. */
5387
+ activeStyle = input("underline", /* @ts-ignore */
5388
+ ...(ngDevMode ? [{ debugName: "activeStyle" }] : /* istanbul ignore next */ []));
5389
+ /**
5390
+ * Bar orientation. Defaults to `auto`: inside a `fold-nav-layout` it follows
5391
+ * the layout (vertical rail, horizontal once folded) with no wiring; on its
5392
+ * own it is `vertical` — the readable-first shape, reading as the app's
5393
+ * **third navigation rail** (app menu → workspace → in-page views, the
5394
+ * `--fold-rail-tertiary` level). Force `horizontal` for a page-level top bar.
5395
+ */
5396
+ direction = input("auto", /* @ts-ignore */
5397
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
5398
+ /**
5399
+ * Density — pure padding/typography:
5400
+ * - `compact` (default) — inline / popover bars.
5401
+ * - `comfortable` — a prominent, page-level bar.
5402
+ */
5403
+ size = input("compact", /* @ts-ignore */
5404
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
5405
+ /**
5406
+ * Collapse the bar to icons (independent of {@link size}). **Horizontal**, an
5407
+ * icon accordion: every item but the active one drops to its icon, the active
5408
+ * one keeps its label. **Vertical**, a collapsed icon rail (like a folded
5409
+ * menu): every item shows just its icon, its label a hover/focus tooltip and
5410
+ * its count a corner bubble — narrow the layout's `--fold-nav-layout-rail-width`
5411
+ * to match. An item with no icon falls back to a square glyph of its label's
5412
+ * initial, so the rail stays icon-width whether or not items carry icons.
5413
+ * Toggle it from your own breakpoint when the bar is too tight.
5414
+ */
5415
+ collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5416
+ /**
5417
+ * `transparent` (default — blends with the app, so the items read directly) or
5418
+ * `surface` (a filled bar that carries its own rail background).
5419
+ */
5420
+ background = input("transparent", /* @ts-ignore */
5421
+ ...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
5422
+ /** Emits the `key` of a clicked **button** item (link items just navigate). */
5423
+ activeChange = output();
5424
+ /** The nearest layout, if any — lets `direction="auto"` follow it. */
5425
+ layout = inject(FOLD_NAV_LAYOUT, { optional: true });
5426
+ /** `direction` with `auto` resolved against the wrapping layout. */
5427
+ resolvedDirection = computed(() => {
5428
+ const d = this.direction();
5429
+ if (d !== "auto") {
5430
+ return d;
5431
+ }
5432
+ return this.layout?.stacked() ? "horizontal" : "vertical";
5433
+ }, /* @ts-ignore */
5434
+ ...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
5435
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5436
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldViewNavComponent, isStandalone: true, selector: "fold-view-nav", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeChange: "activeChange" }, ngImport: i0, template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeChange.emit(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
5437
+ }
5438
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldViewNavComponent, decorators: [{
5439
+ type: Component,
5440
+ args: [{ selector: "fold-view-nav", standalone: true, imports: [
5441
+ NgTemplateOutlet,
5442
+ FoldIconComponent,
5443
+ FoldBadgeComponent,
5444
+ RouterLink,
5445
+ RouterLinkActive,
5446
+ ], template: "<nav\n class=\"tab-bar\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (item of items(); track item.key) {\n @if (item.disabled) {\n <button\n type=\"button\"\n class=\"tab-bar-item is-disabled\"\n disabled\n aria-disabled=\"true\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: false }\"\n />\n </button>\n } @else if (item.link !== undefined) {\n <a\n class=\"tab-bar-item\"\n [routerLink]=\"item.link\"\n routerLinkActive=\"is-active\"\n #rla=\"routerLinkActive\"\n [attr.aria-current]=\"rla.isActive ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{ $implicit: item, active: rla.isActive }\"\n />\n </a>\n } @else if (item.href !== undefined) {\n <a\n class=\"tab-bar-item\"\n [href]=\"item.href\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"tab-bar-item\"\n [class.is-active]=\"activeKey() === item.key\"\n [attr.aria-current]=\"activeKey() === item.key ? 'page' : null\"\n (click)=\"activeChange.emit(item.key)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"body\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n active: activeKey() === item.key,\n }\"\n />\n </button>\n }\n }\n</nav>\n\n<!-- One item's inner content, shared by the link / button branches. -->\n<ng-template #body let-item let-active=\"active\">\n @if (item.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <!-- Collapsed needs a fixed glyph; an icon-less item falls back to its\n initial so the rail stays icon-width and the label still tooltips. -->\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n item.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ item.label }}</span>\n @if (item.badge !== undefined && item.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"item.badge + ''\"\n [variant]=\"active ? 'accent' : 'neutral'\"\n />\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"] }]
5447
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], activeChange: [{ type: i0.Output, args: ["activeChange"] }] } });
5448
+
5449
+ /**
5450
+ * `<fold-tabs>` — the **in-page tabs widget** (the ARIA Tabs pattern): a
5451
+ * `role="tablist"` of `role="tab"` buttons that switch layered panels **without
5452
+ * navigating**. Arrow keys move between tabs (roving tabindex — only the active
5453
+ * tab is in the Tab order), `Home`/`End` jump to the ends, and each tab is wired
5454
+ * to its panel via `aria-controls` ↔ `aria-labelledby`.
5455
+ *
5456
+ * Pair it with one {@link FoldTabPanelComponent} per key, handed the bar via a
5457
+ * template ref so the two coordinate across slots (e.g. inside a
5458
+ * `fold-nav-layout`). Same visual as {@link FoldViewNavComponent}; the
5459
+ * difference is **semantics** — use `fold-tabs` for in-page panel switching, and
5460
+ * `fold-view-nav` when the tabs actually navigate (route) between views.
5461
+ *
5462
+ * ```html
5463
+ * <fold-nav-layout>
5464
+ * <fold-tabs
5465
+ * nav
5466
+ * #t="foldTabs"
5467
+ * [tabs]="tabs"
5468
+ * [activeKey]="tab()"
5469
+ * (tabChange)="tab.set($event)"
5470
+ * />
5471
+ * <fold-tab-panel [tabs]="t" key="overview">…</fold-tab-panel>
5472
+ * <fold-tab-panel [tabs]="t" key="settings">…</fold-tab-panel>
5473
+ * </fold-nav-layout>
5150
5474
  * ```
5475
+ *
5476
+ * Same visual knobs as `fold-view-nav` (`activeStyle` · `direction` · `size` ·
5477
+ * `background`). Sizing shares the tab-bar tokens.
5478
+ *
5479
+ * @selector `fold-tabs`
5151
5480
  */
5152
- class FoldTabNavComponent {
5481
+ class FoldTabsComponent {
5153
5482
  /** The tabs to render, in order. */
5154
5483
  tabs = input.required(/* @ts-ignore */
5155
5484
  ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5156
- /** The `key` of the currently active tab. */
5485
+ /** The `key` of the active tab (the shown panel). */
5157
5486
  activeKey = input.required(/* @ts-ignore */
5158
5487
  ...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
5159
5488
  /** How the active tab reads: accent underline, or accent fill. */
5160
5489
  activeStyle = input("underline", /* @ts-ignore */
5161
5490
  ...(ngDevMode ? [{ debugName: "activeStyle" }] : /* istanbul ignore next */ []));
5162
- /** Bar orientation (vertical auto-collapses to horizontal on mobile). */
5163
- direction = input("horizontal", /* @ts-ignore */
5164
- ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
5165
5491
  /**
5166
- * Density, from tightest to roomiest:
5167
- * - `reduce` — an icon accordion: every tab but the active one drops its label
5168
- * (and badge) to just its icon, while the active tab keeps its label and
5169
- * takes the remaining room. A tab with no icon keeps its label, so nothing
5170
- * is left unlabelled. Swap to it from your own breakpoint when the bar is
5171
- * too tight for every label.
5172
- * - `compact` (default) — inline / popover bars.
5173
- * - `comfortable` — a prominent, page-level bar.
5492
+ * Bar orientation (drives `aria-orientation`). Defaults to `auto`: follows a
5493
+ * wrapping `fold-nav-layout`, else `horizontal`.
5174
5494
  */
5495
+ direction = input("auto", /* @ts-ignore */
5496
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
5497
+ /** Density — see {@link FoldViewNavComponent} for the same scale. */
5175
5498
  size = input("compact", /* @ts-ignore */
5176
5499
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
5177
- /** `surface` (filled bar, default) or `transparent` (blends with the app). */
5500
+ /** Collapse to icons — see {@link FoldViewNavComponent.collapsed}. */
5501
+ collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5502
+ /** `surface` (filled bar, default) or `transparent`. */
5178
5503
  background = input("surface", /* @ts-ignore */
5179
5504
  ...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
5180
- /** Emits the `key` of the clicked tab. */
5505
+ /** Emits the newly-selected tab's `key`. */
5181
5506
  tabChange = output();
5182
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabNavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldTabNavComponent, isStandalone: true, selector: "fold-tab-nav", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: true, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange" }, ngImport: i0, template: "<nav\n class=\"tab-nav\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"direction() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.size-reduce]=\"size() === 'reduce'\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n type=\"button\"\n class=\"tab-nav-item\"\n [class.is-active]=\"activeKey() === tab.key\"\n (click)=\"tabChange.emit(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-nav-icon\" [name]=\"ic\" size=\"sm\" />\n }\n <span class=\"tab-nav-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-nav-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</nav>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-nav{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-nav-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-size:10px;font-weight:600;cursor:pointer;text-align:center;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-nav-item:hover{color:var(--fold-color-text-secondary)}.size-comfortable .tab-nav-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-nav-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-nav-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-nav-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-nav-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-nav-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-nav-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-nav-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-nav-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-nav-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.size-reduce .tab-nav-item{flex:0 0 auto;gap:0}.size-reduce .tab-nav-item:not(.is-active) .tab-nav-icon~.tab-nav-label,.size-reduce .tab-nav-item:not(.is-active) .tab-nav-badge{display:none}.size-reduce .tab-nav-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.size-reduce .tab-nav-item.is-active .tab-nav-label{overflow:hidden;text-overflow:ellipsis}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-nav-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-nav-item .tab-nav-label{display:none}.dir-vertical .tab-nav-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-nav-item.is-active .tab-nav-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-nav-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-nav-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-nav-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-nav-icon{flex-shrink:0}.tab-nav-badge{flex-shrink:0;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }] });
5507
+ /** The nearest layout, if any — lets `direction="auto"` follow it. */
5508
+ layout = inject(FOLD_NAV_LAYOUT, { optional: true });
5509
+ /** `direction` with `auto` resolved against the wrapping layout. */
5510
+ resolvedDirection = computed(() => {
5511
+ const d = this.direction();
5512
+ if (d !== "auto") {
5513
+ return d;
5514
+ }
5515
+ return this.layout?.stacked() ? "horizontal" : "vertical";
5516
+ }, /* @ts-ignore */
5517
+ ...(ngDevMode ? [{ debugName: "resolvedDirection" }] : /* istanbul ignore next */ []));
5518
+ /** Per-instance id root, so ids are unique + stable across SSR. */
5519
+ uid = inject(FoldIdService).next("fold-tabs");
5520
+ tabButtons = viewChildren("tabBtn", /* @ts-ignore */
5521
+ ...(ngDevMode ? [{ debugName: "tabButtons" }] : /* istanbul ignore next */ []));
5522
+ tabId(key) {
5523
+ return `${this.uid}-tab-${key}`;
5524
+ }
5525
+ panelId(key) {
5526
+ return `${this.uid}-panel-${key}`;
5527
+ }
5528
+ select(key) {
5529
+ this.tabChange.emit(key);
5530
+ }
5531
+ /**
5532
+ * Roving-tabindex keyboard model (APG Tabs, automatic activation): arrows move
5533
+ * — and select — the adjacent tab, `Home`/`End` the ends. Both axes work, so
5534
+ * it stays correct whether the bar is horizontal, vertical, or a
5535
+ * mobile-collapsed vertical.
5536
+ */
5537
+ onKeydown(event) {
5538
+ const keys = this.tabs().map((t) => t.key);
5539
+ if (keys.length === 0) {
5540
+ return;
5541
+ }
5542
+ const current = keys.indexOf(this.activeKey());
5543
+ let target;
5544
+ switch (event.key) {
5545
+ case "ArrowRight":
5546
+ case "ArrowDown":
5547
+ target = (current + 1) % keys.length;
5548
+ break;
5549
+ case "ArrowLeft":
5550
+ case "ArrowUp":
5551
+ target = (current - 1 + keys.length) % keys.length;
5552
+ break;
5553
+ case "Home":
5554
+ target = 0;
5555
+ break;
5556
+ case "End":
5557
+ target = keys.length - 1;
5558
+ break;
5559
+ default:
5560
+ return;
5561
+ }
5562
+ event.preventDefault();
5563
+ const key = keys[target];
5564
+ if (key === undefined) {
5565
+ return;
5566
+ }
5567
+ this.tabChange.emit(key);
5568
+ this.tabButtons()[target]?.nativeElement.focus();
5569
+ }
5570
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5571
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldTabsComponent, isStandalone: true, selector: "fold-tabs", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: true, transformFunction: null }, activeStyle: { classPropertyName: "activeStyle", publicName: "activeStyle", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange" }, viewQueries: [{ propertyName: "tabButtons", predicate: ["tabBtn"], descendants: true, isSignal: true }], exportAs: ["foldTabs"], ngImport: i0, template: "<div\n class=\"tab-bar\"\n role=\"tablist\"\n [attr.aria-orientation]=\"\n resolvedDirection() === 'vertical' ? 'vertical' : 'horizontal'\n \"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n (keydown)=\"onKeydown($event)\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"tab-bar-item\"\n [id]=\"tabId(tab.key)\"\n [class.is-active]=\"activeKey() === tab.key\"\n [attr.aria-selected]=\"activeKey() === tab.key\"\n [attr.aria-controls]=\"panelId(tab.key)\"\n [tabindex]=\"activeKey() === tab.key ? 0 : -1\"\n (click)=\"select(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n tab.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"], dependencies: [{ kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "title"] }, { kind: "component", type: FoldBadgeComponent, selector: "fold-badge", inputs: ["content", "radius", "variant"] }] });
5572
+ }
5573
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabsComponent, decorators: [{
5574
+ type: Component,
5575
+ args: [{ selector: "fold-tabs", standalone: true, exportAs: "foldTabs", imports: [FoldIconComponent, FoldBadgeComponent], template: "<div\n class=\"tab-bar\"\n role=\"tablist\"\n [attr.aria-orientation]=\"\n resolvedDirection() === 'vertical' ? 'vertical' : 'horizontal'\n \"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"resolvedDirection() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.is-collapsed]=\"collapsed()\"\n [class.bg-surface]=\"background() === 'surface'\"\n (keydown)=\"onKeydown($event)\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n #tabBtn\n type=\"button\"\n role=\"tab\"\n class=\"tab-bar-item\"\n [id]=\"tabId(tab.key)\"\n [class.is-active]=\"activeKey() === tab.key\"\n [attr.aria-selected]=\"activeKey() === tab.key\"\n [attr.aria-controls]=\"panelId(tab.key)\"\n [tabindex]=\"activeKey() === tab.key ? 0 : -1\"\n (click)=\"select(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-bar-icon\" [name]=\"ic\" size=\"sm\" />\n } @else if (collapsed()) {\n <span class=\"tab-bar-icon tab-bar-initial\" aria-hidden=\"true\">{{\n tab.label.charAt(0)\n }}</span>\n }\n <span class=\"tab-bar-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-bar-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-bar{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-bar-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;text-align:center;text-decoration:none;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-bar-item:hover{color:var(--fold-color-text-secondary)}.tab-bar-item.is-disabled{opacity:.5;cursor:default;pointer-events:none}.size-comfortable .tab-bar-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.dir-vertical.size-comfortable .tab-bar-item{padding:12px 14px;font-size:var(--fold-text-md)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-bar-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-bar-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-bar-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-bar-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-bar-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-bar-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.is-collapsed .tab-bar-item{position:relative;flex:0 0 auto;gap:0}.is-collapsed .tab-bar-item:not(.is-active) .tab-bar-badge{display:none}.is-collapsed .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.is-collapsed .tab-bar-item.is-active .tab-bar-label{overflow:hidden;text-overflow:ellipsis}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{position:absolute;padding:4px 8px;border-radius:var(--fold-radius-sm);background:var(--fold-color-glass);border:1px solid var(--fold-color-glass-border);color:var(--fold-color-text);font-size:var(--fold-text-xs);font-weight:500;white-space:nowrap;opacity:0;pointer-events:none;box-shadow:var(--fold-shadow-md);transition:opacity var(--fold-motion-fast);z-index:100}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-icon~.tab-bar-label{left:calc(100% + 8px);top:50%;transform:translateY(-50%)}.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active) .tab-bar-icon~.tab-bar-label{top:calc(100% + 6px);left:50%;transform:translate(-50%)}.dir-vertical.is-collapsed .tab-bar-item:hover .tab-bar-icon~.tab-bar-label,.dir-vertical.is-collapsed .tab-bar-item:focus-visible .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):hover .tab-bar-icon~.tab-bar-label,.is-collapsed:not(.dir-vertical) .tab-bar-item:not(.is-active):focus-visible .tab-bar-icon~.tab-bar-label{opacity:1}.dir-vertical.is-collapsed{align-items:center}.dir-vertical.is-collapsed .tab-bar-item,.dir-vertical.is-collapsed .tab-bar-item.is-active{justify-content:center;gap:0}.dir-vertical.is-collapsed .tab-bar-item .tab-bar-badge{display:block;position:absolute;top:-2px;right:-2px;transform:scale(.85);transform-origin:top right;pointer-events:none}:host:has(.is-collapsed){overflow:visible}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-bar-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-bar-item .tab-bar-label{display:none}.dir-vertical .tab-bar-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-bar-item.is-active .tab-bar-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-bar-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-bar-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-bar-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-bar-icon{flex-shrink:0}.tab-bar-initial{display:grid;place-items:center;width:18px;height:18px;border-radius:var(--fold-radius-sm);font-size:11px;font-weight:700;line-height:1;text-transform:uppercase}.tab-bar-badge{flex-shrink:0;font-variant-numeric:tabular-nums}.tab-bar-item:focus-visible{outline:2px solid var(--fold-color-primary);outline-offset:-2px;border-radius:var(--fold-radius-sm)}\n"] }]
5576
+ }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: true }] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], tabButtons: [{ type: i0.ViewChildren, args: ["tabBtn", { isSignal: true }] }] } });
5577
+
5578
+ /**
5579
+ * `<fold-tab-panel>` — one panel of a {@link FoldTabsComponent}. Handed the bar
5580
+ * via a template ref (`[tabs]="t"`, where `#t="foldTabs"`) plus its `key`, it
5581
+ * becomes a `role="tabpanel"` named by its tab (`aria-labelledby`), reachable by
5582
+ * `aria-controls` from that tab, focusable while active, and `hidden` otherwise.
5583
+ *
5584
+ * The ref pairing is what lets the tabs bar and its panels sit in **different**
5585
+ * slots (e.g. the bar in a `fold-nav-layout`'s nav slot, the panels in its
5586
+ * content) and still coordinate.
5587
+ *
5588
+ * ```html
5589
+ * <fold-tabs #t="foldTabs" [tabs]="tabs" [activeKey]="tab()" (tabChange)="tab.set($event)" />
5590
+ * <fold-tab-panel [tabs]="t" key="overview">…</fold-tab-panel>
5591
+ * ```
5592
+ *
5593
+ * @selector `fold-tab-panel`
5594
+ */
5595
+ class FoldTabPanelComponent {
5596
+ /** The owning `fold-tabs`, read via its template ref (`#t="foldTabs"`). */
5597
+ tabs = input.required(/* @ts-ignore */
5598
+ ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
5599
+ /** This panel's tab key — must match one tab in the bar. */
5600
+ key = input.required(/* @ts-ignore */
5601
+ ...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
5602
+ isActive = computed(() => this.tabs().activeKey() === this.key(), /* @ts-ignore */
5603
+ ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
5604
+ panelId = computed(() => this.tabs().panelId(this.key()), /* @ts-ignore */
5605
+ ...(ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []));
5606
+ labelledBy = computed(() => this.tabs().tabId(this.key()), /* @ts-ignore */
5607
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
5608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: FoldTabPanelComponent, isStandalone: true, selector: "fold-tab-panel", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tabpanel" }, properties: { "id": "panelId()", "attr.aria-labelledby": "labelledBy()", "attr.tabindex": "isActive() ? 0 : null", "hidden": "!isActive()" } }, ngImport: i0, template: "<ng-content />", isInline: true });
5184
5610
  }
5185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabNavComponent, decorators: [{
5611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabPanelComponent, decorators: [{
5186
5612
  type: Component,
5187
- args: [{ selector: "fold-tab-nav", standalone: true, imports: [FoldIconComponent, FoldBadgeComponent], template: "<nav\n class=\"tab-nav\"\n [class.style-underline]=\"activeStyle() === 'underline'\"\n [class.style-fill]=\"activeStyle() === 'fill'\"\n [class.dir-vertical]=\"direction() === 'vertical'\"\n [class.size-comfortable]=\"size() === 'comfortable'\"\n [class.size-reduce]=\"size() === 'reduce'\"\n [class.bg-surface]=\"background() === 'surface'\"\n>\n @for (tab of tabs(); track tab.key) {\n <button\n type=\"button\"\n class=\"tab-nav-item\"\n [class.is-active]=\"activeKey() === tab.key\"\n (click)=\"tabChange.emit(tab.key)\"\n >\n @if (tab.icon; as ic) {\n <fold-icon class=\"tab-nav-icon\" [name]=\"ic\" size=\"sm\" />\n }\n <span class=\"tab-nav-label\">{{ tab.label }}</span>\n @if (tab.badge !== undefined && tab.badge !== null) {\n <fold-badge\n class=\"tab-nav-badge\"\n [content]=\"tab.badge + ''\"\n [variant]=\"activeKey() === tab.key ? 'accent' : 'neutral'\"\n />\n }\n </button>\n }\n</nav>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100%;max-width:100%;overflow:hidden}.tab-nav{flex:1;display:flex;max-width:100%;gap:2px;-webkit-user-select:none;user-select:none}.bg-surface{background:var(--fold-color-bg-rail-tertiary);padding:4px 8px 0}.bg-surface.dir-vertical{padding:12px 8px}.tab-nav-item{flex:1;display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 8px;background:none;border:none;color:var(--fold-color-text-muted);font-size:10px;font-weight:600;cursor:pointer;text-align:center;transition:color .1s ease,background .1s ease,border-color .1s ease;white-space:nowrap}.tab-nav-item:hover{color:var(--fold-color-text-secondary)}.size-comfortable .tab-nav-item{flex:0 1 auto;gap:10px;padding:11px 16px;font-size:var(--fold-text-sm)}.style-underline{border-bottom:1px solid var(--fold-color-border)}.style-underline .tab-nav-item{border-bottom:2px solid transparent;margin-bottom:-1px}.style-underline .tab-nav-item.is-active{color:var(--fold-color-text);border-bottom-color:var(--fold-color-primary)}.style-fill .tab-nav-item{border-radius:var(--fold-radius-sm);border:1px solid transparent}.style-fill .tab-nav-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.style-fill .tab-nav-item:hover:not(.is-active){background:var(--fold-color-surface-hover);color:var(--fold-color-text)}.dir-vertical{flex-direction:column}.dir-vertical .tab-nav-item{flex:none;justify-content:flex-start;text-align:left;padding:9px 12px;gap:10px;border-radius:var(--fold-radius-sm);font-size:var(--fold-text-sm);font-weight:500}.dir-vertical.style-underline{border-bottom:none;border-right:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-nav-item{border-bottom:none;border-left:2px solid transparent;margin-bottom:0;margin-right:-1px}.dir-vertical.style-underline .tab-nav-item.is-active{border-left-color:var(--fold-color-primary)}.dir-vertical.style-fill .tab-nav-item.is-active{background:var(--fold-color-primary-surface);color:var(--fold-color-primary-text);font-weight:600}.size-reduce .tab-nav-item{flex:0 0 auto;gap:0}.size-reduce .tab-nav-item:not(.is-active) .tab-nav-icon~.tab-nav-label,.size-reduce .tab-nav-item:not(.is-active) .tab-nav-badge{display:none}.size-reduce .tab-nav-item.is-active{flex:1 1 auto;min-width:0;gap:7px}.size-reduce .tab-nav-item.is-active .tab-nav-label{overflow:hidden;text-overflow:ellipsis}@media(max-width:768px){.dir-vertical{flex-direction:row;align-items:center}.dir-vertical .tab-nav-item{flex:0 0 auto;justify-content:center;text-align:center;padding:8px;gap:0;font-size:var(--fold-text-xs);font-weight:600}.dir-vertical .tab-nav-item .tab-nav-label{display:none}.dir-vertical .tab-nav-item.is-active{flex:1 1 auto;min-width:0;gap:6px;padding:8px 12px}.dir-vertical .tab-nav-item.is-active .tab-nav-label{display:inline;overflow:hidden;text-overflow:ellipsis}.dir-vertical .tab-nav-badge{display:none}.dir-vertical.style-underline{border-right:none;border-bottom:1px solid var(--fold-color-border)}.dir-vertical.style-underline .tab-nav-item{border-left:none;border-bottom:2px solid transparent;margin-right:0;margin-bottom:-1px}.dir-vertical.style-underline .tab-nav-item.is-active{border-bottom-color:var(--fold-color-primary)}.dir-vertical.style-fill{border-right:none}}.tab-nav-icon{flex-shrink:0}.tab-nav-badge{flex-shrink:0;font-variant-numeric:tabular-nums}\n"] }]
5188
- }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: true }] }], activeStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeStyle", required: false }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }] } });
5613
+ args: [{
5614
+ selector: "fold-tab-panel",
5615
+ standalone: true,
5616
+ host: {
5617
+ role: "tabpanel",
5618
+ "[id]": "panelId()",
5619
+ "[attr.aria-labelledby]": "labelledBy()",
5620
+ "[attr.tabindex]": "isActive() ? 0 : null",
5621
+ "[hidden]": "!isActive()",
5622
+ },
5623
+ template: "<ng-content />",
5624
+ }]
5625
+ }], propDecorators: { tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: true }] }] } });
5189
5626
 
5190
5627
  /** Variant → leading status icon. */
5191
5628
  const VARIANT_ICON = {
@@ -5816,5 +6253,5 @@ class FoldPanelToggle {
5816
6253
  * Generated bundle index. Do not edit.
5817
6254
  */
5818
6255
 
5819
- export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_MOTION_TOKENS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalloutComponent, FoldCardComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDisclosureComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInputComponent, FoldLinkComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldNavLauncherComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective, FoldTabLayoutComponent, FoldTabNavComponent, FoldTimelineComponent, FoldToastComponent, FoldToastContainerComponent, FoldToastService, FoldToggleIconComponent, foldBlurVar, foldColorProperty, foldColorVar, foldHashSeed, foldMotionVar, foldRadiusVar, foldResolvePalette, foldShadowVar, foldSpaceVar, foldTextVar, observeElementWidth, provideFoldIcons, provideFoldPalette, provideFoldPanelLabels, provideFoldToasts };
6256
+ export { FOLD_AUTO_PALETTES, FOLD_BLUR_TOKENS, FOLD_BUILTIN_ICONS, FOLD_MOTION_TOKENS, FOLD_PALETTE_DEFAULT, FOLD_PANEL_CLOSE_LABEL, FOLD_RADIUS_TOKENS, FOLD_SEMANTIC_COLOR_TOKENS, FOLD_SHADOW_TOKENS, FOLD_SPACE_TOKENS, FOLD_TEXT_TOKENS, FOLD_TOAST_CONFIG, FoldAppShellComponent, FoldAsideLayoutComponent, FoldAvatarComponent, FoldAvatarDetailComponent, FoldAvatarListComponent, FoldBadgeComponent, FoldButtonComponent, FoldButtonIconComponent, FoldCalloutComponent, FoldCardComponent, FoldChoiceRowComponent, FoldContextCardComponent, FoldDataTableCellDirective, FoldDataTableComponent, FoldDisclosureComponent, FoldElementTitleComponent, FoldElevatedDirective, FoldEmptyStateComponent, FoldFieldComponent, FoldFieldIdDirective, FoldFieldListComponent, FoldFileDropzoneComponent, FoldHeroCardComponent, FoldHeroSectionComponent, FoldIconComponent, FoldIconRegistry, FoldIdService, FoldInputComponent, FoldLinkComponent, FoldLoadingStateComponent, FoldMenuComponent, FoldMenuItemComponent, FoldMenuSectionComponent, FoldMenuSeparatorComponent, FoldNavLauncherComponent, FoldNavLayoutComponent, FoldNavTileComponent, FoldNumberInputComponent, FoldPageLayoutComponent, FoldPageSectionComponent, FoldPageTitleDirective, FoldPaginatorComponent, FoldPaletteRegistry, FoldPanelComponentOutletDirective, FoldPanelHeaderComponent, FoldPanelHostComponent, FoldPanelHostService, FoldPanelRef, FoldPanelToggle, FoldRangeSliderComponent, FoldRepeatPressDirective, FoldSearchComponent, FoldSelectComponent, FoldSliderComponent, FoldSpinnerComponent, FoldStatusBadgeComponent, FoldStickyColumnDirective, FoldSurfaceDirective, FoldTabPanelComponent, FoldTabsComponent, FoldTimelineComponent, FoldToastComponent, FoldToastContainerComponent, FoldToastService, FoldToggleIconComponent, FoldViewNavComponent, foldBlurVar, foldColorProperty, foldColorVar, foldHashSeed, foldMotionVar, foldRadiusVar, foldResolvePalette, foldShadowVar, foldSpaceVar, foldTextVar, observeElementWidth, provideFoldIcons, provideFoldPalette, provideFoldPanelLabels, provideFoldToasts };
5820
6257
  //# sourceMappingURL=fold-ng.mjs.map