ngwr 15.3.0 → 15.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.
Files changed (38) hide show
  1. package/bits/border-glow/styles/_rules.scss +60 -49
  2. package/bits/click-spark/click-spark.scss +5 -1
  3. package/bits/click-spark/styles/_index.scss +10 -15
  4. package/bits/click-spark/styles/_rules.scss +20 -0
  5. package/fesm2022/ngwr-bits-aurora.mjs +108 -52
  6. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  7. package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
  8. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  9. package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
  10. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  11. package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
  12. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  13. package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
  14. package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
  15. package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
  16. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  17. package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
  18. package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
  19. package/fesm2022/ngwr-bits-waves.mjs +10 -15
  20. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  21. package/fesm2022/ngwr-pipes.mjs +162 -51
  22. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  23. package/fesm2022/ngwr-statistic.mjs +11 -16
  24. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  25. package/fesm2022/ngwr-version.mjs +1 -1
  26. package/fesm2022/ngwr-version.mjs.map +1 -1
  27. package/llms-full.txt +2 -2
  28. package/package.json +1 -1
  29. package/schematics/use/symbol-map.json +1 -0
  30. package/skills/ngwr/SKILL.md +3 -1
  31. package/types/ngwr-bits-aurora.d.ts +24 -2
  32. package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
  33. package/types/ngwr-bits-border-glow.d.ts +10 -4
  34. package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
  35. package/types/ngwr-bits-ripple-grid.d.ts +7 -2
  36. package/types/ngwr-bits-splash-cursor.d.ts +2 -1
  37. package/types/ngwr-pipes.d.ts +142 -32
  38. package/types/ngwr-version.d.ts +1 -1
package/package.json CHANGED
@@ -1234,5 +1234,5 @@
1234
1234
  "sass": "./styles/_typography.scss"
1235
1235
  }
1236
1236
  },
1237
- "version": "15.3.0"
1237
+ "version": "15.4.0"
1238
1238
  }
@@ -157,6 +157,7 @@
157
157
  "WrPagination": "ngwr/pagination",
158
158
  "WrBytes": "ngwr/pipes",
159
159
  "WrDate": "ngwr/pipes",
160
+ "WrDuration": "ngwr/pipes",
160
161
  "WrMark": "ngwr/pipes",
161
162
  "WrNumber": "ngwr/pipes",
162
163
  "WrPlural": "ngwr/pipes",
@@ -111,7 +111,9 @@ guess gets backwards.
111
111
  through `ngwr/i18n`; every component takes overridable `*Label` inputs.
112
112
  - **Prefer the library over hand-rolling.** `ngwr/utils` (coercion, dom,
113
113
  keyboard, id, css-size, math), `ngwr/pipes` (`wrDate`, `wrBytes`,
114
- `wrTruncate`, `wrNumber`, `wrMark`, `wrPlural`, `wrRange`) and
114
+ `wrTruncate`, `wrNumber`, `wrMark`, `wrPlural`, `wrRange`, `wrDuration`,
115
+ and for a string built in TypeScript `wrFormatDate`, `wrFormatBytes`,
116
+ `wrFormatNumber`, `wrFormatPlural` and `wrFormatDuration`) and
115
117
  `ngwr/validators` already exist.
116
118
 
117
119
  ## Setting a component up
@@ -11,6 +11,13 @@ import * as i0 from "@angular/core";
11
11
  * box on the palette's own ground, which is what forced text and marks are chosen
12
12
  * against. The loop also stops off screen and with no box to draw on.
13
13
  *
14
+ * With no WebGL2 context, or a shader the driver refuses, nothing is thrown and
15
+ * the canvas stays empty. A context the browser takes away is rebuilt if it offers
16
+ * one back. A destroyed host hands its GPU resources back at once. Browsers keep a
17
+ * small number of live contexts and kill the oldest: Chromium frees the slot at that
18
+ * point, while WebKit frees it only when the context is collected and offers an
19
+ * evicted one no restore, so there a host that is not drawing can stay empty for good.
20
+ *
14
21
  * @example
