@ethlete/core 5.0.0-next.36 ā 5.0.0-next.38
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 +102 -26
- package/README.md +1 -1
- package/fesm2022/ethlete-core.mjs +1247 -605
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/generators.json +5 -0
- package/generators/migrate-provider-shape/migration-scope.js +35 -0
- package/generators/migrate-provider-shape/migration.js +51 -0
- package/generators/migrate-provider-shape/provider-shape.js +217 -0
- package/generators/migrate-provider-shape/schema.json +26 -0
- package/generators/tailwind-4-color-theme/generator.js +4 -4
- package/generators/tailwind-4-surface-theme/generator.js +7 -7
- package/package.json +18 -2
- package/types/ethlete-core.d.ts +3057 -2830
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
"schema": "./migrate-to-v5/schema.json",
|
|
6
6
|
"description": "Migrate to v5"
|
|
7
7
|
},
|
|
8
|
+
"migrate-provider-shape": {
|
|
9
|
+
"factory": "./migrate-provider-shape/migration",
|
|
10
|
+
"schema": "./migrate-provider-shape/schema.json",
|
|
11
|
+
"description": "Rewrite destructured provider tuples into the tree-shakeable definition shape"
|
|
12
|
+
},
|
|
8
13
|
"tailwind-4-color-theme": {
|
|
9
14
|
"factory": "./tailwind-4-color-theme/generator",
|
|
10
15
|
"schema": "./tailwind-4-color-theme/schema.json",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getProjects, visitNotIgnoredFiles } from '@nx/devkit';
|
|
2
|
+
const stripTrailingSlash = (value) => (value.endsWith('/') ? value.slice(0, -1) : value);
|
|
3
|
+
/**
|
|
4
|
+
* Limits the migration to a set of projects or paths. Unlike the query v3 migration there is no
|
|
5
|
+
* cross-file coupling here - every provider declaration is rewritten on its own - so any scope is safe;
|
|
6
|
+
* this exists to keep the diff reviewable.
|
|
7
|
+
*/
|
|
8
|
+
export const createMigrationScope = (tree, options) => {
|
|
9
|
+
const roots = [];
|
|
10
|
+
for (const projectName of options.projects ?? []) {
|
|
11
|
+
const project = getProjects(tree).get(projectName);
|
|
12
|
+
if (!project) {
|
|
13
|
+
throw new Error(`Unknown project "${projectName}". Pass names as they appear in \`nx show projects\`, or use --include with a path prefix.`);
|
|
14
|
+
}
|
|
15
|
+
roots.push(stripTrailingSlash(project.root));
|
|
16
|
+
}
|
|
17
|
+
for (const includePath of options.include ?? []) {
|
|
18
|
+
roots.push(stripTrailingSlash(includePath));
|
|
19
|
+
}
|
|
20
|
+
if (roots.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
visit: (targetTree, callback) => visitNotIgnoredFiles(targetTree, '', callback),
|
|
23
|
+
describe: () => 'the whole workspace',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
visit: (targetTree, callback) => {
|
|
28
|
+
for (const root of roots) {
|
|
29
|
+
visitNotIgnoredFiles(targetTree, root, callback);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
describe: () => roots.join(', '),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=migration-scope.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from './migration-scope.js';
|
|
3
|
+
import { migrateProviderShapeInFile } from './provider-shape.js';
|
|
4
|
+
export const PROVIDER_SHAPE_REPORT_PATH = 'provider-shape-migration-tasks.md';
|
|
5
|
+
const renderReport = (tasks) => [
|
|
6
|
+
'# Provider-shape migration tasks',
|
|
7
|
+
'',
|
|
8
|
+
'The codemod rewrote every destructured provider declaration into a definition plus one extractor per',
|
|
9
|
+
'binding. The sites below are **not** plain module-scope declarations, so they were left alone - each',
|
|
10
|
+
'needs a decision a codemod cannot make.',
|
|
11
|
+
'',
|
|
12
|
+
'Recipes:',
|
|
13
|
+
'',
|
|
14
|
+
'- A factory of your own that returned a `[provide, inject, token]` tuple: return the definition from',
|
|
15
|
+
' `define*Provider(ā¦)` directly, and let its callers use the extractors.',
|
|
16
|
+
'- A call inside a function: read `.provide` / `.inject` / `.token` off the definition.',
|
|
17
|
+
'',
|
|
18
|
+
...tasks.map((task) => [`## \`${task.id}\``, '', `- ${task.file}:${task.line}`, `- ${task.message}`, ''].join('\n')),
|
|
19
|
+
].join('\n');
|
|
20
|
+
export default async function migrateProviderShape(tree, schema) {
|
|
21
|
+
console.log('\nš Migrating provider tuples to the definition shape...');
|
|
22
|
+
const scope = createMigrationScope(tree, schema);
|
|
23
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
24
|
+
const tasks = [];
|
|
25
|
+
let filesChanged = 0;
|
|
26
|
+
scope.visit(tree, (filePath) => {
|
|
27
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
|
|
28
|
+
return;
|
|
29
|
+
const before = tree.read(filePath, 'utf-8');
|
|
30
|
+
if (!before)
|
|
31
|
+
return;
|
|
32
|
+
const result = migrateProviderShapeInFile(filePath, before);
|
|
33
|
+
tasks.push(...result.tasks);
|
|
34
|
+
if (result.changed) {
|
|
35
|
+
tree.write(filePath, result.content);
|
|
36
|
+
filesChanged++;
|
|
37
|
+
console.log(` ā ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (tasks.length > 0) {
|
|
41
|
+
tree.write(PROVIDER_SHAPE_REPORT_PATH, renderReport(tasks));
|
|
42
|
+
}
|
|
43
|
+
if (!schema.skipFormat) {
|
|
44
|
+
await formatFiles(tree);
|
|
45
|
+
}
|
|
46
|
+
console.log(`\nā
Rewrote ${filesChanged} file(s).`);
|
|
47
|
+
if (tasks.length > 0) {
|
|
48
|
+
console.log(`ā ļø ${tasks.length} site(s) need a manual decision - see ${PROVIDER_SHAPE_REPORT_PATH}.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* `export const [provideX, injectX] = createRootProvider(ā¦)` is undroppable by construction: array
|
|
4
|
+
* destructuring invokes the iterator protocol, so no bundler will remove the statement, and everything
|
|
5
|
+
* the factory's closure names is retained with it. The definition shape is droppable per binding.
|
|
6
|
+
*
|
|
7
|
+
* Two families need rewriting. The `create*Provider` / `createLabels` helpers were replaced by
|
|
8
|
+
* `define*`, and their declarations take a pure annotation because they sit in library source. The
|
|
9
|
+
* runtime factories below kept their names - only their return shape changed from tuple to definition -
|
|
10
|
+
* and their call sites are application code, where an annotation would be noise.
|
|
11
|
+
*/
|
|
12
|
+
const FACTORY_TO_DEFINE = {
|
|
13
|
+
createProvider: 'defineProvider',
|
|
14
|
+
createRootProvider: 'defineRootProvider',
|
|
15
|
+
createStaticProvider: 'defineStaticProvider',
|
|
16
|
+
createStaticRootProvider: 'defineStaticRootProvider',
|
|
17
|
+
createLabels: 'defineLabels',
|
|
18
|
+
};
|
|
19
|
+
const REF_FACTORIES = new Set([
|
|
20
|
+
'createQueryClient',
|
|
21
|
+
'createBearerAuthProvider',
|
|
22
|
+
'createWebSocketClient',
|
|
23
|
+
'createQueryContext',
|
|
24
|
+
]);
|
|
25
|
+
const isKnownFactory = (name) => FACTORY_TO_DEFINE[name] !== undefined || REF_FACTORIES.has(name);
|
|
26
|
+
/** Which extractor names the three tuple positions become. */
|
|
27
|
+
const POSITION_TO_EXTRACTOR = ['toProvideFn', 'toInjectFn', 'toToken'];
|
|
28
|
+
const PURE = '/* @__PURE__ */';
|
|
29
|
+
/** `injectFooBar` / `provideFooBar` / `FOO_BAR_TOKEN` ā `FOO_BAR`. */
|
|
30
|
+
const toDefinitionBaseName = (bindingName) => {
|
|
31
|
+
const withoutPrefix = bindingName
|
|
32
|
+
.replace(/^ɵ/, '')
|
|
33
|
+
.replace(/^(provide|inject)/i, '')
|
|
34
|
+
.replace(/_TOKEN$/, '');
|
|
35
|
+
const screaming = withoutPrefix
|
|
36
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
37
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
38
|
+
.toUpperCase();
|
|
39
|
+
return screaming.replace(/^_+|_+$/g, '') || 'PROVIDER';
|
|
40
|
+
};
|
|
41
|
+
const uniqueDefinitionName = (bindingNames, taken) => {
|
|
42
|
+
const base = `${toDefinitionBaseName(bindingNames[1] ?? bindingNames[0] ?? bindingNames[2] ?? 'provider')}_DEF`;
|
|
43
|
+
let candidate = base;
|
|
44
|
+
let suffix = 2;
|
|
45
|
+
while (taken.has(candidate)) {
|
|
46
|
+
candidate = `${base}_${suffix++}`;
|
|
47
|
+
}
|
|
48
|
+
taken.add(candidate);
|
|
49
|
+
return candidate;
|
|
50
|
+
};
|
|
51
|
+
const findTupleSites = (sourceFile) => {
|
|
52
|
+
const sites = [];
|
|
53
|
+
for (const statement of sourceFile.statements) {
|
|
54
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1)
|
|
55
|
+
continue;
|
|
56
|
+
const declaration = statement.declarationList.declarations[0];
|
|
57
|
+
if (!declaration || !ts.isArrayBindingPattern(declaration.name))
|
|
58
|
+
continue;
|
|
59
|
+
const call = declaration.initializer;
|
|
60
|
+
if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
|
|
61
|
+
continue;
|
|
62
|
+
const factory = call.expression.text;
|
|
63
|
+
if (!isKnownFactory(factory))
|
|
64
|
+
continue;
|
|
65
|
+
const bindings = declaration.name.elements.map((element) => {
|
|
66
|
+
if (ts.isOmittedExpression(element) || !ts.isIdentifier(element.name))
|
|
67
|
+
return null;
|
|
68
|
+
return element.name.text;
|
|
69
|
+
});
|
|
70
|
+
sites.push({
|
|
71
|
+
statement,
|
|
72
|
+
call,
|
|
73
|
+
factory,
|
|
74
|
+
bindings,
|
|
75
|
+
isExported: !!statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return sites;
|
|
79
|
+
};
|
|
80
|
+
/** The offset the replacement starts at: before the declaration's own JSDoc, if it has one. */
|
|
81
|
+
const declarationStart = (content, statement, sourceFile) => {
|
|
82
|
+
const start = statement.getStart(sourceFile);
|
|
83
|
+
const comments = ts.getLeadingCommentRanges(content, statement.getFullStart());
|
|
84
|
+
const first = comments?.[0];
|
|
85
|
+
// Only treat comments as the declaration's own when nothing but whitespace separates them from it.
|
|
86
|
+
if (!first || /\S/.test(content.slice(comments[comments.length - 1].end, start)))
|
|
87
|
+
return start;
|
|
88
|
+
return first.pos;
|
|
89
|
+
};
|
|
90
|
+
const rewriteSite = (content, sourceFile, site, definitionName) => {
|
|
91
|
+
const start = declarationStart(content, site.statement, sourceFile);
|
|
92
|
+
const statementStart = site.statement.getStart(sourceFile);
|
|
93
|
+
const doc = content.slice(start, statementStart);
|
|
94
|
+
const replacement = FACTORY_TO_DEFINE[site.factory];
|
|
95
|
+
const annotation = replacement ? `${PURE} ` : '';
|
|
96
|
+
const callTail = content.slice(site.call.expression.getEnd(), site.call.getEnd());
|
|
97
|
+
const definition = `const ${definitionName} = ${annotation}${replacement ?? site.factory}${callTail};`;
|
|
98
|
+
const exportKeyword = site.isExported ? 'export ' : '';
|
|
99
|
+
const extractions = site.bindings
|
|
100
|
+
.map((binding, index) => binding === null
|
|
101
|
+
? null
|
|
102
|
+
: `${exportKeyword}const ${binding} = ${annotation}${POSITION_TO_EXTRACTOR[index]}(${definitionName});`)
|
|
103
|
+
.filter((line) => line !== null);
|
|
104
|
+
// The JSDoc documented the pair, so it belongs on the first binding a consumer can reach.
|
|
105
|
+
const documented = extractions.length > 0 ? [definition, '', doc + extractions[0], ...extractions.slice(1)] : [doc + definition];
|
|
106
|
+
return { start, end: site.statement.getEnd(), text: documented.join('\n') };
|
|
107
|
+
};
|
|
108
|
+
const rewriteImports = (content, sourceFile, factoriesUsed, extractorsUsed) => {
|
|
109
|
+
const rewrites = [];
|
|
110
|
+
const imports = sourceFile.statements.filter(ts.isImportDeclaration);
|
|
111
|
+
/** The declaration the extractors join: the one that already provides them, else any core import. */
|
|
112
|
+
const extractorHost = imports.find((statement) => namedImportsOf(statement, sourceFile)?.some((name) => extractorsUsed.has(name))) ??
|
|
113
|
+
imports.find((statement) => {
|
|
114
|
+
const names = namedImportsOf(statement, sourceFile);
|
|
115
|
+
return names?.some((name) => FACTORY_TO_DEFINE[name] !== undefined) || isCoreImport(statement);
|
|
116
|
+
});
|
|
117
|
+
for (const statement of imports) {
|
|
118
|
+
const bindings = statement.importClause?.namedBindings;
|
|
119
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
120
|
+
continue;
|
|
121
|
+
const names = bindings.elements.map((element) => element.getText(sourceFile));
|
|
122
|
+
// A ref factory keeps its name and its import; only a replaced factory is swapped out.
|
|
123
|
+
const replaced = names.filter((name) => factoriesUsed.has(name) && FACTORY_TO_DEFINE[name] !== undefined);
|
|
124
|
+
const extractors = statement === extractorHost ? [...extractorsUsed] : [];
|
|
125
|
+
if (replaced.length === 0 && extractors.length === 0)
|
|
126
|
+
continue;
|
|
127
|
+
const kept = names.filter((name) => !replaced.includes(name));
|
|
128
|
+
const merged = [
|
|
129
|
+
...new Set([...kept, ...replaced.map((factory) => FACTORY_TO_DEFINE[factory]), ...extractors]),
|
|
130
|
+
].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
131
|
+
rewrites.push({
|
|
132
|
+
start: bindings.getStart(sourceFile),
|
|
133
|
+
end: bindings.getEnd(),
|
|
134
|
+
text: `{ ${merged.join(', ')} }`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (extractorHost)
|
|
138
|
+
return rewrites;
|
|
139
|
+
// Nothing to join - the file only imported a ref factory from another package, so add a core import.
|
|
140
|
+
const anchor = imports[imports.length - 1];
|
|
141
|
+
const position = anchor ? anchor.getEnd() : 0;
|
|
142
|
+
const statement = `import { ${[...extractorsUsed].sort().join(', ')} } from '@ethlete/core';`;
|
|
143
|
+
rewrites.push({ start: position, end: position, text: anchor ? `\n${statement}` : `${statement}\n` });
|
|
144
|
+
return rewrites;
|
|
145
|
+
};
|
|
146
|
+
const namedImportsOf = (statement, sourceFile) => {
|
|
147
|
+
const bindings = statement.importClause?.namedBindings;
|
|
148
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
149
|
+
return undefined;
|
|
150
|
+
return bindings.elements.map((element) => element.getText(sourceFile));
|
|
151
|
+
};
|
|
152
|
+
const isCoreImport = (statement) => ts.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === '@ethlete/core';
|
|
153
|
+
/**
|
|
154
|
+
* Rewrites every top-level destructured provider tuple in one file into a descriptor plus one pure
|
|
155
|
+
* extractor per exported binding. Returns the file unchanged when there is nothing to do.
|
|
156
|
+
*/
|
|
157
|
+
export const migrateProviderShapeInFile = (filePath, content) => {
|
|
158
|
+
if (![...Object.keys(FACTORY_TO_DEFINE), ...REF_FACTORIES].some((factory) => content.includes(factory))) {
|
|
159
|
+
return { content, changed: false, tasks: [] };
|
|
160
|
+
}
|
|
161
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
162
|
+
const sites = findTupleSites(sourceFile);
|
|
163
|
+
const tasks = collectTasks(filePath, sourceFile, sites);
|
|
164
|
+
if (sites.length === 0)
|
|
165
|
+
return { content, changed: false, tasks };
|
|
166
|
+
const taken = new Set(sourceFile.statements
|
|
167
|
+
.filter(ts.isVariableStatement)
|
|
168
|
+
.flatMap((statement) => statement.declarationList.declarations)
|
|
169
|
+
.flatMap((declaration) => (ts.isIdentifier(declaration.name) ? [declaration.name.text] : [])));
|
|
170
|
+
const factoriesUsed = new Set(sites.map((site) => site.factory));
|
|
171
|
+
const extractorsUsed = new Set(sites.flatMap((site) => site.bindings.flatMap((binding, index) => (binding === null ? [] : [POSITION_TO_EXTRACTOR[index]]))));
|
|
172
|
+
const edits = [
|
|
173
|
+
...sites.map((site) => rewriteSite(content, sourceFile, site, uniqueDefinitionName(site.bindings.filter((b) => b !== null), taken))),
|
|
174
|
+
...rewriteImports(content, sourceFile, factoriesUsed, extractorsUsed),
|
|
175
|
+
].sort((a, b) => b.start - a.start);
|
|
176
|
+
let updated = content;
|
|
177
|
+
for (const edit of edits) {
|
|
178
|
+
updated = updated.slice(0, edit.start) + edit.text + updated.slice(edit.end);
|
|
179
|
+
}
|
|
180
|
+
return { content: updated, changed: updated !== content, tasks };
|
|
181
|
+
};
|
|
182
|
+
/** Call sites the codemod cannot safely rewrite - a factory result that is not a plain tuple binding. */
|
|
183
|
+
const collectTasks = (filePath, sourceFile, sites) => {
|
|
184
|
+
const rewritten = new Set(sites.map((site) => site.statement));
|
|
185
|
+
const tasks = [];
|
|
186
|
+
const visit = (node) => {
|
|
187
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && isKnownFactory(node.expression.text)) {
|
|
188
|
+
const statement = enclosingStatement(node);
|
|
189
|
+
if (!statement || !rewritten.has(statement)) {
|
|
190
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
191
|
+
const factory = node.expression.text;
|
|
192
|
+
const replacement = FACTORY_TO_DEFINE[factory];
|
|
193
|
+
tasks.push({
|
|
194
|
+
id: `provider-shape:${filePath}:${line + 1}`,
|
|
195
|
+
file: filePath,
|
|
196
|
+
line: line + 1,
|
|
197
|
+
message: replacement
|
|
198
|
+
? `\`${factory}\` is gone. Replace it with \`${replacement}\` and read the halves with \`toProvideFn\` / \`toInjectFn\` / \`toToken\`.`
|
|
199
|
+
: `\`${factory}\` returns a provider definition now, not a \`[provide, inject, token]\` tuple. Read the halves with \`toProvideFn\` / \`toInjectFn\` / \`toToken\`, or \`.provide\` / \`.inject\` / \`.token\` inside a function.`,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
ts.forEachChild(node, visit);
|
|
204
|
+
};
|
|
205
|
+
visit(sourceFile);
|
|
206
|
+
return tasks;
|
|
207
|
+
};
|
|
208
|
+
const enclosingStatement = (node) => {
|
|
209
|
+
let current = node;
|
|
210
|
+
while (current && !ts.isSourceFile(current)) {
|
|
211
|
+
if (ts.isVariableStatement(current))
|
|
212
|
+
return current;
|
|
213
|
+
current = current.parent;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=provider-shape.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-provider-shape",
|
|
4
|
+
"title": "Migrate provider tuples to the definition shape",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to migrate. Defaults to every project in the workspace. Every declaration is rewritten independently, so any scope is safe - use this to keep the diff reviewable.",
|
|
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. 'libs/ui'). Unioned with `projects`. Defaults to the whole workspace.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -38,7 +38,7 @@ export default async function generate(tree, schema) {
|
|
|
38
38
|
}
|
|
39
39
|
// Step 4: Apply the generation-time default override, if any. The shared theme
|
|
40
40
|
// definitions may mark a default via isDefault, but in a monorepo each app picks its
|
|
41
|
-
// own default at its generation invocation
|
|
41
|
+
// own default at its generation invocation - the option wins over the definitions.
|
|
42
42
|
if (schema.defaultTheme) {
|
|
43
43
|
try {
|
|
44
44
|
const defaultName = applyDefaultThemeOverride(themes, schema.defaultTheme);
|
|
@@ -411,7 +411,7 @@ function generateTailwindThemeCss(themes, utilityPrefix, runtimePrefix, schema)
|
|
|
411
411
|
// Collect the dynamic theme colors once. They are emitted in `@theme` (so Tailwind generates
|
|
412
412
|
// the utilities) and ALSO re-declared on every color selector in the alias block below. A
|
|
413
413
|
// Tailwind `@theme` variable only lands on `:root`, so `rgb(var(--<runtime>-color-*))` resolves
|
|
414
|
-
// once against the root color scope and inherits that concrete color into descendants
|
|
414
|
+
// once against the root color scope and inherits that concrete color into descendants - which
|
|
415
415
|
// means `bg-<prefix>-theme-*` utilities would ignore nested `.<runtime>-color--*` scopes.
|
|
416
416
|
// Re-declaring them per color selector makes the utilities resolve against the nearest scope.
|
|
417
417
|
const dynamicColorVars = [];
|
|
@@ -449,7 +449,7 @@ function generateTailwindThemeCss(themes, utilityPrefix, runtimePrefix, schema)
|
|
|
449
449
|
while (dynamicColorAliasLines.length && dynamicColorAliasLines[dynamicColorAliasLines.length - 1] === '') {
|
|
450
450
|
dynamicColorAliasLines.pop();
|
|
451
451
|
}
|
|
452
|
-
// Convenience var aliases
|
|
452
|
+
// Convenience var aliases - available on any element with a color class (or root for default)
|
|
453
453
|
const aliasBlock = `/* Convenience aliases (rgb + solid + opacity variants) */
|
|
454
454
|
@layer base {
|
|
455
455
|
:root, :where([class*="${runtimePrefix}-color--"]) {
|
|
@@ -468,7 +468,7 @@ function generateTailwindThemeCss(themes, utilityPrefix, runtimePrefix, schema)
|
|
|
468
468
|
--${runtimePrefix}-theme-color-ink-solid: rgb(var(--${runtimePrefix}-theme-color-ink-rgb) / var(--${runtimePrefix}-theme-color-ink-opacity));
|
|
469
469
|
--${runtimePrefix}-theme-color-ink: rgb(var(--${runtimePrefix}-theme-color-ink-rgb) / var(--${runtimePrefix}-theme-color-ink-opacity));
|
|
470
470
|
|
|
471
|
-
/* Dynamic Tailwind theme colors
|
|
471
|
+
/* Dynamic Tailwind theme colors - re-declared per color scope so that
|
|
472
472
|
bg-${utilityPrefix}-theme-* utilities resolve against the nearest .${runtimePrefix}-color--*
|
|
473
473
|
scope instead of the value computed once at :root. */
|
|
474
474
|
${dynamicColorAliasLines.join('\n')}
|
|
@@ -35,7 +35,7 @@ export default async function generate(tree, schema) {
|
|
|
35
35
|
}
|
|
36
36
|
// Apply the generation-time default overrides, if any. The shared theme definitions may
|
|
37
37
|
// mark defaults via isDefault, but in a monorepo each app picks its own defaults at its
|
|
38
|
-
// generation invocation
|
|
38
|
+
// generation invocation - the options win over the definitions (per surface type).
|
|
39
39
|
try {
|
|
40
40
|
if (schema.defaultLightTheme) {
|
|
41
41
|
const name = applyDefaultSurfaceThemeOverride(themes, schema.defaultLightTheme, 'light');
|
|
@@ -304,7 +304,7 @@ function generateSurfaceThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
|
|
|
304
304
|
`;
|
|
305
305
|
const defaultThemes = themes.filter((t) => t.isDefault);
|
|
306
306
|
const regularThemes = themes.filter((t) => !t.isDefault);
|
|
307
|
-
// Static Tailwind @theme block
|
|
307
|
+
// Static Tailwind @theme block - one color set per surface theme
|
|
308
308
|
for (const theme of themes) {
|
|
309
309
|
const name = createCssSurfaceName(theme.name);
|
|
310
310
|
tailwindVars.push(` /* ${theme.name} surface */`);
|
|
@@ -325,14 +325,14 @@ function generateSurfaceThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
|
|
|
325
325
|
// Dynamic surface colors (reference runtime CSS variables).
|
|
326
326
|
// These are ALSO re-declared on every surface selector in the alias block below. A Tailwind
|
|
327
327
|
// `@theme` variable only lands on `:root`, so `rgb(var(--<runtime>-surface-*))` resolves once
|
|
328
|
-
// against the root surface and inherits that concrete color into descendants
|
|
328
|
+
// against the root surface and inherits that concrete color into descendants - which means
|
|
329
329
|
// `bg-<prefix>-surface-*` utilities would ignore nested surface scopes (e.g. an elevated
|
|
330
330
|
// surface). Re-declaring them per surface selector makes the utilities resolve against the
|
|
331
331
|
// nearest surface instead.
|
|
332
332
|
const dynamicSurfaceColors = buildDynamicSurfaceColors(utilityPrefix, runtimePrefix);
|
|
333
333
|
tailwindVars.push(' /* Dynamic surface colors (references runtime CSS variables) */');
|
|
334
334
|
tailwindVars.push(...dynamicSurfaceColors.map((decl) => ` ${decl}`));
|
|
335
|
-
// Runtime CSS
|
|
335
|
+
// Runtime CSS - theme selector classes
|
|
336
336
|
for (const theme of themes) {
|
|
337
337
|
const name = createCssSurfaceName(theme.name);
|
|
338
338
|
const isDefault = theme.isDefault;
|
|
@@ -346,7 +346,7 @@ function generateSurfaceThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
|
|
|
346
346
|
pushSurfaceVars(themeVars, runtimePrefix, theme, ' ');
|
|
347
347
|
themeVars.push('}\n');
|
|
348
348
|
}
|
|
349
|
-
// Media query defaults
|
|
349
|
+
// Media query defaults - apply surface vars to :root based on prefers-color-scheme
|
|
350
350
|
const defaultLight = themes.find((t) => t.isDefault && t.type === 'light');
|
|
351
351
|
const defaultDark = themes.find((t) => t.isDefault && t.type === 'dark');
|
|
352
352
|
if (defaultLight) {
|
|
@@ -363,7 +363,7 @@ function generateSurfaceThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
|
|
|
363
363
|
themeVars.push(` }`);
|
|
364
364
|
themeVars.push(`}\n`);
|
|
365
365
|
}
|
|
366
|
-
// Convenience var aliases
|
|
366
|
+
// Convenience var aliases - available on any element with a surface class or :root (via media queries)
|
|
367
367
|
const aliasBlock = `/* Convenience aliases (rgb + solid variants) */
|
|
368
368
|
@layer base {
|
|
369
369
|
:root, :where([class*="${runtimePrefix}-surface--"]) {
|
|
@@ -397,7 +397,7 @@ function generateSurfaceThemeCss(themes, utilityPrefix, runtimePrefix, schema) {
|
|
|
397
397
|
--${runtimePrefix}-surface-interaction-disabled-rgb: var(--${runtimePrefix}-surface-interaction-disabled);
|
|
398
398
|
--${runtimePrefix}-surface-interaction-disabled-solid: rgb(var(--${runtimePrefix}-surface-interaction-disabled-rgb));
|
|
399
399
|
|
|
400
|
-
/* Dynamic Tailwind surface colors
|
|
400
|
+
/* Dynamic Tailwind surface colors - re-declared per surface scope so that
|
|
401
401
|
bg-${utilityPrefix}-surface-* utilities resolve against the nearest surface
|
|
402
402
|
(e.g. a nested elevated surface) instead of the value computed once at :root. */
|
|
403
403
|
${dynamicSurfaceColors.map((decl) => ` ${decl}`).join('\n')}
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/core",
|
|
3
|
-
"version": "5.0.0-next.
|
|
3
|
+
"version": "5.0.0-next.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"peerDependencies": {
|
|
7
7
|
"@analogjs/vite-plugin-angular": "2.6.3",
|
|
8
|
-
"@angular/cdk": "22.0.5",
|
|
9
8
|
"@angular/common": "22.0.7",
|
|
10
9
|
"@angular/core": "22.0.7",
|
|
11
10
|
"@angular/forms": "22.0.7",
|
|
@@ -19,6 +18,23 @@
|
|
|
19
18
|
"typescript": "6.0.3",
|
|
20
19
|
"vite": "8.1.5"
|
|
21
20
|
},
|
|
21
|
+
"peerDependenciesMeta": {
|
|
22
|
+
"@analogjs/vite-plugin-angular": {
|
|
23
|
+
"optional": true
|
|
24
|
+
},
|
|
25
|
+
"@nx/devkit": {
|
|
26
|
+
"optional": true
|
|
27
|
+
},
|
|
28
|
+
"ts-morph": {
|
|
29
|
+
"optional": true
|
|
30
|
+
},
|
|
31
|
+
"typescript": {
|
|
32
|
+
"optional": true
|
|
33
|
+
},
|
|
34
|
+
"vite": {
|
|
35
|
+
"optional": true
|
|
36
|
+
}
|
|
37
|
+
},
|
|
22
38
|
"generators": "./generators/generators.json",
|
|
23
39
|
"module": "fesm2022/ethlete-core.mjs",
|
|
24
40
|
"typings": "types/ethlete-core.d.ts",
|