@ethlete/components 1.0.0-next.66 → 1.0.0-next.68

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 (31) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/fesm2022/ethlete-components-testing.mjs +524 -0
  3. package/fesm2022/ethlete-components-testing.mjs.map +1 -0
  4. package/fesm2022/ethlete-components.mjs +5846 -2918
  5. package/fesm2022/ethlete-components.mjs.map +1 -1
  6. package/generators/generators.json +35 -0
  7. package/generators/migrate-cascader-tree-internals/cascader-tree-internals.js +6 -0
  8. package/generators/migrate-cascader-tree-internals/migration.js +11 -0
  9. package/generators/migrate-cascader-tree-internals/schema.json +26 -0
  10. package/generators/migrate-duration-format-internals/duration-format-internals.js +6 -0
  11. package/generators/migrate-duration-format-internals/migration.js +11 -0
  12. package/generators/migrate-duration-format-internals/schema.json +26 -0
  13. package/generators/migrate-error-color-theme-signal/error-color-theme-signal.js +37 -0
  14. package/generators/migrate-error-color-theme-signal/migration.js +55 -0
  15. package/generators/migrate-error-color-theme-signal/schema.json +26 -0
  16. package/generators/migrate-line-chart-stacked-area-mix/line-chart-stacked-area-mix.js +48 -0
  17. package/generators/migrate-line-chart-stacked-area-mix/migration.js +35 -0
  18. package/generators/migrate-line-chart-stacked-area-mix/schema.json +26 -0
  19. package/generators/migrate-loader-determinate/loader-determinate.js +87 -0
  20. package/generators/migrate-loader-determinate/migration.js +49 -0
  21. package/generators/migrate-loader-determinate/schema.json +26 -0
  22. package/generators/migrate-select-input-renames/migration.js +41 -0
  23. package/generators/migrate-select-input-renames/schema.json +26 -0
  24. package/generators/migrate-select-input-renames/select-input-renames.js +37 -0
  25. package/generators/migrate-time-picker-ring/migration.js +17 -0
  26. package/generators/migrate-time-picker-ring/schema.json +26 -0
  27. package/generators/migrate-time-picker-ring/time-picker-ring.js +25 -0
  28. package/migrations.json +75 -0
  29. package/package.json +17 -17
  30. package/types/ethlete-components-testing.d.ts +311 -0
  31. package/types/ethlete-components.d.ts +8779 -9694
@@ -55,6 +55,11 @@
55
55
  "schema": "./migrate-chart-baseline-y/schema.json",
56
56
  "description": "Rename the removed chart-grid baselineY alias to baseline"
57
57
  },
58
+ "migrate-line-chart-stacked-area-mix": {
59
+ "factory": "./migrate-line-chart-stacked-area-mix/migration",
60
+ "schema": "./migrate-line-chart-stacked-area-mix/schema.json",
61
+ "description": "Rename the removed --et-line-chart-stacked-area-opacity token to --et-line-chart-stacked-area-mix"
62
+ },
58
63
  "migrate-grid-engine-internals": {
59
64
  "factory": "./migrate-grid-engine-internals/migration",
60
65
  "schema": "./migrate-grid-engine-internals/schema.json",
@@ -65,6 +70,36 @@
65
70
  "schema": "./migrate-overlay-fullscreen-animation/schema.json",
66
71
  "description": "Remove the overlay fullscreen animation internals from @ethlete/components imports and mark their uses"
67
72
  },