15
22
  * ```html
16
23
  * <div style="position: relative; min-height: 24rem; overflow: hidden">
@@ -42,11 +49,11 @@ export declare class WrAurora {
42
49
  private readonly platform;
43
50
  private readonly isBrowser;
44
51
  private gl;
45
- private uniforms;
52
+ private gpu;
46
53
  private rafId;
47
54
  /** The shader time last drawn, so a resize can repaint the frame it just cleared. */
48
55
  private time;
49
- /** Flips once `boot()` has a context and a program — the loop waits for it. */
56
+ /** Flips once a program is built, and back when the context goes — everything that draws waits for it. */
50
57
  private readonly booted;
51
58
  /** Whether the host is in the viewport; stays `true` without an observer. */
52
59
  private readonly onScreen;
@@ -57,6 +64,21 @@ export declare class WrAurora {
57
64
  private readonly hasBox;
58
65
  constructor();
59
66
  private boot;
67
+ /**
68
+ * Compile, link and upload what one context needs, and make it current. Returns
69
+ * `null` when it cannot: on a lost context, where every call answers no and
70
+ * nothing is wrong with the shader, it says nothing.
71
+ */
72
+ private build;
73
+ private compile;
74
+ /**
75
+ * Give the GPU resources back at once rather than to the garbage collector:
76
+ * browsers keep a small number of live contexts and kill the oldest, so a
77
+ * navigating single-page app would lose the context of a page still on screen.
78
+ * Chromium frees the slot here too. WebKit holds it until the context is collected,
79
+ * and no API frees it sooner, so a still or parked host can still be evicted there.
80
+ */
81
+ private release;
60
82
  private pushUniforms;
61
83
  /**
62
84
  * Stop `index` as shader channels. Explicit input wins; otherwise the theme's
@@ -32,10 +32,13 @@ interface WrBorderGlowHarnessFilters extends BaseHarnessFilters {
32
32
  * {@link parseInlineDeclarations} for why a computed read would be a different, and
33
33
  * wrong, question.
34
34
  *
35
- * **A colour getter answering `null` means that variable is not on the host now**, which
36
- * is the contract when the input is unset or its value was refused: the stylesheet's
37
- * per-theme default takes over. An input set and then put back to `null` comes off the
38
- * host, so `null` says nothing about whether it was ever set.
35
+ * **A colour getter answering `null` means the input is not on the host now**, which is
36
+ * the contract when it is unset or its value was refused: the stylesheet's per-theme
37
+ * default takes over. An input set and then put back to `null` comes off the host, so
38
+ * `null` says nothing about whether it was ever set. Each colour input writes its own
39
+ * `--wr-border-glow-input-*` twin, and that twin is what these read — never the hook it
40
+ * stands in for (`--wr-border-glow-bg`, `-color`, `-gradient-*`), which stays the
41
+ * author's, so a value an author put there is not what a getter reports.
39
42
  *
40
43
  * There is deliberately no `getGlowOpacity()`, `isGlowVisible()` or `isHovered()`: the
41
44
  * visible strength is a `calc()` in the stylesheet gated behind `:not(:hover)`, and
@@ -81,7 +84,8 @@ export declare class WrBorderGlowHarness extends ComponentHarness {
81
84
  /** Strength of the interior soft-light bloom, again unitless. */
82
85
  getFillOpacity(): Promise<number>;
83
86
  /**
84
- * The card fill the author asked for, or `null` when they left it to the theme.
87
+ * The card fill `[backgroundColor]` asked for, or `null` when it left the fill to the
88
+ * `--wr-border-glow-bg` hook, and so to the theme.
85
89
  *
86
90
  * The distinction is the whole point: an unset `[backgroundColor]` means the surface
87
91
  * token decides, light or dark, and a card that starts declaring its own fill has
@@ -24,9 +24,11 @@ import * as i0 from "@angular/core";
24
24
  */
25
25
  export declare class WrBorderGlow {
26
26
  /**
27
- * Card background fill. When unset, uses the theme surface token. A hook set inline on
28
- * the same element is replaced by this input and does not come back when it is unset,
29
- * so set it from a stylesheet rule. The same goes for `glowColor` and `colors`.
27
+ * Card background fill. When unset, `--wr-border-glow-bg` decides, which defaults to
28
+ * the theme surface. Written to `--wr-border-glow-input-bg` rather than to the hook,
29
+ * so a `--wr-border-glow-bg` an author set on the element — inline or from a rule —
30
+ * stays there under this input and is read again when it is unset. The same goes
31
+ * for `glowColor` and `colors`.
30
32
  */
31
33
  readonly backgroundColor: import("@angular/core").InputSignal<string | null>;
32
34
  /** Corner radius in pixels. @default 28 */
@@ -80,7 +82,11 @@ export declare class WrBorderGlow {
80
82
  private readonly isBrowser;
81
83
  /** Whether a pointer is over the card — the loop holds still while it drives. */
82
84
  private pointerInside;
83
- /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */
85
+ /**
86
+ * Inline CSS variable bag — the colour inputs, recomputed when one changes. Every
87
+ * name in it is an `--wr-border-glow-input-*` twin that is there only while its
88
+ * input is set; the geometry, which is always set, is bound in `host`.
89
+ */
84
90
  protected readonly cssVars: import("@angular/core").Signal<Record<string, string>>;
85
91
  constructor();
86
92
  protected onPointerEnter(): void;
@@ -4,7 +4,8 @@ type WrFuzzyTextDirection = 'horizontal' | 'vertical' | 'both';
4
4
  * Canvas-rendered "fuzzy" text — each row/column of glyphs is offset by
5
5
  * a random amount per frame, giving the text a TV-static or motion-blur
6
6
  * appearance. Intensity ramps up on hover, click, and during periodic
7
- * glitch bursts (configurable).
7
+ * glitch bursts (configurable). Off screen, or drawn at no size, the loop
8
+ * stops asking for frames and resumes where it left off.
8
9
  *
9
10
  * @example
10
11
  * ```html
