ngwr 14.4.0 → 14.5.1

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 (163) hide show
  1. package/README.md +12 -6
  2. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  3. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  4. package/fesm2022/ngwr-cascader.mjs +15 -3
  5. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  6. package/fesm2022/ngwr-color-picker.mjs +18 -2
  7. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  8. package/fesm2022/ngwr-context-menu.mjs +35 -3
  9. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  10. package/fesm2022/ngwr-date-picker.mjs +30 -2
  11. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  12. package/fesm2022/ngwr-dialog.mjs +12 -1
  13. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  14. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  15. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  16. package/fesm2022/ngwr-drawer.mjs +48 -2
  17. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  18. package/fesm2022/ngwr-dropdown.mjs +22 -2
  19. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  20. package/fesm2022/ngwr-graph-testing.mjs +243 -0
  21. package/fesm2022/ngwr-graph-testing.mjs.map +1 -0
  22. package/fesm2022/ngwr-graph.mjs +1112 -0
  23. package/fesm2022/ngwr-graph.mjs.map +1 -0
  24. package/fesm2022/ngwr-i18n-ar.mjs +5 -0
  25. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  26. package/fesm2022/ngwr-i18n-cs.mjs +5 -0
  27. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  28. package/fesm2022/ngwr-i18n-de.mjs +5 -0
  29. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  30. package/fesm2022/ngwr-i18n-en.mjs +25 -5
  31. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  32. package/fesm2022/ngwr-i18n-es.mjs +5 -0
  33. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-fr.mjs +5 -0
  35. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n-he.mjs +5 -0
  37. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  38. package/fesm2022/ngwr-i18n-hi.mjs +5 -0
  39. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  40. package/fesm2022/ngwr-i18n-id.mjs +5 -0
  41. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  42. package/fesm2022/ngwr-i18n-it.mjs +5 -0
  43. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  44. package/fesm2022/ngwr-i18n-ja.mjs +5 -0
  45. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  46. package/fesm2022/ngwr-i18n-ko.mjs +5 -0
  47. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  48. package/fesm2022/ngwr-i18n-nl.mjs +5 -0
  49. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  50. package/fesm2022/ngwr-i18n-pl.mjs +5 -0
  51. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  52. package/fesm2022/ngwr-i18n-pt.mjs +5 -0
  53. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  54. package/fesm2022/ngwr-i18n-ru.mjs +5 -0
  55. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  56. package/fesm2022/ngwr-i18n-sv.mjs +5 -0
  57. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  58. package/fesm2022/ngwr-i18n-tr.mjs +5 -0
  59. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  60. package/fesm2022/ngwr-i18n-uk.mjs +5 -0
  61. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  62. package/fesm2022/ngwr-i18n-vi.mjs +5 -0
  63. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  64. package/fesm2022/ngwr-i18n-zh-TW.mjs +5 -0
  65. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -1
  66. package/fesm2022/ngwr-i18n-zh.mjs +5 -0
  67. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  68. package/fesm2022/ngwr-i18n.mjs +13 -0
  69. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  70. package/fesm2022/ngwr-lightbox.mjs +25 -4
  71. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  72. package/fesm2022/ngwr-list.mjs +2 -2
  73. package/fesm2022/ngwr-list.mjs.map +1 -1
  74. package/fesm2022/ngwr-mention.mjs +18 -2
  75. package/fesm2022/ngwr-mention.mjs.map +1 -1
  76. package/fesm2022/ngwr-overlay.mjs +119 -4
  77. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  78. package/fesm2022/ngwr-popconfirm.mjs +19 -3
  79. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  80. package/fesm2022/ngwr-popover-testing.mjs +22 -5
  81. package/fesm2022/ngwr-popover-testing.mjs.map +1 -1
  82. package/fesm2022/ngwr-popover.mjs +215 -27
  83. package/fesm2022/ngwr-popover.mjs.map +1 -1
  84. package/fesm2022/ngwr-segmented.mjs +34 -1
  85. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  86. package/fesm2022/ngwr-select.mjs +15 -1
  87. package/fesm2022/ngwr-select.mjs.map +1 -1
  88. package/fesm2022/ngwr-table.mjs +10 -2
  89. package/fesm2022/ngwr-table.mjs.map +1 -1
  90. package/fesm2022/ngwr-toast.mjs +19 -2
  91. package/fesm2022/ngwr-toast.mjs.map +1 -1
  92. package/fesm2022/ngwr-tour.mjs +20 -6
  93. package/fesm2022/ngwr-tour.mjs.map +1 -1
  94. package/fesm2022/ngwr-tree.mjs +9 -2
  95. package/fesm2022/ngwr-tree.mjs.map +1 -1
  96. package/fesm2022/ngwr-version.mjs +1 -1
  97. package/fesm2022/ngwr-version.mjs.map +1 -1
  98. package/fesm2022/ngwr-window.mjs +18 -1
  99. package/fesm2022/ngwr-window.mjs.map +1 -1
  100. package/graph/styles/_index.scss +113 -0
  101. package/i18n/ar.json +5 -0
  102. package/i18n/cs.json +5 -0
  103. package/i18n/de.json +5 -0
  104. package/i18n/en.json +5 -0
  105. package/i18n/es.json +5 -0
  106. package/i18n/fr.json +5 -0
  107. package/i18n/he.json +5 -0
  108. package/i18n/hi.json +5 -0
  109. package/i18n/id.json +5 -0
  110. package/i18n/it.json +5 -0
  111. package/i18n/ja.json +5 -0
  112. package/i18n/ko.json +5 -0
  113. package/i18n/nl.json +5 -0
  114. package/i18n/pl.json +5 -0
  115. package/i18n/pt.json +5 -0
  116. package/i18n/ru.json +5 -0
  117. package/i18n/sv.json +5 -0
  118. package/i18n/tr.json +5 -0
  119. package/i18n/uk.json +5 -0
  120. package/i18n/vi.json +5 -0
  121. package/i18n/zh-TW.json +5 -0
  122. package/i18n/zh.json +5 -0
  123. package/llms-full.txt +13 -3
  124. package/llms.txt +20 -9
  125. package/mcp/providers.js +8 -2
  126. package/package.json +10 -1
  127. package/schematics/migrations/v14/index.js +90 -34
  128. package/schematics/migrations/v7/index.js +15 -4
  129. package/schematics/migrations/v8/index.js +13 -2
  130. package/schematics/migrations/v9/index.js +19 -10
  131. package/schematics/use/symbol-map.json +2 -0
  132. package/segmented/styles/_index.scss +8 -0
  133. package/skills/ngwr/SKILL.md +10 -5
  134. package/skills/ngwr/references/catalog.md +2 -0
  135. package/skills/ngwr/references/setup.md +2 -2
  136. package/styles.scss +1 -0
  137. package/table/styles/_index.scss +39 -12
  138. package/theme/styles/_focus.scss +7 -0
  139. package/types/ngwr-cascader.d.ts +7 -0
  140. package/types/ngwr-color-picker.d.ts +8 -0
  141. package/types/ngwr-context-menu.d.ts +10 -0
  142. package/types/ngwr-date-picker.d.ts +16 -0
  143. package/types/ngwr-dialog.d.ts +1 -0
  144. package/types/ngwr-drawer.d.ts +28 -0
  145. package/types/ngwr-dropdown.d.ts +9 -0
  146. package/types/ngwr-graph-testing.d.ts +190 -0
  147. package/types/ngwr-graph.d.ts +203 -0
  148. package/types/ngwr-i18n-en.d.ts +16 -5
  149. package/types/ngwr-i18n.d.ts +13 -0
  150. package/types/ngwr-lightbox.d.ts +15 -0
  151. package/types/ngwr-mention.d.ts +7 -0
  152. package/types/ngwr-overlay.d.ts +62 -4
  153. package/types/ngwr-popconfirm.d.ts +1 -0
  154. package/types/ngwr-popover-testing.d.ts +22 -5
  155. package/types/ngwr-popover.d.ts +28 -8
  156. package/types/ngwr-segmented.d.ts +16 -0
  157. package/types/ngwr-select.d.ts +7 -0
  158. package/types/ngwr-table.d.ts +8 -0
  159. package/types/ngwr-toast.d.ts +2 -0
  160. package/types/ngwr-tour.d.ts +6 -0
  161. package/types/ngwr-tree.d.ts +2 -1
  162. package/types/ngwr-version.d.ts +1 -1
  163. package/types/ngwr-window.d.ts +1 -0
