fold-ng 0.14.0 → 0.16.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 CHANGED
@@ -8,6 +8,49 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.16.0] - 2026-08-24
12
+
13
+ ### Added
14
+
15
+ - **`fold-page-layout` a un créneau `[pageSubtitle]`** — la ligne posée SERRÉE
16
+ sous le titre : les faits qui identifient la page (une référence, une famille,
17
+ un compte), pas de la prose. C'est un autre registre que `[description]`, et
18
+ c'est toute la raison d'être du créneau : une ligne de faits poussée dans la
19
+ description se lit avec l'espacement d'un paragraphe — et l'espacement d'un
20
+ paragraphe est une promesse de paragraphe. Les deux cohabitent : les faits
21
+ serrés, la prose avec sa propre respiration en dessous.
22
+
23
+ - **`fold-page-layout` gagne `separator`** — l'en-tête se ferme sur un filet.
24
+ Éteint par défaut : sur une page dont le corps est une pile de cartes, le
25
+ rythme de page sépare déjà, et le filet n'ajouterait que de l'encre. On
26
+ l'allume quand le corps arrive au ras de l'en-tête — un formulaire, un
27
+ tableau, des sections sans surface propre — là où l'en-tête se lirait sinon
28
+ comme la première ligne du contenu.
29
+
30
+ ## [0.15.0] - 2026-08-24
31
+
32
+ ### Added
33
+
34
+ - **`fold-page-layout` a un créneau `[pageEyebrow]`** — la rangée AU-DESSUS du
35
+ titre, pour un fil d'Ariane, un lien de retour, une étiquette de rubrique.
36
+ C'est le seul créneau d'en-tête dont la mise en page ne restyle pas le
37
+ contenu : un fil et une étiquette n'ont rien de typographique en commun, donc
38
+ ce qu'on y projette garde son visage. Un eyebrow seul **n'allume pas**
39
+ l'en-tête — une étiquette sans titre n'étiquette rien.
40
+
41
+ Ce que ça remplace : un lien « ← Retour » projeté dans `[pageActions]`, donc
42
+ peint en haut à DROITE alors qu'il désigne le haut de la hiérarchie. Il y
43
+ était faute de place pour l'écrire ailleurs.
44
+
45
+ - **`fold-breadcrumb` gagne `[currentPage]`** (défaut `true`, aucun changement).
46
+ À `false`, le fil ne nomme que les **ancêtres** : le dernier maillon se
47
+ comporte comme les autres — lien s'il porte une cible, texte sinon — et plus
48
+ rien ne porte `aria-current`. C'est la forme qu'un fil prend au-dessus d'un
49
+ `<h1>` : le titre de la page EST la page courante. Sans ça, un fil posé dans
50
+ `[pageEyebrow]` devait soit répéter le titre en dernier maillon (du bruit que
51
+ l'œil saute), soit désigner la famille comme page courante — ce qui raconte au
52
+ lecteur d'écran quelque chose de faux.
53
+
11
54
  ## [0.14.0] - 2026-08-23
12
55
 
13
56
  ### Changed
@@ -1912,7 +1955,9 @@ design-token stylesheet.
1912
1955
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
1913
1956
  strings localise via inputs / providers (`provideFoldPanelLabels`).
1914
1957
 
1915
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.14.0...HEAD
1958
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.16.0...HEAD
1959
+ [0.16.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.16.0
1960
+ [0.15.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.15.0
1916
1961
  [0.14.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.14.0
1917
1962
  [0.13.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.13.0
1918
1963
  [0.12.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.12.1
@@ -11517,11 +11517,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
11517
11517
  *
11518
11518
  * Content projection:
11519
11519
  * - default slot → the page body (sections, cards, banners…).
11520
+ * - `[pageEyebrow]` → the row **above** the title: a {@link FoldBreadcrumbComponent}
11521
+ * trail, a {@link FoldBackLinkComponent}, a kicker label. It is the one header
11522
+ * row the layout does not restyle — a trail and a kicker share no typography,
11523
+ * so what you project keeps its own face. A breadcrumb placed here should name
11524
+ * the **ancestors only** (`[currentPage]="false"`): the page's own `<h1>` is
11525
+ * the current page, and repeating it in the trail is noise. Projecting an
11526
+ * eyebrow alone does **not** switch the header on — an eyebrow without a title
11527
+ * is a label with nothing to label.
11520
11528
  * - `[pageTitle]` ({@link FoldPageTitleDirective}) → a custom title, rendered
11521
11529
  * inside the `<h1>` in place of the `icon` + `title` inputs, for when a plain
11522
11530
  * string won't do (an avatar, a two-tone title). Its presence alone switches
11523
11531
  * the header on.
11524
11532
  * - `[titleBadge]` → an inline pill beside the title (e.g. a status/kind badge).
11533
+ * - `[pageSubtitle]` → the line **directly under the title**, set tight against
11534
+ * it: the page's identifying FACTS (a reference, a category, a count), not
11535
+ * prose. It is a different register from `[description]` and that is the whole
11536
+ * reason it exists — a facts line pushed into the description slot reads with
11537
+ * a paragraph's spacing, which is a paragraph's promise. Both can appear: the
11538
+ * facts sit tight, the prose gets its own room below.
11525
11539
  * - `p[description]` → the intro under the title. A **slot**, not a string
11526
11540
  * input: a description that needs a `<code>`, a link or a second sentence is
11527
11541
  * the common case, not the exception. It must be a `<p description>` — the
@@ -11565,6 +11579,16 @@ class FoldPageLayoutComponent {
11565
11579
  /** An optional leading icon shown beside the title. */
11566
11580
  icon = input(/* @ts-ignore */
11567
11581
  ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
11582
+ /**
11583
+ * Draw a hairline rule under the header, closing it off from the body.
11584
+ *
11585
+ * Off by default: on a page whose body is a stack of cards, the header is
11586
+ * already separated by the page rhythm and a rule would just add ink. Turn it
11587
+ * on when the body runs flush against the header — a form, a table, sections
11588
+ * with no surface of their own — and the header would otherwise read as the
11589
+ * first row of the content.
11590
+ */
11591
+ separator = input(false, { ...(ngDevMode ? { debugName: "separator" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
11568
11592
  /**
11569
11593
  * Who owns the page's scroll:
11570
11594
  * - `"flow"` (default) — the page flows inside its host's scroll (the shell
@@ -11579,12 +11603,16 @@ class FoldPageLayoutComponent {
11579
11603
  customTitle = contentChild(FoldPageTitleDirective, /* @ts-ignore */
11580
11604
  ...(ngDevMode ? [{ debugName: "customTitle" }] : /* istanbul ignore next */ []));
11581
11605
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11582
- 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 }, scroll: { classPropertyName: "scroll", publicName: "scroll", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.title": "null", "attr.data-scroll": "scroll()" } }, 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 0 auto;min-height:0}:host([data-scroll=own]){flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--fold-space-lg)}.page-head-text{display:flex;flex-direction:column;gap:var(--fold-space-xs);min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:var(--fold-space-sm);font-size:var(--fold-text-xl);font-weight:var(--fold-weight-bold);letter-spacing:var(--fold-tracking-tighter);line-height:var(--fold-leading-tight);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:var(--fold-space-sm)}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:var(--fold-leading-normal);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;flex-wrap:wrap;gap:var(--fold-space-sm)}.page-actions:empty{display:none}@media(max-width:640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}@container (max-width: 640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}.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", "tone", "title"] }] });
11606
+ 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 }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, scroll: { classPropertyName: "scroll", publicName: "scroll", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.title": "null", "attr.data-scroll": "scroll()", "attr.data-separator": "separator() ? '' : 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 <div class=\"page-eyebrow\"><ng-content select=\"[pageEyebrow]\" /></div>\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-subtitle\"><ng-content select=\"[pageSubtitle]\" /></div>\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 0 auto;min-height:0}:host([data-scroll=own]){flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--fold-space-lg)}:host([data-separator]) .page-head{padding-block-end:var(--fold-space-lg);border-block-end:1px solid var(--fold-color-border)}.page-head-text{display:flex;flex-direction:column;gap:var(--fold-space-xs);min-width:0}.page-eyebrow{margin-block-end:var(--fold-space-xs)}.page-eyebrow:empty{display:none}.page-title{margin:0;display:flex;align-items:center;gap:var(--fold-space-sm);font-size:var(--fold-text-xl);font-weight:var(--fold-weight-bold);letter-spacing:var(--fold-tracking-tighter);line-height:var(--fold-leading-tight);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:var(--fold-space-sm)}.page-title-badge:empty{display:none}.page-subtitle{font-size:var(--fold-text-sm);line-height:var(--fold-leading-snug);color:var(--fold-color-text-muted)}.page-subtitle:empty{display:none}.page-subtitle>*{margin:0}.page-subtitle:not(:empty)+.page-desc{margin-block-start:var(--fold-space-xs)}.page-desc{font-size:var(--fold-text-sm);line-height:var(--fold-leading-normal);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;flex-wrap:wrap;gap:var(--fold-space-sm)}.page-actions:empty{display:none}@media(max-width:640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}@container (max-width: 640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}.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", "tone", "title"] }] });
11583
11607
  }
