ngwr 15.2.0 → 15.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/alert/styles/_index.scss +13 -5
  2. package/fesm2022/ngwr-alert.mjs +2 -2
  3. package/fesm2022/ngwr-alert.mjs.map +1 -1
  4. package/fesm2022/ngwr-breadcrumbs.mjs +18 -2
  5. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  6. package/fesm2022/ngwr-hotkey.mjs +23 -1
  7. package/fesm2022/ngwr-hotkey.mjs.map +1 -1
  8. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  9. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  10. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  11. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  12. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  13. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  14. package/fesm2022/ngwr-i18n-en.mjs +2 -0
  15. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  16. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  17. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  18. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  19. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  20. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  21. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  22. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  23. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  24. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  25. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  26. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  27. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  28. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  29. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  30. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  31. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  32. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  33. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  35. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  37. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  38. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  39. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  40. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  41. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  42. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  43. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  44. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  45. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  46. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  47. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  48. package/fesm2022/ngwr-i18n-zh-tw.mjs +2 -0
  49. package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
  50. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  51. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  52. package/fesm2022/ngwr-pagination.mjs +3 -0
  53. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  54. package/fesm2022/ngwr-pipes.mjs +5 -2
  55. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  56. package/fesm2022/ngwr-schema-form.mjs +3 -0
  57. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  58. package/fesm2022/ngwr-select-testing.mjs +16 -0
  59. package/fesm2022/ngwr-select-testing.mjs.map +1 -1
  60. package/fesm2022/ngwr-select.mjs +218 -38
  61. package/fesm2022/ngwr-select.mjs.map +1 -1
  62. package/fesm2022/ngwr-statistic.mjs +9 -5
  63. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  64. package/fesm2022/ngwr-version.mjs +1 -1
  65. package/fesm2022/ngwr-version.mjs.map +1 -1
  66. package/i18n/ar.json +2 -0
  67. package/i18n/cs.json +2 -0
  68. package/i18n/de.json +2 -0
  69. package/i18n/en.json +2 -0
  70. package/i18n/es.json +2 -0
  71. package/i18n/fr.json +2 -0
  72. package/i18n/he.json +2 -0
  73. package/i18n/hi.json +2 -0
  74. package/i18n/id.json +2 -0
  75. package/i18n/it.json +2 -0
  76. package/i18n/ja.json +2 -0
  77. package/i18n/ko.json +2 -0
  78. package/i18n/nl.json +2 -0
  79. package/i18n/pl.json +2 -0
  80. package/i18n/pt.json +2 -0
  81. package/i18n/ru.json +2 -0
  82. package/i18n/sv.json +2 -0
  83. package/i18n/tr.json +2 -0
  84. package/i18n/uk.json +2 -0
  85. package/i18n/vi.json +2 -0
  86. package/i18n/zh-TW.json +2 -0
  87. package/i18n/zh.json +2 -0
  88. package/llms-full.txt +3 -3
  89. package/package.json +1 -1
  90. package/schematics/use/symbol-map.json +1 -0
  91. package/select/styles/_index.scss +23 -1
  92. package/skills/ngwr/references/catalog.md +1 -1
  93. package/types/ngwr-alert.d.ts +3 -1
  94. package/types/ngwr-breadcrumbs.d.ts +15 -0
  95. package/types/ngwr-hotkey.d.ts +14 -2
  96. package/types/ngwr-select-testing.d.ts +9 -0
  97. package/types/ngwr-select.d.ts +133 -2
  98. package/types/ngwr-sidebar.d.ts +1 -1
  99. package/types/ngwr-version.d.ts +1 -1
@@ -12,6 +12,7 @@ import { WrCountUp } from "ngwr/counter";
12
12
  * Use of this source code is governed by an MIT-style license that can be
13
13
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
14
14
  */
15
+ const PLAIN_DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
15
16
  var WrStatistic = class WrStatistic {
16
17
  platform = inject(WrPlatform);
17
18
  locale = inject(LOCALE_ID);
@@ -37,7 +38,7 @@ var WrStatistic = class WrStatistic {
37
38
  const n = typeof raw === "number" ? raw : coerceNumberProperty(raw, NaN);
38
39
  return Number.isFinite(n) ? n : null;
39
40
  }, ...ngDevMode ? [{ debugName: "numericValue" }] : /* istanbul ignore next */ []);
