ngwr 14.5.0 → 14.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/avatar/styles/_index.scss +46 -0
- package/fesm2022/ngwr-avatar.mjs +58 -7
- package/fesm2022/ngwr-avatar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +16 -5
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +13 -0
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper-testing.mjs +34 -3
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +98 -5
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +1 -1
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +1 -1
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-select-testing.mjs +21 -2
- package/fesm2022/ngwr-select-testing.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +233 -35
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +10 -2
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/image-cropper/styles/_index.scss +7 -0
- package/llms-full.txt +5 -5
- package/llms.txt +12 -3
- package/mcp/providers.js +8 -2
- package/package.json +1 -1
- package/schematics/migrations/v14/index.js +90 -34
- package/schematics/migrations/v7/index.js +15 -4
- package/schematics/migrations/v8/index.js +13 -2
- package/schematics/migrations/v9/index.js +19 -10
- package/schematics/use/symbol-map.json +1 -0
- package/select/styles/_index.scss +34 -0
- package/skills/ngwr/SKILL.md +9 -4
- package/skills/ngwr/references/catalog.md +1 -1
- package/skills/ngwr/references/setup.md +2 -2
- package/table/styles/_index.scss +39 -12
- package/types/ngwr-avatar.d.ts +36 -3
- package/types/ngwr-i18n-en.d.ts +16 -5
- package/types/ngwr-i18n.d.ts +13 -0
- package/types/ngwr-image-cropper-testing.d.ts +25 -3
- package/types/ngwr-image-cropper.d.ts +88 -2
- package/types/ngwr-select-testing.d.ts +18 -1
- package/types/ngwr-select.d.ts +181 -10
- package/types/ngwr-table.d.ts +8 -0
- package/types/ngwr-version.d.ts +1 -1
|
@@ -16,7 +16,7 @@ import { InjectionToken } from '@angular/core';
|
|
|
16
16
|
* Distinct from `Version` exported by `@angular/core`, which reports the
|
|
17
17
|
* loaded Angular runtime version (e.g. `'22.0.0'`), not ngwr's.
|
|
18
18
|
*/
|
|
19
|
-
const NGWR_VERSION = '14.
|
|
19
|
+
const NGWR_VERSION = '14.6.0';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @license
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.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 = '14.
|
|
1
|
+
{"version":3,"file":"ngwr-version.mjs","sources":["../../../projects/lib/version/version.ts","../../../projects/lib/version/version.token.ts","../../../projects/lib/version/ngwr-version.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 = '14.6.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","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAEH;;;;;;;;;AASG;AACI,MAAM,YAAY,GAAG;;ACjB5B;;;;;AAKG;AAMH;;;;;;;;;;;;;AAaG;MACU,kBAAkB,GAAG,IAAI,cAAc,CAAS,oBAAoB,EAAE;AACjF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,YAAY;AAC5B,CAAA;;AC5BD;;AAEG;;;;"}
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
user-select: none;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// A source the browser could not decode. The `<img>` stays in the DOM, since the
|
|
27
|
+
// next `src` goes through the same element, but some engines draw a broken-image
|
|
28
|
+
// glyph for it — and the failure is the host's to explain, not a stray icon's.
|
|
29
|
+
&--failed &__image {
|
|
30
|
+
display: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
&__backdrop {
|
|
27
34
|
position: absolute;
|
|
28
35
|
inset: 0;
|
package/llms-full.txt
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
- import: `import { WrAvatar } from 'ngwr/avatar'`
|
|
46
46
|
- selector: `wr-avatar`
|
|
47
47
|
- exports: WrAvatar, WrAvatarSize, WrAvatarShape
|
|
48
|
-
- Image avatar with a
|
|
48
|
+
- Image avatar with a square, rounded, circle or squircle shape. Projected initials stand in while the image loads and when it fails.
|
|
49
49
|
|
|
50
50
|
## ngwr/back-top
|
|
51
51
|
- import: `import { WrBackTop } from 'ngwr/back-top'`
|
|
@@ -461,7 +461,7 @@
|
|
|
461
461
|
- import: `import { WrI18n } from 'ngwr/i18n'`
|
|
462
462
|
- selector: `[wrT]`
|
|
463
463
|
- exports: WrI18n, WrTPipe, WrTDirective, WR_I18N_CONFIG, DEFAULT_WR_I18N_CONFIG, WrI18nCatalog, WrI18nConfig, WrI18nConfigResolved, WrI18nParams, WrI18nMissingHandler, WR_I18N_LOADER, WrI18nLoader, WR_I18N_BASE_CATALOGS, WrI18nBaseCatalogs, WrI18nStaticLoader, WrI18nStaticCatalogs, WrI18nStaticScopedCatalogs, WrI18nHttpLoader, WrI18nHttpLoaderConfig, provideWrI18n, provideWrI18nBaseCatalogs, provideWrI18nStaticLoader, provideWrI18nHttpLoader, ProvideWrI18nOptions, useI18nText, readI18nText, useI18nFormatter
|
|
464
|
-
- Reactive i18n service. Active locale lives on a signal, catalogs load lazily through a swappable loader (HTTP or static), and double-brace placeholders interpolate at render time. Scopes let feature modules ship and load their own catalogs.
|
|
464
|
+
- Reactive i18n service. Active locale lives on a signal, catalogs load lazily through a swappable loader (HTTP or static), and double-brace placeholders interpolate at render time. Scopes let feature modules ship and load their own catalogs. Keep ngwr's own strings with `provideWrI18nBaseCatalogs` rather than spreading `wrEn` into your catalog: 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.
|
|
465
465
|
|
|
466
466
|
## ngwr/i18n/ar
|
|
467
467
|
- import: `import { wrAr } from 'ngwr/i18n/ar'`
|
|
@@ -568,7 +568,7 @@
|
|
|
568
568
|
## ngwr/image-cropper
|
|
569
569
|
- import: `import { WrImageCropper } from 'ngwr/image-cropper'`
|
|
570
570
|
- selector: `wr-image-cropper`
|
|
571
|
-
- exports: WrImageCropper, WrCropRect, WrCropHandle, WrImageOutputType
|
|
571
|
+
- exports: WrImageCropper, WrCropRect, WrCropHandle, WrImageCropperStatus, WrImageLoadError, WrImageOutputType
|
|
572
572
|
- DOM-based crop UI. Drag the box to move, drag any of the eight handles to resize. Optional `[aspectRatio]` locks the box shape. `(cropped)` fires with a `Blob` after each drag end — wire to upload or preview.
|
|
573
573
|
|
|
574
574
|
## ngwr/image-cropper/testing
|
|
@@ -840,8 +840,8 @@
|
|
|
840
840
|
|
|
841
841
|
## ngwr/select
|
|
842
842
|
- import: `import { WrSelect } from 'ngwr/select'`
|
|
843
|
-
- selector: `wr-option-group`, `wr-option`, `wr-select`
|
|
844
|
-
- exports: WrSelect, WrOption, WrOptionGroup, WR_SELECT, WrSelectContext, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize
|
|
843
|
+
- selector: `wr-option-group`, `ng-template[wrOptionLeading]`, `wr-option`, `wr-select`
|
|
844
|
+
- exports: WrSelect, WrOption, WrOptionGroup, WrOptionLeading, WR_SELECT, WrSelectContext, WrOptionLeadingContext, WrOptionLeadingPlacement, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize
|
|
845
845
|
- Native-like select built on CDK Overlay. A signal-forms native control — it implements `FormValueControl`, so `[formField]` binds straight to its `value` model. `[(value)]` works standalone, and `[(ngModel)]` / reactive forms keep working through [Angular 22's forms bridge](/guides/forms).
|
|
846
846
|
|
|
847
847
|
## ngwr/select/testing
|
package/llms.txt
CHANGED
|
@@ -84,7 +84,12 @@ Add from their entry points as needed:
|
|
|
84
84
|
/ `select`, and `linkTarget` / `copyable` on `markdown`. There is deliberately no
|
|
85
85
|
`color` key — a bound `[color]="null"` means "no intent", which a default cannot
|
|
86
86
|
express without inventing a sentinel.
|
|
87
|
-
- `provideWrI18n(...)` + `
|
|
87
|
+
- `provideWrI18n(...)` + `provideWrI18nBaseCatalogs({ en: wrEn })` +
|
|
88
|
+
`provideWrI18nStaticLoader(...)` — localization. ngwr's catalogs go in as a
|
|
89
|
+
BASE, never spread into yours: a spread is shallow, so any namespace you share
|
|
90
|
+
with ngwr, e.g. `common`, `validation`, `table`, keeps only the side spread
|
|
91
|
+
last, and the other side's keys stop resolving with nothing logged — ngwr's
|
|
92
|
+
components fall back to English and a `wrT` read renders the raw key.
|
|
88
93
|
- `provideWrDateAdapter(...)` — calendar + date-picker (`ngwr/date/adapters/fns`
|
|
89
94
|
or `ngwr/date/adapters/luxon`).
|
|
90
95
|
|
|
@@ -106,8 +111,12 @@ one element at a time; a step whose target is missing is skipped, not floated.
|
|
|
106
111
|
|
|
107
112
|
## Upgrading to v14
|
|
108
113
|
|
|
109
|
-
Run `ng update ngwr@14
|
|
110
|
-
|
|
114
|
+
Run `ng update ngwr@14`, once, from whatever major the app is on — it runs every
|
|
115
|
+
migration between the installed version and 14, in order. Do not step through the
|
|
116
|
+
majors, and never target a 7.x, 8.x or 9.x release: their schematics die with
|
|
117
|
+
`exports is not defined in ES module scope`. It rewrites the renames and REPORTS
|
|
118
|
+
the rest — read its output, because the reported half is where the silent
|
|
119
|
+
failures are.
|
|
111
120
|
|
|
112
121
|
Rewritten for you (six renames, one word per concept):
|
|
113
122
|
|
package/mcp/providers.js
CHANGED
|
@@ -46,8 +46,14 @@ const REQUIRED_PROVIDERS = [
|
|
|
46
46
|
test: /^WrTPipe$|^WrTDirective$|^WrI18n/,
|
|
47
47
|
// Two entry points, and the comment has to say so: the catalog `wrEn` is
|
|
48
48
|
// `ngwr/i18n/en`, not `ngwr/i18n`, which exports neither catalog.
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
//
|
|
50
|
+
// `wrEn` goes in as a BASE and the loader carries only the app's keys. A
|
|
51
|
+
// loader serves only the catalog it is given, so the other way to keep both
|
|
52
|
+
// is a spread, and a spread is shallow: it silently drops one side of every
|
|
53
|
+
// namespace the two catalogs share. This line is what an agent pastes, so it
|
|
54
|
+
// has to be the form that survives the app adding its first `common` key.
|
|
55
|
+
provider: "provideWrI18n() + provideWrI18nBaseCatalogs({ en: wrEn }) + provideWrI18nStaticLoader({ en: { … } }) // from 'ngwr/i18n' + 'ngwr/i18n/en'",
|
|
56
|
+
why: "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",
|
|
51
57
|
},
|
|
52
58
|
];
|
|
53
59
|
export { REQUIRED_PROVIDERS };
|
package/package.json
CHANGED
|
@@ -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])
|
|
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:
|
|
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:
|
|
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
|
-
/**
|
|
206
|
-
|
|
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 =
|
|
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
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
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,
|
|
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:
|
|
39
|
-
{ pattern:
|
|
40
|
-
{ pattern:
|
|
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:
|
|
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:
|
|
33
|
-
{ pattern:
|
|
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
|
-
*
|
|
18
|
-
* plain `[^>]*?` got wrong for two majors: a `>`
|
|
19
|
-
* looks like the end of the tag, so
|
|
20
|
-
* kept its `value` while the same
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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=' },
|
|
@@ -120,6 +120,14 @@
|
|
|
120
120
|
color: var(--wr-color-text-muted);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// The selected option's leading visual on a single-mode button trigger. Never
|
|
124
|
+
// shrinks: the label beside it is the part that truncates.
|
|
125
|
+
&__value-leading {
|
|
126
|
+
display: inline-flex;
|
|
127
|
+
flex: none;
|
|
128
|
+
align-items: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
&__chevron {
|
|
124
132
|
--wr-icon-size: 1rem;
|
|
125
133
|
flex: 0 0 auto;
|
|
@@ -268,6 +276,17 @@
|
|
|
268
276
|
user-select: none;
|
|
269
277
|
}
|
|
270
278
|
|
|
279
|
+
// A selected option's leading visual — an avatar, an icon. Pulled toward the
|
|
280
|
+
// chip's start edge so a round avatar reads as the chip's cap rather than as a
|
|
281
|
+
// glyph floating in the padding, and spaced from the label by a little more
|
|
282
|
+
// than the chip's own gap.
|
|
283
|
+
&__chip-leading {
|
|
284
|
+
display: inline-flex;
|
|
285
|
+
flex: none;
|
|
286
|
+
align-items: center;
|
|
287
|
+
margin-inline: -0.25rem 0.125rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
271
290
|
&__chip-label {
|
|
272
291
|
overflow: hidden;
|
|
273
292
|
text-overflow: ellipsis;
|
|
@@ -424,6 +443,14 @@
|
|
|
424
443
|
}
|
|
425
444
|
}
|
|
426
445
|
|
|
446
|
+
// An option's leading visual — a sibling of the label inside the row's flex,
|
|
447
|
+
// so `--wr-option-inline-gap` spaces it like any projected icon.
|
|
448
|
+
&__leading {
|
|
449
|
+
display: inline-flex;
|
|
450
|
+
flex: none;
|
|
451
|
+
align-items: center;
|
|
452
|
+
}
|
|
453
|
+
|
|
427
454
|
// Search-mode filter — out-of-match options collapse but stay in the
|
|
428
455
|
// DOM so registration order survives the panel re-open.
|
|
429
456
|
&--hidden {
|
|
@@ -461,6 +488,13 @@
|
|
|
461
488
|
position: relative;
|
|
462
489
|
}
|
|
463
490
|
|
|
491
|
+
// A virtual row is a block, not a flex row (one line, so it can ellipsize),
|
|
492
|
+
// so the leading visual takes its spacing as a margin instead of the gap.
|
|
493
|
+
.wr-option__leading {
|
|
494
|
+
vertical-align: middle;
|
|
495
|
+
margin-inline-end: var(--wr-option-inline-gap, 0.5rem);
|
|
496
|
+
}
|
|
497
|
+
|
|
464
498
|
// Keyboard cursor highlight — the virtualized list is navigated by
|
|
465
499
|
// aria-activedescendant, so real focus never lands on the row and the tint
|
|
466
500
|
// is all there was. `--wr-color-hover` is translucent and lands within about
|
package/skills/ngwr/SKILL.md
CHANGED
|
@@ -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
|
|
48
|
-
Angular
|
|
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() +
|
|
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
|
|
@@ -171,7 +171,7 @@ root barrel for components.
|
|
|
171
171
|
| `ngwr/scroll` | `import { WrScroll } from 'ngwr/scroll'` | — |
|
|
172
172
|
| `ngwr/segmented` | `import { WrSegmented } from 'ngwr/segmented'` | `wr-segmented` |
|
|
173
173
|
| `ngwr/segmented/testing` | `import { WrSegmentedHarness } from 'ngwr/segmented/testing'` | — |
|
|
174
|
-
| `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `wr-option-group` `wr-option` `wr-select` |
|
|
174
|
+
| `ngwr/select` | `import { WrSelect } from 'ngwr/select'` | `wr-option-group` `ng-template[wrOptionLeading]` `wr-option` `wr-select` |
|
|
175
175
|
| `ngwr/select/testing` | `import { WrOptionHarness } from 'ngwr/select/testing'` | — |
|
|
176
176
|
| `ngwr/shiny-text` | `import { WrShinyText } from 'ngwr/shiny-text'` | `[wrShimmer]` `wr-shiny-text` |
|
|
177
177
|
| `ngwr/shiny-text/testing` | `import { WrShinyTextHarness } from 'ngwr/shiny-text/testing'` | — |
|
|
@@ -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() +
|
|
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
|
|