@eui/core 23.0.0-alpha.10 → 23.0.0-alpha.12
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 +53 -0
- package/docs/changelog.html +75 -0
- package/docs/injectables/EuiAppShellService.html +2 -2
- package/docs/interfaces/Schema-13.html +1 -1
- package/docs/interfaces/Schema-14.html +1 -1
- package/docs/interfaces/Schema-16.html +1 -1
- package/docs/interfaces/Schema-17.html +1 -1
- package/docs/interfaces/Schema-20.html +1 -1
- package/docs/interfaces/Schema-21.html +1 -1
- package/docs/interfaces/Schema-4.html +1 -1
- package/docs/interfaces/Schema-5.html +1 -1
- package/docs/interfaces/Schema-6.html +1 -1
- package/docs/interfaces/UIState.html +45 -0
- package/docs/js/search/search_index.js +2 -2
- package/docs/json/documentation.json +239 -227
- package/docs/llms.txt +53 -52
- package/docs/miscellaneous/functions.html +56 -56
- package/docs/miscellaneous/variables.html +32 -32
- package/docs/properties.html +1 -1
- package/fesm2022/eui-core.mjs +107 -112
- package/fesm2022/eui-core.mjs.map +1 -1
- package/package.json +2 -2
- package/types/eui-core.d.ts +3 -2
- package/types/eui-core.d.ts.map +1 -1
|
@@ -2036,12 +2036,12 @@
|
|
|
2036
2036
|
},
|
|
2037
2037
|
{
|
|
2038
2038
|
"name": "Schema",
|
|
2039
|
-
"id": "interface-Schema-
|
|
2040
|
-
"file": "packages/core/schematics/
|
|
2039
|
+
"id": "interface-Schema-9c5e016857e1416ac7bbb881973e644c0f578a53bd432bb951c1676ac3f2a6631bfe3344860346a081b841217278723e0bb0bf2fa35fb869de67cb0cc8d99849",
|
|
2040
|
+
"file": "packages/core/schematics/add-eui-imports/index.ts",
|
|
2041
2041
|
"deprecated": false,
|
|
2042
2042
|
"deprecationMessage": "",
|
|
2043
2043
|
"type": "interface",
|
|
2044
|
-
"sourceCode": "export interface Schema {\n /** The path to scan for files to migrate */\n path?: string;\n /** Whether to perform a dry run without making changes */\n dryRun?: boolean;\n}\n",
|
|
2044
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
|
|
2045
2045
|
"displayName": "Schema",
|
|
2046
2046
|
"properties": [
|
|
2047
2047
|
{
|
|
@@ -2052,9 +2052,9 @@
|
|
|
2052
2052
|
"type": "boolean",
|
|
2053
2053
|
"indexKey": "",
|
|
2054
2054
|
"optional": true,
|
|
2055
|
-
"description": "
|
|
2056
|
-
"line":
|
|
2057
|
-
"rawdescription": "\
|
|
2055
|
+
"description": "",
|
|
2056
|
+
"line": 9,
|
|
2057
|
+
"rawdescription": "\n"
|
|
2058
2058
|
},
|
|
2059
2059
|
{
|
|
2060
2060
|
"name": "path",
|
|
@@ -2064,9 +2064,21 @@
|
|
|
2064
2064
|
"type": "string",
|
|
2065
2065
|
"indexKey": "",
|
|
2066
2066
|
"optional": true,
|
|
2067
|
-
"description": "
|
|
2068
|
-
"line":
|
|
2069
|
-
"rawdescription": "\
|
|
2067
|
+
"description": "",
|
|
2068
|
+
"line": 8,
|
|
2069
|
+
"rawdescription": "\n"
|
|
2070
|
+
},
|
|
2071
|
+
{
|
|
2072
|
+
"name": "useClassArray",
|
|
2073
|
+
"coverageIgnore": false,
|
|
2074
|
+
"deprecated": false,
|
|
2075
|
+
"deprecationMessage": "",
|
|
2076
|
+
"type": "boolean",
|
|
2077
|
+
"indexKey": "",
|
|
2078
|
+
"optional": true,
|
|
2079
|
+
"description": "",
|
|
2080
|
+
"line": 10,
|
|
2081
|
+
"rawdescription": "\n"
|
|
2070
2082
|
}
|
|
2071
2083
|
],
|
|
2072
2084
|
"indexSignatures": [],
|
|
@@ -2080,12 +2092,12 @@
|
|
|
2080
2092
|
},
|
|
2081
2093
|
{
|
|
2082
2094
|
"name": "Schema",
|
|
2083
|
-
"id": "interface-Schema-
|
|
2084
|
-
"file": "packages/core/schematics/
|
|
2095
|
+
"id": "interface-Schema-4fe31ff3e9f1d34845a6b865d605e215f33552094b88c3d0eab0b180187fe64ce4d68d687516cb3d62c57d2678a103969b2dacbb18a49b26060f78096678fcce-1",
|
|
2096
|
+
"file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
|
|
2085
2097
|
"deprecated": false,
|
|
2086
2098
|
"deprecationMessage": "",
|
|
2087
2099
|
"type": "interface",
|
|
2088
|
-
"sourceCode": "
|
|
2100
|
+
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst MULTIPLE_EMPTY_LINES = /\\n{3,}/g;\n\nexport function fixNoMultipleEmptyLines(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (filePath) => {\n const buffer = tree.read(filePath);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n const result = original.replace(MULTIPLE_EMPTY_LINES, '\\n\\n');\n\n if (result !== original) {\n if (filePath.endsWith('.ts')) {\n const sourceFile = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if ((sourceFile as any).parseDiagnostics?.length) {\n context.logger.warn(`Skipping ${filePath}: file would not parse after transformation.`);\n return;\n }\n }\n if (options.dryRun) {\n logDryRun(context, `Would collapse multiple empty lines in ${filePath}`);\n } else {\n tree.overwrite(filePath, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Fixed multiple empty lines in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts') && !file.endsWith('.html') && !file.endsWith('.scss') && !file.endsWith('.css')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2089
2101
|
"displayName": "Schema",
|
|
2090
2102
|
"properties": [
|
|
2091
2103
|
{
|
|
@@ -2096,21 +2108,9 @@
|
|
|
2096
2108
|
"type": "boolean",
|
|
2097
2109
|
"indexKey": "",
|
|
2098
2110
|
"optional": true,
|
|
2099
|
-
"description": "
|
|
2111
|
+
"description": "",
|
|
2100
2112
|
"line": 7,
|
|
2101
|
-
"rawdescription": "\
|
|
2102
|
-
},
|
|
2103
|
-
{
|
|
2104
|
-
"name": "mwp",
|
|
2105
|
-
"coverageIgnore": false,
|
|
2106
|
-
"deprecated": false,
|
|
2107
|
-
"deprecationMessage": "",
|
|
2108
|
-
"type": "boolean",
|
|
2109
|
-
"indexKey": "",
|
|
2110
|
-
"optional": true,
|
|
2111
|
-
"description": "<p>Whether to apply MyWorkplace-specific replacements</p>\n",
|
|
2112
|
-
"line": 5,
|
|
2113
|
-
"rawdescription": "\nWhether to apply MyWorkplace-specific replacements"
|
|
2113
|
+
"rawdescription": "\n"
|
|
2114
2114
|
},
|
|
2115
2115
|
{
|
|
2116
2116
|
"name": "path",
|
|
@@ -2120,9 +2120,9 @@
|
|
|
2120
2120
|
"type": "string",
|
|
2121
2121
|
"indexKey": "",
|
|
2122
2122
|
"optional": true,
|
|
2123
|
-
"description": "
|
|
2124
|
-
"line":
|
|
2125
|
-
"rawdescription": "\
|
|
2123
|
+
"description": "",
|
|
2124
|
+
"line": 6,
|
|
2125
|
+
"rawdescription": "\n"
|
|
2126
2126
|
}
|
|
2127
2127
|
],
|
|
2128
2128
|
"indexSignatures": [],
|
|
@@ -2139,12 +2139,12 @@
|
|
|
2139
2139
|
},
|
|
2140
2140
|
{
|
|
2141
2141
|
"name": "Schema",
|
|
2142
|
-
"id": "interface-Schema-
|
|
2143
|
-
"file": "packages/core/schematics/migrate
|
|
2142
|
+
"id": "interface-Schema-5cd6db1920bd5b70a44c0b8a7f7e30f600bfd16a9950f218e6d451a1755ced95b462ef9a62ee87e39bcb8c392981b8d2597895bf0a272fd8aea71f03429ed976-2",
|
|
2143
|
+
"file": "packages/core/schematics/icon-migrate/schema.ts",
|
|
2144
2144
|
"deprecated": false,
|
|
2145
2145
|
"deprecationMessage": "",
|
|
2146
2146
|
"type": "interface",
|
|
2147
|
-
"sourceCode": "
|
|
2147
|
+
"sourceCode": "export interface Schema {\n /** The path to scan for files to migrate */\n path?: string;\n /** Whether to perform a dry run without making changes */\n dryRun?: boolean;\n}\n",
|
|
2148
2148
|
"displayName": "Schema",
|
|
2149
2149
|
"properties": [
|
|
2150
2150
|
{
|
|
@@ -2155,9 +2155,9 @@
|
|
|
2155
2155
|
"type": "boolean",
|
|
2156
2156
|
"indexKey": "",
|
|
2157
2157
|
"optional": true,
|
|
2158
|
-
"description": "",
|
|
2159
|
-
"line":
|
|
2160
|
-
"rawdescription": "\
|
|
2158
|
+
"description": "<p>Whether to perform a dry run without making changes</p>\n",
|
|
2159
|
+
"line": 5,
|
|
2160
|
+
"rawdescription": "\nWhether to perform a dry run without making changes"
|
|
2161
2161
|
},
|
|
2162
2162
|
{
|
|
2163
2163
|
"name": "path",
|
|
@@ -2167,9 +2167,9 @@
|
|
|
2167
2167
|
"type": "string",
|
|
2168
2168
|
"indexKey": "",
|
|
2169
2169
|
"optional": true,
|
|
2170
|
-
"description": "",
|
|
2171
|
-
"line":
|
|
2172
|
-
"rawdescription": "\
|
|
2170
|
+
"description": "<p>The path to scan for files to migrate</p>\n",
|
|
2171
|
+
"line": 3,
|
|
2172
|
+
"rawdescription": "\nThe path to scan for files to migrate"
|
|
2173
2173
|
}
|
|
2174
2174
|
],
|
|
2175
2175
|
"indexSignatures": [],
|
|
@@ -2186,12 +2186,12 @@
|
|
|
2186
2186
|
},
|
|
2187
2187
|
{
|
|
2188
2188
|
"name": "Schema",
|
|
2189
|
-
"id": "interface-Schema-
|
|
2190
|
-
"file": "packages/core/schematics/migrate
|
|
2189
|
+
"id": "interface-Schema-56b9fe60701ca349dc90e152829b0a18bb7a8a9bb303c4bac05f9764cd2e933cce0879775ca17168b4c881e202d0258af3668a2a3a1b940e561f7e4b2349b5cc-3",
|
|
2190
|
+
"file": "packages/core/schematics/migrate/schema.ts",
|
|
2191
2191
|
"deprecated": false,
|
|
2192
2192
|
"deprecationMessage": "",
|
|
2193
2193
|
"type": "interface",
|
|
2194
|
-
"sourceCode": "
|
|
2194
|
+
"sourceCode": "export interface Schema {\n /** The path to scan for files to migrate */\n path?: string;\n /** Whether to apply MyWorkplace-specific replacements */\n mwp?: boolean;\n /** Whether to perform a dry run without making changes */\n dryRun?: boolean;\n}\n",
|
|
2195
2195
|
"displayName": "Schema",
|
|
2196
2196
|
"properties": [
|
|
2197
2197
|
{
|
|
@@ -2202,9 +2202,21 @@
|
|
|
2202
2202
|
"type": "boolean",
|
|
2203
2203
|
"indexKey": "",
|
|
2204
2204
|
"optional": true,
|
|
2205
|
-
"description": "",
|
|
2206
|
-
"line":
|
|
2207
|
-
"rawdescription": "\
|
|
2205
|
+
"description": "<p>Whether to perform a dry run without making changes</p>\n",
|
|
2206
|
+
"line": 7,
|
|
2207
|
+
"rawdescription": "\nWhether to perform a dry run without making changes"
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
"name": "mwp",
|
|
2211
|
+
"coverageIgnore": false,
|
|
2212
|
+
"deprecated": false,
|
|
2213
|
+
"deprecationMessage": "",
|
|
2214
|
+
"type": "boolean",
|
|
2215
|
+
"indexKey": "",
|
|
2216
|
+
"optional": true,
|
|
2217
|
+
"description": "<p>Whether to apply MyWorkplace-specific replacements</p>\n",
|
|
2218
|
+
"line": 5,
|
|
2219
|
+
"rawdescription": "\nWhether to apply MyWorkplace-specific replacements"
|
|
2208
2220
|
},
|
|
2209
2221
|
{
|
|
2210
2222
|
"name": "path",
|
|
@@ -2214,9 +2226,9 @@
|
|
|
2214
2226
|
"type": "string",
|
|
2215
2227
|
"indexKey": "",
|
|
2216
2228
|
"optional": true,
|
|
2217
|
-
"description": "",
|
|
2218
|
-
"line":
|
|
2219
|
-
"rawdescription": "\
|
|
2229
|
+
"description": "<p>The path to scan for files to migrate</p>\n",
|
|
2230
|
+
"line": 3,
|
|
2231
|
+
"rawdescription": "\nThe path to scan for files to migrate"
|
|
2220
2232
|
}
|
|
2221
2233
|
],
|
|
2222
2234
|
"indexSignatures": [],
|
|
@@ -2233,12 +2245,12 @@
|
|
|
2233
2245
|
},
|
|
2234
2246
|
{
|
|
2235
2247
|
"name": "Schema",
|
|
2236
|
-
"id": "interface-Schema-
|
|
2237
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2248
|
+
"id": "interface-Schema-e96d29c1785a16147de773cebe7ab5d4ed82aa23c6b1f7737113252a14d156e236f46f7a16340432dec900b664c8005be0aef5d0a628c2f5b8dd2ec7e41edf56-4",
|
|
2249
|
+
"file": "packages/core/schematics/migrate-eui-accent/index.ts",
|
|
2238
2250
|
"deprecated": false,
|
|
2239
2251
|
"deprecationMessage": "",
|
|
2240
2252
|
"type": "interface",
|
|
2241
|
-
"sourceCode": "import { parseTemplate,
|
|
2253
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'euiAccent';\nconst NEW_NAME = 'euiPrimary';\nconst EUI_DIRECTIVES = ['euiButton', 'euiList', 'euiListItem'];\n\nexport function migrateEuiAccent(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_NAME)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n result = renameTsPropertyAccesses(result);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would replace '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on EUI components in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction isEuiElement(element: TmplAstElement): boolean {\n if (element.name.startsWith('eui-')) return true;\n return element.attributes.some((a) => EUI_DIRECTIVES.includes(a.name)) ||\n element.inputs.some((i) => EUI_DIRECTIVES.includes(i.name));\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_NAME)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction renameTsPropertyAccesses(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: NEW_NAME });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isEuiElement(node)) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2242
2254
|
"displayName": "Schema",
|
|
2243
2255
|
"properties": [
|
|
2244
2256
|
{
|
|
@@ -2280,12 +2292,12 @@
|
|
|
2280
2292
|
},
|
|
2281
2293
|
{
|
|
2282
2294
|
"name": "Schema",
|
|
2283
|
-
"id": "interface-Schema-
|
|
2284
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2295
|
+
"id": "interface-Schema-585b211d989563bbec5bd67bcdb58d5f264396adbe0ed2e51d0b4a0ecaf8b3ec36d821647c170d616bb409fc603c5a4c0e699eb7511e02c6add8d8670fc9cb9f-5",
|
|
2296
|
+
"file": "packages/core/schematics/migrate-eui-alert/index.ts",
|
|
2285
2297
|
"deprecated": false,
|
|
2286
2298
|
"deprecationMessage": "",
|
|
2287
2299
|
"type": "interface",
|
|
2288
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst
|
|
2300
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['alertIconType', 'alertIconFillColor', 'isMuted', 'isBordered']);\n\nexport function migrateEuiAlert(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-alert') && !original.includes('euiAlert')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove deprecated inputs in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (![...REMOVED_INPUTS].some((input) => original.includes(input))) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && REMOVED_INPUTS.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"${node.name.text}\" is no longer a valid input on eui-alert. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-alert inputs from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n // Apply removals in reverse order\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n // Simple approach: find template strings containing eui-alert and process them\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-alert') && !templateContent.includes('euiAlert')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-alert' || hasAttribute(node, 'euiAlert')) {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction hasAttribute(element: TmplAstElement, name: string): boolean {\n return element.attributes.some((a) => a.name === name);\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
|
|
2289
2301
|
"displayName": "Schema",
|
|
2290
2302
|
"properties": [
|
|
2291
2303
|
{
|
|
@@ -2327,12 +2339,12 @@
|
|
|
2327
2339
|
},
|
|
2328
2340
|
{
|
|
2329
2341
|
"name": "Schema",
|
|
2330
|
-
"id": "interface-Schema-
|
|
2331
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2342
|
+
"id": "interface-Schema-c7e2cc0add09c97d83ec2675d59c86951681f8e2d49f133fb2f216ca98d0da1fdf892d45af31f19ecbbfd0d32f4ad70dd99e862f75725d3812f66a73ec34362c-6",
|
|
2343
|
+
"file": "packages/core/schematics/migrate-eui-avatar/index.ts",
|
|
2332
2344
|
"deprecated": false,
|
|
2333
2345
|
"deprecationMessage": "",
|
|
2334
2346
|
"type": "interface",
|
|
2335
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['
|
|
2347
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['isFlat']);\n\nexport function migrateEuiAvatar(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-avatar') && !original.includes('euiAvatar')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'isFlat' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (!original.includes('isFlat')) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'isFlat') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"isFlat\" is no longer a valid input on eui-avatar. Remove this assignment.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-avatar 'isFlat' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-avatar') && !templateContent.includes('euiAvatar')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction isAvatarElement(element: TmplAstElement): boolean {\n if (element.name === 'eui-avatar') return true;\n return element.attributes.some((a) => a.name === 'euiAvatar');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isAvatarElement(node)) collectRemovals(node, removals);\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
|
|
2336
2348
|
"displayName": "Schema",
|
|
2337
2349
|
"properties": [
|
|
2338
2350
|
{
|
|
@@ -2374,12 +2386,12 @@
|
|
|
2374
2386
|
},
|
|
2375
2387
|
{
|
|
2376
2388
|
"name": "Schema",
|
|
2377
|
-
"id": "interface-Schema-
|
|
2378
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2389
|
+
"id": "interface-Schema-c0c08f5e83da9afa13ce8375f982fb280b9bde854e00c914f41b04d44deecd531496c2cee264b50314e92a6260c26cebe35741a2cf5ec068d8b6da5c5267539e-7",
|
|
2390
|
+
"file": "packages/core/schematics/migrate-eui-button/index.ts",
|
|
2379
2391
|
"deprecated": false,
|
|
2380
2392
|
"deprecationMessage": "",
|
|
2381
2393
|
"type": "interface",
|
|
2382
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
|
|
2394
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'euiButtonCall';\nconst NEW_NAME = 'euiCTAButton';\n\nexport function migrateEuiButton(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_NAME)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (!original.includes(OLD_NAME)) return;\n\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\". Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on elements with euiButton in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_NAME)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction hasEuiButtonAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiButton') ||\n element.inputs.some((i) => i.name === 'euiButton');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (hasEuiButtonAttribute(node)) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2383
2395
|
"displayName": "Schema",
|
|
2384
2396
|
"properties": [
|
|
2385
2397
|
{
|
|
@@ -2391,7 +2403,7 @@
|
|
|
2391
2403
|
"indexKey": "",
|
|
2392
2404
|
"optional": true,
|
|
2393
2405
|
"description": "",
|
|
2394
|
-
"line":
|
|
2406
|
+
"line": 8,
|
|
2395
2407
|
"rawdescription": "\n"
|
|
2396
2408
|
},
|
|
2397
2409
|
{
|
|
@@ -2403,7 +2415,7 @@
|
|
|
2403
2415
|
"indexKey": "",
|
|
2404
2416
|
"optional": true,
|
|
2405
2417
|
"description": "",
|
|
2406
|
-
"line":
|
|
2418
|
+
"line": 7,
|
|
2407
2419
|
"rawdescription": "\n"
|
|
2408
2420
|
}
|
|
2409
2421
|
],
|
|
@@ -2421,12 +2433,12 @@
|
|
|
2421
2433
|
},
|
|
2422
2434
|
{
|
|
2423
2435
|
"name": "Schema",
|
|
2424
|
-
"id": "interface-Schema-
|
|
2425
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2436
|
+
"id": "interface-Schema-39d3d52e788050bebc4e0b99ed0756f7e9727556cb090ae8796bd4261a56463fe1f479c322b23028cd4134d71e4d1dc44cd248ac62a9a148b5fc46a665466f7c-8",
|
|
2437
|
+
"file": "packages/core/schematics/migrate-eui-chip/index.ts",
|
|
2426
2438
|
"deprecated": false,
|
|
2427
2439
|
"deprecationMessage": "",
|
|
2428
2440
|
"type": "interface",
|
|
2429
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst
|
|
2441
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['isSquared']);\n\nexport function migrateEuiChip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-chip') && !original.includes('euiChip')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'isSquared' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('isSquared')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'isSquared') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"isSquared\" is no longer a valid input on eui-chip. Remove this assignment.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed deprecated eui-chip 'isSquared' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-chip') && !templateContent.includes('euiChip')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === 'eui-chip') return true;\n return element.attributes.some((a) => a.name === 'euiChip');\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) collectRemovals(node, removals);\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
|
|
2430
2442
|
"displayName": "Schema",
|
|
2431
2443
|
"properties": [
|
|
2432
2444
|
{
|
|
@@ -2468,12 +2480,12 @@
|
|
|
2468
2480
|
},
|
|
2469
2481
|
{
|
|
2470
2482
|
"name": "Schema",
|
|
2471
|
-
"id": "interface-Schema-
|
|
2472
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2483
|
+
"id": "interface-Schema-311bef7d5e4b40b356b0fdb5e35dd89e48a5c5beec29eeb865c22c2ca1c4c4fb1fd0bb4391ed0b33825a6d1d744bc3a2e9822b347fbc686a537e15b170c279dd-9",
|
|
2484
|
+
"file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
|
|
2473
2485
|
"deprecated": false,
|
|
2474
2486
|
"deprecationMessage": "",
|
|
2475
2487
|
"type": "interface",
|
|
2476
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2488
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst CHIP_LIST_TAG = 'eui-chip-list';\nconst CHIP_LIST_ATTR = 'euiChipList';\nconst CHIP_TAG = 'eui-chip';\nconst CHIP_ATTR = 'euiChip';\n\nconst PROPAGATED_INPUTS = new Set([\n 'euiPrimary', 'euiSecondary', 'euiSuccess', 'euiInfo', 'euiWarning',\n 'euiDanger', 'euiAccent', 'euiVariant', 'euiSizeS', 'euiSizeVariant',\n 'euiOutline', 'euiDisabled',\n]);\n\nconst WARN_PROPERTIES = new Set([...PROPAGATED_INPUTS, 'chipRemove', 'isChipsRemovable', 'chipsLabelTruncateCount',\n 'maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel',\n 'isChipsSorted', 'chipsSortOrder']);\n\nconst REMOVED_INPUTS = new Set(['maxVisibleChipsCount', 'isMaxVisibleChipsOpened', 'toggleLinkMoreLabel', 'toggleLinkLessLabel', 'isChipsSorted', 'chipsSortOrder']);\n\nconst TRUNCATE_PIPE_IMPORT = 'EuiTruncatePipe';\nconst TRUNCATE_PIPE_PATH = '@eui/components/pipes';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiChipList(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const filesNeedingTruncateImport = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(CHIP_LIST_TAG) && !original.includes(CHIP_LIST_ATTR)) return;\n\n let result: string;\n let addedTruncate = false;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n // Find the associated .ts file\n const tsPath = path.replace(/\\.html$/, '.ts');\n if (tree.exists(tsPath)) {\n filesNeedingTruncateImport.add(tsPath);\n } else {\n // Try component naming convention\n const componentTsPath = path.replace(/\\.html$/, '.component.ts');\n if (tree.exists(componentTsPath)) {\n filesNeedingTruncateImport.add(componentTsPath);\n }\n }\n }\n } else {\n result = migrateInlineTemplates(original);\n if (result !== original && result.includes('euiTruncate') && !original.includes('euiTruncate')) {\n addedTruncate = true;\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would move variant/size/outline inputs to child eui-chip in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n if (addedTruncate) {\n filesNeedingTruncateImport.add(path);\n }\n\n // Warn about TS usages of removed properties\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if ([...WARN_PROPERTIES].some((p) => original.includes(p))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && WARN_PROPERTIES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been removed from eui-chip-list. Move it to individual eui-chip elements.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n // Add EuiTruncatePipe import to component files that need it\n for (const tsPath of filesNeedingTruncateImport) {\n const buffer = tree.read(tsPath);\n if (!buffer) continue;\n const source = buffer.toString('utf-8');\n if (source.includes(TRUNCATE_PIPE_IMPORT)) continue;\n const result = addTruncatePipeImport(source, tsPath);\n if (result !== source) {\n if (!options.dryRun) {\n tree.overwrite(tsPath, result);\n }\n }\n }\n\n context.logger.info(`Migrated eui-chip-list inputs/outputs to child eui-chip in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction addTruncatePipeImport(source: string, filePath: string): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n const edits: Edit[] = [];\n\n // 1. Add ES import statement for EuiTruncatePipe\n let hasEsImport = false;\n let lastImportEnd = 0;\n\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n if (moduleSpec === TRUNCATE_PIPE_PATH) {\n const namedBindings = stmt.importClause?.namedBindings;\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n if (namedBindings.elements.some((el) => el.name.text === TRUNCATE_PIPE_IMPORT)) {\n hasEsImport = true;\n } else {\n // Add to existing import from same path\n const lastEl = namedBindings.elements[namedBindings.elements.length - 1];\n edits.push({ start: lastEl.getEnd(), end: lastEl.getEnd(), replacement: `, ${TRUNCATE_PIPE_IMPORT}` });\n hasEsImport = true;\n }\n }\n }\n }\n }\n\n if (!hasEsImport) {\n const importStatement = `\\nimport { ${TRUNCATE_PIPE_IMPORT} } from '${TRUNCATE_PIPE_PATH}';`;\n edits.push({ start: lastImportEnd, end: lastImportEnd, replacement: importStatement });\n }\n\n // 2. Add EuiTruncatePipe to @Component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const arr = prop.initializer;\n const arrText = source.slice(arr.getStart(sourceFile), arr.getEnd());\n if (arrText.includes(TRUNCATE_PIPE_IMPORT)) continue;\n if (arr.elements.length > 0) {\n const lastElement = arr.elements[arr.elements.length - 1];\n edits.push({ start: lastElement.getEnd(), end: lastElement.getEnd(), replacement: `,\\n ${TRUNCATE_PIPE_IMPORT}` });\n } else {\n edits.push({ start: arr.getStart(sourceFile) + 1, end: arr.getEnd() - 1, replacement: TRUNCATE_PIPE_IMPORT });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(CHIP_LIST_TAG) && !rawTemplate.includes(CHIP_LIST_ATTR)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, changes);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipListElement(node)) {\n migrateChipListElement(node, source, edits);\n }\n visitNodes(node.children, source, edits);\n }\n }\n}\n\nfunction isChipListElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_LIST_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_LIST_ATTR);\n}\n\nfunction isChipElement(element: TmplAstElement): boolean {\n if (element.name === CHIP_TAG) return true;\n return element.attributes.some((a) => a.name === CHIP_ATTR);\n}\n\nfunction findChildChips(nodes: TmplAstNode[]): TmplAstElement[] {\n const chips: TmplAstElement[] = [];\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (isChipElement(node)) {\n chips.push(node);\n } else {\n chips.push(...findChildChips(node.children));\n }\n }\n }\n return chips;\n}\n\nfunction migrateChipListElement(element: TmplAstElement, source: string, edits: Edit[]): void {\n const childChips = findChildChips(element.children);\n const insertions: string[] = [];\n let truncateValue: string | null = null;\n\n // Collect and remove propagated static attributes\n for (const attr of element.attributes) {\n if (PROPAGATED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n insertions.push(attr.value ? `${attr.name}=\"${attr.value}\"` : attr.name);\n }\n if (attr.name === 'isChipsRemovable') {\n edits.push(removalEdit(attr, source));\n insertions.push('isChipRemovable');\n }\n if (attr.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(attr, source));\n truncateValue = attr.value || null;\n }\n if (REMOVED_INPUTS.has(attr.name)) {\n edits.push(removalEdit(attr, source));\n }\n }\n\n // Collect and remove propagated bound inputs\n for (const input of element.inputs) {\n if (PROPAGATED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n insertions.push(raw);\n }\n if (input.name === 'isChipsRemovable') {\n edits.push(removalEdit(input, source));\n const valueText = extractBindingValue(input, source);\n insertions.push(`[isChipRemovable]=\"${valueText}\"`);\n }\n if (input.name === 'chipsLabelTruncateCount') {\n edits.push(removalEdit(input, source));\n truncateValue = extractBindingValue(input, source);\n }\n if (REMOVED_INPUTS.has(input.name)) {\n edits.push(removalEdit(input, source));\n }\n }\n\n // Collect and remove (chipRemove) output\n let chipRemoveHandler: string | null = null;\n for (const output of element.outputs) {\n if (output.name === 'chipRemove') {\n edits.push(removalEdit(output, source));\n chipRemoveHandler = extractHandlerExpression(output, source);\n }\n }\n\n if (chipRemoveHandler) {\n insertions.push(`(remove)=\"${chipRemoveHandler}\"`);\n }\n\n // Add collected attributes to each child eui-chip\n if (insertions.length > 0) {\n for (const chip of childChips) {\n const existingNames = getExistingAttrNames(chip);\n const toInsert = insertions.filter((ins) => {\n const name = extractAttrName(ins);\n return !existingNames.has(name);\n });\n if (toInsert.length > 0) {\n const insertPos = chip.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' ' + toInsert.join(' ') });\n }\n }\n }\n\n // Add euiTruncate pipe to chip label content\n if (truncateValue) {\n for (const chip of childChips) {\n const labelEdit = buildTruncatePipeEdit(chip, source, truncateValue);\n if (labelEdit) edits.push(labelEdit);\n }\n }\n}\n\nfunction buildTruncatePipeEdit(chip: TmplAstElement, source: string, truncateValue: string): Edit | null {\n // Find <span euiLabel>...</span> inside the chip\n const labelEl = findLabelElement(chip.children);\n if (labelEl && labelEl.endSourceSpan) {\n const contentStart = labelEl.startSourceSpan.end.offset;\n const contentEnd = labelEl.endSourceSpan.start.offset;\n const content = source.slice(contentStart, contentEnd);\n if (content && !content.includes('euiTruncate')) {\n const trimmed = content.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch) {\n return { start: contentStart, end: contentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n return { start: contentStart, end: contentEnd, replacement: `{{ '${trimmed}' | euiTruncate: ${truncateValue} }}` };\n }\n }\n\n // Check direct text content inside chip (no label element)\n if (chip.endSourceSpan) {\n const chipContentStart = chip.startSourceSpan.end.offset;\n const chipContentEnd = chip.endSourceSpan.start.offset;\n const chipContent = source.slice(chipContentStart, chipContentEnd);\n const trimmed = chipContent.trim();\n const interpMatch = trimmed.match(/^\\{\\{\\s*(.+?)\\s*\\}\\}$/);\n if (interpMatch && !chipContent.includes('euiTruncate')) {\n return { start: chipContentStart, end: chipContentEnd, replacement: `{{ ${interpMatch[1]} | euiTruncate: ${truncateValue} }}` };\n }\n }\n return null;\n}\n\nfunction findLabelElement(nodes: TmplAstNode[]): TmplAstElement | null {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.attributes.some((a) => a.name === 'euiLabel')) return node;\n const nested = findLabelElement(node.children);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nfunction getExistingAttrNames(element: TmplAstElement): Set<string> {\n const names = new Set<string>();\n for (const attr of element.attributes) names.add(attr.name);\n for (const input of element.inputs) names.add(input.name);\n for (const output of element.outputs) names.add(output.name);\n return names;\n}\n\nfunction extractAttrName(insertion: string): string {\n const outputMatch = insertion.match(/^\\(([^)]+)\\)/);\n if (outputMatch) return outputMatch[1];\n const inputMatch = insertion.match(/^\\[([^\\]]+)\\]/);\n if (inputMatch) return inputMatch[1];\n return insertion.split('=')[0];\n}\n\nfunction removalEdit(node: TmplAstTextAttribute | TmplAstBoundAttribute | TmplAstBoundEvent, source: string): Edit {\n let start = node.sourceSpan.start.offset;\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n return { start, end: node.sourceSpan.end.offset, replacement: '' };\n}\n\nfunction extractBindingValue(input: TmplAstBoundAttribute, source: string): string {\n const raw = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : 'true';\n}\n\nfunction extractHandlerExpression(output: TmplAstBoundEvent, source: string): string {\n const raw = source.slice(output.sourceSpan.start.offset, output.sourceSpan.end.offset);\n const match = raw.match(/=[\"']([^\"']*)[\"']/);\n return match ? match[1] : '';\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n seen.set(`${edit.start}:${edit.end}`, edit);\n }\n return Array.from(seen.values());\n}\n",
|
|
2477
2489
|
"displayName": "Schema",
|
|
2478
2490
|
"properties": [
|
|
2479
2491
|
{
|
|
@@ -2485,7 +2497,7 @@
|
|
|
2485
2497
|
"indexKey": "",
|
|
2486
2498
|
"optional": true,
|
|
2487
2499
|
"description": "",
|
|
2488
|
-
"line":
|
|
2500
|
+
"line": 28,
|
|
2489
2501
|
"rawdescription": "\n"
|
|
2490
2502
|
},
|
|
2491
2503
|
{
|
|
@@ -2497,7 +2509,7 @@
|
|
|
2497
2509
|
"indexKey": "",
|
|
2498
2510
|
"optional": true,
|
|
2499
2511
|
"description": "",
|
|
2500
|
-
"line":
|
|
2512
|
+
"line": 27,
|
|
2501
2513
|
"rawdescription": "\n"
|
|
2502
2514
|
}
|
|
2503
2515
|
],
|
|
@@ -2515,12 +2527,12 @@
|
|
|
2515
2527
|
},
|
|
2516
2528
|
{
|
|
2517
2529
|
"name": "Schema",
|
|
2518
|
-
"id": "interface-Schema-
|
|
2519
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2530
|
+
"id": "interface-Schema-869dfc324e9111966817cbebb3553eabfe200acfe33bb77efa71a6c46e1cba0ff5852de7b9ade3060f87264035b99591361331a9e0622daaac22b8d59146c761-10",
|
|
2531
|
+
"file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
|
|
2520
2532
|
"deprecated": false,
|
|
2521
2533
|
"deprecationMessage": "",
|
|
2522
2534
|
"type": "interface",
|
|
2523
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-
|
|
2535
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-discussion-thread';\n\nexport function migrateEuiDiscussionThread(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove [trackBy] binding in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes('trackByFn')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'trackByFn') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"trackByFn\" has been removed from ${COMPONENT_TAG}. Remove this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Removed trackByFn-based [trackBy] bindings from ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, source, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes(COMPONENT_TAG)) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRemovals(node, source, removals);\n visitNodes(node.children, source, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, source: string, removals: { start: number; end: number }[]): void {\n for (const input of element.inputs) {\n if (input.name === 'trackBy') {\n const valueSource = source.slice(input.sourceSpan.start.offset, input.sourceSpan.end.offset);\n if (valueSource.includes('trackByFn')) {\n removals.push({ start: input.sourceSpan.start.offset, end: input.sourceSpan.end.offset });\n }\n }\n }\n}\n",
|
|
2524
2536
|
"displayName": "Schema",
|
|
2525
2537
|
"properties": [
|
|
2526
2538
|
{
|
|
@@ -2562,12 +2574,12 @@
|
|
|
2562
2574
|
},
|
|
2563
2575
|
{
|
|
2564
2576
|
"name": "Schema",
|
|
2565
|
-
"id": "interface-Schema-
|
|
2566
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2577
|
+
"id": "interface-Schema-375dc0924084a2acbafe4a6a32577d59f631c9a386d151180d8fb1c89e7e7cd23da9fd459e597592ed823692adb6ad2633c50baf16621f003246e8c9bb1c6ce0-11",
|
|
2578
|
+
"file": "packages/core/schematics/migrate-eui-editor/index.ts",
|
|
2567
2579
|
"deprecated": false,
|
|
2568
2580
|
"deprecationMessage": "",
|
|
2569
2581
|
"type": "interface",
|
|
2570
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\
|
|
2582
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst COMPONENT_TAG = 'eui-editor';\nconst OLD_NAME = 'onEditorChanged';\nconst NEW_NAME = 'contentChange';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiEditor(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (path.endsWith('.html')) {\n if (!original.includes(COMPONENT_TAG)) return;\n const result = migrateTemplate(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n return;\n }\n\n // .ts file — handle both migration and warnings in one pass\n const hasTag = original.includes(COMPONENT_TAG);\n const hasOldName = original.includes(OLD_NAME);\n if (!hasTag && !hasOldName) return;\n\n if (hasTag) {\n const result = migrateInlineTemplates(original);\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (skip spec files)\n if (hasOldName && !path.endsWith('.spec.ts')) {\n warnPropertyAccesses(path, original, context);\n }\n });\n\n context.logger.info(`Renamed '(${OLD_NAME})' → '(${NEW_NAME})' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return;\n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction warnPropertyAccesses(path: string, source: string, context: SchematicContext): void {\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const output of element.outputs) {\n if (output.name === OLD_NAME) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2571
2583
|
"displayName": "Schema",
|
|
2572
2584
|
"properties": [
|
|
2573
2585
|
{
|
|
@@ -2579,7 +2591,7 @@
|
|
|
2579
2591
|
"indexKey": "",
|
|
2580
2592
|
"optional": true,
|
|
2581
2593
|
"description": "",
|
|
2582
|
-
"line":
|
|
2594
|
+
"line": 12,
|
|
2583
2595
|
"rawdescription": "\n"
|
|
2584
2596
|
},
|
|
2585
2597
|
{
|
|
@@ -2591,7 +2603,7 @@
|
|
|
2591
2603
|
"indexKey": "",
|
|
2592
2604
|
"optional": true,
|
|
2593
2605
|
"description": "",
|
|
2594
|
-
"line":
|
|
2606
|
+
"line": 11,
|
|
2595
2607
|
"rawdescription": "\n"
|
|
2596
2608
|
}
|
|
2597
2609
|
],
|
|
@@ -2609,12 +2621,12 @@
|
|
|
2609
2621
|
},
|
|
2610
2622
|
{
|
|
2611
2623
|
"name": "Schema",
|
|
2612
|
-
"id": "interface-Schema-
|
|
2613
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2624
|
+
"id": "interface-Schema-0ee574e11dd651dd971057cb66220845e5b87b2949c69c623c8ddf7355af92a396c58c237a52a05f18c22a5332a2695b717bcfeb8ea840c4e7df8d0084c2b49c-12",
|
|
2625
|
+
"file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
|
|
2614
2626
|
"deprecated": false,
|
|
2615
2627
|
"deprecationMessage": "",
|
|
2616
2628
|
"type": "interface",
|
|
2617
|
-
"sourceCode": "import { parseTemplate,
|
|
2629
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-fieldset';\nconst OLD_NAME = 'iconSvgType';\nconst NEW_NAME = 'iconSvgName';\n\nexport function migrateEuiFieldset(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && original.includes(OLD_NAME)) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(`${path}:${line + 1} - \"${OLD_NAME}\" has been renamed to \"${NEW_NAME}\" on ${COMPONENT_TAG}. Update this reference manually.`);\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) changes.push({ start, end, text: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) collectRenames(node, edits);\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2618
2630
|
"displayName": "Schema",
|
|
2619
2631
|
"properties": [
|
|
2620
2632
|
{
|
|
@@ -2656,12 +2668,12 @@
|
|
|
2656
2668
|
},
|
|
2657
2669
|
{
|
|
2658
2670
|
"name": "Schema",
|
|
2659
|
-
"id": "interface-Schema-
|
|
2660
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2671
|
+
"id": "interface-Schema-b3ff4600c4a5ca1888c45e5baf3600fa6dc6477f1e473e5241ff6682e2929dc8950a5dfa8b1048a348dba7ad1f0ce8cc3681471933911b689e04713334ef4811-13",
|
|
2672
|
+
"file": "packages/core/schematics/migrate-eui-icon-svg/index.ts",
|
|
2661
2673
|
"deprecated": false,
|
|
2662
2674
|
"deprecationMessage": "",
|
|
2663
2675
|
"type": "interface",
|
|
2664
|
-
"sourceCode": "import { parseTemplate,
|
|
2676
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst COMPONENT_TAG = 'eui-icon-svg';\n\nconst INPUT_RENAMES = new Map([\n ['variant', 'fillColor'],\n ['aria-label', 'ariaLabel'],\n]);\n\nconst TS_PROPERTY_RENAMES = new Map([\n ['variant', 'fillColor'],\n]);\n\nexport function migrateEuiIconSvg(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n result = renameTsPropertyAccesses(result);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename deprecated inputs in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n context.logger.info(`Migrated eui-icon-svg inputs in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) {\n changes.push({ start, end, text: migrated });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction renameTsPropertyAccesses(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && TS_PROPERTY_RENAMES.has(node.name.text)) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: TS_PROPERTY_RENAMES.get(node.name.text)! });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) {\n collectRenames(node, edits);\n }\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n const newName = INPUT_RENAMES.get(attr.name);\n if (newName) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n }\n for (const input of element.inputs) {\n const newName = INPUT_RENAMES.get(input.name);\n if (newName) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2665
2677
|
"displayName": "Schema",
|
|
2666
2678
|
"properties": [
|
|
2667
2679
|
{
|
|
@@ -2703,12 +2715,12 @@
|
|
|
2703
2715
|
},
|
|
2704
2716
|
{
|
|
2705
2717
|
"name": "Schema",
|
|
2706
|
-
"id": "interface-Schema-
|
|
2707
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2718
|
+
"id": "interface-Schema-21147930d3fbc38fbb3052a2ed9e7aa2b7505e6ed8c88c28796ce7101bd2ad914726eb230dda5826647036190edec6dae7e7e1d172387cd3803aa00bcdf790be-14",
|
|
2719
|
+
"file": "packages/core/schematics/migrate-eui-icon-toggle/index.ts",
|
|
2708
2720
|
"deprecated": false,
|
|
2709
2721
|
"deprecationMessage": "",
|
|
2710
2722
|
"type": "interface",
|
|
2711
|
-
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst
|
|
2723
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_NAME = 'iconSet';\nconst NEW_NAME = 'iconSvgName';\nconst COMPONENT_TAG = 'eui-icon-toggle';\n\nexport function migrateEuiIconToggle(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let templateCount = 0;\n let tsCount = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(COMPONENT_TAG)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original);\n } else {\n result = migrateInlineTemplates(original);\n result = renameTsPropertyAccesses(result);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename '${OLD_NAME}' → '${NEW_NAME}' in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n if (path.endsWith('.html')) templateCount++;\n else tsCount++;\n }\n });\n\n context.logger.info(`Renamed '${OLD_NAME}' → '${NEW_NAME}' on ${COMPONENT_TAG} in ${templateCount + tsCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(COMPONENT_TAG)) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) {\n changes.push({ start, end, text: migrated });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction renameTsPropertyAccesses(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === OLD_NAME) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: NEW_NAME });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === COMPONENT_TAG) {\n collectRenames(node, edits);\n }\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n if (attr.name === OLD_NAME) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n for (const input of element.inputs) {\n if (input.name === OLD_NAME) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: NEW_NAME });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2712
2724
|
"displayName": "Schema",
|
|
2713
2725
|
"properties": [
|
|
2714
2726
|
{
|
|
@@ -2750,12 +2762,12 @@
|
|
|
2750
2762
|
},
|
|
2751
2763
|
{
|
|
2752
2764
|
"name": "Schema",
|
|
2753
|
-
"id": "interface-Schema-
|
|
2754
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2765
|
+
"id": "interface-Schema-a806c1769fb526271565003a6a3ef9ab4c67e421d93ee0cb54e1f6de223807afc1f16ad294656ef614a77a91a38cc914ce41ac97ed3a8a4195a1e74a729c0c08-15",
|
|
2766
|
+
"file": "packages/core/schematics/migrate-eui-popover/index.ts",
|
|
2755
2767
|
"deprecated": false,
|
|
2756
2768
|
"deprecationMessage": "",
|
|
2757
2769
|
"type": "interface",
|
|
2758
|
-
"sourceCode": "import { BindingPipe, parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute, AST, ASTWithSource, Interpolation } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Edit { start: number; end: number; replacement: string; }\n\n// --- Table-level input renames (on elements with euiTable attribute) ---\nconst TABLE_INPUT_RENAMES = new Map([\n ['rows', 'data'],\n ['loading', 'isLoading'],\n ['asyncTable', 'isAsync'],\n ['euiTableResponsive', 'isTableResponsive'],\n ['euiTableFixedLayout', 'isTableFixedLayout'],\n ['euiTableCompact', 'isTableCompact'],\n ['hasStickyColumns', 'hasStickyCols'],\n]);\n\n// --- Child element input renames (scoped to euiTable context) ---\nconst TH_TD_INPUT_RENAMES = new Map([\n ['isStickyColumn', 'isStickyCol'],\n ['sortable', 'isSortable'],\n]);\n\nconst TR_INPUT_RENAMES = new Map([\n ['isSelectableHeader', 'isHeaderSelectable'],\n ['isSelectable', 'isDataSelectable'],\n]);\n\n// --- Removed inputs ---\nconst REMOVED_INPUTS = new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable']);\n\n// --- Output renames/removals ---\nconst OUTPUT_RENAMES = new Map([['selectedRows', 'rowsSelect']]);\nconst REMOVED_OUTPUTS = new Set(['multiSortChange']);\n\n// --- Pipe rename ---\nconst OLD_PIPE = 'euiTableHighlightFilter';\nconst NEW_PIPE = 'euiTableHighlight';\n\n// --- TS property renames ---\nconst TS_PROPERTY_RENAMES = new Map([['filteredRows', 'getFilteredData']]);\n\nconst PAGINATOR_IMPORT_PATH = '@eui/components/eui-paginator';\nconst PAGINATOR_COMPONENT = 'EuiPaginatorComponent';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiTable(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const paginatorHtmlFiles = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('euiTable') && !original.includes(OLD_PIPE) && !original.includes('filteredRows') && !original.includes('setSort')) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n const { output, hasPaginator } = migrateTemplateWithPaginator(original, path, context);\n result = output;\n if (hasPaginator) paginatorHtmlFiles.add(path);\n } else {\n const { output, hasPaginator } = migrateTypeScript(original, path, context);\n result = output;\n if (hasPaginator) {\n result = addPaginatorImport(result, path);\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-table breaking changes in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n // Handle paginator imports for external templates\n if (paginatorHtmlFiles.size > 0) {\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!source.includes('templateUrl')) return;\n\n for (const htmlFile of paginatorHtmlFiles) {\n const htmlFileName = htmlFile.split('/').pop()!;\n if (source.includes(htmlFileName)) {\n const updated = addPaginatorImport(source, path);\n if (updated !== source) {\n if (options.dryRun) {\n logDryRun(context, `Would add paginator import in ${path}`);\n } else {\n tree.overwrite(path, updated);\n }\n count++;\n }\n break;\n }\n }\n });\n }\n\n context.logger.info(`Migrated eui-table in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplateWithPaginator(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n let hasPaginator = false;\n\n const paginatorResult = visitNodesForTable(parsed.nodes, source, edits, false, filePath, context);\n if (paginatorResult) hasPaginator = true;\n\n collectPipeRenames(parsed.nodes, source, edits);\n\n return { output: applyEdits(source, edits), hasPaginator };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n return migrateTemplateWithPaginator(source, filePath, context).output;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n let result = source;\n let hasPaginator = false;\n\n // Migrate inline templates\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = result.slice(start, end);\n if (!rawTemplate.includes('euiTable') && !rawTemplate.includes(OLD_PIPE)) {\n ts.forEachChild(node, visit); return;\n}\n const { output: migrated, hasPaginator: pag } = migrateTemplateWithPaginator(rawTemplate, filePath, context);\n if (pag) hasPaginator = true;\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n result = applyEdits(result, changes);\n\n // Rename TS property accesses\n result = renameTsProperties(result);\n\n // Warn about setSort\n warnSetSort(result, filePath, context);\n\n return { output: result, hasPaginator };\n}\n\nfunction renameTsProperties(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && TS_PROPERTY_RENAMES.has(node.name.text)) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: TS_PROPERTY_RENAMES.get(node.name.text)! });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, edits);\n}\n\nfunction warnSetSort(source: string, filePath: string, context: SchematicContext): void {\n if (!source.includes('setSort')) return;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&\n ts.isIdentifier(node.expression.name) && node.expression.name.text === 'setSort') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"setSort\" signature changed from setSort(sort: string, order: \"asc\" | \"desc\") to setSort(Sort[]). Update manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\n// --- Template AST visitors ---\n\nfunction visitNodesForTable(\n nodes: TmplAstNode[], source: string, edits: Edit[], insideEuiTable: boolean, filePath: string, context: SchematicContext,\n): boolean {\n let hasPaginator = false;\n\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n const isEuiTable = hasEuiTableAttribute(node);\n\n if (isEuiTable) {\n collectTableInputRenames(node, edits);\n collectInputRemovals(node, source, edits, filePath, context);\n collectOutputChanges(node, source, edits, filePath, context);\n if (collectPaginatorMigration(node, source, edits)) hasPaginator = true;\n }\n\n if (isEuiTable || insideEuiTable) {\n collectChildElementRenames(node, edits);\n collectChildInputRemovals(node, source, edits, filePath, context);\n collectEmptyMessageRename(node, edits);\n }\n\n const childResult = visitNodesForTable(node.children, source, edits, isEuiTable || insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n\n if (node instanceof TmplAstTemplate) {\n if (insideEuiTable) {\n collectTemplateEmptyMessageRename(node, edits);\n }\n const childResult = visitNodesForTable(node.children, source, edits, insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n }\n\n return hasPaginator;\n}\n\nfunction hasEuiTableAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiTable') ||\n element.inputs.some((i) => i.name === 'euiTable');\n}\n\nfunction collectTableInputRenames(element: TmplAstElement, edits: Edit[]): void {\n for (const attr of element.attributes) {\n const newName = TABLE_INPUT_RENAMES.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = TABLE_INPUT_RENAMES.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildElementRenames(element: TmplAstElement, edits: Edit[]): void {\n const renames = (element.name === 'th' || element.name === 'td') ? TH_TD_INPUT_RENAMES\n : element.name === 'tr' ? TR_INPUT_RENAMES : null;\n if (!renames) return;\n\n for (const attr of element.attributes) {\n const newName = renames.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = renames.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n const childRemovedInputs = new Set(['defaultMultiOrder']);\n\n for (const attr of element.attributes) {\n if (childRemovedInputs.has(attr.name)) {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (childRemovedInputs.has(input.name)) {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name) && attr.name !== 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name) && input.name !== 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectOutputChanges(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const output of element.outputs) {\n const newName = OUTPUT_RENAMES.get(output.name);\n if (newName) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: newName });\n }\n if (REMOVED_OUTPUTS.has(output.name)) {\n removeAttribute(output.sourceSpan.start.offset, output.sourceSpan.end.offset, source, edits);\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(${output.name})\" removed. Multi-sort is now supported by (sortChange) output.`,\n );\n }\n }\n}\n\nfunction collectPaginatorMigration(element: TmplAstElement, source: string, edits: Edit[]): boolean {\n let found = false;\n\n for (const attr of element.attributes) {\n if (attr.name === 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n for (const input of element.inputs) {\n if (input.name === 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n\n if (found) {\n // Add [paginator]=\"paginator\" before closing > of opening tag\n const insertPos = element.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' [paginator]=\"paginator\"' });\n\n // Add eui-paginator after </table>\n if (element.endSourceSpan) {\n const afterTable = element.endSourceSpan.end.offset;\n edits.push({\n start: afterTable,\n end: afterTable,\n replacement: '\\n<!-- TODO: Configure paginator and implement onPageChange handler -->\\n<eui-paginator #paginator [pageSize]=\"10\" [pageSizeOptions]=\"[5, 10, 25, 50]\" />',\n });\n }\n }\n\n return found;\n}\n\nfunction collectEmptyMessageRename(element: TmplAstElement, edits: Edit[]): void {\n // Handle direct attribute on elements (unlikely but handle)\n for (const attr of element.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\nfunction collectTemplateEmptyMessageRename(template: TmplAstTemplate, edits: Edit[]): void {\n for (const attr of template.templateAttrs) {\n if (attr instanceof TmplAstTextAttribute && attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n for (const attr of template.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\n// --- Pipe rename via AST ---\n\nfunction collectPipeRenames(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n for (const output of node.outputs) {\n if (output.handler) visitExpressionForPipes(output.handler, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstTemplate) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstBoundText) {\n visitExpressionForPipes(node.value, edits);\n }\n }\n}\n\nfunction visitExpressionForPipes(expr: AST, edits: Edit[]): void {\n if (expr instanceof ASTWithSource && expr.ast) {\n visitAstForPipes(expr.ast, edits);\n } else {\n visitAstForPipes(expr, edits);\n }\n}\n\nfunction visitAstForPipes(ast: AST, edits: Edit[]): void {\n if (ast instanceof BindingPipe) {\n if (ast.name === OLD_PIPE && ast.nameSpan) {\n edits.push({ start: ast.nameSpan.start, end: ast.nameSpan.end, replacement: NEW_PIPE });\n }\n visitAstForPipes(ast.exp, edits);\n for (const arg of ast.args) {\n visitAstForPipes(arg, edits);\n }\n return;\n }\n\n if (ast instanceof Interpolation) {\n for (const expr of ast.expressions) {\n visitAstForPipes(expr, edits);\n }\n return;\n }\n\n // Recursively visit all properties that could contain AST nodes\n for (const key of Object.keys(ast)) {\n // eslint-disable-next-line\n const val = (ast as any)[key];\n if (val instanceof AST) {\n visitAstForPipes(val, edits);\n } else if (Array.isArray(val)) {\n for (const item of val) {\n if (item instanceof AST) visitAstForPipes(item, edits);\n }\n }\n }\n}\n\n// --- Import handling for paginator ---\n\nfunction addPaginatorImport(source: string, filePath: string): string {\n if (source.includes(PAGINATOR_COMPONENT)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Add import statement after last import\n let lastImportEnd = 0;\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n }\n }\n\n if (lastImportEnd > 0) {\n edits.push({\n start: lastImportEnd,\n end: lastImportEnd,\n replacement: `\\nimport { ${PAGINATOR_COMPONENT} } from '${PAGINATOR_IMPORT_PATH}';`,\n });\n }\n\n // Add to component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && isComponentMetadataProperty(node)) {\n if (ts.isArrayLiteralExpression(node.initializer)) {\n const arr = node.initializer;\n const elements = arr.elements;\n if (elements.length > 0) {\n const lastElement = elements[elements.length - 1];\n edits.push({\n start: lastElement.getEnd(),\n end: lastElement.getEnd(),\n replacement: `, ${PAGINATOR_COMPONENT}`,\n });\n } else {\n const insertPos = arr.getStart(sourceFile) + 1;\n edits.push({ start: insertPos, end: insertPos, replacement: PAGINATOR_COMPONENT });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\n// --- Helpers ---\n\nfunction removeAttribute(start: number, end: number, source: string, edits: Edit[]): void {\n let adjustedStart = start;\n while (adjustedStart > 0 && (source[adjustedStart - 1] === ' ' || source[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n edits.push({ start: adjustedStart, end, replacement: '' });\n}\n\nfunction logRemovalWarning(name: string, filePath: string, element: TmplAstElement, context: SchematicContext): void {\n const { line } = element.startSourceSpan.start;\n if (name === 'defaultMultiOrder') {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed. Use setSort(Sort[]) to initialize sorting.`);\n } else {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed to align to Design System.`);\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n unique.set(key, edit);\n }\n let result = source;\n for (const edit of [...unique.values()].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2770
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst REMOVED_INPUTS = new Set(['type']);\n\nexport function migrateEuiPopover(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('eui-popover')) return;\n\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would remove 'type' input in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n\n // Warn about TS usages inline\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts') && (original.includes('eui-popover') || original.includes('euiPopover') || original.includes('EuiPopover'))) {\n if (original.includes('type')) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && node.name.text === 'type') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${path}:${line + 1} - Manual action needed: \"type\" is no longer a valid input on eui-popover. Remove this assignment.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Removed deprecated eui-popover 'type' input from ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const removals: { start: number; end: number }[] = [];\n\n visitNodes(parsed.nodes, removals);\n\n let result = source;\n for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {\n // Extend start backwards to consume leading whitespace\n let adjustedStart = start;\n while (adjustedStart > 0 && (result[adjustedStart - 1] === ' ' || result[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n result = result.slice(0, adjustedStart) + result.slice(end);\n }\n\n return result;\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const templateRegex = /template\\s*:\\s*`([^`]*)`/gs;\n return source.replace(templateRegex, (match, templateContent: string) => {\n if (!templateContent.includes('eui-popover')) return match;\n const migrated = migrateTemplate(templateContent);\n if (migrated === templateContent) return match;\n return match.replace(templateContent, migrated);\n });\n}\n\nfunction visitNodes(nodes: TmplAstNode[], removals: { start: number; end: number }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-popover') {\n collectRemovals(node, removals);\n }\n visitNodes(node.children, removals);\n }\n }\n}\n\nfunction collectRemovals(element: TmplAstElement, removals: { start: number; end: number }[]): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name)) {\n removals.push(getAttributeSpan(attr));\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name)) {\n removals.push(getAttributeSpan(input));\n }\n }\n}\n\nfunction getAttributeSpan(attr: TmplAstTextAttribute | TmplAstBoundAttribute): { start: number; end: number } {\n return { start: attr.sourceSpan.start.offset, end: attr.sourceSpan.end.offset };\n}\n",
|
|
2759
2771
|
"displayName": "Schema",
|
|
2760
2772
|
"properties": [
|
|
2761
2773
|
{
|
|
@@ -2767,7 +2779,7 @@
|
|
|
2767
2779
|
"indexKey": "",
|
|
2768
2780
|
"optional": true,
|
|
2769
2781
|
"description": "",
|
|
2770
|
-
"line":
|
|
2782
|
+
"line": 8,
|
|
2771
2783
|
"rawdescription": "\n"
|
|
2772
2784
|
},
|
|
2773
2785
|
{
|
|
@@ -2779,7 +2791,7 @@
|
|
|
2779
2791
|
"indexKey": "",
|
|
2780
2792
|
"optional": true,
|
|
2781
2793
|
"description": "",
|
|
2782
|
-
"line":
|
|
2794
|
+
"line": 7,
|
|
2783
2795
|
"rawdescription": "\n"
|
|
2784
2796
|
}
|
|
2785
2797
|
],
|
|
@@ -2797,12 +2809,12 @@
|
|
|
2797
2809
|
},
|
|
2798
2810
|
{
|
|
2799
2811
|
"name": "Schema",
|
|
2800
|
-
"id": "interface-Schema-
|
|
2801
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2812
|
+
"id": "interface-Schema-90e62c16ce9ada881e8d434336bb633dae9745236106ddf60964afbd11de6aa579255a13d6036b384281860eb6ed2ee329194b519c51fdaf20f79bc511d0f64f-16",
|
|
2813
|
+
"file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
|
|
2802
2814
|
"deprecated": false,
|
|
2803
2815
|
"deprecationMessage": "",
|
|
2804
2816
|
"type": "interface",
|
|
2805
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface TextChange {\n start: number;\n end: number;\n text: string;\n}\n\ninterface MigrationResult {\n content: string;\n migrated: number;\n skipped: number;\n}\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_LABEL = 'eui-tab-label';\nconst OLD_SUB_LABEL = 'euiTabSubLabel';\nconst OLD_CONTENT = 'eui-tab-content';\nconst NEW_HEADER = 'eui-tab-header';\nconst NEW_HEADER_LABEL = 'eui-tab-header-label';\nconst NEW_HEADER_SUB_LABEL = 'eui-tab-header-sub-label';\nconst NEW_BODY = 'eui-tab-body';\n\nexport function migrateEuiTabs(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let migrated = 0;\n let skipped = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) {\n return;\n }\n\n const original = buffer.toString('utf-8');\n const result = path.endsWith('.html')\n ? migrateTemplate(original, path, context)\n : migrateInlineTemplates(original, path, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.migrated} tab block(s) in ${path}`);\n } else {\n tree.overwrite(path, result.content);\n }\n }\n });\n\n context.logger.info(`Migrated ${migrated} EUI tab template block(s).`);\n if (skipped > 0) {\n context.logger.warn(`Skipped ${skipped} malformed EUI tab template block(s).`);\n }\n if (options.dryRun) {\n logDryRunNote(context);\n }\n\n return tree;\n };\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const initializer = unwrapExpression(node.initializer);\n\n if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {\n const start = initializer.getStart(sourceFile) + 1;\n const end = initializer.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n const result = migrateTemplate(rawTemplate, `${filePath}@inline-template`, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== rawTemplate) {\n changes.push({ start, end, text: result.content });\n }\n } else if (ts.isTemplateExpression(initializer)) {\n context.logger.warn(`Skipping interpolated inline template in ${filePath}.`);\n skipped++;\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return {\n content: applyChanges(source, changes),\n migrated,\n skipped,\n };\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (\n (ts.isIdentifier(name) && name.text === 'template') ||\n (ts.isStringLiteral(name) && name.text === 'template')\n );\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) {\n return false;\n }\n\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) {\n return false;\n }\n\n return (\n ts.isDecorator(callExpression.parent) &&\n ts.isIdentifier(callExpression.expression) &&\n callExpression.expression.text === 'Component'\n );\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const parsed = parseTemplate(source, filePath, { preserveWhitespaces: true });\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n for (const error of parsed.errors ?? []) {\n context.logger.warn(`Template parse warning in ${filePath}: ${error.msg}`);\n }\n\n for (const tab of findElements(parsed.nodes, 'eui-tab')) {\n const label = findDirectChild(tab, OLD_LABEL);\n const content = findDirectChild(tab, OLD_CONTENT);\n const hasOldMarkup = Boolean(label || content);\n const hasNewMarkup = Boolean(findDirectChild(tab, NEW_HEADER) || findDirectChild(tab, NEW_BODY));\n\n if (!hasOldMarkup || hasNewMarkup) {\n continue;\n }\n\n if (!label || !content) {\n context.logger.warn(`Skipping malformed <eui-tab> in ${filePath}.`);\n skipped++;\n continue;\n }\n\n changes.push({\n start: label.sourceSpan.start.offset,\n end: label.sourceSpan.end.offset,\n text: buildHeaderReplacement(source, label),\n });\n changes.push({\n start: content.sourceSpan.start.offset,\n end: content.sourceSpan.end.offset,\n text: buildBodyReplacement(source, content),\n });\n migrated++;\n }\n\n return {\n content: applyChanges(source, withoutOverlaps(changes)),\n migrated,\n skipped,\n };\n}\n\nfunction findElements(nodes: readonly TmplAstNode[], name: string): TmplAstElement[] {\n const matches: TmplAstElement[] = [];\n\n for (const node of nodes) {\n if (isElement(node)) {\n if (node.name === name) {\n matches.push(node);\n }\n matches.push(...findElements(node.children, name));\n }\n }\n\n return matches;\n}\n\nfunction findDirectChild(element: TmplAstElement, name: string): TmplAstElement | undefined {\n return element.children.find((child): child is TmplAstElement => isElement(child) && child.name === name);\n}\n\nfunction isElement(node: TmplAstNode): node is TmplAstElement {\n return node instanceof TmplAstElement;\n}\n\nfunction buildHeaderReplacement(source: string, label: TmplAstElement): string {\n const indent = getIndent(source, label.sourceSpan.start.offset);\n const labelAttributes = getAttributeText(source, label, OLD_LABEL);\n const subLabels = findElements(label.children, OLD_SUB_LABEL);\n const mainLabelContent = removeElementRanges(getInnerText(source, label), label, subLabels);\n const lines = [\n `<${NEW_HEADER}>`,\n `${indent} <${NEW_HEADER_LABEL}${labelAttributes}>`,\n ...formatInnerLines(mainLabelContent, `${indent} `),\n `${indent} </${NEW_HEADER_LABEL}>`,\n ];\n\n for (const subLabel of subLabels) {\n const subLabelAttributes = getAttributeText(source, subLabel, OLD_SUB_LABEL);\n lines.push(\n `${indent} <${NEW_HEADER_SUB_LABEL}${subLabelAttributes}>`,\n ...formatInnerLines(getInnerText(source, subLabel), `${indent} `),\n `${indent} </${NEW_HEADER_SUB_LABEL}>`,\n );\n }\n\n lines.push(`${indent}</${NEW_HEADER}>`);\n\n return lines.join('\\n');\n}\n\nfunction buildBodyReplacement(source: string, content: TmplAstElement): string {\n const indent = getIndent(source, content.sourceSpan.start.offset);\n const attributes = getAttributeText(source, content, OLD_CONTENT);\n const innerText = getInnerText(source, content);\n const trimmed = innerText.trim();\n\n if (trimmed && !trimmed.includes('\\n')) {\n return `<${NEW_BODY}${attributes}>${trimmed}</${NEW_BODY}>`;\n }\n\n return [\n `<${NEW_BODY}${attributes}>`,\n ...formatInnerLines(innerText, `${indent} `),\n `${indent}</${NEW_BODY}>`,\n ].join('\\n');\n}\n\nfunction getInnerText(source: string, element: TmplAstElement): string {\n if (!element.endSourceSpan) {\n return '';\n }\n\n return source.slice(element.startSourceSpan.end.offset, element.endSourceSpan.start.offset);\n}\n\nfunction removeElementRanges(source: string, parent: TmplAstElement, elements: readonly TmplAstElement[]): string {\n const parentContentStart = parent.startSourceSpan.end.offset;\n let result = source;\n\n for (const element of [...elements].sort((a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset)) {\n const start = element.sourceSpan.start.offset - parentContentStart;\n const end = element.sourceSpan.end.offset - parentContentStart;\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction getAttributeText(source: string, element: TmplAstElement, tagName: string): string {\n const startTag = source.slice(element.startSourceSpan.start.offset, element.startSourceSpan.end.offset);\n const tagStart = startTag.indexOf(tagName);\n\n if (tagStart === -1) {\n return '';\n }\n\n const contentEnd = startTag.endsWith('/>') ? startTag.length - 2 : startTag.length - 1;\n return startTag.slice(tagStart + tagName.length, contentEnd).trimEnd();\n}\n\nfunction getIndent(source: string, offset: number): string {\n const lineStart = source.lastIndexOf('\\n', offset - 1) + 1;\n const prefix = source.slice(lineStart, offset);\n return prefix.match(/^\\s*/)?.[0] ?? '';\n}\n\nfunction formatInnerLines(source: string, indent: string): string[] {\n const trimmed = source.trim();\n\n if (!trimmed) {\n return [];\n }\n\n return trimmed.split(/\\r?\\n/).map((line) => `${indent}${line.trim()}`);\n}\n\nfunction withoutOverlaps(changes: TextChange[]): TextChange[] {\n const accepted: TextChange[] = [];\n\n for (const change of [...changes].sort((a, b) => a.start - b.start || b.end - a.end)) {\n if (!accepted.some((current) => change.start < current.end && current.start < change.end)) {\n accepted.push(change);\n }\n }\n\n return accepted;\n}\n\nfunction applyChanges(source: string, changes: readonly TextChange[]): string {\n let result = source;\n\n for (const change of [...changes].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2817
|
+
"sourceCode": "import { parseTemplate, TmplAstBoundAttribute, TmplAstElement, TmplAstNode, TmplAstTextAttribute } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst INPUT_RENAMES = new Map([\n ['iconLabelClass', 'icon'],\n ['iconLabelStyleClass', 'fillColor'],\n]);\n\nexport function migrateEuiProgressCircle(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const oldNames = [...INPUT_RENAMES.keys()];\n\n const dir = tree.getDir(scanPath || '/');\n visitDir(dir, (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n\n if (original.includes('eui-progress-circle')) {\n const result = path.endsWith('.html')\n ? migrateTemplate(original)\n : migrateInlineTemplates(original);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would rename deprecated inputs in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n }\n\n // Warn about TS property access usages (merged from warnTsUsages)\n if (path.endsWith('.ts') && !path.endsWith('.spec.ts')) {\n if (oldNames.some((name) => original.includes(name))) {\n const sourceFile = ts.createSourceFile(path, original, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && INPUT_RENAMES.has(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const newName = INPUT_RENAMES.get(node.name.text);\n context.logger.warn(\n `${path}:${line + 1} - \"${node.name.text}\" has been renamed to \"${newName}\" on eui-progress-circle. Update this reference manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n }\n }\n });\n\n context.logger.info(`Renamed deprecated eui-progress-circle inputs in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction migrateTemplate(source: string): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: { start: number; end: number; replacement: string }[] = [];\n\n visitNodes(parsed.nodes, edits);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateInlineTemplates(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: { start: number; end: number; text: string }[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes('eui-progress-circle')) {\n ts.forEachChild(node, visit);\n return;\n }\n const migrated = migrateTemplate(rawTemplate);\n if (migrated !== rawTemplate) {\n changes.push({ start, end, text: migrated });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n let result = source;\n for (const change of changes.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n return result;\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n}\n\nfunction visitNodes(nodes: TmplAstNode[], edits: { start: number; end: number; replacement: string }[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === 'eui-progress-circle') {\n collectRenames(node, edits);\n }\n visitNodes(node.children, edits);\n }\n }\n}\n\nfunction collectRenames(element: TmplAstElement, edits: { start: number; end: number; replacement: string }[]): void {\n for (const attr of element.attributes) {\n const newName = INPUT_RENAMES.get(attr.name);\n if (newName) {\n edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n }\n for (const input of element.inputs) {\n const newName = INPUT_RENAMES.get(input.name);\n if (newName) {\n edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n }\n}\n\nfunction applyEdits(source: string, edits: { start: number; end: number; replacement: string }[]): string {\n let result = source;\n for (const edit of edits.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n",
|
|
2806
2818
|
"displayName": "Schema",
|
|
2807
2819
|
"properties": [
|
|
2808
2820
|
{
|
|
@@ -2814,7 +2826,7 @@
|
|
|
2814
2826
|
"indexKey": "",
|
|
2815
2827
|
"optional": true,
|
|
2816
2828
|
"description": "",
|
|
2817
|
-
"line":
|
|
2829
|
+
"line": 8,
|
|
2818
2830
|
"rawdescription": "\n"
|
|
2819
2831
|
},
|
|
2820
2832
|
{
|
|
@@ -2826,7 +2838,7 @@
|
|
|
2826
2838
|
"indexKey": "",
|
|
2827
2839
|
"optional": true,
|
|
2828
2840
|
"description": "",
|
|
2829
|
-
"line":
|
|
2841
|
+
"line": 7,
|
|
2830
2842
|
"rawdescription": "\n"
|
|
2831
2843
|
}
|
|
2832
2844
|
],
|
|
@@ -2844,12 +2856,12 @@
|
|
|
2844
2856
|
},
|
|
2845
2857
|
{
|
|
2846
2858
|
"name": "Schema",
|
|
2847
|
-
"id": "interface-Schema-
|
|
2848
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2859
|
+
"id": "interface-Schema-91c9f900bf3ebb82488fb65644938ef486ceaa447cff8bfd9d710df9595d82acb267b26f3c252173bc684353f362473120226426ed7ee5d98b33e30011dd9383-17",
|
|
2860
|
+
"file": "packages/core/schematics/migrate-eui-table/index.ts",
|
|
2849
2861
|
"deprecated": false,
|
|
2850
2862
|
"deprecationMessage": "",
|
|
2851
2863
|
"type": "interface",
|
|
2852
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2864
|
+
"sourceCode": "import { BindingPipe, parseTemplate, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate, TmplAstTextAttribute, AST, ASTWithSource, Interpolation } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Edit { start: number; end: number; replacement: string; }\n\n// --- Table-level input renames (on elements with euiTable attribute) ---\nconst TABLE_INPUT_RENAMES = new Map([\n ['rows', 'data'],\n ['loading', 'isLoading'],\n ['asyncTable', 'isAsync'],\n ['euiTableResponsive', 'isTableResponsive'],\n ['euiTableFixedLayout', 'isTableFixedLayout'],\n ['euiTableCompact', 'isTableCompact'],\n ['hasStickyColumns', 'hasStickyCols'],\n]);\n\n// --- Child element input renames (scoped to euiTable context) ---\nconst TH_TD_INPUT_RENAMES = new Map([\n ['isStickyColumn', 'isStickyCol'],\n ['sortable', 'isSortable'],\n]);\n\nconst TR_INPUT_RENAMES = new Map([\n ['isSelectableHeader', 'isHeaderSelectable'],\n ['isSelectable', 'isDataSelectable'],\n]);\n\n// --- Removed inputs ---\nconst REMOVED_INPUTS = new Set(['euiTableBordered', 'isHoverable', 'defaultMultiOrder', 'paginable']);\n\n// --- Output renames/removals ---\nconst OUTPUT_RENAMES = new Map([['selectedRows', 'rowsSelect']]);\nconst REMOVED_OUTPUTS = new Set(['multiSortChange']);\n\n// --- Pipe rename ---\nconst OLD_PIPE = 'euiTableHighlightFilter';\nconst NEW_PIPE = 'euiTableHighlight';\n\n// --- TS property renames ---\nconst TS_PROPERTY_RENAMES = new Map([['filteredRows', 'getFilteredData']]);\n\nconst PAGINATOR_IMPORT_PATH = '@eui/components/eui-paginator';\nconst PAGINATOR_COMPONENT = 'EuiPaginatorComponent';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateEuiTable(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let count = 0;\n const paginatorHtmlFiles = new Set<string>();\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes('euiTable') && !original.includes(OLD_PIPE) && !original.includes('filteredRows') && !original.includes('setSort')) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n const { output, hasPaginator } = migrateTemplateWithPaginator(original, path, context);\n result = output;\n if (hasPaginator) paginatorHtmlFiles.add(path);\n } else {\n const { output, hasPaginator } = migrateTypeScript(original, path, context);\n result = output;\n if (hasPaginator) {\n result = addPaginatorImport(result, path);\n }\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-table breaking changes in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n count++;\n }\n });\n\n // Handle paginator imports for external templates\n if (paginatorHtmlFiles.size > 0) {\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!source.includes('templateUrl')) return;\n\n for (const htmlFile of paginatorHtmlFiles) {\n const htmlFileName = htmlFile.split('/').pop()!;\n if (source.includes(htmlFileName)) {\n const updated = addPaginatorImport(source, path);\n if (updated !== source) {\n if (options.dryRun) {\n logDryRun(context, `Would add paginator import in ${path}`);\n } else {\n tree.overwrite(path, updated);\n }\n count++;\n }\n break;\n }\n }\n });\n }\n\n context.logger.info(`Migrated eui-table in ${count} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplateWithPaginator(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n let hasPaginator = false;\n\n const paginatorResult = visitNodesForTable(parsed.nodes, source, edits, false, filePath, context);\n if (paginatorResult) hasPaginator = true;\n\n collectPipeRenames(parsed.nodes, source, edits);\n\n return { output: applyEdits(source, edits), hasPaginator };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n return migrateTemplateWithPaginator(source, filePath, context).output;\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): { output: string; hasPaginator: boolean } {\n let result = source;\n let hasPaginator = false;\n\n // Migrate inline templates\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = result.slice(start, end);\n if (!rawTemplate.includes('euiTable') && !rawTemplate.includes(OLD_PIPE)) {\n ts.forEachChild(node, visit); return;\n}\n const { output: migrated, hasPaginator: pag } = migrateTemplateWithPaginator(rawTemplate, filePath, context);\n if (pag) hasPaginator = true;\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n result = applyEdits(result, changes);\n\n // Rename TS property accesses\n result = renameTsProperties(result);\n\n // Warn about setSort\n warnSetSort(result, filePath, context);\n\n return { output: result, hasPaginator };\n}\n\nfunction renameTsProperties(source: string): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && TS_PROPERTY_RENAMES.has(node.name.text)) {\n edits.push({ start: node.name.getStart(sourceFile), end: node.name.getEnd(), replacement: TS_PROPERTY_RENAMES.get(node.name.text)! });\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, edits);\n}\n\nfunction warnSetSort(source: string, filePath: string, context: SchematicContext): void {\n if (!source.includes('setSort')) return;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n const visit = (node: ts.Node): void => {\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) &&\n ts.isIdentifier(node.expression.name) && node.expression.name.text === 'setSort') {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"setSort\" signature changed from setSort(sort: string, order: \"asc\" | \"desc\") to setSort(Sort[]). Update manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\n// --- Template AST visitors ---\n\nfunction visitNodesForTable(\n nodes: TmplAstNode[], source: string, edits: Edit[], insideEuiTable: boolean, filePath: string, context: SchematicContext,\n): boolean {\n let hasPaginator = false;\n\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n const isEuiTable = hasEuiTableAttribute(node);\n\n if (isEuiTable) {\n collectTableInputRenames(node, edits);\n collectInputRemovals(node, source, edits, filePath, context);\n collectOutputChanges(node, source, edits, filePath, context);\n if (collectPaginatorMigration(node, source, edits)) hasPaginator = true;\n }\n\n if (isEuiTable || insideEuiTable) {\n collectChildElementRenames(node, edits);\n collectChildInputRemovals(node, source, edits, filePath, context);\n collectEmptyMessageRename(node, edits);\n }\n\n const childResult = visitNodesForTable(node.children, source, edits, isEuiTable || insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n\n if (node instanceof TmplAstTemplate) {\n if (insideEuiTable) {\n collectTemplateEmptyMessageRename(node, edits);\n }\n const childResult = visitNodesForTable(node.children, source, edits, insideEuiTable, filePath, context);\n if (childResult) hasPaginator = true;\n }\n }\n\n return hasPaginator;\n}\n\nfunction hasEuiTableAttribute(element: TmplAstElement): boolean {\n return element.attributes.some((a) => a.name === 'euiTable') ||\n element.inputs.some((i) => i.name === 'euiTable');\n}\n\nfunction collectTableInputRenames(element: TmplAstElement, edits: Edit[]): void {\n for (const attr of element.attributes) {\n const newName = TABLE_INPUT_RENAMES.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = TABLE_INPUT_RENAMES.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildElementRenames(element: TmplAstElement, edits: Edit[]): void {\n const renames = (element.name === 'th' || element.name === 'td') ? TH_TD_INPUT_RENAMES\n : element.name === 'tr' ? TR_INPUT_RENAMES : null;\n if (!renames) return;\n\n for (const attr of element.attributes) {\n const newName = renames.get(attr.name);\n if (newName) edits.push({ start: attr.keySpan!.start.offset, end: attr.keySpan!.end.offset, replacement: newName });\n }\n for (const input of element.inputs) {\n const newName = renames.get(input.name);\n if (newName) edits.push({ start: input.keySpan!.start.offset, end: input.keySpan!.end.offset, replacement: newName });\n }\n}\n\nfunction collectChildInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n const childRemovedInputs = new Set(['defaultMultiOrder']);\n\n for (const attr of element.attributes) {\n if (childRemovedInputs.has(attr.name)) {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (childRemovedInputs.has(input.name)) {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectInputRemovals(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const attr of element.attributes) {\n if (REMOVED_INPUTS.has(attr.name) && attr.name !== 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n logRemovalWarning(attr.name, filePath, element, context);\n }\n }\n for (const input of element.inputs) {\n if (REMOVED_INPUTS.has(input.name) && input.name !== 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n logRemovalWarning(input.name, filePath, element, context);\n }\n }\n}\n\nfunction collectOutputChanges(element: TmplAstElement, source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const output of element.outputs) {\n const newName = OUTPUT_RENAMES.get(output.name);\n if (newName) {\n edits.push({ start: output.keySpan!.start.offset, end: output.keySpan!.end.offset, replacement: newName });\n }\n if (REMOVED_OUTPUTS.has(output.name)) {\n removeAttribute(output.sourceSpan.start.offset, output.sourceSpan.end.offset, source, edits);\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(${output.name})\" removed. Multi-sort is now supported by (sortChange) output.`,\n );\n }\n }\n}\n\nfunction collectPaginatorMigration(element: TmplAstElement, source: string, edits: Edit[]): boolean {\n let found = false;\n\n for (const attr of element.attributes) {\n if (attr.name === 'paginable') {\n removeAttribute(attr.sourceSpan.start.offset, attr.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n for (const input of element.inputs) {\n if (input.name === 'paginable') {\n removeAttribute(input.sourceSpan.start.offset, input.sourceSpan.end.offset, source, edits);\n found = true;\n }\n }\n\n if (found) {\n // Add [paginator]=\"paginator\" before closing > of opening tag\n const insertPos = element.startSourceSpan.end.offset - 1;\n edits.push({ start: insertPos, end: insertPos, replacement: ' [paginator]=\"paginator\"' });\n\n // Add eui-paginator after </table>\n if (element.endSourceSpan) {\n const afterTable = element.endSourceSpan.end.offset;\n edits.push({\n start: afterTable,\n end: afterTable,\n replacement: '\\n<!-- TODO: Configure paginator and implement onPageChange handler -->\\n<eui-paginator #paginator [pageSize]=\"10\" [pageSizeOptions]=\"[5, 10, 25, 50]\" />',\n });\n }\n }\n\n return found;\n}\n\nfunction collectEmptyMessageRename(element: TmplAstElement, edits: Edit[]): void {\n // Handle direct attribute on elements (unlikely but handle)\n for (const attr of element.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\nfunction collectTemplateEmptyMessageRename(template: TmplAstTemplate, edits: Edit[]): void {\n for (const attr of template.templateAttrs) {\n if (attr instanceof TmplAstTextAttribute && attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n for (const attr of template.attributes) {\n if (attr.name === 'euiTemplate' && attr.value === 'emptyMessage' && attr.valueSpan) {\n edits.push({ start: attr.valueSpan.start.offset, end: attr.valueSpan.end.offset, replacement: 'footer' });\n }\n }\n}\n\n// --- Pipe rename via AST ---\n\nfunction collectPipeRenames(nodes: TmplAstNode[], source: string, edits: Edit[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n for (const output of node.outputs) {\n if (output.handler) visitExpressionForPipes(output.handler, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstTemplate) {\n for (const input of node.inputs) {\n visitExpressionForPipes(input.value, edits);\n }\n collectPipeRenames(node.children, source, edits);\n }\n if (node instanceof TmplAstBoundText) {\n visitExpressionForPipes(node.value, edits);\n }\n }\n}\n\nfunction visitExpressionForPipes(expr: AST, edits: Edit[]): void {\n if (expr instanceof ASTWithSource && expr.ast) {\n visitAstForPipes(expr.ast, edits);\n } else {\n visitAstForPipes(expr, edits);\n }\n}\n\nfunction visitAstForPipes(ast: AST, edits: Edit[]): void {\n if (ast instanceof BindingPipe) {\n if (ast.name === OLD_PIPE && ast.nameSpan) {\n edits.push({ start: ast.nameSpan.start, end: ast.nameSpan.end, replacement: NEW_PIPE });\n }\n visitAstForPipes(ast.exp, edits);\n for (const arg of ast.args) {\n visitAstForPipes(arg, edits);\n }\n return;\n }\n\n if (ast instanceof Interpolation) {\n for (const expr of ast.expressions) {\n visitAstForPipes(expr, edits);\n }\n return;\n }\n\n // Recursively visit all properties that could contain AST nodes\n for (const key of Object.keys(ast)) {\n // eslint-disable-next-line\n const val = (ast as any)[key];\n if (val instanceof AST) {\n visitAstForPipes(val, edits);\n } else if (Array.isArray(val)) {\n for (const item of val) {\n if (item instanceof AST) visitAstForPipes(item, edits);\n }\n }\n }\n}\n\n// --- Import handling for paginator ---\n\nfunction addPaginatorImport(source: string, filePath: string): string {\n if (source.includes(PAGINATOR_COMPONENT)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Add import statement after last import\n let lastImportEnd = 0;\n for (const stmt of sourceFile.statements) {\n if (ts.isImportDeclaration(stmt)) {\n lastImportEnd = stmt.getEnd();\n }\n }\n\n if (lastImportEnd > 0) {\n edits.push({\n start: lastImportEnd,\n end: lastImportEnd,\n replacement: `\\nimport { ${PAGINATOR_COMPONENT} } from '${PAGINATOR_IMPORT_PATH}';`,\n });\n }\n\n // Add to component imports array\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && isComponentMetadataProperty(node)) {\n if (ts.isArrayLiteralExpression(node.initializer)) {\n const arr = node.initializer;\n const elements = arr.elements;\n if (elements.length > 0) {\n const lastElement = elements[elements.length - 1];\n edits.push({\n start: lastElement.getEnd(),\n end: lastElement.getEnd(),\n replacement: `, ${PAGINATOR_COMPONENT}`,\n });\n } else {\n const insertPos = arr.getStart(sourceFile) + 1;\n edits.push({ start: insertPos, end: insertPos, replacement: PAGINATOR_COMPONENT });\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return applyEdits(source, edits);\n}\n\n// --- Helpers ---\n\nfunction removeAttribute(start: number, end: number, source: string, edits: Edit[]): void {\n let adjustedStart = start;\n while (adjustedStart > 0 && (source[adjustedStart - 1] === ' ' || source[adjustedStart - 1] === '\\t')) {\n adjustedStart--;\n }\n edits.push({ start: adjustedStart, end, replacement: '' });\n}\n\nfunction logRemovalWarning(name: string, filePath: string, element: TmplAstElement, context: SchematicContext): void {\n const { line } = element.startSourceSpan.start;\n if (name === 'defaultMultiOrder') {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed. Use setSort(Sort[]) to initialize sorting.`);\n } else {\n context.logger.warn(`${filePath}:${line + 1} - \"[${name}]\" removed to align to Design System.`);\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n unique.set(key, edit);\n }\n let result = source;\n for (const edit of [...unique.values()].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2853
2865
|
"displayName": "Schema",
|
|
2854
2866
|
"properties": [
|
|
2855
2867
|
{
|
|
@@ -2861,7 +2873,7 @@
|
|
|
2861
2873
|
"indexKey": "",
|
|
2862
2874
|
"optional": true,
|
|
2863
2875
|
"description": "",
|
|
2864
|
-
"line":
|
|
2876
|
+
"line": 49,
|
|
2865
2877
|
"rawdescription": "\n"
|
|
2866
2878
|
},
|
|
2867
2879
|
{
|
|
@@ -2873,7 +2885,7 @@
|
|
|
2873
2885
|
"indexKey": "",
|
|
2874
2886
|
"optional": true,
|
|
2875
2887
|
"description": "",
|
|
2876
|
-
"line":
|
|
2888
|
+
"line": 48,
|
|
2877
2889
|
"rawdescription": "\n"
|
|
2878
2890
|
}
|
|
2879
2891
|
],
|
|
@@ -2891,12 +2903,12 @@
|
|
|
2891
2903
|
},
|
|
2892
2904
|
{
|
|
2893
2905
|
"name": "Schema",
|
|
2894
|
-
"id": "interface-Schema-
|
|
2895
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
2906
|
+
"id": "interface-Schema-760dec88d3f709d5b38226e55f7cbfb1e5fabcea6c004fd46e611be1dd563b8b247e9e6c77667e5582fe7b443374ba2c4facd2b2db2985edb46c9a4a37a8a876-18",
|
|
2907
|
+
"file": "packages/core/schematics/migrate-eui-tabs/index.ts",
|
|
2896
2908
|
"deprecated": false,
|
|
2897
2909
|
"deprecationMessage": "",
|
|
2898
2910
|
"type": "interface",
|
|
2899
|
-
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2911
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface TextChange {\n start: number;\n end: number;\n text: string;\n}\n\ninterface MigrationResult {\n content: string;\n migrated: number;\n skipped: number;\n}\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst OLD_LABEL = 'eui-tab-label';\nconst OLD_SUB_LABEL = 'euiTabSubLabel';\nconst OLD_CONTENT = 'eui-tab-content';\nconst NEW_HEADER = 'eui-tab-header';\nconst NEW_HEADER_LABEL = 'eui-tab-header-label';\nconst NEW_HEADER_SUB_LABEL = 'eui-tab-header-sub-label';\nconst NEW_BODY = 'eui-tab-body';\n\nexport function migrateEuiTabs(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let migrated = 0;\n let skipped = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) {\n return;\n }\n\n const original = buffer.toString('utf-8');\n const result = path.endsWith('.html')\n ? migrateTemplate(original, path, context)\n : migrateInlineTemplates(original, path, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate ${result.migrated} tab block(s) in ${path}`);\n } else {\n tree.overwrite(path, result.content);\n }\n }\n });\n\n context.logger.info(`Migrated ${migrated} EUI tab template block(s).`);\n if (skipped > 0) {\n context.logger.warn(`Skipped ${skipped} malformed EUI tab template block(s).`);\n }\n if (options.dryRun) {\n logDryRunNote(context);\n }\n\n return tree;\n };\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const initializer = unwrapExpression(node.initializer);\n\n if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {\n const start = initializer.getStart(sourceFile) + 1;\n const end = initializer.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n const result = migrateTemplate(rawTemplate, `${filePath}@inline-template`, context);\n\n migrated += result.migrated;\n skipped += result.skipped;\n\n if (result.content !== rawTemplate) {\n changes.push({ start, end, text: result.content });\n }\n } else if (ts.isTemplateExpression(initializer)) {\n context.logger.warn(`Skipping interpolated inline template in ${filePath}.`);\n skipped++;\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n\n return {\n content: applyChanges(source, changes),\n migrated,\n skipped,\n };\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (\n (ts.isIdentifier(name) && name.text === 'template') ||\n (ts.isStringLiteral(name) && name.text === 'template')\n );\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n\n while (ts.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n\n return current;\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) {\n return false;\n }\n\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) {\n return false;\n }\n\n return (\n ts.isDecorator(callExpression.parent) &&\n ts.isIdentifier(callExpression.expression) &&\n callExpression.expression.text === 'Component'\n );\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): MigrationResult {\n const parsed = parseTemplate(source, filePath, { preserveWhitespaces: true });\n const changes: TextChange[] = [];\n let migrated = 0;\n let skipped = 0;\n\n for (const error of parsed.errors ?? []) {\n context.logger.warn(`Template parse warning in ${filePath}: ${error.msg}`);\n }\n\n for (const tab of findElements(parsed.nodes, 'eui-tab')) {\n const label = findDirectChild(tab, OLD_LABEL);\n const content = findDirectChild(tab, OLD_CONTENT);\n const hasOldMarkup = Boolean(label || content);\n const hasNewMarkup = Boolean(findDirectChild(tab, NEW_HEADER) || findDirectChild(tab, NEW_BODY));\n\n if (!hasOldMarkup || hasNewMarkup) {\n continue;\n }\n\n if (!label || !content) {\n context.logger.warn(`Skipping malformed <eui-tab> in ${filePath}.`);\n skipped++;\n continue;\n }\n\n changes.push({\n start: label.sourceSpan.start.offset,\n end: label.sourceSpan.end.offset,\n text: buildHeaderReplacement(source, label),\n });\n changes.push({\n start: content.sourceSpan.start.offset,\n end: content.sourceSpan.end.offset,\n text: buildBodyReplacement(source, content),\n });\n migrated++;\n }\n\n return {\n content: applyChanges(source, withoutOverlaps(changes)),\n migrated,\n skipped,\n };\n}\n\nfunction findElements(nodes: readonly TmplAstNode[], name: string): TmplAstElement[] {\n const matches: TmplAstElement[] = [];\n\n for (const node of nodes) {\n if (isElement(node)) {\n if (node.name === name) {\n matches.push(node);\n }\n matches.push(...findElements(node.children, name));\n }\n }\n\n return matches;\n}\n\nfunction findDirectChild(element: TmplAstElement, name: string): TmplAstElement | undefined {\n return element.children.find((child): child is TmplAstElement => isElement(child) && child.name === name);\n}\n\nfunction isElement(node: TmplAstNode): node is TmplAstElement {\n return node instanceof TmplAstElement;\n}\n\nfunction buildHeaderReplacement(source: string, label: TmplAstElement): string {\n const indent = getIndent(source, label.sourceSpan.start.offset);\n const labelAttributes = getAttributeText(source, label, OLD_LABEL);\n const subLabels = findElements(label.children, OLD_SUB_LABEL);\n const mainLabelContent = removeElementRanges(getInnerText(source, label), label, subLabels);\n const lines = [\n `<${NEW_HEADER}>`,\n `${indent} <${NEW_HEADER_LABEL}${labelAttributes}>`,\n ...formatInnerLines(mainLabelContent, `${indent} `),\n `${indent} </${NEW_HEADER_LABEL}>`,\n ];\n\n for (const subLabel of subLabels) {\n const subLabelAttributes = getAttributeText(source, subLabel, OLD_SUB_LABEL);\n lines.push(\n `${indent} <${NEW_HEADER_SUB_LABEL}${subLabelAttributes}>`,\n ...formatInnerLines(getInnerText(source, subLabel), `${indent} `),\n `${indent} </${NEW_HEADER_SUB_LABEL}>`,\n );\n }\n\n lines.push(`${indent}</${NEW_HEADER}>`);\n\n return lines.join('\\n');\n}\n\nfunction buildBodyReplacement(source: string, content: TmplAstElement): string {\n const indent = getIndent(source, content.sourceSpan.start.offset);\n const attributes = getAttributeText(source, content, OLD_CONTENT);\n const innerText = getInnerText(source, content);\n const trimmed = innerText.trim();\n\n if (trimmed && !trimmed.includes('\\n')) {\n return `<${NEW_BODY}${attributes}>${trimmed}</${NEW_BODY}>`;\n }\n\n return [\n `<${NEW_BODY}${attributes}>`,\n ...formatInnerLines(innerText, `${indent} `),\n `${indent}</${NEW_BODY}>`,\n ].join('\\n');\n}\n\nfunction getInnerText(source: string, element: TmplAstElement): string {\n if (!element.endSourceSpan) {\n return '';\n }\n\n return source.slice(element.startSourceSpan.end.offset, element.endSourceSpan.start.offset);\n}\n\nfunction removeElementRanges(source: string, parent: TmplAstElement, elements: readonly TmplAstElement[]): string {\n const parentContentStart = parent.startSourceSpan.end.offset;\n let result = source;\n\n for (const element of [...elements].sort((a, b) => b.sourceSpan.start.offset - a.sourceSpan.start.offset)) {\n const start = element.sourceSpan.start.offset - parentContentStart;\n const end = element.sourceSpan.end.offset - parentContentStart;\n result = result.slice(0, start) + result.slice(end);\n }\n\n return result;\n}\n\nfunction getAttributeText(source: string, element: TmplAstElement, tagName: string): string {\n const startTag = source.slice(element.startSourceSpan.start.offset, element.startSourceSpan.end.offset);\n const tagStart = startTag.indexOf(tagName);\n\n if (tagStart === -1) {\n return '';\n }\n\n const contentEnd = startTag.endsWith('/>') ? startTag.length - 2 : startTag.length - 1;\n return startTag.slice(tagStart + tagName.length, contentEnd).trimEnd();\n}\n\nfunction getIndent(source: string, offset: number): string {\n const lineStart = source.lastIndexOf('\\n', offset - 1) + 1;\n const prefix = source.slice(lineStart, offset);\n return prefix.match(/^\\s*/)?.[0] ?? '';\n}\n\nfunction formatInnerLines(source: string, indent: string): string[] {\n const trimmed = source.trim();\n\n if (!trimmed) {\n return [];\n }\n\n return trimmed.split(/\\r?\\n/).map((line) => `${indent}${line.trim()}`);\n}\n\nfunction withoutOverlaps(changes: TextChange[]): TextChange[] {\n const accepted: TextChange[] = [];\n\n for (const change of [...changes].sort((a, b) => a.start - b.start || b.end - a.end)) {\n if (!accepted.some((current) => change.start < current.end && current.start < change.end)) {\n accepted.push(change);\n }\n }\n\n return accepted;\n}\n\nfunction applyChanges(source: string, changes: readonly TextChange[]): string {\n let result = source;\n\n for (const change of [...changes].sort((a, b) => b.start - a.start)) {\n result = result.slice(0, change.start) + change.text + result.slice(change.end);\n }\n\n return result;\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2900
2912
|
"displayName": "Schema",
|
|
2901
2913
|
"properties": [
|
|
2902
2914
|
{
|
|
@@ -2908,7 +2920,7 @@
|
|
|
2908
2920
|
"indexKey": "",
|
|
2909
2921
|
"optional": true,
|
|
2910
2922
|
"description": "",
|
|
2911
|
-
"line":
|
|
2923
|
+
"line": 20,
|
|
2912
2924
|
"rawdescription": "\n"
|
|
2913
2925
|
},
|
|
2914
2926
|
{
|
|
@@ -2920,7 +2932,7 @@
|
|
|
2920
2932
|
"indexKey": "",
|
|
2921
2933
|
"optional": true,
|
|
2922
2934
|
"description": "",
|
|
2923
|
-
"line":
|
|
2935
|
+
"line": 19,
|
|
2924
2936
|
"rawdescription": "\n"
|
|
2925
2937
|
}
|
|
2926
2938
|
],
|
|
@@ -2938,12 +2950,12 @@
|
|
|
2938
2950
|
},
|
|
2939
2951
|
{
|
|
2940
2952
|
"name": "Schema",
|
|
2941
|
-
"id": "interface-Schema-
|
|
2942
|
-
"file": "packages/core/schematics/migrate-
|
|
2953
|
+
"id": "interface-Schema-e1cd02924eb82a618c71a0b26c081bdd020519d2699aa0e2dc98640c4e0f347c3649b967c5fc87543e41bbbfabd1304835850ccc38f40684d6521c242b2030ed-19",
|
|
2954
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
2943
2955
|
"deprecated": false,
|
|
2944
2956
|
"deprecationMessage": "",
|
|
2945
2957
|
"type": "interface",
|
|
2946
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface ModuleMapping {\n importPath: string;\n selectors: Record<string, string>;\n}\n\nconst MODULE_MAPPINGS: Record<string, ModuleMapping> = {\n EuiAccordionModule: {\n importPath: '@eui/components/eui-accordion',\n selectors: {\n 'eui-accordion': 'EuiAccordionComponent',\n 'eui-accordion-item': 'EuiAccordionItemComponent',\n euiAccordionItemHeader: 'EuiAccordionItemHeaderDirective',\n },\n },\n EuiAlertModule: {\n importPath: '@eui/components/eui-alert',\n selectors: {\n 'eui-alert': 'EuiAlertComponent',\n euiAlert: 'EuiAlertComponent',\n 'eui-alert-title': 'EuiAlertTitleComponent',\n },\n },\n EuiAutocompleteModule: {\n importPath: '@eui/components/eui-autocomplete',\n selectors: {\n 'eui-autocomplete': 'EuiAutocompleteComponent',\n euiAutocomplete: 'EuiAutocompleteComponent',\n 'eui-autocomplete-option': 'EuiAutocompleteOptionComponent',\n 'eui-autocomplete-option-group': 'EuiAutocompleteOptionGroupComponent',\n 'eui-autocomplete-panel': 'EuiAutocompletePanelComponent',\n },\n },\n EuiAvatarModule: {\n importPath: '@eui/components/eui-avatar',\n selectors: {\n 'eui-avatar': 'EuiAvatarComponent',\n euiAvatar: 'EuiAvatarComponent',\n },\n },\n EuiBadgeModule: {\n importPath: '@eui/components/eui-badge',\n selectors: {\n 'eui-badge': 'EuiBadgeComponent',\n euiBadge: 'EuiBadgeComponent',\n },\n },\n EuiBlockContentModule: {\n importPath: '@eui/components/eui-block-content',\n selectors: {\n 'eui-block-content': 'EuiBlockContentComponent',\n },\n },\n EuiBreadcrumbModule: {\n importPath: '@eui/components/eui-breadcrumb',\n selectors: {\n 'eui-breadcrumb': 'EuiBreadcrumbComponent',\n },\n },\n EuiButtonModule: {\n importPath: '@eui/components/eui-button',\n selectors: {\n euiButton: 'EuiButtonComponent',\n },\n },\n EuiButtonGroupModule: {\n importPath: '@eui/components/eui-button-group',\n selectors: {\n 'eui-button-group': 'EuiButtonGroupComponent',\n },\n },\n EuiCardModule: {\n importPath: '@eui/components/eui-card',\n selectors: {\n 'eui-card': 'EuiCardComponent',\n 'eui-card-header': 'EuiCardHeaderComponent',\n 'eui-card-header-title': 'EuiCardHeaderTitleComponent',\n 'eui-card-content': 'EuiCardContentComponent',\n 'eui-card-footer': 'EuiCardFooterComponent',\n 'eui-card-media': 'EuiCardMediaComponent',\n },\n },\n EuiChipModule: {\n importPath: '@eui/components/eui-chip',\n selectors: {\n 'eui-chip': 'EuiChipComponent',\n euiChip: 'EuiChipComponent',\n },\n },\n EuiChipListModule: {\n importPath: '@eui/components/eui-chip-list',\n selectors: {\n 'eui-chip-list': 'EuiChipListComponent',\n },\n },\n EuiChipGroupModule: {\n importPath: '@eui/components/eui-chip-group',\n selectors: {\n 'eui-chip-group': 'EuiChipGroupComponent',\n },\n },\n EuiDashboardCardModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDashboardButtonModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDatepickerModule: {\n importPath: '@eui/components/eui-datepicker',\n selectors: {\n 'eui-datepicker': 'EuiDatepickerComponent',\n },\n },\n EuiDateRangeSelectorModule: {\n importPath: '@eui/components/eui-date-range-selector',\n selectors: {\n 'eui-date-range-selector': 'EuiDateRangeSelectorComponent',\n },\n },\n EuiDialogModule: {\n importPath: '@eui/components/eui-dialog',\n selectors: {\n 'eui-dialog': 'EuiDialogComponent',\n 'eui-dialog-header': 'EuiDialogHeaderDirective',\n 'eui-dialog-footer': 'EuiDialogFooterDirective',\n 'eui-dialog-container': 'EuiDialogContainerComponent',\n },\n },\n EuiDisableContentModule: {\n importPath: '@eui/components/eui-disable-content',\n selectors: {\n 'eui-disable-content': 'EuiDisableContentComponent',\n },\n },\n EuiDiscussionThreadModule: {\n importPath: '@eui/components/eui-discussion-thread',\n selectors: {\n 'eui-discussion-thread': 'EuiDiscussionThreadComponent',\n 'eui-discussion-thread-item': 'EuiDiscussionThreadItemComponent',\n },\n },\n EuiDropdownModule: {\n importPath: '@eui/components/eui-dropdown',\n selectors: {\n 'eui-dropdown': 'EuiDropdownComponent',\n },\n },\n EuiFeedbackMessageModule: {\n importPath: '@eui/components/eui-feedback-message',\n selectors: {\n 'eui-feedback-message': 'EuiFeedbackMessageComponent',\n },\n },\n EuiFieldsetModule: {\n importPath: '@eui/components/eui-fieldset',\n selectors: {\n 'eui-fieldset': 'EuiFieldsetComponent',\n euiFieldsetLabelRightContent: 'EuiFieldsetLabelRightContentTagDirective',\n euiFieldsetLabelExtraContent: 'EuiFieldsetLabelExtraContentTagDirective',\n },\n },\n EuiFileUploadModule: {\n importPath: '@eui/components/eui-file-upload',\n selectors: {\n 'eui-file-upload': 'EuiFileUploadComponent',\n },\n },\n EuiGrowlModule: {\n importPath: '@eui/components/eui-growl',\n selectors: {\n 'eui-growl': 'EuiGrowlComponent',\n },\n },\n EuiIconModule: {\n importPath: '@eui/components/eui-icon',\n selectors: {\n 'eui-icon-svg': 'EuiIconSvgComponent',\n euiIconSvg: 'EuiIconSvgComponent',\n },\n },\n EuiIconButtonModule: {\n importPath: '@eui/components/eui-icon-button',\n selectors: {\n 'eui-icon-button': 'EuiIconButtonComponent',\n },\n },\n EuiIconToggleModule: {\n importPath: '@eui/components/eui-icon-toggle',\n selectors: {\n 'eui-icon-toggle': 'EuiIconToggleComponent',\n },\n },\n EuiInputCheckboxModule: {\n importPath: '@eui/components/eui-input-checkbox',\n selectors: {\n euiInputCheckBox: 'EuiInputCheckboxComponent',\n },\n },\n EuiInputGroupModule: {\n importPath: '@eui/components/eui-input-group',\n selectors: {\n euiInputGroup: 'EuiInputGroupComponent',\n 'eui-input-group-addon': 'EuiInputGroupAddOnComponent',\n euiInputGroupAddOn: 'EuiInputGroupAddOnComponent',\n 'eui-input-group-addon-item': 'EuiInputGroupAddOnItemComponent',\n euiInputGroupAddOnItem: 'EuiInputGroupAddOnItemComponent',\n },\n },\n EuiInputNumberModule: {\n importPath: '@eui/components/eui-input-number',\n selectors: {\n euiInputNumber: 'EuiInputNumberComponent',\n },\n },\n EuiInputRadioModule: {\n importPath: '@eui/components/eui-input-radio',\n selectors: {\n euiInputRadio: 'EuiInputRadioComponent',\n },\n },\n EuiInputTextModule: {\n importPath: '@eui/components/eui-input-text',\n selectors: {\n euiInputText: 'EuiInputTextComponent',\n },\n },\n EuiLabelModule: {\n importPath: '@eui/components/eui-label',\n selectors: {\n 'eui-label': 'EuiLabelComponent',\n euiLabel: 'EuiLabelComponent',\n },\n },\n EuiListModule: {\n importPath: '@eui/components/eui-list',\n selectors: {\n 'eui-list': 'EuiListComponent',\n euiList: 'EuiListComponent',\n 'eui-list-item': 'EuiListItemComponent',\n euiListItem: 'EuiListItemComponent',\n },\n },\n EuiMenuModule: {\n importPath: '@eui/components/eui-menu',\n selectors: {\n 'eui-menu': 'EuiMenuComponent',\n 'eui-menu-item': 'EuiMenuItemComponent',\n },\n },\n EuiMessageBoxModule: {\n importPath: '@eui/components/eui-message-box',\n selectors: {\n 'eui-message-box': 'EuiMessageBoxComponent',\n 'eui-message-box-footer': 'EuiMessageBoxFooterDirective',\n },\n },\n EuiOverlayModule: {\n importPath: '@eui/components/eui-overlay',\n selectors: {\n 'eui-overlay': 'EuiOverlayComponent',\n },\n },\n EuiPageModule: {\n importPath: '@eui/components/eui-page',\n selectors: {\n 'eui-page': 'EuiPageComponent',\n },\n },\n EuiPaginatorModule: {\n importPath: '@eui/components/eui-paginator',\n selectors: {\n 'eui-paginator': 'EuiPaginatorComponent',\n },\n },\n EuiPopoverModule: {\n importPath: '@eui/components/eui-popover',\n selectors: {\n 'eui-popover': 'EuiPopoverComponent',\n },\n },\n EuiProgressBarModule: {\n importPath: '@eui/components/eui-progress-bar',\n selectors: {\n 'eui-progress-bar': 'EuiProgressBarComponent',\n },\n },\n EuiProgressCircleModule: {\n importPath: '@eui/components/eui-progress-circle',\n selectors: {\n 'eui-progress-circle': 'EuiProgressCircleComponent',\n },\n },\n EuiSelectModule: {\n importPath: '@eui/components/eui-select',\n selectors: {\n euiSelect: 'EuiSelectComponent',\n },\n },\n EuiSidebarMenuModule: {\n importPath: '@eui/components/eui-sidebar-menu',\n selectors: {\n 'eui-sidebar-menu': 'EuiSidebarMenuComponent',\n },\n },\n EuiSkeletonModule: {\n importPath: '@eui/components/eui-skeleton',\n selectors: {\n 'eui-skeleton': 'EuiSkeletonComponent',\n },\n },\n EuiSlideToggleModule: {\n importPath: '@eui/components/eui-slide-toggle',\n selectors: {\n 'eui-slide-toggle': 'EuiSlideToggleComponent',\n },\n },\n EuiTableModule: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTableV2Module: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTabsModule: {\n importPath: '@eui/components/eui-tabs',\n selectors: {\n 'eui-tabs': 'EuiTabsComponent',\n 'eui-tab': 'EuiTabComponent',\n 'eui-tab-header': 'EuiTabHeaderComponent',\n 'eui-tab-body': 'EuiTabBodyComponent',\n },\n },\n EuiTextAreaModule: {\n importPath: '@eui/components/eui-textarea',\n selectors: {\n euiTextArea: 'EuiTextareaComponent',\n },\n },\n EuiTimelineModule: {\n importPath: '@eui/components/eui-timeline',\n selectors: {\n 'eui-timeline': 'EuiTimelineComponent',\n 'eui-timeline-item': 'EuiTimelineItemComponent',\n },\n },\n EuiTimepickerModule: {\n importPath: '@eui/components/eui-timepicker',\n selectors: {\n 'eui-timepicker': 'EuiTimepickerComponent',\n },\n },\n EuiTreeModule: {\n importPath: '@eui/components/eui-tree',\n selectors: {\n 'eui-tree': 'EuiTreeComponent',\n },\n },\n EuiTreeListModule: {\n importPath: '@eui/components/eui-tree-list',\n selectors: {\n 'eui-tree-list': 'EuiTreeListComponent',\n 'eui-tree-list-item': 'EuiTreeListItemComponent',\n },\n },\n EuiUserProfileModule: {\n importPath: '@eui/components/eui-user-profile',\n selectors: {\n 'eui-user-profile': 'EuiUserProfileComponent',\n },\n },\n EuiWizardModule: {\n importPath: '@eui/components/eui-wizard',\n selectors: {\n 'eui-wizard': 'EuiWizardComponent',\n 'eui-wizard-step': 'EuiWizardStepComponent',\n },\n },\n EuiTooltipDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTooltip: 'EuiTooltipDirective',\n },\n },\n EuiTemplateDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTemplate: 'EuiTemplateDirective',\n },\n },\n EuiResizableDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiResizable: 'EuiResizableDirective',\n 'eui-resizable': 'EuiResizableComponent',\n },\n },\n EuiMaxLengthDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiEditorMaxlength: 'EuiMaxLengthDirective',\n },\n },\n EuiTruncatePipeModule: {\n importPath: '@eui/components/pipes',\n selectors: {\n euiTruncate: 'EuiTruncatePipe',\n },\n },\n EuiLayoutModule: {\n importPath: '@eui/components/layout',\n selectors: {\n 'eui-app': 'EuiAppComponent',\n 'eui-header': 'EuiHeaderComponent',\n 'eui-footer': 'EuiFooterComponent',\n 'eui-toolbar': 'EuiToolbarComponent',\n 'eui-sidebar-toggle': 'EuiSidebarToggleComponent',\n },\n },\n};\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateToStandalone(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const allModuleNames = Object.keys(MODULE_MAPPINGS);\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!allModuleNames.some((m) => source.includes(m))) return;\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const componentDecorators = findComponentDecorators(sourceFile);\n\n for (const decorator of componentDecorators) {\n const importsNode = findImportsArrayNode(decorator);\n if (!importsNode) continue;\n\n const currentSource = tree.read(path)!.toString('utf-8');\n const modulesInArray = allModuleNames.filter((m) => hasModuleInArray(importsNode, m, source));\n if (modulesInArray.length === 0) continue;\n\n const templateSelectors = getTemplateSelectors(tree, path, decorator, source);\n const replacements = buildReplacements(modulesInArray, templateSelectors);\n if (replacements.size === 0) continue;\n\n const result = applyReplacements(currentSource, path, replacements);\n if (options.dryRun) {\n logDryRun(context, `Would replace module imports with standalone imports in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n }\n });\n\n context.logger.info('Migration to standalone imports complete.');\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction buildReplacements(moduleNames: string[], templateSelectors: Set<string>): Map<string, { components: string[]; importPath: string }> {\n const map = new Map<string, { components: string[]; importPath: string }>();\n\n for (const moduleName of moduleNames) {\n const mapping = MODULE_MAPPINGS[moduleName];\n const components: string[] = [];\n\n for (const [selector, component] of Object.entries(mapping.selectors)) {\n if (templateSelectors.has(selector)) {\n components.push(component);\n }\n }\n\n if (components.length > 0) {\n map.set(moduleName, { components: [...new Set(components)].sort(), importPath: mapping.importPath });\n }\n }\n\n return map;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ts.Decorator[] {\n const decorators: ts.Decorator[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n decorators.push(dec);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return decorators;\n}\n\nfunction findImportsArrayNode(decorator: ts.Decorator): ts.ArrayLiteralExpression | undefined {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) return undefined;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer;\n }\n }\n }\n return undefined;\n}\n\nfunction hasModuleInArray(array: ts.ArrayLiteralExpression, moduleName: string, source: string): boolean {\n return array.elements.some((el) => source.slice(el.getStart(), el.getEnd()).trim() === moduleName);\n}\n\nfunction getTemplateSelectors(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): Set<string> {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0] as ts.ObjectLiteralExpression;\n const allSelectors = Object.values(MODULE_MAPPINGS).flatMap((m) => Object.keys(m.selectors));\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return findSelectorsInTemplate(init.text, allSelectors);\n }\n }\n }\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) {\n return findSelectorsInTemplate(templateBuffer.toString('utf-8'), allSelectors);\n }\n }\n }\n }\n\n return new Set();\n}\n\nfunction findSelectorsInTemplate(html: string, knownSelectors: string[]): Set<string> {\n const selectors = new Set<string>();\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n\n const visit = (nodes: TmplAstNode[]): void => {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (knownSelectors.includes(node.name)) selectors.add(node.name);\n for (const attr of node.attributes) {\n if (knownSelectors.includes(attr.name)) selectors.add(attr.name);\n }\n visit(node.children);\n }\n }\n };\n visit(parsed.nodes);\n return selectors;\n}\n\nfunction applyReplacements(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n let result = replaceInImportsArray(source, sourceFile, replacements);\n result = updateEsImports(result, filePath, replacements);\n return result;\n}\n\nfunction replaceInImportsArray(source: string, sourceFile: ts.SourceFile, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const newElements = prop.initializer.elements\n .map((el) => {\n const text = result.slice(el.getStart(sourceFile), el.getEnd()).trim();\n const replacement = replacements.get(text);\n return replacement ? replacement.components.join(', ') : text;\n })\n .join(', ');\n result = result.slice(0, prop.initializer.getStart(sourceFile) + 1) + newElements + result.slice(prop.initializer.getEnd() - 1);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return result;\n}\n\nfunction updateEsImports(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n\n for (const [moduleName, { components, importPath }] of replacements) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const importNames = namedBindings.elements.map((el) => el.name.text);\n if (!importNames.includes(moduleName)) continue;\n\n const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const remaining = importNames.filter((n) => n !== moduleName);\n const toAdd = components.filter((c) => !importNames.includes(c));\n\n if (moduleSpecifier === importPath) {\n const newNames = [...remaining, ...toAdd].sort();\n const newClause = `{ ${newNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n if (remaining.length > 0) {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n }\n\n if (toAdd.length > 0) {\n const updatedSf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n const existing = updatedSf.statements.find(\n (s) => ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n ) as ts.ImportDeclaration | undefined;\n\n if (existing?.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {\n const existingNames = existing.importClause.namedBindings.elements.map((el) => el.name.text);\n const allNames = [...new Set([...existingNames, ...toAdd])].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, existing.importClause.namedBindings.getStart(updatedSf)) + newClause + result.slice(existing.importClause.namedBindings.getEnd());\n } else {\n const newImport = `import { ${toAdd.join(', ')} } from '${importPath}';\\n`;\n result = newImport + result;\n }\n }\n }\n break;\n }\n }\n\n return result;\n}\n",
|
|
2958
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\nconst OLD_TAG = 'eui-toolbar-menu';\nconst NEW_TAG = 'eui-toolbar-mega-menu';\nconst OLD_COMPONENT = 'EuiToolbarMenuComponent';\nconst NEW_COMPONENT = 'EuiToolbarMegaMenuComponent';\nconst OLD_INTERFACE = 'ToolbarItem';\nconst NEW_INTERFACE = 'EuiMenuItem';\nconst NEW_COMPONENT_PATH = '@eui/components/layout';\nconst NEW_INTERFACE_PATH = '@eui/core';\nconst REMOVED_OUTPUT = 'menuItemClick';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nexport function migrateEuiToolbarMenu(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_TAG) && !original.includes(OLD_COMPONENT) && !original.includes(OLD_INTERFACE)) return;\n\n let result: string;\n\n if (path.endsWith('.html')) {\n result = migrateTemplate(original, path, context);\n } else {\n result = migrateTypeScript(original, path, context);\n }\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate eui-toolbar-menu → eui-toolbar-mega-menu in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated eui-toolbar-menu → eui-toolbar-mega-menu in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTemplate(source: string, filePath: string, context: SchematicContext): string {\n const parsed = parseTemplate(source, '', { preserveWhitespaces: true });\n const edits: Edit[] = [];\n\n visitNodes(parsed.nodes, source, edits, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n let result = migrateInlineTemplates(source, filePath, context);\n result = migrateImportsAndTypes(result, filePath, context);\n return result;\n}\n\nfunction migrateInlineTemplates(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const changes: Edit[] = [];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAssignment(node) && isTemplateProperty(node) && isComponentMetadataProperty(node)) {\n const init = unwrapExpression(node.initializer);\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n const start = init.getStart(sourceFile) + 1;\n const end = init.getEnd() - 1;\n const rawTemplate = source.slice(start, end);\n if (!rawTemplate.includes(OLD_TAG)) {\n ts.forEachChild(node, visit); return; \n}\n const migrated = migrateTemplate(rawTemplate, filePath, context);\n if (migrated !== rawTemplate) changes.push({ start, end, replacement: migrated });\n }\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n return applyEdits(source, changes);\n}\n\nfunction migrateImportsAndTypes(source: string, filePath: string, context: SchematicContext): string {\n if (!source.includes(OLD_COMPONENT) && !source.includes(OLD_INTERFACE)) return source;\n\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiMenuItem is already imported from @eui/core\n let hasEuiMenuItemImport = false;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const moduleSpec = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE && moduleSpec === NEW_INTERFACE_PATH) {\n hasEuiMenuItemImport = true;\n }\n }\n }\n\n // Second pass: collect edits for import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const moduleSpec = stmt.moduleSpecifier as ts.StringLiteral;\n const specifiers = namedBindings.elements;\n const hasComponent = specifiers.some((s) => s.name.text === OLD_COMPONENT);\n const hasInterface = specifiers.some((s) => s.name.text === OLD_INTERFACE);\n\n if (hasComponent && hasInterface) {\n // Both are in the same import → must split into two different paths\n const others = specifiers.filter((s) => s.name.text !== OLD_COMPONENT && s.name.text !== OLD_INTERFACE);\n const lines: string[] = [];\n lines.push(`import { ${NEW_COMPONENT} } from '${NEW_COMPONENT_PATH}';`);\n if (!hasEuiMenuItemImport) {\n lines.push(`import { ${NEW_INTERFACE} } from '${NEW_INTERFACE_PATH}';`);\n }\n if (others.length > 0) {\n const otherNames = others.map((s) => s.name.text).join(', ');\n lines.push(`import { ${otherNames} } from '${moduleSpec.text}';`);\n }\n edits.push({\n start: stmt.getStart(sourceFile),\n end: stmt.getEnd(),\n replacement: lines.join('\\n'),\n });\n } else if (hasComponent) {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_COMPONENT_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_COMPONENT) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_COMPONENT,\n });\n }\n }\n } else if (hasInterface) {\n if (hasEuiMenuItemImport) {\n removeImportSpecifier(namedBindings, specifiers.find((s) => s.name.text === OLD_INTERFACE)!, sourceFile, edits);\n } else {\n edits.push({\n start: moduleSpec.getStart(sourceFile) + 1,\n end: moduleSpec.getEnd() - 1,\n replacement: NEW_INTERFACE_PATH,\n });\n for (const specifier of specifiers) {\n if (specifier.name.text === OLD_INTERFACE) {\n edits.push({\n start: specifier.name.getStart(sourceFile),\n end: specifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n }\n }\n }\n\n // Third pass: rename identifier references in non-import positions\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return; // skip imports (already handled)\n if (ts.isIdentifier(node)) {\n if (node.text === OLD_COMPONENT) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_COMPONENT });\n }\n if (node.text === OLD_INTERFACE) {\n edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: NEW_INTERFACE });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n // Warn about ToolbarItem-specific properties\n warnRemovedProperties(sourceFile, filePath, context);\n\n return applyEdits(source, edits);\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end: importDecl.getEnd(),\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction warnRemovedProperties(sourceFile: ts.SourceFile, filePath: string, context: SchematicContext): void {\n const deprecated = ['isHome', 'isSeparator'];\n\n const visit = (node: ts.Node): void => {\n if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && deprecated.includes(node.name.text)) {\n const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n context.logger.warn(\n `${filePath}:${line + 1} - \"${node.name.text}\" was part of ToolbarItem but does not exist on EuiMenuItem. Review manually.`,\n );\n }\n ts.forEachChild(node, visit);\n };\n\n visit(sourceFile);\n}\n\nfunction visitNodes(nodes: TmplAstNode[], source: string, edits: Edit[], filePath: string, context: SchematicContext): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (node.name === OLD_TAG) {\n collectTagRenames(node, source, edits);\n collectOutputRemovals(node, source, edits, filePath, context);\n }\n visitNodes(node.children, source, edits, filePath, context);\n }\n }\n}\n\nfunction collectTagRenames(element: TmplAstElement, source: string, edits: Edit[]): void {\n // Rename opening tag\n const openStart = element.startSourceSpan.start.offset + 1; // skip '<'\n edits.push({ start: openStart, end: openStart + OLD_TAG.length, replacement: NEW_TAG });\n\n // Rename closing tag\n if (element.endSourceSpan) {\n const closeStart = element.endSourceSpan.start.offset + 2; // skip '</'\n edits.push({ start: closeStart, end: closeStart + OLD_TAG.length, replacement: NEW_TAG });\n }\n}\n\nfunction collectOutputRemovals(\n element: TmplAstElement,\n source: string,\n edits: Edit[],\n filePath: string,\n context: SchematicContext,\n): void {\n for (const output of element.outputs) {\n if (output.name === REMOVED_OUTPUT) {\n let start = output.sourceSpan.start.offset;\n // Remove leading whitespace\n while (start > 0 && (source[start - 1] === ' ' || source[start - 1] === '\\t')) {\n start--;\n }\n edits.push({ start, end: output.sourceSpan.end.offset, replacement: '' });\n\n const { line } = element.startSourceSpan.start;\n context.logger.warn(\n `${filePath}:${line + 1} - \"(menuItemClick)\" has been removed. There is no equivalent on eui-toolbar-mega-menu.`,\n );\n }\n }\n}\n\nfunction isTemplateProperty(node: ts.PropertyAssignment): boolean {\n const name = node.name;\n return (ts.isIdentifier(name) && name.text === 'template') || (ts.isStringLiteral(name) && name.text === 'template');\n}\n\nfunction isComponentMetadataProperty(node: ts.PropertyAssignment): boolean {\n const objectLiteral = node.parent;\n if (!ts.isObjectLiteralExpression(objectLiteral)) return false;\n const callExpression = objectLiteral.parent;\n if (!ts.isCallExpression(callExpression) || callExpression.arguments[0] !== objectLiteral) return false;\n return ts.isDecorator(callExpression.parent) && ts.isIdentifier(callExpression.expression) && callExpression.expression.text === 'Component';\n}\n\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n let current = expression;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n // Deduplicate edits at same position (e.g. module path edits when both Component and ToolbarItem are from same source)\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n // Last wins for same range\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2947
2959
|
"displayName": "Schema",
|
|
2948
2960
|
"properties": [
|
|
2949
2961
|
{
|
|
@@ -2955,7 +2967,7 @@
|
|
|
2955
2967
|
"indexKey": "",
|
|
2956
2968
|
"optional": true,
|
|
2957
2969
|
"description": "",
|
|
2958
|
-
"line":
|
|
2970
|
+
"line": 18,
|
|
2959
2971
|
"rawdescription": "\n"
|
|
2960
2972
|
},
|
|
2961
2973
|
{
|
|
@@ -2967,7 +2979,7 @@
|
|
|
2967
2979
|
"indexKey": "",
|
|
2968
2980
|
"optional": true,
|
|
2969
2981
|
"description": "",
|
|
2970
|
-
"line":
|
|
2982
|
+
"line": 17,
|
|
2971
2983
|
"rawdescription": "\n"
|
|
2972
2984
|
}
|
|
2973
2985
|
],
|
|
@@ -2985,12 +2997,12 @@
|
|
|
2985
2997
|
},
|
|
2986
2998
|
{
|
|
2987
2999
|
"name": "Schema",
|
|
2988
|
-
"id": "interface-Schema-
|
|
2989
|
-
"file": "packages/core/schematics/
|
|
3000
|
+
"id": "interface-Schema-d36032102ed30a7ada1e3d36bb9ca41b7234b855760cac9783a25818f7ffe2097e1ebd8e808b574ddec0760f827272f6cd561f45f3eb1578f87f39ee2633730a-20",
|
|
3001
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
2990
3002
|
"deprecated": false,
|
|
2991
3003
|
"deprecationMessage": "",
|
|
2992
3004
|
"type": "interface",
|
|
2993
|
-
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nconst
|
|
3005
|
+
"sourceCode": "import { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\ninterface Edit {\n start: number;\n end: number;\n replacement: string;\n}\n\nconst OLD_CLASS = 'EuiTooltipConfig';\nconst NEW_INTERFACE = 'EuiTooltipInterface';\n\nexport function migrateEuiTooltip(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n let fileCount = 0;\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const original = buffer.toString('utf-8');\n if (!original.includes(OLD_CLASS)) return;\n\n const result = migrateTypeScript(original, path, context);\n\n if (result !== original) {\n if (options.dryRun) {\n logDryRun(context, `Would migrate EuiTooltipConfig → EuiTooltipInterface in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n fileCount++;\n }\n });\n\n context.logger.info(`Migrated EuiTooltipConfig → EuiTooltipInterface in ${fileCount} file(s).`);\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction migrateTypeScript(source: string, filePath: string, context: SchematicContext): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n const edits: Edit[] = [];\n\n // Track if EuiTooltipInterface is already imported\n let hasInterfaceImport = false;\n let classImportDecl: ts.ImportDeclaration | null = null;\n let classImportModuleSpecifier: string | null = null;\n\n // First pass: analyze imports\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n for (const specifier of namedBindings.elements) {\n if (specifier.name.text === NEW_INTERFACE) {\n hasInterfaceImport = true;\n }\n if (specifier.name.text === OLD_CLASS) {\n classImportDecl = stmt;\n classImportModuleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n }\n }\n }\n\n // Second pass: handle import declarations\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) continue;\n const namedBindings = stmt.importClause?.namedBindings;\n if (!namedBindings || !ts.isNamedImports(namedBindings)) continue;\n\n const specifiers = namedBindings.elements;\n const classSpecifier = specifiers.find((s) => s.name.text === OLD_CLASS);\n if (!classSpecifier) continue;\n\n if (hasInterfaceImport) {\n // EuiTooltipInterface is already imported elsewhere → remove EuiTooltipConfig from this import\n removeImportSpecifier(namedBindings, classSpecifier, sourceFile, edits);\n } else {\n // Rename EuiTooltipConfig → EuiTooltipInterface in the import\n edits.push({\n start: classSpecifier.name.getStart(sourceFile),\n end: classSpecifier.name.getEnd(),\n replacement: NEW_INTERFACE,\n });\n hasInterfaceImport = true;\n }\n }\n\n // Third pass: replace `new EuiTooltipConfig(...)` → spread/cast to interface\n const visitNewExpressions = (node: ts.Node): void => {\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) {\n const args = node.arguments;\n if (args && args.length === 1) {\n const arg = args[0];\n // `new EuiTooltipConfig({ ... })` → `{ ... } as EuiTooltipInterface`\n // But if the argument is just a variable, we keep it: `varName as EuiTooltipInterface`\n const argText = source.slice(arg.getStart(sourceFile), arg.getEnd());\n\n if (ts.isObjectLiteralExpression(arg)) {\n // Inline object: `new EuiTooltipConfig({ x: 1 })` → `{ x: 1 }`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n } else {\n // Variable or expression: `new EuiTooltipConfig(opts)` → `opts`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: argText,\n });\n }\n } else if (!args || args.length === 0) {\n // `new EuiTooltipConfig()` → `{} as EuiTooltipInterface`\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: `{} as ${NEW_INTERFACE}`,\n });\n }\n return; // don't recurse into children we've already replaced\n }\n ts.forEachChild(node, visitNewExpressions);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitNewExpressions(stmt);\n }\n }\n\n // Fourth pass: rename all remaining identifier references (type annotations, etc.)\n const visitRefs = (node: ts.Node): void => {\n if (ts.isImportDeclaration(node)) return;\n // Skip nodes we already covered in new expressions\n if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === OLD_CLASS) return;\n\n if (ts.isIdentifier(node) && node.text === OLD_CLASS) {\n // Ensure this is not part of an import declaration\n if (!isPartOfImport(node)) {\n edits.push({\n start: node.getStart(sourceFile),\n end: node.getEnd(),\n replacement: NEW_INTERFACE,\n });\n }\n }\n ts.forEachChild(node, visitRefs);\n };\n\n for (const stmt of sourceFile.statements) {\n if (!ts.isImportDeclaration(stmt)) {\n visitRefs(stmt);\n }\n }\n\n return applyEdits(source, edits);\n}\n\nfunction isPartOfImport(node: ts.Node): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isImportDeclaration(current)) return true;\n current = current.parent;\n }\n return false;\n}\n\nfunction removeImportSpecifier(\n namedImports: ts.NamedImports,\n specifier: ts.ImportSpecifier,\n sourceFile: ts.SourceFile,\n edits: Edit[],\n): void {\n const elements = namedImports.elements;\n if (elements.length === 1) {\n // Remove the entire import declaration\n const importDecl = namedImports.parent.parent;\n let end = importDecl.getEnd();\n // Also remove trailing newline if present\n const fullText = sourceFile.getFullText();\n if (fullText[end] === '\\n') end++;\n edits.push({\n start: importDecl.getStart(sourceFile),\n end,\n replacement: '',\n });\n } else {\n // Remove just this specifier with surrounding comma/whitespace\n const idx = elements.indexOf(specifier);\n let start: number;\n let end: number;\n if (idx < elements.length - 1) {\n // Not the last → remove from this specifier start to next specifier start\n start = specifier.getStart(sourceFile);\n end = elements[idx + 1].getStart(sourceFile);\n } else {\n // Last element → remove from previous element end to this end\n start = elements[idx - 1].getEnd();\n end = specifier.getEnd();\n }\n edits.push({ start, end, replacement: '' });\n }\n}\n\nfunction applyEdits(source: string, edits: Edit[]): string {\n const unique = deduplicateEdits(edits);\n let result = source;\n for (const edit of unique.sort((a, b) => b.start - a.start)) {\n result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);\n }\n return result;\n}\n\nfunction deduplicateEdits(edits: Edit[]): Edit[] {\n const seen = new Map<string, Edit>();\n for (const edit of edits) {\n const key = `${edit.start}:${edit.end}`;\n seen.set(key, edit);\n }\n return Array.from(seen.values());\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n",
|
|
2994
3006
|
"displayName": "Schema",
|
|
2995
3007
|
"properties": [
|
|
2996
3008
|
{
|
|
@@ -3032,12 +3044,12 @@
|
|
|
3032
3044
|
},
|
|
3033
3045
|
{
|
|
3034
3046
|
"name": "Schema",
|
|
3035
|
-
"id": "interface-Schema-
|
|
3036
|
-
"file": "packages/core/schematics/
|
|
3047
|
+
"id": "interface-Schema-817c4b549cc3eab9fcf4936acab2e71182d90a4480369f5c003cbbda10792efa587ad99d65acf049f64e7030e32589e4fabc4a6d7a5621e4fe54725ef91dc9e8-21",
|
|
3048
|
+
"file": "packages/core/schematics/migrate-to-standalone/index.ts",
|
|
3037
3049
|
"deprecated": false,
|
|
3038
3050
|
"deprecationMessage": "",
|
|
3039
3051
|
"type": "interface",
|
|
3040
|
-
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode, TmplAstTemplate } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\nimport { SELECTOR_MAP, SelectorEntry, getClassNamesForArray } from './selector-map';\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n useClassArray?: boolean;\n}\n\ninterface ImportToAdd {\n /** The symbol to add to imports array (class name or array name for spread) */\n symbol: string;\n /** Whether this should be spread (...EUI_BUTTON) */\n isSpread: boolean;\n /** ES import path */\n importPath: string;\n}\n\nexport function addEuiImports(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const useClassArray = options.useClassArray ?? false;\n let filesUpdated = 0;\n\n // Index NgModules for standalone:false support\n const ngModuleIndex = buildNgModuleIndex(tree, tree.getDir(scanPath || '/'));\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const components = findComponentDecorators(sourceFile);\n if (components.length === 0) return;\n\n let modified = false;\n\n for (const { decorator, className: componentClassName, isNonStandalone } of components) {\n const templateHtml = getTemplateContent(tree, path, decorator, source);\n if (!templateHtml) continue;\n\n const matched = matchSelectorsInTemplate(templateHtml);\n if (matched.length === 0) continue;\n\n const importsToAdd = resolveImports(matched, useClassArray);\n if (importsToAdd.length === 0) continue;\n\n if (isNonStandalone) {\n // Find the NgModule that declares this component and add imports there\n const moduleInfo = findDeclaringModule(ngModuleIndex, componentClassName);\n if (!moduleInfo) {\n context.logger.warn(`⚠ Could not find declaring NgModule for ${componentClassName} in ${path}`);\n continue;\n }\n const moduleBuffer = tree.read(moduleInfo.path);\n if (!moduleBuffer) continue;\n const moduleSource = moduleBuffer.toString('utf-8');\n const result = addImportsToFile(moduleSource, moduleInfo.path, moduleInfo.decoratorPos, importsToAdd, useClassArray);\n if (result !== moduleSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to NgModule in ${moduleInfo.path} for component ${componentClassName}`);\n } else {\n tree.overwrite(moduleInfo.path, result);\n }\n modified = true;\n }\n } else {\n // Standalone component — add imports directly\n const currentSource = tree.read(path)!.toString('utf-8');\n const result = addImportsToFile(currentSource, path, decorator.getStart(), importsToAdd, useClassArray);\n if (result !== currentSource) {\n if (options.dryRun) {\n logDryRun(context, `Would add EUI imports to ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n modified = true;\n }\n }\n }\n\n if (modified) filesUpdated++;\n });\n\n context.logger.info(`add-eui-imports: ${filesUpdated} file(s) updated.`);\n if (options.dryRun) logDryRunNote(context);\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.html') && !file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\n// --- Selector Matching ---\n\nfunction matchSelectorsInTemplate(html: string): SelectorEntry[] {\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n if (parsed.errors?.length) return [];\n\n const matched: SelectorEntry[] = [];\n visitTemplateNodes(parsed.nodes, matched);\n return matched;\n}\n\nfunction visitTemplateNodes(nodes: TmplAstNode[], matched: SelectorEntry[]): void {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n matchElement(node, matched);\n visitTemplateNodes(node.children, matched);\n } else if (node instanceof TmplAstTemplate) {\n visitTemplateNodes(node.children, matched);\n }\n }\n}\n\nfunction matchElement(element: TmplAstElement, matched: SelectorEntry[]): void {\n const tagName = element.name;\n const attrNames = new Set([\n ...element.attributes.map(a => a.name),\n ...element.inputs.map(i => i.name),\n ]);\n\n for (const entry of SELECTOR_MAP) {\n if (entry.element && entry.element !== tagName) continue;\n if (!entry.element && entry.attributes.length === 0) continue;\n if (!entry.attributes.every(attr => attrNames.has(attr))) continue;\n // If no element specified, at least one attribute must match on this element\n if (!entry.element && entry.attributes.length > 0 && !entry.attributes.some(attr => attrNames.has(attr))) continue;\n matched.push(entry);\n }\n}\n\n// --- Import Resolution ---\n\nfunction resolveImports(matched: SelectorEntry[], useClassArray: boolean): ImportToAdd[] {\n const seen = new Set<string>();\n const result: ImportToAdd[] = [];\n\n for (const entry of matched) {\n if (useClassArray && entry.classArray) {\n if (seen.has(entry.classArray)) continue;\n seen.add(entry.classArray);\n result.push({ symbol: entry.classArray, isSpread: true, importPath: entry.importPath });\n } else {\n if (seen.has(entry.className)) continue;\n seen.add(entry.className);\n result.push({ symbol: entry.className, isSpread: false, importPath: entry.importPath });\n }\n }\n\n return result;\n}\n\n// --- Template Extraction ---\n\nfunction getTemplateContent(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): string | null {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return null;\n const metadata = call.arguments[0];\n\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;\n if (prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return init.text;\n }\n }\n if (prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) return templateBuffer.toString('utf-8');\n }\n }\n }\n return null;\n}\n\n// --- Component Decorator Detection ---\n\ninterface ComponentInfo {\n decorator: ts.Decorator;\n className: string;\n isNonStandalone: boolean;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ComponentInfo[] {\n const results: ComponentInfo[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node) && node.name) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n const isNonStandalone = hasStandaloneFalse(dec);\n results.push({ decorator: dec, className: node.name.text, isNonStandalone });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return results;\n}\n\nfunction hasStandaloneFalse(decorator: ts.Decorator): boolean {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return false;\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'standalone') {\n return prop.initializer.kind === ts.SyntaxKind.FalseKeyword;\n }\n }\n return false;\n}\n\n// --- NgModule Index ---\n\ninterface NgModuleInfo {\n path: string;\n declarations: string[];\n decoratorPos: number;\n}\n\nfunction buildNgModuleIndex(tree: Tree, dir: DirEntry): NgModuleInfo[] {\n const modules: NgModuleInfo[] = [];\n\n visitDir(dir, (path) => {\n if (!path.endsWith('.ts') || path.endsWith('.spec.ts')) return;\n\n const buffer = tree.read(path);\n if (!buffer) return;\n const source = buffer.toString('utf-8');\n if (!source.includes('NgModule')) return;\n\n const sf = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'NgModule') {\n const declarations = extractArrayProperty(dec, 'declarations', source);\n modules.push({ path, declarations, decoratorPos: dec.getStart() });\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n });\n\n return modules;\n}\n\nfunction extractArrayProperty(decorator: ts.Decorator, propName: string, source: string): string[] {\n const call = decorator.expression as ts.CallExpression;\n if (!call.arguments[0] || !ts.isObjectLiteralExpression(call.arguments[0])) return [];\n for (const prop of call.arguments[0].properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === propName) {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer.elements\n .filter(ts.isIdentifier)\n .map(id => id.text);\n }\n }\n }\n return [];\n}\n\nfunction findDeclaringModule(modules: NgModuleInfo[], componentClassName: string): NgModuleInfo | undefined {\n return modules.find(m => m.declarations.includes(componentClassName));\n}\n\n// --- Import Addition ---\n\nfunction addImportsToFile(source: string, filePath: string, decoratorStartHint: number, imports: ImportToAdd[], useClassArray: boolean): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Find the imports array in the decorator closest to decoratorStartHint\n const importsArrayInfo = findDecoratorImportsArray(sf, source, decoratorStartHint);\n if (!importsArrayInfo) return source;\n\n const { arrayNode, decoratorType } = importsArrayInfo;\n\n // Determine what's already in the imports array\n const existingSymbols = new Set<string>();\n const existingSpreads = new Set<string>();\n for (const el of arrayNode.elements) {\n if (ts.isSpreadElement(el) && ts.isIdentifier(el.expression)) {\n existingSpreads.add(el.expression.text);\n } else if (ts.isIdentifier(el)) {\n existingSymbols.add(el.text);\n }\n }\n\n // Filter out already-present imports and compute what to add/remove\n const toAdd: ImportToAdd[] = [];\n const toRemoveFromArray: string[] = []; // individual class names to consolidate\n\n for (const imp of imports) {\n if (imp.isSpread) {\n if (existingSpreads.has(imp.symbol)) continue; // Already has ...EUI_X\n toAdd.push(imp);\n // Consolidate: remove individual class names covered by this array\n if (useClassArray) {\n const coveredClasses = getClassNamesForArray(imp.symbol);\n for (const cls of coveredClasses) {\n if (existingSymbols.has(cls)) toRemoveFromArray.push(cls);\n }\n }\n } else {\n if (existingSymbols.has(imp.symbol)) continue;\n // Also skip if a spread already covers this class\n const coveringArray = imports.find(i => i.isSpread && getClassNamesForArray(i.symbol).includes(imp.symbol));\n if (coveringArray && (existingSpreads.has(coveringArray.symbol) || toAdd.some(a => a.symbol === coveringArray.symbol))) continue;\n toAdd.push(imp);\n }\n }\n\n if (toAdd.length === 0 && toRemoveFromArray.length === 0) return source;\n\n // Build new array content\n let result = source;\n result = updateDecoratorImportsArray(result, filePath, arrayNode, toAdd, toRemoveFromArray);\n\n // Add ES imports\n result = addEsImports(result, filePath, toAdd);\n\n // Remove consolidated class names from ES imports\n if (toRemoveFromArray.length > 0) {\n result = removeFromEsImports(result, filePath, toRemoveFromArray);\n }\n\n return result;\n}\n\ninterface ImportsArrayInfo {\n arrayNode: ts.ArrayLiteralExpression;\n decoratorType: 'Component' | 'NgModule';\n}\n\nfunction findDecoratorImportsArray(sf: ts.SourceFile, source: string, decoratorStartHint: number): ImportsArrayInfo | null {\n let found: ImportsArrayInfo | null = null;\n\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression)) continue;\n if (!ts.isIdentifier(dec.expression.expression)) continue;\n const decName = dec.expression.expression.text;\n if (decName !== 'Component' && decName !== 'NgModule') continue;\n if (Math.abs(dec.getStart() - decoratorStartHint) > 5) continue; // Match by position\n\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n found = { arrayNode: prop.initializer, decoratorType: decName as 'Component' | 'NgModule' };\n return;\n }\n }\n }\n\n // No imports array found — create one\n if (!found && decName === 'Component') {\n // We need to add `imports: []` to the decorator\n // Insert after the last property\n const lastProp = metadata.properties[metadata.properties.length - 1];\n if (lastProp) {\n const insertPos = lastProp.getEnd();\n const indent = detectIndent(source, metadata.getStart());\n const insertion = `,\\n${indent} imports: []`;\n const newSource = source.slice(0, insertPos) + insertion + source.slice(insertPos);\n // Re-parse to get the array node\n const newSf = ts.createSourceFile('', newSource, ts.ScriptTarget.Latest, true);\n const newArray = findImportsArrayInSource(newSf);\n if (newArray) {\n // We can't return a node from a different source file in the general case.\n // Instead, we'll handle the \"no imports array\" case by adding it inline.\n found = null; // Will be handled separately\n }\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction findImportsArrayInSource(sf: ts.SourceFile): ts.ArrayLiteralExpression | null {\n let found: ts.ArrayLiteralExpression | null = null;\n const visit = (node: ts.Node): void => {\n if (found) return;\n if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && node.name.text === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {\n found = node.initializer;\n }\n ts.forEachChild(node, visit);\n };\n visit(sf);\n return found;\n}\n\nfunction updateDecoratorImportsArray(source: string, filePath: string, arrayNode: ts.ArrayLiteralExpression, toAdd: ImportToAdd[], toRemove: string[]): string {\n const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n\n // Rebuild the array content\n const existingElements: string[] = [];\n for (const el of arrayNode.elements) {\n const text = source.slice(el.getStart(sf), el.getEnd()).trim();\n // Check if this element should be removed (consolidation)\n if (ts.isIdentifier(el) && toRemove.includes(el.text)) continue;\n existingElements.push(text);\n }\n\n // Add new entries\n for (const imp of toAdd) {\n const entry = imp.isSpread ? `...${imp.symbol}` : imp.symbol;\n if (!existingElements.includes(entry)) {\n existingElements.push(entry);\n }\n }\n\n // Determine formatting\n const arrayStart = arrayNode.getStart(sf);\n const arrayEnd = arrayNode.getEnd();\n const originalText = source.slice(arrayStart, arrayEnd);\n const isMultiline = originalText.includes('\\n');\n\n let newArrayText: string;\n if (isMultiline || existingElements.length > 3) {\n const indent = detectIndent(source, arrayStart);\n const itemIndent = indent + ' ';\n newArrayText = `[\\n${existingElements.map(e => `${itemIndent}${e},`).join('\\n')}\\n${indent}]`;\n } else {\n newArrayText = `[${existingElements.join(', ')}]`;\n }\n\n return source.slice(0, arrayStart) + newArrayText + source.slice(arrayEnd);\n}\n\nfunction addEsImports(source: string, filePath: string, imports: ImportToAdd[]): string {\n let result = source;\n\n // Group by import path\n const byPath = new Map<string, string[]>();\n for (const imp of imports) {\n const existing = byPath.get(imp.importPath) || [];\n existing.push(imp.symbol);\n byPath.set(imp.importPath, existing);\n }\n\n for (const [importPath, symbols] of byPath) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n // Check if there's already an import from this path\n const existingImport = sf.statements.find(\n (s): s is ts.ImportDeclaration =>\n ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n );\n\n if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {\n // Extend existing import\n const namedBindings = existingImport.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const newNames = symbols.filter(s => !existingNames.includes(s));\n if (newNames.length === 0) continue;\n\n const allNames = [...existingNames, ...newNames].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n // Add new import statement\n const sortedSymbols = [...symbols].sort();\n const newImport = `import { ${sortedSymbols.join(', ')} } from '${importPath}';\\n`;\n\n // Insert after the last existing import\n const lastImport = [...sf.statements].reverse().find(ts.isImportDeclaration);\n if (lastImport) {\n const pos = lastImport.getEnd();\n result = result.slice(0, pos) + '\\n' + newImport.trimEnd() + result.slice(pos);\n } else {\n result = newImport + result;\n }\n }\n }\n\n return result;\n}\n\nfunction detectIndent(source: string, pos: number): string {\n const lineStart = source.lastIndexOf('\\n', pos - 1) + 1;\n const match = source.slice(lineStart, pos).match(/^(\\s*)/);\n return match ? match[1] : '';\n}\n\nfunction removeFromEsImports(source: string, filePath: string, symbolsToRemove: string[]): string {\n let result = source;\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const existingNames = namedBindings.elements.map(el => el.name.text);\n const remaining = existingNames.filter(n => !symbolsToRemove.includes(n));\n\n if (remaining.length === existingNames.length) continue; // Nothing to remove from this import\n\n if (remaining.length === 0) {\n // Remove the entire import statement\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n } else {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n }\n break; // Only process the first matching import for the consolidated symbols\n }\n\n return result;\n}\n",
|
|
3052
|
+
"sourceCode": "import { parseTemplate, TmplAstElement, TmplAstNode } from '@angular/compiler';\nimport { DirEntry, Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport * as ts from 'typescript';\nimport { logDryRun, logDryRunNote } from '../utils/dry-run';\n\ninterface ModuleMapping {\n importPath: string;\n selectors: Record<string, string>;\n}\n\nconst MODULE_MAPPINGS: Record<string, ModuleMapping> = {\n EuiAccordionModule: {\n importPath: '@eui/components/eui-accordion',\n selectors: {\n 'eui-accordion': 'EuiAccordionComponent',\n 'eui-accordion-item': 'EuiAccordionItemComponent',\n euiAccordionItemHeader: 'EuiAccordionItemHeaderDirective',\n },\n },\n EuiAlertModule: {\n importPath: '@eui/components/eui-alert',\n selectors: {\n 'eui-alert': 'EuiAlertComponent',\n euiAlert: 'EuiAlertComponent',\n 'eui-alert-title': 'EuiAlertTitleComponent',\n },\n },\n EuiAutocompleteModule: {\n importPath: '@eui/components/eui-autocomplete',\n selectors: {\n 'eui-autocomplete': 'EuiAutocompleteComponent',\n euiAutocomplete: 'EuiAutocompleteComponent',\n 'eui-autocomplete-option': 'EuiAutocompleteOptionComponent',\n 'eui-autocomplete-option-group': 'EuiAutocompleteOptionGroupComponent',\n 'eui-autocomplete-panel': 'EuiAutocompletePanelComponent',\n },\n },\n EuiAvatarModule: {\n importPath: '@eui/components/eui-avatar',\n selectors: {\n 'eui-avatar': 'EuiAvatarComponent',\n euiAvatar: 'EuiAvatarComponent',\n },\n },\n EuiBadgeModule: {\n importPath: '@eui/components/eui-badge',\n selectors: {\n 'eui-badge': 'EuiBadgeComponent',\n euiBadge: 'EuiBadgeComponent',\n },\n },\n EuiBlockContentModule: {\n importPath: '@eui/components/eui-block-content',\n selectors: {\n 'eui-block-content': 'EuiBlockContentComponent',\n },\n },\n EuiBreadcrumbModule: {\n importPath: '@eui/components/eui-breadcrumb',\n selectors: {\n 'eui-breadcrumb': 'EuiBreadcrumbComponent',\n },\n },\n EuiButtonModule: {\n importPath: '@eui/components/eui-button',\n selectors: {\n euiButton: 'EuiButtonComponent',\n },\n },\n EuiButtonGroupModule: {\n importPath: '@eui/components/eui-button-group',\n selectors: {\n 'eui-button-group': 'EuiButtonGroupComponent',\n },\n },\n EuiCardModule: {\n importPath: '@eui/components/eui-card',\n selectors: {\n 'eui-card': 'EuiCardComponent',\n 'eui-card-header': 'EuiCardHeaderComponent',\n 'eui-card-header-title': 'EuiCardHeaderTitleComponent',\n 'eui-card-content': 'EuiCardContentComponent',\n 'eui-card-footer': 'EuiCardFooterComponent',\n 'eui-card-media': 'EuiCardMediaComponent',\n },\n },\n EuiChipModule: {\n importPath: '@eui/components/eui-chip',\n selectors: {\n 'eui-chip': 'EuiChipComponent',\n euiChip: 'EuiChipComponent',\n },\n },\n EuiChipListModule: {\n importPath: '@eui/components/eui-chip-list',\n selectors: {\n 'eui-chip-list': 'EuiChipListComponent',\n },\n },\n EuiChipGroupModule: {\n importPath: '@eui/components/eui-chip-group',\n selectors: {\n 'eui-chip-group': 'EuiChipGroupComponent',\n },\n },\n EuiDashboardCardModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDashboardButtonModule: {\n importPath: '@eui/components/eui-dashboard-card',\n selectors: {\n 'eui-dashboard-card': 'EuiDashboardCardComponent',\n 'eui-dashboard-card-content': 'EuiDashboardCardContentComponent',\n 'eui-dashboard-card-content-header': 'EuiDashboardCardContentHeaderComponent',\n 'eui-dashboard-card-content-body': 'EuiDashboardCardContentBodyComponent',\n 'eui-dashboard-card-content-footer': 'EuiDashboardCardContentFooterComponent',\n },\n },\n EuiDatepickerModule: {\n importPath: '@eui/components/eui-datepicker',\n selectors: {\n 'eui-datepicker': 'EuiDatepickerComponent',\n },\n },\n EuiDateRangeSelectorModule: {\n importPath: '@eui/components/eui-date-range-selector',\n selectors: {\n 'eui-date-range-selector': 'EuiDateRangeSelectorComponent',\n },\n },\n EuiDialogModule: {\n importPath: '@eui/components/eui-dialog',\n selectors: {\n 'eui-dialog': 'EuiDialogComponent',\n 'eui-dialog-header': 'EuiDialogHeaderDirective',\n 'eui-dialog-footer': 'EuiDialogFooterDirective',\n 'eui-dialog-container': 'EuiDialogContainerComponent',\n },\n },\n EuiDisableContentModule: {\n importPath: '@eui/components/eui-disable-content',\n selectors: {\n 'eui-disable-content': 'EuiDisableContentComponent',\n },\n },\n EuiDiscussionThreadModule: {\n importPath: '@eui/components/eui-discussion-thread',\n selectors: {\n 'eui-discussion-thread': 'EuiDiscussionThreadComponent',\n 'eui-discussion-thread-item': 'EuiDiscussionThreadItemComponent',\n },\n },\n EuiDropdownModule: {\n importPath: '@eui/components/eui-dropdown',\n selectors: {\n 'eui-dropdown': 'EuiDropdownComponent',\n },\n },\n EuiFeedbackMessageModule: {\n importPath: '@eui/components/eui-feedback-message',\n selectors: {\n 'eui-feedback-message': 'EuiFeedbackMessageComponent',\n },\n },\n EuiFieldsetModule: {\n importPath: '@eui/components/eui-fieldset',\n selectors: {\n 'eui-fieldset': 'EuiFieldsetComponent',\n euiFieldsetLabelRightContent: 'EuiFieldsetLabelRightContentTagDirective',\n euiFieldsetLabelExtraContent: 'EuiFieldsetLabelExtraContentTagDirective',\n },\n },\n EuiFileUploadModule: {\n importPath: '@eui/components/eui-file-upload',\n selectors: {\n 'eui-file-upload': 'EuiFileUploadComponent',\n },\n },\n EuiGrowlModule: {\n importPath: '@eui/components/eui-growl',\n selectors: {\n 'eui-growl': 'EuiGrowlComponent',\n },\n },\n EuiIconModule: {\n importPath: '@eui/components/eui-icon',\n selectors: {\n 'eui-icon-svg': 'EuiIconSvgComponent',\n euiIconSvg: 'EuiIconSvgComponent',\n },\n },\n EuiIconButtonModule: {\n importPath: '@eui/components/eui-icon-button',\n selectors: {\n 'eui-icon-button': 'EuiIconButtonComponent',\n },\n },\n EuiIconToggleModule: {\n importPath: '@eui/components/eui-icon-toggle',\n selectors: {\n 'eui-icon-toggle': 'EuiIconToggleComponent',\n },\n },\n EuiInputCheckboxModule: {\n importPath: '@eui/components/eui-input-checkbox',\n selectors: {\n euiInputCheckBox: 'EuiInputCheckboxComponent',\n },\n },\n EuiInputGroupModule: {\n importPath: '@eui/components/eui-input-group',\n selectors: {\n euiInputGroup: 'EuiInputGroupComponent',\n 'eui-input-group-addon': 'EuiInputGroupAddOnComponent',\n euiInputGroupAddOn: 'EuiInputGroupAddOnComponent',\n 'eui-input-group-addon-item': 'EuiInputGroupAddOnItemComponent',\n euiInputGroupAddOnItem: 'EuiInputGroupAddOnItemComponent',\n },\n },\n EuiInputNumberModule: {\n importPath: '@eui/components/eui-input-number',\n selectors: {\n euiInputNumber: 'EuiInputNumberComponent',\n },\n },\n EuiInputRadioModule: {\n importPath: '@eui/components/eui-input-radio',\n selectors: {\n euiInputRadio: 'EuiInputRadioComponent',\n },\n },\n EuiInputTextModule: {\n importPath: '@eui/components/eui-input-text',\n selectors: {\n euiInputText: 'EuiInputTextComponent',\n },\n },\n EuiLabelModule: {\n importPath: '@eui/components/eui-label',\n selectors: {\n 'eui-label': 'EuiLabelComponent',\n euiLabel: 'EuiLabelComponent',\n },\n },\n EuiListModule: {\n importPath: '@eui/components/eui-list',\n selectors: {\n 'eui-list': 'EuiListComponent',\n euiList: 'EuiListComponent',\n 'eui-list-item': 'EuiListItemComponent',\n euiListItem: 'EuiListItemComponent',\n },\n },\n EuiMenuModule: {\n importPath: '@eui/components/eui-menu',\n selectors: {\n 'eui-menu': 'EuiMenuComponent',\n 'eui-menu-item': 'EuiMenuItemComponent',\n },\n },\n EuiMessageBoxModule: {\n importPath: '@eui/components/eui-message-box',\n selectors: {\n 'eui-message-box': 'EuiMessageBoxComponent',\n 'eui-message-box-footer': 'EuiMessageBoxFooterDirective',\n },\n },\n EuiOverlayModule: {\n importPath: '@eui/components/eui-overlay',\n selectors: {\n 'eui-overlay': 'EuiOverlayComponent',\n },\n },\n EuiPageModule: {\n importPath: '@eui/components/eui-page',\n selectors: {\n 'eui-page': 'EuiPageComponent',\n },\n },\n EuiPaginatorModule: {\n importPath: '@eui/components/eui-paginator',\n selectors: {\n 'eui-paginator': 'EuiPaginatorComponent',\n },\n },\n EuiPopoverModule: {\n importPath: '@eui/components/eui-popover',\n selectors: {\n 'eui-popover': 'EuiPopoverComponent',\n },\n },\n EuiProgressBarModule: {\n importPath: '@eui/components/eui-progress-bar',\n selectors: {\n 'eui-progress-bar': 'EuiProgressBarComponent',\n },\n },\n EuiProgressCircleModule: {\n importPath: '@eui/components/eui-progress-circle',\n selectors: {\n 'eui-progress-circle': 'EuiProgressCircleComponent',\n },\n },\n EuiSelectModule: {\n importPath: '@eui/components/eui-select',\n selectors: {\n euiSelect: 'EuiSelectComponent',\n },\n },\n EuiSidebarMenuModule: {\n importPath: '@eui/components/eui-sidebar-menu',\n selectors: {\n 'eui-sidebar-menu': 'EuiSidebarMenuComponent',\n },\n },\n EuiSkeletonModule: {\n importPath: '@eui/components/eui-skeleton',\n selectors: {\n 'eui-skeleton': 'EuiSkeletonComponent',\n },\n },\n EuiSlideToggleModule: {\n importPath: '@eui/components/eui-slide-toggle',\n selectors: {\n 'eui-slide-toggle': 'EuiSlideToggleComponent',\n },\n },\n EuiTableModule: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTableV2Module: {\n importPath: '@eui/components/eui-table',\n selectors: {\n 'eui-table': 'EuiTableComponent',\n euiTable: 'EuiTableComponent',\n 'eui-table-filter': 'EuiTableFilterComponent',\n isSortable: 'EuiTableSortableColComponent',\n isStickyCol: 'EuiTableStickyColDirective',\n isHeaderSelectable: 'EuiTableSelectableHeaderComponent',\n isDataSelectable: 'EuiTableSelectableRowComponent',\n isExpandableRow: 'EuiTableExpandableRowDirective',\n },\n },\n EuiTabsModule: {\n importPath: '@eui/components/eui-tabs',\n selectors: {\n 'eui-tabs': 'EuiTabsComponent',\n 'eui-tab': 'EuiTabComponent',\n 'eui-tab-header': 'EuiTabHeaderComponent',\n 'eui-tab-body': 'EuiTabBodyComponent',\n },\n },\n EuiTextAreaModule: {\n importPath: '@eui/components/eui-textarea',\n selectors: {\n euiTextArea: 'EuiTextareaComponent',\n },\n },\n EuiTimelineModule: {\n importPath: '@eui/components/eui-timeline',\n selectors: {\n 'eui-timeline': 'EuiTimelineComponent',\n 'eui-timeline-item': 'EuiTimelineItemComponent',\n },\n },\n EuiTimepickerModule: {\n importPath: '@eui/components/eui-timepicker',\n selectors: {\n 'eui-timepicker': 'EuiTimepickerComponent',\n },\n },\n EuiTreeModule: {\n importPath: '@eui/components/eui-tree',\n selectors: {\n 'eui-tree': 'EuiTreeComponent',\n },\n },\n EuiTreeListModule: {\n importPath: '@eui/components/eui-tree-list',\n selectors: {\n 'eui-tree-list': 'EuiTreeListComponent',\n 'eui-tree-list-item': 'EuiTreeListItemComponent',\n },\n },\n EuiUserProfileModule: {\n importPath: '@eui/components/eui-user-profile',\n selectors: {\n 'eui-user-profile': 'EuiUserProfileComponent',\n },\n },\n EuiWizardModule: {\n importPath: '@eui/components/eui-wizard',\n selectors: {\n 'eui-wizard': 'EuiWizardComponent',\n 'eui-wizard-step': 'EuiWizardStepComponent',\n },\n },\n EuiTooltipDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTooltip: 'EuiTooltipDirective',\n },\n },\n EuiTemplateDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiTemplate: 'EuiTemplateDirective',\n },\n },\n EuiResizableDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiResizable: 'EuiResizableDirective',\n 'eui-resizable': 'EuiResizableComponent',\n },\n },\n EuiMaxLengthDirectiveModule: {\n importPath: '@eui/components/directives',\n selectors: {\n euiEditorMaxlength: 'EuiMaxLengthDirective',\n },\n },\n EuiTruncatePipeModule: {\n importPath: '@eui/components/pipes',\n selectors: {\n euiTruncate: 'EuiTruncatePipe',\n },\n },\n EuiLayoutModule: {\n importPath: '@eui/components/layout',\n selectors: {\n 'eui-app': 'EuiAppComponent',\n 'eui-header': 'EuiHeaderComponent',\n 'eui-footer': 'EuiFooterComponent',\n 'eui-toolbar': 'EuiToolbarComponent',\n 'eui-sidebar-toggle': 'EuiSidebarToggleComponent',\n },\n },\n};\n\ninterface Schema {\n path?: string;\n dryRun?: boolean;\n}\n\nexport function migrateToStandalone(options: Schema = {}): Rule {\n return (tree: Tree, context: SchematicContext) => {\n const scanPath = options.path ? '/' + options.path.replace(/^\\.?\\//, '').replace(/\\/$/, '') : '';\n const allModuleNames = Object.keys(MODULE_MAPPINGS);\n\n visitDir(tree.getDir(scanPath || '/'), (path) => {\n const buffer = tree.read(path);\n if (!buffer) return;\n\n const source = buffer.toString('utf-8');\n if (!allModuleNames.some((m) => source.includes(m))) return;\n\n const sourceFile = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true);\n const componentDecorators = findComponentDecorators(sourceFile);\n\n for (const decorator of componentDecorators) {\n const importsNode = findImportsArrayNode(decorator);\n if (!importsNode) continue;\n\n const currentSource = tree.read(path)!.toString('utf-8');\n const modulesInArray = allModuleNames.filter((m) => hasModuleInArray(importsNode, m, source));\n if (modulesInArray.length === 0) continue;\n\n const templateSelectors = getTemplateSelectors(tree, path, decorator, source);\n const replacements = buildReplacements(modulesInArray, templateSelectors);\n if (replacements.size === 0) continue;\n\n const result = applyReplacements(currentSource, path, replacements);\n if (options.dryRun) {\n logDryRun(context, `Would replace module imports with standalone imports in ${path}`);\n } else {\n tree.overwrite(path, result);\n }\n }\n });\n\n context.logger.info('Migration to standalone imports complete.');\n if (options.dryRun) {\n logDryRunNote(context);\n }\n return tree;\n };\n}\n\nfunction visitDir(dir: DirEntry, callback: (path: string) => void): void {\n for (const file of dir.subfiles) {\n if (file.endsWith('.d.ts')) continue;\n if (!file.endsWith('.ts')) continue;\n callback(`${dir.path}/${file}`);\n }\n for (const sub of dir.subdirs) {\n if (sub === 'node_modules' || sub === 'dist') continue;\n visitDir(dir.dir(sub), callback);\n }\n}\n\nfunction buildReplacements(moduleNames: string[], templateSelectors: Set<string>): Map<string, { components: string[]; importPath: string }> {\n const map = new Map<string, { components: string[]; importPath: string }>();\n\n for (const moduleName of moduleNames) {\n const mapping = MODULE_MAPPINGS[moduleName];\n const components: string[] = [];\n\n for (const [selector, component] of Object.entries(mapping.selectors)) {\n if (templateSelectors.has(selector)) {\n components.push(component);\n }\n }\n\n if (components.length > 0) {\n map.set(moduleName, { components: [...new Set(components)].sort(), importPath: mapping.importPath });\n }\n }\n\n return map;\n}\n\nfunction findComponentDecorators(sourceFile: ts.SourceFile): ts.Decorator[] {\n const decorators: ts.Decorator[] = [];\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (decs) {\n for (const dec of decs) {\n if (ts.isCallExpression(dec.expression) && ts.isIdentifier(dec.expression.expression) && dec.expression.expression.text === 'Component') {\n decorators.push(dec);\n }\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return decorators;\n}\n\nfunction findImportsArrayNode(decorator: ts.Decorator): ts.ArrayLiteralExpression | undefined {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) return undefined;\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'imports') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n return prop.initializer;\n }\n }\n }\n return undefined;\n}\n\nfunction hasModuleInArray(array: ts.ArrayLiteralExpression, moduleName: string, source: string): boolean {\n return array.elements.some((el) => source.slice(el.getStart(), el.getEnd()).trim() === moduleName);\n}\n\nfunction getTemplateSelectors(tree: Tree, tsPath: string, decorator: ts.Decorator, source: string): Set<string> {\n const call = decorator.expression as ts.CallExpression;\n const metadata = call.arguments[0] as ts.ObjectLiteralExpression;\n const allSelectors = Object.values(MODULE_MAPPINGS).flatMap((m) => Object.keys(m.selectors));\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'template') {\n const init = prop.initializer;\n if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {\n return findSelectorsInTemplate(init.text, allSelectors);\n }\n }\n }\n\n for (const prop of metadata.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {\n if (ts.isStringLiteral(prop.initializer)) {\n const dir = tsPath.substring(0, tsPath.lastIndexOf('/'));\n const templateBuffer = tree.read(`${dir}/${prop.initializer.text}`);\n if (templateBuffer) {\n return findSelectorsInTemplate(templateBuffer.toString('utf-8'), allSelectors);\n }\n }\n }\n }\n\n return new Set();\n}\n\nfunction findSelectorsInTemplate(html: string, knownSelectors: string[]): Set<string> {\n const selectors = new Set<string>();\n const parsed = parseTemplate(html, '', { preserveWhitespaces: true });\n\n const visit = (nodes: TmplAstNode[]): void => {\n for (const node of nodes) {\n if (node instanceof TmplAstElement) {\n if (knownSelectors.includes(node.name)) selectors.add(node.name);\n for (const attr of node.attributes) {\n if (knownSelectors.includes(attr.name)) selectors.add(attr.name);\n }\n visit(node.children);\n }\n }\n };\n visit(parsed.nodes);\n return selectors;\n}\n\nfunction applyReplacements(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);\n let result = replaceInImportsArray(source, sourceFile, replacements);\n result = updateEsImports(result, filePath, replacements);\n return result;\n}\n\nfunction replaceInImportsArray(source: string, sourceFile: ts.SourceFile, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n const visit = (node: ts.Node): void => {\n if (ts.isClassDeclaration(node)) {\n const decs = ts.getDecorators(node);\n if (!decs) return;\n for (const dec of decs) {\n if (!ts.isCallExpression(dec.expression) || !ts.isIdentifier(dec.expression.expression) || dec.expression.expression.text !== 'Component') continue;\n const metadata = dec.expression.arguments[0];\n if (!ts.isObjectLiteralExpression(metadata)) continue;\n for (const prop of metadata.properties) {\n if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || prop.name.text !== 'imports') continue;\n if (!ts.isArrayLiteralExpression(prop.initializer)) continue;\n const newElements = prop.initializer.elements\n .map((el) => {\n const text = result.slice(el.getStart(sourceFile), el.getEnd()).trim();\n const replacement = replacements.get(text);\n return replacement ? replacement.components.join(', ') : text;\n })\n .join(', ');\n result = result.slice(0, prop.initializer.getStart(sourceFile) + 1) + newElements + result.slice(prop.initializer.getEnd() - 1);\n }\n }\n }\n ts.forEachChild(node, visit);\n };\n visit(sourceFile);\n return result;\n}\n\nfunction updateEsImports(source: string, filePath: string, replacements: Map<string, { components: string[]; importPath: string }>): string {\n let result = source;\n\n for (const [moduleName, { components, importPath }] of replacements) {\n const sf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n\n for (const stmt of sf.statements) {\n if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings || !ts.isNamedImports(stmt.importClause.namedBindings)) continue;\n const namedBindings = stmt.importClause.namedBindings;\n const importNames = namedBindings.elements.map((el) => el.name.text);\n if (!importNames.includes(moduleName)) continue;\n\n const moduleSpecifier = (stmt.moduleSpecifier as ts.StringLiteral).text;\n const remaining = importNames.filter((n) => n !== moduleName);\n const toAdd = components.filter((c) => !importNames.includes(c));\n\n if (moduleSpecifier === importPath) {\n const newNames = [...remaining, ...toAdd].sort();\n const newClause = `{ ${newNames.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n if (remaining.length > 0) {\n const newClause = `{ ${remaining.join(', ')} }`;\n result = result.slice(0, namedBindings.getStart(sf)) + newClause + result.slice(namedBindings.getEnd());\n } else {\n result = result.slice(0, stmt.getStart(sf)) + result.slice(stmt.getEnd()).replace(/^\\r?\\n/, '');\n }\n\n if (toAdd.length > 0) {\n const updatedSf = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true);\n const existing = updatedSf.statements.find(\n (s) => ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier) && s.moduleSpecifier.text === importPath,\n ) as ts.ImportDeclaration | undefined;\n\n if (existing?.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {\n const existingNames = existing.importClause.namedBindings.elements.map((el) => el.name.text);\n const allNames = [...new Set([...existingNames, ...toAdd])].sort();\n const newClause = `{ ${allNames.join(', ')} }`;\n result = result.slice(0, existing.importClause.namedBindings.getStart(updatedSf)) + newClause + result.slice(existing.importClause.namedBindings.getEnd());\n } else {\n const newImport = `import { ${toAdd.join(', ')} } from '${importPath}';\\n`;\n result = newImport + result;\n }\n }\n }\n break;\n }\n }\n\n return result;\n}\n",
|
|
3041
3053
|
"displayName": "Schema",
|
|
3042
3054
|
"properties": [
|
|
3043
3055
|
{
|
|
@@ -3049,7 +3061,7 @@
|
|
|
3049
3061
|
"indexKey": "",
|
|
3050
3062
|
"optional": true,
|
|
3051
3063
|
"description": "",
|
|
3052
|
-
"line":
|
|
3064
|
+
"line": 460,
|
|
3053
3065
|
"rawdescription": "\n"
|
|
3054
3066
|
},
|
|
3055
3067
|
{
|
|
@@ -3061,19 +3073,7 @@
|
|
|
3061
3073
|
"indexKey": "",
|
|
3062
3074
|
"optional": true,
|
|
3063
3075
|
"description": "",
|
|
3064
|
-
"line":
|
|
3065
|
-
"rawdescription": "\n"
|
|
3066
|
-
},
|
|
3067
|
-
{
|
|
3068
|
-
"name": "useClassArray",
|
|
3069
|
-
"coverageIgnore": false,
|
|
3070
|
-
"deprecated": false,
|
|
3071
|
-
"deprecationMessage": "",
|
|
3072
|
-
"type": "boolean",
|
|
3073
|
-
"indexKey": "",
|
|
3074
|
-
"optional": true,
|
|
3075
|
-
"description": "",
|
|
3076
|
-
"line": 10,
|
|
3076
|
+
"line": 459,
|
|
3077
3077
|
"rawdescription": "\n"
|
|
3078
3078
|
}
|
|
3079
3079
|
],
|
|
@@ -3424,12 +3424,12 @@
|
|
|
3424
3424
|
},
|
|
3425
3425
|
{
|
|
3426
3426
|
"name": "UIState",
|
|
3427
|
-
"id": "interface-UIState-
|
|
3427
|
+
"id": "interface-UIState-4c61415cdeff5a7a75fdde5b834b027e0675b839b2b3fedc419445f7ab7a87cab30edea6bf5e4bfb88bdeefed11e734aeb6c3b741636e85a1c25065769b5c637",
|
|
3428
3428
|
"file": "packages/core/src/lib/services/eui-app-shell.service.ts",
|
|
3429
3429
|
"deprecated": false,
|
|
3430
3430
|
"deprecationMessage": "",
|
|
3431
3431
|
"type": "interface",
|
|
3432
|
-
"sourceCode": "import { Injectable, PLATFORM_ID, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { BehaviorSubject, defer, firstValueFrom, Observable } from 'rxjs';\nimport { EuiEuLanguages, GlobalConfig, getActiveLang, EuiLanguage, EuiMenuItem } from '@eui/base';\nimport { GLOBAL_CONFIG_TOKEN } from './config/tokens';\nimport { I18nService } from './i18n';\nimport { Router, NavigationEnd } from '@angular/router';\nimport { StoreService } from './store/store.service';\nimport { distinctUntilChanged, filter, map } from 'rxjs/operators';\nimport { isEqual, get } from 'lodash-es';\nimport { CssUtils } from '../helpers/css-utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface UIState<BP = any, DI = any, AMD =any, BPV = any> {\n // app state\n appName?: string;\n appShortName?: string;\n appSubTitle?: string;\n appBaseFontSize?: string;\n\n // Sidebar state\n isSidebarOpen?: boolean;\n isSidebarActive?: boolean;\n hasFixedPosition?: boolean;\n hasSidebar?: boolean;\n hasSideContainer?: boolean;\n hasBreadcrumb?: boolean;\n hasHeader?: boolean;\n hasHeaderLogo?: boolean;\n hasHeaderEnvironment?: boolean;\n hasToolbar?: boolean;\n hasToolbarMegaMenu?: boolean;\n hasToolbarMenu?: boolean;\n environmentValue?: string;\n isSidebarHidden?: boolean;\n isSidebarFocused?: boolean;\n hasSidebarCollapsedVariant?: boolean;\n hasTopMessage?: boolean;\n\n // window state\n windowWidth?: number;\n windowHeight?: number;\n mainContentHeight?: number;\n pageHeaderHeight?: number;\n breakpoint?: string;\n wrapperClasses?: string;\n breakpoints?: BP;\n breakpointValues?: BPV;\n\n // navigation state\n menuLinks?: EuiMenuItem[];\n sidebarLinks?: EuiMenuItem[];\n combinedLinks?: EuiMenuItem[];\n\n // other states\n isBlockDocumentActive?: boolean;\n\n // device info\n deviceInfo: DI;\n\n // language infos\n activeLanguage: string;\n languages: (string | EuiLanguage)[];\n\n // app metadata\n appMetadata: AMD;\n\n // various dynamic state\n hasModalActive?: boolean;\n isDimmerActive?: boolean; // Usage: map to eui base directive input coerce euiHighlighted\n}\n\nconst initialState: UIState = {\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasFixedPosition: true,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class EuiAppShellService {\n navigationStartCustomHandler: () => void;\n navigationEndCustomHandler: () => void;\n protected config = inject<GlobalConfig>(GLOBAL_CONFIG_TOKEN, { optional: true });\n private http = inject(HttpClient);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private router = inject(Router);\n private storeService = inject(StoreService);\n private i18nService = inject(I18nService, { optional: true });\n\n // -------------------\n get state$(): Observable<UIState> {\n return this._state$.asObservable();\n }\n\n // -------------------\n // exposed observables\n\n get breakpoint$(): Observable<string> {\n return this._breakpoint$.asObservable();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get breakpoints$(): Observable<any> {\n return this._breakpoints$.asObservable();\n }\n\n // ----------------\n // state operations\n // ----------------\n get state(): UIState {\n return this._state$.getValue();\n }\n\n // ----------------------------\n // public setters and functions\n // ----------------------------\n set isSidebarOpen(isOpen: boolean) {\n this.setState({\n ...this.state,\n isSidebarOpen: isOpen,\n });\n }\n\n get isSidebarOpen(): boolean {\n return this.state.isSidebarOpen;\n }\n\n set isSidebarActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isSidebarActive: isActive,\n });\n }\n\n set sidebarLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n sidebarLinks: links,\n });\n }\n\n set hasSidebarCollapsedVariant(isActive: boolean) {\n this.setState({\n ...this.state,\n hasSidebarCollapsedVariant: isActive,\n });\n CssUtils.activateSidebarCssVars(this.document, this.platformId, isActive);\n }\n\n set menuLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n menuLinks: links,\n });\n }\n\n set isBlockDocumentActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isBlockDocumentActive: isActive,\n });\n }\n\n get hasHeader(): boolean {\n return this.state.hasHeader;\n }\n\n // Edit mode\n get isDimmerActive(): boolean {\n return this.state.isDimmerActive;\n }\n\n set isDimmerActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isDimmerActive: isActive,\n });\n }\n\n private _state$: BehaviorSubject<UIState>;\n private _breakpoint$: BehaviorSubject<string>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _breakpoints$: BehaviorSubject<any>;\n\n constructor() {\n const config = this.config;\n\n let stateWithConfig = initialState;\n const languages = config?.i18n?.i18nService?.languages || initialState.languages;\n const defaultLanguage = config?.i18n?.i18nService?.defaultLanguage || initialState.activeLanguage;\n stateWithConfig = {\n ...stateWithConfig,\n ...{\n languages,\n activeLanguage: defaultLanguage,\n },\n };\n this._state$ = new BehaviorSubject(stateWithConfig);\n this._breakpoint$ = new BehaviorSubject('');\n this._breakpoints$ = new BehaviorSubject({});\n this.bindActiveLanguageToAppShellState();\n }\n\n setState(nextState: UIState, updateI18 = true): void {\n let breakpoint, breakpoints;\n let combinedLinks;\n\n const state = this.state;\n\n // check if window width has been updated from previous state\n if (this.state.windowWidth !== nextState.windowWidth) {\n breakpoint = this.getBreakpoint(nextState.windowWidth);\n breakpoints = this.getBreakpoints(breakpoint);\n\n this._breakpoint$.next(breakpoint);\n this._breakpoints$.next(breakpoints);\n\n // if not propagate the old ones without doing any calculations\n } else {\n breakpoint = state.breakpoint;\n breakpoints = state.breakpoints;\n }\n\n // finally get the wrapper classes when both the state and breakpoint are known\n const wrapperClasses = this.getWrapperClasses(nextState, breakpoint);\n\n // check if the menuLinks or sidebarLinks have changed from previous state\n if (this.state.menuLinks !== nextState.menuLinks || this.state.sidebarLinks !== nextState.sidebarLinks) {\n combinedLinks = [...nextState.menuLinks, ...nextState.sidebarLinks];\n } else {\n combinedLinks = this.state.combinedLinks;\n }\n\n const stateBeforeUpdate = { ...this.state };\n\n // we put it all together with the calculated properties\n this._state$.next({\n ...nextState,\n wrapperClasses,\n breakpoint,\n breakpoints,\n combinedLinks,\n });\n\n // update the Store Language\n if (updateI18 && nextState.activeLanguage !== stateBeforeUpdate.activeLanguage) {\n this.i18nService.updateState({ activeLang: nextState.activeLanguage });\n }\n }\n\n /**\n * Emits a slice from the state whether that changes\n *\n * @param key can be 'key' or 'key.sub.sub'\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getState<T = any>(key?: string): Observable<T> {\n return defer(() =>\n // check if key exists\n key\n ? this.state$.pipe(\n map((state) => get(state, key)),\n // filter((state) => state),\n distinctUntilChanged((x, y) => isEqual(x, y)),\n )\n : this.state$,\n );\n }\n\n public sidebarToggle(): void {\n this.isSidebarOpen = !this.state.isSidebarOpen;\n }\n\n // Edit mode\n public dimmerActiveToggle(): void {\n const isActive = this.isDimmerActive;\n this.setState({\n ...this.state,\n isDimmerActive: !isActive,\n });\n CssUtils.activateEditModeCssVars(!isActive, this.document);\n }\n\n public setDimmerActiveState(activeState: boolean): void {\n this.setState({\n ...this.state,\n isDimmerActive: activeState,\n });\n CssUtils.activateEditModeCssVars(activeState, this.document);\n }\n\n // --------------\n // public methods\n // --------------\n public fetchAppMetadata(metadataFilePath = 'assets/app-metadata.json'): void {\n this.getJson(metadataFilePath).then((data) => {\n this.setState({\n ...this.state,\n appMetadata: data,\n });\n });\n }\n\n public activateSidebar(): void {\n this.setState({\n ...this.state,\n hasSidebar: true,\n });\n\n if (!this.state.isSidebarHidden) {\n CssUtils.activateSidebarCssVars(this.document, this.platformId, this.state.hasSidebarCollapsedVariant);\n }\n }\n\n public activateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: true,\n });\n\n CssUtils.activateSideContainerCssVars(this.document, this.platformId);\n } \n\n public deactivateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: false,\n });\n\n CssUtils.deactivateSideContainerCssVars(this.document, this.platformId);\n } \n\n public activateSidebarHeader(): void {\n CssUtils.activateSidebarHeaderCssVars(this.document, this.platformId);\n }\n\n public activateSidebarFooter(): void {\n CssUtils.activateSidebarFooterCssVars(this.document, this.platformId);\n }\n\n public activateHeader(): void {\n this.setState({\n ...this.state,\n hasHeader: true,\n });\n CssUtils.activateHeaderCssVars(this.document, this.platformId);\n }\n\n public activateBreadcrumb(): void {\n this.setState({\n ...this.state,\n hasBreadcrumb: true,\n });\n CssUtils.activateBreadcrumbCssVars(this.document, this.platformId);\n }\n\n public activateTopMessage(height: number): void {\n this.setState({\n ...this.state,\n hasTopMessage: true,\n });\n CssUtils.activateTopMessageCssVars(height, this.document);\n }\n\n public activateToolbar(): void {\n this.setState({\n ...this.state,\n hasToolbar: true,\n });\n CssUtils.activateToolbarCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMegaMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMegaMenu: true,\n });\n CssUtils.activateToolbarMegaMenuCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMenu: true,\n });\n }\n\n /**\n * Returns the current value of --eui-f-size-base CSS variable\n */\n public getBaseFontSize(): string {\n return this.state.appBaseFontSize || CssUtils.getCssVarValue('--eui-f-size-base', this.document, this.platformId);\n }\n\n /**\n * Updates the current value of --eui-f-size-base CSS variable and the UIState appBaseFontSize\n */\n public setBaseFontSize(newsize: string): void {\n this.setState(\n {\n ...this.state,\n appBaseFontSize: newsize,\n },\n false,\n );\n CssUtils.setCssVarValue('--eui-f-size-base', newsize, this.document);\n }\n\n // ---------------\n // private getters\n // ---------------\n private getWrapperClasses(state: UIState, breakpoint: string): string {\n const classes: string[] = [];\n\n classes.push(breakpoint);\n\n if (state.hasSidebar) {\n if (state.isSidebarHidden) {\n classes.push('sidebar--hidden');\n }\n if (state.isSidebarOpen) {\n classes.push('sidebar--open');\n } else {\n classes.push('sidebar--close');\n }\n }\n if (state.deviceInfo?.isFF) {\n classes.push('ff');\n }\n if (state.deviceInfo?.isIE) {\n classes.push('ie');\n }\n if (state.deviceInfo?.isChrome) {\n classes.push('chrome');\n }\n if (state.hasFixedPosition) {\n classes.push('fixed-position');\n } else {\n classes.push('relative-position');\n }\n return classes.join(' ');\n }\n\n private getBreakpoint(windowWidth: number): string {\n let bkp = '';\n\n if (this.state.breakpointValues.length === 0) {\n this.setState({\n ...this.state,\n breakpointValues: CssUtils.getBreakpointValues(this.document, this.platformId),\n });\n }\n\n this.state.breakpointValues.forEach((b, i) => {\n if (i < this.state.breakpointValues.length) {\n if (windowWidth >= b.value && windowWidth < this.state.breakpointValues[i+1]?.value) {\n bkp = b.bkp;\n }\n } else if(windowWidth >= b.value) {\n bkp = b.bkp;\n }\n });\n\n return bkp;\n }\n\n private getBreakpoints(bkp: string): object {\n return {\n isMobile: bkp === 'xs' || bkp === 'sm',\n isTablet: bkp === 'md',\n isLtLargeTablet: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg',\n isLtDesktop: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg' || bkp === 'xl',\n isDesktop: bkp === 'xxl',\n isXL: bkp === 'xl',\n isXXL: bkp === 'xxl',\n isFHD: bkp === 'fhd',\n is2K: bkp === '2k',\n is4K: bkp === '4k',\n };\n }\n\n private getJson(url: string): Promise<object> {\n return firstValueFrom(this.http.get(url)).then(this.extractData).catch(this.handleError);\n }\n\n private extractData(res: Response): object {\n const body = res;\n return body || {};\n }\n\n private handleError<T extends Error>(error: T): Promise<T> {\n console.error('An error occurred', error);\n return Promise.reject(error.message || error);\n }\n\n private bindActiveLanguageToAppShellState(): void {\n this.i18nService.getState((s) => s.activeLang).subscribe((activeLang) => {\n if (activeLang !== this.state.activeLanguage) {\n this.setState(\n {\n ...this.state,\n activeLanguage: activeLang,\n },\n false,\n );\n }\n });\n }\n}\n",
|
|
3432
|
+
"sourceCode": "import { Injectable, PLATFORM_ID, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { BehaviorSubject, defer, firstValueFrom, Observable } from 'rxjs';\nimport { EuiEuLanguages, GlobalConfig, getActiveLang, EuiLanguage, EuiMenuItem } from '@eui/base';\nimport { GLOBAL_CONFIG_TOKEN } from './config/tokens';\nimport { I18nService } from './i18n';\nimport { Router, NavigationEnd } from '@angular/router';\nimport { StoreService } from './store/store.service';\nimport { distinctUntilChanged, filter, map } from 'rxjs/operators';\nimport { isEqual, get } from 'lodash-es';\nimport { CssUtils } from '../helpers/css-utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface UIState<BP = any, DI = any, AMD =any, BPV = any> {\n // app state\n appName?: string;\n appShortName?: string;\n appSubTitle?: string;\n appBaseFontSize?: string;\n\n // Sidebar state\n isSidebarOpen?: boolean;\n isSidebarActive?: boolean;\n hasSidebar?: boolean;\n hasSideContainer?: boolean;\n hasBreadcrumb?: boolean;\n hasHeader?: boolean;\n isShrinkHeaderActive?: boolean;\n hasHeaderLogo?: boolean;\n hasHeaderEnvironment?: boolean;\n hasToolbar?: boolean;\n hasToolbarMegaMenu?: boolean;\n hasToolbarMenu?: boolean;\n environmentValue?: string;\n isSidebarHidden?: boolean;\n isSidebarFocused?: boolean;\n hasSidebarCollapsedVariant?: boolean;\n hasTopMessage?: boolean;\n\n // window state\n hasFixedPosition?: boolean;\n windowWidth?: number;\n windowHeight?: number;\n mainContentHeight?: number;\n pageHeaderHeight?: number;\n breakpoint?: string;\n wrapperClasses?: string;\n breakpoints?: BP;\n breakpointValues?: BPV;\n\n // navigation state\n menuLinks?: EuiMenuItem[];\n sidebarLinks?: EuiMenuItem[];\n combinedLinks?: EuiMenuItem[];\n\n // other states\n isBlockDocumentActive?: boolean;\n\n // device info\n deviceInfo: DI;\n\n // language infos\n activeLanguage: string;\n languages: (string | EuiLanguage)[];\n\n // app metadata\n appMetadata: AMD;\n\n // various dynamic state\n hasModalActive?: boolean;\n isDimmerActive?: boolean; // Usage: map to eui base directive input coerce euiHighlighted\n}\n\nconst initialState: UIState = {\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n isShrinkHeaderActive: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class EuiAppShellService {\n navigationStartCustomHandler: () => void;\n navigationEndCustomHandler: () => void;\n protected config = inject<GlobalConfig>(GLOBAL_CONFIG_TOKEN, { optional: true });\n private http = inject(HttpClient);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private router = inject(Router);\n private storeService = inject(StoreService);\n private i18nService = inject(I18nService, { optional: true });\n\n // -------------------\n get state$(): Observable<UIState> {\n return this._state$.asObservable();\n }\n\n // -------------------\n // exposed observables\n\n get breakpoint$(): Observable<string> {\n return this._breakpoint$.asObservable();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get breakpoints$(): Observable<any> {\n return this._breakpoints$.asObservable();\n }\n\n // ----------------\n // state operations\n // ----------------\n get state(): UIState {\n return this._state$.getValue();\n }\n\n // ----------------------------\n // public setters and functions\n // ----------------------------\n set isSidebarOpen(isOpen: boolean) {\n this.setState({\n ...this.state,\n isSidebarOpen: isOpen,\n });\n }\n\n get isSidebarOpen(): boolean {\n return this.state.isSidebarOpen;\n }\n\n set isSidebarActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isSidebarActive: isActive,\n });\n }\n\n set sidebarLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n sidebarLinks: links,\n });\n }\n\n set hasSidebarCollapsedVariant(isActive: boolean) {\n this.setState({\n ...this.state,\n hasSidebarCollapsedVariant: isActive,\n });\n CssUtils.activateSidebarCssVars(this.document, this.platformId, isActive);\n }\n\n set menuLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n menuLinks: links,\n });\n }\n\n set isBlockDocumentActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isBlockDocumentActive: isActive,\n });\n }\n\n get hasHeader(): boolean {\n return this.state.hasHeader;\n }\n\n // Edit mode\n get isDimmerActive(): boolean {\n return this.state.isDimmerActive;\n }\n\n set isDimmerActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isDimmerActive: isActive,\n });\n }\n\n private _state$: BehaviorSubject<UIState>;\n private _breakpoint$: BehaviorSubject<string>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _breakpoints$: BehaviorSubject<any>;\n\n constructor() {\n const config = this.config;\n\n let stateWithConfig = initialState;\n const languages = config?.i18n?.i18nService?.languages || initialState.languages;\n const defaultLanguage = config?.i18n?.i18nService?.defaultLanguage || initialState.activeLanguage;\n stateWithConfig = {\n ...stateWithConfig,\n ...{\n languages,\n activeLanguage: defaultLanguage,\n },\n };\n this._state$ = new BehaviorSubject(stateWithConfig);\n this._breakpoint$ = new BehaviorSubject('');\n this._breakpoints$ = new BehaviorSubject({});\n this.bindActiveLanguageToAppShellState();\n }\n\n //eslint-disable-next-line @typescript-eslint/no-explicit-any\n setState(nextState: UIState<any, any, any, any>, updateI18 = true): void {\n let breakpoint, breakpoints;\n let combinedLinks;\n\n const state = this.state;\n\n // check if window width has been updated from previous state\n if (this.state.windowWidth !== nextState.windowWidth) {\n breakpoint = this.getBreakpoint(nextState.windowWidth);\n breakpoints = this.getBreakpoints(breakpoint);\n\n this._breakpoint$.next(breakpoint);\n this._breakpoints$.next(breakpoints);\n\n // if not propagate the old ones without doing any calculations\n } else {\n breakpoint = state.breakpoint;\n breakpoints = state.breakpoints;\n }\n\n // finally get the wrapper classes when both the state and breakpoint are known\n const wrapperClasses = this.getWrapperClasses(nextState, breakpoint);\n\n // check if the menuLinks or sidebarLinks have changed from previous state\n if (this.state.menuLinks !== nextState.menuLinks || this.state.sidebarLinks !== nextState.sidebarLinks) {\n combinedLinks = [...nextState.menuLinks, ...nextState.sidebarLinks];\n } else {\n combinedLinks = this.state.combinedLinks;\n }\n\n const stateBeforeUpdate = { ...this.state };\n\n // we put it all together with the calculated properties\n this._state$.next({\n ...nextState,\n wrapperClasses,\n breakpoint,\n breakpoints,\n combinedLinks,\n });\n\n // update the Store Language\n if (updateI18 && nextState.activeLanguage !== stateBeforeUpdate.activeLanguage) {\n this.i18nService.updateState({ activeLang: nextState.activeLanguage });\n }\n }\n\n /**\n * Emits a slice from the state whether that changes\n *\n * @param key can be 'key' or 'key.sub.sub'\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getState<T = any>(key?: string): Observable<T> {\n return defer(() =>\n // check if key exists\n key\n ? this.state$.pipe(\n map((state) => get(state, key)),\n // filter((state) => state),\n distinctUntilChanged((x, y) => isEqual(x, y)),\n )\n : this.state$,\n );\n }\n\n public sidebarToggle(): void {\n this.isSidebarOpen = !this.state.isSidebarOpen;\n }\n\n // Edit mode\n public dimmerActiveToggle(): void {\n const isActive = this.isDimmerActive;\n this.setState({\n ...this.state,\n isDimmerActive: !isActive,\n });\n CssUtils.activateEditModeCssVars(!isActive, this.document);\n }\n\n public setDimmerActiveState(activeState: boolean): void {\n this.setState({\n ...this.state,\n isDimmerActive: activeState,\n });\n CssUtils.activateEditModeCssVars(activeState, this.document);\n }\n\n // --------------\n // public methods\n // --------------\n public fetchAppMetadata(metadataFilePath = 'assets/app-metadata.json'): void {\n this.getJson(metadataFilePath).then((data) => {\n this.setState({\n ...this.state,\n appMetadata: data,\n });\n });\n }\n\n public activateSidebar(): void {\n this.setState({\n ...this.state,\n hasSidebar: true,\n });\n\n if (!this.state.isSidebarHidden) {\n CssUtils.activateSidebarCssVars(this.document, this.platformId, this.state.hasSidebarCollapsedVariant);\n }\n }\n\n public activateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: true,\n });\n\n CssUtils.activateSideContainerCssVars(this.document, this.platformId);\n }\n\n public deactivateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: false,\n });\n\n CssUtils.deactivateSideContainerCssVars(this.document, this.platformId);\n }\n\n public activateSidebarHeader(): void {\n CssUtils.activateSidebarHeaderCssVars(this.document, this.platformId);\n }\n\n public activateSidebarFooter(): void {\n CssUtils.activateSidebarFooterCssVars(this.document, this.platformId);\n }\n\n public activateHeader(): void {\n this.setState({\n ...this.state,\n hasHeader: true,\n });\n CssUtils.activateHeaderCssVars(this.document, this.platformId);\n }\n\n public activateBreadcrumb(): void {\n this.setState({\n ...this.state,\n hasBreadcrumb: true,\n });\n CssUtils.activateBreadcrumbCssVars(this.document, this.platformId);\n }\n\n public activateTopMessage(height: number): void {\n this.setState({\n ...this.state,\n hasTopMessage: true,\n });\n CssUtils.activateTopMessageCssVars(height, this.document);\n }\n\n public activateToolbar(): void {\n this.setState({\n ...this.state,\n hasToolbar: true,\n });\n CssUtils.activateToolbarCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMegaMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMegaMenu: true,\n });\n CssUtils.activateToolbarMegaMenuCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMenu: true,\n });\n }\n\n /**\n * Returns the current value of --eui-f-size-base CSS variable\n */\n public getBaseFontSize(): string {\n return this.state.appBaseFontSize || CssUtils.getCssVarValue('--eui-f-size-base', this.document, this.platformId);\n }\n\n /**\n * Updates the current value of --eui-f-size-base CSS variable and the UIState appBaseFontSize\n */\n public setBaseFontSize(newsize: string): void {\n this.setState(\n {\n ...this.state,\n appBaseFontSize: newsize,\n },\n false,\n );\n CssUtils.setCssVarValue('--eui-f-size-base', newsize, this.document);\n }\n\n // ---------------\n // private getters\n // ---------------\n private getWrapperClasses(state: UIState, breakpoint: string): string {\n const classes: string[] = [];\n\n classes.push(breakpoint);\n\n if (state.hasSidebar) {\n if (state.isSidebarHidden) {\n classes.push('sidebar--hidden');\n }\n if (state.isSidebarOpen) {\n classes.push('sidebar--open');\n } else {\n classes.push('sidebar--close');\n }\n }\n if (state.deviceInfo?.isFF) {\n classes.push('ff');\n }\n if (state.deviceInfo?.isIE) {\n classes.push('ie');\n }\n if (state.deviceInfo?.isChrome) {\n classes.push('chrome');\n }\n return classes.join(' ');\n }\n\n private getBreakpoint(windowWidth: number): string {\n let bkp = '';\n\n if (this.state.breakpointValues.length === 0) {\n this.setState({\n ...this.state,\n breakpointValues: CssUtils.getBreakpointValues(this.document, this.platformId),\n });\n }\n\n this.state.breakpointValues.forEach((b, i) => {\n if (i < this.state.breakpointValues.length) {\n if (windowWidth >= b.value && windowWidth < this.state.breakpointValues[i+1]?.value) {\n bkp = b.bkp;\n }\n } else if(windowWidth >= b.value) {\n bkp = b.bkp;\n }\n });\n\n return bkp;\n }\n\n private getBreakpoints(bkp: string): object {\n return {\n isMobile: bkp === 'xs' || bkp === 'sm',\n isTablet: bkp === 'md',\n isLtLargeTablet: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg',\n isLtDesktop: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg' || bkp === 'xl',\n isDesktop: bkp === 'xxl',\n isXL: bkp === 'xl',\n isXXL: bkp === 'xxl',\n isFHD: bkp === 'fhd',\n is2K: bkp === '2k',\n is4K: bkp === '4k',\n };\n }\n\n private getJson(url: string): Promise<object> {\n return firstValueFrom(this.http.get(url)).then(this.extractData).catch(this.handleError);\n }\n\n private extractData(res: Response): object {\n const body = res;\n return body || {};\n }\n\n private handleError<T extends Error>(error: T): Promise<T> {\n console.error('An error occurred', error);\n return Promise.reject(error.message || error);\n }\n\n private bindActiveLanguageToAppShellState(): void {\n this.i18nService.getState((s) => s.activeLang).subscribe((activeLang) => {\n if (activeLang !== this.state.activeLanguage) {\n this.setState(\n {\n ...this.state,\n activeLanguage: activeLang,\n },\n false,\n );\n }\n });\n }\n}\n",
|
|
3433
3433
|
"displayName": "UIState<BP = any, DI = any, AMD =any, BPV = any>",
|
|
3434
3434
|
"typeParameters": [
|
|
3435
3435
|
"BP = any",
|
|
@@ -3447,7 +3447,7 @@
|
|
|
3447
3447
|
"indexKey": "",
|
|
3448
3448
|
"optional": false,
|
|
3449
3449
|
"description": "",
|
|
3450
|
-
"line":
|
|
3450
|
+
"line": 64,
|
|
3451
3451
|
"rawdescription": "\n"
|
|
3452
3452
|
},
|
|
3453
3453
|
{
|
|
@@ -3471,7 +3471,7 @@
|
|
|
3471
3471
|
"indexKey": "",
|
|
3472
3472
|
"optional": false,
|
|
3473
3473
|
"description": "",
|
|
3474
|
-
"line":
|
|
3474
|
+
"line": 68,
|
|
3475
3475
|
"rawdescription": "\n"
|
|
3476
3476
|
},
|
|
3477
3477
|
{
|
|
@@ -3519,7 +3519,7 @@
|
|
|
3519
3519
|
"indexKey": "",
|
|
3520
3520
|
"optional": true,
|
|
3521
3521
|
"description": "",
|
|
3522
|
-
"line":
|
|
3522
|
+
"line": 47,
|
|
3523
3523
|
"rawdescription": "\n"
|
|
3524
3524
|
},
|
|
3525
3525
|
{
|
|
@@ -3531,7 +3531,7 @@
|
|
|
3531
3531
|
"indexKey": "",
|
|
3532
3532
|
"optional": true,
|
|
3533
3533
|
"description": "",
|
|
3534
|
-
"line":
|
|
3534
|
+
"line": 49,
|
|
3535
3535
|
"rawdescription": "\n"
|
|
3536
3536
|
},
|
|
3537
3537
|
{
|
|
@@ -3543,7 +3543,7 @@
|
|
|
3543
3543
|
"indexKey": "",
|
|
3544
3544
|
"optional": true,
|
|
3545
3545
|
"description": "",
|
|
3546
|
-
"line":
|
|
3546
|
+
"line": 50,
|
|
3547
3547
|
"rawdescription": "\n"
|
|
3548
3548
|
},
|
|
3549
3549
|
{
|
|
@@ -3555,7 +3555,7 @@
|
|
|
3555
3555
|
"indexKey": "",
|
|
3556
3556
|
"optional": true,
|
|
3557
3557
|
"description": "",
|
|
3558
|
-
"line":
|
|
3558
|
+
"line": 55,
|
|
3559
3559
|
"rawdescription": "\n"
|
|
3560
3560
|
},
|
|
3561
3561
|
{
|
|
@@ -3567,7 +3567,7 @@
|
|
|
3567
3567
|
"indexKey": "",
|
|
3568
3568
|
"optional": false,
|
|
3569
3569
|
"description": "",
|
|
3570
|
-
"line":
|
|
3570
|
+
"line": 61,
|
|
3571
3571
|
"rawdescription": "\n"
|
|
3572
3572
|
},
|
|
3573
3573
|
{
|
|
@@ -3591,7 +3591,7 @@
|
|
|
3591
3591
|
"indexKey": "",
|
|
3592
3592
|
"optional": true,
|
|
3593
3593
|
"description": "",
|
|
3594
|
-
"line":
|
|
3594
|
+
"line": 27,
|
|
3595
3595
|
"rawdescription": "\n"
|
|
3596
3596
|
},
|
|
3597
3597
|
{
|
|
@@ -3603,7 +3603,7 @@
|
|
|
3603
3603
|
"indexKey": "",
|
|
3604
3604
|
"optional": true,
|
|
3605
3605
|
"description": "",
|
|
3606
|
-
"line":
|
|
3606
|
+
"line": 42,
|
|
3607
3607
|
"rawdescription": "\n"
|
|
3608
3608
|
},
|
|
3609
3609
|
{
|
|
@@ -3615,7 +3615,7 @@
|
|
|
3615
3615
|
"indexKey": "",
|
|
3616
3616
|
"optional": true,
|
|
3617
3617
|
"description": "",
|
|
3618
|
-
"line":
|
|
3618
|
+
"line": 28,
|
|
3619
3619
|
"rawdescription": "\n"
|
|
3620
3620
|
},
|
|
3621
3621
|
{
|
|
@@ -3651,7 +3651,7 @@
|
|
|
3651
3651
|
"indexKey": "",
|
|
3652
3652
|
"optional": true,
|
|
3653
3653
|
"description": "",
|
|
3654
|
-
"line":
|
|
3654
|
+
"line": 71,
|
|
3655
3655
|
"rawdescription": "\n"
|
|
3656
3656
|
},
|
|
3657
3657
|
{
|
|
@@ -3663,7 +3663,7 @@
|
|
|
3663
3663
|
"indexKey": "",
|
|
3664
3664
|
"optional": true,
|
|
3665
3665
|
"description": "",
|
|
3666
|
-
"line":
|
|
3666
|
+
"line": 25,
|
|
3667
3667
|
"rawdescription": "\n"
|
|
3668
3668
|
},
|
|
3669
3669
|
{
|
|
@@ -3687,7 +3687,7 @@
|
|
|
3687
3687
|
"indexKey": "",
|
|
3688
3688
|
"optional": true,
|
|
3689
3689
|
"description": "",
|
|
3690
|
-
"line":
|
|
3690
|
+
"line": 26,
|
|
3691
3691
|
"rawdescription": "\n"
|
|
3692
3692
|
},
|
|
3693
3693
|
{
|
|
@@ -3747,7 +3747,7 @@
|
|
|
3747
3747
|
"indexKey": "",
|
|
3748
3748
|
"optional": true,
|
|
3749
3749
|
"description": "",
|
|
3750
|
-
"line":
|
|
3750
|
+
"line": 58,
|
|
3751
3751
|
"rawdescription": "\n"
|
|
3752
3752
|
},
|
|
3753
3753
|
{
|
|
@@ -3759,7 +3759,19 @@
|
|
|
3759
3759
|
"indexKey": "",
|
|
3760
3760
|
"optional": true,
|
|
3761
3761
|
"description": "",
|
|
3762
|
-
"line":
|
|
3762
|
+
"line": 72,
|
|
3763
|
+
"rawdescription": "\n"
|
|
3764
|
+
},
|
|
3765
|
+
{
|
|
3766
|
+
"name": "isShrinkHeaderActive",
|
|
3767
|
+
"coverageIgnore": false,
|
|
3768
|
+
"deprecated": false,
|
|
3769
|
+
"deprecationMessage": "",
|
|
3770
|
+
"type": "boolean",
|
|
3771
|
+
"indexKey": "",
|
|
3772
|
+
"optional": true,
|
|
3773
|
+
"description": "",
|
|
3774
|
+
"line": 29,
|
|
3763
3775
|
"rawdescription": "\n"
|
|
3764
3776
|
},
|
|
3765
3777
|
{
|
|
@@ -3819,7 +3831,7 @@
|
|
|
3819
3831
|
"indexKey": "",
|
|
3820
3832
|
"optional": false,
|
|
3821
3833
|
"description": "",
|
|
3822
|
-
"line":
|
|
3834
|
+
"line": 65,
|
|
3823
3835
|
"rawdescription": "\n"
|
|
3824
3836
|
},
|
|
3825
3837
|
{
|
|
@@ -3831,7 +3843,7 @@
|
|
|
3831
3843
|
"indexKey": "",
|
|
3832
3844
|
"optional": true,
|
|
3833
3845
|
"description": "",
|
|
3834
|
-
"line":
|
|
3846
|
+
"line": 45,
|
|
3835
3847
|
"rawdescription": "\n"
|
|
3836
3848
|
},
|
|
3837
3849
|
{
|
|
@@ -3843,7 +3855,7 @@
|
|
|
3843
3855
|
"indexKey": "",
|
|
3844
3856
|
"optional": true,
|
|
3845
3857
|
"description": "",
|
|
3846
|
-
"line":
|
|
3858
|
+
"line": 53,
|
|
3847
3859
|
"rawdescription": "\n"
|
|
3848
3860
|
},
|
|
3849
3861
|
{
|
|
@@ -3855,7 +3867,7 @@
|
|
|
3855
3867
|
"indexKey": "",
|
|
3856
3868
|
"optional": true,
|
|
3857
3869
|
"description": "",
|
|
3858
|
-
"line":
|
|
3870
|
+
"line": 46,
|
|
3859
3871
|
"rawdescription": "\n"
|
|
3860
3872
|
},
|
|
3861
3873
|
{
|
|
@@ -3867,7 +3879,7 @@
|
|
|
3867
3879
|
"indexKey": "",
|
|
3868
3880
|
"optional": true,
|
|
3869
3881
|
"description": "",
|
|
3870
|
-
"line":
|
|
3882
|
+
"line": 54,
|
|
3871
3883
|
"rawdescription": "\n"
|
|
3872
3884
|
},
|
|
3873
3885
|
{
|
|
@@ -3879,7 +3891,7 @@
|
|
|
3879
3891
|
"indexKey": "",
|
|
3880
3892
|
"optional": true,
|
|
3881
3893
|
"description": "",
|
|
3882
|
-
"line":
|
|
3894
|
+
"line": 44,
|
|
3883
3895
|
"rawdescription": "\n"
|
|
3884
3896
|
},
|
|
3885
3897
|
{
|
|
@@ -3891,7 +3903,7 @@
|
|
|
3891
3903
|
"indexKey": "",
|
|
3892
3904
|
"optional": true,
|
|
3893
3905
|
"description": "",
|
|
3894
|
-
"line":
|
|
3906
|
+
"line": 43,
|
|
3895
3907
|
"rawdescription": "\n"
|
|
3896
3908
|
},
|
|
3897
3909
|
{
|
|
@@ -3903,7 +3915,7 @@
|
|
|
3903
3915
|
"indexKey": "",
|
|
3904
3916
|
"optional": true,
|
|
3905
3917
|
"description": "",
|
|
3906
|
-
"line":
|
|
3918
|
+
"line": 48,
|
|
3907
3919
|
"rawdescription": "\n"
|
|
3908
3920
|
}
|
|
3909
3921
|
],
|
|
@@ -4885,7 +4897,7 @@
|
|
|
4885
4897
|
},
|
|
4886
4898
|
{
|
|
4887
4899
|
"name": "EuiAppShellService",
|
|
4888
|
-
"id": "injectable-EuiAppShellService-
|
|
4900
|
+
"id": "injectable-EuiAppShellService-4c61415cdeff5a7a75fdde5b834b027e0675b839b2b3fedc419445f7ab7a87cab30edea6bf5e4bfb88bdeefed11e734aeb6c3b741636e85a1c25065769b5c637",
|
|
4889
4901
|
"file": "packages/core/src/lib/services/eui-app-shell.service.ts",
|
|
4890
4902
|
"coverageIgnore": false,
|
|
4891
4903
|
"properties": [
|
|
@@ -4899,7 +4911,7 @@
|
|
|
4899
4911
|
"indexKey": "",
|
|
4900
4912
|
"optional": false,
|
|
4901
4913
|
"description": "",
|
|
4902
|
-
"line":
|
|
4914
|
+
"line": 135,
|
|
4903
4915
|
"rawdescription": "\n",
|
|
4904
4916
|
"modifierKind": [
|
|
4905
4917
|
124
|
|
@@ -4914,7 +4926,7 @@
|
|
|
4914
4926
|
"indexKey": "",
|
|
4915
4927
|
"optional": false,
|
|
4916
4928
|
"description": "",
|
|
4917
|
-
"line":
|
|
4929
|
+
"line": 134,
|
|
4918
4930
|
"rawdescription": "\n"
|
|
4919
4931
|
},
|
|
4920
4932
|
{
|
|
@@ -4926,7 +4938,7 @@
|
|
|
4926
4938
|
"indexKey": "",
|
|
4927
4939
|
"optional": false,
|
|
4928
4940
|
"description": "",
|
|
4929
|
-
"line":
|
|
4941
|
+
"line": 133,
|
|
4930
4942
|
"rawdescription": "\n"
|
|
4931
4943
|
}
|
|
4932
4944
|
],
|
|
@@ -4938,7 +4950,7 @@
|
|
|
4938
4950
|
"optional": false,
|
|
4939
4951
|
"returnType": "void",
|
|
4940
4952
|
"typeParameters": [],
|
|
4941
|
-
"line":
|
|
4953
|
+
"line": 403,
|
|
4942
4954
|
"deprecated": false,
|
|
4943
4955
|
"deprecationMessage": "",
|
|
4944
4956
|
"rawdescription": "\n",
|
|
@@ -4954,7 +4966,7 @@
|
|
|
4954
4966
|
"optional": false,
|
|
4955
4967
|
"returnType": "void",
|
|
4956
4968
|
"typeParameters": [],
|
|
4957
|
-
"line":
|
|
4969
|
+
"line": 395,
|
|
4958
4970
|
"deprecated": false,
|
|
4959
4971
|
"deprecationMessage": "",
|
|
4960
4972
|
"rawdescription": "\n",
|
|
@@ -4970,7 +4982,7 @@
|
|
|
4970
4982
|
"optional": false,
|
|
4971
4983
|
"returnType": "void",
|
|
4972
4984
|
"typeParameters": [],
|
|
4973
|
-
"line":
|
|
4985
|
+
"line": 358,
|
|
4974
4986
|
"deprecated": false,
|
|
4975
4987
|
"deprecationMessage": "",
|
|
4976
4988
|
"rawdescription": "\n",
|
|
@@ -4986,7 +4998,7 @@
|
|
|
4986
4998
|
"optional": false,
|
|
4987
4999
|
"returnType": "void",
|
|
4988
5000
|
"typeParameters": [],
|
|
4989
|
-
"line":
|
|
5001
|
+
"line": 391,
|
|
4990
5002
|
"deprecated": false,
|
|
4991
5003
|
"deprecationMessage": "",
|
|
4992
5004
|
"rawdescription": "\n",
|
|
@@ -5002,7 +5014,7 @@
|
|
|
5002
5014
|
"optional": false,
|
|
5003
5015
|
"returnType": "void",
|
|
5004
5016
|
"typeParameters": [],
|
|
5005
|
-
"line":
|
|
5017
|
+
"line": 387,
|
|
5006
5018
|
"deprecated": false,
|
|
5007
5019
|
"deprecationMessage": "",
|
|
5008
5020
|
"rawdescription": "\n",
|
|
@@ -5018,7 +5030,7 @@
|
|
|
5018
5030
|
"optional": false,
|
|
5019
5031
|
"returnType": "void",
|
|
5020
5032
|
"typeParameters": [],
|
|
5021
|
-
"line":
|
|
5033
|
+
"line": 369,
|
|
5022
5034
|
"deprecated": false,
|
|
5023
5035
|
"deprecationMessage": "",
|
|
5024
5036
|
"rawdescription": "\n",
|
|
@@ -5034,7 +5046,7 @@
|
|
|
5034
5046
|
"optional": false,
|
|
5035
5047
|
"returnType": "void",
|
|
5036
5048
|
"typeParameters": [],
|
|
5037
|
-
"line":
|
|
5049
|
+
"line": 419,
|
|
5038
5050
|
"deprecated": false,
|
|
5039
5051
|
"deprecationMessage": "",
|
|
5040
5052
|
"rawdescription": "\n",
|
|
@@ -5050,7 +5062,7 @@
|
|
|
5050
5062
|
"optional": false,
|
|
5051
5063
|
"returnType": "void",
|
|
5052
5064
|
"typeParameters": [],
|
|
5053
|
-
"line":
|
|
5065
|
+
"line": 427,
|
|
5054
5066
|
"deprecated": false,
|
|
5055
5067
|
"deprecationMessage": "",
|
|
5056
5068
|
"rawdescription": "\n",
|
|
@@ -5066,7 +5078,7 @@
|
|
|
5066
5078
|
"optional": false,
|
|
5067
5079
|
"returnType": "void",
|
|
5068
5080
|
"typeParameters": [],
|
|
5069
|
-
"line":
|
|
5081
|
+
"line": 435,
|
|
5070
5082
|
"deprecated": false,
|
|
5071
5083
|
"deprecationMessage": "",
|
|
5072
5084
|
"rawdescription": "\n",
|
|
@@ -5091,7 +5103,7 @@
|
|
|
5091
5103
|
"optional": false,
|
|
5092
5104
|
"returnType": "void",
|
|
5093
5105
|
"typeParameters": [],
|
|
5094
|
-
"line":
|
|
5106
|
+
"line": 411,
|
|
5095
5107
|
"deprecated": false,
|
|
5096
5108
|
"deprecationMessage": "",
|
|
5097
5109
|
"rawdescription": "\n",
|
|
@@ -5120,7 +5132,7 @@
|
|
|
5120
5132
|
"optional": false,
|
|
5121
5133
|
"returnType": "void",
|
|
5122
5134
|
"typeParameters": [],
|
|
5123
|
-
"line":
|
|
5135
|
+
"line": 378,
|
|
5124
5136
|
"deprecated": false,
|
|
5125
5137
|
"deprecationMessage": "",
|
|
5126
5138
|
"rawdescription": "\n",
|
|
@@ -5136,7 +5148,7 @@
|
|
|
5136
5148
|
"optional": false,
|
|
5137
5149
|
"returnType": "void",
|
|
5138
5150
|
"typeParameters": [],
|
|
5139
|
-
"line":
|
|
5151
|
+
"line": 329,
|
|
5140
5152
|
"deprecated": false,
|
|
5141
5153
|
"deprecationMessage": "",
|
|
5142
5154
|
"rawdescription": "\n",
|
|
@@ -5162,7 +5174,7 @@
|
|
|
5162
5174
|
"optional": false,
|
|
5163
5175
|
"returnType": "void",
|
|
5164
5176
|
"typeParameters": [],
|
|
5165
|
-
"line":
|
|
5177
|
+
"line": 349,
|
|
5166
5178
|
"deprecated": false,
|
|
5167
5179
|
"deprecationMessage": "",
|
|
5168
5180
|
"rawdescription": "\n",
|
|
@@ -5192,7 +5204,7 @@
|
|
|
5192
5204
|
"optional": false,
|
|
5193
5205
|
"returnType": "string",
|
|
5194
5206
|
"typeParameters": [],
|
|
5195
|
-
"line":
|
|
5207
|
+
"line": 445,
|
|
5196
5208
|
"deprecated": false,
|
|
5197
5209
|
"deprecationMessage": "",
|
|
5198
5210
|
"rawdescription": "\n\nReturns the current value of --eui-f-size-base CSS variable\n",
|
|
@@ -5219,7 +5231,7 @@
|
|
|
5219
5231
|
"typeParameters": [
|
|
5220
5232
|
"T"
|
|
5221
5233
|
],
|
|
5222
|
-
"line":
|
|
5234
|
+
"line": 311,
|
|
5223
5235
|
"deprecated": false,
|
|
5224
5236
|
"deprecationMessage": "",
|
|
5225
5237
|
"rawdescription": "\n\nEmits a slice from the state whether that changes\n\n",
|
|
@@ -5227,8 +5239,8 @@
|
|
|
5227
5239
|
"jsdoctags": [
|
|
5228
5240
|
{
|
|
5229
5241
|
"name": {
|
|
5230
|
-
"pos":
|
|
5231
|
-
"end":
|
|
5242
|
+
"pos": 9111,
|
|
5243
|
+
"end": 9114,
|
|
5232
5244
|
"kind": 80,
|
|
5233
5245
|
"id": 0,
|
|
5234
5246
|
"flags": 16842752,
|
|
@@ -5241,8 +5253,8 @@
|
|
|
5241
5253
|
"deprecated": false,
|
|
5242
5254
|
"deprecationMessage": "",
|
|
5243
5255
|
"tagName": {
|
|
5244
|
-
"pos":
|
|
5245
|
-
"end":
|
|
5256
|
+
"pos": 9105,
|
|
5257
|
+
"end": 9110,
|
|
5246
5258
|
"kind": 80,
|
|
5247
5259
|
"id": 0,
|
|
5248
5260
|
"flags": 16842752,
|
|
@@ -5269,7 +5281,7 @@
|
|
|
5269
5281
|
"optional": false,
|
|
5270
5282
|
"returnType": "void",
|
|
5271
5283
|
"typeParameters": [],
|
|
5272
|
-
"line":
|
|
5284
|
+
"line": 452,
|
|
5273
5285
|
"deprecated": false,
|
|
5274
5286
|
"deprecationMessage": "",
|
|
5275
5287
|
"rawdescription": "\n\nUpdates the current value of --eui-f-size-base CSS variable and the UIState appBaseFontSize\n",
|
|
@@ -5307,7 +5319,7 @@
|
|
|
5307
5319
|
"optional": false,
|
|
5308
5320
|
"returnType": "void",
|
|
5309
5321
|
"typeParameters": [],
|
|
5310
|
-
"line":
|
|
5322
|
+
"line": 338,
|
|
5311
5323
|
"deprecated": false,
|
|
5312
5324
|
"deprecationMessage": "",
|
|
5313
5325
|
"rawdescription": "\n",
|
|
@@ -5335,7 +5347,7 @@
|
|
|
5335
5347
|
"args": [
|
|
5336
5348
|
{
|
|
5337
5349
|
"name": "nextState",
|
|
5338
|
-
"type": "UIState",
|
|
5350
|
+
"type": "UIState<any | any | any | any>",
|
|
5339
5351
|
"optional": false,
|
|
5340
5352
|
"dotDotDotToken": false,
|
|
5341
5353
|
"deprecated": false,
|
|
@@ -5354,7 +5366,7 @@
|
|
|
5354
5366
|
"optional": false,
|
|
5355
5367
|
"returnType": "void",
|
|
5356
5368
|
"typeParameters": [],
|
|
5357
|
-
"line":
|
|
5369
|
+
"line": 258,
|
|
5358
5370
|
"deprecated": false,
|
|
5359
5371
|
"deprecationMessage": "",
|
|
5360
5372
|
"rawdescription": "\n",
|
|
@@ -5362,7 +5374,7 @@
|
|
|
5362
5374
|
"jsdoctags": [
|
|
5363
5375
|
{
|
|
5364
5376
|
"name": "nextState",
|
|
5365
|
-
"type": "UIState",
|
|
5377
|
+
"type": "UIState<any | any | any | any>",
|
|
5366
5378
|
"optional": false,
|
|
5367
5379
|
"dotDotDotToken": false,
|
|
5368
5380
|
"deprecated": false,
|
|
@@ -5392,7 +5404,7 @@
|
|
|
5392
5404
|
"optional": false,
|
|
5393
5405
|
"returnType": "void",
|
|
5394
5406
|
"typeParameters": [],
|
|
5395
|
-
"line":
|
|
5407
|
+
"line": 324,
|
|
5396
5408
|
"deprecated": false,
|
|
5397
5409
|
"deprecationMessage": "",
|
|
5398
5410
|
"rawdescription": "\n",
|
|
@@ -5406,14 +5418,14 @@
|
|
|
5406
5418
|
"deprecationMessage": "",
|
|
5407
5419
|
"description": "",
|
|
5408
5420
|
"rawdescription": "\n",
|
|
5409
|
-
"sourceCode": "import { Injectable, PLATFORM_ID, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { BehaviorSubject, defer, firstValueFrom, Observable } from 'rxjs';\nimport { EuiEuLanguages, GlobalConfig, getActiveLang, EuiLanguage, EuiMenuItem } from '@eui/base';\nimport { GLOBAL_CONFIG_TOKEN } from './config/tokens';\nimport { I18nService } from './i18n';\nimport { Router, NavigationEnd } from '@angular/router';\nimport { StoreService } from './store/store.service';\nimport { distinctUntilChanged, filter, map } from 'rxjs/operators';\nimport { isEqual, get } from 'lodash-es';\nimport { CssUtils } from '../helpers/css-utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface UIState<BP = any, DI = any, AMD =any, BPV = any> {\n // app state\n appName?: string;\n appShortName?: string;\n appSubTitle?: string;\n appBaseFontSize?: string;\n\n // Sidebar state\n isSidebarOpen?: boolean;\n isSidebarActive?: boolean;\n hasFixedPosition?: boolean;\n hasSidebar?: boolean;\n hasSideContainer?: boolean;\n hasBreadcrumb?: boolean;\n hasHeader?: boolean;\n hasHeaderLogo?: boolean;\n hasHeaderEnvironment?: boolean;\n hasToolbar?: boolean;\n hasToolbarMegaMenu?: boolean;\n hasToolbarMenu?: boolean;\n environmentValue?: string;\n isSidebarHidden?: boolean;\n isSidebarFocused?: boolean;\n hasSidebarCollapsedVariant?: boolean;\n hasTopMessage?: boolean;\n\n // window state\n windowWidth?: number;\n windowHeight?: number;\n mainContentHeight?: number;\n pageHeaderHeight?: number;\n breakpoint?: string;\n wrapperClasses?: string;\n breakpoints?: BP;\n breakpointValues?: BPV;\n\n // navigation state\n menuLinks?: EuiMenuItem[];\n sidebarLinks?: EuiMenuItem[];\n combinedLinks?: EuiMenuItem[];\n\n // other states\n isBlockDocumentActive?: boolean;\n\n // device info\n deviceInfo: DI;\n\n // language infos\n activeLanguage: string;\n languages: (string | EuiLanguage)[];\n\n // app metadata\n appMetadata: AMD;\n\n // various dynamic state\n hasModalActive?: boolean;\n isDimmerActive?: boolean; // Usage: map to eui base directive input coerce euiHighlighted\n}\n\nconst initialState: UIState = {\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasFixedPosition: true,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class EuiAppShellService {\n navigationStartCustomHandler: () => void;\n navigationEndCustomHandler: () => void;\n protected config = inject<GlobalConfig>(GLOBAL_CONFIG_TOKEN, { optional: true });\n private http = inject(HttpClient);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private router = inject(Router);\n private storeService = inject(StoreService);\n private i18nService = inject(I18nService, { optional: true });\n\n // -------------------\n get state$(): Observable<UIState> {\n return this._state$.asObservable();\n }\n\n // -------------------\n // exposed observables\n\n get breakpoint$(): Observable<string> {\n return this._breakpoint$.asObservable();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get breakpoints$(): Observable<any> {\n return this._breakpoints$.asObservable();\n }\n\n // ----------------\n // state operations\n // ----------------\n get state(): UIState {\n return this._state$.getValue();\n }\n\n // ----------------------------\n // public setters and functions\n // ----------------------------\n set isSidebarOpen(isOpen: boolean) {\n this.setState({\n ...this.state,\n isSidebarOpen: isOpen,\n });\n }\n\n get isSidebarOpen(): boolean {\n return this.state.isSidebarOpen;\n }\n\n set isSidebarActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isSidebarActive: isActive,\n });\n }\n\n set sidebarLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n sidebarLinks: links,\n });\n }\n\n set hasSidebarCollapsedVariant(isActive: boolean) {\n this.setState({\n ...this.state,\n hasSidebarCollapsedVariant: isActive,\n });\n CssUtils.activateSidebarCssVars(this.document, this.platformId, isActive);\n }\n\n set menuLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n menuLinks: links,\n });\n }\n\n set isBlockDocumentActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isBlockDocumentActive: isActive,\n });\n }\n\n get hasHeader(): boolean {\n return this.state.hasHeader;\n }\n\n // Edit mode\n get isDimmerActive(): boolean {\n return this.state.isDimmerActive;\n }\n\n set isDimmerActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isDimmerActive: isActive,\n });\n }\n\n private _state$: BehaviorSubject<UIState>;\n private _breakpoint$: BehaviorSubject<string>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _breakpoints$: BehaviorSubject<any>;\n\n constructor() {\n const config = this.config;\n\n let stateWithConfig = initialState;\n const languages = config?.i18n?.i18nService?.languages || initialState.languages;\n const defaultLanguage = config?.i18n?.i18nService?.defaultLanguage || initialState.activeLanguage;\n stateWithConfig = {\n ...stateWithConfig,\n ...{\n languages,\n activeLanguage: defaultLanguage,\n },\n };\n this._state$ = new BehaviorSubject(stateWithConfig);\n this._breakpoint$ = new BehaviorSubject('');\n this._breakpoints$ = new BehaviorSubject({});\n this.bindActiveLanguageToAppShellState();\n }\n\n setState(nextState: UIState, updateI18 = true): void {\n let breakpoint, breakpoints;\n let combinedLinks;\n\n const state = this.state;\n\n // check if window width has been updated from previous state\n if (this.state.windowWidth !== nextState.windowWidth) {\n breakpoint = this.getBreakpoint(nextState.windowWidth);\n breakpoints = this.getBreakpoints(breakpoint);\n\n this._breakpoint$.next(breakpoint);\n this._breakpoints$.next(breakpoints);\n\n // if not propagate the old ones without doing any calculations\n } else {\n breakpoint = state.breakpoint;\n breakpoints = state.breakpoints;\n }\n\n // finally get the wrapper classes when both the state and breakpoint are known\n const wrapperClasses = this.getWrapperClasses(nextState, breakpoint);\n\n // check if the menuLinks or sidebarLinks have changed from previous state\n if (this.state.menuLinks !== nextState.menuLinks || this.state.sidebarLinks !== nextState.sidebarLinks) {\n combinedLinks = [...nextState.menuLinks, ...nextState.sidebarLinks];\n } else {\n combinedLinks = this.state.combinedLinks;\n }\n\n const stateBeforeUpdate = { ...this.state };\n\n // we put it all together with the calculated properties\n this._state$.next({\n ...nextState,\n wrapperClasses,\n breakpoint,\n breakpoints,\n combinedLinks,\n });\n\n // update the Store Language\n if (updateI18 && nextState.activeLanguage !== stateBeforeUpdate.activeLanguage) {\n this.i18nService.updateState({ activeLang: nextState.activeLanguage });\n }\n }\n\n /**\n * Emits a slice from the state whether that changes\n *\n * @param key can be 'key' or 'key.sub.sub'\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getState<T = any>(key?: string): Observable<T> {\n return defer(() =>\n // check if key exists\n key\n ? this.state$.pipe(\n map((state) => get(state, key)),\n // filter((state) => state),\n distinctUntilChanged((x, y) => isEqual(x, y)),\n )\n : this.state$,\n );\n }\n\n public sidebarToggle(): void {\n this.isSidebarOpen = !this.state.isSidebarOpen;\n }\n\n // Edit mode\n public dimmerActiveToggle(): void {\n const isActive = this.isDimmerActive;\n this.setState({\n ...this.state,\n isDimmerActive: !isActive,\n });\n CssUtils.activateEditModeCssVars(!isActive, this.document);\n }\n\n public setDimmerActiveState(activeState: boolean): void {\n this.setState({\n ...this.state,\n isDimmerActive: activeState,\n });\n CssUtils.activateEditModeCssVars(activeState, this.document);\n }\n\n // --------------\n // public methods\n // --------------\n public fetchAppMetadata(metadataFilePath = 'assets/app-metadata.json'): void {\n this.getJson(metadataFilePath).then((data) => {\n this.setState({\n ...this.state,\n appMetadata: data,\n });\n });\n }\n\n public activateSidebar(): void {\n this.setState({\n ...this.state,\n hasSidebar: true,\n });\n\n if (!this.state.isSidebarHidden) {\n CssUtils.activateSidebarCssVars(this.document, this.platformId, this.state.hasSidebarCollapsedVariant);\n }\n }\n\n public activateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: true,\n });\n\n CssUtils.activateSideContainerCssVars(this.document, this.platformId);\n } \n\n public deactivateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: false,\n });\n\n CssUtils.deactivateSideContainerCssVars(this.document, this.platformId);\n } \n\n public activateSidebarHeader(): void {\n CssUtils.activateSidebarHeaderCssVars(this.document, this.platformId);\n }\n\n public activateSidebarFooter(): void {\n CssUtils.activateSidebarFooterCssVars(this.document, this.platformId);\n }\n\n public activateHeader(): void {\n this.setState({\n ...this.state,\n hasHeader: true,\n });\n CssUtils.activateHeaderCssVars(this.document, this.platformId);\n }\n\n public activateBreadcrumb(): void {\n this.setState({\n ...this.state,\n hasBreadcrumb: true,\n });\n CssUtils.activateBreadcrumbCssVars(this.document, this.platformId);\n }\n\n public activateTopMessage(height: number): void {\n this.setState({\n ...this.state,\n hasTopMessage: true,\n });\n CssUtils.activateTopMessageCssVars(height, this.document);\n }\n\n public activateToolbar(): void {\n this.setState({\n ...this.state,\n hasToolbar: true,\n });\n CssUtils.activateToolbarCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMegaMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMegaMenu: true,\n });\n CssUtils.activateToolbarMegaMenuCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMenu: true,\n });\n }\n\n /**\n * Returns the current value of --eui-f-size-base CSS variable\n */\n public getBaseFontSize(): string {\n return this.state.appBaseFontSize || CssUtils.getCssVarValue('--eui-f-size-base', this.document, this.platformId);\n }\n\n /**\n * Updates the current value of --eui-f-size-base CSS variable and the UIState appBaseFontSize\n */\n public setBaseFontSize(newsize: string): void {\n this.setState(\n {\n ...this.state,\n appBaseFontSize: newsize,\n },\n false,\n );\n CssUtils.setCssVarValue('--eui-f-size-base', newsize, this.document);\n }\n\n // ---------------\n // private getters\n // ---------------\n private getWrapperClasses(state: UIState, breakpoint: string): string {\n const classes: string[] = [];\n\n classes.push(breakpoint);\n\n if (state.hasSidebar) {\n if (state.isSidebarHidden) {\n classes.push('sidebar--hidden');\n }\n if (state.isSidebarOpen) {\n classes.push('sidebar--open');\n } else {\n classes.push('sidebar--close');\n }\n }\n if (state.deviceInfo?.isFF) {\n classes.push('ff');\n }\n if (state.deviceInfo?.isIE) {\n classes.push('ie');\n }\n if (state.deviceInfo?.isChrome) {\n classes.push('chrome');\n }\n if (state.hasFixedPosition) {\n classes.push('fixed-position');\n } else {\n classes.push('relative-position');\n }\n return classes.join(' ');\n }\n\n private getBreakpoint(windowWidth: number): string {\n let bkp = '';\n\n if (this.state.breakpointValues.length === 0) {\n this.setState({\n ...this.state,\n breakpointValues: CssUtils.getBreakpointValues(this.document, this.platformId),\n });\n }\n\n this.state.breakpointValues.forEach((b, i) => {\n if (i < this.state.breakpointValues.length) {\n if (windowWidth >= b.value && windowWidth < this.state.breakpointValues[i+1]?.value) {\n bkp = b.bkp;\n }\n } else if(windowWidth >= b.value) {\n bkp = b.bkp;\n }\n });\n\n return bkp;\n }\n\n private getBreakpoints(bkp: string): object {\n return {\n isMobile: bkp === 'xs' || bkp === 'sm',\n isTablet: bkp === 'md',\n isLtLargeTablet: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg',\n isLtDesktop: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg' || bkp === 'xl',\n isDesktop: bkp === 'xxl',\n isXL: bkp === 'xl',\n isXXL: bkp === 'xxl',\n isFHD: bkp === 'fhd',\n is2K: bkp === '2k',\n is4K: bkp === '4k',\n };\n }\n\n private getJson(url: string): Promise<object> {\n return firstValueFrom(this.http.get(url)).then(this.extractData).catch(this.handleError);\n }\n\n private extractData(res: Response): object {\n const body = res;\n return body || {};\n }\n\n private handleError<T extends Error>(error: T): Promise<T> {\n console.error('An error occurred', error);\n return Promise.reject(error.message || error);\n }\n\n private bindActiveLanguageToAppShellState(): void {\n this.i18nService.getState((s) => s.activeLang).subscribe((activeLang) => {\n if (activeLang !== this.state.activeLanguage) {\n this.setState(\n {\n ...this.state,\n activeLanguage: activeLang,\n },\n false,\n );\n }\n });\n }\n}\n",
|
|
5421
|
+
"sourceCode": "import { Injectable, PLATFORM_ID, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { BehaviorSubject, defer, firstValueFrom, Observable } from 'rxjs';\nimport { EuiEuLanguages, GlobalConfig, getActiveLang, EuiLanguage, EuiMenuItem } from '@eui/base';\nimport { GLOBAL_CONFIG_TOKEN } from './config/tokens';\nimport { I18nService } from './i18n';\nimport { Router, NavigationEnd } from '@angular/router';\nimport { StoreService } from './store/store.service';\nimport { distinctUntilChanged, filter, map } from 'rxjs/operators';\nimport { isEqual, get } from 'lodash-es';\nimport { CssUtils } from '../helpers/css-utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport interface UIState<BP = any, DI = any, AMD =any, BPV = any> {\n // app state\n appName?: string;\n appShortName?: string;\n appSubTitle?: string;\n appBaseFontSize?: string;\n\n // Sidebar state\n isSidebarOpen?: boolean;\n isSidebarActive?: boolean;\n hasSidebar?: boolean;\n hasSideContainer?: boolean;\n hasBreadcrumb?: boolean;\n hasHeader?: boolean;\n isShrinkHeaderActive?: boolean;\n hasHeaderLogo?: boolean;\n hasHeaderEnvironment?: boolean;\n hasToolbar?: boolean;\n hasToolbarMegaMenu?: boolean;\n hasToolbarMenu?: boolean;\n environmentValue?: string;\n isSidebarHidden?: boolean;\n isSidebarFocused?: boolean;\n hasSidebarCollapsedVariant?: boolean;\n hasTopMessage?: boolean;\n\n // window state\n hasFixedPosition?: boolean;\n windowWidth?: number;\n windowHeight?: number;\n mainContentHeight?: number;\n pageHeaderHeight?: number;\n breakpoint?: string;\n wrapperClasses?: string;\n breakpoints?: BP;\n breakpointValues?: BPV;\n\n // navigation state\n menuLinks?: EuiMenuItem[];\n sidebarLinks?: EuiMenuItem[];\n combinedLinks?: EuiMenuItem[];\n\n // other states\n isBlockDocumentActive?: boolean;\n\n // device info\n deviceInfo: DI;\n\n // language infos\n activeLanguage: string;\n languages: (string | EuiLanguage)[];\n\n // app metadata\n appMetadata: AMD;\n\n // various dynamic state\n hasModalActive?: boolean;\n isDimmerActive?: boolean; // Usage: map to eui base directive input coerce euiHighlighted\n}\n\nconst initialState: UIState = {\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n isShrinkHeaderActive: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class EuiAppShellService {\n navigationStartCustomHandler: () => void;\n navigationEndCustomHandler: () => void;\n protected config = inject<GlobalConfig>(GLOBAL_CONFIG_TOKEN, { optional: true });\n private http = inject(HttpClient);\n private platformId = inject(PLATFORM_ID);\n private document = inject<Document>(DOCUMENT);\n private router = inject(Router);\n private storeService = inject(StoreService);\n private i18nService = inject(I18nService, { optional: true });\n\n // -------------------\n get state$(): Observable<UIState> {\n return this._state$.asObservable();\n }\n\n // -------------------\n // exposed observables\n\n get breakpoint$(): Observable<string> {\n return this._breakpoint$.asObservable();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n get breakpoints$(): Observable<any> {\n return this._breakpoints$.asObservable();\n }\n\n // ----------------\n // state operations\n // ----------------\n get state(): UIState {\n return this._state$.getValue();\n }\n\n // ----------------------------\n // public setters and functions\n // ----------------------------\n set isSidebarOpen(isOpen: boolean) {\n this.setState({\n ...this.state,\n isSidebarOpen: isOpen,\n });\n }\n\n get isSidebarOpen(): boolean {\n return this.state.isSidebarOpen;\n }\n\n set isSidebarActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isSidebarActive: isActive,\n });\n }\n\n set sidebarLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n sidebarLinks: links,\n });\n }\n\n set hasSidebarCollapsedVariant(isActive: boolean) {\n this.setState({\n ...this.state,\n hasSidebarCollapsedVariant: isActive,\n });\n CssUtils.activateSidebarCssVars(this.document, this.platformId, isActive);\n }\n\n set menuLinks(links: EuiMenuItem[]) {\n this.setState({\n ...this.state,\n menuLinks: links,\n });\n }\n\n set isBlockDocumentActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isBlockDocumentActive: isActive,\n });\n }\n\n get hasHeader(): boolean {\n return this.state.hasHeader;\n }\n\n // Edit mode\n get isDimmerActive(): boolean {\n return this.state.isDimmerActive;\n }\n\n set isDimmerActive(isActive: boolean) {\n this.setState({\n ...this.state,\n isDimmerActive: isActive,\n });\n }\n\n private _state$: BehaviorSubject<UIState>;\n private _breakpoint$: BehaviorSubject<string>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _breakpoints$: BehaviorSubject<any>;\n\n constructor() {\n const config = this.config;\n\n let stateWithConfig = initialState;\n const languages = config?.i18n?.i18nService?.languages || initialState.languages;\n const defaultLanguage = config?.i18n?.i18nService?.defaultLanguage || initialState.activeLanguage;\n stateWithConfig = {\n ...stateWithConfig,\n ...{\n languages,\n activeLanguage: defaultLanguage,\n },\n };\n this._state$ = new BehaviorSubject(stateWithConfig);\n this._breakpoint$ = new BehaviorSubject('');\n this._breakpoints$ = new BehaviorSubject({});\n this.bindActiveLanguageToAppShellState();\n }\n\n //eslint-disable-next-line @typescript-eslint/no-explicit-any\n setState(nextState: UIState<any, any, any, any>, updateI18 = true): void {\n let breakpoint, breakpoints;\n let combinedLinks;\n\n const state = this.state;\n\n // check if window width has been updated from previous state\n if (this.state.windowWidth !== nextState.windowWidth) {\n breakpoint = this.getBreakpoint(nextState.windowWidth);\n breakpoints = this.getBreakpoints(breakpoint);\n\n this._breakpoint$.next(breakpoint);\n this._breakpoints$.next(breakpoints);\n\n // if not propagate the old ones without doing any calculations\n } else {\n breakpoint = state.breakpoint;\n breakpoints = state.breakpoints;\n }\n\n // finally get the wrapper classes when both the state and breakpoint are known\n const wrapperClasses = this.getWrapperClasses(nextState, breakpoint);\n\n // check if the menuLinks or sidebarLinks have changed from previous state\n if (this.state.menuLinks !== nextState.menuLinks || this.state.sidebarLinks !== nextState.sidebarLinks) {\n combinedLinks = [...nextState.menuLinks, ...nextState.sidebarLinks];\n } else {\n combinedLinks = this.state.combinedLinks;\n }\n\n const stateBeforeUpdate = { ...this.state };\n\n // we put it all together with the calculated properties\n this._state$.next({\n ...nextState,\n wrapperClasses,\n breakpoint,\n breakpoints,\n combinedLinks,\n });\n\n // update the Store Language\n if (updateI18 && nextState.activeLanguage !== stateBeforeUpdate.activeLanguage) {\n this.i18nService.updateState({ activeLang: nextState.activeLanguage });\n }\n }\n\n /**\n * Emits a slice from the state whether that changes\n *\n * @param key can be 'key' or 'key.sub.sub'\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getState<T = any>(key?: string): Observable<T> {\n return defer(() =>\n // check if key exists\n key\n ? this.state$.pipe(\n map((state) => get(state, key)),\n // filter((state) => state),\n distinctUntilChanged((x, y) => isEqual(x, y)),\n )\n : this.state$,\n );\n }\n\n public sidebarToggle(): void {\n this.isSidebarOpen = !this.state.isSidebarOpen;\n }\n\n // Edit mode\n public dimmerActiveToggle(): void {\n const isActive = this.isDimmerActive;\n this.setState({\n ...this.state,\n isDimmerActive: !isActive,\n });\n CssUtils.activateEditModeCssVars(!isActive, this.document);\n }\n\n public setDimmerActiveState(activeState: boolean): void {\n this.setState({\n ...this.state,\n isDimmerActive: activeState,\n });\n CssUtils.activateEditModeCssVars(activeState, this.document);\n }\n\n // --------------\n // public methods\n // --------------\n public fetchAppMetadata(metadataFilePath = 'assets/app-metadata.json'): void {\n this.getJson(metadataFilePath).then((data) => {\n this.setState({\n ...this.state,\n appMetadata: data,\n });\n });\n }\n\n public activateSidebar(): void {\n this.setState({\n ...this.state,\n hasSidebar: true,\n });\n\n if (!this.state.isSidebarHidden) {\n CssUtils.activateSidebarCssVars(this.document, this.platformId, this.state.hasSidebarCollapsedVariant);\n }\n }\n\n public activateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: true,\n });\n\n CssUtils.activateSideContainerCssVars(this.document, this.platformId);\n }\n\n public deactivateSideContainer(): void {\n this.setState({\n ...this.state,\n hasSideContainer: false,\n });\n\n CssUtils.deactivateSideContainerCssVars(this.document, this.platformId);\n }\n\n public activateSidebarHeader(): void {\n CssUtils.activateSidebarHeaderCssVars(this.document, this.platformId);\n }\n\n public activateSidebarFooter(): void {\n CssUtils.activateSidebarFooterCssVars(this.document, this.platformId);\n }\n\n public activateHeader(): void {\n this.setState({\n ...this.state,\n hasHeader: true,\n });\n CssUtils.activateHeaderCssVars(this.document, this.platformId);\n }\n\n public activateBreadcrumb(): void {\n this.setState({\n ...this.state,\n hasBreadcrumb: true,\n });\n CssUtils.activateBreadcrumbCssVars(this.document, this.platformId);\n }\n\n public activateTopMessage(height: number): void {\n this.setState({\n ...this.state,\n hasTopMessage: true,\n });\n CssUtils.activateTopMessageCssVars(height, this.document);\n }\n\n public activateToolbar(): void {\n this.setState({\n ...this.state,\n hasToolbar: true,\n });\n CssUtils.activateToolbarCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMegaMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMegaMenu: true,\n });\n CssUtils.activateToolbarMegaMenuCssVars(this.document, this.platformId);\n }\n\n public activateToolbarMenu(): void {\n this.setState({\n ...this.state,\n hasToolbarMenu: true,\n });\n }\n\n /**\n * Returns the current value of --eui-f-size-base CSS variable\n */\n public getBaseFontSize(): string {\n return this.state.appBaseFontSize || CssUtils.getCssVarValue('--eui-f-size-base', this.document, this.platformId);\n }\n\n /**\n * Updates the current value of --eui-f-size-base CSS variable and the UIState appBaseFontSize\n */\n public setBaseFontSize(newsize: string): void {\n this.setState(\n {\n ...this.state,\n appBaseFontSize: newsize,\n },\n false,\n );\n CssUtils.setCssVarValue('--eui-f-size-base', newsize, this.document);\n }\n\n // ---------------\n // private getters\n // ---------------\n private getWrapperClasses(state: UIState, breakpoint: string): string {\n const classes: string[] = [];\n\n classes.push(breakpoint);\n\n if (state.hasSidebar) {\n if (state.isSidebarHidden) {\n classes.push('sidebar--hidden');\n }\n if (state.isSidebarOpen) {\n classes.push('sidebar--open');\n } else {\n classes.push('sidebar--close');\n }\n }\n if (state.deviceInfo?.isFF) {\n classes.push('ff');\n }\n if (state.deviceInfo?.isIE) {\n classes.push('ie');\n }\n if (state.deviceInfo?.isChrome) {\n classes.push('chrome');\n }\n return classes.join(' ');\n }\n\n private getBreakpoint(windowWidth: number): string {\n let bkp = '';\n\n if (this.state.breakpointValues.length === 0) {\n this.setState({\n ...this.state,\n breakpointValues: CssUtils.getBreakpointValues(this.document, this.platformId),\n });\n }\n\n this.state.breakpointValues.forEach((b, i) => {\n if (i < this.state.breakpointValues.length) {\n if (windowWidth >= b.value && windowWidth < this.state.breakpointValues[i+1]?.value) {\n bkp = b.bkp;\n }\n } else if(windowWidth >= b.value) {\n bkp = b.bkp;\n }\n });\n\n return bkp;\n }\n\n private getBreakpoints(bkp: string): object {\n return {\n isMobile: bkp === 'xs' || bkp === 'sm',\n isTablet: bkp === 'md',\n isLtLargeTablet: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg',\n isLtDesktop: bkp === 'xs' || bkp === 'sm' || bkp === 'md' || bkp === 'lg' || bkp === 'xl',\n isDesktop: bkp === 'xxl',\n isXL: bkp === 'xl',\n isXXL: bkp === 'xxl',\n isFHD: bkp === 'fhd',\n is2K: bkp === '2k',\n is4K: bkp === '4k',\n };\n }\n\n private getJson(url: string): Promise<object> {\n return firstValueFrom(this.http.get(url)).then(this.extractData).catch(this.handleError);\n }\n\n private extractData(res: Response): object {\n const body = res;\n return body || {};\n }\n\n private handleError<T extends Error>(error: T): Promise<T> {\n console.error('An error occurred', error);\n return Promise.reject(error.message || error);\n }\n\n private bindActiveLanguageToAppShellState(): void {\n this.i18nService.getState((s) => s.activeLang).subscribe((activeLang) => {\n if (activeLang !== this.state.activeLanguage) {\n this.setState(\n {\n ...this.state,\n activeLanguage: activeLang,\n },\n false,\n );\n }\n });\n }\n}\n",
|
|
5410
5422
|
"constructorObj": {
|
|
5411
5423
|
"name": "constructor",
|
|
5412
5424
|
"description": "",
|
|
5413
5425
|
"deprecated": false,
|
|
5414
5426
|
"deprecationMessage": "",
|
|
5415
5427
|
"args": [],
|
|
5416
|
-
"line":
|
|
5428
|
+
"line": 236,
|
|
5417
5429
|
"rawdescription": "\n"
|
|
5418
5430
|
},
|
|
5419
5431
|
"accessors": {
|
|
@@ -5423,7 +5435,7 @@
|
|
|
5423
5435
|
"name": "state$",
|
|
5424
5436
|
"type": "unknown",
|
|
5425
5437
|
"returnType": "Observable<UIState>",
|
|
5426
|
-
"line":
|
|
5438
|
+
"line": 144,
|
|
5427
5439
|
"rawdescription": "\n",
|
|
5428
5440
|
"description": ""
|
|
5429
5441
|
}
|
|
@@ -5434,7 +5446,7 @@
|
|
|
5434
5446
|
"name": "breakpoint$",
|
|
5435
5447
|
"type": "unknown",
|
|
5436
5448
|
"returnType": "Observable<string>",
|
|
5437
|
-
"line":
|
|
5449
|
+
"line": 151,
|
|
5438
5450
|
"rawdescription": "\n",
|
|
5439
5451
|
"description": ""
|
|
5440
5452
|
}
|
|
@@ -5445,7 +5457,7 @@
|
|
|
5445
5457
|
"name": "breakpoints$",
|
|
5446
5458
|
"type": "unknown",
|
|
5447
5459
|
"returnType": "Observable<any>",
|
|
5448
|
-
"line":
|
|
5460
|
+
"line": 156,
|
|
5449
5461
|
"rawdescription": "\n",
|
|
5450
5462
|
"description": ""
|
|
5451
5463
|
}
|
|
@@ -5456,7 +5468,7 @@
|
|
|
5456
5468
|
"name": "state",
|
|
5457
5469
|
"type": "unknown",
|
|
5458
5470
|
"returnType": "UIState",
|
|
5459
|
-
"line":
|
|
5471
|
+
"line": 163,
|
|
5460
5472
|
"rawdescription": "\n",
|
|
5461
5473
|
"description": ""
|
|
5462
5474
|
}
|
|
@@ -5479,7 +5491,7 @@
|
|
|
5479
5491
|
}
|
|
5480
5492
|
],
|
|
5481
5493
|
"returnType": "void",
|
|
5482
|
-
"line":
|
|
5494
|
+
"line": 170,
|
|
5483
5495
|
"rawdescription": "\n",
|
|
5484
5496
|
"description": "",
|
|
5485
5497
|
"jsdoctags": [
|
|
@@ -5500,7 +5512,7 @@
|
|
|
5500
5512
|
"name": "isSidebarOpen",
|
|
5501
5513
|
"type": "boolean",
|
|
5502
5514
|
"returnType": "boolean",
|
|
5503
|
-
"line":
|
|
5515
|
+
"line": 177,
|
|
5504
5516
|
"rawdescription": "\n",
|
|
5505
5517
|
"description": ""
|
|
5506
5518
|
}
|
|
@@ -5523,7 +5535,7 @@
|
|
|
5523
5535
|
}
|
|
5524
5536
|
],
|
|
5525
5537
|
"returnType": "void",
|
|
5526
|
-
"line":
|
|
5538
|
+
"line": 181,
|
|
5527
5539
|
"rawdescription": "\n",
|
|
5528
5540
|
"description": "",
|
|
5529
5541
|
"jsdoctags": [
|
|
@@ -5559,7 +5571,7 @@
|
|
|
5559
5571
|
}
|
|
5560
5572
|
],
|
|
5561
5573
|
"returnType": "void",
|
|
5562
|
-
"line":
|
|
5574
|
+
"line": 188,
|
|
5563
5575
|
"rawdescription": "\n",
|
|
5564
5576
|
"description": "",
|
|
5565
5577
|
"jsdoctags": [
|
|
@@ -5595,7 +5607,7 @@
|
|
|
5595
5607
|
}
|
|
5596
5608
|
],
|
|
5597
5609
|
"returnType": "void",
|
|
5598
|
-
"line":
|
|
5610
|
+
"line": 195,
|
|
5599
5611
|
"rawdescription": "\n",
|
|
5600
5612
|
"description": "",
|
|
5601
5613
|
"jsdoctags": [
|
|
@@ -5631,7 +5643,7 @@
|
|
|
5631
5643
|
}
|
|
5632
5644
|
],
|
|
5633
5645
|
"returnType": "void",
|
|
5634
|
-
"line":
|
|
5646
|
+
"line": 203,
|
|
5635
5647
|
"rawdescription": "\n",
|
|
5636
5648
|
"description": "",
|
|
5637
5649
|
"jsdoctags": [
|
|
@@ -5667,7 +5679,7 @@
|
|
|
5667
5679
|
}
|
|
5668
5680
|
],
|
|
5669
5681
|
"returnType": "void",
|
|
5670
|
-
"line":
|
|
5682
|
+
"line": 210,
|
|
5671
5683
|
"rawdescription": "\n",
|
|
5672
5684
|
"description": "",
|
|
5673
5685
|
"jsdoctags": [
|
|
@@ -5691,7 +5703,7 @@
|
|
|
5691
5703
|
"name": "hasHeader",
|
|
5692
5704
|
"type": "boolean",
|
|
5693
5705
|
"returnType": "boolean",
|
|
5694
|
-
"line":
|
|
5706
|
+
"line": 217,
|
|
5695
5707
|
"rawdescription": "\n",
|
|
5696
5708
|
"description": ""
|
|
5697
5709
|
}
|
|
@@ -5714,7 +5726,7 @@
|
|
|
5714
5726
|
}
|
|
5715
5727
|
],
|
|
5716
5728
|
"returnType": "void",
|
|
5717
|
-
"line":
|
|
5729
|
+
"line": 226,
|
|
5718
5730
|
"rawdescription": "\n",
|
|
5719
5731
|
"description": "",
|
|
5720
5732
|
"jsdoctags": [
|
|
@@ -5735,7 +5747,7 @@
|
|
|
5735
5747
|
"name": "isDimmerActive",
|
|
5736
5748
|
"type": "boolean",
|
|
5737
5749
|
"returnType": "boolean",
|
|
5738
|
-
"line":
|
|
5750
|
+
"line": 222,
|
|
5739
5751
|
"rawdescription": "\n",
|
|
5740
5752
|
"description": ""
|
|
5741
5753
|
}
|
|
@@ -22660,7 +22672,7 @@
|
|
|
22660
22672
|
"deprecated": false,
|
|
22661
22673
|
"deprecationMessage": "",
|
|
22662
22674
|
"type": "UIState",
|
|
22663
|
-
"defaultValue": "{\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n
|
|
22675
|
+
"defaultValue": "{\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n isShrinkHeaderActive: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n}"
|
|
22664
22676
|
},
|
|
22665
22677
|
{
|
|
22666
22678
|
"name": "initialState",
|
|
@@ -27200,7 +27212,7 @@
|
|
|
27200
27212
|
},
|
|
27201
27213
|
{
|
|
27202
27214
|
"name": "findComponentDecorators",
|
|
27203
|
-
"file": "packages/core/schematics/
|
|
27215
|
+
"file": "packages/core/schematics/add-eui-imports/index.ts",
|
|
27204
27216
|
"ctype": "miscellaneous",
|
|
27205
27217
|
"subtype": "function",
|
|
27206
27218
|
"coverageIgnore": false,
|
|
@@ -27216,7 +27228,7 @@
|
|
|
27216
27228
|
"deprecationMessage": ""
|
|
27217
27229
|
}
|
|
27218
27230
|
],
|
|
27219
|
-
"returnType": "
|
|
27231
|
+
"returnType": "ComponentInfo[]",
|
|
27220
27232
|
"jsdoctags": [
|
|
27221
27233
|
{
|
|
27222
27234
|
"name": "sourceFile",
|
|
@@ -27230,7 +27242,7 @@
|
|
|
27230
27242
|
},
|
|
27231
27243
|
{
|
|
27232
27244
|
"name": "findComponentDecorators",
|
|
27233
|
-
"file": "packages/core/schematics/
|
|
27245
|
+
"file": "packages/core/schematics/migrate-to-standalone/index.ts",
|
|
27234
27246
|
"ctype": "miscellaneous",
|
|
27235
27247
|
"subtype": "function",
|
|
27236
27248
|
"coverageIgnore": false,
|
|
@@ -27246,7 +27258,7 @@
|
|
|
27246
27258
|
"deprecationMessage": ""
|
|
27247
27259
|
}
|
|
27248
27260
|
],
|
|
27249
|
-
"returnType": "
|
|
27261
|
+
"returnType": "ts.Decorator[]",
|
|
27250
27262
|
"jsdoctags": [
|
|
27251
27263
|
{
|
|
27252
27264
|
"name": "sourceFile",
|
|
@@ -34829,7 +34841,7 @@
|
|
|
34829
34841
|
},
|
|
34830
34842
|
{
|
|
34831
34843
|
"name": "visitDir",
|
|
34832
|
-
"file": "packages/core/schematics/
|
|
34844
|
+
"file": "packages/core/schematics/add-eui-imports/index.ts",
|
|
34833
34845
|
"ctype": "miscellaneous",
|
|
34834
34846
|
"subtype": "function",
|
|
34835
34847
|
"coverageIgnore": false,
|
|
@@ -34874,7 +34886,7 @@
|
|
|
34874
34886
|
},
|
|
34875
34887
|
{
|
|
34876
34888
|
"name": "visitDir",
|
|
34877
|
-
"file": "packages/core/schematics/
|
|
34889
|
+
"file": "packages/core/schematics/fix-no-multiple-empty-lines/index.ts",
|
|
34878
34890
|
"ctype": "miscellaneous",
|
|
34879
34891
|
"subtype": "function",
|
|
34880
34892
|
"coverageIgnore": false,
|
|
@@ -34919,7 +34931,7 @@
|
|
|
34919
34931
|
},
|
|
34920
34932
|
{
|
|
34921
34933
|
"name": "visitDir",
|
|
34922
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
34934
|
+
"file": "packages/core/schematics/migrate-eui-accent/index.ts",
|
|
34923
34935
|
"ctype": "miscellaneous",
|
|
34924
34936
|
"subtype": "function",
|
|
34925
34937
|
"coverageIgnore": false,
|
|
@@ -34964,7 +34976,7 @@
|
|
|
34964
34976
|
},
|
|
34965
34977
|
{
|
|
34966
34978
|
"name": "visitDir",
|
|
34967
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
34979
|
+
"file": "packages/core/schematics/migrate-eui-alert/index.ts",
|
|
34968
34980
|
"ctype": "miscellaneous",
|
|
34969
34981
|
"subtype": "function",
|
|
34970
34982
|
"coverageIgnore": false,
|
|
@@ -35009,7 +35021,7 @@
|
|
|
35009
35021
|
},
|
|
35010
35022
|
{
|
|
35011
35023
|
"name": "visitDir",
|
|
35012
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35024
|
+
"file": "packages/core/schematics/migrate-eui-avatar/index.ts",
|
|
35013
35025
|
"ctype": "miscellaneous",
|
|
35014
35026
|
"subtype": "function",
|
|
35015
35027
|
"coverageIgnore": false,
|
|
@@ -35054,7 +35066,7 @@
|
|
|
35054
35066
|
},
|
|
35055
35067
|
{
|
|
35056
35068
|
"name": "visitDir",
|
|
35057
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35069
|
+
"file": "packages/core/schematics/migrate-eui-button/index.ts",
|
|
35058
35070
|
"ctype": "miscellaneous",
|
|
35059
35071
|
"subtype": "function",
|
|
35060
35072
|
"coverageIgnore": false,
|
|
@@ -35099,7 +35111,7 @@
|
|
|
35099
35111
|
},
|
|
35100
35112
|
{
|
|
35101
35113
|
"name": "visitDir",
|
|
35102
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35114
|
+
"file": "packages/core/schematics/migrate-eui-chip/index.ts",
|
|
35103
35115
|
"ctype": "miscellaneous",
|
|
35104
35116
|
"subtype": "function",
|
|
35105
35117
|
"coverageIgnore": false,
|
|
@@ -35144,7 +35156,7 @@
|
|
|
35144
35156
|
},
|
|
35145
35157
|
{
|
|
35146
35158
|
"name": "visitDir",
|
|
35147
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35159
|
+
"file": "packages/core/schematics/migrate-eui-chip-list/index.ts",
|
|
35148
35160
|
"ctype": "miscellaneous",
|
|
35149
35161
|
"subtype": "function",
|
|
35150
35162
|
"coverageIgnore": false,
|
|
@@ -35189,7 +35201,7 @@
|
|
|
35189
35201
|
},
|
|
35190
35202
|
{
|
|
35191
35203
|
"name": "visitDir",
|
|
35192
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35204
|
+
"file": "packages/core/schematics/migrate-eui-discussion-thread/index.ts",
|
|
35193
35205
|
"ctype": "miscellaneous",
|
|
35194
35206
|
"subtype": "function",
|
|
35195
35207
|
"coverageIgnore": false,
|
|
@@ -35234,7 +35246,7 @@
|
|
|
35234
35246
|
},
|
|
35235
35247
|
{
|
|
35236
35248
|
"name": "visitDir",
|
|
35237
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35249
|
+
"file": "packages/core/schematics/migrate-eui-editor/index.ts",
|
|
35238
35250
|
"ctype": "miscellaneous",
|
|
35239
35251
|
"subtype": "function",
|
|
35240
35252
|
"coverageIgnore": false,
|
|
@@ -35279,7 +35291,7 @@
|
|
|
35279
35291
|
},
|
|
35280
35292
|
{
|
|
35281
35293
|
"name": "visitDir",
|
|
35282
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35294
|
+
"file": "packages/core/schematics/migrate-eui-fieldset/index.ts",
|
|
35283
35295
|
"ctype": "miscellaneous",
|
|
35284
35296
|
"subtype": "function",
|
|
35285
35297
|
"coverageIgnore": false,
|
|
@@ -35324,7 +35336,7 @@
|
|
|
35324
35336
|
},
|
|
35325
35337
|
{
|
|
35326
35338
|
"name": "visitDir",
|
|
35327
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35339
|
+
"file": "packages/core/schematics/migrate-eui-icon-svg/index.ts",
|
|
35328
35340
|
"ctype": "miscellaneous",
|
|
35329
35341
|
"subtype": "function",
|
|
35330
35342
|
"coverageIgnore": false,
|
|
@@ -35369,7 +35381,7 @@
|
|
|
35369
35381
|
},
|
|
35370
35382
|
{
|
|
35371
35383
|
"name": "visitDir",
|
|
35372
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35384
|
+
"file": "packages/core/schematics/migrate-eui-icon-toggle/index.ts",
|
|
35373
35385
|
"ctype": "miscellaneous",
|
|
35374
35386
|
"subtype": "function",
|
|
35375
35387
|
"coverageIgnore": false,
|
|
@@ -35414,7 +35426,7 @@
|
|
|
35414
35426
|
},
|
|
35415
35427
|
{
|
|
35416
35428
|
"name": "visitDir",
|
|
35417
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35429
|
+
"file": "packages/core/schematics/migrate-eui-popover/index.ts",
|
|
35418
35430
|
"ctype": "miscellaneous",
|
|
35419
35431
|
"subtype": "function",
|
|
35420
35432
|
"coverageIgnore": false,
|
|
@@ -35459,7 +35471,7 @@
|
|
|
35459
35471
|
},
|
|
35460
35472
|
{
|
|
35461
35473
|
"name": "visitDir",
|
|
35462
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35474
|
+
"file": "packages/core/schematics/migrate-eui-progress-circle/index.ts",
|
|
35463
35475
|
"ctype": "miscellaneous",
|
|
35464
35476
|
"subtype": "function",
|
|
35465
35477
|
"coverageIgnore": false,
|
|
@@ -35504,7 +35516,7 @@
|
|
|
35504
35516
|
},
|
|
35505
35517
|
{
|
|
35506
35518
|
"name": "visitDir",
|
|
35507
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35519
|
+
"file": "packages/core/schematics/migrate-eui-table/index.ts",
|
|
35508
35520
|
"ctype": "miscellaneous",
|
|
35509
35521
|
"subtype": "function",
|
|
35510
35522
|
"coverageIgnore": false,
|
|
@@ -35549,7 +35561,7 @@
|
|
|
35549
35561
|
},
|
|
35550
35562
|
{
|
|
35551
35563
|
"name": "visitDir",
|
|
35552
|
-
"file": "packages/core/schematics/migrate-eui-
|
|
35564
|
+
"file": "packages/core/schematics/migrate-eui-tabs/index.ts",
|
|
35553
35565
|
"ctype": "miscellaneous",
|
|
35554
35566
|
"subtype": "function",
|
|
35555
35567
|
"coverageIgnore": false,
|
|
@@ -35594,7 +35606,7 @@
|
|
|
35594
35606
|
},
|
|
35595
35607
|
{
|
|
35596
35608
|
"name": "visitDir",
|
|
35597
|
-
"file": "packages/core/schematics/migrate-
|
|
35609
|
+
"file": "packages/core/schematics/migrate-eui-toolbar-menu/index.ts",
|
|
35598
35610
|
"ctype": "miscellaneous",
|
|
35599
35611
|
"subtype": "function",
|
|
35600
35612
|
"coverageIgnore": false,
|
|
@@ -35639,7 +35651,7 @@
|
|
|
35639
35651
|
},
|
|
35640
35652
|
{
|
|
35641
35653
|
"name": "visitDir",
|
|
35642
|
-
"file": "packages/core/schematics/
|
|
35654
|
+
"file": "packages/core/schematics/migrate-eui-tooltip/index.ts",
|
|
35643
35655
|
"ctype": "miscellaneous",
|
|
35644
35656
|
"subtype": "function",
|
|
35645
35657
|
"coverageIgnore": false,
|
|
@@ -35684,7 +35696,7 @@
|
|
|
35684
35696
|
},
|
|
35685
35697
|
{
|
|
35686
35698
|
"name": "visitDir",
|
|
35687
|
-
"file": "packages/core/schematics/
|
|
35699
|
+
"file": "packages/core/schematics/migrate-to-standalone/index.ts",
|
|
35688
35700
|
"ctype": "miscellaneous",
|
|
35689
35701
|
"subtype": "function",
|
|
35690
35702
|
"coverageIgnore": false,
|
|
@@ -38638,7 +38650,7 @@
|
|
|
38638
38650
|
"deprecated": false,
|
|
38639
38651
|
"deprecationMessage": "",
|
|
38640
38652
|
"type": "UIState",
|
|
38641
|
-
"defaultValue": "{\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n
|
|
38653
|
+
"defaultValue": "{\n appName: '',\n appShortName: '',\n appSubTitle: '',\n appBaseFontSize: '',\n\n isSidebarOpen: true,\n isSidebarActive: false,\n hasSidebar: false,\n hasSideContainer: false,\n hasHeader: false,\n isShrinkHeaderActive: false,\n hasBreadcrumb: false,\n hasHeaderLogo: false,\n hasHeaderEnvironment: false,\n hasToolbar: false,\n hasToolbarMegaMenu: false,\n hasToolbarMenu: false,\n environmentValue: '',\n isSidebarHidden: false,\n isSidebarFocused: false,\n hasSidebarCollapsedVariant: false,\n hasTopMessage: false,\n windowWidth: 0,\n windowHeight: 0,\n mainContentHeight: 0,\n pageHeaderHeight: 0,\n wrapperClasses: '',\n breakpoint: '',\n breakpoints: {\n isMobile: false,\n isTablet: false,\n isLtLargeTablet: false,\n isLtDesktop: false,\n isDesktop: false,\n isXL: false,\n isXXL: false,\n isFHD: false,\n is2K: false,\n is4K: false,\n },\n breakpointValues: [],\n menuLinks: [],\n sidebarLinks: [],\n combinedLinks: [],\n isBlockDocumentActive: false,\n deviceInfo: null,\n activeLanguage: 'en',\n languages: EuiEuLanguages.getLanguages(),\n appMetadata: null,\n hasModalActive: false,\n isDimmerActive: false,\n}"
|
|
38642
38654
|
}
|
|
38643
38655
|
],
|
|
38644
38656
|
"packages/core/src/lib/services/eui-theme.service.ts": [
|