@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +843 -0
  2. package/README.md +28 -4
  3. package/design/interactive-styles.ts +5 -0
  4. package/design/theme-css.js +392 -0
  5. package/design/theme-css.ts +547 -0
  6. package/fesm2022/ethlete-core.mjs +9338 -5667
  7. package/fesm2022/ethlete-core.mjs.map +1 -1
  8. package/generators/devtools-about/generator.js +122 -0
  9. package/generators/devtools-about/schema.json +25 -0
  10. package/generators/generators.json +41 -5
  11. package/generators/migrate-color-theme-variants/color-theme-variants.js +136 -0
  12. package/generators/migrate-color-theme-variants/migration.js +46 -0
  13. package/generators/migrate-color-theme-variants/schema.json +26 -0
  14. package/generators/migrate-create-destroy/create-destroy.js +317 -0
  15. package/generators/migrate-create-destroy/migration.js +46 -0
  16. package/generators/migrate-create-destroy/schema.json +26 -0
  17. package/generators/migrate-legacy-color-themes/legacy-color-themes.js +123 -0
  18. package/generators/migrate-legacy-color-themes/migration.js +93 -0
  19. package/generators/migrate-legacy-color-themes/schema.json +26 -0
  20. package/generators/migrate-provider-shape/migration-scope.js +35 -0
  21. package/generators/migrate-provider-shape/migration.js +50 -0
  22. package/generators/migrate-provider-shape/provider-shape.js +284 -0
  23. package/generators/migrate-provider-shape/schema.json +26 -0
  24. package/generators/migrate-surface-interaction-swatch/interaction-swatch.js +63 -0
  25. package/generators/migrate-surface-interaction-swatch/migration.js +36 -0
  26. package/generators/migrate-surface-interaction-swatch/schema.json +26 -0
  27. package/generators/migrate-to-v5/apply-replacements.js +14 -0
  28. package/generators/migrate-to-v5/color-naming.js +124 -0
  29. package/generators/migrate-to-v5/create-provider.js +67 -55
  30. package/generators/migrate-to-v5/migration-files.js +13 -0
  31. package/generators/migrate-to-v5/migration.js +33 -19
  32. package/generators/migrate-to-v5/removed-exports.js +161 -0
  33. package/generators/migrate-to-v5/router-state-service.js +125 -330
  34. package/generators/migrate-to-v5/schema.json +22 -0
  35. package/generators/migrate-to-v5/service-classes.js +108 -0
  36. package/generators/migrate-to-v5/sync-observable-consumers.js +80 -0
  37. package/generators/migrate-to-v5/viewport-service.js +226 -491
  38. package/generators/tailwind-4-color-theme/generator.js +411 -0
  39. package/generators/tailwind-4-color-theme/schema.json +44 -0
  40. package/generators/tailwind-4-surface-theme/generator.js +379 -0
  41. package/generators/tailwind-4-surface-theme/schema.json +48 -0
  42. package/migrations/legacy-color-themes.md +38 -0
  43. package/migrations/seo-directive-removed.md +81 -0
  44. package/migrations/surface-theme-regenerate.md +20 -0
  45. package/migrations.json +59 -0
  46. package/package.json +38 -21
  47. package/src/lib/animations/easing-tokens-styles.component.css +155 -0
  48. package/src/lib/providers/visually-hidden-styles.component.css +13 -0
  49. package/src/lib/resize-handles/resize-handles.component.css +137 -0
  50. package/src/lib/theming/color-interactive-styles.component.css +147 -0
  51. package/src/lib/theming/surface-interactive-styles.component.css +42 -0
  52. package/types/ethlete-core.d.ts +3842 -2205
  53. package/generators/tailwind-4-theme/generator.js +0 -514
  54. package/generators/tailwind-4-theme/schema.json +0 -32
@@ -1,38 +1,65 @@
1
- import { logger } from '@nx/devkit';
2
1
  import * as ts from 'typescript';