@@ -101,28 +101,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
101
101
  * Both are now zero for an app that does not opt in.
102
102
  */
103
103
  const IGNORE_DIRS = new Set(['node_modules', 'dist', '.git', '.cache', '.angular', 'coverage', '.next', '.nuxt']);
104
+ /**
105
+ * What an open tag may hold between its name and the attribute a rule is after:
106
+ * a quoted value, consumed only as a PAIR, or any one character that is neither
107
+ * a quote nor `>`. Every element-scoped rule and attribute detector in this file
108
+ * is built on it, and both halves of "only as a pair" were earned.
109
+ *
110
+ * Stepping OVER a quoted value is what a plain `[^>]*?` got wrong: a `>` inside a
111
+ * binding looks like the end of the tag, so
112
+ * `<wr-alert [type]="n > 0 ? 'a' : 'b'" closeable>` left `closeable` behind while
113
+ * the same attributes in the other order moved. The leftover is SILENT — a static
114
+ * `closeable` / `totalItems="42"` that matches no input is an ordinary DOM
115
+ * attribute and `strictTemplates` says nothing, so the alert quietly loses its
116
+ * dismiss button. (Bracketed forms are loud: NG8002.)
117
+ *
118
+ * Never consuming a LONE quote is what the first fix got wrong. It spelled the
119
+ * fallback `[^>]`, which matches a quote as well, so the engine could backtrack
120
+ * into a single-quoted span that STARTS at the apostrophe inside `title="it's"`
121
+ * and runs past `/>` into the following element: `<wr-alert title="it's" />`
122
+ * then `<app-panel [label]="'x'" closeable />` renamed the PANEL's attribute. The
123
+ * same overlap made a miss exponential — one `<wr-alert>` holding twenty-two
124
+ * quoted attributes and nothing to rename took sixteen seconds in the bare
125
+ * `closeable` rule alone, where this form takes under a millisecond. And a miss
126
+ * did not stay inside its tag: since a pair may contain `>`, a pairing that
127
+ * started at a CLOSING quote jumped over `/>` into later elements, so an
128
+ * ordinary element without the attribute sent the search across the rest of the
129
+ * file. On a 64-line template that never finished, and because `ng update`
130
+ * commits each migration's tree separately, v12 and v13 landed while this one
131
+ * wrote nothing. The spec's timing cases are sized to fail in seconds on that form.
132
+ *
133
+ * What it guarantees: a match cannot leave the open tag it started in, as long as
134
+ * every quote in that tag opens or closes an attribute value. The one markup that
135
+ * breaks the condition is an UNQUOTED value holding a quote (`title=it's`), and a
136
+ * tag like that is skipped rather than guessed at — which costs nothing real,
137
+ * because Angular's own template parser refuses it (`Opening tag "input" not
138
+ * terminated.`).
139
+ */
140
+ const IN_TAG = String.raw `(?:"[^"]*"|'[^']*'|[^>"'])*?`;
104
141
  /**
105
142
  * An open tag, up to and including the whitespace before some attribute.
106
- * `[^>]*?` cannot cross a `>`, so a rename can never escape the element it is
107
- * scoped to, however the tag wraps across lines.
108
143
  *
109
144
  * The anchor is `(?![-\w])` and NOT `\b`: a word boundary still matches at the
110
145
  * front of a longer element name, which is how a v9-shaped rule would have
111
146
  * renamed an attribute on `<wr-table-filter>` while claiming to touch only
112
147
  * `<wr-table>`. Every one of these four has such a neighbour — `wr-table-filter`
113
148
  * and `wr-table-sort`, `wr-window-container` and `wr-window-taskbar` — or would
114
- * acquire one the moment the catalog grows.
115
- *
116
- * Quoted values are stepped over rather than excluded, and that is the half a
117
- * plain `[^>]*?` gets wrong: a `>` inside a binding expression looks like the end
118
- * of the tag, so `<wr-alert [type]="n > 0 ? 'a' : 'b'" closeable>` left
119
- * `closeable` behind while the same attributes in the other order moved. The
120
- * leftover is SILENT — a static `closeable` / `totalItems="42"` that matches no
121
- * input is an ordinary DOM attribute and `strictTemplates` says nothing, so the
122
- * alert quietly loses its dismiss button. (Bracketed forms are loud: NG8002.)
123
- * `migration-v9` carries the same anchoring and the same hole.
149
+ * acquire one the moment the catalog grows. `migration-v9` carries the same
150
+ * anchoring and the same pattern.
124
151
  */