@@ -28,8 +28,8 @@ import * as i0 from "@angular/core";
28
28
  *
29
29
  * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in
30
30
  * it takes focus. The frame loop runs only while a ring is alive, and never
31
- * while the host is off screen; an idle ring comes due only while frames run,
32
- * so a hidden tab does not queue them up.
31
+ * while the host is off screen or has no box to draw on; an idle ring comes due
32
+ * only while frames run, so a hidden tab does not queue them up.
33
33
  *
34
34
  * Under `prefers-reduced-motion` one still grid is drawn, in the line colour,
35
35
  * with no ripples, no idle rings and no pointer listeners; it is drawn again
@@ -158,6 +158,11 @@ export declare class WrRippleGrid {
158
158
  private armIdle;
159
159
  /** Flips once `boot()` has a context and a box — everything after waits for it. */
160
160
  private readonly booted;
161
+ /**
162
+ * Whether the box has an area. The intersection observer calls a zero-area host
163
+ * intersecting and sends no entry when it gains a size, so this reads the box instead.
164
+ */
165
+ private readonly hasBox;
161
166
  constructor();
162
167
  private boot;
163
168
  /** Follow the pointer, keep the idle timer and draw on demand; returns what stops all three. */
@@ -6,7 +6,8 @@ import * as i0 from "@angular/core";
6
6
  * everywhere) and it tears down with the component.
7
7
  *
8
8
  * Renders nothing under `prefers-reduced-motion` or when WebGL is
9
- * unavailable.
9
+ * unavailable. Off screen, or with no box to draw on, the solver stops asking
10
+ * for frames and resumes where it left off.
10
11
  *
11
12
  * @example
