ngwr 13.0.1 → 14.0.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 (167) hide show
  1. package/README.md +13 -6
  2. package/color-picker/styles/_index.scss +43 -5
  3. package/fesm2022/ngwr-alert.mjs +5 -5
  4. package/fesm2022/ngwr-alert.mjs.map +1 -1
  5. package/fesm2022/ngwr-back-top.mjs +11 -1
  6. package/fesm2022/ngwr-back-top.mjs.map +1 -1
  7. package/fesm2022/ngwr-blur-text.mjs +13 -2
  8. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  9. package/fesm2022/ngwr-button.mjs +4 -4
  10. package/fesm2022/ngwr-button.mjs.map +1 -1
  11. package/fesm2022/ngwr-calendar-testing.mjs +15 -0
  12. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
  13. package/fesm2022/ngwr-calendar.mjs +51 -5
  14. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  15. package/fesm2022/ngwr-circular-text.mjs +20 -1
  16. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  17. package/fesm2022/ngwr-collapse.mjs.map +1 -1
  18. package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
  19. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
  20. package/fesm2022/ngwr-color-picker.mjs +89 -10
  21. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  22. package/fesm2022/ngwr-command-palette.mjs +27 -3
  23. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  24. package/fesm2022/ngwr-context-menu.mjs +7 -1
  25. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  26. package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
  27. package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
  28. package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
  29. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  30. package/fesm2022/ngwr-date-picker.mjs +103 -13
  31. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  32. package/fesm2022/ngwr-date.mjs +480 -79
  33. package/fesm2022/ngwr-date.mjs.map +1 -1
  34. package/fesm2022/ngwr-drag-drop.mjs +30 -2
  35. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  36. package/fesm2022/ngwr-dropdown.mjs +2 -1
  37. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  38. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
  39. package/fesm2022/ngwr-event-calendar.mjs +1 -1
  40. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  41. package/fesm2022/ngwr-file-upload.mjs +52 -12
  42. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  43. package/fesm2022/ngwr-i18n-en.mjs +63 -1
  44. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  45. package/fesm2022/ngwr-i18n-ru.mjs +28 -1
  46. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  47. package/fesm2022/ngwr-i18n.mjs +150 -22
  48. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  49. package/fesm2022/ngwr-input-number.mjs +3 -3
  50. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  51. package/fesm2022/ngwr-input-otp.mjs +21 -3
  52. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  53. package/fesm2022/ngwr-input.mjs +14 -7
  54. package/fesm2022/ngwr-input.mjs.map +1 -1
  55. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  56. package/fesm2022/ngwr-knob.mjs +20 -2
  57. package/fesm2022/ngwr-knob.mjs.map +1 -1
  58. package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
  59. package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
  60. package/fesm2022/ngwr-loading-bar.mjs +21 -32
  61. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  62. package/fesm2022/ngwr-mention.mjs +104 -14
  63. package/fesm2022/ngwr-mention.mjs.map +1 -1
  64. package/fesm2022/ngwr-pagination-testing.mjs +11 -10
  65. package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
  66. package/fesm2022/ngwr-pagination.mjs +65 -30
  67. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  68. package/fesm2022/ngwr-popconfirm.mjs +3 -2
  69. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  70. package/fesm2022/ngwr-popover.mjs +2 -1
  71. package/fesm2022/ngwr-popover.mjs.map +1 -1
  72. package/fesm2022/ngwr-select.mjs +93 -34
  73. package/fesm2022/ngwr-select.mjs.map +1 -1
  74. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
  75. package/fesm2022/ngwr-squircle.mjs +2 -1
  76. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  77. package/fesm2022/ngwr-statistic.mjs +35 -4
  78. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  79. package/fesm2022/ngwr-table.mjs +35 -12
  80. package/fesm2022/ngwr-table.mjs.map +1 -1
  81. package/fesm2022/ngwr-tabs-router.mjs +158 -0
  82. package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
  83. package/fesm2022/ngwr-tabs-testing.mjs +13 -11
  84. package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
  85. package/fesm2022/ngwr-tabs.mjs +98 -10
  86. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  87. package/fesm2022/ngwr-textarea.mjs +20 -1
  88. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  89. package/fesm2022/ngwr-tour.mjs +1 -1
  90. package/fesm2022/ngwr-tour.mjs.map +1 -1
  91. package/fesm2022/ngwr-transfer.mjs +2 -2
  92. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  93. package/fesm2022/ngwr-tree.mjs +18 -2
  94. package/fesm2022/ngwr-tree.mjs.map +1 -1
  95. package/fesm2022/ngwr-utils.mjs +37 -1
  96. package/fesm2022/ngwr-utils.mjs.map +1 -1
  97. package/fesm2022/ngwr-version.mjs +1 -1
  98. package/fesm2022/ngwr-version.mjs.map +1 -1
  99. package/fesm2022/ngwr-window-testing.mjs +10 -2
  100. package/fesm2022/ngwr-window-testing.mjs.map +1 -1
  101. package/fesm2022/ngwr-window.mjs +4 -4
  102. package/fesm2022/ngwr-window.mjs.map +1 -1
  103. package/i18n/en.json +33 -6
  104. package/i18n/ru.json +33 -6
  105. package/llms-full.txt +20 -11
  106. package/llms.txt +47 -16
  107. package/mcp/catalog.js +3 -3
  108. package/mcp/providers.js +11 -0
  109. package/package.json +9 -1
  110. package/schematics/migrations/v13/index.js +101 -0
  111. package/schematics/migrations/v14/index.js +422 -0
  112. package/schematics/migrations.json +10 -0
  113. package/schematics/page/templates.js +1 -1
  114. package/schematics/use/symbol-map.json +1 -0
  115. package/skills/ngwr/SKILL.md +4 -1
  116. package/skills/ngwr/references/catalog.md +2 -0
  117. package/skills/ngwr/references/setup.md +4 -0
  118. package/stepper/styles/_index.scss +19 -1
  119. package/textarea/styles/_index.scss +14 -6
  120. package/toast/styles/_index.scss +12 -4
  121. package/types/ngwr-alert.d.ts +3 -3
  122. package/types/ngwr-blur-text.d.ts +8 -0
  123. package/types/ngwr-button.d.ts +2 -2
  124. package/types/ngwr-calendar-testing.d.ts +21 -4
  125. package/types/ngwr-calendar.d.ts +37 -0
  126. package/types/ngwr-circular-text.d.ts +9 -0
  127. package/types/ngwr-collapse.d.ts +19 -15
  128. package/types/ngwr-color-picker-testing.d.ts +10 -4
  129. package/types/ngwr-color-picker.d.ts +70 -2
  130. package/types/ngwr-command-palette.d.ts +14 -0
  131. package/types/ngwr-context-menu.d.ts +1 -1
  132. package/types/ngwr-date-adapters-fns.d.ts +71 -29
  133. package/types/ngwr-date-picker-testing.d.ts +206 -185
  134. package/types/ngwr-date-picker.d.ts +7 -0
  135. package/types/ngwr-date.d.ts +143 -13
  136. package/types/ngwr-drag-drop.d.ts +19 -0
  137. package/types/ngwr-dropdown.d.ts +1 -1
  138. package/types/ngwr-event-calendar-testing.d.ts +12 -9
  139. package/types/ngwr-file-upload.d.ts +31 -3
  140. package/types/ngwr-i18n.d.ts +89 -7
  141. package/types/ngwr-input-number.d.ts +1 -1
  142. package/types/ngwr-input-otp.d.ts +3 -0
  143. package/types/ngwr-input.d.ts +11 -4
  144. package/types/ngwr-knob-testing.d.ts +8 -4
  145. package/types/ngwr-knob.d.ts +18 -0
  146. package/types/ngwr-loading-bar-router.d.ts +35 -0
  147. package/types/ngwr-loading-bar.d.ts +19 -9
  148. package/types/ngwr-mention.d.ts +13 -0
  149. package/types/ngwr-pagination-testing.d.ts +11 -10
  150. package/types/ngwr-pagination.d.ts +39 -10
  151. package/types/ngwr-popconfirm.d.ts +1 -1
  152. package/types/ngwr-popover.d.ts +1 -1
  153. package/types/ngwr-select.d.ts +65 -10
  154. package/types/ngwr-splitter-testing.d.ts +9 -4
  155. package/types/ngwr-squircle.d.ts +1 -1
  156. package/types/ngwr-statistic.d.ts +22 -2
  157. package/types/ngwr-table.d.ts +24 -5
  158. package/types/ngwr-tabs-router.d.ts +67 -0
  159. package/types/ngwr-tabs-testing.d.ts +12 -10
  160. package/types/ngwr-tabs.d.ts +103 -6
  161. package/types/ngwr-textarea.d.ts +9 -0
  162. package/types/ngwr-tree.d.ts +7 -0
  163. package/types/ngwr-utils.d.ts +33 -1
  164. package/types/ngwr-version.d.ts +1 -1
  165. package/types/ngwr-window-testing.d.ts +11 -3
  166. package/types/ngwr-window.d.ts +11 -4
  167. package/window/styles/_index.scss +1 -1
