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.
- package/bits/border-glow/styles/_rules.scss +60 -49
- package/bits/click-spark/click-spark.scss +5 -1
- package/bits/click-spark/styles/_index.scss +10 -15
- package/bits/click-spark/styles/_rules.scss +20 -0
- package/fesm2022/ngwr-bits-aurora.mjs +108 -52
- package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
- package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
- package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
- package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
- package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
- package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
- package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-bits-waves.mjs +10 -15
- package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +162 -51
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +11 -16
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/llms-full.txt +2 -2
- package/package.json +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +3 -1
- package/types/ngwr-bits-aurora.d.ts +24 -2
- package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
- package/types/ngwr-bits-border-glow.d.ts +10 -4
- package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
- package/types/ngwr-bits-ripple-grid.d.ts +7 -2
- package/types/ngwr-bits-splash-cursor.d.ts +2 -1
- package/types/ngwr-pipes.d.ts +142 -32
- package/types/ngwr-version.d.ts +1 -1
package/fesm2022/ngwr-pipes.mjs
CHANGED
|
@@ -16,30 +16,26 @@ const UNITS = [
|
|
|
16
16
|
"terabyte",
|
|
17
17
|
"petabyte"
|
|
18
18
|
];
|
|
19
|
+
function format(locale, value, unit, fractionDigits) {
|
|
20
|
+
const parts = new Intl.NumberFormat(locale, {
|
|
21
|
+
style: "unit",
|
|
22
|
+
unit: UNITS[unit],
|
|
23
|
+
unitDisplay: "narrow",
|
|
24
|
+
minimumFractionDigits: fractionDigits,
|
|
25
|
+
maximumFractionDigits: fractionDigits
|
|
26
|
+
}).formatToParts(value);
|
|
27
|
+
const at = parts.findIndex((part) => part.type === "unit");
|
|
28
|
+
if (at === -1) return parts.map((part) => part.value).join("");
|
|
29
|
+
const out = parts.map((part) => part.value);
|
|
30
|
+
const gap = at === 0 ? 1 : at - 1;
|
|
31
|
+
if (parts[gap]?.type === "literal") out[gap] = " ";
|
|
32
|
+
else out.splice(at === 0 ? 1 : at, 0, " ");
|
|
33
|
+
return out.join("");
|
|
34
|
+
}
|
|
19
35
|
var WrBytes = class WrBytes {
|
|
20
36
|
locale = inject(LOCALE_ID);
|
|
21
37
|
transform(value, decimals = 1) {
|
|
22
|
-
|
|
23
|
-
const n = Number(value);
|
|
24
|
-
if (!Number.isFinite(n) || n <= 0) return this.format(0, 0, 0);
|
|
25
|
-
const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);
|
|
26
|
-
return this.format(n / 1024 ** i, i, i === 0 ? 0 : Math.max(0, decimals));
|
|
27
|
-
}
|
|
28
|
-
format(value, unit, fractionDigits) {
|
|
29
|
-
const parts = new Intl.NumberFormat(this.locale, {
|
|
30
|
-
style: "unit",
|
|
31
|
-
unit: UNITS[unit],
|
|
32
|
-
unitDisplay: "narrow",
|
|
33
|
-
minimumFractionDigits: fractionDigits,
|
|
34
|
-
maximumFractionDigits: fractionDigits
|
|
35
|
-
}).formatToParts(value);
|
|
36
|
-
const at = parts.findIndex((part) => part.type === "unit");
|
|
37
|
-
if (at === -1) return parts.map((part) => part.value).join("");
|
|
38
|
-
const out = parts.map((part) => part.value);
|
|
39
|
-
const gap = at === 0 ? 1 : at - 1;
|
|
40
|
-
if (parts[gap]?.type === "literal") out[gap] = " ";
|
|
41
|
-
else out.splice(at === 0 ? 1 : at, 0, " ");
|
|
42
|
-
return out.join("");
|
|
38
|
+
return wrFormatBytes(value, this.locale, decimals);
|
|
43
39
|
}
|
|
44
40
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
45
41
|
minVersion: "12.0.0",
|
|
@@ -68,19 +64,29 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
68
64
|
args: [{ name: "wrBytes" }]
|
|
69
65
|
}]
|
|
70
66
|
});
|
|
67
|
+
function wrFormatBytes(value, locale, decimals = 1) {
|
|
68
|
+
if (value === null || value === void 0 || value === "") return "";
|
|
69
|
+
const n = Number(value);
|
|
70
|
+
if (!Number.isFinite(n) || n <= 0) return format(locale, 0, 0, 0);
|
|
71
|
+
const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);
|
|
72
|
+
return format(locale, n / 1024 ** i, i, i === 0 ? 0 : Math.max(0, decimals));
|
|
73
|
+
}
|
|
71
74
|
/**
|
|
72
75
|
* @license
|
|
73
76
|
*
|
|
74
77
|
* Use of this source code is governed by an MIT-style license that can be
|
|
75
78
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
76
79
|
*/
|
|
80
|
+
function wrFormatDate(value, adapter, format = "shortDate") {
|
|
81
|
+
if (value === null || value === void 0 || value === "") return "";
|
|
82
|
+
const date = value instanceof Date ? value : toLocalDate(value);
|
|
83
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
84
|
+
return adapter.format(date, format);
|
|
85
|
+
}
|
|
77
86
|
var WrDate = class WrDate {
|
|
78
87
|
adapter = inject(WrDateAdapter);
|
|
79
88
|
transform(value, format = "shortDate") {
|
|
80
|
-
|
|
81
|
-
const date = value instanceof Date ? value : toLocalDate(value);
|
|
82
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
83
|
-
return this.adapter.format(date, format);
|
|
89
|
+
return wrFormatDate(value, this.adapter, format);
|
|
84
90
|
}
|
|
85
91
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
86
92
|
minVersion: "12.0.0",
|
|
@@ -115,6 +121,105 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
115
121
|
* Use of this source code is governed by an MIT-style license that can be
|
|
116
122
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
117
123
|
*/
|
|
124
|
+
const UNIT_MS = [
|
|
125
|
+
["day", 864e5],
|
|
126
|
+
["hour", 36e5],
|
|
127
|
+
["minute", 6e4],
|
|
128
|
+
["second", 1e3],
|
|
129
|
+
["millisecond", 1]
|
|
130
|
+
];
|
|
131
|
+
const CLOCK_UNITS = /* @__PURE__ */ new Set([
|
|
132
|
+
"hour",
|
|
133
|
+
"minute",
|
|
134
|
+
"second"
|
|
135
|
+
]);
|
|
136
|
+
const LIST_UNITS = /* @__PURE__ */ new Set([
|
|
137
|
+
"day",
|
|
138
|
+
"hour",
|
|
139
|
+
"minute",
|
|
140
|
+
"second"
|
|
141
|
+
]);
|
|
142
|
+
function formatClock(ms, locale) {
|
|
143
|
+
const { hour = 0, minute = 0, second = 0 } = wrSplitDuration(ms, CLOCK_UNITS);
|
|
144
|
+
const digits = (n, width) => new Intl.NumberFormat(locale, {
|
|
145
|
+
useGrouping: false,
|
|
146
|
+
minimumIntegerDigits: width
|
|
147
|
+
}).format(n);
|
|
148
|
+
const tail = `${digits(minute, hour > 0 ? 2 : 1)}:${digits(second, 2)}`;
|
|
149
|
+
return hour > 0 ? `${digits(hour, 1)}:${tail}` : tail;
|
|
150
|
+
}
|
|
151
|
+
function formatUnits(ms, locale) {
|
|
152
|
+
const parts = wrSplitDuration(ms, LIST_UNITS);
|
|
153
|
+
const unit = (name, n) => new Intl.NumberFormat(locale, {
|
|
154
|
+
style: "unit",
|
|
155
|
+
unit: name,
|
|
156
|
+
unitDisplay: "short"
|
|
157
|
+
}).format(n);
|
|
158
|
+
const words = Object.entries(parts).filter(([, n]) => n > 0).map(([name, n]) => unit(name, n));
|
|
159
|
+
if (words.length === 0) return unit("second", 0);
|
|
160
|
+
return new Intl.ListFormat(locale, {
|
|
161
|
+
style: "narrow",
|
|
162
|
+
type: "unit"
|
|
163
|
+
}).format(words);
|
|
164
|
+
}
|
|
165
|
+
function wrSplitDuration(ms, units) {
|
|
166
|
+
const out = {};
|
|
167
|
+
let rest = ms;
|
|
168
|
+
for (const [unit, size] of UNIT_MS) {
|
|
169
|
+
if (!units.has(unit)) continue;
|
|
170
|
+
const n = Math.floor(rest / size);
|
|
171
|
+
out[unit] = n;
|
|
172
|
+
rest -= n * size;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function wrFormatDuration(value, locale, style = "clock") {
|
|
177
|
+
if (value === null || value === void 0 || value === "") return "";
|
|
178
|
+
const ms = Number(typeof value === "string" ? value.trim() : value);
|
|
179
|
+
if (!Number.isFinite(ms)) return "";
|
|
180
|
+
const span = Math.abs(ms);
|
|
181
|
+
const body = style === "units" ? formatUnits(span, locale) : formatClock(span, locale);
|
|
182
|
+
if (ms >= 0 || span < 1e3) return body;
|
|
183
|
+
return `${new Intl.NumberFormat(locale).formatToParts(-1).find((part) => part.type === "minusSign")?.value ?? "-"}${body}`;
|
|
184
|
+
}
|
|
185
|
+
var WrDuration = class WrDuration {
|
|
186
|
+
locale = inject(LOCALE_ID);
|
|
187
|
+
transform(value, style = "clock") {
|
|
188
|
+
return wrFormatDuration(value, this.locale, style);
|
|
189
|
+
}
|
|
190
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
191
|
+
minVersion: "12.0.0",
|
|
192
|
+
version: "22.2.4",
|
|
193
|
+
ngImport: i0,
|
|
194
|
+
type: WrDuration,
|
|
195
|
+
deps: [],
|
|
196
|
+
target: i0.ɵɵFactoryTarget.Pipe
|
|
197
|
+
});
|
|
198
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({
|
|
199
|
+
minVersion: "14.0.0",
|
|
200
|
+
version: "22.2.4",
|
|
201
|
+
ngImport: i0,
|
|
202
|
+
type: WrDuration,
|
|
203
|
+
isStandalone: true,
|
|
204
|
+
name: "wrDuration"
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
208
|
+
minVersion: "12.0.0",
|
|
209
|
+
version: "22.2.4",
|
|
210
|
+
ngImport: i0,
|
|
211
|
+
type: WrDuration,
|
|
212
|
+
decorators: [{
|
|
213
|
+
type: Pipe,
|
|
214
|
+
args: [{ name: "wrDuration" }]
|
|
215
|
+
}]
|
|
216
|
+
});
|
|
217
|
+
/**
|
|
218
|
+
* @license
|
|
219
|
+
*
|
|
220
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
221
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
222
|
+
*/
|
|
118
223
|
const RE_META = /[\\^$.*+?()[\]{}|]/g;
|
|
119
224
|
function escapeHtml(s) {
|
|
120
225
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -163,29 +268,32 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
163
268
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
164
269
|
*/
|
|
165
270
|
const PLAIN_DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
|
|
271
|
+
function toOptions(style, currency) {
|
|
272
|
+
switch (style) {
|
|
273
|
+
case "percent": return {
|
|
274
|
+
style: "percent",
|
|
275
|
+
maximumFractionDigits: 2
|
|
276
|
+
};
|
|
277
|
+
case "currency": return {
|
|
278
|
+
style: "currency",
|
|
279
|
+
currency
|
|
280
|
+
};
|
|
281
|
+
default: return { style: "decimal" };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function wrFormatNumber(value, locale, styleOrOptions = "decimal", currency = "USD") {
|
|
285
|
+
if (value === null || value === void 0 || value === "") return "";
|
|
286
|
+
const text = typeof value === "string" ? value.trim() : null;
|
|
287
|
+
const n = Number(text ?? value);
|
|
288
|
+
if (!Number.isFinite(n)) return "";
|
|
289
|
+
const options = typeof styleOrOptions === "string" ? toOptions(styleOrOptions, currency) : styleOrOptions;
|
|
290
|
+
const exact = text !== null && PLAIN_DECIMAL.test(text) ? text : n;
|
|
291
|
+
return new Intl.NumberFormat(locale, options).format(exact);
|
|
292
|
+
}
|
|
166
293
|
var WrNumber = class WrNumber {
|
|
167
294
|
locale = inject(LOCALE_ID);
|
|
168
295
|
transform(value, styleOrOptions = "decimal", currency = "USD") {
|
|
169
|
-
|
|
170
|
-
const text = typeof value === "string" ? value.trim() : null;
|
|
171
|
-
const n = Number(text ?? value);
|
|
172
|
-
if (!Number.isFinite(n)) return "";
|
|
173
|
-
const options = typeof styleOrOptions === "string" ? this.toOptions(styleOrOptions, currency) : styleOrOptions;
|
|
174
|
-
const exact = text !== null && PLAIN_DECIMAL.test(text) ? text : n;
|
|
175
|
-
return new Intl.NumberFormat(this.locale, options).format(exact);
|
|
176
|
-
}
|
|
177
|
-
toOptions(style, currency) {
|
|
178
|
-
switch (style) {
|
|
179
|
-
case "percent": return {
|
|
180
|
-
style: "percent",
|
|
181
|
-
maximumFractionDigits: 2
|
|
182
|
-
};
|
|
183
|
-
case "currency": return {
|
|
184
|
-
style: "currency",
|
|
185
|
-
currency
|
|
186
|
-
};
|
|
187
|
-
default: return { style: "decimal" };
|
|
188
|
-
}
|
|
296
|
+
return wrFormatNumber(value, this.locale, styleOrOptions, currency);
|
|
189
297
|
}
|
|
190
298
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
191
299
|
minVersion: "12.0.0",
|
|
@@ -220,14 +328,17 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
220
328
|
* Use of this source code is governed by an MIT-style license that can be
|
|
221
329
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
222
330
|
*/
|
|
331
|
+
function wrFormatPlural(value, locale, forms, options = {}) {
|
|
332
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) return "";
|
|
333
|
+
const tag = options.locale ?? locale;
|
|
334
|
+
const word = forms[new Intl.PluralRules(tag).select(value)] ?? forms.other;
|
|
335
|
+
if (options.includeValue === false) return word;
|
|
336
|
+
return `${new Intl.NumberFormat(tag).format(value)} ${word}`;
|
|
337
|
+
}
|
|
223
338
|
var WrPlural = class WrPlural {
|
|
224
339
|
locale = inject(LOCALE_ID);
|
|
225
340
|
transform(value, forms, options = {}) {
|
|
226
|
-
|
|
227
|
-
const locale = options.locale ?? this.locale;
|
|
228
|
-
const word = forms[new Intl.PluralRules(locale).select(value)] ?? forms.other;
|
|
229
|
-
if (options.includeValue === false) return word;
|
|
230
|
-
return `${new Intl.NumberFormat(locale).format(value)} ${word}`;
|
|
341
|
+
return wrFormatPlural(value, this.locale, forms, options);
|
|
231
342
|
}
|
|
232
343
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
233
344
|
minVersion: "12.0.0",
|
|
@@ -341,6 +452,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
341
452
|
args: [{ name: "wrTruncate" }]
|
|
342
453
|
}]
|
|
343
454
|
});
|
|
344
|
-
export { WrBytes, WrDate, WrMark, WrNumber, WrPlural, WrRange, WrTruncate };
|
|
455
|
+
export { WrBytes, WrDate, WrDuration, WrMark, WrNumber, WrPlural, WrRange, WrTruncate, wrFormatBytes, wrFormatDate, wrFormatDuration, wrFormatNumber, wrFormatPlural, wrSplitDuration };
|
|
345
456
|
|
|
346
457
|
//# sourceMappingURL=ngwr-pipes.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-pipes.mjs","names":[],"sources":["../../../projects/lib/pipes/bytes.ts","../../../projects/lib/pipes/date.ts","../../../projects/lib/pipes/mark.ts","../../../projects/lib/pipes/number.ts","../../../projects/lib/pipes/plural.ts","../../../projects/lib/pipes/range.ts","../../../projects/lib/pipes/truncate.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Binary magnitudes, as the `Intl` unit that labels each one. The COUNTING is\n * 1024-based and these names are decimal — which is the same thing the old\n * hand-written `['B', 'KB', 'MB', …]` table did, and what almost every file\n * manager does. Strictly, 1024 bytes is a kibibyte; `Intl` has no unit for it.\n */\nconst UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'] as const;\n\n/**\n * Humanise a byte count using binary (1024-based) units, in the reader's locale.\n *\n * @example\n * ```html\n * {{ 1234 | wrBytes }} <!-- en: \"1.2 kB\" de: \"1,2 kB\" ru: \"1,2 кБ\" -->\n * {{ 1234567 | wrBytes: 0 }} <!-- en: \"1 MB\" fr: \"1 Mo\" -->\n * {{ 0 | wrBytes }} <!-- en: \"0 B\" ru: \"0 Б\" -->\n * ```\n *\n * **Both halves come from `Intl`, and the unit table is not the catalog's.** The\n * number always did need `LOCALE_ID` — it went through `toFixed`, which writes a\n * full stop in every language, so a German page read `1.2 KB` beside its own\n * `1,2` everywhere else — and the three formatting pipes beside this one\n * (`wrDate`, `wrNumber`, `wrPlural`) had injected it since they shipped.\n *\n * The unit could not follow them into `ngwr/i18n`, and the reason is the pipe\n * rather than the strings: a PURE pipe is re-invoked only when its argument\n * changes, so a catalog that lands after the first render — which is every\n * `provideWrI18nHttpLoader` app — would never reach it, and the pipe would be\n * correct under the static loader and silently English under the other one.\n * `LOCALE_ID` is a constant, so `Intl` has no such failure mode, and it knows\n * every locale ICU ships rather than the twenty-two this package translates.\n *\n * One visible change in English and it is the only one: `KB` became `kB`, which\n * is what CLDR calls that unit. `B`, the space before it and every other\n * magnitude read exactly as they did — see `format` for the part of that which\n * is not free.\n *\n * The other consequence, kept here so nobody rediscovers it as a bug:\n * `<wr-file-upload>` prints its own sizes from `fileUpload.unitKb` and friends,\n * which are public catalog keys a consumer may have overridden, so it renders\n * `KB` where this pipe renders `kB`. Retiring those keys would silently break\n * every override that had been translating them, which is a major-version move.\n */\n@Pipe({ name: 'wrBytes' })\nexport class WrBytes implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | string | null | undefined, decimals = 1): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n) || n <= 0) return this.format(0, 0, 0);\n\n // Floored as well as capped: for `0 < n < 1` the log is negative, so an\n // unclamped index reads `UNITS[-1]` (`undefined`) and divides by `1024 ** -1`,\n // which rendered `0.5` as \"512.0 undefined\".\n const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);\n // Whole bytes never show a fractional part.\n return this.format(n / 1024 ** i, i, i === 0 ? 0 : Math.max(0, decimals));\n }\n\n /**\n * `Intl`'s NARROW unit, with one space put back between it and the number.\n *\n * Neither display mode is right on its own. `short` spells the smallest unit\n * as a word — \"512 byte\", \"0 byte\" in English, \"0 Byte\" in German — which is\n * both worse copy than the `B` this pipe has always rendered and a bigger\n * change than the fix is worth. `narrow` gives `B` / `Б` / `o` correctly and\n * then closes the gap: \"1.2kB\".\n *\n * So the parts are reassembled rather than the string. Going through\n * `formatToParts` is what keeps the ORDER the locale's own — Hebrew puts the\n * unit first, and hand-joining `${n} ${unit}` would silently straighten every\n * such locale into English order.\n */\n private format(value: number, unit: number, fractionDigits: number): string {\n const parts = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: UNITS[unit],\n unitDisplay: 'narrow',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).formatToParts(value);\n\n const at = parts.findIndex(part => part.type === 'unit');\n if (at === -1) return parts.map(part => part.value).join('');\n\n const out = parts.map(part => part.value);\n // The slot on the number's side of the unit: already a separator in some\n // locales, absent in others.\n const gap = at === 0 ? 1 : at - 1;\n if (parts[gap]?.type === 'literal') out[gap] = ' ';\n else out.splice(at === 0 ? 1 : at, 0, ' ');\n return out.join('');\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport { type WrDateFormat, WrDateAdapter, toLocalDate } from 'ngwr/date';\n\n/**\n * Locale-aware date formatting. Delegates to `WrDateAdapter` — the native one\n * unless the app provides its own — so both the named format keys\n * (`'shortDate'`, `'longDate'`, …) and raw token strings work with nothing to\n * set up. A date-only string (`'2026-01-01'`) is that day in the local zone,\n * as `DatePipe` reads it, not UTC midnight.\n *\n * @example\n * ```html\n * {{ now | wrDate }} <!-- shortDate -->\n * {{ now | wrDate: 'mediumDateTime' }} <!-- named key -->\n * {{ now | wrDate: 'dd.MM.yyyy' }} <!-- raw token string -->\n * ```\n */\n@Pipe({ name: 'wrDate' })\nexport class WrDate implements PipeTransform {\n private readonly adapter = inject<WrDateAdapter<Date>>(WrDateAdapter);\n\n transform(\n value: Date | string | number | null | undefined,\n format: WrDateFormat | (string & {}) = 'shortDate'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const date = value instanceof Date ? value : toLocalDate(value);\n if (Number.isNaN(date.getTime())) return '';\n\n return this.adapter.format(date, format);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe, type PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, type SafeHtml } from '@angular/platform-browser';\n\n/** Escapes RegExp metacharacters so user input is safe to inline. @internal */\nconst RE_META = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** HTML-entities for the four chars that matter inside our `<mark>` wrap. @internal */\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\n/**\n * Wrap every occurrence of `query` in `<mark>` tags. Useful for highlighting\n * search-term matches in autocomplete results, lists, and table cells.\n *\n * The output is `SafeHtml` — bind with `[innerHTML]`. Input is escaped first\n * so user-supplied values can't inject markup.\n *\n * @example\n * ```html\n * <span [innerHTML]=\"row.name | wrMark: query()\"></span>\n *\n * <!-- Case-sensitive: -->\n * <span [innerHTML]=\"row.name | wrMark: query() : true\"></span>\n * ```\n */\n@Pipe({ name: 'wrMark' })\nexport class WrMark implements PipeTransform {\n private readonly sanitizer = inject(DomSanitizer);\n\n /**\n * @param value Source text.\n * @param query Search term to highlight. Empty/null skips the wrap.\n * @param caseSensitive Match exactly. @default false\n */\n transform(value: string | null | undefined, query: string | null | undefined, caseSensitive = false): SafeHtml {\n if (value === null || value === undefined) return '';\n const text = String(value);\n // Escaped on this path too. The output is bound with `[innerHTML]`, so\n // handing back the raw string let markup in `value` render as elements the\n // moment the search box was cleared — the same value drawn as text while a\n // query was typed, as an `<img>` when it was not.\n if (!query) return escapeHtml(text);\n\n // Match the RAW text and escape the pieces afterwards, never the other way\n // round. Escaping first rewrites `&` as `&`, and a query of `m` then lands\n // INSIDE that entity: the `<mark>` splits it and the browser paints the\n // entity's own letters, so `Tom & Jerry` renders as `Tom & Jerry`. The\n // mirror failure is a match that can never happen — `<`, `>` and `\"` no longer\n // exist in escaped text. The pipe's promise is untouched: every piece that came\n // from `value` still goes through `escapeHtml`, matched or not, so the only\n // tags in the output are the `<mark>`s built here.\n const pattern = new RegExp(`(${query.replace(RE_META, '\\\\$&')})`, caseSensitive ? 'g' : 'gi');\n const html = text\n .split(pattern)\n .map((part, index) => (index % 2 ? `<mark>${escapeHtml(part)}</mark>` : escapeHtml(part)))\n .join('');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrNumberStyle } from './types';\n\n/** Digits with one optional point and a sign: the form `Intl` reads exactly. */\nconst PLAIN_DECIMAL = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/;\n\n/**\n * Locale-aware number formatting via `Intl.NumberFormat`. Uses Angular's\n * `LOCALE_ID` as the default locale.\n *\n * Accepts either a style shortcut (with optional currency code) or a full\n * `Intl.NumberFormatOptions` object for fine-grained control.\n *\n * @example\n * ```html\n * {{ 1234.5 | wrNumber }} <!-- \"1,234.5\" -->\n * {{ 0.875 | wrNumber: 'percent' }} <!-- \"87.5%\" -->\n * {{ 19.99 | wrNumber: 'currency': 'USD' }} <!-- \"$19.99\" -->\n * {{ 19.99 | wrNumber: { minimumFractionDigits: 2 } }}\n * ```\n */\n@Pipe({ name: 'wrNumber' })\nexport class WrNumber implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: number | string | null | undefined,\n styleOrOptions: WrNumberStyle | Intl.NumberFormatOptions = 'decimal',\n currency = 'USD'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const text = typeof value === 'string' ? value.trim() : null;\n const n = Number(text ?? value);\n if (!Number.isFinite(n)) return '';\n\n const options: Intl.NumberFormatOptions =\n typeof styleOrOptions === 'string' ? this.toOptions(styleOrOptions, currency) : styleOrOptions;\n\n // A plain decimal string goes to `Intl` as it is: through `Number()`,\n // `'12345678901234567.89'` printed as `12,345,678,901,234,568`. Any other\n // form keeps the number, since ICU throws on an exponent of a billion or more.\n // `format()` has read a string since ES2023 (Chrome 106, Firefox 116, Safari\n // 15.4); the ES2022 lib this builds against types it as a number only.\n const exact = text !== null && PLAIN_DECIMAL.test(text) ? (text as unknown as number) : n;\n return new Intl.NumberFormat(this.locale, options).format(exact);\n }\n\n private toOptions(style: WrNumberStyle, currency: string): Intl.NumberFormatOptions {\n switch (style) {\n case 'percent':\n return { style: 'percent', maximumFractionDigits: 2 };\n case 'currency':\n return { style: 'currency', currency };\n case 'decimal':\n default:\n return { style: 'decimal' };\n }\n }\n}\n\nexport type { WrNumberStyle } from './types';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrPluralForms, WrPluralOptions } from './types';\n\n/**\n * Locale-aware pluralization via `Intl.PluralRules`. Picks the right word\n * form for the count — including languages with more than two forms\n * (Russian, Polish, Arabic, …) — without hand-rolled `n % 10` rules.\n *\n * Categories follow CLDR: `zero` / `one` / `two` / `few` / `many` /\n * `other`. Missing categories fall back to `other`.\n *\n * @example\n * ```html\n * {{ 1 | wrPlural: { one: 'comment', other: 'comments' } }}\n * <!-- \"1 comment\" -->\n *\n * {{ 5 | wrPlural: { one: 'файл', few: 'файла', other: 'файлов' } }}\n * <!-- \"5 файлов\" (with LOCALE_ID = ru) -->\n *\n * {{ count() | wrPlural: forms : { includeValue: false } }}\n * <!-- word only -->\n * ```\n */\n@Pipe({ name: 'wrPlural' })\nexport class WrPlural implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | null | undefined, forms: WrPluralForms, options: WrPluralOptions = {}): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '';\n const locale = options.locale ?? this.locale;\n const category = new Intl.PluralRules(locale).select(value);\n const word = forms[category] ?? forms.other;\n if (options.includeValue === false) return word;\n return `${new Intl.NumberFormat(locale).format(value)} ${word}`;\n }\n}\n\nexport type { WrPluralForms, WrPluralOptions } from './types';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Creates an array of sequential indices `[0, 1, 2, ..., n-1]`.\n *\n * Useful for repeating template blocks a fixed number of times.\n *\n * @example\n * ```html\n * @for (i of (5 | wrRange); track i) {\n * <div>Item {{ i }}</div>\n * }\n * ```\n */\n@Pipe({ name: 'wrRange' })\nexport class WrRange implements PipeTransform {\n transform(length: number): number[] {\n return Array.from({ length }, (_, i) => i);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nlet segmenter: Intl.Segmenter | undefined;\n\n/**\n * Clamp a string to `length` characters, appending `ellipsis` when truncated.\n * A character is what a reader sees as one — a grapheme cluster — so an emoji,\n * a flag or a letter with a combining accent is kept whole or dropped whole,\n * never cut into half a surrogate pair that renders as a replacement box.\n *\n * @example\n * ```html\n * {{ 'Hello, world' | wrTruncate: 5 }} <!-- \"Hello…\" -->\n * {{ 'Hello, world' | wrTruncate: 8: '...' }} <!-- \"Hello, w...\" -->\n * ```\n */\n@Pipe({ name: 'wrTruncate' })\nexport class WrTruncate implements PipeTransform {\n transform(value: string | null | undefined, length = 80, ellipsis = '…'): string {\n if (value === null || value === undefined) return '';\n const s = String(value);\n // No more graphemes than UTF-16 units, so a string this short is never cut.\n if (s.length <= length) return s;\n // `slice`'s own coercion of the count, which is what this pipe did before it\n // counted graphemes: a fraction truncates, and NaN or a negative keeps nothing.\n const limit = Math.max(0, Math.trunc(length) || 0);\n segmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const { index } of segmenter.segment(s)) {\n if (count === limit) return s.slice(0, index) + ellipsis;\n count++;\n }\n return s;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-pipes.mjs","names":[],"sources":["../../../projects/lib/pipes/bytes.ts","../../../projects/lib/pipes/date.ts","../../../projects/lib/pipes/duration.ts","../../../projects/lib/pipes/mark.ts","../../../projects/lib/pipes/number.ts","../../../projects/lib/pipes/plural.ts","../../../projects/lib/pipes/range.ts","../../../projects/lib/pipes/truncate.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Binary magnitudes, as the `Intl` unit that labels each one. The COUNTING is\n * 1024-based and these names are decimal — which is the same thing the old\n * hand-written `['B', 'KB', 'MB', …]` table did, and what almost every file\n * manager does. Strictly, 1024 bytes is a kibibyte; `Intl` has no unit for it.\n */\nconst UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'] as const;\n\n/**\n * `Intl`'s NARROW unit, with one space put back between it and the number.\n *\n * Neither display mode is right on its own. `short` spells the smallest unit\n * as a word — \"512 byte\", \"0 byte\" in English, \"0 Byte\" in German — which is\n * both worse copy than the `B` this pipe has always rendered and a bigger\n * change than the fix is worth. `narrow` gives `B` / `Б` / `o` correctly and\n * then closes the gap: \"1.2kB\".\n *\n * So the parts are reassembled rather than the string. Going through\n * `formatToParts` is what keeps the ORDER the locale's own — Hebrew puts the\n * unit first, and hand-joining `${n} ${unit}` would silently straighten every\n * such locale into English order.\n */\nfunction format(locale: string, value: number, unit: number, fractionDigits: number): string {\n const parts = new Intl.NumberFormat(locale, {\n style: 'unit',\n unit: UNITS[unit],\n unitDisplay: 'narrow',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).formatToParts(value);\n\n const at = parts.findIndex(part => part.type === 'unit');\n if (at === -1) return parts.map(part => part.value).join('');\n\n const out = parts.map(part => part.value);\n // The slot on the number's side of the unit: already a separator in some\n // locales, absent in others.\n const gap = at === 0 ? 1 : at - 1;\n if (parts[gap]?.type === 'literal') out[gap] = ' ';\n else out.splice(at === 0 ? 1 : at, 0, ' ');\n return out.join('');\n}\n\n/**\n * Humanise a byte count using binary (1024-based) units, in the reader's locale.\n *\n * @example\n * ```html\n * {{ 1234 | wrBytes }} <!-- en: \"1.2 kB\" de: \"1,2 kB\" ru: \"1,2 кБ\" -->\n * {{ 1234567 | wrBytes: 0 }} <!-- en: \"1 MB\" fr: \"1 Mo\" -->\n * {{ 0 | wrBytes }} <!-- en: \"0 B\" ru: \"0 Б\" -->\n * ```\n *\n * **Both halves come from `Intl`, and the unit table is not the catalog's.** The\n * number always did need `LOCALE_ID` — it went through `toFixed`, which writes a\n * full stop in every language, so a German page read `1.2 KB` beside its own\n * `1,2` everywhere else — and the three formatting pipes beside this one\n * (`wrDate`, `wrNumber`, `wrPlural`) had injected it since they shipped.\n *\n * The unit could not follow them into `ngwr/i18n`, and the reason is the pipe\n * rather than the strings: a PURE pipe is re-invoked only when its argument\n * changes, so a catalog that lands after the first render — which is every\n * `provideWrI18nHttpLoader` app — would never reach it, and the pipe would be\n * correct under the static loader and silently English under the other one.\n * `LOCALE_ID` is a constant, so `Intl` has no such failure mode, and it knows\n * every locale ICU ships rather than the twenty-two this package translates.\n *\n * One visible change in English and it is the only one: `KB` became `kB`, which\n * is what CLDR calls that unit. `B`, the space before it and every other\n * magnitude read exactly as they did — see `format` for the part of that which\n * is not free.\n *\n * The other consequence, kept here so nobody rediscovers it as a bug:\n * `<wr-file-upload>` prints its own sizes from `fileUpload.unitKb` and friends,\n * which are public catalog keys a consumer may have overridden, so it renders\n * `KB` where this pipe renders `kB`. Retiring those keys would silently break\n * every override that had been translating them, which is a major-version move.\n *\n * {@link wrFormatBytes} is the same output as a function.\n */\n@Pipe({ name: 'wrBytes' })\nexport class WrBytes implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | string | null | undefined, decimals = 1): string {\n return wrFormatBytes(value, this.locale, decimals);\n }\n}\n\n/**\n * A byte count in binary (1024-based) units, in the given locale: the same\n * output as the `wrBytes` pipe, for a string built in TypeScript. `null`,\n * `undefined` and an empty string give an empty string; zero, a negative\n * number and a value that is not a number give `0 B`.\n *\n * @example\n * ```ts\n * wrFormatBytes(1234, 'en-US'); // \"1.2 kB\"\n * wrFormatBytes(1234567, 'fr-FR', 0); // \"1 Mo\"\n * ```\n */\nexport function wrFormatBytes(value: number | string | null | undefined, locale: string, decimals = 1): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n) || n <= 0) return format(locale, 0, 0, 0);\n\n // Floored as well as capped: for `0 < n < 1` the log is negative, so an\n // unclamped index reads `UNITS[-1]` (`undefined`) and divides by `1024 ** -1`,\n // which rendered `0.5` as \"512.0 undefined\".\n const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);\n // Whole bytes never show a fractional part.\n return format(locale, n / 1024 ** i, i, i === 0 ? 0 : Math.max(0, decimals));\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport { type WrDateFormat, WrDateAdapter, toLocalDate } from 'ngwr/date';\n\n/**\n * A date through a `WrDateAdapter`: the same output as the `wrDate` pipe, for a\n * string built in TypeScript. A date-only string (`'2026-01-01'`) is that day in\n * the local zone, as `DatePipe` reads it. `null`, `undefined`, an empty string\n * and a date the engine refuses give an empty string.\n *\n * The adapter stands where `wrFormatNumber` takes a locale, because the output\n * is the adapter's: the native one formats in `WR_DATE_LOCALE`, and an app's own\n * adapter formats however it formats. Neither can be rebuilt from a locale\n * string, so the caller passes `inject(WrDateAdapter)` once.\n *\n * @example\n * ```ts\n * private readonly dates = inject(WrDateAdapter);\n *\n * wrFormatDate(row.createdAt, this.dates); // shortDate\n * wrFormatDate(row.createdAt, this.dates, 'mediumDateTime'); // named key\n * wrFormatDate(row.createdAt, this.dates, 'dd.MM.yyyy'); // raw token string\n * ```\n */\nexport function wrFormatDate(\n value: Date | string | number | null | undefined,\n adapter: WrDateAdapter<Date>,\n format: WrDateFormat | (string & {}) = 'shortDate'\n): string {\n if (value === null || value === undefined || value === '') return '';\n const date = value instanceof Date ? value : toLocalDate(value);\n if (Number.isNaN(date.getTime())) return '';\n\n return adapter.format(date, format);\n}\n\n/**\n * Locale-aware date formatting. Delegates to `WrDateAdapter` — the native one\n * unless the app provides its own — so both the named format keys\n * (`'shortDate'`, `'longDate'`, …) and raw token strings work with nothing to\n * set up. A date-only string (`'2026-01-01'`) is that day in the local zone,\n * as `DatePipe` reads it, not UTC midnight. {@link wrFormatDate} is the same\n * output as a function.\n *\n * @example\n * ```html\n * {{ now | wrDate }} <!-- shortDate -->\n * {{ now | wrDate: 'mediumDateTime' }} <!-- named key -->\n * {{ now | wrDate: 'dd.MM.yyyy' }} <!-- raw token string -->\n * ```\n */\n@Pipe({ name: 'wrDate' })\nexport class WrDate implements PipeTransform {\n private readonly adapter = inject<WrDateAdapter<Date>>(WrDateAdapter);\n\n transform(\n value: Date | string | number | null | undefined,\n format: WrDateFormat | (string & {}) = 'shortDate'\n ): string {\n return wrFormatDate(value, this.adapter, format);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrDurationStyle, WrDurationUnit } from './types';\n\n/** Each unit's length in milliseconds, largest first. */\nconst UNIT_MS: readonly (readonly [WrDurationUnit, number])[] = [\n ['day', 86_400_000],\n ['hour', 3_600_000],\n ['minute', 60_000],\n ['second', 1000],\n ['millisecond', 1],\n];\n\nconst CLOCK_UNITS: ReadonlySet<WrDurationUnit> = new Set(['hour', 'minute', 'second']);\nconst LIST_UNITS: ReadonlySet<WrDurationUnit> = new Set(['day', 'hour', 'minute', 'second']);\n\nfunction formatClock(ms: number, locale: string): string {\n const { hour = 0, minute = 0, second = 0 } = wrSplitDuration(ms, CLOCK_UNITS);\n const digits = (n: number, width: number): string =>\n new Intl.NumberFormat(locale, { useGrouping: false, minimumIntegerDigits: width }).format(n);\n const tail = `${digits(minute, hour > 0 ? 2 : 1)}:${digits(second, 2)}`;\n return hour > 0 ? `${digits(hour, 1)}:${tail}` : tail;\n}\n\nfunction formatUnits(ms: number, locale: string): string {\n const parts = wrSplitDuration(ms, LIST_UNITS);\n const unit = (name: WrDurationUnit, n: number): string =>\n new Intl.NumberFormat(locale, { style: 'unit', unit: name, unitDisplay: 'short' }).format(n);\n\n const words = (Object.entries(parts) as [WrDurationUnit, number][])\n .filter(([, n]) => n > 0)\n .map(([name, n]) => unit(name, n));\n if (words.length === 0) return unit('second', 0);\n return new Intl.ListFormat(locale, { style: 'narrow', type: 'unit' }).format(words);\n}\n\n/**\n * Split a span of milliseconds into the units asked for. A unit left out is\n * carried into the next larger one that is kept, so `['hour', 'minute',\n * 'second']` over three days and an hour reads 73 hours; the smallest unit kept\n * floors whatever remains. Shared with `wr-statistic-countdown`, whose format\n * tokens pick the units.\n *\n * @internal\n */\nexport function wrSplitDuration(\n ms: number,\n units: ReadonlySet<WrDurationUnit>\n): Readonly<Partial<Record<WrDurationUnit, number>>> {\n const out: Partial<Record<WrDurationUnit, number>> = {};\n let rest = ms;\n for (const [unit, size] of UNIT_MS) {\n if (!units.has(unit)) continue;\n const n = Math.floor(rest / size);\n out[unit] = n;\n rest -= n * size;\n }\n return out;\n}\n\n/**\n * A span of milliseconds as text, in the given locale.\n *\n * Two styles. `'clock'` is digits and colons: `1:30:00`, and `5:07` under an\n * hour; days are carried into the hours, since a clock has no day digit, so two\n * days read `48:00:00`. `'units'` is every non-zero unit as a word the locale\n * writes, `1 hr 30 min` in English and `1 h 30 min` in French, through\n * `Intl.NumberFormat`'s unit style and `Intl.ListFormat` — `Intl.DurationFormat`\n * is above the browser floor. The digits are the locale's own in both styles.\n *\n * Whole seconds: a fraction of a second is floored in both styles. Zero reads\n * `0:00` and `0 sec`. A negative span is its absolute value with the locale's\n * minus sign in front, `-5:07`. `null`, `undefined`, an empty string and a value\n * that is not a finite number give an empty string, as `wrFormatNumber` does.\n *\n * The same output as the `wrDuration` pipe, for a string built in TypeScript.\n *\n * @example\n * ```ts\n * wrFormatDuration(5_400_000, 'en-US'); // \"1:30:00\"\n * wrFormatDuration(5_400_000, 'en-US', 'units'); // \"1 hr 30 min\"\n * wrFormatDuration(307_000, 'fr-FR', 'units'); // \"5 min 7 s\"\n * ```\n */\nexport function wrFormatDuration(\n value: number | string | null | undefined,\n locale: string,\n style: WrDurationStyle = 'clock'\n): string {\n if (value === null || value === undefined || value === '') return '';\n const ms = Number(typeof value === 'string' ? value.trim() : value);\n if (!Number.isFinite(ms)) return '';\n\n const span = Math.abs(ms);\n const body = style === 'units' ? formatUnits(span, locale) : formatClock(span, locale);\n // A span that floors to zero carries no sign: `-0:00` states nothing.\n if (ms >= 0 || span < 1000) return body;\n\n const minus = new Intl.NumberFormat(locale).formatToParts(-1).find(part => part.type === 'minusSign');\n return `${minus?.value ?? '-'}${body}`;\n}\n\n/**\n * A span of milliseconds as clock digits or as units, in Angular's `LOCALE_ID`.\n * See {@link wrFormatDuration} for the two styles and the edge cases.\n *\n * @example\n * ```html\n * {{ 5400000 | wrDuration }} <!-- \"1:30:00\" -->\n * {{ 5400000 | wrDuration: 'units' }} <!-- \"1 hr 30 min\" -->\n * {{ 307000 | wrDuration }} <!-- \"5:07\" -->\n * ```\n */\n@Pipe({ name: 'wrDuration' })\nexport class WrDuration implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | string | null | undefined, style: WrDurationStyle = 'clock'): string {\n return wrFormatDuration(value, this.locale, style);\n }\n}\n\nexport type { WrDurationStyle, WrDurationUnit } from './types';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe, type PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, type SafeHtml } from '@angular/platform-browser';\n\n/** Escapes RegExp metacharacters so user input is safe to inline. @internal */\nconst RE_META = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** HTML-entities for the four chars that matter inside our `<mark>` wrap. @internal */\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\n/**\n * Wrap every occurrence of `query` in `<mark>` tags. Useful for highlighting\n * search-term matches in autocomplete results, lists, and table cells.\n *\n * The output is `SafeHtml` — bind with `[innerHTML]`. Input is escaped first\n * so user-supplied values can't inject markup.\n *\n * @example\n * ```html\n * <span [innerHTML]=\"row.name | wrMark: query()\"></span>\n *\n * <!-- Case-sensitive: -->\n * <span [innerHTML]=\"row.name | wrMark: query() : true\"></span>\n * ```\n */\n@Pipe({ name: 'wrMark' })\nexport class WrMark implements PipeTransform {\n private readonly sanitizer = inject(DomSanitizer);\n\n /**\n * @param value Source text.\n * @param query Search term to highlight. Empty/null skips the wrap.\n * @param caseSensitive Match exactly. @default false\n */\n transform(value: string | null | undefined, query: string | null | undefined, caseSensitive = false): SafeHtml {\n if (value === null || value === undefined) return '';\n const text = String(value);\n // Escaped on this path too. The output is bound with `[innerHTML]`, so\n // handing back the raw string let markup in `value` render as elements the\n // moment the search box was cleared — the same value drawn as text while a\n // query was typed, as an `<img>` when it was not.\n if (!query) return escapeHtml(text);\n\n // Match the RAW text and escape the pieces afterwards, never the other way\n // round. Escaping first rewrites `&` as `&`, and a query of `m` then lands\n // INSIDE that entity: the `<mark>` splits it and the browser paints the\n // entity's own letters, so `Tom & Jerry` renders as `Tom & Jerry`. The\n // mirror failure is a match that can never happen — `<`, `>` and `\"` no longer\n // exist in escaped text. The pipe's promise is untouched: every piece that came\n // from `value` still goes through `escapeHtml`, matched or not, so the only\n // tags in the output are the `<mark>`s built here.\n const pattern = new RegExp(`(${query.replace(RE_META, '\\\\$&')})`, caseSensitive ? 'g' : 'gi');\n const html = text\n .split(pattern)\n .map((part, index) => (index % 2 ? `<mark>${escapeHtml(part)}</mark>` : escapeHtml(part)))\n .join('');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrNumberStyle } from './types';\n\n/** Digits with one optional point and a sign: the form `Intl` reads exactly. */\nconst PLAIN_DECIMAL = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/;\n\nfunction toOptions(style: WrNumberStyle, currency: string): Intl.NumberFormatOptions {\n switch (style) {\n case 'percent':\n return { style: 'percent', maximumFractionDigits: 2 };\n case 'currency':\n return { style: 'currency', currency };\n case 'decimal':\n default:\n return { style: 'decimal' };\n }\n}\n\n/**\n * A number through `Intl.NumberFormat`, in the given locale: the same output as\n * the `wrNumber` pipe, for a string built in TypeScript. Takes a style shortcut\n * (with an optional currency code) or a full `Intl.NumberFormatOptions` object.\n * `null`, `undefined`, an empty string and a value that is not a finite number\n * give an empty string.\n *\n * @example\n * ```ts\n * wrFormatNumber(1234.5, 'en-US'); // \"1,234.5\"\n * wrFormatNumber(0.875, 'en-US', 'percent'); // \"87.5%\"\n * wrFormatNumber(19.99, 'de-DE', 'currency', 'EUR'); // \"19,99 €\"\n * wrFormatNumber(19.99, 'en-US', { minimumFractionDigits: 2 });\n * ```\n */\nexport function wrFormatNumber(\n value: number | string | null | undefined,\n locale: string,\n styleOrOptions: WrNumberStyle | Intl.NumberFormatOptions = 'decimal',\n currency = 'USD'\n): string {\n if (value === null || value === undefined || value === '') return '';\n const text = typeof value === 'string' ? value.trim() : null;\n const n = Number(text ?? value);\n if (!Number.isFinite(n)) return '';\n\n const options: Intl.NumberFormatOptions =\n typeof styleOrOptions === 'string' ? toOptions(styleOrOptions, currency) : styleOrOptions;\n\n // A plain decimal string goes to `Intl` as it is: through `Number()`,\n // `'12345678901234567.89'` printed as `12,345,678,901,234,568`. Any other\n // form keeps the number, since ICU throws on an exponent of a billion or more.\n // `format()` has read a string since ES2023 (Chrome 106, Firefox 116, Safari\n // 15.4); the ES2022 lib this builds against types it as a number only.\n const exact = text !== null && PLAIN_DECIMAL.test(text) ? (text as unknown as number) : n;\n return new Intl.NumberFormat(locale, options).format(exact);\n}\n\n/**\n * Locale-aware number formatting via `Intl.NumberFormat`. Uses Angular's\n * `LOCALE_ID` as the default locale.\n *\n * Accepts either a style shortcut (with optional currency code) or a full\n * `Intl.NumberFormatOptions` object for fine-grained control.\n * {@link wrFormatNumber} is the same output as a function.\n *\n * @example\n * ```html\n * {{ 1234.5 | wrNumber }} <!-- \"1,234.5\" -->\n * {{ 0.875 | wrNumber: 'percent' }} <!-- \"87.5%\" -->\n * {{ 19.99 | wrNumber: 'currency': 'USD' }} <!-- \"$19.99\" -->\n * {{ 19.99 | wrNumber: { minimumFractionDigits: 2 } }}\n * ```\n */\n@Pipe({ name: 'wrNumber' })\nexport class WrNumber implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: number | string | null | undefined,\n styleOrOptions: WrNumberStyle | Intl.NumberFormatOptions = 'decimal',\n currency = 'USD'\n ): string {\n return wrFormatNumber(value, this.locale, styleOrOptions, currency);\n }\n}\n\nexport type { WrNumberStyle } from './types';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrPluralForms, WrPluralOptions } from './types';\n\n/**\n * A count and its word form through `Intl.PluralRules`, in the given locale:\n * the same output as the `wrPlural` pipe, for a string built in TypeScript.\n * `options.locale` still wins over `locale`, as it wins over `LOCALE_ID` in the\n * pipe. `null`, `undefined` and a value that is not a finite number give an\n * empty string.\n *\n * @example\n * ```ts\n * wrFormatPlural(1, 'en', { one: 'comment', other: 'comments' }); // \"1 comment\"\n * wrFormatPlural(5, 'ru', { one: 'файл', few: 'файла', other: 'файлов' }); // \"5 файлов\"\n * wrFormatPlural(5, 'en', forms, { includeValue: false }); // \"comments\"\n * ```\n */\nexport function wrFormatPlural(\n value: number | null | undefined,\n locale: string,\n forms: WrPluralForms,\n options: WrPluralOptions = {}\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '';\n const tag = options.locale ?? locale;\n const category = new Intl.PluralRules(tag).select(value);\n const word = forms[category] ?? forms.other;\n if (options.includeValue === false) return word;\n return `${new Intl.NumberFormat(tag).format(value)} ${word}`;\n}\n\n/**\n * Locale-aware pluralization via `Intl.PluralRules`. Picks the right word\n * form for the count — including languages with more than two forms\n * (Russian, Polish, Arabic, …) — without hand-rolled `n % 10` rules.\n *\n * Categories follow CLDR: `zero` / `one` / `two` / `few` / `many` /\n * `other`. Missing categories fall back to `other`. {@link wrFormatPlural} is\n * the same output as a function.\n *\n * @example\n * ```html\n * {{ 1 | wrPlural: { one: 'comment', other: 'comments' } }}\n * <!-- \"1 comment\" -->\n *\n * {{ 5 | wrPlural: { one: 'файл', few: 'файла', other: 'файлов' } }}\n * <!-- \"5 файлов\" (with LOCALE_ID = ru) -->\n *\n * {{ count() | wrPlural: forms : { includeValue: false } }}\n * <!-- word only -->\n * ```\n */\n@Pipe({ name: 'wrPlural' })\nexport class WrPlural implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | null | undefined, forms: WrPluralForms, options: WrPluralOptions = {}): string {\n return wrFormatPlural(value, this.locale, forms, options);\n }\n}\n\nexport type { WrPluralForms, WrPluralOptions } from './types';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Creates an array of sequential indices `[0, 1, 2, ..., n-1]`.\n *\n * Useful for repeating template blocks a fixed number of times.\n *\n * @example\n * ```html\n * @for (i of (5 | wrRange); track i) {\n * <div>Item {{ i }}</div>\n * }\n * ```\n */\n@Pipe({ name: 'wrRange' })\nexport class WrRange implements PipeTransform {\n transform(length: number): number[] {\n return Array.from({ length }, (_, i) => i);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nlet segmenter: Intl.Segmenter | undefined;\n\n/**\n * Clamp a string to `length` characters, appending `ellipsis` when truncated.\n * A character is what a reader sees as one — a grapheme cluster — so an emoji,\n * a flag or a letter with a combining accent is kept whole or dropped whole,\n * never cut into half a surrogate pair that renders as a replacement box.\n *\n * @example\n * ```html\n * {{ 'Hello, world' | wrTruncate: 5 }} <!-- \"Hello…\" -->\n * {{ 'Hello, world' | wrTruncate: 8: '...' }} <!-- \"Hello, w...\" -->\n * ```\n */\n@Pipe({ name: 'wrTruncate' })\nexport class WrTruncate implements PipeTransform {\n transform(value: string | null | undefined, length = 80, ellipsis = '…'): string {\n if (value === null || value === undefined) return '';\n const s = String(value);\n // No more graphemes than UTF-16 units, so a string this short is never cut.\n if (s.length <= length) return s;\n // `slice`'s own coercion of the count, which is what this pipe did before it\n // counted graphemes: a fraction truncates, and NaN or a negative keeps nothing.\n const limit = Math.max(0, Math.trunc(length) || 0);\n segmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n let count = 0;\n for (const { index } of segmenter.segment(s)) {\n if (count === limit) return s.slice(0, index) + ellipsis;\n count++;\n }\n return s;\n }\n}\n"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ import { useI18nFormatter, useI18nText } from "ngwr/i18n";
|
|
|
6
6
|
import { numAttr } from "ngwr/utils";
|
|
7
7
|
import { WrPlatform } from "ngwr/platform";
|
|
8
8
|
import { WrCountUp } from "ngwr/counter";
|
|
9
|
+
import { wrSplitDuration } from "ngwr/pipes";
|
|
9
10
|
/**
|
|
10
11
|
* @license
|
|
11
12
|
*
|
|
@@ -304,24 +305,18 @@ function pad(n, width) {
|
|
|
304
305
|
return String(n).padStart(width, "0");
|
|
305
306
|
}
|
|
306
307
|
const TOKEN = /(?<![A-Za-z])(?:SSS|DD|HH|mm|ss|D|H|m|s)(?![A-Za-z])/g;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
308
|
+
const UNIT_OF = {
|
|
309
|
+
D: "day",
|
|
310
|
+
H: "hour",
|
|
311
|
+
m: "minute",
|
|
312
|
+
s: "second",
|
|
313
|
+
S: "millisecond"
|
|
314
|
+
};
|
|
314
315
|
function formatRemaining(ms, fmt) {
|
|
315
|
-
const shown = new Set((fmt.match(TOKEN) ?? []).map((token) => token[0]));
|
|
316
|
-
const value =
|
|
317
|
-
let rest = ms;
|
|
318
|
-
for (const [unit, size] of UNITS) {
|
|
319
|
-
if (!shown.has(unit)) continue;
|
|
320
|
-
value[unit] = Math.floor(rest / size);
|
|
321
|
-
rest -= value[unit] * size;
|
|
322
|
-
}
|
|
316
|
+
const shown = new Set((fmt.match(TOKEN) ?? []).map((token) => UNIT_OF[token[0]]));
|
|
317
|
+
const value = wrSplitDuration(ms, shown);
|
|
323
318
|
return fmt.replace(TOKEN, (token) => {
|
|
324
|
-
const n = value[token[0]];
|
|
319
|
+
const n = value[UNIT_OF[token[0]]] ?? 0;
|
|
325
320
|
return token === "SSS" ? pad(n, 3) : token.length === 2 ? pad(n, 2) : String(n);
|
|
326
321
|
});
|
|
327
322
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-statistic.mjs","names":[],"sources":["../../../projects/lib/statistic/statistic.ts","../../../projects/lib/statistic/statistic.html","../../../projects/lib/statistic/statistic-countdown.ts","../../../projects/lib/statistic/statistic-countdown.html","../../../projects/lib/statistic/statistic-group.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\n\nimport { WrCountUp } from 'ngwr/counter';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\n/** Digits with one optional point and a sign: the form `Intl` reads exactly. */\nconst PLAIN_DECIMAL = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/;\n\n/**\n * KPI card — label + big number + optional prefix/suffix + delta. Pair\n * with `<wr-statistic-group>` for a responsive dashboard grid.\n *\n * When `value` is numeric the number counts up (eased) from its previous\n * value to the new one on every change — see `animate`. String values and\n * `prefers-reduced-motion` render instantly.\n *\n * @example\n * ```html\n * <wr-statistic label=\"Active users\" [value]=\"12345\" suffix=\"users\" />\n * <wr-statistic label=\"Revenue\" [value]=\"9512\" prefix=\"$\" [delta]=\"12.4\" />\n * <wr-statistic label=\"Churn\" [value]=\"1.8\" suffix=\"%\" [precision]=\"1\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic',\n templateUrl: './statistic.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-statistic' },\n imports: [WrCountUp],\n})\nexport class WrStatistic {\n private readonly platform = inject(WrPlatform);\n private readonly locale = inject(LOCALE_ID);\n\n /**\n * The caption above the figure — what the number counts. Keep it short; the\n * value is the thing being read, and this names it.\n */\n readonly label = input<string>('');\n\n /** The number (or string) to display in the main slot. */\n readonly value = input<number | string | null>(null);\n\n /** Prefix glyph / symbol (e.g. `'$'`). */\n readonly prefix = input<string>('');\n\n /** Suffix glyph / unit (e.g. `'%'`, `'kg'`). */\n readonly suffix = input<string>('');\n\n /** Fixed number of decimals applied to numeric values. @default 0 */\n readonly precision = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /**\n * Count up to a new numeric `value` instead of swapping instantly. Always\n * off for string values or under `prefers-reduced-motion`. @default true\n */\n readonly animate = input(true, { transform: coerceBooleanProperty });\n\n /** Count-up duration (ms). @default 700 */\n readonly duration = input(700, { transform: numAttr(700) });\n\n /**\n * The current value coerced to a finite number, or `null` when `value` is\n * a non-numeric string / empty / nullish (rendered as-is).\n */\n protected readonly numericValue = computed<number | null>(() => {\n const raw = this.value();\n if (raw === null || raw === '') return null;\n const n = typeof raw === 'number' ? raw : coerceNumberProperty(raw, Number.NaN);\n return Number.isFinite(n) ? n : null;\n });\n\n /** Whether the value should count up (browser, numeric, opted-in, motion allowed). */\n protected readonly animated = computed<boolean>(\n () =>\n this.platform.isBrowser &&\n this.animate() &&\n !this.platform.prefersReducedMotion() &&\n typeof this.value() === 'number' &&\n this.numericValue() !== null\n );\n\n /** Animation start — the previously-rendered numeric value (0 on first paint). */\n protected readonly animateFrom = signal(0);\n\n /** Bookkeeping: the last numeric value seen, fed back as the next `from`. */\n private readonly lastNumeric = signal(0);\n\n constructor() {\n // Whenever the numeric value changes, snapshot the prior value as the\n // count-up start, then remember the new one for the next transition.\n effect(() => {\n const next = this.numericValue();\n if (next === null) return;\n untracked(() => {\n this.animateFrom.set(this.lastNumeric());\n this.lastNumeric.set(next);\n });\n });\n }\n\n /**\n * Static (non-animated) display string. Numeric values honour `precision`\n * and locale grouping so they match the count-up output; everything else\n * renders verbatim with an em-dash fallback.\n */\n protected readonly displayValue = computed<string>(() => {\n const n = this.numericValue();\n if (n !== null) {\n // A plain decimal string is formatted as it is, the way `wrNumber` does it,\n // so a long amount keeps every digit. `format()` has taken a string since\n // ES2023, though the ES2022 lib types it as a number only.\n const raw = typeof this.value() === 'string' ? String(this.value()).trim() : '';\n return new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: this.precision(),\n maximumFractionDigits: this.precision(),\n }).format(PLAIN_DECIMAL.test(raw) ? (raw as unknown as number) : n);\n }\n // `numericValue` above already reads `''` as \"no value\"; rendering it\n // verbatim put an empty card on screen instead of the placeholder.\n const raw = this.value();\n return raw === null || raw === undefined || raw === '' ? '—' : String(raw);\n });\n\n /**\n * Optional delta vs previous period — shown as `+x.x%` (or `-x.x%`).\n * Use `deltaSuffix` to change the trailing unit.\n */\n readonly delta = input<number | null>(null, {\n transform: (v: unknown): number | null => {\n if (v === null || v === undefined || v === '') return null;\n const n = coerceNumberProperty(v, Number.NaN);\n return Number.isFinite(n) ? n : null;\n },\n });\n\n // The default moved from the literal `'%'` to `null` so the catalog gets a\n // say. `%` is a string the LIBRARY owns and no locale could reach: a locale\n // that writes it differently — or writes it as a word — had nowhere to put\n // that but a per-instance binding on every `<wr-statistic>` on the page. A\n // bound value still wins, exactly as before, which is what makes this safe.\n /** Unit appended to the delta value. Falls back to `statistic.deltaSuffix`, then `'%'`. */\n readonly deltaSuffix = input<string | null>(null);\n\n protected readonly hasDelta = computed(() => this.delta() !== null);\n\n protected readonly deltaDirection = computed<'up' | 'down' | 'flat'>(() => {\n const d = this.delta();\n if (d === null || d === 0) return 'flat';\n return d > 0 ? 'up' : 'down';\n });\n\n protected readonly resolvedDeltaSuffix = useI18nText(this.deltaSuffix, 'statistic.deltaSuffix', '%');\n\n /** `{{value}}{{suffix}}` — the join, so a locale owns the space before the unit. */\n private readonly deltaFormat = useI18nFormatter('statistic.delta', '{{value}}{{suffix}}');\n\n /**\n * The delta chip, e.g. `+12.4%`.\n *\n * It was `` `${sign}${d}${suffix}` ``, printed one line under a value that\n * goes through `Intl.NumberFormat` — so the two disagreed inside one\n * component: ar-SA showed `١٬٢٣٤٬٥٦٧٫٨٩` above a Latin `12.4%`, and de-DE\n * `1.234.567,89` above `+12.4%`. The sign comes from `signDisplay`, which also\n * fixes the RTL half of it: `+` written by hand is a neutral character that\n * the BiDi algorithm moves to the other end of the number.\n *\n * The digit count is taken from the number rather than from `precision()`,\n * which describes the VALUE and not the delta — so what renders is the same\n * digits as before, in the locale's own numerals. One exception, and it is an\n * improvement rather than a regression: a magnitude JavaScript prints in\n * exponent notation (`1e-7`, `1e+21`) used to reach the DOM that way and is\n * now formatted as a number.\n */\n protected readonly deltaText = computed(() => {\n const d = this.delta();\n if (d === null) return '';\n const decimals = Math.min(20, (String(Math.abs(d)).split('.')[1] ?? '').length);\n const value = new Intl.NumberFormat(this.locale, {\n signDisplay: 'exceptZero',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(d);\n return this.deltaFormat({ value, suffix: this.resolvedDeltaSuffix() });\n });\n}\n","@if (label()) {\n <div class=\"wr-statistic__label\">{{ label() }}</div>\n}\n<div class=\"wr-statistic__value\">\n @if (prefix()) {\n <span class=\"wr-statistic__prefix\">{{ prefix() }}</span>\n }\n @if (animated()) {\n <wr-count-up\n class=\"wr-statistic__number\"\n [from]=\"animateFrom()\"\n [to]=\"numericValue() ?? 0\"\n [decimals]=\"precision()\"\n [duration]=\"duration()\"\n />\n } @else {\n <span class=\"wr-statistic__number\">{{ displayValue() }}</span>\n }\n @if (suffix()) {\n <span class=\"wr-statistic__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (hasDelta()) {\n <div [class]=\"`wr-statistic__delta wr-statistic__delta--${deltaDirection()}`\">\n @if (deltaDirection() === 'up') {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M5 1 L9 8 L1 8 Z\" fill=\"currentColor\" />\n </svg>\n } @else if (deltaDirection() === 'down') {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M5 9 L9 2 L1 2 Z\" fill=\"currentColor\" />\n </svg>\n }\n <span>{{ deltaText() }}</span>\n </div>\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n PLATFORM_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { numAttr } from 'ngwr/utils';\n\n// Helpers\n\nfunction toMs(value: Date | string | number): number {\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'number') return value;\n return new Date(value).getTime();\n}\n\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0');\n}\n\n/**\n * The format's tokens, longest first inside the alternation so `DD` beats `D`,\n * `HH` beats `H`, `mm` beats `m` and `SSS` beats `ss` beats `s`. Matched in one pass\n * so the output is never re-scanned: a sequential `.replace()` chain turned\n * `D days HH:mm:ss` into `1 day42 11:59:42`, its last `/s/g` step re-matching the\n * literal `s` inside `days`.\n */\nconst TOKEN = /(?<![A-Za-z])(?:SSS|DD|HH|mm|ss|D|H|m|s)(?![A-Za-z])/g;\n\n/** Each unit's length, largest first, keyed by its token's letter. */\nconst UNITS = [\n ['D', 86_400_000],\n ['H', 3_600_000],\n ['m', 60_000],\n ['s', 1000],\n ['S', 1],\n] as const;\n\n/**\n * A unit the format does not show is carried into the next larger one it does:\n * `HH:mm:ss` with three days left reads `73:00:00`. Every unit was taken modulo\n * the next one whatever the format held, so the default format dropped whole days\n * and showed `00:59:59` for three days and an hour.\n */\nfunction formatRemaining(ms: number, fmt: string): string {\n const shown = new Set((fmt.match(TOKEN) ?? []).map(token => token[0]));\n const value: Record<string, number> = {};\n let rest = ms;\n for (const [unit, size] of UNITS) {\n if (!shown.has(unit)) continue;\n value[unit] = Math.floor(rest / size);\n rest -= value[unit] * size;\n }\n return fmt.replace(TOKEN, token => {\n const n = value[token[0]];\n return token === 'SSS' ? pad(n, 3) : token.length === 2 ? pad(n, 2) : String(n);\n });\n}\n\n/**\n * Live-counting variant of `<wr-statistic>` that targets a future date\n * and ticks down to zero. The format string uses Day/Hour/Minute/Second\n * tokens (`D`, `H`, `m`, `s`, double for zero-padded, plus `SSS` for\n * milliseconds).\n *\n * @example\n * ```html\n * <wr-statistic-countdown\n * label=\"Launch in\"\n * [target]=\"launchDate\"\n * format=\"D days HH:mm:ss\"\n * (countdownEnd)=\"onLive()\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic-countdown',\n templateUrl: './statistic-countdown.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-statistic' },\n})\nexport class WrStatisticCountdown {\n /** Target date / timestamp the countdown is running down to. */\n readonly target = input.required<Date | string | number>();\n\n /** Label rendered above the value. */\n readonly label = input<string>('');\n\n /**\n * Format string. Tokens: `D`/`DD` (days), `H`/`HH` (hours),\n * `m`/`mm` (minutes), `s`/`ss` (seconds), `SSS` (milliseconds).\n * Double-letter forms zero-pad to width 2. A unit the format leaves out is\n * carried into the next larger one it shows, so `HH:mm:ss` reads `73:00:00`\n * with three days and an hour left. @default 'HH:mm:ss'\n */\n readonly format = input<string>('HH:mm:ss');\n\n /** Optional text shown once the countdown reaches zero. */\n readonly endText = input<string | null>(null);\n\n /**\n * Tick interval in ms. Drop to ~16 for `SSS` (millisecond display). Floored\n * at one frame — `0` (or an unparseable value) asked `setInterval` to run as\n * fast as the browser would allow, which is a busy loop, not a countdown.\n * @default 1000\n */\n readonly tickMs = input(1000, { transform: (v: unknown): number => Math.max(16, numAttr(1000)(v)) });\n\n /** Fires once when the countdown crosses zero. */\n readonly countdownEnd = output<void>();\n\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly targetMs = computed(() => toMs(this.target()));\n\n /**\n * Milliseconds left, or `null` when `target` does not parse to a date at all.\n * `new Date('nope').getTime()` is `NaN`, and `NaN` propagated all the way to\n * the screen as `NaN:NaN:NaN` — and, worse, `tick` compared it to `0` and so\n * never fired `countdownEnd`, while a guard that only clamped the text would\n * have fired it immediately for a target that was never reached.\n */\n private remainingMs(): number | null {\n const target = this.targetMs();\n return Number.isFinite(target) ? Math.max(0, target - Date.now()) : null;\n }\n\n /** Remaining milliseconds (live signal). */\n protected readonly remaining = signal(0);\n protected readonly finished = signal(false);\n\n protected readonly display = computed(() => {\n if (this.finished() && this.endText() != null) return this.endText()!;\n return formatRemaining(this.remaining(), this.format());\n });\n\n constructor() {\n // Seed once synchronously so SSR / first paint shows a sane value.\n effect(() => {\n const ms = this.remainingMs();\n this.remaining.set(ms ?? 0);\n this.finished.set(ms === 0);\n });\n\n if (!this.isBrowser) return;\n\n // The interval is REBUILT when `tickMs` changes, which is what makes the\n // input bindable rather than an authoring-time constant. Read once from\n // inside `afterNextRender` it was a plain, untracked read, so\n // `[tickMs]=\"precise() ? 16 : 1000\"` kept whatever period the first frame\n // saw and nothing looked wrong — the countdown still counted. Same shape\n // `wr-rotating-text` uses for its own rotation period; the `onCleanup` is\n // what keeps a period change from leaving the old interval running beside\n // the new one.\n effect(onCleanup => {\n const handle = setInterval(() => this.tick(), this.tickMs());\n onCleanup(() => clearInterval(handle));\n });\n }\n\n private tick(): void {\n const ms = this.remainingMs();\n if (ms === null) return;\n this.remaining.set(ms);\n if (ms === 0 && !this.finished()) {\n this.finished.set(true);\n this.countdownEnd.emit();\n }\n }\n}\n","@if (label()) {\n <div class=\"wr-statistic__label\">{{ label() }}</div>\n}\n<div class=\"wr-statistic__value\">\n <span class=\"wr-statistic__number\">{{ display() }}</span>\n</div>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, input } from '@angular/core';\n\n/**\n * Dashboard grid for `<wr-statistic>` cards. The column count follows the\n * group's OWN width — an intrinsic container-query grid sized by its box, not\n * a viewport media query — so it reflows correctly inside any column, card, or\n * split pane. Columns are never narrower than `min`; `columns` optionally caps\n * how many ever sit side by side on a wide container.\n *\n * @example\n * ```html\n * <wr-statistic-group [columns]=\"4\" min=\"11rem\">\n * <wr-statistic label=\"Active users\" [value]=\"12345\" />\n * <wr-statistic label=\"Revenue\" [value]=\"9512\" prefix=\"$\" [delta]=\"12.4\" />\n * <wr-statistic label=\"Churn\" [value]=\"1.8\" suffix=\"%\" [delta]=\"-0.4\" />\n * <wr-statistic label=\"Sessions\" [value]=\"48210\" />\n * </wr-statistic-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic-group',\n template: '<div class=\"wr-statistic-group__grid\"><ng-content /></div>',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-statistic-group',\n '[style.--wr-statistic-group-min]': 'min()',\n '[style.--wr-statistic-group-columns]': 'columns() || null',\n },\n})\nexport class WrStatisticGroup {\n /**\n * Minimum width of each column before the grid reflows to fewer columns.\n * Any CSS length — smaller packs more cards per row. @default '12rem'\n */\n readonly min = input<string>('12rem');\n\n /**\n * Cap on how many columns ever sit side by side on a wide container. `0`\n * (default) leaves it uncapped — fit as many `min`-wide columns as the\n * width allows. @default 0\n */\n readonly columns = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-statistic.mjs","names":[],"sources":["../../../projects/lib/statistic/statistic.ts","../../../projects/lib/statistic/statistic.html","../../../projects/lib/statistic/statistic-countdown.ts","../../../projects/lib/statistic/statistic-countdown.html","../../../projects/lib/statistic/statistic-group.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n LOCALE_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\n\nimport { WrCountUp } from 'ngwr/counter';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\n/** Digits with one optional point and a sign: the form `Intl` reads exactly. */\nconst PLAIN_DECIMAL = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)$/;\n\n/**\n * KPI card — label + big number + optional prefix/suffix + delta. Pair\n * with `<wr-statistic-group>` for a responsive dashboard grid.\n *\n * When `value` is numeric the number counts up (eased) from its previous\n * value to the new one on every change — see `animate`. String values and\n * `prefers-reduced-motion` render instantly.\n *\n * @example\n * ```html\n * <wr-statistic label=\"Active users\" [value]=\"12345\" suffix=\"users\" />\n * <wr-statistic label=\"Revenue\" [value]=\"9512\" prefix=\"$\" [delta]=\"12.4\" />\n * <wr-statistic label=\"Churn\" [value]=\"1.8\" suffix=\"%\" [precision]=\"1\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic',\n templateUrl: './statistic.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-statistic' },\n imports: [WrCountUp],\n})\nexport class WrStatistic {\n private readonly platform = inject(WrPlatform);\n private readonly locale = inject(LOCALE_ID);\n\n /**\n * The caption above the figure — what the number counts. Keep it short; the\n * value is the thing being read, and this names it.\n */\n readonly label = input<string>('');\n\n /** The number (or string) to display in the main slot. */\n readonly value = input<number | string | null>(null);\n\n /** Prefix glyph / symbol (e.g. `'$'`). */\n readonly prefix = input<string>('');\n\n /** Suffix glyph / unit (e.g. `'%'`, `'kg'`). */\n readonly suffix = input<string>('');\n\n /** Fixed number of decimals applied to numeric values. @default 0 */\n readonly precision = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /**\n * Count up to a new numeric `value` instead of swapping instantly. Always\n * off for string values or under `prefers-reduced-motion`. @default true\n */\n readonly animate = input(true, { transform: coerceBooleanProperty });\n\n /** Count-up duration (ms). @default 700 */\n readonly duration = input(700, { transform: numAttr(700) });\n\n /**\n * The current value coerced to a finite number, or `null` when `value` is\n * a non-numeric string / empty / nullish (rendered as-is).\n */\n protected readonly numericValue = computed<number | null>(() => {\n const raw = this.value();\n if (raw === null || raw === '') return null;\n const n = typeof raw === 'number' ? raw : coerceNumberProperty(raw, Number.NaN);\n return Number.isFinite(n) ? n : null;\n });\n\n /** Whether the value should count up (browser, numeric, opted-in, motion allowed). */\n protected readonly animated = computed<boolean>(\n () =>\n this.platform.isBrowser &&\n this.animate() &&\n !this.platform.prefersReducedMotion() &&\n typeof this.value() === 'number' &&\n this.numericValue() !== null\n );\n\n /** Animation start — the previously-rendered numeric value (0 on first paint). */\n protected readonly animateFrom = signal(0);\n\n /** Bookkeeping: the last numeric value seen, fed back as the next `from`. */\n private readonly lastNumeric = signal(0);\n\n constructor() {\n // Whenever the numeric value changes, snapshot the prior value as the\n // count-up start, then remember the new one for the next transition.\n effect(() => {\n const next = this.numericValue();\n if (next === null) return;\n untracked(() => {\n this.animateFrom.set(this.lastNumeric());\n this.lastNumeric.set(next);\n });\n });\n }\n\n /**\n * Static (non-animated) display string. Numeric values honour `precision`\n * and locale grouping so they match the count-up output; everything else\n * renders verbatim with an em-dash fallback.\n */\n protected readonly displayValue = computed<string>(() => {\n const n = this.numericValue();\n if (n !== null) {\n // A plain decimal string is formatted as it is, the way `wrNumber` does it,\n // so a long amount keeps every digit. `format()` has taken a string since\n // ES2023, though the ES2022 lib types it as a number only.\n const raw = typeof this.value() === 'string' ? String(this.value()).trim() : '';\n return new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: this.precision(),\n maximumFractionDigits: this.precision(),\n }).format(PLAIN_DECIMAL.test(raw) ? (raw as unknown as number) : n);\n }\n // `numericValue` above already reads `''` as \"no value\"; rendering it\n // verbatim put an empty card on screen instead of the placeholder.\n const raw = this.value();\n return raw === null || raw === undefined || raw === '' ? '—' : String(raw);\n });\n\n /**\n * Optional delta vs previous period — shown as `+x.x%` (or `-x.x%`).\n * Use `deltaSuffix` to change the trailing unit.\n */\n readonly delta = input<number | null>(null, {\n transform: (v: unknown): number | null => {\n if (v === null || v === undefined || v === '') return null;\n const n = coerceNumberProperty(v, Number.NaN);\n return Number.isFinite(n) ? n : null;\n },\n });\n\n // The default moved from the literal `'%'` to `null` so the catalog gets a\n // say. `%` is a string the LIBRARY owns and no locale could reach: a locale\n // that writes it differently — or writes it as a word — had nowhere to put\n // that but a per-instance binding on every `<wr-statistic>` on the page. A\n // bound value still wins, exactly as before, which is what makes this safe.\n /** Unit appended to the delta value. Falls back to `statistic.deltaSuffix`, then `'%'`. */\n readonly deltaSuffix = input<string | null>(null);\n\n protected readonly hasDelta = computed(() => this.delta() !== null);\n\n protected readonly deltaDirection = computed<'up' | 'down' | 'flat'>(() => {\n const d = this.delta();\n if (d === null || d === 0) return 'flat';\n return d > 0 ? 'up' : 'down';\n });\n\n protected readonly resolvedDeltaSuffix = useI18nText(this.deltaSuffix, 'statistic.deltaSuffix', '%');\n\n /** `{{value}}{{suffix}}` — the join, so a locale owns the space before the unit. */\n private readonly deltaFormat = useI18nFormatter('statistic.delta', '{{value}}{{suffix}}');\n\n /**\n * The delta chip, e.g. `+12.4%`.\n *\n * It was `` `${sign}${d}${suffix}` ``, printed one line under a value that\n * goes through `Intl.NumberFormat` — so the two disagreed inside one\n * component: ar-SA showed `١٬٢٣٤٬٥٦٧٫٨٩` above a Latin `12.4%`, and de-DE\n * `1.234.567,89` above `+12.4%`. The sign comes from `signDisplay`, which also\n * fixes the RTL half of it: `+` written by hand is a neutral character that\n * the BiDi algorithm moves to the other end of the number.\n *\n * The digit count is taken from the number rather than from `precision()`,\n * which describes the VALUE and not the delta — so what renders is the same\n * digits as before, in the locale's own numerals. One exception, and it is an\n * improvement rather than a regression: a magnitude JavaScript prints in\n * exponent notation (`1e-7`, `1e+21`) used to reach the DOM that way and is\n * now formatted as a number.\n */\n protected readonly deltaText = computed(() => {\n const d = this.delta();\n if (d === null) return '';\n const decimals = Math.min(20, (String(Math.abs(d)).split('.')[1] ?? '').length);\n const value = new Intl.NumberFormat(this.locale, {\n signDisplay: 'exceptZero',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(d);\n return this.deltaFormat({ value, suffix: this.resolvedDeltaSuffix() });\n });\n}\n","@if (label()) {\n <div class=\"wr-statistic__label\">{{ label() }}</div>\n}\n<div class=\"wr-statistic__value\">\n @if (prefix()) {\n <span class=\"wr-statistic__prefix\">{{ prefix() }}</span>\n }\n @if (animated()) {\n <wr-count-up\n class=\"wr-statistic__number\"\n [from]=\"animateFrom()\"\n [to]=\"numericValue() ?? 0\"\n [decimals]=\"precision()\"\n [duration]=\"duration()\"\n />\n } @else {\n <span class=\"wr-statistic__number\">{{ displayValue() }}</span>\n }\n @if (suffix()) {\n <span class=\"wr-statistic__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (hasDelta()) {\n <div [class]=\"`wr-statistic__delta wr-statistic__delta--${deltaDirection()}`\">\n @if (deltaDirection() === 'up') {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M5 1 L9 8 L1 8 Z\" fill=\"currentColor\" />\n </svg>\n } @else if (deltaDirection() === 'down') {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M5 9 L9 2 L1 2 Z\" fill=\"currentColor\" />\n </svg>\n }\n <span>{{ deltaText() }}</span>\n </div>\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n PLATFORM_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { type WrDurationUnit, wrSplitDuration } from 'ngwr/pipes';\nimport { numAttr } from 'ngwr/utils';\n\n// Helpers\n\nfunction toMs(value: Date | string | number): number {\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'number') return value;\n return new Date(value).getTime();\n}\n\nfunction pad(n: number, width: number): string {\n return String(n).padStart(width, '0');\n}\n\n/**\n * The format's tokens, longest first inside the alternation so `DD` beats `D`,\n * `HH` beats `H`, `mm` beats `m` and `SSS` beats `ss` beats `s`. Matched in one pass\n * so the output is never re-scanned: a sequential `.replace()` chain turned\n * `D days HH:mm:ss` into `1 day42 11:59:42`, its last `/s/g` step re-matching the\n * literal `s` inside `days`.\n */\nconst TOKEN = /(?<![A-Za-z])(?:SSS|DD|HH|mm|ss|D|H|m|s)(?![A-Za-z])/g;\n\n/** The unit each token's letter stands for. */\nconst UNIT_OF: Readonly<Record<string, WrDurationUnit>> = {\n D: 'day',\n H: 'hour',\n m: 'minute',\n s: 'second',\n S: 'millisecond',\n};\n\n/**\n * A unit the format does not show is carried into the next larger one it does:\n * `HH:mm:ss` with three days left reads `73:00:00`. Every unit was taken modulo\n * the next one whatever the format held, so the default format dropped whole days\n * and showed `00:59:59` for three days and an hour. The split itself is\n * `wrSplitDuration`, shared with `wrDuration`.\n */\nfunction formatRemaining(ms: number, fmt: string): string {\n const shown = new Set((fmt.match(TOKEN) ?? []).map(token => UNIT_OF[token[0]]));\n const value = wrSplitDuration(ms, shown);\n return fmt.replace(TOKEN, token => {\n const n = value[UNIT_OF[token[0]]] ?? 0;\n return token === 'SSS' ? pad(n, 3) : token.length === 2 ? pad(n, 2) : String(n);\n });\n}\n\n/**\n * Live-counting variant of `<wr-statistic>` that targets a future date\n * and ticks down to zero. The format string uses Day/Hour/Minute/Second\n * tokens (`D`, `H`, `m`, `s`, double for zero-padded, plus `SSS` for\n * milliseconds).\n *\n * @example\n * ```html\n * <wr-statistic-countdown\n * label=\"Launch in\"\n * [target]=\"launchDate\"\n * format=\"D days HH:mm:ss\"\n * (countdownEnd)=\"onLive()\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic-countdown',\n templateUrl: './statistic-countdown.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-statistic' },\n})\nexport class WrStatisticCountdown {\n /** Target date / timestamp the countdown is running down to. */\n readonly target = input.required<Date | string | number>();\n\n /** Label rendered above the value. */\n readonly label = input<string>('');\n\n /**\n * Format string. Tokens: `D`/`DD` (days), `H`/`HH` (hours),\n * `m`/`mm` (minutes), `s`/`ss` (seconds), `SSS` (milliseconds).\n * Double-letter forms zero-pad to width 2. A unit the format leaves out is\n * carried into the next larger one it shows, so `HH:mm:ss` reads `73:00:00`\n * with three days and an hour left. @default 'HH:mm:ss'\n */\n readonly format = input<string>('HH:mm:ss');\n\n /** Optional text shown once the countdown reaches zero. */\n readonly endText = input<string | null>(null);\n\n /**\n * Tick interval in ms. Drop to ~16 for `SSS` (millisecond display). Floored\n * at one frame — `0` (or an unparseable value) asked `setInterval` to run as\n * fast as the browser would allow, which is a busy loop, not a countdown.\n * @default 1000\n */\n readonly tickMs = input(1000, { transform: (v: unknown): number => Math.max(16, numAttr(1000)(v)) });\n\n /** Fires once when the countdown crosses zero. */\n readonly countdownEnd = output<void>();\n\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly targetMs = computed(() => toMs(this.target()));\n\n /**\n * Milliseconds left, or `null` when `target` does not parse to a date at all.\n * `new Date('nope').getTime()` is `NaN`, and `NaN` propagated all the way to\n * the screen as `NaN:NaN:NaN` — and, worse, `tick` compared it to `0` and so\n * never fired `countdownEnd`, while a guard that only clamped the text would\n * have fired it immediately for a target that was never reached.\n */\n private remainingMs(): number | null {\n const target = this.targetMs();\n return Number.isFinite(target) ? Math.max(0, target - Date.now()) : null;\n }\n\n /** Remaining milliseconds (live signal). */\n protected readonly remaining = signal(0);\n protected readonly finished = signal(false);\n\n protected readonly display = computed(() => {\n if (this.finished() && this.endText() != null) return this.endText()!;\n return formatRemaining(this.remaining(), this.format());\n });\n\n constructor() {\n // Seed once synchronously so SSR / first paint shows a sane value.\n effect(() => {\n const ms = this.remainingMs();\n this.remaining.set(ms ?? 0);\n this.finished.set(ms === 0);\n });\n\n if (!this.isBrowser) return;\n\n // The interval is REBUILT when `tickMs` changes, which is what makes the\n // input bindable rather than an authoring-time constant. Read once from\n // inside `afterNextRender` it was a plain, untracked read, so\n // `[tickMs]=\"precise() ? 16 : 1000\"` kept whatever period the first frame\n // saw and nothing looked wrong — the countdown still counted. Same shape\n // `wr-rotating-text` uses for its own rotation period; the `onCleanup` is\n // what keeps a period change from leaving the old interval running beside\n // the new one.\n effect(onCleanup => {\n const handle = setInterval(() => this.tick(), this.tickMs());\n onCleanup(() => clearInterval(handle));\n });\n }\n\n private tick(): void {\n const ms = this.remainingMs();\n if (ms === null) return;\n this.remaining.set(ms);\n if (ms === 0 && !this.finished()) {\n this.finished.set(true);\n this.countdownEnd.emit();\n }\n }\n}\n","@if (label()) {\n <div class=\"wr-statistic__label\">{{ label() }}</div>\n}\n<div class=\"wr-statistic__value\">\n <span class=\"wr-statistic__number\">{{ display() }}</span>\n</div>\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, input } from '@angular/core';\n\n/**\n * Dashboard grid for `<wr-statistic>` cards. The column count follows the\n * group's OWN width — an intrinsic container-query grid sized by its box, not\n * a viewport media query — so it reflows correctly inside any column, card, or\n * split pane. Columns are never narrower than `min`; `columns` optionally caps\n * how many ever sit side by side on a wide container.\n *\n * @example\n * ```html\n * <wr-statistic-group [columns]=\"4\" min=\"11rem\">\n * <wr-statistic label=\"Active users\" [value]=\"12345\" />\n * <wr-statistic label=\"Revenue\" [value]=\"9512\" prefix=\"$\" [delta]=\"12.4\" />\n * <wr-statistic label=\"Churn\" [value]=\"1.8\" suffix=\"%\" [delta]=\"-0.4\" />\n * <wr-statistic label=\"Sessions\" [value]=\"48210\" />\n * </wr-statistic-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/statistic\n */\n@Component({\n selector: 'wr-statistic-group',\n template: '<div class=\"wr-statistic-group__grid\"><ng-content /></div>',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-statistic-group',\n '[style.--wr-statistic-group-min]': 'min()',\n '[style.--wr-statistic-group-columns]': 'columns() || null',\n },\n})\nexport class WrStatisticGroup {\n /**\n * Minimum width of each column before the grid reflows to fewer columns.\n * Any CSS length — smaller packs more cards per row. @default '12rem'\n */\n readonly min = input<string>('12rem');\n\n /**\n * Cap on how many columns ever sit side by side on a wide container. `0`\n * (default) leaves it uncapped — fit as many `min`-wide columns as the\n * width allows. @default 0\n */\n readonly columns = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,MAAM,gBAAgB;AA0BtB,IAAa,cAAb,MAAa,YAAW;CACL,WAAW,OAAO,UAAU;CAC5B,SAAS,OAAO,SAAS;CAMjC,QAAQ,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGzB,QAAQ,MAA8B,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,SAAS,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG1B,SAAS,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAG1B,YAAY,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC;CAAC,CAAA;CAMjG,UAAU,MAAM,MAAI;EAAA,GAAA,YAAA,EAAA,WAAA,UAAA,+BAAA,CAAA;EAAI,WAAW;CAAqB,CAAA;CAGxD,WAAW,MAAM,KAAG;EAAA,GAAA,YAAA,EAAA,WAAA,WAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,GAAG;CAAC,CAAA;CAMrC,eAAe,eAA6B;EAC7D,MAAM,MAAM,KAAK,MAAK;EACtB,IAAI,QAAQ,QAAQ,QAAQ,IAAI,OAAO;EACvC,MAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,qBAAqB,KAAK,GAAU;EAC9E,OAAO,OAAO,SAAS,CAAC,IAAI,IAAI;CAClC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAGiB,WAAW,eAE1B,KAAK,SAAS,aACd,KAAK,QAAO,KACZ,CAAC,KAAK,SAAS,qBAAoB,KACnC,OAAO,KAAK,MAAK,MAAO,YACxB,KAAK,aAAY,MAAO,MAC3B,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAGkB,cAAc,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAGzB,cAAc,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAExC,cAAA;EAGE,aAAY;GACV,MAAM,OAAO,KAAK,aAAY;GAC9B,IAAI,SAAS,MAAM;GACnB,gBAAe;IACb,KAAK,YAAY,IAAI,KAAK,YAAW,CAAE;IACvC,KAAK,YAAY,IAAI,IAAI;GAC3B,CAAC;EACH,CAAC;CACH;CAOmB,eAAe,eAAsB;EACtD,MAAM,IAAI,KAAK,aAAY;EAC3B,IAAI,MAAM,MAAM;GAId,MAAM,MAAM,OAAO,KAAK,MAAK,MAAO,WAAW,OAAO,KAAK,MAAK,CAAE,CAAC,CAAC,KAAI,IAAK;GAC7E,OAAO,IAAI,KAAK,aAAa,KAAK,QAAQ;IACxC,uBAAuB,KAAK,UAAS;IACrC,uBAAuB,KAAK,UAAS;GACtC,CAAA,CAAC,CAAC,OAAO,cAAc,KAAK,GAAG,IAAK,MAA4B,CAAC;EACpE;EAGA,MAAM,MAAM,KAAK,MAAK;EACtB,OAAO,QAAQ,QAAQ,QAAQ,UAAa,QAAQ,KAAK,MAAM,OAAO,GAAG;CAC3E,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAMO,QAAQ,MAAqB,MAAI;EAAA,GAAA,YAAA,EAAA,WAAA,QAAA,+BAAA,CAAA;EACxC,YAAY,MAA6B;GACvC,IAAI,MAAM,QAAQ,MAAM,UAAa,MAAM,IAAI,OAAO;GACtD,MAAM,IAAI,qBAAqB,GAAG,GAAU;GAC5C,OAAO,OAAO,SAAS,CAAC,IAAI,IAAI;EAClC;CAAC,CAAA;CASM,cAAc,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAE9B,WAAW,eAAe,KAAK,MAAK,MAAO,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAEhD,iBAAiB,eAAsC;EACxE,MAAM,IAAI,KAAK,MAAK;EACpB,IAAI,MAAM,QAAQ,MAAM,GAAG,OAAO;EAClC,OAAO,IAAI,IAAI,OAAO;CACxB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAEiB,sBAAsB,YAAY,KAAK,aAAa,yBAAyB,GAAG;CAGlF,cAAc,iBAAiB,mBAAmB,qBAAqB;CAmBrE,YAAY,eAAc;EAC3C,MAAM,IAAI,KAAK,MAAK;EACpB,IAAI,MAAM,MAAM,OAAO;EACvB,MAAM,WAAW,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,GAAA,CAAI,MAAM;EAC9E,MAAM,QAAQ,IAAI,KAAK,aAAa,KAAK,QAAQ;GAC/C,aAAa;GACb,uBAAuB;GACvB,uBAAuB;EACxB,CAAA,CAAC,CAAC,OAAO,CAAC;EACX,OAAO,KAAK,YAAY;GAAE;GAAO,QAAQ,KAAK,oBAAmB;EAAE,CAAE;CACvE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CA1JS,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAW,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAX,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAW,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,aAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,gBAAA,eAAA;EAAA,UAAA;EAAA,UCpDxB;EAoCA,cAAA,CAAA;GAAA,MAAA;GAAA,MDcY;GAAS,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;GAAA,SAAA,CAAA,WAAA,WAAA;EAAA,CAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAER,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAW,YAAA,CAAA;EAPvB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAc,eAET,kBAAkB;GAAI,MAC/B,EAAE,OAAO,eAAc;GAAE,SACtB,CAAC,SAAS;GAAC,UAAA;EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEzBtB,SAAS,KAAK,OAA6B;CACzC,IAAI,iBAAiB,MAAM,OAAO,MAAM,QAAO;CAC/C,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,OAAO,IAAI,KAAK,KAAK,CAAC,CAAC,QAAO;AAChC;AAEA,SAAS,IAAI,GAAW,OAAa;CACnC,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,OAAO,GAAG;AACtC;AASA,MAAM,QAAQ;AAGd,MAAM,UAAoD;CACxD,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACJ;AASD,SAAS,gBAAgB,IAAY,KAAW;CAC9C,MAAM,QAAQ,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,CAAA,EAAA,CAAI,KAAI,UAAS,QAAQ,MAAM,GAAG,CAAC;CAC9E,MAAM,QAAQ,gBAAgB,IAAI,KAAK;CACvC,OAAO,IAAI,QAAQ,QAAO,UAAQ;EAChC,MAAM,IAAI,MAAM,QAAQ,MAAM,QAAQ;EACtC,OAAO,UAAU,QAAQ,IAAI,GAAG,CAAC,IAAI,MAAM,WAAW,IAAI,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC;CAChF,CAAC;AACH;AA0BA,IAAa,uBAAb,MAAa,qBAAoB;CAEtB,SAAS,MAAM,SAAkC,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGjD,QAAQ,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CASzB,SAAS,MAAc,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAGlC,UAAU,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAQpC,SAAS,MAAM,KAAI;EAAA,GAAA,YAAA,EAAA,WAAA,SAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,KAAK,IAAI,IAAI,QAAQ,GAAI,CAAC,CAAC,CAAC,CAAC;CAAC,CAAA;CAGxF,eAAe,OAAM;CAEb,YAAY,kBAAkB,OAAO,WAAW,CAAC;CAEjD,WAAW,eAAe,KAAK,KAAK,OAAM,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CASvD,cAAW;EACjB,MAAM,SAAS,KAAK,SAAQ;EAC5B,OAAO,OAAO,SAAS,MAAM,IAAI,KAAK,IAAI,GAAG,SAAS,KAAK,IAAG,CAAE,IAAI;CACtE;CAGmB,YAAY,OAAO,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CACrB,WAAW,OAAO,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAExB,UAAU,eAAc;EACzC,IAAI,KAAK,SAAQ,KAAM,KAAK,QAAO,KAAM,MAAM,OAAO,KAAK,QAAO;EAClE,OAAO,gBAAgB,KAAK,UAAS,GAAI,KAAK,OAAM,CAAE;CACxD,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEF,cAAA;EAEE,aAAY;GACV,MAAM,KAAK,KAAK,YAAW;GAC3B,KAAK,UAAU,IAAI,MAAM,CAAC;GAC1B,KAAK,SAAS,IAAI,OAAO,CAAC;EAC5B,CAAC;EAED,IAAI,CAAC,KAAK,WAAW;EAUrB,QAAO,cAAY;GACjB,MAAM,SAAS,kBAAkB,KAAK,KAAI,GAAI,KAAK,OAAM,CAAE;GAC3D,gBAAgB,cAAc,MAAM,CAAC;EACvC,CAAC;CACH;CAEQ,OAAI;EACV,MAAM,KAAK,KAAK,YAAW;EAC3B,IAAI,OAAO,MAAM;EACjB,KAAK,UAAU,IAAI,EAAE;EACrB,IAAI,OAAO,KAAK,CAAC,KAAK,SAAQ,GAAI;GAChC,KAAK,SAAS,IAAI,IAAI;GACtB,KAAK,aAAa,KAAI;EACxB;CACF;CAvFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAoB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,cAAA,eAAA;EAAA,MAAA,EAAA,gBAAA,eAAA;EAAA,UAAA;EAAA,UC7FjC;EAMA,eAAA,GAAA,kBAAA;CAAA,CAAA;;ADuFa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EANhC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAwB,eAEnB,kBAAkB;GAAI,MAC/B,EAAE,OAAO,eAAc;GAAE,UAAA;EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEpDjC,IAAa,mBAAb,MAAa,iBAAgB;CAKlB,MAAM,MAAc,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CAO5B,UAAU,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,UAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,KAAK,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC;CAAC,CAAA;CAZ7F,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAgB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAhB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAgB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,KAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,YAAA;IAAA,kCAAA;IAAA,sCAAA;GAAA;GAAA,gBAAA;EAAA;EAAA,UAAA;EAAA,UARjB;EAA4D,UAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAQ3D,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAgB,YAAA,CAAA;EAV5B,MAAA;EAAU,MAAA,CAAA;GACT,UAAU;GACV,UAAU;GACV,eAAe,kBAAkB;GACjC,MAAM;IACJ,OAAO;IACP,oCAAoC;IACpC,wCAAwC;GACzC;EACF,CAAA"}
|
|
@@ -5,7 +5,7 @@ import { InjectionToken } from "@angular/core";
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
7
7
|
*/
|
|
8
|
-
const NGWR_VERSION = "15.
|
|
8
|
+
const NGWR_VERSION = "15.4.0";
|
|
9
9
|
/**
|
|
10
10
|
* @license
|
|
11
11
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-version.mjs","names":[],"sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release/prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '15.
|
|
1
|
+
{"version":3,"file":"ngwr-version.mjs","names":[],"sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The ngwr library version string, kept in sync with\n * `projects/lib/package.json` by `scripts/release/prepare.ts`.\n *\n * Prefer this over reading `package.json` at runtime — that requires a JSON\n * import or `require`, neither of which tree-shakes well in Angular builds.\n *\n * Distinct from `Version` exported by `@angular/core`, which reports the\n * loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.\n */\nexport const NGWR_VERSION = '15.4.0';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { InjectionToken } from '@angular/core';\n\nimport { NGWR_VERSION } from './version';\n\n/**\n * Injection token holding the ngwr library version string.\n *\n * Default factory returns {@link NGWR_VERSION}. Override it in tests or in\n * apps that want to surface a custom build identifier.\n *\n * @example\n * ```ts\n * @Component({...})\n * export class FooterComponent {\n * readonly version = inject(NGWR_VERSION_TOKEN);\n * }\n * ```\n */\nexport const NGWR_VERSION_TOKEN = new InjectionToken<string>('NGWR_VERSION_TOKEN', {\n providedIn: 'root',\n factory: () => NGWR_VERSION,\n});\n"],"mappings":";;;;;;;AAiBA,MAAa,eAAe;;;;;;;ACQ5B,MAAa,qBAAqB,IAAI,eAAuB,sBAAsB;CACjF,YAAY;CACZ,eAAe;AAChB,CAAA"}
|
package/llms-full.txt
CHANGED
|
@@ -1031,8 +1031,8 @@
|
|
|
1031
1031
|
|
|
1032
1032
|
## ngwr/pipes
|
|
1033
1033
|
- import: `import { WrBytes } from 'ngwr/pipes'`
|
|
1034
|
-
- exports: WrBytes, WrDate, WrMark, WrNumber, WrNumberStyle, WrPlural, WrPluralForms, WrPluralOptions, WrRange, WrTruncate
|
|
1035
|
-
- Template formatting: `wrDate`, `wrNumber`, `wrBytes`, `wrPlural`, `wrRange`, `wrTruncate` and `wrMark` for search highlighting.
|
|
1034
|
+
- exports: WrBytes, wrFormatBytes, WrDate, wrFormatDate, WrDuration, wrFormatDuration, wrSplitDuration, WrDurationStyle, WrDurationUnit, WrMark, WrNumber, wrFormatNumber, WrNumberStyle, WrPlural, wrFormatPlural, WrPluralForms, WrPluralOptions, WrRange, WrTruncate
|
|
1035
|
+
- Template formatting: `wrDate`, `wrNumber`, `wrBytes`, `wrDuration`, `wrPlural`, `wrRange`, `wrTruncate` and `wrMark` for search highlighting. The same formatting as functions, for a string built in TypeScript: `wrFormatDate`, `wrFormatNumber`, `wrFormatBytes`, `wrFormatDuration` and `wrFormatPlural`, each taking the locale as its second argument, except `wrFormatDate`, which takes a `WrDateAdapter`.
|
|
1036
1036
|
|
|
1037
1037
|
## ngwr/platform
|
|
1038
1038
|
- import: `import { WrPlatform } from 'ngwr/platform'`
|