73
+ "migrate-cascader-tree-internals": {
74
+ "factory": "./migrate-cascader-tree-internals/migration",
75
+ "schema": "./migrate-cascader-tree-internals/schema.json",
76
+ "description": "Remove the cascader tree helpers from @ethlete/components imports and mark their uses"
77
+ },
78
+ "migrate-duration-format-internals": {
79
+ "factory": "./migrate-duration-format-internals/migration",
80
+ "schema": "./migrate-duration-format-internals/schema.json",
81
+ "description": "Remove the duration format helpers from @ethlete/components imports and mark their uses"
82
+ },
83
+ "migrate-time-picker-ring": {
84
+ "factory": "./migrate-time-picker-ring/migration",
85
+ "schema": "./migrate-time-picker-ring/schema.json",
86
+ "description": "Replace the removed time picker setActiveSide with activeSide.set and mark the removed column and option directives"
87
+ },
88
+ "migrate-error-color-theme-signal": {
89
+ "factory": "./migrate-error-color-theme-signal/migration",
90
+ "schema": "./migrate-error-color-theme-signal/schema.json",
91
+ "description": "Call the errorColorTheme signal of injectFormSupport() and TableComponent where it is read"
92
+ },
93
+ "migrate-select-input-renames": {
94
+ "factory": "./migrate-select-input-renames/migration",
95
+ "schema": "./migrate-select-input-renames/schema.json",
96
+ "description": "Rename the select and tag input inputs that changed name in templates"
97
+ },
98
+ "migrate-loader-determinate": {
99
+ "factory": "./migrate-loader-determinate/migration",
100
+ "schema": "./migrate-loader-determinate/schema.json",
101
+ "description": "Remove the spinner determinate and progress bar indeterminate inputs in templates"
102
+ },
68
103
  "icons": {
69
104
  "factory": "./icons/generator",
70
105
  "schema": "./icons/schema.json",
@@ -0,0 +1,6 @@
1
+ const NAMES = ['toChildrenObservable', 'toSearchObservable', 'toPathObservable', 'nodesEqual', 'indexOfNode'];
2
+ export const CASCADER_TREE_INTERNALS_REMOVALS = NAMES.map((name) => ({
3
+ name,
4
+ todo: `${name} is no longer exported; it is a cascader internal. A data source may return an array, a Promise or an Observable as it is, and node equality is the compareWith of the cascader applied to node.value.`,
5
+ }));
6
+ //# sourceMappingURL=cascader-tree-internals.js.map
@@ -0,0 +1,11 @@
1
+ import { runRemovedExportsMigration, } from '../removed-exports/run-removed-exports-migration.js';
2
+ import { CASCADER_TREE_INTERNALS_REMOVALS } from './cascader-tree-internals.js';
3
+ export const CASCADER_TREE_INTERNALS_REPORT_PATH = 'cascader-tree-internals-migration-tasks.md';
4
+ export default async function migrateCascaderTreeInternals(tree, schema) {
5
+ await runRemovedExportsMigration(tree, schema, {
6
+ title: 'Removing the cascader tree helpers from @ethlete/components',
7
+ removed: CASCADER_TREE_INTERNALS_REMOVALS,
8
+ reportPath: CASCADER_TREE_INTERNALS_REPORT_PATH,
9
+ });
10
+ }
11
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-cascader-tree-internals",
4
+ "title": "Remove the cascader tree helpers from @ethlete/components and mark the uses it cannot remove",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,6 @@
1
+ const NAMES = ['UNIT_MS', 'deriveDurationFormatSpec', 'formatDuration', 'parseDuration'];
2
+ export const DURATION_FORMAT_INTERNALS_REMOVALS = NAMES.map((name) => ({
3
+ name,
4
+ todo: `${name} is no longer exported; it is a duration input internal. Bind a duration input with a durationFormat, or format and parse the milliseconds in the app.`,
5
+ }));
6
+ //# sourceMappingURL=duration-format-internals.js.map
@@ -0,0 +1,11 @@
1
+ import { runRemovedExportsMigration, } from '../removed-exports/run-removed-exports-migration.js';
2
+ import { DURATION_FORMAT_INTERNALS_REMOVALS } from './duration-format-internals.js';
3
+ export const DURATION_FORMAT_INTERNALS_REPORT_PATH = 'duration-format-internals-migration-tasks.md';
4
+ export default async function migrateDurationFormatInternals(tree, schema) {
5
+ await runRemovedExportsMigration(tree, schema, {
6
+ title: 'Removing the duration format helpers from @ethlete/components',
7
+ removed: DURATION_FORMAT_INTERNALS_REMOVALS,
8
+ reportPath: DURATION_FORMAT_INTERNALS_REPORT_PATH,
9
+ });
10
+ }
11
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-duration-format-internals",
4
+ "title": "Remove the duration format helpers from @ethlete/components and mark the uses it cannot remove",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,37 @@
1
+ const IDENTIFIER = '[A-Za-z_$][\\w$]*';
2
+ const ASSIGNED = `\\s*[!?]?\\s*(?::[^=;\\n]*)?=\\s*`;
3
+ const RECEIVER_PATTERNS = [
4
+ new RegExp(`(${IDENTIFIER})${ASSIGNED}injectFormSupport\\s*\\(`, 'g'),
5
+ new RegExp(`(${IDENTIFIER})\\s*[!?]?\\s*:\\s*(?:Readonly\\s*<\\s*)?FormSupport\\b`, 'g'),
6
+ new RegExp(`(${IDENTIFIER})\\s*[!?]?\\s*:\\s*TableComponent\\b`, 'g'),
7
+ new RegExp(`(${IDENTIFIER})${ASSIGNED}(?:viewChild|contentChild|inject)(?:\\s*\\.\\s*required)?\\s*(?:<[^>]*>)?\\s*\\(\\s*TableComponent\\b`, 'g'),
8
+ new RegExp(`<et-table\\b[^>]*?\\s#(${IDENTIFIER})`, 'g'),
9
+ ];
10
+ const NOT_CALLED_OR_ASSIGNED = '(?![\\w$])(?!\\s*(?:\\(|=(?!=)))';
11
+ const MEMBER_ACCESS = '(?:\\s*\\(\\s*\\))?\\s*!?\\s*(?:\\?\\.|\\.)\\s*';
12
+ const DIRECT_READ = new RegExp(`(injectFormSupport\\s*\\(\\s*\\)${MEMBER_ACCESS})errorColorTheme${NOT_CALLED_OR_ASSIGNED}`, 'g');
13
+ const ANY_READ = new RegExp(`\\.\\s*errorColorTheme${NOT_CALLED_OR_ASSIGNED}`);
14
+ const escape = (value) => value.replace(/[$]/g, '\\$&');
15
+ /** Returns the names a file binds to an `injectFormSupport()` result or a `TableComponent` instance, including `<et-table #ref>` template refs. */
16
+ export const collectErrorColorThemeReceivers = (content) => {
17
+ const names = new Set();
18
+ for (const pattern of RECEIVER_PATTERNS) {
19
+ for (const match of content.matchAll(pattern)) {
20
+ if (match[1])
21
+ names.add(match[1]);
22
+ }
23
+ }
24
+ return names;
25
+ };
26
+ /** Returns the source or template with every read of `errorColorTheme` on a known receiver turned into a call, or `null` when there is none. */
27
+ export const callErrorColorTheme = (content, receivers) => {
28
+ let next = content.replace(DIRECT_READ, '$1errorColorTheme()');
29
+ for (const name of receivers) {
30
+ const read = new RegExp(`(?<![\\w$.])((?:this\\s*\\.\\s*)?${escape(name)}${MEMBER_ACCESS})errorColorTheme${NOT_CALLED_OR_ASSIGNED}`, 'g');
31
+ next = next.replace(read, '$1errorColorTheme()');
32
+ }
33
+ return next === content ? null : next;
34
+ };
35
+ /** Whether the content still reads an `errorColorTheme` member without calling it. */
36
+ export const hasUncalledErrorColorTheme = (content) => ANY_READ.test(content);
37
+ //# sourceMappingURL=error-color-theme-signal.js.map
@@ -0,0 +1,55 @@
1
+ import { formatFiles, joinPathFragments } from '@nx/devkit';
2
+ import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
3
+ import { callErrorColorTheme, collectErrorColorThemeReceivers, hasUncalledErrorColorTheme, } from './error-color-theme-signal.js';
4
+ const componentSourcesOf = (tree, templatePath) => {
5
+ const separator = templatePath.lastIndexOf('/');
6
+ const directory = separator === -1 ? '' : templatePath.slice(0, separator);
7
+ const templateName = templatePath.slice(separator + 1);
8
+ return tree
9
+ .children(directory)
10
+ .filter((name) => name.endsWith('.ts') && !name.endsWith('.d.ts'))
11
+ .map((name) => tree.read(joinPathFragments(directory, name), 'utf-8') ?? '')
12
+ .filter((source) => source.includes(templateName));
13
+ };
14
+ const receiversFor = (tree, filePath, content) => {
15
+ const receivers = collectErrorColorThemeReceivers(content);
16
+ if (filePath.endsWith('.html')) {
17
+ for (const source of componentSourcesOf(tree, filePath)) {
18
+ for (const name of collectErrorColorThemeReceivers(source))
19
+ receivers.add(name);
20
+ }
21
+ }
22
+ return receivers;
23
+ };
24
+ export default async function migrateErrorColorThemeSignal(tree, schema) {
25
+ console.log('\nšŸ”Ž Calling the errorColorTheme signal of injectFormSupport() and TableComponent...');
26
+ const scope = createMigrationScope(tree, schema);
27
+ console.log(` Scope: ${scope.describe()}`);
28
+ const changed = [];
29
+ scope.visit(tree, (filePath) => {
30
+ if (!/\.(ts|html)$/.test(filePath) || filePath.endsWith('.d.ts'))
31
+ return;
32
+ const content = tree.read(filePath, 'utf-8');
33
+ if (!content?.includes('errorColorTheme'))
34
+ return;
35
+ const receivers = receiversFor(tree, filePath, content);
36
+ const next = callErrorColorTheme(content, receivers);
37
+ if (next !== null) {
38
+ tree.write(filePath, next);
39
+ changed.push(filePath);
40
+ }
41
+ if (receivers.size > 0 && hasUncalledErrorColorTheme(next ?? content)) {
42
+ console.warn(`\nāš ļø ${filePath} still reads an errorColorTheme it could not tie to injectFormSupport() or a TableComponent. If it is one of those, call it: errorColorTheme().`);
43
+ }
44
+ });
45
+ if (!schema.skipFormat) {
46
+ await formatFiles(tree);
47
+ }
48
+ if (changed.length > 0) {
49
+ console.log(`\nāœ… Called errorColorTheme in ${changed.length} file(s).`);
50
+ }
51
+ else {
52
+ console.log('\nāœ… Nothing to do.');
53
+ }
54
+ }
55
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-error-color-theme-signal",
4
+ "title": "Call the errorColorTheme signal of injectFormSupport() and TableComponent where it is read",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,48 @@
1
+ const OLD_TOKEN = '--et-line-chart-stacked-area-opacity';
2
+ const NEW_TOKEN = '--et-line-chart-stacked-area-mix';
3
+ const OCCURRENCE = new RegExp([
4
+ `var\\(\\s*${OLD_TOKEN}\\s*(?:(?<fallbackLead>,\\s*)(?<fallback>[^()]*?)(?<fallbackTail>\\s*))?\\)`,
5
+ `${OLD_TOKEN}(?<colon>\\s*:\\s*)(?<value>[^;}\\n"'\`]*)`,
6
+ OLD_TOKEN,
7
+ ].join('|'), 'g');
8
+ const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
9
+ const PERCENTAGE = /^[+-]?(?:\d+\.?\d*|\.\d+)%$/;
10
+ export const opacityToMix = (value) => {
11
+ const trimmed = value.trim();
12
+ if (NUMBER.test(trimmed))
13
+ return `${Math.round(Number(trimmed) * 100)}%`;
14
+ if (PERCENTAGE.test(trimmed))
15
+ return trimmed;
16
+ return null;
17
+ };
18
+ const lineAt = (content, index) => content.slice(0, index).split('\n').length;
19
+ /** Renames every `--et-line-chart-stacked-area-opacity` to `--et-line-chart-stacked-area-mix` and maps its values, or returns `null` when there is none. */
20
+ export const migrateStackedAreaMix = (content) => {
21
+ if (!content.includes(OLD_TOKEN))
22
+ return null;
23
+ const unmapped = [];
24
+ const next = content.replace(OCCURRENCE, (match, ...args) => {
25
+ const index = args.at(-3);
26
+ const groups = args.at(-1);
27
+ if (match.startsWith('var(')) {
28
+ if (groups['fallback'] === undefined)
29
+ return `var(${NEW_TOKEN})`;
30
+ const mapped = opacityToMix(groups['fallback']);
31
+ if (mapped === null)
32
+ unmapped.push(lineAt(content, index));
33
+ return `var(${NEW_TOKEN}${groups['fallbackLead']}${mapped ?? groups['fallback']}${groups['fallbackTail']})`;
34
+ }
35
+ if (groups['colon'] !== undefined) {
36
+ const value = groups['value'] ?? '';
37
+ const trimmed = value.trimEnd();
38
+ const mapped = opacityToMix(trimmed);
39
+ if (mapped === null)
40
+ unmapped.push(lineAt(content, index));
41
+ return `${NEW_TOKEN}${groups['colon']}${mapped ?? trimmed}${value.slice(trimmed.length)}`;
42
+ }
43
+ unmapped.push(lineAt(content, index));
44
+ return NEW_TOKEN;
45
+ });
46
+ return { content: next, unmapped };
47
+ };
48
+ //# sourceMappingURL=line-chart-stacked-area-mix.js.map
@@ -0,0 +1,35 @@
1
+ import { formatFiles } from '@nx/devkit';
2
+ import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
3
+ import { migrateStackedAreaMix } from './line-chart-stacked-area-mix.js';
4
+ const EXTENSIONS = ['.css', '.scss', '.html', '.ts'];
5
+ export default async function migrateLineChartStackedAreaMix(tree, schema) {
6
+ console.log('\nšŸ”Ž Renaming --et-line-chart-stacked-area-opacity to --et-line-chart-stacked-area-mix...');
7
+ const scope = createMigrationScope(tree, schema);
8
+ console.log(` Scope: ${scope.describe()}`);
9
+ const changed = [];
10
+ scope.visit(tree, (filePath) => {
11
+ if (!EXTENSIONS.some((extension) => filePath.endsWith(extension)) || filePath.endsWith('.d.ts'))
12
+ return;
13
+ const content = tree.read(filePath, 'utf-8');
14
+ if (!content)
15
+ return;
16
+ const result = migrateStackedAreaMix(content);
17
+ if (result === null)
18
+ return;
19
+ tree.write(filePath, result.content);
20
+ changed.push(filePath);
21
+ for (const line of result.unmapped) {
22
+ console.warn(`\nāš ļø ${filePath}:${line} now reads --et-line-chart-stacked-area-mix, but its value could not be mapped and was left as is. The token is a percentage (default 45%), the share of the series color in a mix with the surface: an opacity v becomes v * 100%. See the line chart guide: /components/line-chart.`);
23
+ }
24
+ });
25
+ if (!schema.skipFormat) {
26
+ await formatFiles(tree);
27
+ }
28
+ if (changed.length > 0) {
29
+ console.log(`\nāœ… Renamed the stacked area token in ${changed.length} file(s).`);
30
+ }
31
+ else {
32
+ console.log('\nāœ… Nothing to do.');
33
+ }
34
+ }
35
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-line-chart-stacked-area-mix",
4
+ "title": "Rename the removed --et-line-chart-stacked-area-opacity token to --et-line-chart-stacked-area-mix",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,87 @@
1
+ import { ATTRIBUTE, START_TAG } from '../migrate-select-input-renames/select-input-renames.js';
2
+ const LOADERS = {
3
+ 'et-spinner': { flag: 'determinate', flagMeansDeterminate: true },
4
+ 'et-progress-bar': { flag: 'indeterminate', flagMeansDeterminate: false },
5
+ };
6
+ const SIMPLE_EXPRESSION = /^!?[\w$.]+(\(\))?$/;
7
+ const parseAttributes = (attributes) => [...attributes.matchAll(ATTRIBUTE)].map((match) => {
8
+ const rawName = match[2] ?? '';
9
+ const rawValue = (match[3] ?? '').replace(/^\s*=\s*/, '');
10
+ const expression = /^(["'])([\s\S]*)\1$/.exec(rawValue)?.[2] ?? rawValue;
11
+ const bracketed = /^\[([^\]]+)\]$/.exec(rawName)?.[1];
12
+ if (bracketed)
13
+ return { text: match[0], name: bracketed, bound: true, expression: expression.trim() };
14
+ if (rawName.startsWith('bind-')) {
15
+ return { text: match[0], name: rawName.slice(5), bound: true, expression: expression.trim() };
16
+ }
17
+ return { text: match[0], name: rawName, bound: false, expression };
18
+ });
19
+ const readFlag = (attribute) => {
20
+ if (!attribute.bound)
21
+ return { kind: 'constant', on: attribute.expression !== 'false' };
22
+ if (attribute.expression === 'true' || attribute.expression === 'false') {
23
+ return { kind: 'constant', on: attribute.expression === 'true' };
24
+ }
25
+ return { kind: 'dynamic', expression: attribute.expression };
26
+ };
27
+ const valueExpression = (attribute) => {
28
+ if (attribute.bound)
29
+ return attribute.expression;
30
+ return /^-?\d+(\.\d+)?$/.test(attribute.expression.trim())
31
+ ? attribute.expression.trim()
32
+ : `'${attribute.expression}'`;
33
+ };
34
+ const wrapCondition = (expression) => (SIMPLE_EXPRESSION.test(expression) ? expression : `(${expression})`);
35
+ const wrapBranch = (expression) => (/[?|]/.test(expression) ? `(${expression})` : expression);
36
+ const bindValue = (expression) => expression.includes('"') ? `[value]='${expression}'` : `[value]="${expression}"`;
37
+ export const migrateLoaderDeterminate = (content, options = {}) => {
38
+ const findings = [];
39
+ const next = content.replace(START_TAG, (tag, tagName, attributes, offset) => {
40
+ const loader = LOADERS[tagName];
41
+ if (!loader || (tagName === 'et-spinner' && options.skipSpinner))
42
+ return tag;
43
+ const parsed = parseAttributes(attributes);
44
+ if (parsed.some((attribute) => attribute.name === 'mode'))
45
+ return tag;
46
+ const flagAttribute = parsed.find((attribute) => attribute.name === loader.flag);
47
+ const valueAttribute = parsed.find((attribute) => attribute.name === 'value');
48
+ const line = content.slice(0, offset).split('\n').length;
49
+ const report = (message) => findings.push({ line, message: `<${tagName}> ${message}` });
50
+ let rewritten = attributes;
51
+ const remove = (attribute) => (rewritten = rewritten.replace(attribute.text, ''));
52
+ const replace = (attribute, expression) => (rewritten = rewritten.replace(attribute.text, `${/^\s+/.exec(attribute.text)?.[0] ?? ' '}${bindValue(expression)}`));
53
+ if (!flagAttribute) {
54
+ if (loader.flagMeansDeterminate && valueAttribute) {
55
+ report('binds value without determinate. A bound value now makes it determinate; drop it to stay indeterminate.');
56
+ }
57
+ if (!loader.flagMeansDeterminate && !valueAttribute) {
58
+ report('has no value binding, so it now renders indeterminate instead of an empty bar. Bind [value].');
59
+ }
60
+ }
61
+ else {
62
+ const flag = readFlag(flagAttribute);
63
+ remove(flagAttribute);
64
+ if (flag.kind === 'constant') {
65
+ const determinate = flag.on === loader.flagMeansDeterminate;
66
+ if (!determinate && valueAttribute)
67
+ remove(valueAttribute);
68
+ if (determinate && !valueAttribute) {
69
+ report(`had a constant ${loader.flag} binding but no value, so it now renders indeterminate. Bind [value].`);
70
+ }
71
+ }
72
+ else if (valueAttribute) {
73
+ const condition = wrapCondition(flag.expression);
74
+ const value = wrapBranch(valueExpression(valueAttribute));
75
+ replace(valueAttribute, loader.flagMeansDeterminate ? `${condition} ? ${value} : undefined` : `${condition} ? undefined : ${value}`);
76
+ }
77
+ else {
78
+ report(`had [${loader.flag}]="${flag.expression}" but no value. It is removed; bind [value] to a number while the loader is determinate and to undefined otherwise.`);
79
+ }
80
+ }
81
+ return rewritten === attributes
82
+ ? tag
83
+ : `<${tagName}${rewritten}${tag.slice(tagName.length + attributes.length + 1)}`;
84
+ });
85
+ return { content: next === content ? null : next, findings };
86
+ };
87
+ //# sourceMappingURL=loader-determinate.js.map
@@ -0,0 +1,49 @@
1
+ import { formatFiles, joinPathFragments } from '@nx/devkit';
2
+ import { dirname } from 'node:path/posix';
3
+ import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
4
+ import { importsEthleteComponents, TEMPLATE_URL } from '../migrate-select-input-renames/select-input-renames.js';
5
+ import { migrateLoaderDeterminate } from './loader-determinate.js';
6
+ const CDK_SPINNER_IMPORT = /\bProgressSpinnerComponent\b[^;]*from\s*['"]@ethlete\/cdk/;
7
+ export default async function migrateLoaderDeterminateInputs(tree, schema) {
8
+ console.log('\nšŸ”Ž Removing the spinner determinate and progress bar indeterminate inputs...');
9
+ const scope = createMigrationScope(tree, schema);
10
+ console.log(` Scope: ${scope.describe()}`);
11
+ const changed = [];
12
+ const findings = [];
13
+ const migrate = (filePath, content, skipSpinner) => {
14
+ const result = migrateLoaderDeterminate(content, { skipSpinner });
15
+ findings.push(...result.findings.map((finding) => ({ ...finding, filePath })));
16
+ if (result.content === null)
17
+ return;
18
+ tree.write(filePath, result.content);
19
+ changed.push(filePath);
20
+ };
21
+ scope.visit(tree, (filePath) => {
22
+ if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
23
+ return;
24
+ const content = tree.read(filePath, 'utf-8');
25
+ if (!content || !importsEthleteComponents(content))
26
+ return;
27
+ const skipSpinner = CDK_SPINNER_IMPORT.test(content);
28
+ for (const match of content.matchAll(TEMPLATE_URL)) {
29
+ const templatePath = joinPathFragments(dirname(filePath), match[1] ?? '');
30
+ const template = tree.read(templatePath, 'utf-8');
31
+ if (template !== null)
32
+ migrate(templatePath, template, skipSpinner);
33
+ }
34
+ migrate(filePath, content, skipSpinner);
35
+ });
36
+ if (!schema.skipFormat) {
37
+ await formatFiles(tree);
38
+ }
39
+ for (const finding of findings) {
40
+ console.warn(`\nāš ļø ${finding.filePath}:${finding.line} ${finding.message} See /components/loader.`);
41
+ }
42
+ if (changed.length > 0) {
43
+ console.log(`\nāœ… Migrated loader bindings in ${changed.length} file(s).`);
44
+ }
45
+ else if (findings.length === 0) {
46
+ console.log('\nāœ… Nothing to do.');
47
+ }
48
+ }
49
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-loader-determinate",
4
+ "title": "Remove the spinner determinate and progress bar indeterminate inputs",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,41 @@
1
+ import { formatFiles, joinPathFragments } from '@nx/devkit';
2
+ import { dirname } from 'node:path/posix';
3
+ import { createMigrationScope } from '../migrate-query-error-labels/migration-scope.js';
4
+ import { importsEthleteComponents, renameSelectInputs, TEMPLATE_URL } from './select-input-renames.js';
5
+ export default async function migrateSelectInputRenames(tree, schema) {
6
+ console.log('\nšŸ”Ž Renaming the select and tag input inputs that changed name...');
7
+ const scope = createMigrationScope(tree, schema);
8
+ console.log(` Scope: ${scope.describe()}`);
9
+ const changed = [];
10
+ const write = (filePath, content) => {
11
+ const next = renameSelectInputs(content);
12
+ if (next === null)
13
+ return;
14
+ tree.write(filePath, next);
15
+ changed.push(filePath);
16
+ };
17
+ scope.visit(tree, (filePath) => {
18
+ if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
19
+ return;
20
+ const content = tree.read(filePath, 'utf-8');
21
+ if (!content || !importsEthleteComponents(content))
22
+ return;
23
+ for (const match of content.matchAll(TEMPLATE_URL)) {
24
+ const templatePath = joinPathFragments(dirname(filePath), match[1] ?? '');
25
+ const template = tree.read(templatePath, 'utf-8');
26
+ if (template !== null)
27
+ write(templatePath, template);
28
+ }
29
+ write(filePath, content);
30
+ });
31
+ if (!schema.skipFormat) {
32
+ await formatFiles(tree);
33
+ }
34
+ if (changed.length > 0) {
35
+ console.log(`\nāœ… Renamed select and tag input inputs in ${changed.length} file(s).`);
36
+ }
37
+ else {
38
+ console.log('\nāœ… Nothing to do.');
39
+ }
40
+ }
41
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "migrate-select-input-renames",
4
+ "title": "Rename the select and tag input inputs that changed name",
5
+ "type": "object",
6
+ "properties": {
7
+ "projects": {
8
+ "type": "array",
9
+ "items": { "type": "string" },
10
+ "description": "Nx projects to scan. Defaults to every project in the workspace.",
11
+ "alias": "project"
12
+ },
13
+ "include": {
14
+ "type": "array",
15
+ "items": { "type": "string" },
16
+ "description": "Path prefixes to scan, relative to the workspace root (e.g. 'apps/shop'). Unioned with `projects`.",
17
+ "alias": "includePath"
18
+ },
19
+ "skipFormat": {
20
+ "type": "boolean",
21
+ "description": "Skip formatting files after migration",
22
+ "default": false
23
+ }
24
+ },
25
+ "required": []
26
+ }
@@ -0,0 +1,37 @@
1
+ const COMPONENTS_IMPORT = /from\s*['"]@ethlete\/components(?:\/[^'"]*)?['"]/;
2
+ export const START_TAG = /<([a-zA-Z][\w-]*)((?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*)\s*\/?>/g;
3
+ export const ATTRIBUTE = /(\s+)([^\s"'>/=]+)((?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)/g;
4
+ export const TEMPLATE_URL = /templateUrl\s*:\s*['"]([^'"]+)['"]/g;
5
+ export const SELECT_INPUT_RENAMES = [
6
+ { hosts: ['et-select', 'etSelect'], from: 'error', to: 'loadError' },
7
+ { hosts: ['et-select', 'etSelect'], from: 'customValueSeparators', to: 'separators' },
8
+ { hosts: ['et-select', 'etSelect'], from: 'normalizeCustomValue', to: 'normalizeValue' },
9
+ { hosts: ['et-tag-input', 'etTagInput'], from: 'maxTags', to: 'maxSelection' },
10
+ { hosts: ['et-tag-input', 'etTagInput'], from: 'normalizeTag', to: 'normalizeValue' },
11
+ ];
12
+ export const importsEthleteComponents = (content) => COMPONENTS_IMPORT.test(content);
13
+ const unwrapBinding = (name) => {
14
+ const bracketed = /^\[([^\]]+)\]$/.exec(name);
15
+ if (bracketed?.[1])
16
+ return { name: bracketed[1], wrap: (inner) => `[${inner}]` };
17
+ if (name.startsWith('bind-'))
18
+ return { name: name.slice(5), wrap: (inner) => `bind-${inner}` };
19
+ return { name, wrap: (inner) => inner };
20
+ };
21
+ /** Returns the template or source with every renamed input binding rewritten on its host element, or `null` when there is none. */
22
+ export const renameSelectInputs = (content, renames = SELECT_INPUT_RENAMES) => {
23
+ const next = content.replace(START_TAG, (tag, tagName, attributes) => {
24
+ const attributeNames = [...attributes.matchAll(ATTRIBUTE)].map((match) => unwrapBinding(match[2] ?? '').name);
25
+ const applicable = renames.filter((rename) => rename.hosts.some((host) => host === tagName || attributeNames.includes(host)));
26
+ if (!applicable.length)
27
+ return tag;
28
+ const renamed = attributes.replace(ATTRIBUTE, (attribute, space, rawName, value) => {
29
+ const binding = unwrapBinding(rawName);
30
+ const rename = applicable.find((candidate) => candidate.from === binding.name);
31
+ return rename ? `${space}${binding.wrap(rename.to)}${value}` : attribute;
32
+ });
33
+ return `<${tagName}${renamed}${tag.slice(tagName.length + attributes.length + 1)}`;
34
+ });
35
+ return next === content ? null : next;
36
+ };
37
+ //# sourceMappingURL=select-input-renames.js.map