@ethlete/cdk 4.70.0 → 5.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1039 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
+ import { getProjects, logger, visitNotIgnoredFiles } from '@nx/devkit';
4
+ import * as ts from 'typescript';
5
+ // Symbol mappings
6
+ const SYMBOL_REPLACEMENTS = {
7
+ // Refs
8
+ DialogRef: 'OverlayRef',
9
+ BottomSheetRef: 'OverlayRef',
10
+ // Imports
11
+ DialogImports: 'OverlayImports',
12
+ BottomSheetImports: 'OverlayImports',
13
+ DynamicOverlayImports: 'OverlayImports',
14
+ // Providers
15
+ provideDialog: 'provideOverlay',
16
+ provideBottomSheet: 'provideOverlay',
17
+ // Directives
18
+ DialogCloseDirective: 'OverlayCloseDirective',
19
+ DialogTitleDirective: 'OverlayTitleDirective',
20
+ BottomSheetTitleDirective: 'OverlayTitleDirective',
21
+ DynamicOverlayTitleDirective: 'OverlayTitleDirective',
22
+ // Config
23
+ DialogConfig: 'OverlayConfig',
24
+ BottomSheetConfig: 'OverlayConfig',
25
+ // Data tokens
26
+ DIALOG_DATA: 'OVERLAY_DATA',
27
+ BOTTOM_SHEET_DATA: 'OVERLAY_DATA',
28
+ };
29
+ const SERVICE_TYPES = ['DialogService', 'BottomSheetService', 'DynamicOverlayService'];
30
+ // Symbols to remove
31
+ const SYMBOLS_TO_REMOVE = ['BottomSheetDragHandleComponent'];
32
+ // Style properties that need to be moved to strategy config
33
+ const STYLE_PROPERTIES = [
34
+ 'panelClass',
35
+ 'containerClass',
36
+ 'overlayClass',
37
+ 'backdropClass',
38
+ 'width',
39
+ 'height',
40
+ 'minWidth',
41
+ 'minHeight',
42
+ 'maxWidth',
43
+ 'maxHeight',
44
+ 'position',
45
+ ];
46
+ export default async function migrateDialogBottomSheet(tree) {
47
+ logger.log('🔄 Migrating dialog & bottom sheet to overlay...');
48
+ const projects = getProjects(tree);
49
+ for (const [, project] of projects) {
50
+ visitNotIgnoredFiles(tree, project.root, (filePath) => {
51
+ if (filePath.endsWith('.ts')) {
52
+ processTypeScriptFile(filePath, tree, logger);
53
+ }
54
+ else if (filePath.endsWith('.html')) {
55
+ processHtmlFile(filePath, tree);
56
+ }
57
+ });
58
+ }
59
+ logger.log('✅ Dialog & bottom sheet migration completed');
60
+ }
61
+ function processTypeScriptFile(filePath, tree, logger) {
62
+ const content = tree.read(filePath, 'utf-8');
63
+ if (!content)
64
+ return;
65
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
66
+ const changes = [];
67
+ const importsToAdd = new Set();
68
+ const importsToRemove = new Set();
69
+ // Track which symbols are from @ethlete/cdk
70
+ const ethleteSymbols = new Set();
71
+ // Track which identifiers represent which original service types
72
+ const serviceVariableMap = new Map(); // variableName -> original service type
73
+ // First pass: identify symbols from @ethlete/cdk
74
+ function identifyEthleteSymbols(node) {
75
+ if (ts.isImportDeclaration(node)) {
76
+ const moduleSpecifier = node.moduleSpecifier;
77
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
78
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
79
+ node.importClause.namedBindings.elements.forEach((element) => {
80
+ const importedName = element.propertyName?.text || element.name.text;
81
+ if (importedName in SYMBOL_REPLACEMENTS ||
82
+ SYMBOLS_TO_REMOVE.includes(importedName) ||
83
+ SERVICE_TYPES.includes(importedName) // ADD THIS LINE
84
+ ) {
85
+ ethleteSymbols.add(element.name.text);
86
+ }
87
+ });
88
+ }
89
+ }
90
+ }
91
+ ts.forEachChild(node, identifyEthleteSymbols);
92
+ }
93
+ identifyEthleteSymbols(sourceFile);
94
+ // Second pass: map variable names to their original service types
95
+ function mapServiceVariables(node) {
96
+ // Constructor parameters
97
+ if (ts.isParameter(node) && node.type && ts.isTypeReferenceNode(node.type)) {
98
+ if (ts.isIdentifier(node.type.typeName) && ts.isIdentifier(node.name)) {
99
+ const typeName = node.type.typeName.text;
100
+ if (isEthleteSymbol(typeName)) {
101
+ const originalName = getOriginalSymbolName(typeName);
102
+ if (originalName === 'DialogService' ||
103
+ originalName === 'BottomSheetService' ||
104
+ originalName === 'DynamicOverlayService') {
105
+ serviceVariableMap.set(node.name.text, originalName);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ // Property declarations with inject()
111
+ if (ts.isPropertyDeclaration(node) && node.initializer && ts.isCallExpression(node.initializer)) {
112
+ if (ts.isIdentifier(node.initializer.expression) &&
113
+ node.initializer.expression.text === 'inject' &&
114
+ node.initializer.arguments.length > 0) {
115
+ const firstArg = node.initializer.arguments[0];
116
+ // Handle both regular identifiers and private identifiers
117
+ let propertyName;
118
+ if (ts.isIdentifier(node.name)) {
119
+ propertyName = node.name.text;
120
+ }
121
+ else if (ts.isPrivateIdentifier(node.name)) {
122
+ propertyName = node.name.text; // This includes the # prefix
123
+ }
124
+ if (firstArg && ts.isIdentifier(firstArg) && propertyName) {
125
+ const argName = firstArg.text;
126
+ if (isEthleteSymbol(argName)) {
127
+ const originalName = getOriginalSymbolName(argName);
128
+ if (originalName === 'DialogService' ||
129
+ originalName === 'BottomSheetService' ||
130
+ originalName === 'DynamicOverlayService') {
131
+ serviceVariableMap.set(propertyName, originalName);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ ts.forEachChild(node, mapServiceVariables);
138
+ }
139
+ mapServiceVariables(sourceFile);
140
+ // Helper to check if a symbol is from @ethlete/cdk
141
+ function isEthleteSymbol(name) {
142
+ return ethleteSymbols.has(name);
143
+ }
144
+ // Helper to get the original symbol name before aliasing
145
+ function getOriginalSymbolName(localName) {
146
+ let originalName;
147
+ function findOriginalName(node) {
148
+ if (ts.isImportDeclaration(node)) {
149
+ const moduleSpecifier = node.moduleSpecifier;
150
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
151
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
152
+ node.importClause.namedBindings.elements.forEach((element) => {
153
+ if (element.name.text === localName) {
154
+ originalName = element.propertyName?.text || element.name.text;
155
+ }
156
+ });
157
+ }
158
+ }
159
+ }
160
+ ts.forEachChild(node, findOriginalName);
161
+ }
162
+ findOriginalName(sourceFile);
163
+ return originalName;
164
+ }
165
+ function visit(node) {
166
+ // In the visit function, update the constructor handling section
167
+ if (ts.isConstructorDeclaration(node)) {
168
+ const serviceParams = [];
169
+ const otherParams = [];
170
+ // Classify parameters
171
+ node.parameters.forEach((param) => {
172
+ if (param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)) {
173
+ const typeName = param.type.typeName.text;
174
+ if (isEthleteSymbol(typeName)) {
175
+ const originalName = getOriginalSymbolName(typeName);
176
+ if (originalName && SERVICE_TYPES.includes(originalName)) {
177
+ serviceParams.push(param);
178
+ }
179
+ else {
180
+ otherParams.push(param);
181
+ }
182
+ }
183
+ else {
184
+ otherParams.push(param);
185
+ }
186
+ }
187
+ else {
188
+ otherParams.push(param);
189
+ }
190
+ });
191
+ if (serviceParams.length > 0) {
192
+ // Find the class declaration
193
+ let classDecl;
194
+ let current = node.parent;
195
+ while (current) {
196
+ if (ts.isClassDeclaration(current)) {
197
+ classDecl = current;
198
+ break;
199
+ }
200
+ current = current.parent;
201
+ }
202
+ if (classDecl) {
203
+ const hasOnlyServiceParams = otherParams.length === 0;
204
+ const hasNoBody = !node.body || node.body.statements.length === 0;
205
+ if (hasOnlyServiceParams && hasNoBody) {
206
+ // Remove entire constructor and convert to field declarations
207
+ const constructorFullStart = node.getFullStart();
208
+ const constructorEnd = node.getEnd();
209
+ // Find the end including trailing newline
210
+ const fullText = sourceFile.getFullText();
211
+ let endPos = constructorEnd;
212
+ while (endPos < fullText.length && (fullText[endPos] === ' ' || fullText[endPos] === '\t')) {
213
+ endPos++;
214
+ }
215
+ if (endPos < fullText.length && fullText[endPos] === '\n') {
216
+ endPos++;
217
+ }
218
+ // Get the indentation
219
+ const constructorLineStart = fullText.lastIndexOf('\n', constructorFullStart) + 1;
220
+ const indent = fullText.substring(constructorLineStart, constructorFullStart);
221
+ // Build replacement with field declarations
222
+ const fieldDeclarations = serviceParams
223
+ .map((param) => {
224
+ const paramName = param.name.getText(sourceFile);
225
+ const modifiers = param.modifiers?.map((m) => m.getText(sourceFile)).join(' ') || 'private';
226
+ // Track the variable name to original service type
227
+ const typeName = param.type.typeName;
228
+ if (ts.isIdentifier(typeName)) {
229
+ const originalName = getOriginalSymbolName(typeName.text);
230
+ if (originalName && SERVICE_TYPES.includes(originalName)) {
231
+ serviceVariableMap.set(paramName, originalName);
232
+ }
233
+ }
234
+ return `${indent}${modifiers} ${paramName} = injectOverlayManager();`;
235
+ })
236
+ .join('\n');
237
+ changes.push({
238
+ start: constructorFullStart,
239
+ end: endPos,
240
+ replacement: fieldDeclarations + '\n',
241
+ });
242
+ importsToAdd.add('injectOverlayManager');
243
+ serviceParams.forEach((param) => {
244
+ if (param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)) {
245
+ const originalName = getOriginalSymbolName(param.type.typeName.text);
246
+ if (originalName) {
247
+ importsToRemove.add(originalName);
248
+ }
249
+ }
250
+ });
251
+ }
252
+ else if (serviceParams.length > 0 && otherParams.length > 0) {
253
+ // Mixed parameters - replace entire constructor with fields + new constructor
254
+ const constructorFullStart = node.getFullStart();
255
+ const constructorEnd = node.getEnd();
256
+ // Find the end including trailing newline
257
+ const fullText = sourceFile.getFullText();
258
+ let endPos = constructorEnd;
259
+ while (endPos < fullText.length && (fullText[endPos] === ' ' || fullText[endPos] === '\t')) {
260
+ endPos++;
261
+ }
262
+ if (endPos < fullText.length && fullText[endPos] === '\n') {
263
+ endPos++;
264
+ }
265
+ // Get the indentation
266
+ const constructorLineStart = fullText.lastIndexOf('\n', constructorFullStart) + 1;
267
+ const indent = fullText.substring(constructorLineStart, constructorFullStart);
268
+ // Build field declarations for service params
269
+ const fieldDeclarations = serviceParams
270
+ .map((param) => {
271
+ const paramName = param.name.getText(sourceFile);
272
+ const modifiers = param.modifiers?.map((m) => m.getText(sourceFile)).join(' ') || 'private';
273
+ // Track the variable name to original service type
274
+ const typeName = param.type.typeName;
275
+ if (ts.isIdentifier(typeName)) {
276
+ const originalName = getOriginalSymbolName(typeName.text);
277
+ if (originalName && SERVICE_TYPES.includes(originalName)) {
278
+ serviceVariableMap.set(paramName, originalName);
279
+ }
280
+ }
281
+ return `${indent}${modifiers} ${paramName} = injectOverlayManager();`;
282
+ })
283
+ .join('\n');
284
+ // Build new constructor with only non-service params
285
+ const newConstructorParams = otherParams.map((param) => param.getText(sourceFile)).join(',\n ');
286
+ const bodyText = node.body ? ` ${node.body.getText(sourceFile)}` : ' {}';
287
+ const newConstructor = `${indent}constructor(\n ${newConstructorParams}\n )${bodyText}`;
288
+ // Replace the entire constructor section with fields + new constructor
289
+ const replacement = `${fieldDeclarations}\n\n${newConstructor}\n`;
290
+ changes.push({
291
+ start: constructorFullStart,
292
+ end: endPos,
293
+ replacement,
294
+ });
295
+ importsToAdd.add('injectOverlayManager');
296
+ serviceParams.forEach((param) => {
297
+ if (param.type && ts.isTypeReferenceNode(param.type) && ts.isIdentifier(param.type.typeName)) {
298
+ const originalName = getOriginalSymbolName(param.type.typeName.text);
299
+ if (originalName) {
300
+ importsToRemove.add(originalName);
301
+ }
302
+ }
303
+ });
304
+ }
305
+ }
306
+ return; // Don't traverse children, we've handled this constructor
307
+ }
308
+ }
309
+ // Handle inject() calls for services
310
+ if (ts.isCallExpression(node) &&
311
+ ts.isIdentifier(node.expression) &&
312
+ node.expression.text === 'inject' &&
313
+ node.arguments.length > 0) {
314
+ const firstArg = node.arguments[0];
315
+ if (firstArg && ts.isIdentifier(firstArg)) {
316
+ const argName = firstArg.text;
317
+ if (isEthleteSymbol(argName)) {
318
+ const originalName = getOriginalSymbolName(argName);
319
+ if (originalName && SERVICE_TYPES.includes(originalName)) {
320
+ // Replace inject(DialogService) with injectOverlayManager()
321
+ changes.push({
322
+ start: node.getStart(sourceFile),
323
+ end: node.getEnd(),
324
+ replacement: 'injectOverlayManager()',
325
+ });
326
+ importsToAdd.add('injectOverlayManager');
327
+ importsToRemove.add(originalName);
328
+ // Track the variable name if this is a property declaration
329
+ if (ts.isPropertyDeclaration(node.parent)) {
330
+ let propertyName;
331
+ if (ts.isIdentifier(node.parent.name)) {
332
+ propertyName = node.parent.name.text;
333
+ }
334
+ else if (ts.isPrivateIdentifier(node.parent.name)) {
335
+ propertyName = node.parent.name.text; // Includes the # prefix
336
+ }
337
+ if (propertyName) {
338
+ serviceVariableMap.set(propertyName, originalName);
339
+ }
340
+ }
341
+ return; // Don't traverse children
342
+ }
343
+ }
344
+ }
345
+ }
346
+ // Handle type references in type annotation positions (e.g., constructor parameters, return types)
347
+ if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
348
+ const typeName = node.typeName.text;
349
+ if (isEthleteSymbol(typeName)) {
350
+ const originalName = getOriginalSymbolName(typeName);
351
+ if (originalName && originalName in SYMBOL_REPLACEMENTS) {
352
+ const replacement = SYMBOL_REPLACEMENTS[originalName];
353
+ changes.push({
354
+ start: node.typeName.getStart(sourceFile),
355
+ end: node.typeName.getEnd(),
356
+ replacement,
357
+ });
358
+ importsToAdd.add(replacement);
359
+ importsToRemove.add(originalName);
360
+ }
361
+ }
362
+ }
363
+ // Handle type references in expression positions (e.g., inject(DialogRef<T>))
364
+ if (ts.isExpressionWithTypeArguments(node) && ts.isIdentifier(node.expression)) {
365
+ const typeName = node.expression.text;
366
+ if (isEthleteSymbol(typeName)) {
367
+ const originalName = getOriginalSymbolName(typeName);
368
+ if (originalName && originalName in SYMBOL_REPLACEMENTS) {
369
+ const replacement = SYMBOL_REPLACEMENTS[originalName];
370
+ changes.push({
371
+ start: node.expression.getStart(sourceFile),
372
+ end: node.expression.getEnd(),
373
+ replacement,
374
+ });
375
+ importsToAdd.add(replacement);
376
+ importsToRemove.add(originalName);
377
+ }
378
+ }
379
+ }
380
+ // In the identifier handler section, update to track provider removals better:
381
+ // Handle identifiers used as values (e.g., inject(DialogRef), provideDialog(), [BottomSheetDragHandleComponent])
382
+ if (ts.isIdentifier(node)) {
383
+ const name = node.text;
384
+ if (isEthleteSymbol(name)) {
385
+ const originalName = getOriginalSymbolName(name);
386
+ if (originalName) {
387
+ // Check if this identifier is in a position where it needs to be replaced or removed
388
+ const parent = node.parent;
389
+ // Skip if it's part of a type reference (already handled above)
390
+ // Skip if it's part of an import declaration
391
+ // Skip if it's a property name in an object literal or property access
392
+ if (ts.isTypeReferenceNode(parent) ||
393
+ ts.isExpressionWithTypeArguments(parent) ||
394
+ ts.isImportSpecifier(parent) ||
395
+ (ts.isPropertyAccessExpression(parent) && parent.name === node) ||
396
+ (ts.isPropertyAssignment(parent) && parent.name === node)) {
397
+ // Skip - already handled or shouldn't be replaced
398
+ return;
399
+ }
400
+ // Handle symbols to remove (e.g., BottomSheetDragHandleComponent)
401
+ if (SYMBOLS_TO_REMOVE.includes(originalName)) {
402
+ // Check if it's in an array literal (e.g., imports: [BottomSheetDragHandleComponent])
403
+ if (ts.isArrayLiteralExpression(parent)) {
404
+ // Remove the identifier and any trailing comma
405
+ const nodeStart = node.getStart(sourceFile);
406
+ const nodeEnd = node.getEnd();
407
+ // Check if there's a comma after this element
408
+ let end = nodeEnd;
409
+ const fullText = sourceFile.getFullText();
410
+ // Skip whitespace after the identifier
411
+ while (end < fullText.length &&
412
+ (fullText[end] === ' ' || fullText[end] === '\t' || fullText[end] === '\n')) {
413
+ end++;
414
+ }
415
+ // If there's a comma, include it in the removal
416
+ if (fullText[end] === ',') {
417
+ end++;
418
+ // Skip whitespace after the comma
419
+ while (end < fullText.length &&
420
+ (fullText[end] === ' ' || fullText[end] === '\t' || fullText[end] === '\n')) {
421
+ end++;
422
+ }
423
+ }
424
+ else {
425
+ // Check if there's a comma BEFORE this element
426
+ let start = nodeStart;
427
+ while (start > 0 &&
428
+ (fullText[start - 1] === ' ' || fullText[start - 1] === '\t' || fullText[start - 1] === '\n')) {
429
+ start--;
430
+ }
431
+ if (start > 0 && fullText[start - 1] === ',') {
432
+ start--;
433
+ // Include preceding whitespace
434
+ while (start > 0 &&
435
+ (fullText[start - 1] === ' ' || fullText[start - 1] === '\t' || fullText[start - 1] === '\n')) {
436
+ start--;
437
+ }
438
+ changes.push({
439
+ start,
440
+ end: nodeEnd,
441
+ replacement: '',
442
+ });
443
+ importsToRemove.add(originalName);
444
+ return;
445
+ }
446
+ }
447
+ changes.push({
448
+ start: nodeStart,
449
+ end,
450
+ replacement: '',
451
+ });
452
+ importsToRemove.add(originalName);
453
+ return;
454
+ }
455
+ // For other contexts, just remove it
456
+ importsToRemove.add(originalName);
457
+ return;
458
+ }
459
+ // Handle symbols to replace
460
+ if (originalName in SYMBOL_REPLACEMENTS) {
461
+ const replacement = SYMBOL_REPLACEMENTS[originalName];
462
+ // Special handling for items in arrays - check for duplicates
463
+ if (ts.isArrayLiteralExpression(parent)) {
464
+ // Count how many elements in this array will map to the same replacement
465
+ const elementsWithSameReplacement = parent.elements.filter((element) => {
466
+ if (ts.isIdentifier(element)) {
467
+ const elemName = element.text;
468
+ if (isEthleteSymbol(elemName)) {
469
+ const elemOriginalName = getOriginalSymbolName(elemName);
470
+ if (elemOriginalName && elemOriginalName in SYMBOL_REPLACEMENTS) {
471
+ return SYMBOL_REPLACEMENTS[elemOriginalName] === replacement;
472
+ }
473
+ }
474
+ }
475
+ if (ts.isCallExpression(element) && ts.isIdentifier(element.expression)) {
476
+ const elemName = element.expression.text;
477
+ if (isEthleteSymbol(elemName)) {
478
+ const elemOriginalName = getOriginalSymbolName(elemName);
479
+ if (elemOriginalName && elemOriginalName in SYMBOL_REPLACEMENTS) {
480
+ return SYMBOL_REPLACEMENTS[elemOriginalName] === replacement;
481
+ }
482
+ }
483
+ }
484
+ return false;
485
+ });
486
+ // If there are multiple elements that map to the same replacement,
487
+ // only keep the first one and remove the rest
488
+ if (elementsWithSameReplacement.length > 1) {
489
+ const firstElement = elementsWithSameReplacement[0];
490
+ // Check if current node is the first element
491
+ const isFirstElement = node === firstElement ||
492
+ (ts.isCallExpression(parent) && parent.expression === node && parent === firstElement);
493
+ if (!isFirstElement) {
494
+ // This is not the first element, remove it
495
+ // We need to remove the parent if it's a call expression
496
+ const nodeToRemove = ts.isCallExpression(parent) ? parent : node;
497
+ const nodeStart = nodeToRemove.getFullStart();
498
+ const nodeEnd = nodeToRemove.getEnd();
499
+ const fullText = sourceFile.getFullText();
500
+ // Find comma and whitespace to remove
501
+ let start = nodeStart;
502
+ let end = nodeEnd;
503
+ // Look for preceding comma and whitespace
504
+ let checkStart = nodeStart;
505
+ while (checkStart > 0 && /[\s\n]/.test(fullText[checkStart - 1])) {
506
+ checkStart--;
507
+ }
508
+ if (checkStart > 0 && fullText[checkStart - 1] === ',') {
509
+ start = checkStart - 1;
510
+ // Include any whitespace before the comma
511
+ while (start > 0 && /[\s\n]/.test(fullText[start - 1])) {
512
+ start--;
513
+ }
514
+ }
515
+ else {
516
+ // Look for trailing comma and whitespace
517
+ let checkEnd = nodeEnd;
518
+ while (checkEnd < fullText.length && /[\s\n]/.test(fullText[checkEnd])) {
519
+ checkEnd++;
520
+ }
521
+ if (checkEnd < fullText.length && fullText[checkEnd] === ',') {
522
+ end = checkEnd + 1;
523
+ // Include whitespace after comma
524
+ while (end < fullText.length && /[\s\n]/.test(fullText[end])) {
525
+ end++;
526
+ }
527
+ }
528
+ }
529
+ changes.push({
530
+ start,
531
+ end,
532
+ replacement: '',
533
+ });
534
+ importsToRemove.add(originalName);
535
+ return; // Don't process this node further
536
+ }
537
+ }
538
+ }
539
+ // Special handling for provider function calls
540
+ if (ts.isCallExpression(parent) &&
541
+ parent.expression === node &&
542
+ (originalName === 'provideDialog' || originalName === 'provideBottomSheet')) {
543
+ // For provider functions, we need to check if provideOverlay() already exists
544
+ // in the same array to avoid duplicates
545
+ const arrayParent = parent.parent;
546
+ if (ts.isArrayLiteralExpression(arrayParent)) {
547
+ // Count provider calls that will map to provideOverlay
548
+ const providerCallsWithSameReplacement = arrayParent.elements.filter((element) => {
549
+ if (ts.isCallExpression(element) && ts.isIdentifier(element.expression)) {
550
+ const elemName = element.expression.text;
551
+ if (isEthleteSymbol(elemName)) {
552
+ const elemOriginalName = getOriginalSymbolName(elemName);
553
+ if (elemOriginalName && elemOriginalName in SYMBOL_REPLACEMENTS) {
554
+ return (SYMBOL_REPLACEMENTS[elemOriginalName] === 'provideOverlay');
555
+ }
556
+ }
557
+ }
558
+ return false;
559
+ });
560
+ if (providerCallsWithSameReplacement.length > 1) {
561
+ const firstCall = providerCallsWithSameReplacement[0];
562
+ if (firstCall !== parent) {
563
+ // Remove this provider call entirely (including call expression)
564
+ const callStart = parent.getStart(sourceFile);
565
+ let callEnd = parent.getEnd();
566
+ const fullText = sourceFile.getFullText();
567
+ // Check for trailing comma
568
+ let end = callEnd;
569
+ while (end < fullText.length &&
570
+ (fullText[end] === ' ' || fullText[end] === '\t' || fullText[end] === '\n')) {
571
+ end++;
572
+ }
573
+ if (fullText[end] === ',') {
574
+ callEnd = end + 1;
575
+ while (callEnd < fullText.length &&
576
+ (fullText[callEnd] === ' ' || fullText[callEnd] === '\t' || fullText[callEnd] === '\n')) {
577
+ callEnd++;
578
+ }
579
+ }
580
+ else {
581
+ // Check for preceding comma
582
+ let start = callStart;
583
+ while (start > 0 &&
584
+ (fullText[start - 1] === ' ' || fullText[start - 1] === '\t' || fullText[start - 1] === '\n')) {
585
+ start--;
586
+ }
587
+ if (start > 0 && fullText[start - 1] === ',') {
588
+ changes.push({
589
+ start: start - 1,
590
+ end: callEnd,
591
+ replacement: '',
592
+ });
593
+ importsToRemove.add(originalName);
594
+ return;
595
+ }
596
+ }
597
+ changes.push({
598
+ start: callStart,
599
+ end: callEnd,
600
+ replacement: '',
601
+ });
602
+ importsToRemove.add(originalName);
603
+ return;
604
+ }
605
+ }
606
+ }
607
+ }
608
+ changes.push({
609
+ start: node.getStart(sourceFile),
610
+ end: node.getEnd(),
611
+ replacement,
612
+ });
613
+ importsToAdd.add(replacement);
614
+ importsToRemove.add(originalName);
615
+ }
616
+ }
617
+ }
618
+ }
619
+ // Handle imports
620
+ if (ts.isImportDeclaration(node)) {
621
+ const moduleSpecifier = node.moduleSpecifier;
622
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
623
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
624
+ node.importClause.namedBindings.elements.forEach((element) => {
625
+ const importedName = element.propertyName?.text || element.name.text;
626
+ // Handle symbols to replace
627
+ if (importedName in SYMBOL_REPLACEMENTS) {
628
+ importsToRemove.add(importedName);
629
+ importsToAdd.add(SYMBOL_REPLACEMENTS[importedName]);
630
+ }
631
+ // Handle symbols to remove
632
+ if (SYMBOLS_TO_REMOVE.includes(importedName)) {
633
+ importsToRemove.add(importedName);
634
+ }
635
+ });
636
+ }
637
+ }
638
+ }
639
+ // Handle service.open() calls
640
+ if (ts.isCallExpression(node) &&
641
+ ts.isPropertyAccessExpression(node.expression) &&
642
+ node.expression.name.text === 'open') {
643
+ const objectExpr = node.expression.expression;
644
+ // Check if this is a call on one of our services
645
+ let variableName;
646
+ if (ts.isIdentifier(objectExpr)) {
647
+ variableName = objectExpr.text;
648
+ }
649
+ else if (ts.isPropertyAccessExpression(objectExpr)) {
650
+ // Handle this.#dialogService, this._dialogService, or this.dialogService
651
+ if (ts.isIdentifier(objectExpr.name)) {
652
+ variableName = objectExpr.name.text;
653
+ }
654
+ else if (ts.isPrivateIdentifier(objectExpr.name)) {
655
+ variableName = objectExpr.name.text; // Includes the # prefix
656
+ }
657
+ }
658
+ // Look up the original service type from our map
659
+ // Try with and without underscore prefix
660
+ let originalServiceType;
661
+ if (variableName) {
662
+ originalServiceType = serviceVariableMap.get(variableName);
663
+ // If not found and starts with underscore, try without underscore
664
+ if (!originalServiceType && variableName.startsWith('_')) {
665
+ originalServiceType = serviceVariableMap.get(variableName.substring(1));
666
+ }
667
+ // If not found and doesn't start with underscore, try with underscore
668
+ if (!originalServiceType && !variableName.startsWith('_')) {
669
+ originalServiceType = serviceVariableMap.get('_' + variableName);
670
+ }
671
+ }
672
+ if (originalServiceType) {
673
+ if (originalServiceType === 'DialogService') {
674
+ handleDialogServiceOpen(node, sourceFile, changes, importsToAdd);
675
+ }
676
+ else if (originalServiceType === 'BottomSheetService') {
677
+ handleBottomSheetServiceOpen(node, sourceFile, changes, importsToAdd);
678
+ }
679
+ else if (originalServiceType === 'DynamicOverlayService') {
680
+ handleDynamicOverlayServiceOpen(node, sourceFile, changes, importsToAdd);
681
+ }
682
+ }
683
+ }
684
+ ts.forEachChild(node, visit);
685
+ }
686
+ visit(sourceFile);
687
+ // Apply changes
688
+ if (changes.length === 0 && importsToRemove.size === 0) {
689
+ return;
690
+ }
691
+ // Sort changes in reverse order to maintain positions
692
+ changes.sort((a, b) => b.start - a.start);
693
+ // Validate no overlapping changes
694
+ for (let i = 0; i < changes.length - 1; i++) {
695
+ const current = changes[i];
696
+ const next = changes[i + 1];
697
+ if (current.start < next.end) {
698
+ logger.error(`Overlapping changes detected in ${filePath}!`);
699
+ logger.error(`Change 1: ${current.start}-${current.end} = "${current.replacement}"`);
700
+ logger.error(`Change 2: ${next.start}-${next.end} = "${next.replacement}"`);
701
+ throw new Error('Overlapping changes detected - this will corrupt the file');
702
+ }
703
+ }
704
+ let result = content;
705
+ for (const change of changes) {
706
+ result = result.substring(0, change.start) + change.replacement + result.substring(change.end);
707
+ }
708
+ // Update imports
709
+ result = updateImports(result, importsToAdd, importsToRemove);
710
+ tree.write(filePath, result);
711
+ }
712
+ function processHtmlFile(filePath, tree) {
713
+ let content = tree.read(filePath, 'utf-8');
714
+ if (!content)
715
+ return;
716
+ let modified = false;
717
+ // Remove et-bottom-sheet-drag-handle elements
718
+ const dragHandleElementRegex = /<et-bottom-sheet-drag-handle\s*\/?>|<et-bottom-sheet-drag-handle[^>]*>.*?<\/et-bottom-sheet-drag-handle>/gs;
719
+ if (dragHandleElementRegex.test(content)) {
720
+ content = content.replace(dragHandleElementRegex, '');
721
+ modified = true;
722
+ }
723
+ // Remove elements with etBottomSheetDragHandle directive (both camelCase and kebab-case)
724
+ // This regex matches any opening tag that contains the directive and removes the entire element
725
+ const dragHandleDirectivePatterns = [
726
+ // Match self-closing tags: <div etBottomSheetDragHandle />
727
+ /<(\w+)([^>]*?\s+etBottomSheetDragHandle\s*[^>]*?)\/>/gs,
728
+ /<(\w+)([^>]*?\s+et-bottom-sheet-drag-handle\s*[^>]*?)\/>/gs,
729
+ // Match elements with opening and closing tags: <div etBottomSheetDragHandle>...</div>
730
+ /<(\w+)([^>]*?\s+etBottomSheetDragHandle\s*[^>]*?)>(.*?)<\/\1>/gs,
731
+ /<(\w+)([^>]*?\s+et-bottom-sheet-drag-handle\s*[^>]*?)>(.*?)<\/\1>/gs,
732
+ ];
733
+ for (const pattern of dragHandleDirectivePatterns) {
734
+ if (pattern.test(content)) {
735
+ content = content.replace(pattern, '');
736
+ modified = true;
737
+ }
738
+ }
739
+ // Migrate title directives (attribute only)
740
+ const titleAttributeMappings = [
741
+ { from: 'etBottomSheetTitle', to: 'etOverlayTitle' },
742
+ { from: 'etDialogTitle', to: 'etOverlayTitle' },
743
+ { from: 'etDynamicOverlayTitle', to: 'etOverlayTitle' },
744
+ { from: 'et-bottom-sheet-title', to: 'et-overlay-title' },
745
+ { from: 'et-dialog-title', to: 'et-overlay-title' },
746
+ { from: 'et-dynamic-overlay-title', to: 'et-overlay-title' },
747
+ ];
748
+ for (const { from, to } of titleAttributeMappings) {
749
+ // Only match as attributes, not as element tags
750
+ const directiveRegex = new RegExp(`\\b${from}\\b`, 'g');
751
+ if (directiveRegex.test(content)) {
752
+ content = content.replace(directiveRegex, to);
753
+ modified = true;
754
+ }
755
+ }
756
+ // Migrate close directives (attribute only)
757
+ const closeAttributeMappings = [
758
+ { from: 'et-dialog-close', to: 'etOverlayClose' },
759
+ { from: 'etDialogClose', to: 'etOverlayClose' },
760
+ ];
761
+ for (const { from, to } of closeAttributeMappings) {
762
+ const regex = new RegExp(`\\b${from}\\b`, 'g');
763
+ if (regex.test(content)) {
764
+ content = content.replace(regex, to);
765
+ modified = true;
766
+ }
767
+ }
768
+ if (modified) {
769
+ tree.write(filePath, content);
770
+ }
771
+ }
772
+ function updateImports(content, importsToAdd, importsToRemove) {
773
+ const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
774
+ let result = content;
775
+ const changes = [];
776
+ function visit(node) {
777
+ if (ts.isImportDeclaration(node)) {
778
+ const moduleSpecifier = node.moduleSpecifier;
779
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/cdk') {
780
+ if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
781
+ const existingImports = new Set(node.importClause.namedBindings.elements.map((el) => el.name.text));
782
+ // Remove imports that need to be removed
783
+ importsToRemove.forEach((imp) => existingImports.delete(imp));
784
+ // Add new imports
785
+ importsToAdd.forEach((imp) => existingImports.add(imp));
786
+ // Build new import statement
787
+ const sortedImports = Array.from(existingImports).sort();
788
+ const newImports = sortedImports.join(', ');
789
+ if (sortedImports.length > 0) {
790
+ const replacement = `import { ${newImports} } from '@ethlete/cdk';`;
791
+ changes.push({
792
+ start: node.getStart(sourceFile),
793
+ end: node.getEnd(),
794
+ replacement,
795
+ });
796
+ }
797
+ else {
798
+ // Remove the entire import if no imports remain
799
+ changes.push({
800
+ start: node.getFullStart(),
801
+ end: node.getEnd() + 1, // Include newline
802
+ replacement: '',
803
+ });
804
+ }
805
+ }
806
+ }
807
+ }
808
+ ts.forEachChild(node, visit);
809
+ }
810
+ visit(sourceFile);
811
+ // Apply changes in reverse order
812
+ changes.sort((a, b) => b.start - a.start);
813
+ for (const change of changes) {
814
+ result = result.substring(0, change.start) + change.replacement + result.substring(change.end);
815
+ }
816
+ return result;
817
+ }
818
+ function handleDialogServiceOpen(node, sourceFile, changes, importsToAdd) {
819
+ importsToAdd.add('dialogOverlayStrategy');
820
+ // If no config argument, add one with strategies
821
+ if (node.arguments.length === 1) {
822
+ changes.push({
823
+ start: node.arguments[0].getEnd(),
824
+ end: node.arguments[0].getEnd(),
825
+ replacement: ', { strategies: dialogOverlayStrategy() }',
826
+ });
827
+ return;
828
+ }
829
+ // If config exists, transform it
830
+ const configArg = node.arguments[1];
831
+ if (configArg && ts.isObjectLiteralExpression(configArg)) {
832
+ transformConfigWithStyleProperties(configArg, sourceFile, changes, 'dialogOverlayStrategy');
833
+ }
834
+ }
835
+ function handleBottomSheetServiceOpen(node, sourceFile, changes, importsToAdd) {
836
+ importsToAdd.add('bottomSheetOverlayStrategy');
837
+ // If no config argument, add one with strategies
838
+ if (node.arguments.length === 1) {
839
+ changes.push({
840
+ start: node.arguments[0].getEnd(),
841
+ end: node.arguments[0].getEnd(),
842
+ replacement: ', { strategies: bottomSheetOverlayStrategy() }',
843
+ });
844
+ return;
845
+ }
846
+ // If config exists, transform it
847
+ const configArg = node.arguments[1];
848
+ if (configArg && ts.isObjectLiteralExpression(configArg)) {
849
+ transformConfigWithStyleProperties(configArg, sourceFile, changes, 'bottomSheetOverlayStrategy');
850
+ }
851
+ }
852
+ function handleDynamicOverlayServiceOpen(node, sourceFile, changes, importsToAdd) {
853
+ importsToAdd.add('transformingBottomSheetToDialogOverlayStrategy');
854
+ const configArg = node.arguments[1];
855
+ if (!configArg || !ts.isObjectLiteralExpression(configArg)) {
856
+ return;
857
+ }
858
+ // Extract properties from the config
859
+ let isDialogFrom;
860
+ let bottomSheetConfig;
861
+ let dialogConfig;
862
+ const otherProperties = [];
863
+ configArg.properties.forEach((prop) => {
864
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
865
+ const propName = prop.name.text;
866
+ if (propName === 'isDialogFrom') {
867
+ isDialogFrom = prop.initializer.getText(sourceFile).replace(/['"]/g, '');
868
+ }
869
+ else if (propName === 'bottomSheetConfig' && ts.isObjectLiteralExpression(prop.initializer)) {
870
+ bottomSheetConfig = prop.initializer;
871
+ }
872
+ else if (propName === 'dialogConfig' && ts.isObjectLiteralExpression(prop.initializer)) {
873
+ dialogConfig = prop.initializer;
874
+ }
875
+ else {
876
+ otherProperties.push(prop);
877
+ }
878
+ }
879
+ });
880
+ // Extract data and style properties from configs
881
+ let dataPropertyText;
882
+ const bottomSheetStyleProps = [];
883
+ const bottomSheetOtherProps = new Map(); // Use Map to deduplicate
884
+ const dialogStyleProps = [];
885
+ const dialogOtherProps = new Map(); // Use Map to deduplicate
886
+ if (bottomSheetConfig) {
887
+ bottomSheetConfig.properties.forEach((prop) => {
888
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
889
+ const propName = prop.name.text;
890
+ if (propName === 'data') {
891
+ // Store the full property assignment text
892
+ dataPropertyText = prop.getText(sourceFile);
893
+ }
894
+ else if (propName === 'scrollStrategy') {
895
+ // Skip scrollStrategy - it's no longer supported
896
+ }
897
+ else if (STYLE_PROPERTIES.includes(propName)) {
898
+ bottomSheetStyleProps.push(prop);
899
+ }
900
+ else {
901
+ bottomSheetOtherProps.set(propName, prop);
902
+ }
903
+ }
904
+ else if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === 'data') {
905
+ // Handle shorthand property: { data }
906
+ dataPropertyText = prop.getText(sourceFile);
907
+ }
908
+ });
909
+ }
910
+ if (dialogConfig) {
911
+ dialogConfig.properties.forEach((prop) => {
912
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
913
+ const propName = prop.name.text;
914
+ if (propName === 'scrollStrategy') {
915
+ // Skip scrollStrategy - it's no longer supported
916
+ }
917
+ else if (STYLE_PROPERTIES.includes(propName)) {
918
+ dialogStyleProps.push(prop);
919
+ }
920
+ else if (propName !== 'data') {
921
+ // Skip data in dialogConfig as we already got it from bottomSheetConfig
922
+ // Only add if not already in bottomSheetOtherProps (deduplicate)
923
+ if (!bottomSheetOtherProps.has(propName)) {
924
+ dialogOtherProps.set(propName, prop);
925
+ }
926
+ }
927
+ }
928
+ });
929
+ }
930
+ // Build the new config
931
+ const newConfigParts = [];
932
+ // Add data property if exists
933
+ if (dataPropertyText) {
934
+ newConfigParts.push(dataPropertyText);
935
+ }
936
+ // Add other properties from main config
937
+ otherProperties.forEach((prop) => {
938
+ newConfigParts.push(prop.getText(sourceFile));
939
+ });
940
+ // Add other properties from nested configs (non-style, non-data) - deduplicated
941
+ bottomSheetOtherProps.forEach((prop) => {
942
+ newConfigParts.push(prop.getText(sourceFile));
943
+ });
944
+ dialogOtherProps.forEach((prop) => {
945
+ newConfigParts.push(prop.getText(sourceFile));
946
+ });
947
+ // Build the strategy config
948
+ const strategyConfigParts = [];
949
+ // Add bottomSheet config (only if there are style properties)
950
+ if (bottomSheetStyleProps.length > 0) {
951
+ const bottomSheetConfigStr = bottomSheetStyleProps
952
+ .map((prop) => `${prop.name.getText(sourceFile)}: ${prop.initializer.getText(sourceFile)}`)
953
+ .join(',\n ');
954
+ strategyConfigParts.push(`bottomSheet: {\n ${bottomSheetConfigStr}\n }`);
955
+ }
956
+ // Add dialog config (only if there are style properties)
957
+ if (dialogStyleProps.length > 0) {
958
+ const dialogConfigStr = dialogStyleProps
959
+ .map((prop) => `${prop.name.getText(sourceFile)}: ${prop.initializer.getText(sourceFile)}`)
960
+ .join(',\n ');
961
+ strategyConfigParts.push(`dialog: {\n ${dialogConfigStr}\n }`);
962
+ }
963
+ // Add breakpoint
964
+ if (isDialogFrom) {
965
+ strategyConfigParts.push(`breakpoint: '${isDialogFrom}'`);
966
+ }
967
+ const strategyConfig = strategyConfigParts.length > 0 ? `{\n ${strategyConfigParts.join(',\n ')}\n }` : '()';
968
+ newConfigParts.push(`strategies: transformingBottomSheetToDialogOverlayStrategy(${strategyConfig})`);
969
+ // Replace the entire config
970
+ const newConfig = `{\n ${newConfigParts.join(',\n ')}\n }`;
971
+ changes.push({
972
+ start: configArg.getStart(sourceFile),
973
+ end: configArg.getEnd(),
974
+ replacement: newConfig,
975
+ });
976
+ }
977
+ function transformConfigWithStyleProperties(configArg, sourceFile, changes, strategyFunction) {
978
+ const styleProps = [];
979
+ const otherProps = [];
980
+ configArg.properties.forEach((prop) => {
981
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
982
+ const propName = prop.name.text;
983
+ if (propName === 'scrollStrategy') {
984
+ // Skip scrollStrategy - it's no longer supported
985
+ }
986
+ else if (STYLE_PROPERTIES.includes(propName)) {
987
+ styleProps.push(prop);
988
+ }
989
+ else {
990
+ otherProps.push(prop);
991
+ }
992
+ }
993
+ else if (ts.isShorthandPropertyAssignment(prop)) {
994
+ const propName = prop.name.text;
995
+ if (propName === 'scrollStrategy') {
996
+ // Skip scrollStrategy - it's no longer supported
997
+ }
998
+ else {
999
+ otherProps.push(prop);
1000
+ }
1001
+ }
1002
+ else {
1003
+ otherProps.push(prop);
1004
+ }
1005
+ });
1006
+ // If no style properties, just add strategies
1007
+ if (styleProps.length === 0) {
1008
+ // Add strategies property to existing config
1009
+ const lastProp = configArg.properties[configArg.properties.length - 1];
1010
+ if (lastProp) {
1011
+ changes.push({
1012
+ start: lastProp.getEnd(),
1013
+ end: lastProp.getEnd(),
1014
+ replacement: `,\n strategies: ${strategyFunction}()`,
1015
+ });
1016
+ }
1017
+ return;
1018
+ }
1019
+ // Build new config with style props moved to strategy
1020
+ const newConfigParts = [];
1021
+ // Add non-style properties
1022
+ otherProps.forEach((prop) => {
1023
+ newConfigParts.push(prop.getText(sourceFile));
1024
+ });
1025
+ // Build strategy config
1026
+ const strategyConfigParts = styleProps.map((prop) => {
1027
+ return `${prop.name.getText(sourceFile)}: ${prop.initializer.getText(sourceFile)}`;
1028
+ });
1029
+ const strategyConfig = `${strategyFunction}({\n ${strategyConfigParts.join(',\n ')}\n })`;
1030
+ newConfigParts.push(`strategies: ${strategyConfig}`);
1031
+ // Replace the entire config object
1032
+ const newConfig = `{\n ${newConfigParts.join(',\n ')}\n }`;
1033
+ changes.push({
1034
+ start: configArg.getStart(sourceFile),
1035
+ end: configArg.getEnd(),
1036
+ replacement: newConfig,
1037
+ });
1038
+ }
1039
+ //# sourceMappingURL=dialog-bottom-sheet.js.map