@siemens/element-ng 48.2.0-rc.1 → 48.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +5 -0
  2. package/accordion/index.d.ts +5 -1
  3. package/application-header/index.d.ts +15 -2
  4. package/chat-messages/index.d.ts +654 -0
  5. package/chat-messages/package.json +3 -0
  6. package/dashboard/index.d.ts +1 -0
  7. package/fesm2022/siemens-element-ng-accordion.mjs +5 -1
  8. package/fesm2022/siemens-element-ng-accordion.mjs.map +1 -1
  9. package/fesm2022/siemens-element-ng-application-header.mjs +62 -1
  10. package/fesm2022/siemens-element-ng-application-header.mjs.map +1 -1
  11. package/fesm2022/siemens-element-ng-card.mjs +4 -4
  12. package/fesm2022/siemens-element-ng-card.mjs.map +1 -1
  13. package/fesm2022/siemens-element-ng-chat-messages.mjs +863 -0
  14. package/fesm2022/siemens-element-ng-chat-messages.mjs.map +1 -0
  15. package/fesm2022/siemens-element-ng-dashboard.mjs +8 -4
  16. package/fesm2022/siemens-element-ng-dashboard.mjs.map +1 -1
  17. package/fesm2022/siemens-element-ng-file-uploader.mjs +277 -118
  18. package/fesm2022/siemens-element-ng-file-uploader.mjs.map +1 -1
  19. package/fesm2022/siemens-element-ng-filtered-search.mjs +3 -4
  20. package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -1
  21. package/fesm2022/siemens-element-ng-header-dropdown.mjs +13 -1
  22. package/fesm2022/siemens-element-ng-header-dropdown.mjs.map +1 -1
  23. package/fesm2022/siemens-element-ng-icon.mjs +3 -1
  24. package/fesm2022/siemens-element-ng-icon.mjs.map +1 -1
  25. package/fesm2022/siemens-element-ng-ip-input.mjs +116 -117
  26. package/fesm2022/siemens-element-ng-ip-input.mjs.map +1 -1
  27. package/fesm2022/siemens-element-ng-markdown-renderer.mjs +253 -0
  28. package/fesm2022/siemens-element-ng-markdown-renderer.mjs.map +1 -0
  29. package/fesm2022/siemens-element-ng-phone-number.mjs +5 -4
  30. package/fesm2022/siemens-element-ng-phone-number.mjs.map +1 -1
  31. package/fesm2022/siemens-element-ng-popover.mjs +3 -4
  32. package/fesm2022/siemens-element-ng-popover.mjs.map +1 -1
  33. package/fesm2022/siemens-element-ng-resize-observer.mjs +13 -0
  34. package/fesm2022/siemens-element-ng-resize-observer.mjs.map +1 -1
  35. package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
  36. package/fesm2022/siemens-element-ng-tree-view.mjs +41 -2
  37. package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -1
  38. package/file-uploader/index.d.ts +119 -15
  39. package/header-dropdown/index.d.ts +7 -0
  40. package/icon/index.d.ts +3 -1
  41. package/ip-input/index.d.ts +13 -1
  42. package/markdown-renderer/index.d.ts +36 -0
  43. package/markdown-renderer/package.json +3 -0
  44. package/package.json +11 -3
  45. package/resize-observer/index.d.ts +13 -0
  46. package/schematics/collection.json +6 -0
  47. package/schematics/migrations/action-modal-migration/action-modal-migration.js +121 -0
  48. package/schematics/migrations/action-modal-migration/action-modal.mappings.js +98 -0
  49. package/schematics/migrations/action-modal-migration/index.js +5 -0
  50. package/schematics/migrations/data/attribute-selectors.js +6 -0
  51. package/schematics/migrations/data/component-names.js +78 -0
  52. package/schematics/migrations/data/element-selectors.js +10 -0
  53. package/schematics/migrations/data/index.js +17 -0
  54. package/schematics/migrations/data/output-names.js +8 -0
  55. package/schematics/migrations/data/symbol-removals.js +58 -0
  56. package/schematics/migrations/element-migration/element-migration.js +101 -0
  57. package/schematics/migrations/element-migration/index.js +5 -0
  58. package/schematics/migrations/index.js +18 -0
  59. package/schematics/migrations/schema.json +16 -0
  60. package/schematics/migrations/wizard-migration/index.js +88 -0
  61. package/schematics/scss-import-to-siemens-migration/index.js +3 -3
  62. package/schematics/simpl-siemens-migration/index.js +2 -1
  63. package/schematics/ts-import-to-siemens-migration/index.js +2 -2
  64. package/schematics/utils/html-utils.js +72 -0
  65. package/schematics/utils/index.js +4 -2
  66. package/schematics/utils/project-utils.js +24 -35
  67. package/schematics/utils/template-utils.js +190 -0
  68. package/schematics/utils/ts-utils.js +96 -0
  69. package/template-i18n.json +9 -0
  70. package/translate/index.d.ts +9 -0
  71. package/tree-view/index.d.ts +40 -1
