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.
- package/CHANGELOG.md +125 -1
- package/README.md +127 -49
- package/fesm2022/fold-ng.mjs +561 -124
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/tokens/primitives.css +19 -0
- package/tokens/scales.css +19 -0
- package/tokens/semantic.css +238 -0
- package/types/fold-ng.d.ts +401 -91
- package/types/fold-ng.d.ts.map +1 -1
package/fesm2022/fold-ng.mjs
CHANGED
|
@@ -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
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
const
|
|
651
|
-
|
|
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
|
-
:
|
|
657
|
+
: h;
|
|
656
658
|
if (!/^[0-9a-fA-F]{6}$/.test(full)) {
|
|
657
|
-
return
|
|
659
|
+
return null;
|
|
658
660
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
718
|
-
|
|
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 (
|
|
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 (
|
|
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-
|
|
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` —
|
|
1871
|
-
* - `separators` —
|
|
1872
|
-
*
|
|
1873
|
-
*
|
|
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)
|
|
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
|
-
/**
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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.
|
|
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.
|
|
1934
|
-
"[class.
|
|
1935
|
-
|
|
1936
|
-
|
|
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">
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
4726
|
-
* - `[tabNav]` → the
|
|
4727
|
-
*
|
|
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
|
|
4730
|
-
*
|
|
4731
|
-
*
|
|
4732
|
-
*
|
|
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.
|
|
4736
|
-
*
|
|
4737
|
-
*
|
|
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-
|
|
4741
|
-
* <fold-
|
|
4742
|
-
*
|
|
4743
|
-
*
|
|
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
|
-
*
|
|
4753
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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:
|
|
4799
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type:
|
|
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:
|
|
4985
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldNavLayoutComponent, decorators: [{
|
|
4802
4986
|
type: Component,
|
|
4803
|
-
args: [{ selector: "fold-
|
|
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
|
|
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
|
|
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-
|
|
5132
|
-
*
|
|
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
|
|
5135
|
-
*
|
|
5136
|
-
*
|
|
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-
|
|
5364
|
+
* @selector `fold-view-nav`
|
|
5140
5365
|
*
|
|
5141
5366
|
* @example
|
|
5142
5367
|
* ```html
|
|
5143
|
-
*
|
|
5144
|
-
*
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5147
|
-
*
|
|
5148
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
5167
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
5505
|
+
/** Emits the newly-selected tab's `key`. */
|
|
5181
5506
|
tabChange = output();
|
|
5182
|
-
|
|
5183
|
-
|
|
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:
|
|
5611
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldTabPanelComponent, decorators: [{
|
|
5186
5612
|
type: Component,
|
|
5187
|
-
args: [{
|
|
5188
|
-
|
|
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,
|
|
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
|