11584
11608
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldPageLayoutComponent, decorators: [{
11585
11609
  type: Component,
11586
- args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], host: { "[attr.title]": "null", "[attr.data-scroll]": "scroll()" }, 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 0 auto;min-height:0}:host([data-scroll=own]){flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--fold-space-lg)}.page-head-text{display:flex;flex-direction:column;gap:var(--fold-space-xs);min-width:0}.page-title{margin:0;display:flex;align-items:center;gap:var(--fold-space-sm);font-size:var(--fold-text-xl);font-weight:var(--fold-weight-bold);letter-spacing:var(--fold-tracking-tighter);line-height:var(--fold-leading-tight);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:var(--fold-space-sm)}.page-title-badge:empty{display:none}.page-desc{font-size:var(--fold-text-sm);line-height:var(--fold-leading-normal);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;flex-wrap:wrap;gap:var(--fold-space-sm)}.page-actions:empty{display:none}@media(max-width:640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}@container (max-width: 640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"] }]
11587
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], scroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "scroll", required: false }] }], customTitle: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldPageTitleDirective), { isSignal: true }] }] } });
11610
+ args: [{ selector: "fold-page-layout", standalone: true, imports: [FoldIconComponent], host: {
11611
+ "[attr.title]": "null",
11612
+ "[attr.data-scroll]": "scroll()",
11613
+ "[attr.data-separator]": "separator() ? '' : null",
11614
+ }, template: "@if (title() || customTitle()) {\n <header class=\"page-head\">\n <div class=\"page-head-text\">\n <div class=\"page-eyebrow\"><ng-content select=\"[pageEyebrow]\" /></div>\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-subtitle\"><ng-content select=\"[pageSubtitle]\" /></div>\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 0 auto;min-height:0}:host([data-scroll=own]){flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain}.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--fold-space-lg)}:host([data-separator]) .page-head{padding-block-end:var(--fold-space-lg);border-block-end:1px solid var(--fold-color-border)}.page-head-text{display:flex;flex-direction:column;gap:var(--fold-space-xs);min-width:0}.page-eyebrow{margin-block-end:var(--fold-space-xs)}.page-eyebrow:empty{display:none}.page-title{margin:0;display:flex;align-items:center;gap:var(--fold-space-sm);font-size:var(--fold-text-xl);font-weight:var(--fold-weight-bold);letter-spacing:var(--fold-tracking-tighter);line-height:var(--fold-leading-tight);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:var(--fold-space-sm)}.page-title-badge:empty{display:none}.page-subtitle{font-size:var(--fold-text-sm);line-height:var(--fold-leading-snug);color:var(--fold-color-text-muted)}.page-subtitle:empty{display:none}.page-subtitle>*{margin:0}.page-subtitle:not(:empty)+.page-desc{margin-block-start:var(--fold-space-xs)}.page-desc{font-size:var(--fold-text-sm);line-height:var(--fold-leading-normal);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;flex-wrap:wrap;gap:var(--fold-space-sm)}.page-actions:empty{display:none}@media(max-width:640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}@container (max-width: 640px){.page-head{flex-direction:column;align-items:stretch;gap:var(--fold-space-md)}.page-actions{justify-content:flex-start}:host{padding-inline:calc(var(--fold-page-gutter, 32px) / 2)}}.page-body{display:flex;flex-direction:column;gap:var(--fold-page-gap, 32px)}\n"] }]
11615
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], scroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "scroll", required: false }] }], customTitle: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FoldPageTitleDirective), { isSignal: true }] }] } });
11588
11616
 