12
13
  * ```html
@@ -1,6 +1,6 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { PipeTransform } from "@angular/core";
3
- import { WrDateFormat } from "ngwr/date";
3
+ import { WrDateAdapter, WrDateFormat } from "ngwr/date";
4
4
  import { SafeHtml } from "@angular/platform-browser";
5
5
  /**
6
6
  * Humanise a byte count using binary (1024-based) units, in the reader's locale.
@@ -36,34 +36,56 @@ import { SafeHtml } from "@angular/platform-browser";
36
36
  * which are public catalog keys a consumer may have overridden, so it renders
37
37
  * `KB` where this pipe renders `kB`. Retiring those keys would silently break
38
38
  * every override that had been translating them, which is a major-version move.
39
+ *
40
+ * {@link wrFormatBytes} is the same output as a function.
39
41
  */
40
42
  export declare class WrBytes implements PipeTransform {
41
43
  private readonly locale;
42
44
  transform(value: number | string | null | undefined, decimals?: number): string;
43
- /**
44
- * `Intl`'s NARROW unit, with one space put back between it and the number.
45
- *
46
- * Neither display mode is right on its own. `short` spells the smallest unit
47
- * as a word — "512 byte", "0 byte" in English, "0 Byte" in German — which is
48
- * both worse copy than the `B` this pipe has always rendered and a bigger
49
- * change than the fix is worth. `narrow` gives `B` / `Б` / `o` correctly and
50
- * then closes the gap: "1.2kB".
51
- *
52
- * So the parts are reassembled rather than the string. Going through
53
- * `formatToParts` is what keeps the ORDER the locale's own — Hebrew puts the
54
- * unit first, and hand-joining `${n} ${unit}` would silently straighten every
55
- * such locale into English order.
56
- */
57
- private format;
58
45
  static ɵfac: i0.ɵɵFactoryDeclaration<WrBytes, never>;
59
46
  static ɵpipe: i0.ɵɵPipeDeclaration<WrBytes, "wrBytes", true>;
60
47
  }
48
+ /**
49
+ * A byte count in binary (1024-based) units, in the given locale: the same
50
+ * output as the `wrBytes` pipe, for a string built in TypeScript. `null`,
51
+ * `undefined` and an empty string give an empty string; zero, a negative
52
+ * number and a value that is not a number give `0 B`.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * wrFormatBytes(1234, 'en-US'); // "1.2 kB"
57
+ * wrFormatBytes(1234567, 'fr-FR', 0); // "1 Mo"
58
+ * ```
59
+ */
60
+ export declare function wrFormatBytes(value: number | string | null | undefined, locale: string, decimals?: number): string;
61
+ /**
62
+ * A date through a `WrDateAdapter`: the same output as the `wrDate` pipe, for a
63
+ * string built in TypeScript. A date-only string (`'2026-01-01'`) is that day in
64
+ * the local zone, as `DatePipe` reads it. `null`, `undefined`, an empty string
65
+ * and a date the engine refuses give an empty string.
66
+ *
67
+ * The adapter stands where `wrFormatNumber` takes a locale, because the output
68
+ * is the adapter's: the native one formats in `WR_DATE_LOCALE`, and an app's own
69
+ * adapter formats however it formats. Neither can be rebuilt from a locale
70
+ * string, so the caller passes `inject(WrDateAdapter)` once.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * private readonly dates = inject(WrDateAdapter);
75
+ *
76
+ * wrFormatDate(row.createdAt, this.dates); // shortDate
77
+ * wrFormatDate(row.createdAt, this.dates, 'mediumDateTime'); // named key
78
+ * wrFormatDate(row.createdAt, this.dates, 'dd.MM.yyyy'); // raw token string
79
+ * ```
80
+ */
81
+ export declare function wrFormatDate(value: Date | string | number | null | undefined, adapter: WrDateAdapter<Date>, format?: WrDateFormat | (string & {})): string;
61
82
  /**
62
83
  * Locale-aware date formatting. Delegates to `WrDateAdapter` — the native one
63
84
  * unless the app provides its own — so both the named format keys
64
85
  * (`'shortDate'`, `'longDate'`, …) and raw token strings work with nothing to
65
86
  * set up. A date-only string (`'2026-01-01'`) is that day in the local zone,
66
- * as `DatePipe` reads it, not UTC midnight.
87
+ * as `DatePipe` reads it, not UTC midnight. {@link wrFormatDate} is the same
88
+ * output as a function.
67
89
  *
68
90
  * @example
69
91
  * ```html
@@ -78,6 +100,74 @@ export declare class WrDate implements PipeTransform {
78
100
  static ɵfac: i0.ɵɵFactoryDeclaration<WrDate, never>;
79
101
  static ɵpipe: i0.ɵɵPipeDeclaration<WrDate, "wrDate", true>;
80
102
  }
103
+ /** Word forms keyed by CLDR plural category. Only `other` is required. */
104
+ type WrPluralForms = Partial<Record<Intl.LDMLPluralRule, string>> & {
105
+ readonly other: string;
106
+ };
107
+ interface WrPluralOptions {
108
+ /** Prefix the formatted count before the word. @default true */
109
+ readonly includeValue?: boolean;
110
+ /** Locale override; defaults to Angular's `LOCALE_ID`. */
111
+ readonly locale?: string;
112
+ }
113
+ /** Built-in style shortcuts — pick a sensible `Intl.NumberFormat` preset by name. */
114
+ type WrNumberStyle = 'decimal' | 'percent' | 'currency';
115
+ /** How `wrDuration` writes a span: digits and colons (`1:30:00`), or units (`1 hr 30 min`). */
116
+ type WrDurationStyle = 'clock' | 'units';
117
+ /** The units a span of milliseconds splits into, largest first. */
118
+ type WrDurationUnit = 'day' | 'hour' | 'minute' | 'second' | 'millisecond';
119
+ /**
120
+ * Split a span of milliseconds into the units asked for. A unit left out is
121
+ * carried into the next larger one that is kept, so `['hour', 'minute',
122
+ * 'second']` over three days and an hour reads 73 hours; the smallest unit kept
123
+ * floors whatever remains. Shared with `wr-statistic-countdown`, whose format
124
+ * tokens pick the units.
125
+ *
126
+ * @internal
127
+ */
128
+ export declare function wrSplitDuration(ms: number, units: ReadonlySet<WrDurationUnit>): Readonly<Partial<Record<WrDurationUnit, number>>>;
129
+ /**
130
+ * A span of milliseconds as text, in the given locale.
131
+ *
132
+ * Two styles. `'clock'` is digits and colons: `1:30:00`, and `5:07` under an
133
+ * hour; days are carried into the hours, since a clock has no day digit, so two
134
+ * days read `48:00:00`. `'units'` is every non-zero unit as a word the locale
135
+ * writes, `1 hr 30 min` in English and `1 h 30 min` in French, through
136
+ * `Intl.NumberFormat`'s unit style and `Intl.ListFormat` — `Intl.DurationFormat`
137
+ * is above the browser floor. The digits are the locale's own in both styles.
138
+ *
139
+ * Whole seconds: a fraction of a second is floored in both styles. Zero reads
140
+ * `0:00` and `0 sec`. A negative span is its absolute value with the locale's
141
+ * minus sign in front, `-5:07`. `null`, `undefined`, an empty string and a value
142
+ * that is not a finite number give an empty string, as `wrFormatNumber` does.
143
+ *
144
+ * The same output as the `wrDuration` pipe, for a string built in TypeScript.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * wrFormatDuration(5_400_000, 'en-US'); // "1:30:00"
149
+ * wrFormatDuration(5_400_000, 'en-US', 'units'); // "1 hr 30 min"
150
+ * wrFormatDuration(307_000, 'fr-FR', 'units'); // "5 min 7 s"
151
+ * ```
152
+ */
153
+ export declare function wrFormatDuration(value: number | string | null | undefined, locale: string, style?: WrDurationStyle): string;
154
+ /**
155
+ * A span of milliseconds as clock digits or as units, in Angular's `LOCALE_ID`.
156
+ * See {@link wrFormatDuration} for the two styles and the edge cases.
157
+ *
158
+ * @example
159
+ * ```html
160
+ * {{ 5400000 | wrDuration }} <!-- "1:30:00" -->
161
+ * {{ 5400000 | wrDuration: 'units' }} <!-- "1 hr 30 min" -->
162
+ * {{ 307000 | wrDuration }} <!-- "5:07" -->
163
+ * ```
164
+ */
165
+ export declare class WrDuration implements PipeTransform {
166
+ private readonly locale;
167
+ transform(value: number | string | null | undefined, style?: WrDurationStyle): string;
168
+ static ɵfac: i0.ɵɵFactoryDeclaration<WrDuration, never>;
169
+ static ɵpipe: i0.ɵɵPipeDeclaration<WrDuration, "wrDuration", true>;
170
+ }
81
171
  /**
82
172
  * Wrap every occurrence of `query` in `<mark>` tags. Useful for highlighting
83
173
  * search-term matches in autocomplete results, lists, and table cells.
@@ -104,24 +194,29 @@ export declare class WrMark implements PipeTransform {
104
194
  static ɵfac: i0.ɵɵFactoryDeclaration<WrMark, never>;
105
195
  static ɵpipe: i0.ɵɵPipeDeclaration<WrMark, "wrMark", true>;
106
196
  }
107
- /** Word forms keyed by CLDR plural category. Only `other` is required. */
108
- type WrPluralForms = Partial<Record<Intl.LDMLPluralRule, string>> & {
109
- readonly other: string;
110
- };
111
- interface WrPluralOptions {
112
- /** Prefix the formatted count before the word. @default true */
113
- readonly includeValue?: boolean;
114
- /** Locale override; defaults to Angular's `LOCALE_ID`. */
115
- readonly locale?: string;
116
- }
117
- /** Built-in style shortcuts — pick a sensible `Intl.NumberFormat` preset by name. */
118
- type WrNumberStyle = 'decimal' | 'percent' | 'currency';
197
+ /**
198
+ * A number through `Intl.NumberFormat`, in the given locale: the same output as
199
+ * the `wrNumber` pipe, for a string built in TypeScript. Takes a style shortcut
200
+ * (with an optional currency code) or a full `Intl.NumberFormatOptions` object.
201
+ * `null`, `undefined`, an empty string and a value that is not a finite number
202
+ * give an empty string.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * wrFormatNumber(1234.5, 'en-US'); // "1,234.5"
207
+ * wrFormatNumber(0.875, 'en-US', 'percent'); // "87.5%"
208
+ * wrFormatNumber(19.99, 'de-DE', 'currency', 'EUR'); // "19,99 €"
209
+ * wrFormatNumber(19.99, 'en-US', { minimumFractionDigits: 2 });
210
+ * ```
211
+ */
212
+ export declare function wrFormatNumber(value: number | string | null | undefined, locale: string, styleOrOptions?: WrNumberStyle | Intl.NumberFormatOptions, currency?: string): string;
119
213
  /**
120
214
  * Locale-aware number formatting via `Intl.NumberFormat`. Uses Angular's
121
215
  * `LOCALE_ID` as the default locale.
122
216
  *
123
217
  * Accepts either a style shortcut (with optional currency code) or a full
124
218
  * `Intl.NumberFormatOptions` object for fine-grained control.
219
+ * {@link wrFormatNumber} is the same output as a function.
125
220
  *
126
221
  * @example
127
222
  * ```html
@@ -134,17 +229,32 @@ type WrNumberStyle = 'decimal' | 'percent' | 'currency';
134
229
  export declare class WrNumber implements PipeTransform {
135
230
  private readonly locale;
136
231
  transform(value: number | string | null | undefined, styleOrOptions?: WrNumberStyle | Intl.NumberFormatOptions, currency?: string): string;
137
- private toOptions;
138
232
  static ɵfac: i0.ɵɵFactoryDeclaration<WrNumber, never>;
139
233
  static ɵpipe: i0.ɵɵPipeDeclaration<WrNumber, "wrNumber", true>;
140
234
  }
235
+ /**
236
+ * A count and its word form through `Intl.PluralRules`, in the given locale:
237
+ * the same output as the `wrPlural` pipe, for a string built in TypeScript.
238
+ * `options.locale` still wins over `locale`, as it wins over `LOCALE_ID` in the
239
+ * pipe. `null`, `undefined` and a value that is not a finite number give an
240
+ * empty string.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * wrFormatPlural(1, 'en', { one: 'comment', other: 'comments' }); // "1 comment"
245
+ * wrFormatPlural(5, 'ru', { one: 'файл', few: 'файла', other: 'файлов' }); // "5 файлов"
246
+ * wrFormatPlural(5, 'en', forms, { includeValue: false }); // "comments"
247
+ * ```
248
+ */
249
+ export declare function wrFormatPlural(value: number | null | undefined, locale: string, forms: WrPluralForms, options?: WrPluralOptions): string;
141
250
  /**
142
251
  * Locale-aware pluralization via `Intl.PluralRules`. Picks the right word
143
252
  * form for the count — including languages with more than two forms
144
253
  * (Russian, Polish, Arabic, …) — without hand-rolled `n % 10` rules.
145
254
  *
146
255
  * Categories follow CLDR: `zero` / `one` / `two` / `few` / `many` /
147
- * `other`. Missing categories fall back to `other`.
256
+ * `other`. Missing categories fall back to `other`. {@link wrFormatPlural} is
257
+ * the same output as a function.
148
258
  *
149
259
  * @example
150
260
  * ```html
@@ -198,4 +308,4 @@ export declare class WrTruncate implements PipeTransform {
198
308
  static ɵfac: i0.ɵɵFactoryDeclaration<WrTruncate, never>;
199
309
  static ɵpipe: i0.ɵɵPipeDeclaration<WrTruncate, "wrTruncate", true>;
200
310
  }
201
- export type { WrNumberStyle, WrPluralForms, WrPluralOptions };
311
+ export type { WrDurationStyle, WrDurationUnit, WrNumberStyle, WrPluralForms, WrPluralOptions };
@@ -15,7 +15,7 @@ import { InjectionToken } from "@angular/core";
15
15
  * Distinct from `Version` exported by `@angular/core`, which reports the
16
16
  * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.
17
17
  */
18
- export declare const NGWR_VERSION = "15.3.0";
18
+ export declare const NGWR_VERSION = "15.4.0";
19
19
  /**
20
20
  * Injection token holding the ngwr library version string.
21
21
  *