@ethlete/core 5.0.0-next.7 → 5.0.0-next.70
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/CHANGELOG.md +843 -0
- package/README.md +28 -4
- package/design/interactive-styles.ts +5 -0
- package/design/theme-css.js +392 -0
- package/design/theme-css.ts +547 -0
- package/fesm2022/ethlete-core.mjs +9338 -5667
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/devtools-about/generator.js +122 -0
- package/generators/devtools-about/schema.json +25 -0
- package/generators/generators.json +41 -5
- package/generators/migrate-color-theme-variants/color-theme-variants.js +136 -0
- package/generators/migrate-color-theme-variants/migration.js +46 -0
- package/generators/migrate-color-theme-variants/schema.json +26 -0
- package/generators/migrate-create-destroy/create-destroy.js +317 -0
- package/generators/migrate-create-destroy/migration.js +46 -0
- package/generators/migrate-create-destroy/schema.json +26 -0
- package/generators/migrate-legacy-color-themes/legacy-color-themes.js +123 -0
- package/generators/migrate-legacy-color-themes/migration.js +93 -0
- package/generators/migrate-legacy-color-themes/schema.json +26 -0
- package/generators/migrate-provider-shape/migration-scope.js +35 -0
- package/generators/migrate-provider-shape/migration.js +50 -0
- package/generators/migrate-provider-shape/provider-shape.js +284 -0
- package/generators/migrate-provider-shape/schema.json +26 -0
- package/generators/migrate-surface-interaction-swatch/interaction-swatch.js +63 -0
- package/generators/migrate-surface-interaction-swatch/migration.js +36 -0
- package/generators/migrate-surface-interaction-swatch/schema.json +26 -0
- package/generators/migrate-to-v5/apply-replacements.js +14 -0
- package/generators/migrate-to-v5/color-naming.js +124 -0
- package/generators/migrate-to-v5/create-provider.js +67 -55
- package/generators/migrate-to-v5/migration-files.js +13 -0
- package/generators/migrate-to-v5/migration.js +33 -19
- package/generators/migrate-to-v5/removed-exports.js +161 -0
- package/generators/migrate-to-v5/router-state-service.js +125 -330
- package/generators/migrate-to-v5/schema.json +22 -0
- package/generators/migrate-to-v5/service-classes.js +108 -0
- package/generators/migrate-to-v5/sync-observable-consumers.js +80 -0
- package/generators/migrate-to-v5/viewport-service.js +226 -491
- package/generators/tailwind-4-color-theme/generator.js +411 -0
- package/generators/tailwind-4-color-theme/schema.json +44 -0
- package/generators/tailwind-4-surface-theme/generator.js +379 -0
- package/generators/tailwind-4-surface-theme/schema.json +48 -0
- package/migrations/legacy-color-themes.md +38 -0
- package/migrations/seo-directive-removed.md +81 -0
- package/migrations/surface-theme-regenerate.md +20 -0
- package/migrations.json +59 -0
- package/package.json +38 -21
- package/src/lib/animations/easing-tokens-styles.component.css +155 -0
- package/src/lib/providers/visually-hidden-styles.component.css +13 -0
- package/src/lib/resize-handles/resize-handles.component.css +137 -0
- package/src/lib/theming/color-interactive-styles.component.css +147 -0
- package/src/lib/theming/surface-interactive-styles.component.css +42 -0
- package/types/ethlete-core.d.ts +3842 -2205
- package/generators/tailwind-4-theme/generator.js +0 -514
- package/generators/tailwind-4-theme/schema.json +0 -32
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
"title": "Migrate to Core v5",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to migrate. 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 migrate, relative to the workspace root (e.g. 'apps/web'). Unioned with `projects`. Defaults to the whole workspace.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
7
19
|
"skipFormat": {
|
|
8
20
|
"type": "boolean",
|
|
9
21
|
"description": "Skip formatting files after migration",
|
|
@@ -23,6 +35,16 @@
|
|
|
23
35
|
"type": "boolean",
|
|
24
36
|
"description": "Migrate router state service to v5",
|
|
25
37
|
"default": true
|
|
38
|
+
},
|
|
39
|
+
"migrateColorNaming": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"description": "Migrate theme naming to color naming (ProvideThemeDirective -> ProvideColorDirective, etc.)",
|
|
42
|
+
"default": true
|
|
43
|
+
},
|
|
44
|
+
"reportRemovedExports": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"description": "List imports of exports removed in v5 without a replacement (Memo, the props module)",
|
|
47
|
+
"default": true
|
|
26
48
|
}
|
|
27
49
|
},
|
|
28
50
|
"required": []
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { applyReplacements } from './apply-replacements.js';
|
|
3
|
+
export function getServiceImport(sourceFile, importedName) {
|
|
4
|
+
for (const statement of sourceFile.statements) {
|
|
5
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
6
|
+
continue;
|
|
7
|
+
const bindings = statement.importClause?.namedBindings;
|
|
8
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
9
|
+
continue;
|
|
10
|
+
const element = bindings.elements.find((el) => (el.propertyName?.text ?? el.name.text) === importedName);
|
|
11
|
+
if (element)
|
|
12
|
+
return { localName: element.name.text, packageName: statement.moduleSpecifier.text };
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function collectClasses(sourceFile) {
|
|
17
|
+
const classes = [];
|
|
18
|
+
const visit = (node) => {
|
|
19
|
+
if (ts.isClassDeclaration(node))
|
|
20
|
+
classes.push(node);
|
|
21
|
+
ts.forEachChild(node, visit);
|
|
22
|
+
};
|
|
23
|
+
visit(sourceFile);
|
|
24
|
+
return classes;
|
|
25
|
+
}
|
|
26
|
+
export function isServiceProperty(member, serviceName) {
|
|
27
|
+
const initializer = member.initializer;
|
|
28
|
+
const injectsService = !!initializer &&
|
|
29
|
+
ts.isCallExpression(initializer) &&
|
|
30
|
+
ts.isIdentifier(initializer.expression) &&
|
|
31
|
+
initializer.expression.text === 'inject' &&
|
|
32
|
+
!!initializer.arguments[0] &&
|
|
33
|
+
ts.isIdentifier(initializer.arguments[0]) &&
|
|
34
|
+
initializer.arguments[0].text === serviceName;
|
|
35
|
+
return injectsService || isServiceType(member.type, serviceName);
|
|
36
|
+
}
|
|
37
|
+
export function isServiceParameter(param, serviceName) {
|
|
38
|
+
return ts.isIdentifier(param.name) && isServiceType(param.type, serviceName);
|
|
39
|
+
}
|
|
40
|
+
export function findServiceClasses(sourceFile, serviceName) {
|
|
41
|
+
return collectClasses(sourceFile).flatMap((classNode, index) => {
|
|
42
|
+
const fields = classNode.members.flatMap((member) => {
|
|
43
|
+
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name) && isServiceProperty(member, serviceName)) {
|
|
44
|
+
return [member.name.text];
|
|
45
|
+
}
|
|
46
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
47
|
+
return member.parameters
|
|
48
|
+
.filter((param) => isServiceParameter(param, serviceName))
|
|
49
|
+
.map((param) => param.name.text);
|
|
50
|
+
}
|
|
51
|
+
return [];
|
|
52
|
+
});
|
|
53
|
+
return fields.length > 0 ? [{ index, fields }] : [];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function applyReplacementsInClass(content, sourceFile, classNode, replacements) {
|
|
57
|
+
const start = classNode.getStart(sourceFile);
|
|
58
|
+
const end = classNode.getEnd();
|
|
59
|
+
return content.slice(0, start) + applyReplacements(content.slice(start, end), replacements) + content.slice(end);
|
|
60
|
+
}
|
|
61
|
+
export function removeServiceImport(sourceFile, content, importedName) {
|
|
62
|
+
for (const statement of sourceFile.statements) {
|
|
63
|
+
if (!ts.isImportDeclaration(statement))
|
|
64
|
+
continue;
|
|
65
|
+
const bindings = statement.importClause?.namedBindings;
|
|
66
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
67
|
+
continue;
|
|
68
|
+
const elements = bindings.elements;
|
|
69
|
+
const index = elements.findIndex((el) => (el.propertyName?.text ?? el.name.text) === importedName);
|
|
70
|
+
if (index === -1)
|
|
71
|
+
continue;
|
|
72
|
+
if (elements.length === 1) {
|
|
73
|
+
const defaultImport = statement.importClause.name;
|
|
74
|
+
if (defaultImport)
|
|
75
|
+
return content.slice(0, defaultImport.getEnd()) + content.slice(bindings.getEnd());
|
|
76
|
+
let lineEnd = statement.getEnd();
|
|
77
|
+
while (lineEnd < content.length && content[lineEnd] !== '\n')
|
|
78
|
+
lineEnd++;
|
|
79
|
+
if (content[lineEnd] === '\n')
|
|
80
|
+
lineEnd++;
|
|
81
|
+
return content.slice(0, statement.getStart(sourceFile)) + content.slice(lineEnd);
|
|
82
|
+
}
|
|
83
|
+
const element = elements[index];
|
|
84
|
+
const next = elements[index + 1];
|
|
85
|
+
const previous = elements[index - 1];
|
|
86
|
+
const [start, end] = next
|
|
87
|
+
? [element.getStart(sourceFile), next.getStart(sourceFile)]
|
|
88
|
+
: [previous.getEnd(), element.getEnd()];
|
|
89
|
+
return content.slice(0, start) + content.slice(end);
|
|
90
|
+
}
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
function isServiceType(type, serviceName) {
|
|
94
|
+
return !!type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === serviceName;
|
|
95
|
+
}
|
|
96
|
+
export function isServiceStillUsed(sourceFile, serviceName, serviceClasses) {
|
|
97
|
+
const classes = collectClasses(sourceFile);
|
|
98
|
+
const referencesField = serviceClasses.some(({ index, fields }) => containsIdentifier(classes[index], fields));
|
|
99
|
+
const referencesService = sourceFile.statements.some((statement) => !ts.isImportDeclaration(statement) && containsIdentifier(statement, [serviceName]));
|
|
100
|
+
return referencesField || referencesService;
|
|
101
|
+
}
|
|
102
|
+
function containsIdentifier(node, names) {
|
|
103
|
+
if (!node)
|
|
104
|
+
return false;
|
|
105
|
+
if (ts.isIdentifier(node) && names.includes(node.text))
|
|
106
|
+
return true;
|
|
107
|
+
return !!ts.forEachChild(node, (child) => containsIdentifier(child, names) || undefined);
|
|
108
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
const MESSAGE = 'reads an observable made by toObservable(), which emits only after its first effect run. Read the signal directly or use computed().';
|
|
3
|
+
export function findSyncObservableConsumers(filePath, originalContent, content) {
|
|
4
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
5
|
+
const isAddedToObservable = (node) => ts.isCallExpression(node) &&
|
|
6
|
+
ts.isIdentifier(node.expression) &&
|
|
7
|
+
node.expression.text === 'toObservable' &&
|
|
8
|
+
!originalContent.includes(node.getText());
|
|
9
|
+
const containsAddedToObservable = (node) => isAddedToObservable(node) || (ts.forEachChild(node, containsAddedToObservable) ?? false);
|
|
10
|
+
const asyncNames = new Set();
|
|
11
|
+
const collectNames = (node) => {
|
|
12
|
+
if ((ts.isPropertyDeclaration(node) || ts.isVariableDeclaration(node)) &&
|
|
13
|
+
ts.isIdentifier(node.name) &&
|
|
14
|
+
node.initializer &&
|
|
15
|
+
containsAddedToObservable(node.initializer)) {
|
|
16
|
+
asyncNames.add(node.name.text);
|
|
17
|
+
}
|
|
18
|
+
ts.forEachChild(node, collectNames);
|
|
19
|
+
};
|
|
20
|
+
collectNames(sourceFile);
|
|
21
|
+
const isAsyncSource = (expression) => {
|
|
22
|
+
let root = expression;
|
|
23
|
+
while (ts.isCallExpression(root) && ts.isPropertyAccessExpression(root.expression)) {
|
|
24
|
+
root = root.expression.expression;
|
|
25
|
+
}
|
|
26
|
+
if (isAddedToObservable(root))
|
|
27
|
+
return true;
|
|
28
|
+
if (ts.isIdentifier(root))
|
|
29
|
+
return asyncNames.has(root.text);
|
|
30
|
+
return ts.isPropertyAccessExpression(root) && root.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
31
|
+
? asyncNames.has(root.name.text)
|
|
32
|
+
: false;
|
|
33
|
+
};
|
|
34
|
+
const findings = [];
|
|
35
|
+
const visit = (node) => {
|
|
36
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
37
|
+
const [source, options] = node.arguments;
|
|
38
|
+
const name = node.expression.text;
|
|
39
|
+
if (name === 'toSignal' && source && options && requiresSync(options) && isAsyncSource(source)) {
|
|
40
|
+
findings.push({ node, label: `toSignal(${flatText(source)}, { requireSync: true })` });
|
|
41
|
+
}
|
|
42
|
+
if (name === 'withLatestFrom') {
|
|
43
|
+
for (const argument of node.arguments.filter(isAsyncSource)) {
|
|
44
|
+
findings.push({ node: argument, label: `withLatestFrom(${flatText(argument)})` });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (ts.isCallExpression(node) &&
|
|
49
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
50
|
+
node.expression.name.text === 'pipe' &&
|
|
51
|
+
isAsyncSource(node.expression.expression)) {
|
|
52
|
+
for (const operator of node.arguments.filter(isFirstValueOperator)) {
|
|
53
|
+
findings.push({ node: operator, label: flatText(operator) });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ts.forEachChild(node, visit);
|
|
57
|
+
};
|
|
58
|
+
visit(sourceFile);
|
|
59
|
+
return findings
|
|
60
|
+
.sort((a, b) => a.node.getStart() - b.node.getStart())
|
|
61
|
+
.map(({ node, label }) => {
|
|
62
|
+
const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
63
|
+
return `${filePath}:${line}: ${label} ${MESSAGE}`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function requiresSync(options) {
|
|
67
|
+
return (ts.isObjectLiteralExpression(options) &&
|
|
68
|
+
options.properties.some((property) => ts.isPropertyAssignment(property) &&
|
|
69
|
+
property.name.getText() === 'requireSync' &&
|
|
70
|
+
property.initializer.kind === ts.SyntaxKind.TrueKeyword));
|
|
71
|
+
}
|
|
72
|
+
function isFirstValueOperator(operator) {
|
|
73
|
+
if (!ts.isCallExpression(operator) || !ts.isIdentifier(operator.expression))
|
|
74
|
+
return false;
|
|
75
|
+
const name = operator.expression.text;
|
|
76
|
+
return name === 'first' || (name === 'take' && operator.arguments[0]?.getText() === '1');
|
|
77
|
+
}
|
|
78
|
+
function flatText(node) {
|
|
79
|
+
return node.getText().replace(/\s+/g, ' ');
|
|
80
|
+
}
|