125
- const openTag = (name) => String.raw `<${name}(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s`;
152
+ const openTag = (name) => String.raw `<${name}(?![-\w])${IN_TAG}\s`;
153
+ /** The two elements `[wrInput]` applies to — its selector is `input[wrInput], textarea[wrInput]`. */
154
+ const FIELD = String.raw `<(?:input|textarea)(?![-\w])`;
126
155
  const ALERT = openTag('wr-alert');
127
156
  const TABLE = openTag('wr-table');
128
157
  const PAGER = openTag('wr-pagination');
@@ -143,21 +172,15 @@ const HTML_TRANSFORMS = [
143
172
  // native attribute, so rewriting one that was never ngwr's silently rebinds a
144
173
  // character-width hint. The two orders are both real markup, hence two rules.
145
174
  {
146
- pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\swrInput(?:"[^"]*"|'[^']*'|[^>])*?\s)\[wrSize\]/g,
175
+ pattern: new RegExp(String.raw `(${FIELD}${IN_TAG}\swrInput${IN_TAG}\s)\[wrSize\]`, 'g'),
147
176
  replacement: '$1[size]',
148
177
  },
178
+ { pattern: new RegExp(String.raw `(${FIELD}${IN_TAG}\swrInput${IN_TAG}\s)wrSize=`, 'g'), replacement: '$1size=' },
149
179
  {
150
- pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\swrInput(?:"[^"]*"|'[^']*'|[^>])*?\s)wrSize=/g,
151
- replacement: '$1size=',
152
- },
153
- {
154
- pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s)\[wrSize\]((?:"[^"]*"|'[^']*'|[^>])*?\swrInput)/g,
180
+ pattern: new RegExp(String.raw `(${FIELD}${IN_TAG}\s)\[wrSize\](${IN_TAG}\swrInput)`, 'g'),
155
181
  replacement: '$1[size]$2',
156
182
  },
157
- {
158
- pattern: /(<(?:input|textarea)(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s)wrSize=((?:"[^"]*"|'[^']*'|[^>])*?\swrInput)/g,
159
- replacement: '$1size=$2',
160
- },
183
+ { pattern: new RegExp(String.raw `(${FIELD}${IN_TAG}\s)wrSize=(${IN_TAG}\swrInput)`, 'g'), replacement: '$1size=$2' },
161
184
  // <wr-table>, from [totalItems] to [total]
162
185
  { pattern: new RegExp(`(${TABLE})\\[totalItems\\]`, 'g'), replacement: '$1[total]' },
163
186
  { pattern: new RegExp(`(${TABLE})totalItems=`, 'g'), replacement: '$1total=' },
@@ -202,8 +225,12 @@ const GLOBAL_TRANSFORMS = [
202
225
  // keyed on it stops matching with no error — CSS does not have one.
203
226
  { pattern: /\bwr-window--chrome-normal\b/g, replacement: 'wr-window--chrome-md' },
204
227
  ];
205
- /** `<wr-tab …>` carrying a `routerLink`, however the tag is wrapped across lines. */
206
- const ROUTER_TAB = /<wr-tab\b[^>]*\brouterLink\b/;
228
+ /**
229
+ * `<wr-tab …>` carrying a `routerLink`, however the tag is wrapped across lines
230
+ * and whatever an earlier binding compares — the plain `[^>]*` this used to be
231
+ * read `[disabled]="n > 0"` as the end of the tag and reported nothing.
232
+ */
233
+ const ROUTER_TAB = new RegExp(String.raw `<wr-tab(?![-\w])${IN_TAG}\brouterLink\b`);
207
234
  const LOADING_BAR = /<wr-loading-bar\b/;
208
235
  /** `provideWrDateAdapter(` with no `locale:` before the call closes. */
209
236
  // `locale` as a key OR as shorthand, and the scan for it runs to the call's own
@@ -217,8 +244,20 @@ const DATE_ADAPTER_NO_LOCALE = /\bprovideWrDateAdapter\(\s*(?:\)|\{(?![\s\S]*?\b
217
244
  /** `provideWrI18n(` with no `defaultLocale:` before the call closes. */
218
245
  const I18N_NO_DEFAULT_LOCALE = /\bprovideWrI18n\(\s*(?:\)|\{(?![^})]*\bdefaultLocale\s*:))/;
219
246
  const DEFAULT_CONFIG = /\bDEFAULT_WR_I18N_CONFIG\b/;
220
- /** `ofLabel` on a pagination tag, bound (`[ofLabel]`) or static. */
221
- const OF_LABEL = /<wr-pagination\b[^>]*\[?ofLabel\]?\s*=/;
247
+ /** `ofLabel` on a pagination tag, bound (`[ofLabel]`) or static, wherever it sits in the tag. */
248
+ const OF_LABEL = new RegExp(String.raw `${PAGER}\[?ofLabel\]?\s*=`);
249
+ /**
250
+ * `wrSize` still on a `[wrInput]` field AFTER the renames ran, in either
251
+ * attribute order and with any spacing around `=`. The renames read `wrSize=` and
252
+ * `[wrSize]` exactly, and whatever they do not move is silent: a static `wrSize`
253
+ * matches no input, lands on the field as a plain DOM attribute, and the control
254
+ * renders at its default size. So this is the one detector that reads the
255
+ * REWRITTEN text — its whole job is what the rules left behind.
256
+ */
257
+ const LEFTOVER_WR_SIZE = [
258
+ new RegExp(String.raw `${FIELD}${IN_TAG}\swrInput${IN_TAG}\s\[?wrSize\]?\s*=`),
259
+ new RegExp(String.raw `${FIELD}${IN_TAG}\s\[?wrSize\]?\s*=${IN_TAG}\swrInput`),
260
+ ];
222
261
  // The half `OF_LABEL` cannot see. Apps that never bound the input still
223
262
  // translated the key, and `pagination.of` is gone: the range is one
224
263
  // `pagination.range` template now, so a catalog that defines the old key loses
@@ -285,6 +324,7 @@ function ngUpdateV14() {
285
324
  const defaultConfigs = [];
286
325
  const ofLabels = [];
287
326
  const staleCatalogs = [];
327
+ const leftoverSizes = [];
288
328
  let rewritten = 0;
289
329
  visit(tree, '/', filePath => {
290
330
  const lower = filePath.toLowerCase();
@@ -295,10 +335,11 @@ function ngUpdateV14() {
295
335
  // reach stylesheets too, because one of them is a BEM class.
296
336
  if (!isTs && !isHtml && !isStyle && !lower.endsWith('.json'))
297
337
  return;
298
- // Read ONCE. Every detector below then answers about the file the user
299
- // wrote, not about the file a transform left behind — none of the five
300
- // renames touches a reported token today, and relying on that is exactly
301
- // the coupling that breaks the next time one is added.
338
+ // Read ONCE. Every detector below but the leftover `wrSize` check then
339
+ // answers about the file the user wrote, not about the file a transform
340
+ // left behind — none of the six renames touches a token those detectors
341
+ // report today, and relying on that is exactly the coupling that breaks
342
+ // the next time one is added.
302
343
  const content = tree.readText(filePath);
303
344
  if (isTs || isHtml || isStyle) {
304
345
  const next = apply(content, [
@@ -310,6 +351,11 @@ function ngUpdateV14() {
310
351
  tree.overwrite(filePath, next);
311
352
  rewritten += 1;
312
353
  }
354
+ // The one detector that reads what the renames LEFT rather than what the
355
+ // user wrote: a `wrSize` surviving them is exactly what it exists to name.
356
+ if ((isTs || isHtml) && LEFTOVER_WR_SIZE.some(pattern => pattern.test(next))) {
357
+ leftoverSizes.push(filePath);
358
+ }
313
359
  }
314
360
  if (ROUTER_TAB.test(content))
315
361
  routerTabs.push(filePath);
@@ -333,10 +379,19 @@ function ngUpdateV14() {
333
379
  context.logger.info(`ngwr v14 migration: rewrote ${rewritten} file(s) for the renames: on <wr-alert> from ` +
334
380
  'closeable to closable, on <wr-table> from totalItems to total, on <wr-pagination> from ' +
335
381
  'currentPage to page (and from currentPageChange to pageChange), from isDisabledWhenLoading ' +
336
- 'to disabledWhenLoading, and the window chromeSize scale from compact/normal to sm/md ' +
337
- '(class .wr-window--chrome-compact to --chrome-sm).');
382
+ 'to disabledWhenLoading, the window chromeSize scale from compact/normal to sm/md ' +
383
+ '(class .wr-window--chrome-compact to --chrome-sm), and on [wrInput] from wrSize to size ' +
384
+ '(and from [wrSize] to [size]).');
338
385
  context.logger.info('Verify the result with `git diff` — a few edge cases may need manual touch-up.');
339
386
  }
387
+ if (leftoverSizes.length > 0) {
388
+ context.logger.warn(`ngwr v14: \`wrSize\` is still on a [wrInput] field in ${leftoverSizes.length} file(s), in a spelling ` +
389
+ "the rename did not match. [wrInput]'s input has been `size` since v14, so a static wrSize is " +
390
+ 'ignored — it lands as a plain DOM attribute and the field renders at its default size, with ' +
391
+ 'nothing said — and a bound [wrSize] fails to compile (NG8002). Rename it to `size` by hand.');
392
+ for (const file of leftoverSizes)
393
+ context.logger.warn(` ${file}`);
394
+ }
340
395
  if (staleCatalogs.length > 0) {
341
396
  context.logger.warn(`ngwr v14: a translated \`pagination.of\` found in ${staleCatalogs.length} file(s). The range is ` +
342
397
  'one template now — replace it with `pagination.range` ("{{from}}–{{to}} of {{total}}") and ' +
@@ -399,7 +454,8 @@ function ngUpdateV14() {
399
454
  i18nProviders.length === 0 &&
400
455
  defaultConfigs.length === 0 &&
401
456
  ofLabels.length === 0 &&
402
- staleCatalogs.length === 0) {
457
+ staleCatalogs.length === 0 &&
458
+ leftoverSizes.length === 0) {
403
459
  context.logger.info('ngwr v14 migration: nothing to do — no affected usage found.');
404
460
  }
405
461
  return tree;
@@ -6,6 +6,17 @@
6
6
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ /**
10
+ * What an open tag may hold before the attribute a rule is after. A quoted value
11
+ * is consumed only as a PAIR: the plain `[^>]*?` this replaced read a `>` inside
12
+ * a binding as the end of the tag, so
13
+ * `<wr-select [placeholder]="n > 0 ? 'a' : 'b'" multi>` kept its `multi` while the
14
+ * same attributes in the other order moved — and a lone quote is never consumed,
15
+ * so a rule cannot slide through an apostrophe in one element's value into the
16
+ * next element. `migration-v14`'s `IN_TAG` carries the full account, including
17
+ * the one shape this skips: an UNQUOTED value holding a quote (`title=it's`).
18
+ */
19
+ const IN_TAG = String.raw `(?:"[^"]*"|'[^']*'|[^>"'])*?`;
9
20
  const HTML_TRANSFORMS = [
10
21
  // wr-autocomplete → wr-select mode="search"
11
22
  { pattern: /<wr-autocomplete(\s|>|\/)/g, replacement: '<wr-select mode="search"$1' },
@@ -35,9 +46,9 @@ const HTML_TRANSFORMS = [
35
46
  { pattern: /<wr-count-up-text(\s|>|\/)/g, replacement: '<wr-count-up$1' },
36
47
  { pattern: /<\/wr-count-up-text>/g, replacement: '</wr-count-up>' },
37
48
  // wr-select's deprecated [multi] alias → mode="multi"
38
- { pattern: /(<wr-select\b[^>]*?)\s\[multi\]="true"/g, replacement: '$1 mode="multi"' },
39
- { pattern: /(<wr-select\b[^>]*?)\s\[multi\]="false"/g, replacement: '$1' },
40
- { pattern: /(<wr-select\b[^>]*?)\smulti(?=[\s>/])/g, replacement: '$1 mode="multi"' },
49
+ { pattern: new RegExp(String.raw `(<wr-select\b${IN_TAG})\s\[multi\]="true"`, 'g'), replacement: '$1 mode="multi"' },
50
+ { pattern: new RegExp(String.raw `(<wr-select\b${IN_TAG})\s\[multi\]="false"`, 'g'), replacement: '$1' },
51
+ { pattern: new RegExp(String.raw `(<wr-select\b${IN_TAG})\smulti(?=[\s>/])`, 'g'), replacement: '$1 mode="multi"' },
41
52
  // wr-image → wr-lightbox (rename — same surface)
42
53
  { pattern: /<wr-image(\s|>|\/)/g, replacement: '<wr-lightbox$1' },
43
54
  { pattern: /<\/wr-image>/g, replacement: '</wr-lightbox>' },
@@ -46,7 +57,7 @@ const HTML_TRANSFORMS = [
46
57
  // attribute form (`<h1 wr-animated-text …>`) has no 1:1 target and is
47
58
  // left for manual migration; a bound `[mode]` falls back to typewriter.
48
59
  {
49
- pattern: /<wr-animated-text\b([^>]*?)\s*(\/>|>\s*<\/wr-animated-text>)/g,
60
+ pattern: new RegExp(String.raw `<wr-animated-text\b(${IN_TAG})\s*(\/>|>\s*<\/wr-animated-text>)`, 'g'),
50
61
  replacement: (_match, attrs) => {
51
62
  const mode = /\bmode\s*=\s*"(\w+)"/.exec(attrs)?.[1] ?? 'typewriter';
52
63
  const tag = mode === 'split' ? 'wr-split-text' : mode === 'scramble' ? 'wr-decrypt-text' : 'wr-typewriter';
@@ -12,6 +12,17 @@ const DENSITY_MAP = [
12
12
  ['default', 'md'],
13
13
  ['comfortable', 'lg'],
14
14
  ];
15
+ /**
16
+ * What an open tag may hold before the attribute a rule is after. A quoted value
17
+ * is consumed only as a PAIR: the plain `[^>]*?` this replaced read a `>` inside
18
+ * a binding as the end of the tag, so `<wr-pagination [disabled]="n > 0" size="xs" />`
19
+ * kept its `xs` while the same attributes in the other order moved — and a lone
20
+ * quote is never consumed, so a rule cannot slide through an apostrophe in one
21
+ * element's value into the next element. `migration-v14`'s `IN_TAG` carries the
22
+ * full account, including the one shape this skips: an UNQUOTED value holding a
23
+ * quote (`title=it's`).
24
+ */
25
+ const IN_TAG = String.raw `(?:"[^"]*"|'[^']*'|[^>"'])*?`;
15
26
  const HTML_TRANSFORMS = [
16
27
  // density — directive attribute, string form: wrDensity="compact" → "sm"
17
28
  ...DENSITY_MAP.map(([from, to]) => ({
@@ -29,8 +40,8 @@ const HTML_TRANSFORMS = [
29
40
  replacement: `$1${to}$2`,
30
41
  })),
31
42
  // pagination size — scoped to the element so other `size="xs"` attrs are untouched
32
- { pattern: /(<wr-pagination\b[^>]*?\ssize=")xs(")/g, replacement: '$1sm$2' },
33
- { pattern: /(<wr-pagination\b[^>]*?\ssize=")xl(")/g, replacement: '$1lg$2' },
43
+ { pattern: new RegExp(String.raw `(<wr-pagination\b${IN_TAG}\ssize=")xs(")`, 'g'), replacement: '$1sm$2' },
44
+ { pattern: new RegExp(String.raw `(<wr-pagination\b${IN_TAG}\ssize=")xl(")`, 'g'), replacement: '$1lg$2' },
34
45
  ];
35
46
  const TS_TRANSFORMS = [
36
47
  // density — provideWrDensity({ defaultDensity: 'compact' }) → 'sm'
@@ -14,17 +14,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  * `FormValueControl`) and must be left alone — renaming it would break every
15
15
  * bound group.
16
16
  *
17
- * Quoted values are STEPPED OVER rather than excluded, and that is the half a
18
- * plain `[^>]*?` got wrong for two majors: a `>` inside a binding expression
19
- * looks like the end of the tag, so `<wr-checkbox [disabled]="n > 0" value="a">`
20
- * kept its `value` while the same attributes in the other order moved. The
21
- * leftover is SILENT — a static `value="a"` that matches no input is an ordinary
22
- * DOM attribute, `strictTemplates` says nothing, and every checkbox in the group
23
- * keeps the default identity `null`, so they all toggle together. That is the
24
- * exact defect this migration exists to prevent, reintroduced by the migration
25
- * itself. `migration-v14` fixed the same anchoring; this is that fix, ported.
17
+ * A quoted value is consumed only as a PAIR, and both halves of that matter.
18
+ * Stepping OVER it is what a plain `[^>]*?` got wrong for two majors: a `>`
19
+ * inside a binding expression looks like the end of the tag, so
20
+ * `<wr-checkbox [disabled]="n > 0" value="a">` kept its `value` while the same
21
+ * attributes in the other order moved. The leftover is SILENT — a static
22
+ * `value="a"` that matches no input is an ordinary DOM attribute,
23
+ * `strictTemplates` says nothing, and every checkbox in the group keeps the
24
+ * default identity `null`, so they all toggle together. That is the exact defect
25
+ * this migration exists to prevent, reintroduced by the migration itself.
26
+ *
27
+ * Never consuming a LONE quote is the half the first fix missed. It spelled the
28
+ * fallback `[^>]`, which matches a quote too, so the engine could backtrack into
29
+ * a single-quoted span starting at the apostrophe of `label="it's"` and running
30
+ * past `/>` into the next element: `<wr-checkbox label="it's" />` followed by
31
+ * `<wr-select [x]="'y'" value="z" />` renamed the SELECT's form model. Now a rename
32
+ * stays inside one open tag whenever every quote in it opens or closes a value;
33
+ * an UNQUOTED value holding a quote (`title=it's`) breaks that, and is skipped.
34
+ * `migration-v14`'s `IN_TAG` carries the same pattern and the longer account.
26
35
  */
27
- const CHECKBOX_TAG = String.raw `<wr-checkbox(?![-\w])(?:"[^"]*"|'[^']*'|[^>])*?\s`;
36
+ const CHECKBOX_TAG = String.raw `<wr-checkbox(?![-\w])(?:"[^"]*"|'[^']*'|[^>"'])*?\s`;
28
37
  const HTML_TRANSFORMS = [
29
38
  // <wr-checkbox … value="x"> → checkboxValue="x"
30
39
  { pattern: new RegExp(`(${CHECKBOX_TAG})value=`, 'g'), replacement: '$1checkboxValue=' },
@@ -91,6 +91,8 @@
91
91
  "WrGauge": "ngwr/gauge",
92
92
  "WrGlitchText": "ngwr/glitch-text",
93
93
  "WrGradientText": "ngwr/gradient-text",
94
+ "WrGraph": "ngwr/graph",
95
+ "WrGraphNodeTemplate": "ngwr/graph",
94
96
  "WrHotkey": "ngwr/hotkey",
95
97
  "WrHotkeyBinding": "ngwr/hotkey",
96
98
  "WrI18n": "ngwr/i18n",
@@ -70,6 +70,14 @@
70
70
  transition: transform 0.25s var(--wr-ease-out);
71
71
  }
72
72
 
73
+ // A direction flip mirrors the strip, which moves the thumb's slot by the whole
74
+ // width of it. Animated, that drags the pill across options it does not mark;
75
+ // the component sets this class for the tick the flip lands in so the pill is
76
+ // simply already there. Selection changes keep their slide.
77
+ &--flipping &__thumb {
78
+ transition: none;
79
+ }
80
+
73
81
  &--unselected &__thumb {
74
82
  opacity: 0;
75
83
  }
@@ -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: 225 tree-shakable entry points, 112 of them shipping a selector.
8
+ An Angular 22 UI library: 227 tree-shakable entry points, 113 of them shipping a selector.
9
9
  Standalone, signals-first, zoneless, `ViewEncapsulation.None`. One runtime
10
10
  dependency (`tslib`).
11
11
 
@@ -44,8 +44,13 @@ guess gets backwards.
44
44
  - **There is no `ControlValueAccessor`.** Value components implement Signal
45
45
  Forms' `FormValueControl` / `FormCheckboxControl`. Bind
46
46
  `[formField]="form.x"`, or use the two-way model standalone —
47
- `[(value)]`, `[(checked)]`. `[(ngModel)]` and reactive forms still work:
48
- Angular 22 synthesises the accessor.
47
+ `[(value)]`, `[(checked)]`. `[(ngModel)]` and reactive forms still bind,
48
+ with no accessor created: Angular's `NgModel` / `FormControlDirective` drive
49
+ the `value` or `checked` model directly. That bridge does NOT apply template
50
+ validator directives — `required`, `minlength`, `maxlength`, `pattern` or
51
+ `email` on an ngwr control is silently ignored and the form submits — so put
52
+ validators on the `FormControl`. It also ignores `updateOn`, and a
53
+ `{ emitEvent: false }` write needs `markForCheck()`.
49
54
  - **`<wr-checkbox>` group identity is `checkboxValue`, not `value`.**
50
55
  `value` is reserved for the form value, so a leftover `value="x"` lands on the
51
56
  host as a plain DOM attribute, every box in the group keeps the default
@@ -95,8 +100,8 @@ cause.
95
100
  - `provideWrLoadingBarRouter() // from 'ngwr/loading-bar/router'`
96
101
  — without it the bar never responds to navigation; it only moves for manual start() / complete()
97
102
  — needed by: `WrLoadingBar`
98
- - `provideWrI18n() + provideWrI18nStaticLoader({ en: wrEn }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
99
- — the pipe and directive read from a catalog you provide
103
+ - `provideWrI18n() + provideWrI18nBaseCatalogs({ en: wrEn }) + provideWrI18nStaticLoader({ en: { … } }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
104
+ — the pipe and directive read from a catalog you provide. Register ngwr's with `provideWrI18nBaseCatalogs`, never spread it into yours: a spread is shallow, so any namespace you share with ngwr, e.g. `common`, `validation`, `table`, keeps only the side spread last, and the other side's keys stop resolving with nothing logged — ngwr's components fall back to English and a `wrT` read of a lost key renders the raw key
100
105
  — needed by: `WrTPipe`, `WrTDirective`, `WrI18n`
101
106
 
102
107
  ## Testing
@@ -89,6 +89,8 @@ root barrel for components.
89
89
  | `ngwr/glitch-text/testing` | `import { WrGlitchTextHarness } from 'ngwr/glitch-text/testing'` | — |
90
90
  | `ngwr/gradient-text` | `import { WrGradientText } from 'ngwr/gradient-text'` | `wr-gradient-text` |
91
91
  | `ngwr/gradient-text/testing` | `import { WrGradientTextHarness } from 'ngwr/gradient-text/testing'` | — |
92
+ | `ngwr/graph` | `import { WrGraph } from 'ngwr/graph'` | `ng-template[wrGraphNode]` `wr-graph` |
93
+ | `ngwr/graph/testing` | `import { WrGraphHarness } from 'ngwr/graph/testing'` | — |
92
94
  | `ngwr/hotkey` | `import { WrHotkey } from 'ngwr/hotkey'` | `[wrHotkey]` |
93
95
  | `ngwr/i18n` | `import { WrI18n } from 'ngwr/i18n'` | `[wrT]` |
94
96
  | `ngwr/i18n/ar` | `import { wrAr } from 'ngwr/i18n/ar'` | — |
@@ -60,9 +60,9 @@ Every date mode goes through an adapter; there is no built-in default. Needed by
60
60
 
61
61
  Without it the bar never responds to navigation; it only moves for manual start() / complete(). Needed by `WrLoadingBar`.
62
62
 
63
- ### `provideWrI18n() + provideWrI18nStaticLoader({ en: wrEn }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
63
+ ### `provideWrI18n() + provideWrI18nBaseCatalogs({ en: wrEn }) + provideWrI18nStaticLoader({ en: { … } }) // from 'ngwr/i18n' + 'ngwr/i18n/en'`
64
64
 
65
- The pipe and directive read from a catalog you provide. Needed by `WrTPipe`, `WrTDirective`, `WrI18n`.
65
+ The pipe and directive read from a catalog you provide. Register ngwr's with `provideWrI18nBaseCatalogs`, never spread it into yours: a spread is shallow, so any namespace you share with ngwr, e.g. `common`, `validation`, `table`, keeps only the side spread last, and the other side's keys stop resolving with nothing logged — ngwr's components fall back to English and a `wrT` read of a lost key renders the raw key. Needed by `WrTPipe`, `WrTDirective`, `WrI18n`.
66
66
 
67
67
  ## Optional, app-wide
68
68
 
package/styles.scss CHANGED
@@ -61,6 +61,7 @@
61
61
  @forward './gauge/styles';
62
62
  @forward './glitch-text/styles';
63
63
  @forward './gradient-text/styles';
64
+ @forward './graph/styles';
64
65
  @forward './icon/styles';
65
66
  @forward './image-cropper/styles';
66
67
  @forward './input/styles';
@@ -389,6 +389,14 @@
389
389
  padding: 0;
390
390
  cursor: pointer;
391
391
  color: inherit;
392
+ // The indicator inside is a bare `.wr-icon__svg`, which is `1em`, and a
393
+ // `<button>` does not inherit its font-size: it takes the browser's control
394
+ // font (about 13.33px) unless the app resets it. So the same glyph measured
395
+ // 13.33px on a page with no reset and 12px under `ngwr/reset`, which sets
396
+ // `font: inherit`. Inheriting the size makes `1em` the header's own text in
397
+ // every app. `--wr-icon-size` cannot do this job — only a `<wr-icon>` host
398
+ // reads it, and the rule here used to set it for nothing.
399
+ font-size: inherit;
392
400
  line-height: 0;
393
401
  }
394
402
 
@@ -741,21 +749,35 @@
741
749
 
742
750
  .wr-table-sort {
743
751
  display: inline-flex;
744
- --wr-icon-size: 0.875rem;
752
+ // Unsorted is the flat line the template draws, at full strength in the muted
753
+ // role — the same ink as the filter funnel beside it. It used to be the sort
754
+ // ARROW at half opacity, which is how an unsorted column came to read as
755
+ // "sorted ascending, dimmed". The glyph carries the state now, so the opacity
756
+ // has no job left, and dropping it is what lifts the indicator over the 3:1 a
757
+ // graphic that identifies a control is held to. Measured on the header's own
758
+ // background in Chromium, after the colour transition settled: from 1.98:1 to
759
+ // 4.78:1 in light (`#5f6c7d` on `#eff2f6`) and from 2.74:1 to 7.04:1 in dark
760
+ // (`#9aa6b8` on `#131a2b`). An SVG stroke is invisible to axe, so re-measure
761
+ // by hand if this ink or the header fill ever moves.
745
762
  color: var(--wr-color-on-surface-muted);
746
- transition:
747
- transform var(--wr-transition-base),
748
- color var(--wr-transition-base);
749
- opacity: 0.5;
750
-
751
- &--asc {
752
- opacity: 1;
763
+ // Only the colour eases back to unsorted. A transition is taken from the
764
+ // state being ENTERED, so leaving `--desc` for the base would otherwise spin
765
+ // the flat line from 180deg to 0 — motion with no direction to report. The
766
+ // turn between the two arrows still animates, since both modifiers carry it.
767
+ transition: color var(--wr-transition-base);
768
+
769
+ // The template swaps the glyph — a flat line while unsorted, an arrow here
770
+ // (table-sort.ts) — so the state reads from the shape, and the primary ink
771
+ // only says which column is the sorted one.
772
+ &--asc,
773
+ &--desc {
753
774
  color: var(--wr-color-primary);
775
+ transition:
776
+ transform var(--wr-transition-base),
777
+ color var(--wr-transition-base);
754
778
  }
755
779
 
756
780
  &--desc {
757
- opacity: 1;
758
- color: var(--wr-color-primary);
759
781
  transform: rotate(180deg);
760
782
  }
761
783
  }
@@ -781,7 +803,13 @@
781
803
  padding: 0;
782
804
  cursor: pointer;
783
805
  color: inherit;
784
- --wr-icon-size: 0.875rem;
806
+ // The funnel is the same bare `1em` svg as the sort indicator beside it, in
807
+ // the same kind of `<button>`, so it inherits the header's font-size for the
808
+ // same reason (see `.wr-table__sort-btn`). The size only, not the `font`
809
+ // shorthand: that would also bring the header's 1.25rem line-height, and the
810
+ // count pill beside the funnel — 10px type with no line-height of its own —
811
+ // grows from 12px to 20px tall under it.
812
+ font-size: inherit;
785
813
  }
786
814
 
787
815
  &__count {
@@ -855,7 +883,6 @@
855
883
  border-radius: var(--wr-border-radius-sm);
856
884
  border: 1px solid var(--wr-color-outline);
857
885
  color: transparent;
858
- --wr-icon-size: 0.75rem;
859
886
  transition: var(--wr-transition-base);
860
887
  }
861
888
 
@@ -164,3 +164,10 @@
164
164
  .wr-alert__close:focus-visible {
165
165
  @include focus-ring;
166
166
  }
167
+
168
+ // Graph viewport — the scroller is the graph's one tab stop, so a keyboard user
169
+ // scrolling it needs to see where focus is. Safe as an OUTSET ring: the outline
170
+ // sits outside the scroller's own box, and `.wr-graph` sets no `overflow`.
171
+ .wr-graph__viewport:focus-visible {
172
+ @include focus-ring;
173
+ }
@@ -133,6 +133,13 @@ declare class WrCascader<T = string> implements FormValueControl<unknown> {
133
133
  private readonly scrollStrategies;
134
134
  private readonly destroyRef;
135
135
  private readonly injector;
136
+ /**
137
+ * Ambient reading direction, for the open panel alone — nothing in this
138
+ * component reads it directly. Optional so a bare `TestBed` needs no
139
+ * provider; `Directionality` is root-provided anyway, and a missing one
140
+ * simply reads as `ltr`.
141
+ */
142
+ private readonly dir;
136
143
  private overlayRef;
137
144
  constructor();
138
145
  protected onTriggerClick(): void;
@@ -415,6 +415,14 @@ declare class WrColorPickerTrigger {
415
415
  private readonly vcr;
416
416
  private readonly scrollStrategies;
417
417
  private readonly destroyRef;
418
+ private readonly injector;
419
+ /**
420
+ * Ambient reading direction, for the open panel alone — nothing in this
421
+ * directive reads it directly. Optional so a bare `TestBed` needs no
422
+ * provider; `Directionality` is root-provided anyway, and a missing one
423
+ * simply reads as `ltr`.
424
+ */
425
+ private readonly dir;
418
426
  protected readonly isOpen: _angular_core.WritableSignal<boolean>;
419
427
  private overlayRef;
420
428
  /** @internal Public so the host binding can read it. */
@@ -44,9 +44,18 @@ declare class WrContextMenu {
44
44
  readonly menu: _angular_core.InputSignal<WrContextMenuPanel>;
45
45
  private readonly host;
46
46
  private readonly overlay;
47
+ /**
48
+ * The reading direction the open menu has to keep up with.
49
+ *
50
+ * Optional inject, as on every other overlay here: `Directionality` is
51
+ * root-provided and so never null in an app, but a bare `TestBed` that
52
+ * provides nothing should still get a menu that opens.
53
+ */
54
+ private readonly dir;
47
55
  private readonly outsideClick;
48
56
  private readonly vcr;
49
57
  private readonly scrollStrategies;
58
+ private readonly injector;
50
59
  private readonly destroyRef;
51
60
  /**
52
61
  * Id of the menu element while this trigger's menu is open, published on the
@@ -219,6 +228,7 @@ declare class WrContextMenuItem {
219
228
  private readonly dir;
220
229
  private readonly vcr;
221
230
  private readonly scrollStrategies;
231
+ private readonly injector;
222
232
  private readonly destroyRef;
223
233
  private submenuRef;
224
234
  private submenuKeys;
@@ -83,6 +83,14 @@ declare class WrDatePicker implements FormValueControl<Date | null> {
83
83
  private readonly scrollStrategies;
84
84
  private readonly host;
85
85
  private readonly destroyRef;
86
+ private readonly injector;
87
+ /**
88
+ * Ambient reading direction, for the open panel alone — nothing in this
89
+ * component reads it directly. Optional so a bare `TestBed` needs no
90
+ * provider; `Directionality` is root-provided anyway, and a missing one
91
+ * simply reads as `ltr`.
92
+ */
93
+ private readonly dir;
86
94
  protected readonly inputEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
87
95
  protected readonly triggerEl: _angular_core.Signal<ElementRef<HTMLButtonElement> | undefined>;
88
96
  /** The picked Date. Bound by `[formField]`, or two-way via `[(value)]`. */
@@ -306,6 +314,14 @@ declare class WrDateRangePicker implements FormValueControl<WrDateRange | null>
306
314
  private readonly scrollStrategies;
307
315
  private readonly host;
308
316
  private readonly destroyRef;
317
+ private readonly injector;
318
+ /**
319
+ * Ambient reading direction, for the open panel alone — nothing in this
320
+ * component reads it directly. Optional so a bare `TestBed` needs no
321
+ * provider; `Directionality` is root-provided anyway, and a missing one
322
+ * simply reads as `ltr`.
323
+ */
324
+ private readonly dir;
309
325
  protected readonly startEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
310
326
  protected readonly endEl: _angular_core.Signal<ElementRef<HTMLInputElement>>;
311
327
  protected readonly triggerEl: _angular_core.Signal<ElementRef<HTMLButtonElement> | undefined>;
@@ -149,6 +149,7 @@ declare class WrDialog {
149
149
  private readonly isBrowser;
150
150
  private readonly responsiveConfig;
151
151
  private readonly i18n;
152
+ private readonly dir;
152
153
  private readonly location;
153
154
  open<C, R = unknown, D = unknown>(component: ComponentType<C>, options?: WrDialogOptions<D>): WrDialogRef<C, R>;
154
155
  /**