11589
11617
  /**
11590
11618
  * `<fold-page-section>` — a titled, semantic **`<section>`** grouping of page
@@ -12297,7 +12325,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
12297
12325
  * works in a router app and degrades to plain anchors without one (importing it
12298
12326
  * never forces `@angular/router` — `RouterLink` is only instantiated on a crumb
12299
12327
  * that actually sets `routerLink`). The last item renders as the current page
12300
- * (`aria-current="page"`), not a link. Chevron separators are decorative.
12328
+ * (`aria-current="page"`), not a link unless the trail names ancestors ONLY,
12329
+ * for which see {@link currentPage}. Chevron separators are decorative.
12301
12330
  *
12302
12331
  * ```html
12303
12332
  * <fold-breadcrumb
@@ -12318,16 +12347,32 @@ class FoldBreadcrumbComponent {
12318
12347
  /** Accessible name of the navigation landmark. @default 'Breadcrumb' */
12319
12348
  ariaLabel = input("Breadcrumb", /* @ts-ignore */
12320
12349
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
12350
+ /**
12351
+ * Does the last crumb stand for the **current page**?
12352
+ *
12353
+ * `true` (default) — the trail ends *on* the page you are reading: the last
12354
+ * crumb is emphasised, never a link, and carries `aria-current="page"`.
12355
+ *
12356
+ * `false` — the trail names the **ancestors only**, and the page's own `<h1>`
12357
+ * is the current page (the shape a breadcrumb takes when it sits above a
12358
+ * title, e.g. in `fold-page-layout`'s `[pageEyebrow]`). The last crumb then
12359
+ * behaves like any other: a link when it carries a target, plain text when it
12360
+ * does not, and no `aria-current` anywhere — because claiming a parent is the
12361
+ * current page tells a screen reader something untrue, and repeating the
12362
+ * heading as a final crumb is noise a sighted reader has to skip.
12363
+ */
12364
+ currentPage = input(true, /* @ts-ignore */
12365
+ ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
12321
12366
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
12322
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldBreadcrumbComponent, isStandalone: true, selector: "fold-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "ariaLabel()" } }, ngImport: i0, template: "<ol class=\"bc-list\">\n @for (item of items(); track $index; let last = $last) {\n <li class=\"bc-item\">\n @if (last) {\n <span class=\"bc-node bc-current\" aria-current=\"page\">{{\n item.label\n }}</span>\n } @else if (item.routerLink !== undefined) {\n <a class=\"bc-node bc-link\" [routerLink]=\"item.routerLink\">{{\n item.label\n }}</a>\n } @else if (item.href !== undefined) {\n <a class=\"bc-node bc-link\" [href]=\"item.href\">{{ item.label }}</a>\n } @else {\n <span class=\"bc-node\">{{ item.label }}</span>\n }\n\n @if (!last) {\n <fold-icon\n class=\"bc-sep\"\n name=\"chevron-right\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n }\n </li>\n }\n</ol>\n", styles: ["@charset \"UTF-8\";:host{display:block}.bc-list{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem;margin:0;padding:0;list-style:none;font-size:var(--fold-text-sm, .875rem)}.bc-item{display:inline-flex;align-items:center;gap:.15rem;min-width:0}.bc-node{padding:.15rem .3rem;border-radius:var(--fold-radius-sm, 8px);color:var(--fold-color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bc-link{text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}.bc-link:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}.bc-link:focus-visible{outline:2px solid var(--fold-color-primary-border);outline-offset:0}@media(prefers-reduced-motion:reduce){.bc-link{transition:none}}.bc-current{color:var(--fold-color-text);font-weight:var(--fold-weight-semibold)}.bc-sep{flex:none;color:var(--fold-color-text-faded)}@media(forced-colors:active){.bc-link:hover{background:Highlight;color:HighlightText}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12367
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FoldBreadcrumbComponent, isStandalone: true, selector: "fold-breadcrumb", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "navigation" }, properties: { "attr.aria-label": "ariaLabel()" } }, ngImport: i0, template: "<ol class=\"bc-list\">\n @for (item of items(); track $index; let last = $last) {\n <li class=\"bc-item\">\n @if (last && currentPage()) {\n <span class=\"bc-node bc-current\" aria-current=\"page\">{{\n item.label\n }}</span>\n } @else if (item.routerLink !== undefined) {\n <a class=\"bc-node bc-link\" [routerLink]=\"item.routerLink\">{{\n item.label\n }}</a>\n } @else if (item.href !== undefined) {\n <a class=\"bc-node bc-link\" [href]=\"item.href\">{{ item.label }}</a>\n } @else {\n <span class=\"bc-node\">{{ item.label }}</span>\n }\n\n @if (!last) {\n <fold-icon\n class=\"bc-sep\"\n name=\"chevron-right\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n }\n </li>\n }\n</ol>\n", styles: ["@charset \"UTF-8\";:host{display:block}.bc-list{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem;margin:0;padding:0;list-style:none;font-size:var(--fold-text-sm, .875rem)}.bc-item{display:inline-flex;align-items:center;gap:.15rem;min-width:0}.bc-node{padding:.15rem .3rem;border-radius:var(--fold-radius-sm, 8px);color:var(--fold-color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bc-link{text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}.bc-link:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}.bc-link:focus-visible{outline:2px solid var(--fold-color-primary-border);outline-offset:0}@media(prefers-reduced-motion:reduce){.bc-link{transition:none}}.bc-current{color:var(--fold-color-text);font-weight:var(--fold-weight-semibold)}.bc-sep{flex:none;color:var(--fold-color-text-faded)}@media(forced-colors:active){.bc-link:hover{background:Highlight;color:HighlightText}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: FoldIconComponent, selector: "fold-icon", inputs: ["name", "size", "tone", "title"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12323
12368
  }
12324
12369
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FoldBreadcrumbComponent, decorators: [{
12325
12370
  type: Component,
12326
12371
  args: [{ selector: "fold-breadcrumb", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, FoldIconComponent], host: {
12327
12372
  role: "navigation",
12328
12373
  "[attr.aria-label]": "ariaLabel()",
12329
- }, template: "<ol class=\"bc-list\">\n @for (item of items(); track $index; let last = $last) {\n <li class=\"bc-item\">\n @if (last) {\n <span class=\"bc-node bc-current\" aria-current=\"page\">{{\n item.label\n }}</span>\n } @else if (item.routerLink !== undefined) {\n <a class=\"bc-node bc-link\" [routerLink]=\"item.routerLink\">{{\n item.label\n }}</a>\n } @else if (item.href !== undefined) {\n <a class=\"bc-node bc-link\" [href]=\"item.href\">{{ item.label }}</a>\n } @else {\n <span class=\"bc-node\">{{ item.label }}</span>\n }\n\n @if (!last) {\n <fold-icon\n class=\"bc-sep\"\n name=\"chevron-right\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n }\n </li>\n }\n</ol>\n", styles: ["@charset \"UTF-8\";:host{display:block}.bc-list{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem;margin:0;padding:0;list-style:none;font-size:var(--fold-text-sm, .875rem)}.bc-item{display:inline-flex;align-items:center;gap:.15rem;min-width:0}.bc-node{padding:.15rem .3rem;border-radius:var(--fold-radius-sm, 8px);color:var(--fold-color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bc-link{text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}.bc-link:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}.bc-link:focus-visible{outline:2px solid var(--fold-color-primary-border);outline-offset:0}@media(prefers-reduced-motion:reduce){.bc-link{transition:none}}.bc-current{color:var(--fold-color-text);font-weight:var(--fold-weight-semibold)}.bc-sep{flex:none;color:var(--fold-color-text-faded)}@media(forced-colors:active){.bc-link:hover{background:Highlight;color:HighlightText}}\n"] }]
12330
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
12374
+ }, template: "<ol class=\"bc-list\">\n @for (item of items(); track $index; let last = $last) {\n <li class=\"bc-item\">\n @if (last && currentPage()) {\n <span class=\"bc-node bc-current\" aria-current=\"page\">{{\n item.label\n }}</span>\n } @else if (item.routerLink !== undefined) {\n <a class=\"bc-node bc-link\" [routerLink]=\"item.routerLink\">{{\n item.label\n }}</a>\n } @else if (item.href !== undefined) {\n <a class=\"bc-node bc-link\" [href]=\"item.href\">{{ item.label }}</a>\n } @else {\n <span class=\"bc-node\">{{ item.label }}</span>\n }\n\n @if (!last) {\n <fold-icon\n class=\"bc-sep\"\n name=\"chevron-right\"\n size=\"sm\"\n aria-hidden=\"true\"\n />\n }\n </li>\n }\n</ol>\n", styles: ["@charset \"UTF-8\";:host{display:block}.bc-list{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem;margin:0;padding:0;list-style:none;font-size:var(--fold-text-sm, .875rem)}.bc-item{display:inline-flex;align-items:center;gap:.15rem;min-width:0}.bc-node{padding:.15rem .3rem;border-radius:var(--fold-radius-sm, 8px);color:var(--fold-color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bc-link{text-decoration:none;cursor:pointer;transition:color var(--fold-motion-fast)}.bc-link:hover{color:var(--fold-color-text);background:var(--fold-color-surface-hover)}.bc-link:focus-visible{outline:2px solid var(--fold-color-primary-border);outline-offset:0}@media(prefers-reduced-motion:reduce){.bc-link{transition:none}}.bc-current{color:var(--fold-color-text);font-weight:var(--fold-weight-semibold)}.bc-sep{flex:none;color:var(--fold-color-text-faded)}@media(forced-colors:active){.bc-link:hover{background:Highlight;color:HighlightText}}\n"] }]
12375
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }] } });
12331
12376
 
12332
12377
  /**
12333
12378
  * `<fold-back-link>` — the “← Back” affordance for a detail page. Three modes,