@@ -0,0 +1,78 @@
1
+ export const COMPONENT_NAMES_MIGRATION = [
2
+ // Icon current to legacy
3
+ {
4
+ module: /@(siemens|simpl)\/element-ng(\/icon)?/,
5
+ symbolRenamings: [
6
+ {
7
+ replace: 'SiIconComponent',
8
+ replaceWith: 'SiIconLegacyComponent'
9
+ }
10
+ ]
11
+ },
12
+ // Icon next to current
13
+ {
14
+ module: /@(siemens|simpl)\/element-ng(\/icon-next)?/,
15
+ symbolRenamings: [
16
+ {
17
+ replace: 'SiIconNextComponent',
18
+ replaceWith: 'SiIconComponent'
19
+ }
20
+ ]
21
+ },
22
+ // Tabs current to legacy
23
+ {
24
+ module: /@(siemens|simpl)\/element-ng(\/tabs)?/,
25
+ symbolRenamings: [
26
+ {
27
+ replace: 'SiTabComponent',
28
+ replaceWith: 'SiTabLegacyComponent'
29
+ },
30
+ {
31
+ replace: 'SiTabsetComponent',
32
+ replaceWith: 'SiTabsetLegacyComponent'
33
+ },
34
+ {
35
+ replace: 'SiTabsModule',
36
+ replaceWith: 'SiTabsLegacyModule'
37
+ }
38
+ ],
39
+ toModule: '@siemens/element-ng/tabs-legacy'
40
+ },
41
+ // Tabs next to current
42
+ {
43
+ module: /@(siemens|simpl)\/element-ng(\/tabs-next)?/,
44
+ symbolRenamings: [
45
+ {
46
+ replace: 'SiTabNextComponent',
47
+ replaceWith: 'SiTabComponent'
48
+ },
49
+ {
50
+ replace: 'SiTabsetNextComponent',
51
+ replaceWith: 'SiTabsetComponent'
52
+ },
53
+ {
54
+ replace: 'SiTabsNextModule',
55
+ replaceWith: 'SiTabsModule'
56
+ }
57
+ ],
58
+ toModule: '@siemens/element-ng/tabs'
59
+ },
60
+ // Popover current to legacy
61
+ {
62
+ module: /@(siemens|simpl)\/element-ng(\/popover)?/,
63
+ symbolRenamings: [
64
+ { replace: 'SiPopoverDirective', replaceWith: 'SiPopoverLegacyDirective' },
65
+ { replace: 'SiPopoverModule', replaceWith: 'SiPopoverLegacyModule' }
66
+ ],
67
+ toModule: '@siemens/element-ng/popover-legacy'
68
+ },
69
+ // Popover next to current
70
+ {
71
+ module: /@(siemens|simpl)\/element-ng(\/popover-next)?/,
72
+ symbolRenamings: [
73
+ { replace: 'SiPopoverNextDirective', replaceWith: 'SiPopoverDirective' },
74
+ { replace: 'SiPopoverNextModule', replaceWith: 'SiPopoverModule' }
75
+ ],
76
+ toModule: '@siemens/element-ng/popover'
77
+ }
78
+ ];
@@ -0,0 +1,10 @@
1
+ export const ELEMENT_SELECTORS_MIGRATION = [
2
+ // current to legacy
3
+ { replace: 'si-icon', replaceWith: 'si-icon-legacy' },
4
+ { replace: 'si-tabset', replaceWith: 'si-tabset-legacy' },
5
+ { replace: 'si-tab', replaceWith: 'si-tab-legacy' },
6
+ // next to current
7
+ { replace: 'si-icon-next', replaceWith: 'si-icon' },
8
+ { replace: 'si-tabset-next', replaceWith: 'si-tabset' },
9
+ { replace: 'si-tab-next', replaceWith: 'si-tab' }
10
+ ];
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { ATTRIBUTE_SELECTORS_MIGRATION } from './attribute-selectors.js';
6
+ import { COMPONENT_NAMES_MIGRATION } from './component-names.js';
7
+ import { ELEMENT_SELECTORS_MIGRATION } from './element-selectors.js';
8
+ import { OUTPUT_NAMES_MIGRATION } from './output-names.js';
9
+ import { SYMBOL_REMOVALS_MIGRATION } from './symbol-removals.js';
10
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
11
+ export const getElementMigrationData = () => ({
12
+ attributeSelectorChanges: ATTRIBUTE_SELECTORS_MIGRATION,
13
+ componentNameChanges: COMPONENT_NAMES_MIGRATION,
14
+ elementSelectorChanges: ELEMENT_SELECTORS_MIGRATION,
15
+ symbolRemovalChanges: SYMBOL_REMOVALS_MIGRATION,
16
+ outputNameChanges: OUTPUT_NAMES_MIGRATION
17
+ });
@@ -0,0 +1,8 @@
1
+ export const OUTPUT_NAMES_MIGRATION = [
2
+ {
3
+ module: /@(siemens|simpl)\/element-ng(\/accordion)?/,
4
+ elementSelector: 'si-collapsible-panel',
5
+ componentOrModuleName: ['SiCollapsiblePanelComponent', 'SiAccordionModule'],
6
+ apiMappings: [{ replace: '(toggle)', replaceWith: '(panelToggle)' }]
7
+ }
8
+ ];
@@ -0,0 +1,58 @@
1
+ export const SYMBOL_REMOVALS_MIGRATION = [
2
+ {
3
+ module: /@(siemens|simpl)\/element-ng(\/accordion)?/,
4
+ elementSelector: 'si-accordion',
5
+ componentOrModuleName: ['SiAccordionComponent', 'SiAccordionModule'],
6
+ names: ['colorVariant']
7
+ },
8
+ {
9
+ module: /@(siemens|simpl)\/element-ng(\/datepicker)?/,
10
+ elementSelector: 'input',
11
+ attributeSelector: 'siDateInput',
12
+ componentOrModuleName: ['SiDateInputDirective', 'SiDatepickerModule'],
13
+ names: ['dateInputDebounceTime']
14
+ },
15
+ {
16
+ module: /@(siemens|simpl)\/element-ng(\/datepicker)?/,
17
+ elementSelector: 'input',
18
+ attributeSelector: 'siDatepicker',
19
+ componentOrModuleName: ['SiDateInputDirective', 'SiDatepickerModule'],
20
+ names: ['triggeringInput']
21
+ },
22
+ {
23
+ module: /@(siemens|simpl)\/element-ng(\/datepicker)?/,
24
+ elementSelector: 'si-date-range',
25
+ componentOrModuleName: ['SiDateRangeComponent', 'SiDatepickerModule'],
26
+ names: ['debounceTime']
27
+ },
28
+ {
29
+ module: /@(siemens|simpl)\/element-ng(\/filtered-search)?/,
30
+ elementSelector: 'si-filtered-search',
31
+ componentOrModuleName: ['SiFilteredSearchComponent', 'SiFilteredSearchModule'],
32
+ names: ['showIcon', 'noMatchingCriteriaText']
33
+ },
34
+ {
35
+ module: /@(siemens|simpl)\/element-ng(\/form)?/,
36
+ elementSelector: 'si-form-item',
37
+ componentOrModuleName: ['SiFormItemComponent', 'SiFormModule'],
38
+ names: ['inputId', 'readonly']
39
+ },
40
+ {
41
+ module: /@(siemens|simpl)\/element-ng(\/navbar-vertical)?/,
42
+ elementSelector: 'si-navbar-vertical',
43
+ componentOrModuleName: ['SiNavbarVerticalComponent', 'SiNavbarVerticalModule'],
44
+ names: ['autoCollapseDelay']
45
+ },
46
+ {
47
+ module: /@(siemens|simpl)\/element-ng(\/split)?/,
48
+ elementSelector: 'si-split-part',
49
+ componentOrModuleName: ['SiSplitPartComponent', 'SiSplitModule'],
50
+ names: ['headerStatusColor', 'headerStatusIconClass']
51
+ },
52
+ {
53
+ module: /@(siemens|simpl)\/element-ng(\/navbar-vertical)?/,
54
+ elementSelector: 'si-tree-view',
55
+ componentOrModuleName: ['SiTreeViewComponent', 'SiTreeViewModule'],
56
+ names: ['disableFilledIcons', 'trackByFunction']
57
+ }
58
+ ];
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import * as ts from 'typescript';
6
+ import { EmitHint } from 'typescript';
7
+ import { discoverSourceFiles, getImportSpecifiers, renameApi, renameAttribute, renameElementTag, renameIdentifier, removeSymbol } from '../../utils/index.js';
8
+ import { getElementMigrationData } from '../data/index.js';
9
+ export const elementMigrationRule = (options) => {
10
+ return async (tree, context) => {
11
+ const tsSourceFiles = await discoverSourceFiles(tree, context, options.path);
12
+ const migrationData = getElementMigrationData();
13
+ for (const filePath of tsSourceFiles) {
14
+ const content = tree.read(filePath);
15
+ if (!content) {
16
+ continue;
17
+ }
18
+ const sourceFile = ts.createSourceFile(filePath, content.toString(), ts.ScriptTarget.Latest, true);
19
+ let recorder = undefined;
20
+ let printer = undefined;
21
+ // Remove the ifs when it grows a bit more and split into multiple functions
22
+ if (migrationData.componentNameChanges) {
23
+ const changeInstructions = renameIdentifier({
24
+ sourceFile,
25
+ renamingInstructions: migrationData.componentNameChanges
26
+ });
27
+ for (const changeInstruction of changeInstructions) {
28
+ recorder ??= tree.beginUpdate(filePath);
29
+ printer ??= ts.createPrinter();
30
+ recorder.remove(changeInstruction.start, changeInstruction.width);
31
+ recorder.insertLeft(changeInstruction.start, printer.printNode(EmitHint.Unspecified, changeInstruction.newNode, sourceFile));
32
+ }
33
+ }
34
+ if (migrationData.attributeSelectorChanges) {
35
+ recorder ??= tree.beginUpdate(filePath);
36
+ for (const change of migrationData.attributeSelectorChanges) {
37
+ renameAttribute({
38
+ tree,
39
+ recorder,
40
+ sourceFile,
41
+ filePath,
42
+ fromName: change.replace,
43
+ toName: change.replaceWith
44
+ });
45
+ }
46
+ }
47
+ if (migrationData.elementSelectorChanges) {
48
+ recorder ??= tree.beginUpdate(filePath);
49
+ for (const change of migrationData.elementSelectorChanges) {
50
+ renameElementTag({
51
+ tree,
52
+ recorder,
53
+ sourceFile,
54
+ filePath,
55
+ fromName: change.replace,
56
+ toName: change.replaceWith
57
+ });
58
+ }
59
+ }
60
+ if (migrationData.outputNameChanges) {
61
+ recorder ??= tree.beginUpdate(filePath);
62
+ for (const change of migrationData.outputNameChanges) {
63
+ const importSpecifiers = getImportSpecifiers(sourceFile, change.module, change.componentOrModuleName);
64
+ if (!importSpecifiers?.length) {
65
+ continue;
66
+ }
67
+ renameApi({
68
+ tree,
69
+ recorder,
70
+ sourceFile,
71
+ filePath,
72
+ elementName: change.elementSelector,
73
+ apis: change.apiMappings
74
+ });
75
+ }
76
+ }
77
+ if (migrationData.symbolRemovalChanges) {
78
+ recorder ??= tree.beginUpdate(filePath);
79
+ for (const change of migrationData.symbolRemovalChanges) {
80
+ const importSpecifiers = getImportSpecifiers(sourceFile, change.module, change.componentOrModuleName);
81
+ if (!importSpecifiers?.length) {
82
+ continue;
83
+ }
84
+ removeSymbol({
85
+ tree,
86
+ recorder,
87
+ sourceFile,
88
+ filePath,
89
+ elementName: change.elementSelector,
90
+ attributeSelector: change.attributeSelector,
91
+ names: change.names
92
+ });
93
+ }
94
+ }
95
+ if (recorder) {
96
+ tree.commitUpdate(recorder);
97
+ }
98
+ }
99
+ return tree;
100
+ };
101
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ export * from './element-migration.js';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { chain } from '@angular-devkit/schematics';
6
+ import { actionModalMigrationRule } from './action-modal-migration/index.js';
7
+ import { elementMigrationRule } from './element-migration/index.js';
8
+ import { wizardMigrationRule } from './wizard-migration/index.js';
9
+ export const v47to48Migration = (options) => {
10
+ return (tree, context) => {
11
+ context.logger.info('🚀 Starting migration from v47 to v48...');
12
+ return chain([
13
+ actionModalMigrationRule(options),
14
+ elementMigrationRule(options),
15
+ wizardMigrationRule(options)
16
+ ])(tree, context);
17
+ };
18
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "v47to48MigrationSchema",
4
+ "title": "Siemens Element v47 to v48 migration",
5
+ "type": "object",
6
+ "properties": {
7
+ "path": {
8
+ "type": "string",
9
+ "description": "Path to the directory where the migration should be applied.",
10
+ "x-prompt": "Which directory do you want to migrate?",
11
+ "format": "path",
12
+ "default": "/"
13
+ }
14
+ },
15
+ "required": []
16
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { join, dirname } from 'path';
6
+ import * as ts from 'typescript';
7
+ import { discoverSourceFiles, findElement, getImportSpecifiers, getInlineTemplates, getTemplateUrl } from '../../utils/index.js';
8
+ export const wizardMigrationRule = (options) => {
9
+ return async (tree, context) => {
10
+ context.logger.info('🔄 Migrating wizard api...');
11
+ const tsSourceFiles = await discoverSourceFiles(tree, context, options.path);
12
+ for (const filePath of tsSourceFiles) {
13
+ const content = tree.read(filePath);
14
+ if (!content) {
15
+ continue;
16
+ }
17
+ const sourceFile = ts.createSourceFile(filePath, content.toString(), ts.ScriptTarget.Latest, true);
18
+ const modulePathToMatch = /@(siemens|simpl)\/element-ng(\/wizard)?/;
19
+ const wizardImports = getImportSpecifiers(sourceFile, modulePathToMatch, [
20
+ 'SiWizardComponent',
21
+ 'SiWizardModule'
22
+ ]);
23
+ if (!wizardImports?.length) {
24
+ continue;
25
+ }
26
+ const recorder = tree.beginUpdate(filePath);
27
+ renameApi({
28
+ tree,
29
+ filePath,
30
+ sourceFile,
31
+ recorder,
32
+ elementName: 'si-wizard'
33
+ });
34
+ if (recorder) {
35
+ tree.commitUpdate(recorder);
36
+ }
37
+ }
38
+ };
39
+ };
40
+ const renameApi = ({ tree, filePath, sourceFile, recorder, elementName }) => {
41
+ getInlineTemplates(sourceFile).forEach(template => addOrRemoveInlineNavigationAttribute({
42
+ template: template.text,
43
+ offset: template.getStart() + 1,
44
+ elementName,
45
+ recorder
46
+ }));
47
+ getTemplateUrl(sourceFile).forEach(templateUrl => {
48
+ const templatePath = join(dirname(filePath), templateUrl);
49
+ const templateContent = tree.read(templatePath).toString('utf-8');
50
+ const templateRecorder = tree.beginUpdate(templatePath);
51
+ addOrRemoveInlineNavigationAttribute({
52
+ template: templateContent,
53
+ offset: 0,
54
+ elementName,
55
+ recorder: templateRecorder
56
+ });
57
+ tree.commitUpdate(templateRecorder);
58
+ });
59
+ };
60
+ /**
61
+ * Migrates the inlineNavigation attribute on wizard elements.
62
+ * - Adds `inlineNavigation` when not present (new default: true)
63
+ * - Removes `[inlineNavigation]="false"` (false is now the default)
64
+ * - Keeps `[inlineNavigation]="true"` and dynamic bindings unchanged
65
+ */
66
+ const addOrRemoveInlineNavigationAttribute = ({ template, offset, recorder, elementName }) => {
67
+ const elements = findElement(template, element => element.name === elementName);
68
+ for (const element of elements) {
69
+ const inlineNavigationAttr = element.attrs.find(attr => attr.name === '[inlineNavigation]' || attr.name === 'inlineNavigation');
70
+ if (!inlineNavigationAttr) {
71
+ // No attribute exists → Add default inlineNavigation
72
+ const insertPosition = element.startSourceSpan.end.offset + offset - 1;
73
+ recorder.insertLeft(insertPosition, ' inlineNavigation');
74
+ }
75
+ else if ((inlineNavigationAttr.name === '[inlineNavigation]' ||
76
+ inlineNavigationAttr.name === 'inlineNavigation') &&
77
+ inlineNavigationAttr.value === 'false') {
78
+ const { start, end } = inlineNavigationAttr.sourceSpan;
79
+ const length = end.offset - start.offset;
80
+ recorder.remove(start.offset + offset, length);
81
+ // Also remove extra whitespace if present
82
+ const nextChar = template.charAt(end.offset);
83
+ if (nextChar === ' ') {
84
+ recorder.remove(end.offset + offset, 1);
85
+ }
86
+ }
87
+ }
88
+ };
@@ -26,10 +26,10 @@ export const scssImportMigration = (_options) => {
26
26
  * ```
27
27
  */
28
28
  export const scssMigrationRule = (_options) => {
29
- return (tree, context) => {
29
+ return async (tree, context) => {
30
30
  const rules = [];
31
31
  context.logger.info('🎨 Migrating SCSS styles...');
32
- const globalStyles = getGlobalStyles(tree);
32
+ const globalStyles = await getGlobalStyles(tree);
33
33
  for (const style of globalStyles) {
34
34
  if (style.endsWith('.scss') || style.endsWith('.sass')) {
35
35
  const content = tree.readText(style);
@@ -51,7 +51,7 @@ export const scssMigrationRule = (_options) => {
51
51
  }
52
52
  }
53
53
  }
54
- const scssFiles = discoverSourceFiles(tree, context, _options.path, '.scss');
54
+ const scssFiles = await discoverSourceFiles(tree, context, _options.path, '.scss');
55
55
  for (const filePath of scssFiles) {
56
56
  const content = tree.readText(filePath);
57
57
  if (content.includes(STYLE_REPLACEMENTS[0].replace) ||
@@ -10,7 +10,8 @@ export const simplSiemensMigration = (_options) => {
10
10
  context.logger.info('🚀 Starting Simpl to Siemens migration...');
11
11
  const chainedRules = chain([
12
12
  schematic('migrate-ts-imports-to-siemens', _options),
13
- schematic('migrate-scss-imports-to-siemens', _options)
13
+ schematic('migrate-scss-imports-to-siemens', _options),
14
+ schematic('migrate-v47-to-v48', _options)
14
15
  ]);
15
16
  return chainedRules(tree, context);
16
17
  };
@@ -27,10 +27,10 @@ export const tsImportMigration = (_options) => {
27
27
  * ```
28
28
  */
29
29
  export const tsImportMigrationRule = (_options) => {
30
- return (tree, context) => {
30
+ return async (tree, context) => {
31
31
  const rules = [];
32
32
  context.logger.info('📦 Migrating TypeScript imports...');
33
- const sourceFiles = discoverSourceFiles(tree, context, _options.path);
33
+ const sourceFiles = await discoverSourceFiles(tree, context, _options.path);
34
34
  for (const filePath of sourceFiles) {
35
35
  const migrations = collectMigrationImports(filePath, tree, context);
36
36
  const { imports, toRemoveImports } = migrations;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Copyright (c) Siemens 2016 - 2025
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { HtmlParser, RecursiveVisitor, visitAll } from '@angular/compiler';
6
+ export const findElement = (template, filter) => {
7
+ const { rootNodes, errors } = parseTemplate(template);
8
+ // Only evaluate templates without parse errors.
9
+ if (!errors.length) {
10
+ const visitor = new ElementCollector(filter);
11
+ visitAll(visitor, rootNodes);
12
+ return visitor.matches;
13
+ }
14
+ return [];
15
+ };
16
+ export const findAttribute = (template, filter) => {
17
+ const { rootNodes, errors } = parseTemplate(template);
18
+ // Only evaluate templates without parse errors.
19
+ if (!errors.length) {
20
+ const visitor = new AttributeCollector(filter);
21
+ visitAll(visitor, rootNodes);
22
+ return visitor.matches;
23
+ }
24
+ return [];
25
+ };
26
+ const parseTemplate = (template) => {
27
+ return new HtmlParser().parse(template, '', {
28
+ // Allows for ICUs to be parsed.
29
+ tokenizeExpansionForms: true,
30
+ // Explicitly disable blocks so that their characters are treated as plain text.
31
+ tokenizeBlocks: true,
32
+ preserveLineEndings: true
33
+ });
34
+ };
35
+ class ElementCollector extends RecursiveVisitor {
36
+ filter;
37
+ /**
38
+ * All elements which match the filter
39
+ *
40
+ * @defaultValue []
41
+ */
42
+ matches = [];
43
+ constructor(filter) {
44
+ super();
45
+ this.filter = filter;
46
+ }
47
+ visitElement(ast, context) {
48
+ if (this.filter(ast)) {
49
+ this.matches.push(ast);
50
+ }
51
+ super.visitElement(ast, context);
52
+ }
53
+ }
54
+ class AttributeCollector extends RecursiveVisitor {
55
+ filter;
56
+ /**
57
+ * All attributes which match the filter
58
+ *
59
+ * @defaultValue []
60
+ */
61
+ matches = [];
62
+ constructor(filter) {
63
+ super();
64
+ this.filter = filter;
65
+ }
66
+ visitAttribute(ast, context) {
67
+ if (this.filter(ast)) {
68
+ this.matches.push(ast);
69
+ }
70
+ super.visitAttribute(ast, context);
71
+ }
72
+ }
@@ -2,7 +2,9 @@
2
2
  * Copyright (c) Siemens 2016 - 2025
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
+ export * from './html-utils.js';
5
6
  export * from './project-utils.js';
6
- export * from './ts-utils.js';
7
- export * from './testing.js';
8
7
  export * from './schematics-file-system.js';
8
+ export * from './template-utils.js';
9
+ export * from './testing.js';
10
+ export * from './ts-utils.js';
@@ -4,44 +4,46 @@
4
4
  */
5
5
  import { normalize } from '@angular-devkit/core';
6
6
  import { SchematicsException } from '@angular-devkit/schematics';
7
+ import { allTargetOptions, allWorkspaceTargets, getWorkspace } from '@schematics/angular/utility/workspace';
7
8
  import { dirname, isAbsolute, resolve } from 'path';
8
9
  import { parseTsconfigFile } from './ts-utils.js';
9
- export const getGlobalStyles = (tree) => {
10
+ export const getGlobalStyles = async (tree) => {
10
11
  const globalStyles = new Set();
11
- for (const target of getTargets(getWorkspace(tree))) {
12
- if (target.options?.styles && Array.isArray(target.options.styles)) {
13
- target.options.styles.forEach((style) => {
14
- if (typeof style === 'string') {
15
- globalStyles.add(normalize(style));
12
+ for (const [name, target] of allWorkspaceTargets(await getWorkspace(tree))) {
13
+ if (['build', 'test'].includes(name)) {
14
+ for (const [, opt] of allTargetOptions(target)) {
15
+ if (opt.styles && Array.isArray(opt.styles)) {
16
+ opt.styles.forEach((style) => {
17
+ if (typeof style === 'string') {
18
+ globalStyles.add(normalize(style));
19
+ }
20
+ });
16
21
  }
17
- });
22
+ }
18
23
  }
19
24
  }
20
25
  return [...globalStyles];
21
26
  };
22
- export const getWorkspace = (tree) => {
23
- const workspace = tree.read('/angular.json');
24
- if (!workspace) {
25
- throw new SchematicsException('Could not find angular.json');
26
- }
27
- return JSON.parse(workspace.toString());
28
- };
29
- export const getTsConfigPaths = (tree) => {
27
+ export const getTsConfigPaths = async (tree) => {
30
28
  const buildPaths = new Set();
31
- for (const target of getTargets(getWorkspace(tree))) {
32
- if (target.options?.tsConfig && typeof target.options.tsConfig === 'string') {
33
- const tsConfig = target.options.tsConfig;
34
- if (tree.exists(tsConfig)) {
35
- buildPaths.add(normalize(tsConfig));
29
+ for (const [name, target] of allWorkspaceTargets(await getWorkspace(tree))) {
30
+ if (['build', 'test'].includes(name)) {
31
+ for (const [, opt] of allTargetOptions(target)) {
32
+ if (typeof opt?.tsConfig === 'string') {
33
+ const tsConfig = opt.tsConfig;
34
+ if (tree.exists(tsConfig)) {
35
+ buildPaths.add(normalize(tsConfig));
36
+ }
37
+ }
36
38
  }
37
39
  }
38
40
  }
39
41
  return [...buildPaths];
40
42
  };
41
- export const discoverSourceFiles = (tree, context, projectPath, extension = '.ts') => {
43
+ export const discoverSourceFiles = async (tree, context, projectPath, extension = '.ts') => {
42
44
  const basePath = normalize(process.cwd());
43
45
  // Wrap the tree to force full paths since parsing the typescript config requires full paths.
44
- const tsConfigs = getTsConfigPaths(tree);
46
+ const tsConfigs = await getTsConfigPaths(tree);
45
47
  if (!tsConfigs.length) {
46
48
  throw new SchematicsException('Could not find any tsconfig file. Cannot run the migration.');
47
49
  }
@@ -60,16 +62,3 @@ export const discoverSourceFiles = (tree, context, projectPath, extension = '.ts
60
62
  }
61
63
  return Array.from(new Set(sourceFiles)).map(p => p.substring(basePath.length + 1));
62
64
  };
63
- function* getTargets(workspace, targetNames = ['build', 'test']) {
64
- for (const [, projectRaw] of Object.entries(workspace.projects)) {
65
- const project = projectRaw;
66
- if (!project.architect) {
67
- continue;
68
- }
69
- for (const [name, target] of Object.entries(project.architect)) {
70
- if (targetNames.includes(name) && target) {
71
- yield target;
72
- }
73
- }
74
- }
75
- }