40
- animated = computed(() => this.platform.isBrowser && this.animate() && !this.platform.prefersReducedMotion() && this.numericValue() !== null, ...ngDevMode ? [{ debugName: "animated" }] : /* istanbul ignore next */ []);
41
+ animated = computed(() => this.platform.isBrowser && this.animate() && !this.platform.prefersReducedMotion() && typeof this.value() === "number" && this.numericValue() !== null, ...ngDevMode ? [{ debugName: "animated" }] : /* istanbul ignore next */ []);
41
42
  animateFrom = signal(0, ...ngDevMode ? [{ debugName: "animateFrom" }] : /* istanbul ignore next */ []);
42
43
  lastNumeric = signal(0, ...ngDevMode ? [{ debugName: "lastNumeric" }] : /* istanbul ignore next */ []);
43
44
  constructor() {
@@ -52,10 +53,13 @@ var WrStatistic = class WrStatistic {
52
53
  }
53
54
  displayValue = computed(() => {
54
55
  const n = this.numericValue();
55
- if (n !== null) return new Intl.NumberFormat(this.locale, {
56
- minimumFractionDigits: this.precision(),
57
- maximumFractionDigits: this.precision()
58
- }).format(n);
56
+ if (n !== null) {
57
+ const raw = typeof this.value() === "string" ? String(this.value()).trim() : "";
58
+ return new Intl.NumberFormat(this.locale, {
59
+ minimumFractionDigits: this.precision(),
60
+ maximumFractionDigits: this.precision()
61
+ }).format(PLAIN_DECIMAL.test(raw) ? raw : n);
62
+ }
59
63
  const raw = this.value();
60
64
  return raw === null || raw === void 0 || raw === "" ? "—" : String(raw);
61
65
  }, ...ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []);
@@ -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/**\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 && this.animate() && !this.platform.prefersReducedMotion() && 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 return new Intl.NumberFormat(this.locale, {\n minimumFractionDigits: this.precision(),\n maximumFractionDigits: this.precision(),\n }).format(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 { 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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"}
@@ -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.2.0";
8
+ const NGWR_VERSION = "15.3.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.2.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"}
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.3.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/i18n/ar.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "إزالة {{label}}",
101
101
  "noResults": "لا توجد نتائج",
102
102
  "loading": "جارٍ التحميل…",
103
+ "loadError": "تعذّر تحميل النتائج",
104
+ "retry": "إعادة المحاولة",
103
105
  "more": "+{{count}} أخرى"
104
106
  },
105
107
  "tree": {
package/i18n/cs.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Odebrat {{label}}",
101
101
  "noResults": "Žádné výsledky",
102
102
  "loading": "Načítání…",
103
+ "loadError": "Výsledky se nepodařilo načíst",
104
+ "retry": "Zkusit znovu",
103
105
  "more": "Další: {{count}}"
104
106
  },
105
107
  "tree": {
package/i18n/de.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}} entfernen",
101
101
  "noResults": "Keine Ergebnisse",
102
102
  "loading": "Wird geladen…",
103
+ "loadError": "Ergebnisse konnten nicht geladen werden",
104
+ "retry": "Erneut versuchen",
103
105
  "more": "+{{count}} weitere"
104
106
  },
105
107
  "tree": {
package/i18n/en.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Remove {{label}}",
101
101
  "noResults": "No results",
102
102
  "loading": "Loading…",
103
+ "loadError": "Could not load results",
104
+ "retry": "Try again",
103
105
  "more": "+{{count}} more"
104
106
  },
105
107
  "tree": {
package/i18n/es.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Quitar {{label}}",
101
101
  "noResults": "Sin resultados",
102
102
  "loading": "Cargando…",
103
+ "loadError": "No se pudieron cargar los resultados",
104
+ "retry": "Reintentar",
103
105
  "more": "+{{count}} más"
104
106
  },
105
107
  "tree": {
package/i18n/fr.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Retirer {{label}}",
101
101
  "noResults": "Aucun résultat",
102
102
  "loading": "Chargement…",
103
+ "loadError": "Impossible de charger les résultats",
104
+ "retry": "Réessayer",
103
105
  "more": "+{{count}} de plus"
104
106
  },
105
107
  "tree": {
package/i18n/he.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "הסרת {{label}}",
101
101
  "noResults": "אין תוצאות",
102
102
  "loading": "טוען…",
103
+ "loadError": "לא ניתן לטעון את התוצאות",
104
+ "retry": "ניסיון חוזר",
103
105
  "more": "עוד {{count}}"
104
106
  },
105
107
  "tree": {
package/i18n/hi.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}} हटाएं",
101
101
  "noResults": "कोई नतीजा नहीं",
102
102
  "loading": "लोड हो रहा है…",
103
+ "loadError": "नतीजे लोड नहीं हो सके",
104
+ "retry": "फिर से कोशिश करें",
103
105
  "more": "+{{count}} और"
104
106
  },
105
107
  "tree": {
package/i18n/id.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Hapus {{label}}",
101
101
  "noResults": "Tidak ada hasil",
102
102
  "loading": "Memuat…",
103
+ "loadError": "Gagal memuat hasil",
104
+ "retry": "Coba lagi",
103
105
  "more": "+{{count}} lainnya"
104
106
  },
105
107
  "tree": {
package/i18n/it.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Rimuovi {{label}}",
101
101
  "noResults": "Nessun risultato",
102
102
  "loading": "Caricamento…",
103
+ "loadError": "Impossibile caricare i risultati",
104
+ "retry": "Riprova",
103
105
  "more": "+{{count}} in più"
104
106
  },
105
107
  "tree": {
package/i18n/ja.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}}を削除",
101
101
  "noResults": "該当なし",
102
102
  "loading": "読み込み中…",
103
+ "loadError": "結果を読み込めませんでした",
104
+ "retry": "再試行",
103
105
  "more": "他{{count}}件"
104
106
  },
105
107
  "tree": {
package/i18n/ko.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}} 제거",
101
101
  "noResults": "검색 결과 없음",
102
102
  "loading": "불러오는 중…",
103
+ "loadError": "결과를 불러오지 못했습니다",
104
+ "retry": "다시 시도",
103
105
  "more": "+{{count}}개"
104
106
  },
105
107
  "tree": {
package/i18n/nl.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}} verwijderen",
101
101
  "noResults": "Geen resultaten",
102
102
  "loading": "Laden…",
103
+ "loadError": "Resultaten konden niet worden geladen",
104
+ "retry": "Opnieuw proberen",
103
105
  "more": "+{{count}} meer"
104
106
  },
105
107
  "tree": {
package/i18n/pl.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Usuń {{label}}",
101
101
  "noResults": "Brak wyników",
102
102
  "loading": "Ładowanie…",
103
+ "loadError": "Nie udało się wczytać wyników",
104
+ "retry": "Spróbuj ponownie",
103
105
  "more": "+{{count}} więcej"
104
106
  },
105
107
  "tree": {
package/i18n/pt.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Remover {{label}}",
101
101
  "noResults": "Sem resultados",
102
102
  "loading": "A carregar…",
103
+ "loadError": "Não foi possível carregar os resultados",
104
+ "retry": "Tentar novamente",
103
105
  "more": "+{{count}} mais"
104
106
  },
105
107
  "tree": {
package/i18n/ru.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Удалить {{label}}",
101
101
  "noResults": "Ничего не найдено",
102
102
  "loading": "Загрузка…",
103
+ "loadError": "Не удалось загрузить результаты",
104
+ "retry": "Повторить",
103
105
  "more": "ещё {{count}}"
104
106
  },
105
107
  "tree": {
package/i18n/sv.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Ta bort {{label}}",
101
101
  "noResults": "Inga resultat",
102
102
  "loading": "Läser in…",
103
+ "loadError": "Det gick inte att läsa in resultaten",
104
+ "retry": "Försök igen",
103
105
  "more": "+{{count}} till"
104
106
  },
105
107
  "tree": {
package/i18n/tr.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "{{label}} öğesini kaldır",
101
101
  "noResults": "Sonuç yok",
102
102
  "loading": "Yükleniyor…",
103
+ "loadError": "Sonuçlar yüklenemedi",
104
+ "retry": "Tekrar dene",
103
105
  "more": "+{{count}} daha"
104
106
  },
105
107
  "tree": {
package/i18n/uk.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Вилучити {{label}}",
101
101
  "noResults": "Нічого не знайдено",
102
102
  "loading": "Завантаження…",
103
+ "loadError": "Не вдалося завантажити результати",
104
+ "retry": "Повторити",
103
105
  "more": "ще {{count}}"
104
106
  },
105
107
  "tree": {
package/i18n/vi.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "Xóa {{label}}",
101
101
  "noResults": "Không có kết quả",
102
102
  "loading": "Đang tải…",
103
+ "loadError": "Không thể tải kết quả",
104
+ "retry": "Thử lại",
103
105
  "more": "+{{count}} nữa"
104
106
  },
105
107
  "tree": {
package/i18n/zh-TW.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "移除 {{label}}",
101
101
  "noResults": "找不到結果",
102
102
  "loading": "載入中…",
103
+ "loadError": "無法載入結果",
104
+ "retry": "重試",
103
105
  "more": "+{{count}} 項"
104
106
  },
105
107
  "tree": {
package/i18n/zh.json CHANGED
@@ -100,6 +100,8 @@
100
100
  "removeItem": "移除 {{label}}",
101
101
  "noResults": "无搜索结果",
102
102
  "loading": "加载中…",
103
+ "loadError": "无法加载结果",
104
+ "retry": "重试",
103
105
  "more": "+{{count}} 项"
104
106
  },
105
107
  "tree": {
package/llms-full.txt CHANGED
@@ -788,7 +788,7 @@
788
788
  - selector: `[wrHotkey]`
789
789
  - exports: WrHotkey, WrHotkeyBinding, parseHotkeySpec, matchesHotkey, WrParsedHotkey, WrHotkeyHandle, WrHotkeyOptions, WrHotkeySpec
790
790
  - keywords: service, keyboard
791
- - Register keyboard shortcuts like `mod+k`, where `mod` is ⌘ on macOS and Ctrl elsewhere.
791
+ - Register keyboard shortcuts like `mod+k`, where `mod` is ⌘ on macOS and Ctrl elsewhere. A letter shortcut also fires on a non-Latin layout.
792
792
 
793
793
  ## ngwr/i18n
794
794
  - import: `import { WrI18n } from 'ngwr/i18n'`
@@ -1136,8 +1136,8 @@
1136
1136
 
1137
1137
  ## ngwr/select
1138
1138
  - import: `import { WrSelect } from 'ngwr/select'`
1139
- - selector: `wr-option-group`, `ng-template[wrOptionLeading]`, `wr-option`, `wr-select`
1140
- - exports: WrSelect, WrOption, WrOptionGroup, WrOptionLeading, WR_SELECT, WrOptionLeadingContext, WrOptionLeadingPlacement, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize, WrSelectContext
1139
+ - selector: `ng-template[wrSelectLoadError]`, `wr-option-group`, `ng-template[wrOptionLeading]`, `wr-option`, `wr-select`
1140
+ - exports: WrSelect, WrOption, WrOptionGroup, WrOptionLeading, WrSelectLoadError, WR_SELECT, WrOptionLeadingContext, WrOptionLeadingPlacement, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize, WrSelectContext, WrSelectLoadErrorContext
1141
1141
  - keywords: overlay
1142
1142
  - Pick one or several values from a list, with search and free-text tags.
1143
1143
 
package/package.json CHANGED
@@ -1234,5 +1234,5 @@
1234
1234
  "sass": "./styles/_typography.scss"
1235
1235
  }
1236
1236
  },
1237
- "version": "15.2.0"
1237
+ "version": "15.3.0"
1238
1238
  }
@@ -184,6 +184,7 @@
184
184
  "WrOption": "ngwr/select",
185
185
  "WrOptionGroup": "ngwr/select",
186
186
  "WrOptionLeading": "ngwr/select",
187
+ "WrSelectLoadError": "ngwr/select",
187
188
  "WrSidebar": "ngwr/sidebar",
188
189
  "WrSkeleton": "ngwr/skeleton",
189
190
  "WrSlider": "ngwr/slider",
@@ -1,5 +1,10 @@
1
1
  @use '../../theme/styles' as theme;
2
2
 
3
+ // Styles for the entry points this component's own markup renders: the retry on
4
+ // a failed load wears `.wr-btn`, and its loader is a `wr-spinner`.
5
+ @use '../../button/styles' as button-styles;
6
+ @use '../../spinner/styles' as spinner-styles; // WrSpinner
7
+
3
8
  .wr-select {
4
9
  --wr-select-color: var(--wr-color-on-surface);
5
10
  --wr-select-bg: var(--wr-color-surface);
@@ -441,6 +446,8 @@
441
446
  // Shared by the loading and the empty row — one padding for the panel's
442
447
  // status text, whichever of the two is rendered.
443
448
  --wr-select-panel-message-padding: var(--wr-space-md);
449
+ --wr-select-panel-retry-gap: var(--wr-space-sm);
450
+ --wr-select-panel-retry-active-bg: var(--wr-color-hover);
444
451
  // Option sizing — default md. --sm/--lg are set from the trigger size because
445
452
  // the panel is portaled into an overlay and can't inherit the host size class.
446
453
  --wr-option-font-size: var(--wr-control-font-size-md);
@@ -494,12 +501,27 @@
494
501
  }
495
502
 
496
503
  &__loading,
497
- &__empty {
504
+ &__empty,
505
+ &__error {
498
506
  padding: var(--wr-select-panel-message-padding);
499
507
  color: var(--wr-color-on-surface-muted);
500
508
  font-size: var(--wr-option-font-size);
501
509
  text-align: center;
502
510
  }
511
+
512
+ // A button on a line of its own, under the message.
513
+ &__retry {
514
+ display: flex;
515
+ width: fit-content;
516
+ margin-block-start: var(--wr-select-panel-retry-gap);
517
+ margin-inline: auto;
518
+ }
519
+
520
+ // Focus stays in the field, so the button the field names as active takes the
521
+ // tint an active option takes, rather than a ring that would never go away.
522
+ &__error--active &__retry {
523
+ --wr-btn-bg: var(--wr-select-panel-retry-active-bg);
524
+ }
503
525
  }
504
526
 
505
527
  .wr-option {
@@ -209,7 +209,7 @@ root barrel for components: the root `ngwr` exports only `provideWr()`.
209
209
  | `ngwr/scroll` | `import { WrScroll } from 'ngwr/scroll'` | — |
210
210
  | `ngwr/segmented` | `import { WrSegmented } from 'ngwr/segmented'` | `wr-segmented` |
211
211
  | `ngwr/segmented/testing` | `import { WrSegmentedHarness } from 'ngwr/segmented/testing'` | — |
212
- | `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `wr-option-group` `ng-template[wrOptionLeading]` `wr-option` `wr-select` |
212
+ | `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `ng-template[wrSelectLoadError]` `wr-option-group` `ng-template[wrOptionLeading]` `wr-option` `wr-select` |
213
213
  | `ngwr/select/testing` | `import { WrOptionHarness } from 'ngwr/select/testing'` | — |
214
214
  | `ngwr/sidebar` | `import { WrSidebar } from 'ngwr/sidebar'` | `wr-sidebar` |
215
215
  | `ngwr/skeleton` | `import { WrSkeleton } from 'ngwr/skeleton'` | `wr-skeleton` |
@@ -11,11 +11,13 @@ import * as i0 from "@angular/core";
11
11
  type WrAlertType = 'info' | 'success' | 'warning' | 'danger' | 'neutral' | 'offline';
12
12
  /**
13
13
  * Inline status banner. Use for feedback messages — saved/failed/notice etc.
14
+ * Content placed inside renders below the message, in `.wr-alert__content`.
14
15
  *
15
16
  * @example
16
17
  * ```html
17
18
  * <wr-alert title="Saved" message="Your changes are live." type="success" />
18
19
  * <wr-alert title="Failed" type="danger" closable (closed)="onClose()" />
20
+ * <wr-alert title="Sync failed" type="danger">Request <code>8f2c41</code> timed out.</wr-alert>
19
21
  * ```
20
22
  *
21
23
  * @see https://ngwr.dev/reference/components/alert
@@ -123,6 +125,6 @@ export declare class WrAlert {
123
125
  };
124
126
  }, {
125
127
  "closed": "closed";
126
- }, never, never, true, never>;
128
+ }, never, ["*"], true, never>;
127
129
  }
128
130
  export type { WrAlertType };
@@ -1,4 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
+ import { Params } from "@angular/router";
2
3
  /**
3
4
  * Breadcrumb navigation. Project `<wr-breadcrumbs-item>` children — each keeps
4
5
  * its own element and takes `role="listitem"` inside the `<ol>` rather than
@@ -55,6 +56,7 @@ export declare class WrBreadcrumbs {
55
56
  * @example
56
57
  * ```html
57
58
  * <wr-breadcrumbs-item icon="home" routerLink="/">Home</wr-breadcrumbs-item>
59
+ * <wr-breadcrumbs-item routerLink="/debts" [queryParams]="{ date: day }">Debts</wr-breadcrumbs-item>
58
60
  * <wr-breadcrumbs-item href="https://ngwr.dev">External</wr-breadcrumbs-item>
59
61
  * <wr-breadcrumbs-item>Current page</wr-breadcrumbs-item>
60
62
  * ```
@@ -64,6 +66,14 @@ export declare class WrBreadcrumbsItem {
64
66
  readonly href: import("@angular/core").InputSignal<string | null>;
65
67
  /** Angular router target. Anything `RouterLink` accepts (string or `any[]`). */
66
68
  readonly routerLink: import("@angular/core").InputSignal<string | readonly unknown[] | null>;
69
+ /**
70
+ * Query parameters for a `routerLink` crumb, the way `RouterLink` takes them.
71
+ * A `?` written into `routerLink` is part of the path, and the router escapes
72
+ * it: `/debts?date=1` became `/debts%3Fdate%3D1`. Ignored without `routerLink`.
73
+ *
74
+ * @default null
75
+ */
76
+ readonly queryParams: import("@angular/core").InputSignal<Params | null>;
67
77
  /** Open external `href` in a new tab. Ignored when `routerLink` is set. @default false */
68
78
  readonly external: import("@angular/core").InputSignal<boolean>;
69
79
  /** Optional leading icon rendered inline before the label. @default null */
@@ -83,6 +93,11 @@ export declare class WrBreadcrumbsItem {
83
93
  "required": false;
84
94
  "isSignal": true;
85
95
  };
96
+ "queryParams": {
97
+ "alias": "queryParams";
98
+ "required": false;
99
+ "isSignal": true;
100
+ };
86
101
  "external": {
87
102
  "alias": "external";
88
103
  "required": false;
@@ -43,7 +43,8 @@ interface WrHotkeyOptions {
43
43
  /**
44
44
  * Global keybinding registry.
45
45
  *
46
- * Bindings are keyed off `KeyboardEvent.key` + modifier state. Multiple
46
+ * Bindings are keyed off `KeyboardEvent.key` + modifier state, and a letter
47
+ * shortcut also fires on a non-Latin layout, by the physical key. Multiple
47
48
  * handlers per chord fire in priority order; a handler that calls
48
49
  * `event.preventDefault()` stops lower-priority bindings on that key.
49
50
  *
@@ -153,6 +154,17 @@ interface WrParsedHotkey {
153
154
  * @param isMac - when true, `mod` resolves to `meta` (Cmd); otherwise `ctrl`
154
155
  */
155
156
  declare function parseHotkeySpec(spec: WrHotkeySpec, isMac?: boolean): WrParsedHotkey;
156
- /** Does `event` satisfy the parsed hotkey? */
157
+ /**
158
+ * Does `event` satisfy the parsed hotkey?
159
+ *
160
+ * The typed character decides, and the physical key only stands in when that
161
+ * character is a letter of a non-Latin script. On a Russian layout the key
162
+ * labelled K types `л`, so `mod+k` used to be dead for anyone who had not
163
+ * switched to English; the same goes for Greek, Hebrew or Arabic. A Latin
164
+ * layout never falls back, not even on a letter outside ASCII: on Dvorak the key
165
+ * labelled K types `t`, and Turkish `ı` or German `ß` are keys of their own.
166
+ * Nor does a chord holding Alt, since macOS Option and Windows AltGr type a
167
+ * symbol on purpose, `€` on AltGr+E among them.
168
+ */
157
169
  declare function matchesHotkey(event: KeyboardEvent, parsed: WrParsedHotkey): boolean;
158
170
  export { type WrHotkeyHandle, type WrHotkeyOptions, type WrHotkeySpec, type WrParsedHotkey, matchesHotkey, parseHotkeySpec };