@@ -0,0 +1,422 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ /**
10
+ * v13 to v14 migration. Two halves, and the split is deliberate.
11
+ *
12
+ * **It REWRITES the five renames**: on `<wr-alert>` from `closeable` to
13
+ * `closable`, on `<wr-table>` from `[totalItems]` to `[total]`, on
14
+ * `<wr-pagination>` from `[(currentPage)]` to `[(page)]`, from
15
+ * `isDisabledWhenLoading` to `disabledWhenLoading`, and the window
16
+ * `chromeSize` scale from `'compact' | 'normal'` to `'sm' | 'md'`. A rename is
17
+ * exactly what a codemod does well: the new name means what the old one meant,
18
+ * on the same element, and nothing about the app has to be understood to move it.
19
+ *
20
+ * **It REPORTS everything else.** Those need a decision a codemod cannot make
21
+ * correctly, and v10/v11 already establish the rule here — a migration that
22
+ * pretends to have handled something it has not is worse than none, because the
23
+ * silence reads as "nothing to do".
24
+ *
25
+ * **`wr-loading-bar`** no longer subscribes to the router on its own. Add
26
+ * `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router` to your
27
+ * application providers. Without it the bar still works for manual
28
+ * `start()` / `complete()` and simply never moves for navigation — which is the
29
+ * dangerous shape, because nothing throws: this project's own site shipped a
30
+ * permanently-empty bar for the length of a release by missing exactly this.
31
+ * Not auto-fixed because the provider belongs in a bootstrap file this schematic
32
+ * cannot identify: `app.config.ts`, a `main.ts` calling `bootstrapApplication`,
33
+ * or a route-level `providers` array are all legitimate, and guessing wrong puts
34
+ * the subscription in the wrong injector.
35
+ *
36
+ * **`<wr-tab routerLink>`** needs `wrTabsRouting` on the strip and
37
+ * `WrTabsRouting` in the component's `imports`. This one at least fails loudly —
38
+ * the strip throws when a tab carries a `routerLink` and no adapter is present.
39
+ * Not auto-fixed because adding the attribute is only half of it: the class has
40
+ * to reach an `imports` array in a `.ts` file that this rule would have to match
41
+ * to the template by convention, and a template can be inline, external, or
42
+ * shared between components.
43
+ *
44
+ * **The locale sources were collapsed into one precedence**, and two defaults
45
+ * moved with it. `WR_DATE_LOCALE` used to default to `navigator.language`; it
46
+ * now defaults to Angular's `LOCALE_ID`, so `provideWrDateAdapter()` with no
47
+ * `locale` follows the application rather than the browser. `WrI18n`'s own
48
+ * default locale used to be the literal `'en'`; it is now `LOCALE_ID` as well,
49
+ * and `availableLocales` defaults to `[defaultLocale]` instead of `['en']`.
50
+ * Reported rather than rewritten for the usual reason: the fix, where one is
51
+ * wanted at all, is a value only the app knows — an app that really did want the
52
+ * browser's tag writes `provideWrDateAdapter({ locale: navigator.language })`,
53
+ * and one that really did want `'en'` writes it. A codemod cannot tell those
54
+ * from the majority for whom the new default is the answer they meant.
55
+ *
56
+ * **`<wr-pagination ofLabel>`** is gone. The "1–10 of 235" line is now ONE
57
+ * catalog template, `pagination.range`, with `{{from}}` / `{{to}}` /
58
+ * `{{total}}` placeholders — because an input for the word in the middle
59
+ * localised only the middle: the ASCII hyphen between the bounds and the
60
+ * operand order were both frozen in TypeScript, and several languages put the
61
+ * total first. Reported rather than rewritten for the reason the others are —
62
+ * the text moves out of a template into a catalog file this schematic cannot
63
+ * identify, and the sentence it becomes is a translation decision. A bound
64
+ * `[ofLabel]` fails the template type-check, so it cannot ship unnoticed; a
65
+ * static `ofLabel="…"` is the shape worth naming.
66
+ *
67
+ * Two more v14 changes are visible without being breaking API, and are called
68
+ * out here because a snapshot or a text assertion will catch them: the
69
+ * pagination range separates its bounds with an EN DASH and groups its three
70
+ * numbers per `LOCALE_ID` (`1–10 of 1,234`), and `<wr-statistic>` formats its
71
+ * delta through `Intl.NumberFormat` rather than interpolating the raw number,
72
+ * so a non-English locale sees its own digits, sign and decimal separator
73
+ * there — matching the value printed above it, which always did.
74
+ *
75
+ * **The five renames**, and why each one moved. `wr-alert` spelled its dismiss
76
+ * toggle `closeable` while `wr-drawer`, `WrDrawerOptions` and `WrDialogOptions`
77
+ * all spelled the same idea `closable` — and a bare attribute that matches no
78
+ * input raises no template error, so `<wr-alert closable>` and
79
+ * `<wr-drawer closeable>` both compiled to nothing at all. `wr-table` and
80
+ * `wr-pagination` disagreed about the two concepts they share: `totalItems` /
81
+ * `page` against `total` / `currentPage`, two components designed to sit on the
82
+ * same screen. `isDisabledWhenLoading` was the only `is`-prefixed input in the
83
+ * library, against thirty-nine plainly-named `disabled` inputs and no
84
+ * `isDisabled` anywhere; dropping the prefix leaves none at all. And
85
+ * `WrWindowChromeSize` was still `'compact' | 'normal'` — the exact vocabulary
86
+ * v8's density migration exists to retire, three majors after it landed.
87
+ *
88
+ * What the rename rules will NOT catch, because no regex can: a component class
89
+ * reading one of these off a `viewChild()` reference. Those are type errors on
90
+ * upgrade, which is the loud failure and needs no help. One quiet case is worth
91
+ * naming: `WrWindowHarness.getChromeSize()` now answers `'sm'` / `'md'`, so a
92
+ * test asserting `'compact'` fails as a wrong value rather than as a wrong type.
93
+ *
94
+ * Why the split happened at all: the subscription and the link directives are
95
+ * what pull `@angular/router` into the bundle. Measured on 13.0.0, in an app
96
+ * declaring no routes, `ngwr/loading-bar` cost 100.1 kB of which 67.9 kB was the
97
+ * router, against 2.4 kB of its own code; `ngwr/tabs` carried 75.6 kB of router.
98
+ * Both are now zero for an app that does not opt in.
99
+ */
100
+ const IGNORE_DIRS = new Set(['node_modules', 'dist', '.git', '.cache', '.angular', 'coverage', '.next', '.nuxt']);
101
+ /**
102
+ * An open tag, up to and including the whitespace before some attribute.
103
+ * `[^>]*?` cannot cross a `>`, so a rename can never escape the element it is
104
+ * scoped to, however the tag wraps across lines.
105
+ *
106
+ * The anchor is `(?![-\w])` and NOT `\b`: a word boundary still matches at the
107
+ * front of a longer element name, which is how a v9-shaped rule would have
108
+ * renamed an attribute on `<wr-table-filter>` while claiming to touch only
109
+ * `<wr-table>`. Every one of these four has such a neighbour — `wr-table-filter`
110
+ * and `wr-table-sort`, `wr-window-container` and `wr-window-taskbar` — or would
111
+ * acquire one the moment the catalog grows.
112
+ *
113
+ * Quoted values are stepped over rather than excluded, and that is the half a
114
+ * plain `[^>]*?` gets wrong: a `>` inside a binding expression looks like the end
115
+ * of the tag, so `<wr-alert [type]="n > 0 ? 'a' : 'b'" closeable>` left
116
+ * `closeable` behind while the same attributes in the other order moved. The
117
+ * leftover is SILENT — a static `closeable` / `totalItems="42"` that matches no
118
+ * input is an ordinary DOM attribute and `strictTemplates` says nothing, so the
119
+ * alert quietly loses its dismiss button. (Bracketed forms are loud: NG8002.)
120
+ * `migration-v9` carries the same anchoring and the same hole.
121
+ */
122
+ const openTag = (name) => String.raw `<${name}(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s`;
123
+ const ALERT = openTag('wr-alert');
124
+ const TABLE = openTag('wr-table');
125
+ const PAGER = openTag('wr-pagination');
126
+ const WINDOW = openTag('wr-window');
127
+ /**
128
+ * Template renames. Run over `.html` AND `.ts`, because an inline template is
129
+ * a string in a component file and is otherwise invisible to this.
130
+ */
131
+ const HTML_TRANSFORMS = [
132
+ // <wr-alert closeable>, to closable. The bracketed form first: after it runs
133
+ // the bare rule cannot see what it rewrote, and the bare rule's lookahead
134
+ // (`[\s/>=]`) is what keeps it from matching the inside of a longer word.
135
+ { pattern: new RegExp(`(${ALERT})\\[closeable\\]`, 'g'), replacement: '$1[closable]' },
136
+ { pattern: new RegExp(`(${ALERT})closeable(?=[\\s/>=])`, 'g'), replacement: '$1closable' },
137
+ // `[wrInput]`'s size input drops its prefix. Anchored on the DIRECTIVE, not on
138
+ // an element, because it sits on a native `<input>` / `<textarea>` — and the
139
+ // anchor matters more here than anywhere else in this table: `size` is a legal
140
+ // native attribute, so rewriting one that was never ngwr's silently rebinds a
141
+ // character-width hint. The two orders are both real markup, hence two rules.
142
+ {
143
+ pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\swrInput(?:"[^"]*"|'[^']*'|[^>])*?\s)\[wrSize\]/g,
144
+ replacement: '$1[size]',
145
+ },
146
+ {
147
+ pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\swrInput(?:"[^"]*"|'[^']*'|[^>])*?\s)wrSize=/g,
148
+ replacement: '$1size=',
149
+ },
150
+ {
151
+ pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s)\[wrSize\]((?:"[^"]*"|'[^']*'|[^>])*?\swrInput)/g,
152
+ replacement: '$1[size]$2',
153
+ },
154
+ {
155
+ pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s)wrSize=((?:"[^"]*"|'[^']*'|[^>])*?\swrInput)/g,
156
+ replacement: '$1size=$2',
157
+ },
158
+ // <wr-table>, from [totalItems] to [total]
159
+ { pattern: new RegExp(`(${TABLE})\\[totalItems\\]`, 'g'), replacement: '$1[total]' },
160
+ { pattern: new RegExp(`(${TABLE})totalItems=`, 'g'), replacement: '$1total=' },
161
+ // <wr-pagination>, from [(currentPage)] to [(page)], and the one-way pair with it.
162
+ { pattern: new RegExp(`(${PAGER})\\[\\(currentPage\\)\\]`, 'g'), replacement: '$1[(page)]' },
163
+ { pattern: new RegExp(`(${PAGER})\\(currentPageChange\\)`, 'g'), replacement: '$1(pageChange)' },
164
+ { pattern: new RegExp(`(${PAGER})\\[currentPage\\]`, 'g'), replacement: '$1[page]' },
165
+ { pattern: new RegExp(`(${PAGER})currentPage=`, 'g'), replacement: '$1page=' },
166
+ // <wr-window>, from chromeSize="compact" to "sm", bound literal included.
167
+ { pattern: new RegExp(`(${WINDOW}chromeSize=")compact(")`, 'g'), replacement: '$1sm$2' },
168
+ { pattern: new RegExp(`(${WINDOW}chromeSize=")normal(")`, 'g'), replacement: '$1md$2' },
169
+ { pattern: new RegExp(`(${WINDOW}\\[chromeSize\\]=")'compact'(")`, 'g'), replacement: "$1'sm'$2" },
170
+ { pattern: new RegExp(`(${WINDOW}\\[chromeSize\\]=")'normal'(")`, 'g'), replacement: "$1'md'$2" },
171
+ ];
172
+ /** `.ts` only — object keys and a harness filter, neither of which is markup. */
173
+ const TS_TRANSFORMS = [
174
+ // WrWindowManager.open(Cmp, { chromeSize: 'compact' }) — the primary form,
175
+ // since a window is always a service call. Keyed on the property name rather
176
+ // than the element: there is no element in a `.ts` object literal to scope to,
177
+ // and `chromeSize` carrying one of exactly these two values is not a shape
178
+ // another library hands you by accident.
179
+ { pattern: /(\bchromeSize\s*:\s*['"])compact(['"])/g, replacement: '$1sm$2' },
180
+ { pattern: /(\bchromeSize\s*:\s*['"])normal(['"])/g, replacement: '$1md$2' },
181
+ // WrPaginationHarness.with({ currentPage: 3 }), to { page: 3 }. The filter is
182
+ // named after the input, so it moved with it. Scoped to the `with(` call —
183
+ // `currentPage` is far too ordinary a property name to rename on sight.
184
+ { pattern: /(WrPaginationHarness\.with\(\s*\{[^}]*?\b)currentPage(\s*:)/g, replacement: '$1page$2' },
185
+ ];
186
+ /**
187
+ * Renames safe ANYWHERE, in markup, TypeScript and stylesheets alike, because
188
+ * the string itself is unambiguous. `isDisabledWhenLoading` was the library's
189
+ * only `is`-prefixed input and appears nowhere else; `wr-window--chrome-compact`
190
+ * is a BEM class, which is public API here, so a consumer stylesheet overriding
191
+ * it has to move with the component.
192
+ */
193
+ const GLOBAL_TRANSFORMS = [
194
+ { pattern: /\bisDisabledWhenLoading\b/g, replacement: 'disabledWhenLoading' },
195
+ { pattern: /\bwr-window--chrome-compact\b/g, replacement: 'wr-window--chrome-sm' },
196
+ // `--chrome-normal` too, even though the library never styled it: the class is
197
+ // emitted as `wr-window--chrome-${chromeSize()}`, so every default window in
198
+ // v13 carried it, BEM classes are public API here, and a consumer override
199
+ // keyed on it stops matching with no error — CSS does not have one.
200
+ { pattern: /\bwr-window--chrome-normal\b/g, replacement: 'wr-window--chrome-md' },
201
+ ];
202
+ /** `<wr-tab …>` carrying a `routerLink`, however the tag is wrapped across lines. */
203
+ const ROUTER_TAB = /<wr-tab\b[^>]*\brouterLink\b/;
204
+ const LOADING_BAR = /<wr-loading-bar\b/;
205
+ /** `provideWrDateAdapter(` with no `locale:` before the call closes. */
206
+ // `locale` as a key OR as shorthand, and the scan for it runs to the call's own
207
+ // closing brace rather than to the first `)`. The first version demanded a colon
208
+ // and could not cross a nested call, so it reported
209
+ // `provideWrDateAdapter({ locale })` — which the audit's own stand writes — and
210
+ // `provideWrDateAdapter({ adapter: mk(), locale: 'ru' })` as having no locale.
211
+ // Over-reporting is the gentler failure, but a warning that fires on the
212
+ // idiomatic form is a warning people learn to skip.
213
+ const DATE_ADAPTER_NO_LOCALE = /\bprovideWrDateAdapter\(\s*(?:\)|\{(?![\s\S]*?\blocale\s*[,:}]))/;
214
+ /** `provideWrI18n(` with no `defaultLocale:` before the call closes. */
215
+ const I18N_NO_DEFAULT_LOCALE = /\bprovideWrI18n\(\s*(?:\)|\{(?![^})]*\bdefaultLocale\s*:))/;
216
+ const DEFAULT_CONFIG = /\bDEFAULT_WR_I18N_CONFIG\b/;
217
+ /** `ofLabel` on a pagination tag, bound (`[ofLabel]`) or static. */
218
+ const OF_LABEL = /<wr-pagination\b[^>]*\[?ofLabel\]?\s*=/;
219
+ // The half `OF_LABEL` cannot see. Apps that never bound the input still
220
+ // translated the key, and `pagination.of` is gone: the range is one
221
+ // `pagination.range` template now, so a catalog that defines the old key loses
222
+ // its translation with nothing said. Markup detection misses every one of them,
223
+ // which is why this looks at the catalog instead.
224
+ const REMOVED_KEY = /['"`]?\bof['"`]?\s*:\s*['"`]/;
225
+ const PAGINATION_SCOPE = /\bpagination\s*:\s*\{/g;
226
+ /**
227
+ * Every `pagination: { … }` object literal in a file, brace-matched and
228
+ * QUOTE-AWARE — and it has to be both, because the first version was neither.
229
+ *
230
+ * It sliced to `content.indexOf('}')`, and the shipped catalog carries
231
+ * `perPage: '{{size}} / page'` two lines ABOVE `of`, so the slice ended inside
232
+ * `{{size` and the scan never reached the key it exists to find. The detector
233
+ * therefore answered "clean" for the one shape that actually occurs — a
234
+ * consumer catalog copied from ngwr's own — and green for the toy literal in
235
+ * its spec, `pagination: { of: 'von' }`, where `of` comes first and no
236
+ * placeholder precedes it. A warning that only fires on a shape nobody writes
237
+ * is the silent half of a silent change.
238
+ *
239
+ * All of them rather than the first, since one file commonly holds several
240
+ * locales.
241
+ */
242
+ function paginationBlocks(content) {
243
+ const blocks = [];
244
+ PAGINATION_SCOPE.lastIndex = 0;
245
+ for (let scope = PAGINATION_SCOPE.exec(content); scope; scope = PAGINATION_SCOPE.exec(content)) {
246
+ // The `{` the match ends on.
247
+ const start = scope.index + scope[0].length - 1;
248
+ let depth = 0;
249
+ let quote = null;
250
+ for (let i = start; i < content.length; i += 1) {
251
+ const char = content[i];
252
+ if (quote !== null) {
253
+ if (char === '\\')
254
+ i += 1;
255
+ else if (char === quote)
256
+ quote = null;
257
+ continue;
258
+ }
259
+ if (char === "'" || char === '"' || char === '`') {
260
+ quote = char;
261
+ }
262
+ else if (char === '{') {
263
+ depth += 1;
264
+ }
265
+ else if (char === '}') {
266
+ depth -= 1;
267
+ if (depth === 0) {
268
+ blocks.push(content.slice(start, i + 1));
269
+ break;
270
+ }
271
+ }
272
+ }
273
+ }
274
+ return blocks;
275
+ }
276
+ function ngUpdateV14() {
277
+ return (tree, context) => {
278
+ const routerTabs = [];
279
+ const loadingBars = [];
280
+ const dateAdapters = [];
281
+ const i18nProviders = [];
282
+ const defaultConfigs = [];
283
+ const ofLabels = [];
284
+ const staleCatalogs = [];
285
+ let rewritten = 0;
286
+ visit(tree, '/', filePath => {
287
+ const lower = filePath.toLowerCase();
288
+ const isTs = lower.endsWith('.ts');
289
+ const isHtml = lower.endsWith('.html');
290
+ const isStyle = lower.endsWith('.scss') || lower.endsWith('.css');
291
+ // Inline templates live in `.ts`, external ones in `.html`; the renames
292
+ // reach stylesheets too, because one of them is a BEM class.
293
+ if (!isTs && !isHtml && !isStyle && !lower.endsWith('.json'))
294
+ return;
295
+ // Read ONCE. Every detector below then answers about the file the user
296
+ // wrote, not about the file a transform left behind — none of the five
297
+ // renames touches a reported token today, and relying on that is exactly
298
+ // the coupling that breaks the next time one is added.
299
+ const content = tree.readText(filePath);
300
+ if (isTs || isHtml || isStyle) {
301
+ const next = apply(content, [
302
+ ...(isTs || isHtml ? HTML_TRANSFORMS : []),
303
+ ...(isTs ? TS_TRANSFORMS : []),
304
+ ...GLOBAL_TRANSFORMS,
305
+ ]);
306
+ if (next !== content) {
307
+ tree.overwrite(filePath, next);
308
+ rewritten += 1;
309
+ }
310
+ }
311
+ if (ROUTER_TAB.test(content))
312
+ routerTabs.push(filePath);
313
+ if (LOADING_BAR.test(content))
314
+ loadingBars.push(filePath);
315
+ if (DATE_ADAPTER_NO_LOCALE.test(content))
316
+ dateAdapters.push(filePath);
317
+ if (I18N_NO_DEFAULT_LOCALE.test(content))
318
+ i18nProviders.push(filePath);
319
+ if (DEFAULT_CONFIG.test(content))
320
+ defaultConfigs.push(filePath);
321
+ if (OF_LABEL.test(content))
322
+ ofLabels.push(filePath);
323
+ // Scoped to the `pagination:` block so a `common: { of: '…' }` — a key that
324
+ // is still shipped and still unread — does not drag every catalog in.
325
+ if (paginationBlocks(content).some(block => REMOVED_KEY.test(block))) {
326
+ staleCatalogs.push(filePath);
327
+ }
328
+ });
329
+ if (rewritten > 0) {
330
+ context.logger.info(`ngwr v14 migration: rewrote ${rewritten} file(s) for the renames: on <wr-alert> from ` +
331
+ 'closeable to closable, on <wr-table> from totalItems to total, on <wr-pagination> from ' +
332
+ 'currentPage to page (and from currentPageChange to pageChange), from isDisabledWhenLoading ' +
333
+ 'to disabledWhenLoading, and the window chromeSize scale from compact/normal to sm/md ' +
334
+ '(class .wr-window--chrome-compact to --chrome-sm).');
335
+ context.logger.info('Verify the result with `git diff` — a few edge cases may need manual touch-up.');
336
+ }
337
+ if (staleCatalogs.length > 0) {
338
+ context.logger.warn(`ngwr v14: a translated \`pagination.of\` found in ${staleCatalogs.length} file(s). The range is ` +
339
+ 'one template now — replace it with `pagination.range` ("{{from}}–{{to}} of {{total}}") and ' +
340
+ '`pagination.compact`. Nothing throws: the old key is simply never read again, so a catalog ' +
341
+ 'that keeps it silently reverts that line to English.');
342
+ for (const file of staleCatalogs)
343
+ context.logger.warn(` ${file}`);
344
+ }
345
+ if (loadingBars.length > 0) {
346
+ context.logger.warn(`ngwr v14: <wr-loading-bar> found in ${loadingBars.length} file(s). Router navigations no ` +
347
+ 'longer drive it by default — add `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router` ' +
348
+ 'to your application providers. Nothing throws without it; the bar simply stays at 0%.');
349
+ for (const file of loadingBars)
350
+ context.logger.warn(` ${file}`);
351
+ }
352
+ if (routerTabs.length > 0) {
353
+ context.logger.warn(`ngwr v14: <wr-tab routerLink> found in ${routerTabs.length} file(s). Add \`wrTabsRouting\` to the ` +
354
+ "`<wr-tabs>` element and `WrTabsRouting` (from `ngwr/tabs/router`) to the component's imports. " +
355
+ 'The strip throws at runtime until you do, so this one cannot ship unnoticed.');
356
+ for (const file of routerTabs)
357
+ context.logger.warn(` ${file}`);
358
+ }
359
+ if (dateAdapters.length > 0) {
360
+ context.logger.warn(`ngwr v14: provideWrDateAdapter() with no \`locale\` found in ${dateAdapters.length} file(s). It now ` +
361
+ "takes Angular's LOCALE_ID instead of navigator.language, so a prerendered page and its hydrated " +
362
+ 'self finally agree and an app that set LOCALE_ID stops rendering an English calendar inside ' +
363
+ "itself. If you genuinely wanted the browser's tag, say so: " +
364
+ 'provideWrDateAdapter({ locale: navigator.language }).');
365
+ for (const file of dateAdapters)
366
+ context.logger.warn(` ${file}`);
367
+ }
368
+ if (i18nProviders.length > 0) {
369
+ context.logger.warn(`ngwr v14: provideWrI18n() with no \`defaultLocale\` found in ${i18nProviders.length} file(s). The ` +
370
+ "default is now LOCALE_ID rather than the literal 'en', and `availableLocales` defaults to " +
371
+ "[defaultLocale] rather than ['en']. Catalog lookups also fall back from a region to its language, " +
372
+ 'so a `ru` catalog answers an app running `ru-RU`. Pass `defaultLocale` explicitly to pin the old ' +
373
+ 'behaviour.');
374
+ for (const file of i18nProviders)
375
+ context.logger.warn(` ${file}`);
376
+ }
377
+ if (defaultConfigs.length > 0) {
378
+ context.logger.warn(`ngwr v14: DEFAULT_WR_I18N_CONFIG referenced in ${defaultConfigs.length} file(s). It no longer ` +
379
+ 'carries `defaultLocale` or `availableLocales` — those cannot be constants now that they resolve ' +
380
+ 'from LOCALE_ID — so spreading it no longer produces a complete WrI18nConfigResolved.');
381
+ for (const file of defaultConfigs)
382
+ context.logger.warn(` ${file}`);
383
+ }
384
+ if (ofLabels.length > 0) {
385
+ context.logger.warn(`ngwr v14: <wr-pagination ofLabel> found in ${ofLabels.length} file(s). The input is gone — the ` +
386
+ 'whole "1–10 of 235" line is now the `pagination.range` catalog entry, with `{{from}}`, ' +
387
+ '`{{to}}` and `{{total}}` placeholders, so a translation owns the operand order and the ' +
388
+ 'punctuation as well as the word between them. Move your text there and drop the binding.');
389
+ for (const file of ofLabels)
390
+ context.logger.warn(` ${file}`);
391
+ }
392
+ if (rewritten === 0 &&
393
+ loadingBars.length === 0 &&
394
+ routerTabs.length === 0 &&
395
+ dateAdapters.length === 0 &&
396
+ i18nProviders.length === 0 &&
397
+ defaultConfigs.length === 0 &&
398
+ ofLabels.length === 0 &&
399
+ staleCatalogs.length === 0) {
400
+ context.logger.info('ngwr v14 migration: nothing to do — no affected usage found.');
401
+ }
402
+ return tree;
403
+ };
404
+ }
405
+ function apply(content, transforms) {
406
+ let next = content;
407
+ for (const { pattern, replacement } of transforms) {
408
+ next = next.replace(pattern, replacement);
409
+ }
410
+ return next;
411
+ }
412
+ function visit(tree, path, visitor) {
413
+ const dir = tree.getDir(path);
414
+ for (const file of dir.subfiles)
415
+ visitor(`${path === '/' ? '' : path}/${file}`);
416
+ for (const sub of dir.subdirs) {
417
+ if (IGNORE_DIRS.has(sub))
418
+ continue;
419
+ visit(tree, `${path === '/' ? '' : path}/${sub}`, visitor);
420
+ }
421
+ }
422
+ exports.default = ngUpdateV14;
@@ -20,6 +20,16 @@
20
20
  "version": "12.0.0",
21
21
  "description": "Move the date entry points under one namespace: ngwr/date-adapter \u2192 ngwr/date, ngwr/date-adapter-fns \u2192 ngwr/date/adapters/fns, ngwr/date-adapter-luxon \u2192 ngwr/date/adapters/luxon. Import paths only \u2014 every exported symbol keeps its name. Touches .ts, .html and .json files.",
22
22
  "factory": "./migrations/v12/index#default"
23
+ },
24
+ "migration-v13": {
25
+ "version": "13.0.0",
26
+ "description": "Reports every host-id selector on <wr-checkbox>, <wr-radio> and <wr-switch>: `[id]` no longer lands on the host element, it goes to the inner <input> where the input was always documented to put it. Rewrites nothing — whether `wr-checkbox#agree` should become `#agree` (the tick), `wr-checkbox:has(#agree)` (the row) or a class is a decision only the author can make, and a selector that stops matching is silent in CSS, in querySelector and in a test locator alike. Reads .ts, .html, .scss and .css files.",
27
+ "factory": "./migrations/v13/index#default"
28
+ },
29
+ "migration-v14": {
30
+ "version": "14.0.0",
31
+ "description": "Rewrites five renames: on <wr-alert> from closeable to closable; on <wr-table> from totalItems to total; on <wr-pagination> from currentPage to page (and from currentPageChange to pageChange); from isDisabledWhenLoading to disabledWhenLoading; and the window chromeSize scale from compact/normal to sm/md, including the .wr-window--chrome-compact class. Reports what a codemod cannot decide: router integration is now opt-in (<wr-loading-bar> needs provideWrLoadingBarRouter() from ngwr/loading-bar/router, <wr-tab routerLink> needs wrTabsRouting on the strip plus WrTabsRouting in imports), the locale defaults now follow LOCALE_ID, and <wr-pagination ofLabel> is gone. Touches .ts, .html, .scss and .css files.",
32
+ "factory": "./migrations/v14/index#default"
23
33
  }
24
34
  }
25
35
  }
@@ -120,7 +120,7 @@ export default class ${pascalName} {
120
120
  <wr-table [columns]="columns" [items]="rows()" />
121
121
 
122
122
  <wr-pagination
123
- [(currentPage)]="page"
123
+ [(page)]="page"
124
124
  [(pageSize)]="pageSize"
125
125
  [total]="total()"
126
126
  />
@@ -193,6 +193,7 @@
193
193
  "WrTableFilter": "ngwr/table",
194
194
  "WrTabs": "ngwr/tabs",
195
195
  "WrTab": "ngwr/tabs",
196
+ "WrTabsRouting": "ngwr/tabs/router",
196
197
  "WrTextarea": "ngwr/textarea",
197
198
  "WrTheme": "ngwr/theme",
198
199
  "WrTiltCard": "ngwr/tilt-card",
@@ -5,7 +5,7 @@ description: Build Angular UIs with the ngwr component library (standalone, sign
5
5
 
6
6
  # ngwr
7
7
 
8
- An Angular 22 UI library: 202 tree-shakable entry points, 110 of them shipping a selector.
8
+ An Angular 22 UI library: 204 tree-shakable entry points, 111 of them shipping a selector.
9
9
  Standalone, signals-first, zoneless, `ViewEncapsulation.None`. One runtime
10
10
  dependency (`tslib`).
11
11
 
@@ -92,6 +92,9 @@ cause.
92
92
  - `provideWrDateFnsAdapter() // from 'ngwr/date/adapters/fns'`
93
93
  — every date mode goes through an adapter; there is no built-in default
94
94
  — needed by: `WrDatePicker`, `WrCalendar`, `WrEventCalendar`
95
+ - `provideWrLoadingBarRouter() // from 'ngwr/loading-bar/router'`
96
+ — without it the bar never responds to navigation; it only moves for manual start() / complete()
97
+ — needed by: `WrLoadingBar`
95
98
  - `provideWrI18n() + provideWrI18nStaticLoader({ en: wrEn }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
96
99
  — the pipe and directive read from a catalog you provide
97
100
  — needed by: `WrTPipe`, `WrTDirective`, `WrI18n`
@@ -114,6 +114,7 @@ root barrel for components.
114
114
  | `ngwr/line-chart/testing` | `import { WrLineChartHarness } from 'ngwr/line-chart/testing'` | — |
115
115
  | `ngwr/list` | `import { WrList } from 'ngwr/list'` | `wr-list-item` `wr-list` |
116
116
  | `ngwr/loading-bar` | `import { WrLoadingBar } from 'ngwr/loading-bar'` | `wr-loading-bar` |
117
+ | `ngwr/loading-bar/router` | `import { provideWrLoadingBarRouter } from 'ngwr/loading-bar/router'` | — |
117
118
  | `ngwr/markdown` | `import { WrMarkdown } from 'ngwr/markdown'` | `wr-markdown` |
118
119
  | `ngwr/markdown/testing` | `import { WrMarkdownCodeBlockHarness } from 'ngwr/markdown/testing'` | — |
119
120
  | `ngwr/marquee` | `import { WrMarquee } from 'ngwr/marquee'` | `wr-marquee` |
@@ -180,6 +181,7 @@ root barrel for components.
180
181
  | `ngwr/table` | `import { WrTable } from 'ngwr/table'` | `[wrTableCell]` `ng-template[wrTableExpand]` `wr-table-filter` `ng-template[wrTableGroupHeader]` `wr-table-sort` `wr-table` |
181
182
  | `ngwr/table/testing` | `import { WrTableCellHarness } from 'ngwr/table/testing'` | — |
182
183
  | `ngwr/tabs` | `import { WrTabs } from 'ngwr/tabs'` | `wr-tab` `wr-tabs` |
184
+ | `ngwr/tabs/router` | `import { WrTabsRouting } from 'ngwr/tabs/router'` | `[wrTabsRouting]` |
183
185
  | `ngwr/tabs/testing` | `import { WrTabHarness } from 'ngwr/tabs/testing'` | — |
184
186
  | `ngwr/textarea` | `import { WrTextarea } from 'ngwr/textarea'` | `wr-textarea` |
185
187
  | `ngwr/textarea/testing` | `import { WrTextareaHarness } from 'ngwr/textarea/testing'` | — |
@@ -56,6 +56,10 @@ Icons resolve by name from a registry you populate. Needed by `WrIcon`.
56
56
 
57
57
  Every date mode goes through an adapter; there is no built-in default. Needed by `WrDatePicker`, `WrCalendar`, `WrEventCalendar`.
58
58
 
59
+ ### `provideWrLoadingBarRouter() // from 'ngwr/loading-bar/router'`
60
+
61
+ Without it the bar never responds to navigation; it only moves for manual start() / complete(). Needed by `WrLoadingBar`.
62
+
59
63
  ### `provideWrI18n() + provideWrI18nStaticLoader({ en: wrEn }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
60
64
 
61
65
  The pipe and directive read from a catalog you provide. Needed by `WrTPipe`, `WrTDirective`, `WrI18n`.
@@ -95,7 +95,17 @@ $header-padding-block: 0.25rem;
95
95
  color: var(--wr-color-on-surface);
96
96
  cursor: pointer;
97
97
  text-align: start;
98
- flex: 0 0 auto;
98
+ // Shrinkable, with a floor of nothing. `flex: 0 0 auto` froze the button at
99
+ // its max-content width, so the `<li>`'s own `min-width: 0` bought nothing:
100
+ // the row shrank and the button overflowed it. English hid this — the defect
101
+ // needs a label the row cannot fit, which is what a translation is. Measured
102
+ // in Chromium at a 375px viewport, three steps: `Übersichtsdarstellung /
103
+ // Grundeinstellungen / Rechnungsstellung` pushed the DOCUMENT to 423px, so
104
+ // the whole page scrolled sideways and the last label ran off the screen; the
105
+ // Finnish and Japanese sets did the same by 16px. A horizontal stepper is a
106
+ // full-width row of unbounded strings — it has to be able to give.
107
+ flex: 0 1 auto;
108
+ min-width: 0;
99
109
 
100
110
  &:disabled {
101
111
  cursor: not-allowed;
@@ -145,6 +155,14 @@ $header-padding-block: 0.25rem;
145
155
  font-size: var(--wr-text-sm);
146
156
  font-weight: 500;
147
157
  line-height: $label-line-height;
158
+ // The other half of the shrink above: a flex item's automatic minimum size
159
+ // is its min-content width, and for a German compound noun that IS the whole
160
+ // word, so the button would shrink and the label would not. `break-word`
161
+ // only splits a word that cannot fit a line of its own, so nothing changes
162
+ // until the row is genuinely too narrow — and nothing changes at all in
163
+ // English or above 768px, measured.
164
+ min-width: 0;
165
+ overflow-wrap: break-word;
148
166
  // Centre the label's FIRST LINE on the indicator, now that both are anchored
149
167
  // to the button's block start. Half the difference between the circle and one
150
168
  // line box — derived, not typed, because `--wr-stepper-indicator-size` is a
@@ -94,12 +94,13 @@
94
94
  // cursor follow the `resize` direction.
95
95
  &__resize {
96
96
  position: absolute;
97
- // `textarea.ts` starts the drag from `event.clientX` and applies
98
- // `startWidth + (clientX - startX)`, so the grip has to sit on the physical
99
- // side a growing width extends toward. Flip it only together with that sign,
100
- // once the drag maths itself reads `Directionality`.
101
- // rtl-ok: paired with the physical clientX drag maths in textarea.ts
102
- right: 0.25rem;
97
+ // Logical now, and the drag maths moved with it: `onResize` reads
98
+ // `Directionality` and negates the `clientX` delta under `dir="rtl"`, because
99
+ // a block-level box is anchored at its inline START and grows toward its
100
+ // inline END — physically right in LTR, physically left in RTL. The grip has
101
+ // to sit on the corner the box actually extends toward, so the two halves
102
+ // must be changed together or not at all.
103
+ inset-inline-end: 0.25rem;
103
104
  bottom: 0.25rem;
104
105
  display: inline-flex;
105
106
  color: var(--wr-color-text-faint);
@@ -113,7 +114,14 @@
113
114
  cursor: ew-resize;
114
115
  }
115
116
  &--both {
117
+ // The diagonal cursor names two physical corners, so it is the one cue here
118
+ // that has no logical spelling: at the bottom-LEFT corner an RTL field grows
119
+ // along the north-east / south-west diagonal instead.
116
120
  cursor: nwse-resize;
121
+
122
+ [dir='rtl'] & {
123
+ cursor: nesw-resize;
124
+ }
117
125
  }
118
126
 
119
127
  .wr-icon__svg {
@@ -209,14 +209,22 @@ $toast-colors: (
209
209
  inset-inline-start: 0;
210
210
  height: 2px;
211
211
  width: 100%;
212
- // `transform-origin` has no logical keyword, and the drain is a physical
213
- // `scaleX`. Flipping the bar to drain from the reader's start needs a
214
- // direction-aware rule, not a property rename.
215
- // rtl-ok: origin is one half of a physical scaleX pair
212
+ // `transform-origin` has no logical keyword and the drain is a physical
213
+ // `scaleX`, so the anchor cannot be renamed into a logical property — it has
214
+ // to be swapped by a direction-aware rule. The bar drains toward the reader's
215
+ // end, which means it stays anchored at the reader's START: physically left
216
+ // in LTR, physically right in RTL. Leaving it at `left` in both pinned the
217
+ // remaining time to the side an RTL reader's eye leaves from.
218
+ // rtl-ok: origin is one half of a physical scaleX pair, mirrored below
216
219
  transform-origin: left;
217
220
  background: var(--wr-toast-accent);
218
221
  animation: wr-toast-progress linear forwards;
219
222
  animation-duration: var(--wr-toast-duration, 4000ms);
223
+
224
+ [dir='rtl'] & {
225
+ // rtl-ok: the mirrored half of the pair above
226
+ transform-origin: right;
227
+ }
220
228
  }
221
229
 
222
230
  &:hover &__progress {
@@ -17,7 +17,7 @@ type WrAlertType = 'info' | 'success' | 'warning' | 'danger' | 'neutral' | 'offl
17
17
  * @example
18
18
  * ```html
19
19
  * <wr-alert title="Saved" message="Your changes are live." type="success" />
20
- * <wr-alert title="Failed" type="danger" closeable (closed)="onClose()" />
20
+ * <wr-alert title="Failed" type="danger" closable (closed)="onClose()" />
21
21
  * ```
22
22
  *
23
23
  * @see https://ngwr.dev/reference/components/alert
@@ -62,7 +62,7 @@ declare class WrAlert {
62
62
  *
63
63
  * @default false
64
64
  */
65
- readonly closeable: _angular_core.InputSignalWithTransform<boolean, any>;
65
+ readonly closable: _angular_core.InputSignalWithTransform<boolean, any>;
66
66
  /**
67
67
  * Emitted when the user dismisses the alert via the close button.
68
68
  */
@@ -74,7 +74,7 @@ declare class WrAlert {
74
74
  protected readonly liveLevel: _angular_core.Signal<"assertive" | "polite" | null>;
75
75
  protected onClose(): void;
76
76
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrAlert, never>;
77
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrAlert, "wr-alert", never, { "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "iconName": { "alias": "iconName"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "closeable": { "alias": "closeable"; "required": false; "isSignal": true; }; }, { "closed": "closed"; }, never, never, true, never>;
77
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrAlert, "wr-alert", never, { "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "iconName": { "alias": "iconName"; "required": false; "isSignal": true; }; "message": { "alias": "message"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "closable": { "alias": "closable"; "required": false; "isSignal": true; }; }, { "closed": "closed"; }, never, never, true, never>;
78
78
  }
79
79
 
80
80
  export { WrAlert };