3
- export default async function migrateCreateProvider(tree) {
4
- logger.log('\nšŸ”„ Migrating createProvider imports from @ethlete/cdk to @ethlete/core...\n');
5
- const tsFiles = [];
6
- // Recursively find all TypeScript files
7
- function findTsFiles(dir) {
8
- const children = tree.children(dir);
9
- for (const child of children) {
10
- const path = dir === '.' ? child : `${dir}/${child}`;
11
- if (tree.isFile(path)) {
12
- if (path.endsWith('.ts') && !path.includes('node_modules') && !path.endsWith('.spec.ts')) {
13
- tsFiles.push(path);
14
- }
15
- }
16
- else {
17
- findTsFiles(path);
18
- }
19
- }
20
- }
21
- findTsFiles('.');
2
+ import { collectFiles } from './migration-files.js';
3
+ export default async function migrateCreateProvider(tree, scope) {
4
+ const review = [];
22
5
  let filesModified = 0;
23
- for (const filePath of tsFiles) {
24
- const wasModified = migrateCreateProviderInFile(tree, filePath);
25
- if (wasModified) {
6
+ for (const filePath of collectFiles(tree, scope, ['.ts'])) {
7
+ const reexportsModified = migrateCreateProviderReexports(tree, filePath, review);
8
+ const wasModified = migrateCreateProviderInFile(tree, filePath) || reexportsModified;
9
+ if (wasModified)
26
10
  filesModified++;
27
- logger.log(` āœ“ ${filePath}`);
28
- }
29
11
  }
30
- if (filesModified > 0) {
31
- logger.log(`\nāœ… Successfully migrated createProvider in ${filesModified} file(s)\n`);
12
+ return { filesChanged: filesModified, review };
13
+ }
14
+ function migrateCreateProviderReexports(tree, filePath, review) {
15
+ const content = tree.read(filePath, 'utf-8');
16
+ if (!content || !content.includes('createProvider'))
17
+ return false;
18
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
19
+ const edits = [];
20
+ for (const statement of sourceFile.statements) {
21
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
22
+ continue;
23
+ if (!statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier))
24
+ continue;
25
+ if (statement.moduleSpecifier.text !== '@ethlete/cdk')
26
+ continue;
27
+ if (ts.isImportDeclaration(statement)) {
28
+ const bindings = statement.importClause?.namedBindings;
29
+ if (bindings && ts.isNamespaceImport(bindings) && content.includes(`${bindings.name.text}.createProvider`)) {
30
+ review.push(`${filePath}: ${bindings.name.text}.createProvider is used through a namespace import of @ethlete/cdk. Switch it to createProvider from @ethlete/core manually.`);
31
+ }
32
+ continue;
33
+ }
34
+ const clause = statement.exportClause;
35
+ if (!clause) {
36
+ review.push(`${filePath}: "export * from '@ethlete/cdk'" may re-export createProvider. Re-export it from @ethlete/core manually.`);
37
+ continue;
38
+ }
39
+ if (ts.isNamespaceExport(clause))
40
+ continue;
41
+ const isCreateProvider = (element) => (element.propertyName?.text ?? element.name.text) === 'createProvider';
42
+ const moved = clause.elements.filter(isCreateProvider);
43
+ if (moved.length === 0)
44
+ continue;
45
+ const rest = clause.elements.filter((element) => !isCreateProvider(element));
46
+ const typeOnly = statement.isTypeOnly ? 'type ' : '';
47
+ const lines = [
48
+ ...(rest.length > 0
49
+ ? [`export ${typeOnly}{ ${rest.map((el) => el.getText(sourceFile)).join(', ')} } from '@ethlete/cdk';`]
50
+ : []),
51
+ `export ${typeOnly}{ ${moved.map((el) => el.getText(sourceFile)).join(', ')} } from '@ethlete/core';`,
52
+ ];
53
+ edits.push({ start: statement.getStart(sourceFile), end: statement.getEnd(), text: lines.join('\n') });
32
54
  }
33
- else {
34
- logger.log('\nā„¹ļø No files needed migration\n');
55
+ if (edits.length === 0)
56
+ return false;
57
+ let updatedContent = content;
58
+ for (const edit of edits.reverse()) {
59
+ updatedContent = updatedContent.slice(0, edit.start) + edit.text + updatedContent.slice(edit.end);
35
60
  }
61
+ tree.write(filePath, updatedContent);
62
+ return true;
36
63
  }
37
64
  function migrateCreateProviderInFile(tree, filePath) {
38
65
  const content = tree.read(filePath, 'utf-8');
@@ -40,35 +67,30 @@ function migrateCreateProviderInFile(tree, filePath) {
40
67
  return false;
41
68
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
42
69
  let cdkImport;
43
- let coreImport;
44
70
  let hasCreateProviderInCdk = false;
45
- // Find relevant imports
71
+ let createProviderImport = 'createProvider';
46
72
  for (const statement of sourceFile.statements) {
47
73
  if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
48
74
  const modulePath = statement.moduleSpecifier.text;
49
75
  if (modulePath === '@ethlete/cdk') {
50
76
  cdkImport = statement;
51
77
  if (statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
52
- hasCreateProviderInCdk = statement.importClause.namedBindings.elements.some((element) => element.name.text === 'createProvider');
78
+ const specifier = statement.importClause.namedBindings.elements.find((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
79
+ hasCreateProviderInCdk = !!specifier;
80
+ createProviderImport = specifier?.getText(sourceFile) ?? createProviderImport;
53
81
  }
54
82
  }
55
- else if (modulePath === '@ethlete/core') {
56
- coreImport = statement;
57
- }
58
83
  }
59
84
  }
60
- // If createProvider is not imported from @ethlete/cdk, nothing to do
61
85
  if (!hasCreateProviderInCdk)
62
86
  return false;
63
87
  let updatedContent = content;
64
- // Remove createProvider from @ethlete/cdk import
65
88
  if (cdkImport && cdkImport.importClause?.namedBindings && ts.isNamedImports(cdkImport.importClause.namedBindings)) {
66
89
  const namedBindings = cdkImport.importClause.namedBindings;
67
- const otherImports = namedBindings.elements.filter((element) => element.name.text !== 'createProvider');
90
+ const otherImports = namedBindings.elements.filter((element) => (element.propertyName?.text ?? element.name.text) !== 'createProvider');
68
91
  const importStart = cdkImport.getStart(sourceFile);
69
92
  const importEnd = cdkImport.getEnd();
70
93
  if (otherImports.length === 0) {
71
- // Remove entire import line including newline
72
94
  let lineStart = importStart;
73
95
  while (lineStart > 0 && content[lineStart - 1] !== '\n') {
74
96
  lineStart--;
@@ -83,15 +105,12 @@ function migrateCreateProviderInFile(tree, filePath) {
83
105
  updatedContent = content.slice(0, lineStart) + content.slice(lineEnd);
84
106
  }
85
107
  else {
86
- // Keep other imports
87
- const newImports = otherImports.map((el) => el.name.text).join(', ');
108
+ const newImports = otherImports.map((el) => el.getText(sourceFile)).join(', ');
88
109
  const newImportText = `import { ${newImports} } from '@ethlete/cdk';`;
89
110
  updatedContent = content.slice(0, importStart) + newImportText + content.slice(importEnd);
90
111
  }
91
112
  }
92
- // Re-parse after first modification
93
113
  const intermediateSourceFile = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
94
- // Find @ethlete/core import in the updated content
95
114
  let updatedCoreImport;
96
115
  for (const statement of intermediateSourceFile.statements) {
97
116
  if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
@@ -101,26 +120,22 @@ function migrateCreateProviderInFile(tree, filePath) {
101
120
  }
102
121
  }
103
122
  }
104
- // Add createProvider to @ethlete/core import
105
123
  if (updatedCoreImport?.importClause?.namedBindings &&
106
124
  ts.isNamedImports(updatedCoreImport.importClause.namedBindings)) {
107
125
  const namedBindings = updatedCoreImport.importClause.namedBindings;
108
- // Check if createProvider already exists
109
- const hasCreateProvider = namedBindings.elements.some((element) => element.name.text === 'createProvider');
126
+ const hasCreateProvider = namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
110
127
  if (!hasCreateProvider) {
111
128
  const importStart = updatedCoreImport.getStart(intermediateSourceFile);
112
129
  const importEnd = updatedCoreImport.getEnd();
113
- const existingImports = namedBindings.elements.map((el) => el.name.text);
114
- const allImports = [...existingImports, 'createProvider'].sort();
130
+ const existingImports = namedBindings.elements.map((el) => el.getText(intermediateSourceFile));
131
+ const allImports = [...existingImports, createProviderImport].sort();
115
132
  const newImportText = `import { ${allImports.join(', ')} } from '@ethlete/core';`;
116
133
  updatedContent = updatedContent.slice(0, importStart) + newImportText + updatedContent.slice(importEnd);
117
134
  }
118
135
  }
119
136
  else {
120
- // Add new import from @ethlete/core at the top
121
137
  const firstImportIndex = updatedContent.indexOf('import');
122
138
  if (firstImportIndex !== -1) {
123
- // Add after other imports
124
139
  let insertPosition;
125
140
  let lastImportEnd = firstImportIndex;
126
141
  for (const statement of intermediateSourceFile.statements) {
@@ -132,7 +147,6 @@ function migrateCreateProviderInFile(tree, filePath) {
132
147
  }
133
148
  }
134
149
  insertPosition = lastImportEnd;
135
- // Find end of line
136
150
  while (insertPosition < updatedContent.length && updatedContent[insertPosition] !== '\n') {
137
151
  insertPosition++;
138
152
  }
@@ -141,12 +155,11 @@ function migrateCreateProviderInFile(tree, filePath) {
141
155
  }
142
156
  updatedContent =
143
157
  updatedContent.slice(0, insertPosition) +
144
- "import { createProvider } from '@ethlete/core';\n" +
158
+ `import { ${createProviderImport} } from '@ethlete/core';\n` +
145
159
  updatedContent.slice(insertPosition);
146
160
  }
147
161
  else {
148
- // No imports, add at the beginning
149
- updatedContent = "import { createProvider } from '@ethlete/core';\n\n" + updatedContent;
162
+ updatedContent = `import { ${createProviderImport} } from '@ethlete/core';\n\n` + updatedContent;
150
163
  }
151
164
  }
152
165
  if (updatedContent !== content) {
@@ -155,4 +168,3 @@ function migrateCreateProviderInFile(tree, filePath) {
155
168
  }
156
169
  return false;
157
170
  }
158
- //# sourceMappingURL=create-provider.js.map
@@ -0,0 +1,13 @@
1
+ import { createMigrationScope } from '../migrate-provider-shape/migration-scope.js';
2
+ const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', '.git', '.angular', '.nx']);
3
+ export const collectFiles = (tree, scope, extensions) => {
4
+ const files = [];
5
+ (scope ?? createMigrationScope(tree, {})).visit(tree, (filePath) => {
6
+ if (filePath.split('/').some((segment) => SKIPPED_DIRECTORIES.has(segment)))
7
+ return;
8
+ if (!extensions.some((extension) => filePath.endsWith(extension)))
9
+ return;
10
+ files.push(filePath);
11
+ });
12
+ return files;
13
+ };
@@ -1,28 +1,42 @@
1
- import { formatFiles } from '@nx/devkit';
1
+ import { formatFiles, logger } from '@nx/devkit';
2
+ import { createMigrationScope, } from '../migrate-provider-shape/migration-scope.js';
3
+ import migrateColorNaming from './color-naming.js';
2
4
  import migrateCreateProvider from './create-provider.js';
5
+ import reportRemovedExports from './removed-exports.js';
3
6
  import migrateRouterStateService from './router-state-service.js';
4
7
  import migrateViewportService from './viewport-service.js';
5
8
  export default async function migrate(tree, schema) {
6
- console.log('\nšŸ”„ Starting core v5 migration...');
7
- const shouldMigrateViewportService = schema.migrateViewportService !== false;
8
- const shouldMigrateCreateProvider = schema.migrateCreateProvider !== false;
9
- const shouldMigrateRouterStateService = schema.migrateRouterStateService !== false;
10
- if (shouldMigrateViewportService) {
11
- console.log(' • Migrating viewport service...');
12
- await migrateViewportService(tree);
13
- }
14
- if (shouldMigrateCreateProvider) {
15
- console.log(' • Migrating create provider...');
16
- await migrateCreateProvider(tree);
17
- }
18
- if (shouldMigrateRouterStateService) {
19
- await migrateRouterStateService(tree);
9
+ const scope = createMigrationScope(tree, schema);
10
+ logger.info('\nšŸ”„ Starting core v5 migration...');
11
+ logger.info(` Scope: ${scope.describe()}`);
12
+ const transforms = [
13
+ { label: 'ViewportService', enabled: schema.migrateViewportService !== false, run: migrateViewportService },
14
+ { label: 'createProvider', enabled: schema.migrateCreateProvider !== false, run: migrateCreateProvider },
15
+ {
16
+ label: 'RouterStateService',
17
+ enabled: schema.migrateRouterStateService !== false,
18
+ run: migrateRouterStateService,
19
+ },
20
+ { label: 'Theme → color naming', enabled: schema.migrateColorNaming !== false, run: migrateColorNaming },
21
+ { label: 'Removed exports', enabled: schema.reportRemovedExports !== false, run: reportRemovedExports },
22
+ ];
23
+ const reports = [];
24
+ for (const transform of transforms) {
25
+ if (!transform.enabled)
26
+ continue;
27
+ logger.info(` • ${transform.label}...`);
28
+ reports.push([transform.label, await transform.run(tree, scope)]);
20
29
  }
21
30
  if (!schema.skipFormat) {
22
- console.log(' • Formatting files...');
23
31
  await formatFiles(tree);
24
32
  }
25
- console.log('\nāœ… Migration completed successfully!');
33
+ logger.info('\nSummary:');
34
+ for (const [label, report] of reports) {
35
+ const review = report.review.length > 0 ? `, ${report.review.length} to review manually` : '';
36
+ logger.info(` • ${label}: ${report.filesChanged} file(s) changed${review}`);
37
+ for (const message of report.review) {
38
+ logger.warn(` ⚠ ${message}`);
39
+ }
40
+ }
41
+ logger.info('\nāœ… Migration completed. Review the diff before committing.');
26
42
  }
27
- //#endregion
28
- //# sourceMappingURL=migration.js.map
@@ -0,0 +1,161 @@
1
+ import * as ts from 'typescript';
2
+ import { collectFiles } from './migration-files.js';
3
+ export const REMOVED_EXPORTS = new Map([
4
+ ...['Memo', 'MemoConfig', 'MemoResolver', 'MapLike'].map((name) => [name, 'together with the @Memo decorator and has no replacement.']),
5
+ ...[
6
+ 'createProps',
7
+ 'createHostProps',
8
+ 'createPropHandlers',
9
+ 'createSetup',
10
+ 'createDependencyStash',
11
+ 'createElementDictionary',
12
+ 'bindProps',
13
+ 'unbindProps',
14
+ 'PropsDirective',
15
+ 'templateComputed',
16
+ 'ComponentType',
17
+ 'AnyTemplateType',
18
+ 'TemplateRefWithContext',
19
+ 'ComponentTypeWithInputs',
20
+ 'StringTemplate',
21
+ 'NgTemplateTemplate',
22
+ 'ComponentTemplate',
23
+ 'Props',
24
+ 'PropsAttachedElements',
25
+ 'PropsAttachedElementsInternal',
26
+ 'PropsInternal',
27
+ 'CreatePropsOptions',
28
+ 'HostProps',
29
+ 'PropHandlers',
30
+ 'BindPropsOptions',
31
+ 'UnbindPropsOptions',
32
+ ].map((name) => [name, 'together with the props module and has no replacement.']),
33
+ ...['LetDirective', 'LetContext'].map((name) => [
34
+ name,
35
+ "in v5. Use Angular's `@let`. `@ethlete/cdk:migrate-to-v5` rewrites `*etLet` templates; without the cdk, rewrite them by hand.",
36
+ ]),
37
+ ...['IsActiveElementDirective', 'IS_ACTIVE_ELEMENT', 'IsElementDirective', 'IS_ELEMENT'].map((name) => [
38
+ name,
39
+ 'in v5. Inside a scrollable, use `ScrollableIsActiveChildDirective`; `@ethlete/cdk:migrate-to-v5` rewrites it.',
40
+ ]),
41
+ ...['ObserveResizeDirective', 'ResizeObserverService', 'ResizeObserverFactory', 'createResizeObservable'].map((name) => [name, 'in v5. Use `signalElementDimensions()`.']),
42
+ ...['ObserveContentDirective', 'ContentObserverService', 'MutationObserverFactory', 'createMutationObservable'].map((name) => [name, 'in v5. Use `signalElementMutations()`.']),
43
+ ...[
44
+ 'ObserveVisibilityDirective',
45
+ 'OBSERVE_VISIBILITY_TOKEN',
46
+ 'ObserveVisibilityChange',
47
+ 'IntersectionObserverService',
48
+ 'IntersectionObserverFactory',
49
+ 'getIntersectionInfo',
50
+ 'signalVisibilityChangeClasses',
51
+ ].map((name) => [name, 'in v5. Use `signalElementIntersection()`.']),
52
+ ...[
53
+ 'ObserveScrollStateDirective',
54
+ 'OBSERVE_SCROLL_STATE',
55
+ 'ScrollObserverFirstElementDirective',
56
+ 'ScrollObserverLastElementDirective',
57
+ 'ScrollObserverIgnoreTargetDirective',
58
+ 'SCROLL_OBSERVER_FIRST_ELEMENT_CLASS',
59
+ 'SCROLL_OBSERVER_IGNORE_TARGET_CLASS',
60
+ 'SCROLL_OBSERVER_LAST_ELEMENT_CLASS',
61
+ 'SCROLL_OBSERVER_OBSERVING_FIRST_ELEMENT_CLASS',
62
+ 'SCROLL_OBSERVER_OBSERVING_LAST_ELEMENT_CLASS',
63
+ 'ScrollObserverScrollState',
64
+ 'ObservedScrollableChild',
65
+ 'areScrollStatesEqual',
66
+ ].map((name) => [
67
+ name,
68
+ 'in v5. Use `signalElementScrollState()`, or `ScrollableScrollState` of the `@ethlete/components` scrollable.',
69
+ ]),
70
+ ...['CursorDragScrollDirective', 'CURSOR_DRAG_SCROLLING_CLASS', 'CURSOR_DRAG_SCROLLING_PREPARED_CLASS'].map((name) => [name, 'in v5. Use `useCursorDragScroll()`.']),
71
+ ...['RootBoundaryDirective', 'ROOT_BOUNDARY_TOKEN'].map((name) => [name, 'in v5. Use `provideBoundaryElement()`.']),
72
+ ...['DelayableDirective', 'DELAYABLE_TOKEN'].map((name) => [name, 'in v5. Use `provideInfinityQueryResponseDelay()` from `@ethlete/query`.']),
73
+ ...[
74
+ 'SeoDirective',
75
+ 'SEO_DIRECTIVE_TOKEN',
76
+ 'SeoConfig',
77
+ 'mergeSeoConfig',
78
+ 'OpenGraph',
79
+ 'TwitterCard',
80
+ 'FacebookCard',
81
+ 'AlternateLink',
82
+ ].map((name) => [name, 'in v5. Use the `apply*Binding` functions; the SEO guide has a per-key migration table.']),
83
+ ...['AnimatedOverlayDirective', 'AnimatedOverlayComponentBase'].map((name) => [
84
+ name,
85
+ 'in v5. It moved to `@ethlete/cdk`; `@ethlete/components` overlays use the overlay strategy controller.',
86
+ ]),
87
+ ...[
88
+ 'SelectionModel',
89
+ 'ActiveSelectionModel',
90
+ 'SelectionModelBinding',
91
+ 'SelectionModelOptionValueFn',
92
+ 'SelectionModelPropertyPath',
93
+ 'SelectionModelTypes',
94
+ ].map((name) => [name, 'in v5. It moved into the `@ethlete/cdk` select and has no public replacement.']),
95
+ ...['FocusVisibleService'].map((name) => [name, 'in v5. Use `injectFocusVisibleTracker()`.']),
96
+ ...['createMediaQueryObservable'].map((name) => [name, 'in v5. Use `injectBreakpointIsMatched()` or the other media query signals.']),
97
+ ...['createReactiveBindings', 'ReactiveAttributes', 'ReactiveBindingResult'].map((name) => [name, 'in v5. Use `signalHostClasses()` / `signalHostAttributes()`.']),
98
+ ...['debouncedControlValueSignal', 'DebouncedControlValueSignalOptions'].map((name) => [name, 'in v5. Debounce the `controlValueSignal()` value with `toObservable()` and `debounceTime()`.']),
99
+ ...['ProviderResult', 'RootProviderResult', 'StaticProviderResult', 'CreateProviderOptions'].map((name) => [
100
+ name,
101
+ 'in v5. It belonged to the removed `create*Provider` factories, which `@ethlete/core:migrate-provider-shape` rewrites.',
102
+ ]),
103
+ ...['VIEWPORT_CONFIG'].map((name) => [name, 'in v5. Use `provideViewportConfig()`.']),
104
+ ...[
105
+ 'ClickObserverService',
106
+ 'ClickObserverFactory',
107
+ 'DebugDirective',
108
+ 'DEBUG_TOKEN',
109
+ 'HostDirective',
110
+ 'MaybeObservable',
111
+ 'Primitive',
112
+ 'Reference',
113
+ 'Size',
114
+ 'Validators',
115
+ 'isEmptyArray',
116
+ 'isObjectArray',
117
+ 'isPrimitiveArray',
118
+ 'ScrollEnhancementsConfig',
119
+ 'SmartBlockScrollStrategy',
120
+ ].map((name) => [name, 'in v5. It has no replacement.']),
121
+ ]);
122
+ const ET_PROPS_ATTRIBUTE = /\betProps\b/;
123
+ export default async function reportRemovedExports(tree, scope) {
124
+ const review = [];
125
+ for (const filePath of collectFiles(tree, scope, ['.ts', '.html'])) {
126
+ const messages = filePath.endsWith('.ts') ? findRemovedImports(tree, filePath) : findEtPropsUsage(tree, filePath);
127
+ review.push(...messages.map((message) => `${filePath}: ${message}`));
128
+ }
129
+ return { filesChanged: 0, review };
130
+ }
131
+ function findRemovedImports(tree, filePath) {
132
+ const content = tree.read(filePath, 'utf-8');
133
+ if (!content || !content.includes('@ethlete/core'))
134
+ return [];
135
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
136
+ const messages = [];
137
+ for (const statement of sourceFile.statements) {
138
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
139
+ continue;
140
+ const specifier = statement.moduleSpecifier;
141
+ if (!specifier || !ts.isStringLiteral(specifier) || specifier.text !== '@ethlete/core')
142
+ continue;
143
+ const bindings = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : statement.exportClause;
144
+ if (!bindings || ts.isNamespaceImport(bindings) || ts.isNamespaceExport(bindings))
145
+ continue;
146
+ for (const element of bindings.elements) {
147
+ const name = (element.propertyName ?? element.name).text;
148
+ const successor = REMOVED_EXPORTS.get(name);
149
+ if (successor) {
150
+ messages.push(`${name} was removed from @ethlete/core ${successor}`);
151
+ }
152
+ }
153
+ }
154
+ return messages;
155
+ }
156
+ function findEtPropsUsage(tree, filePath) {
157
+ const content = tree.read(filePath, 'utf-8');
158
+ if (!content || !ET_PROPS_ATTRIBUTE.test(content))
159
+ return [];
160
+ return ['[etProps] (PropsDirective) was removed from @ethlete/core together with the props module.'];
161
+ }