@expeed/ngx-data-mapper 1.0.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,2475 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, Injectable, EventEmitter, inject, ViewChildren, ViewChild, Output, Input, Component, HostListener } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i3 from '@angular/material/icon';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import * as i2$1 from '@angular/material/button';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import * as i8 from '@angular/material/tooltip';
10
+ import { MatTooltipModule } from '@angular/material/tooltip';
11
+ import * as i2 from '@angular/forms';
12
+ import { FormsModule } from '@angular/forms';
13
+ import * as i5 from '@angular/material/select';
14
+ import { MatSelectModule } from '@angular/material/select';
15
+ import * as i5$1 from '@angular/material/input';
16
+ import { MatInputModule } from '@angular/material/input';
17
+ import { MatFormFieldModule } from '@angular/material/form-field';
18
+ import * as i6 from '@angular/material/radio';
19
+ import { MatRadioModule } from '@angular/material/radio';
20
+ import * as i7 from '@angular/material/slide-toggle';
21
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
22
+ import * as i9 from '@angular/material/divider';
23
+ import { MatDividerModule } from '@angular/material/divider';
24
+ import * as i6$1 from '@angular/material/datepicker';
25
+ import { MatDatepickerModule } from '@angular/material/datepicker';
26
+ import { MatNativeDateModule } from '@angular/material/core';
27
+ import * as i7$1 from '@angular/material/menu';
28
+ import { MatMenuModule } from '@angular/material/menu';
29
+ import { DragDropModule } from '@angular/cdk/drag-drop';
30
+
31
+ class MappingService {
32
+ mappings = signal([], ...(ngDevMode ? [{ debugName: "mappings" }] : []));
33
+ arrayMappings = signal([], ...(ngDevMode ? [{ debugName: "arrayMappings" }] : []));
34
+ arrayToObjectMappings = signal([], ...(ngDevMode ? [{ debugName: "arrayToObjectMappings" }] : []));
35
+ defaultValues = signal([], ...(ngDevMode ? [{ debugName: "defaultValues" }] : []));
36
+ selectedMappingId = signal(null, ...(ngDevMode ? [{ debugName: "selectedMappingId" }] : []));
37
+ dragState = signal({
38
+ isDragging: false,
39
+ sourceField: null,
40
+ startPoint: null,
41
+ currentPoint: null,
42
+ }, ...(ngDevMode ? [{ debugName: "dragState" }] : []));
43
+ allMappings = computed(() => this.mappings(), ...(ngDevMode ? [{ debugName: "allMappings" }] : []));
44
+ allArrayMappings = computed(() => this.arrayMappings(), ...(ngDevMode ? [{ debugName: "allArrayMappings" }] : []));
45
+ allArrayToObjectMappings = computed(() => this.arrayToObjectMappings(), ...(ngDevMode ? [{ debugName: "allArrayToObjectMappings" }] : []));
46
+ allDefaultValues = computed(() => this.defaultValues(), ...(ngDevMode ? [{ debugName: "allDefaultValues" }] : []));
47
+ selectedMapping = computed(() => {
48
+ const id = this.selectedMappingId();
49
+ return this.mappings().find((m) => m.id === id) || null;
50
+ }, ...(ngDevMode ? [{ debugName: "selectedMapping" }] : []));
51
+ currentDragState = computed(() => this.dragState(), ...(ngDevMode ? [{ debugName: "currentDragState" }] : []));
52
+ generateId() {
53
+ return `mapping-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
54
+ }
55
+ startDrag(field, startPoint) {
56
+ this.dragState.set({
57
+ isDragging: true,
58
+ sourceField: field,
59
+ startPoint,
60
+ currentPoint: startPoint,
61
+ });
62
+ }
63
+ updateDragPosition(currentPoint) {
64
+ this.dragState.update((state) => ({
65
+ ...state,
66
+ currentPoint,
67
+ }));
68
+ }
69
+ endDrag() {
70
+ this.dragState.set({
71
+ isDragging: false,
72
+ sourceField: null,
73
+ startPoint: null,
74
+ currentPoint: null,
75
+ });
76
+ }
77
+ createMapping(sourceFields, targetField, transformation) {
78
+ // Check if this is an array-to-array mapping
79
+ const sourceField = sourceFields[0];
80
+ if (sourceField.type === 'array' && targetField.type === 'array') {
81
+ return this.createArrayMapping(sourceField, targetField);
82
+ }
83
+ // Check if this is an array-to-object mapping
84
+ if (sourceField.type === 'array' && targetField.type === 'object') {
85
+ return this.createArrayToObjectMapping(sourceField, targetField);
86
+ }
87
+ // Check if fields are within arrays and need array context
88
+ const arrayMappingId = this.findOrCreateArrayContext(sourceField, targetField);
89
+ // Check if fields need array-to-object context
90
+ const arrayToObjectMappingId = this.findOrCreateArrayToObjectContext(sourceField, targetField);
91
+ const existingMapping = this.mappings().find((m) => m.targetField.id === targetField.id);
92
+ if (existingMapping) {
93
+ // Add source fields to existing mapping (for concat scenarios)
94
+ const updatedMapping = {
95
+ ...existingMapping,
96
+ sourceFields: [
97
+ ...existingMapping.sourceFields,
98
+ ...sourceFields.filter((sf) => !existingMapping.sourceFields.some((esf) => esf.id === sf.id)),
99
+ ],
100
+ transformation: existingMapping.sourceFields.length + sourceFields.length > 1
101
+ ? { type: 'concat', separator: ' ', ...transformation }
102
+ : transformation || { type: 'direct' },
103
+ };
104
+ this.mappings.update((mappings) => mappings.map((m) => (m.id === existingMapping.id ? updatedMapping : m)));
105
+ return updatedMapping;
106
+ }
107
+ const newMapping = {
108
+ id: this.generateId(),
109
+ sourceFields,
110
+ targetField,
111
+ transformation: transformation || { type: 'direct' },
112
+ isArrayMapping: false, // Only true for array-to-array connections
113
+ arrayMappingId, // Links to parent array mapping if within array context
114
+ isArrayToObjectMapping: false,
115
+ arrayToObjectMappingId, // Links to parent array-to-object mapping if applicable
116
+ };
117
+ this.mappings.update((mappings) => [...mappings, newMapping]);
118
+ // If this is part of an array mapping, add to itemMappings
119
+ if (arrayMappingId) {
120
+ this.arrayMappings.update((ams) => ams.map((am) => am.id === arrayMappingId
121
+ ? { ...am, itemMappings: [...am.itemMappings, newMapping] }
122
+ : am));
123
+ }
124
+ // If this is part of an array-to-object mapping, add to itemMappings
125
+ if (arrayToObjectMappingId) {
126
+ this.arrayToObjectMappings.update((ams) => ams.map((am) => am.id === arrayToObjectMappingId
127
+ ? { ...am, itemMappings: [...am.itemMappings, newMapping] }
128
+ : am));
129
+ }
130
+ return newMapping;
131
+ }
132
+ createArrayMapping(sourceArray, targetArray) {
133
+ // Check if array mapping already exists
134
+ const existingArrayMapping = this.arrayMappings().find((am) => am.sourceArray.id === sourceArray.id && am.targetArray.id === targetArray.id);
135
+ if (existingArrayMapping) {
136
+ // Return a dummy field mapping representing the array mapping
137
+ const existing = this.mappings().find(m => m.id === existingArrayMapping.id);
138
+ if (existing)
139
+ return existing;
140
+ }
141
+ const arrayMapping = {
142
+ id: this.generateId(),
143
+ sourceArray,
144
+ targetArray,
145
+ itemMappings: [],
146
+ };
147
+ this.arrayMappings.update((ams) => [...ams, arrayMapping]);
148
+ // Also create a field mapping to visualize the array connection
149
+ const fieldMapping = {
150
+ id: arrayMapping.id,
151
+ sourceFields: [sourceArray],
152
+ targetField: targetArray,
153
+ transformation: { type: 'direct' },
154
+ isArrayMapping: true,
155
+ };
156
+ this.mappings.update((mappings) => [...mappings, fieldMapping]);
157
+ return fieldMapping;
158
+ }
159
+ createArrayToObjectMapping(sourceArray, targetObject) {
160
+ // Check if array-to-object mapping already exists
161
+ const existingMapping = this.arrayToObjectMappings().find((am) => am.sourceArray.id === sourceArray.id && am.targetObject.id === targetObject.id);
162
+ if (existingMapping) {
163
+ const existing = this.mappings().find(m => m.id === existingMapping.id);
164
+ if (existing)
165
+ return existing;
166
+ }
167
+ const arrayToObjectMapping = {
168
+ id: this.generateId(),
169
+ sourceArray,
170
+ targetObject,
171
+ selector: { mode: 'first' }, // Default to first item
172
+ itemMappings: [],
173
+ };
174
+ this.arrayToObjectMappings.update((ams) => [...ams, arrayToObjectMapping]);
175
+ // Create a field mapping to visualize the connection
176
+ const fieldMapping = {
177
+ id: arrayToObjectMapping.id,
178
+ sourceFields: [sourceArray],
179
+ targetField: targetObject,
180
+ transformation: { type: 'direct' },
181
+ isArrayToObjectMapping: true,
182
+ };
183
+ this.mappings.update((mappings) => [...mappings, fieldMapping]);
184
+ return fieldMapping;
185
+ }
186
+ findOrCreateArrayContext(sourceField, targetField) {
187
+ // If both fields are array items, check if an array mapping exists
188
+ if (sourceField.isArrayItem && targetField.isArrayItem) {
189
+ const existingArrayMapping = this.arrayMappings().find((am) => am.sourceArray.path === sourceField.parentArrayPath &&
190
+ am.targetArray.path === targetField.parentArrayPath);
191
+ if (existingArrayMapping) {
192
+ return existingArrayMapping.id;
193
+ }
194
+ }
195
+ return undefined;
196
+ }
197
+ findOrCreateArrayToObjectContext(sourceField, targetField) {
198
+ // If source is array item and target is within an object (not array item)
199
+ if (sourceField.isArrayItem && !targetField.isArrayItem && targetField.path.includes('.')) {
200
+ // Find the parent object path
201
+ const targetParts = targetField.path.split('.');
202
+ const parentObjectPath = targetParts.slice(0, -1).join('.');
203
+ const existingMapping = this.arrayToObjectMappings().find((am) => am.sourceArray.path === sourceField.parentArrayPath &&
204
+ am.targetObject.path === parentObjectPath);
205
+ if (existingMapping) {
206
+ return existingMapping.id;
207
+ }
208
+ }
209
+ return undefined;
210
+ }
211
+ getArrayMapping(id) {
212
+ return this.arrayMappings().find((am) => am.id === id);
213
+ }
214
+ getArrayMappingForField(field) {
215
+ if (!field.parentArrayPath)
216
+ return undefined;
217
+ return this.arrayMappings().find((am) => am.sourceArray.path === field.parentArrayPath ||
218
+ am.targetArray.path === field.parentArrayPath);
219
+ }
220
+ removeArrayMapping(arrayMappingId) {
221
+ // Remove the array mapping
222
+ this.arrayMappings.update((ams) => ams.filter((am) => am.id !== arrayMappingId));
223
+ // Remove all field mappings associated with this array mapping
224
+ this.mappings.update((mappings) => mappings.filter((m) => m.arrayMappingId !== arrayMappingId && m.id !== arrayMappingId));
225
+ }
226
+ updateArrayFilter(arrayMappingId, filter) {
227
+ this.arrayMappings.update((ams) => ams.map((am) => am.id === arrayMappingId ? { ...am, filter } : am));
228
+ }
229
+ getArrayToObjectMapping(id) {
230
+ return this.arrayToObjectMappings().find((am) => am.id === id);
231
+ }
232
+ updateArrayToObjectSelector(mappingId, selector) {
233
+ this.arrayToObjectMappings.update((ams) => ams.map((am) => am.id === mappingId ? { ...am, selector } : am));
234
+ }
235
+ removeArrayToObjectMapping(mappingId) {
236
+ // Remove the array-to-object mapping
237
+ this.arrayToObjectMappings.update((ams) => ams.filter((am) => am.id !== mappingId));
238
+ // Remove all field mappings associated with this mapping
239
+ this.mappings.update((mappings) => mappings.filter((m) => m.arrayToObjectMappingId !== mappingId && m.id !== mappingId));
240
+ }
241
+ updateMapping(mappingId, updates) {
242
+ this.mappings.update((mappings) => mappings.map((m) => (m.id === mappingId ? { ...m, ...updates } : m)));
243
+ }
244
+ updateTransformation(mappingId, transformation) {
245
+ this.mappings.update((mappings) => mappings.map((m) => m.id === mappingId ? { ...m, transformation } : m));
246
+ }
247
+ removeMapping(mappingId) {
248
+ this.mappings.update((mappings) => mappings.filter((m) => m.id !== mappingId));
249
+ if (this.selectedMappingId() === mappingId) {
250
+ this.selectedMappingId.set(null);
251
+ }
252
+ }
253
+ removeSourceFromMapping(mappingId, sourceFieldId) {
254
+ const mapping = this.mappings().find((m) => m.id === mappingId);
255
+ if (!mapping)
256
+ return;
257
+ if (mapping.sourceFields.length <= 1) {
258
+ this.removeMapping(mappingId);
259
+ }
260
+ else {
261
+ this.mappings.update((mappings) => mappings.map((m) => m.id === mappingId
262
+ ? {
263
+ ...m,
264
+ sourceFields: m.sourceFields.filter((sf) => sf.id !== sourceFieldId),
265
+ transformation: m.sourceFields.length - 1 === 1
266
+ ? { type: 'direct' }
267
+ : m.transformation,
268
+ }
269
+ : m));
270
+ }
271
+ }
272
+ selectMapping(mappingId) {
273
+ this.selectedMappingId.set(mappingId);
274
+ }
275
+ getMappingForTarget(targetFieldId) {
276
+ return this.mappings().find((m) => m.targetField.id === targetFieldId);
277
+ }
278
+ getMappingsForSource(sourceFieldId) {
279
+ return this.mappings().filter((m) => m.sourceFields.some((sf) => sf.id === sourceFieldId));
280
+ }
281
+ clearAllMappings() {
282
+ this.mappings.set([]);
283
+ this.arrayMappings.set([]);
284
+ this.arrayToObjectMappings.set([]);
285
+ this.defaultValues.set([]);
286
+ this.selectedMappingId.set(null);
287
+ }
288
+ // Default value methods
289
+ setDefaultValue(targetField, value) {
290
+ const valueType = this.getValueType(targetField.type);
291
+ const existingDefault = this.defaultValues().find(d => d.targetField.id === targetField.id);
292
+ if (existingDefault) {
293
+ const updated = { ...existingDefault, value, valueType };
294
+ this.defaultValues.update(dv => dv.map(d => d.id === existingDefault.id ? updated : d));
295
+ return updated;
296
+ }
297
+ const newDefault = {
298
+ id: this.generateId(),
299
+ targetField,
300
+ value,
301
+ valueType,
302
+ };
303
+ this.defaultValues.update(dv => [...dv, newDefault]);
304
+ return newDefault;
305
+ }
306
+ getDefaultValue(targetFieldId) {
307
+ return this.defaultValues().find(d => d.targetField.id === targetFieldId);
308
+ }
309
+ removeDefaultValue(targetFieldId) {
310
+ this.defaultValues.update(dv => dv.filter(d => d.targetField.id !== targetFieldId));
311
+ }
312
+ hasDefaultValue(targetFieldId) {
313
+ return this.defaultValues().some(d => d.targetField.id === targetFieldId);
314
+ }
315
+ getValueType(fieldType) {
316
+ switch (fieldType) {
317
+ case 'number': return 'number';
318
+ case 'boolean': return 'boolean';
319
+ case 'date': return 'date';
320
+ default: return 'string';
321
+ }
322
+ }
323
+ exportMappings() {
324
+ const exportData = {
325
+ mappings: this.mappings(),
326
+ arrayMappings: this.arrayMappings(),
327
+ arrayToObjectMappings: this.arrayToObjectMappings(),
328
+ defaultValues: this.defaultValues(),
329
+ };
330
+ return JSON.stringify(exportData, null, 2);
331
+ }
332
+ importMappings(json) {
333
+ try {
334
+ const data = JSON.parse(json);
335
+ if (data.mappings) {
336
+ this.mappings.set(data.mappings);
337
+ this.arrayMappings.set(data.arrayMappings || []);
338
+ this.arrayToObjectMappings.set(data.arrayToObjectMappings || []);
339
+ this.defaultValues.set(data.defaultValues || []);
340
+ }
341
+ else {
342
+ // Legacy format
343
+ this.mappings.set(data);
344
+ }
345
+ }
346
+ catch (e) {
347
+ console.error('Failed to import mappings:', e);
348
+ }
349
+ }
350
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MappingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
351
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MappingService, providedIn: 'root' });
352
+ }
353
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: MappingService, decorators: [{
354
+ type: Injectable,
355
+ args: [{
356
+ providedIn: 'root',
357
+ }]
358
+ }] });
359
+
360
+ class TransformationService {
361
+ applyTransformation(sourceValues, sourceFields, config) {
362
+ const values = sourceFields.map((f) => this.getValueByPath(sourceValues, f.path));
363
+ switch (config.type) {
364
+ case 'direct':
365
+ return String(values[0] ?? '');
366
+ case 'concat':
367
+ if (config.template) {
368
+ return this.applyTemplate(config.template, sourceFields, sourceValues);
369
+ }
370
+ return values.join(config.separator ?? ' ');
371
+ case 'substring':
372
+ const str = String(values[0] ?? '');
373
+ return str.substring(config.startIndex ?? 0, config.endIndex ?? str.length);
374
+ case 'replace':
375
+ return String(values[0] ?? '').replace(new RegExp(config.searchValue ?? '', 'g'), config.replaceValue ?? '');
376
+ case 'uppercase':
377
+ return String(values[0] ?? '').toUpperCase();
378
+ case 'lowercase':
379
+ return String(values[0] ?? '').toLowerCase();
380
+ case 'dateFormat':
381
+ return this.formatDate(values[0], config.inputFormat, config.outputFormat);
382
+ case 'extractYear':
383
+ return this.extractDatePart(values[0], 'year');
384
+ case 'extractMonth':
385
+ return this.extractDatePart(values[0], 'month');
386
+ case 'extractDay':
387
+ return this.extractDatePart(values[0], 'day');
388
+ case 'extractHour':
389
+ return this.extractDatePart(values[0], 'hour');
390
+ case 'extractMinute':
391
+ return this.extractDatePart(values[0], 'minute');
392
+ case 'extractSecond':
393
+ return this.extractDatePart(values[0], 'second');
394
+ case 'numberFormat':
395
+ return this.formatNumber(values[0], config.decimalPlaces, config.prefix, config.suffix);
396
+ case 'template':
397
+ return this.applyTemplate(config.template ?? '', sourceFields, sourceValues);
398
+ case 'custom':
399
+ return this.applyCustomExpression(config.expression ?? '', sourceFields, sourceValues);
400
+ default:
401
+ return String(values[0] ?? '');
402
+ }
403
+ }
404
+ getValueByPath(obj, path) {
405
+ return path.split('.').reduce((acc, part) => {
406
+ if (acc && typeof acc === 'object') {
407
+ return acc[part];
408
+ }
409
+ return undefined;
410
+ }, obj);
411
+ }
412
+ applyTemplate(template, sourceFields, sourceValues) {
413
+ let result = template;
414
+ sourceFields.forEach((field) => {
415
+ const value = this.getValueByPath(sourceValues, field.path);
416
+ result = result.replace(new RegExp(`\\{${field.name}\\}`, 'g'), String(value ?? ''));
417
+ result = result.replace(new RegExp(`\\{${field.path}\\}`, 'g'), String(value ?? ''));
418
+ });
419
+ return result;
420
+ }
421
+ formatDate(value, inputFormat, outputFormat) {
422
+ if (!value)
423
+ return '';
424
+ try {
425
+ const date = new Date(value);
426
+ if (isNaN(date.getTime()))
427
+ return String(value);
428
+ // Simple format implementation
429
+ const format = outputFormat ?? 'YYYY-MM-DD';
430
+ return format
431
+ .replace('YYYY', date.getFullYear().toString())
432
+ .replace('MM', (date.getMonth() + 1).toString().padStart(2, '0'))
433
+ .replace('DD', date.getDate().toString().padStart(2, '0'))
434
+ .replace('HH', date.getHours().toString().padStart(2, '0'))
435
+ .replace('mm', date.getMinutes().toString().padStart(2, '0'))
436
+ .replace('ss', date.getSeconds().toString().padStart(2, '0'));
437
+ }
438
+ catch {
439
+ return String(value);
440
+ }
441
+ }
442
+ extractDatePart(value, part) {
443
+ if (!value)
444
+ return '';
445
+ try {
446
+ const date = new Date(value);
447
+ if (isNaN(date.getTime()))
448
+ return String(value);
449
+ switch (part) {
450
+ case 'year':
451
+ return date.getFullYear().toString();
452
+ case 'month':
453
+ return (date.getMonth() + 1).toString().padStart(2, '0');
454
+ case 'day':
455
+ return date.getDate().toString().padStart(2, '0');
456
+ case 'hour':
457
+ return date.getHours().toString().padStart(2, '0');
458
+ case 'minute':
459
+ return date.getMinutes().toString().padStart(2, '0');
460
+ case 'second':
461
+ return date.getSeconds().toString().padStart(2, '0');
462
+ default:
463
+ return String(value);
464
+ }
465
+ }
466
+ catch {
467
+ return String(value);
468
+ }
469
+ }
470
+ formatNumber(value, decimalPlaces, prefix, suffix) {
471
+ if (value === null || value === undefined)
472
+ return '';
473
+ const num = Number(value);
474
+ if (isNaN(num))
475
+ return String(value);
476
+ let formatted = decimalPlaces !== undefined
477
+ ? num.toFixed(decimalPlaces)
478
+ : num.toString();
479
+ return `${prefix ?? ''}${formatted}${suffix ?? ''}`;
480
+ }
481
+ applyCustomExpression(expression, sourceFields, sourceValues) {
482
+ try {
483
+ // Create a safe context with field values
484
+ const context = {};
485
+ sourceFields.forEach((field) => {
486
+ context[field.name] = this.getValueByPath(sourceValues, field.path);
487
+ });
488
+ // Very basic expression evaluation - in production use a proper parser
489
+ const fn = new Function(...Object.keys(context), `return ${expression}`);
490
+ return String(fn(...Object.values(context)));
491
+ }
492
+ catch (e) {
493
+ console.error('Custom expression error:', e);
494
+ return '[Error]';
495
+ }
496
+ }
497
+ getTransformationLabel(type) {
498
+ const labels = {
499
+ direct: 'Direct Mapping',
500
+ concat: 'Concatenate',
501
+ substring: 'Substring',
502
+ replace: 'Find & Replace',
503
+ uppercase: 'Uppercase',
504
+ lowercase: 'Lowercase',
505
+ dateFormat: 'Date Format',
506
+ extractYear: 'Extract Year',
507
+ extractMonth: 'Extract Month',
508
+ extractDay: 'Extract Day',
509
+ extractHour: 'Extract Hour',
510
+ extractMinute: 'Extract Minute',
511
+ extractSecond: 'Extract Second',
512
+ numberFormat: 'Number Format',
513
+ template: 'Template',
514
+ custom: 'Custom Expression',
515
+ };
516
+ return labels[type];
517
+ }
518
+ getAvailableTransformations() {
519
+ return [
520
+ { type: 'direct', label: 'Direct Mapping' },
521
+ { type: 'concat', label: 'Concatenate', category: 'String' },
522
+ { type: 'substring', label: 'Substring', category: 'String' },
523
+ { type: 'replace', label: 'Find & Replace', category: 'String' },
524
+ { type: 'uppercase', label: 'Uppercase', category: 'String' },
525
+ { type: 'lowercase', label: 'Lowercase', category: 'String' },
526
+ { type: 'template', label: 'Template', category: 'String' },
527
+ { type: 'dateFormat', label: 'Format Date', category: 'Date' },
528
+ { type: 'extractYear', label: 'Extract Year', category: 'Date' },
529
+ { type: 'extractMonth', label: 'Extract Month', category: 'Date' },
530
+ { type: 'extractDay', label: 'Extract Day', category: 'Date' },
531
+ { type: 'extractHour', label: 'Extract Hour', category: 'Date' },
532
+ { type: 'extractMinute', label: 'Extract Minute', category: 'Date' },
533
+ { type: 'extractSecond', label: 'Extract Second', category: 'Date' },
534
+ { type: 'numberFormat', label: 'Number Format', category: 'Number' },
535
+ { type: 'custom', label: 'Custom Expression', category: 'Advanced' },
536
+ ];
537
+ }
538
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
539
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, providedIn: 'root' });
540
+ }
541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, decorators: [{
542
+ type: Injectable,
543
+ args: [{
544
+ providedIn: 'root',
545
+ }]
546
+ }] });
547
+
548
+ class SvgConnectorService {
549
+ createBezierPath(start, end) {
550
+ const dx = end.x - start.x;
551
+ const controlPointOffset = Math.min(Math.abs(dx) * 0.5, 150);
552
+ const cp1x = start.x + controlPointOffset;
553
+ const cp1y = start.y;
554
+ const cp2x = end.x - controlPointOffset;
555
+ const cp2y = end.y;
556
+ return `M ${start.x} ${start.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${end.x} ${end.y}`;
557
+ }
558
+ createMultiSourcePath(sources, target) {
559
+ const mergeX = target.x - 80;
560
+ const mergeY = target.y;
561
+ const mergePoint = { x: mergeX, y: mergeY };
562
+ const paths = sources.map((source) => {
563
+ // Path from source to merge point
564
+ const dx1 = mergeX - source.x;
565
+ const cp1Offset = Math.min(Math.abs(dx1) * 0.4, 100);
566
+ return `M ${source.x} ${source.y} C ${source.x + cp1Offset} ${source.y}, ${mergeX - cp1Offset} ${mergeY}, ${mergeX} ${mergeY}`;
567
+ });
568
+ // Add path from merge point to target
569
+ const finalPath = `M ${mergeX} ${mergeY} L ${target.x} ${target.y}`;
570
+ paths.push(finalPath);
571
+ return { paths, mergePoint };
572
+ }
573
+ getMidPoint(start, end) {
574
+ return {
575
+ x: (start.x + end.x) / 2,
576
+ y: (start.y + end.y) / 2,
577
+ };
578
+ }
579
+ getMultiSourceMidPoint(sources, target) {
580
+ const mergeX = target.x - 80;
581
+ return {
582
+ x: mergeX,
583
+ y: target.y,
584
+ };
585
+ }
586
+ calculateConnectionPoint(rect, side, containerRect) {
587
+ const relativeY = rect.top - containerRect.top + rect.height / 2;
588
+ if (side === 'source') {
589
+ return {
590
+ x: rect.right - containerRect.left,
591
+ y: relativeY,
592
+ };
593
+ }
594
+ else {
595
+ return {
596
+ x: rect.left - containerRect.left,
597
+ y: relativeY,
598
+ };
599
+ }
600
+ }
601
+ isPointNearPath(point, pathStart, pathEnd, threshold = 10) {
602
+ // Simplified hit detection using distance to line segment
603
+ const A = point.x - pathStart.x;
604
+ const B = point.y - pathStart.y;
605
+ const C = pathEnd.x - pathStart.x;
606
+ const D = pathEnd.y - pathStart.y;
607
+ const dot = A * C + B * D;
608
+ const lenSq = C * C + D * D;
609
+ let param = -1;
610
+ if (lenSq !== 0) {
611
+ param = dot / lenSq;
612
+ }
613
+ let xx, yy;
614
+ if (param < 0) {
615
+ xx = pathStart.x;
616
+ yy = pathStart.y;
617
+ }
618
+ else if (param > 1) {
619
+ xx = pathEnd.x;
620
+ yy = pathEnd.y;
621
+ }
622
+ else {
623
+ xx = pathStart.x + param * C;
624
+ yy = pathStart.y + param * D;
625
+ }
626
+ const dx = point.x - xx;
627
+ const dy = point.y - yy;
628
+ const distance = Math.sqrt(dx * dx + dy * dy);
629
+ return distance <= threshold;
630
+ }
631
+ createDragPath(start, end) {
632
+ return this.createBezierPath(start, end);
633
+ }
634
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SvgConnectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
635
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SvgConnectorService, providedIn: 'root' });
636
+ }
637
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SvgConnectorService, decorators: [{
638
+ type: Injectable,
639
+ args: [{
640
+ providedIn: 'root',
641
+ }]
642
+ }] });
643
+
644
+ class SchemaParserService {
645
+ modelRegistry = {};
646
+ idCounter = 0;
647
+ registerModels(models) {
648
+ this.modelRegistry = { ...this.modelRegistry, ...models };
649
+ }
650
+ clearRegistry() {
651
+ this.modelRegistry = {};
652
+ }
653
+ parseSchema(schemaJson, schemaName = 'Schema') {
654
+ const schema = typeof schemaJson === 'string' ? JSON.parse(schemaJson) : schemaJson;
655
+ this.idCounter = 0;
656
+ // Extract definitions from the schema document itself
657
+ const localDefs = schema.$defs || schema.definitions || {};
658
+ const combinedRegistry = { ...this.modelRegistry, ...localDefs };
659
+ let resolvedSchema;
660
+ if (schema.$ref) {
661
+ // Resolve the reference
662
+ resolvedSchema = this.resolveRef(schema.$ref, combinedRegistry);
663
+ }
664
+ else if (schema.properties) {
665
+ // Direct schema with properties
666
+ resolvedSchema = schema;
667
+ }
668
+ else {
669
+ throw new Error('Schema must have either $ref or properties');
670
+ }
671
+ // Build fields from the resolved schema
672
+ let fields = this.buildFields(resolvedSchema, combinedRegistry, '');
673
+ // Apply exclude filter
674
+ if (schema.exclude && schema.exclude.length > 0) {
675
+ fields = this.applyExclude(fields, schema.exclude);
676
+ }
677
+ // Apply include filter (only if specified)
678
+ if (schema.include && schema.include.length > 0) {
679
+ fields = this.applyInclude(fields, schema.include);
680
+ }
681
+ return {
682
+ name: schema.title || schemaName,
683
+ fields,
684
+ };
685
+ }
686
+ resolveRef(ref, registry) {
687
+ // Handle different ref formats:
688
+ // #model, #/definitions/model, #/$defs/model, model
689
+ let modelName;
690
+ if (ref.startsWith('#/$defs/')) {
691
+ modelName = ref.substring(8);
692
+ }
693
+ else if (ref.startsWith('#/definitions/')) {
694
+ modelName = ref.substring(14);
695
+ }
696
+ else if (ref.startsWith('#')) {
697
+ modelName = ref.substring(1);
698
+ }
699
+ else {
700
+ modelName = ref;
701
+ }
702
+ const resolved = registry[modelName];
703
+ if (!resolved) {
704
+ throw new Error(`Cannot resolve reference: ${ref}. Model "${modelName}" not found in registry.`);
705
+ }
706
+ // If the resolved schema also has a $ref, resolve it recursively
707
+ if (resolved.$ref) {
708
+ return this.resolveRef(resolved.$ref, registry);
709
+ }
710
+ return resolved;
711
+ }
712
+ buildFields(schema, registry, parentPath, arrayContext) {
713
+ const fields = [];
714
+ if (!schema.properties) {
715
+ return fields;
716
+ }
717
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
718
+ const path = parentPath ? `${parentPath}.${name}` : name;
719
+ const field = this.buildField(name, propSchema, registry, path, arrayContext);
720
+ fields.push(field);
721
+ }
722
+ return fields;
723
+ }
724
+ buildField(name, schema, registry, path, arrayContext) {
725
+ // Resolve $ref if present
726
+ let resolvedSchema = schema;
727
+ if (schema.$ref) {
728
+ resolvedSchema = { ...this.resolveRef(schema.$ref, registry), ...schema };
729
+ delete resolvedSchema.$ref;
730
+ }
731
+ const fieldType = this.mapType(resolvedSchema);
732
+ const field = {
733
+ id: `field-${++this.idCounter}-${name}`,
734
+ name,
735
+ type: fieldType,
736
+ path,
737
+ description: resolvedSchema.description,
738
+ isArrayItem: arrayContext?.isArrayItem,
739
+ parentArrayPath: arrayContext?.parentArrayPath,
740
+ };
741
+ // Handle nested objects
742
+ if (fieldType === 'object' && resolvedSchema.properties) {
743
+ field.children = this.buildFields(resolvedSchema, registry, path, arrayContext);
744
+ field.expanded = true;
745
+ }
746
+ // Handle arrays with object items
747
+ if (fieldType === 'array' && resolvedSchema.items) {
748
+ let itemSchema = resolvedSchema.items;
749
+ if (itemSchema.$ref) {
750
+ itemSchema = this.resolveRef(itemSchema.$ref, registry);
751
+ }
752
+ if (itemSchema.properties) {
753
+ // Mark children as array items with reference to parent array
754
+ field.children = this.buildFields(itemSchema, registry, `${path}[]`, {
755
+ isArrayItem: true,
756
+ parentArrayPath: path,
757
+ });
758
+ field.expanded = true;
759
+ }
760
+ }
761
+ return field;
762
+ }
763
+ mapType(schema) {
764
+ const type = schema.type;
765
+ const format = schema.format;
766
+ // Check format first for date types
767
+ if (format === 'date' || format === 'date-time' || format === 'time') {
768
+ return 'date';
769
+ }
770
+ switch (type) {
771
+ case 'string':
772
+ return 'string';
773
+ case 'number':
774
+ case 'integer':
775
+ return 'number';
776
+ case 'boolean':
777
+ return 'boolean';
778
+ case 'object':
779
+ return 'object';
780
+ case 'array':
781
+ return 'array';
782
+ default:
783
+ // If type is not specified but has properties, it's an object
784
+ if (schema.properties) {
785
+ return 'object';
786
+ }
787
+ return 'string';
788
+ }
789
+ }
790
+ applyExclude(fields, exclude) {
791
+ return fields
792
+ .filter((field) => !exclude.includes(field.name) && !exclude.includes(field.path))
793
+ .map((field) => {
794
+ if (field.children) {
795
+ // Filter nested fields by checking both full path and relative name
796
+ const filteredChildren = this.applyExclude(field.children, exclude);
797
+ return { ...field, children: filteredChildren };
798
+ }
799
+ return field;
800
+ });
801
+ }
802
+ applyInclude(fields, include) {
803
+ return fields
804
+ .filter((field) => {
805
+ // Include if field name or path matches
806
+ if (include.includes(field.name) || include.includes(field.path)) {
807
+ return true;
808
+ }
809
+ // Include if any child path matches
810
+ if (field.children) {
811
+ return this.hasIncludedChild(field.children, include);
812
+ }
813
+ return false;
814
+ })
815
+ .map((field) => {
816
+ if (field.children) {
817
+ // Keep parent but filter children
818
+ const filteredChildren = this.applyInclude(field.children, include);
819
+ return { ...field, children: filteredChildren.length > 0 ? filteredChildren : field.children };
820
+ }
821
+ return field;
822
+ });
823
+ }
824
+ hasIncludedChild(fields, include) {
825
+ return fields.some((field) => {
826
+ if (include.includes(field.name) || include.includes(field.path)) {
827
+ return true;
828
+ }
829
+ if (field.children) {
830
+ return this.hasIncludedChild(field.children, include);
831
+ }
832
+ return false;
833
+ });
834
+ }
835
+ // Utility method to create a schema document from model name
836
+ createSchemaFromRef(modelRef, options) {
837
+ return {
838
+ $ref: modelRef,
839
+ title: options?.title,
840
+ exclude: options?.exclude,
841
+ include: options?.include,
842
+ };
843
+ }
844
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaParserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
845
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaParserService, providedIn: 'root' });
846
+ }
847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaParserService, decorators: [{
848
+ type: Injectable,
849
+ args: [{
850
+ providedIn: 'root',
851
+ }]
852
+ }] });
853
+
854
+ class SchemaTreeComponent {
855
+ schema;
856
+ side = 'source';
857
+ mappings = [];
858
+ defaultValues = [];
859
+ fieldDragStart = new EventEmitter();
860
+ fieldDragEnd = new EventEmitter();
861
+ fieldDrop = new EventEmitter();
862
+ fieldPositionsChanged = new EventEmitter();
863
+ fieldDefaultValueClick = new EventEmitter();
864
+ schemaFieldsContainer;
865
+ fieldItems;
866
+ mappingService = inject(MappingService);
867
+ resizeObserver;
868
+ scrollHandler = () => this.onScroll();
869
+ ngAfterViewInit() {
870
+ this.emitFieldPositions();
871
+ this.resizeObserver = new ResizeObserver(() => {
872
+ this.emitFieldPositions();
873
+ });
874
+ this.fieldItems.changes.subscribe(() => {
875
+ this.emitFieldPositions();
876
+ });
877
+ // Add scroll listener to update connector positions
878
+ if (this.schemaFieldsContainer?.nativeElement) {
879
+ this.schemaFieldsContainer.nativeElement.addEventListener('scroll', this.scrollHandler, { passive: true });
880
+ }
881
+ }
882
+ ngOnDestroy() {
883
+ if (this.resizeObserver) {
884
+ this.resizeObserver.disconnect();
885
+ }
886
+ if (this.schemaFieldsContainer?.nativeElement) {
887
+ this.schemaFieldsContainer.nativeElement.removeEventListener('scroll', this.scrollHandler);
888
+ }
889
+ }
890
+ onScroll() {
891
+ this.emitFieldPositions();
892
+ }
893
+ emitFieldPositions() {
894
+ setTimeout(() => {
895
+ const positions = new Map();
896
+ this.fieldItems.forEach((item) => {
897
+ const fieldId = item.nativeElement.getAttribute('data-field-id');
898
+ if (fieldId) {
899
+ positions.set(fieldId, item.nativeElement.getBoundingClientRect());
900
+ }
901
+ });
902
+ this.fieldPositionsChanged.emit(positions);
903
+ });
904
+ }
905
+ toggleExpand(field, event) {
906
+ event.stopPropagation();
907
+ field.expanded = !field.expanded;
908
+ setTimeout(() => this.emitFieldPositions(), 50);
909
+ }
910
+ onDragStart(event, field) {
911
+ if (this.side !== 'source')
912
+ return;
913
+ const element = event.currentTarget;
914
+ const rect = element.getBoundingClientRect();
915
+ this.fieldDragStart.emit({ field, element, rect });
916
+ }
917
+ onDragOver(event) {
918
+ if (this.side === 'target') {
919
+ event.preventDefault();
920
+ }
921
+ }
922
+ onDrop(event, field) {
923
+ if (this.side !== 'target')
924
+ return;
925
+ const element = event.currentTarget;
926
+ const rect = element.getBoundingClientRect();
927
+ this.fieldDrop.emit({ field, element, rect });
928
+ }
929
+ getTypeIcon(type) {
930
+ const icons = {
931
+ string: 'text_fields',
932
+ number: 'pin',
933
+ boolean: 'toggle_on',
934
+ object: 'data_object',
935
+ array: 'data_array',
936
+ date: 'calendar_today',
937
+ };
938
+ return icons[type] || 'help_outline';
939
+ }
940
+ isFieldMapped(field) {
941
+ if (this.side === 'source') {
942
+ return this.mappings.some((m) => m.sourceFields.some((sf) => sf.id === field.id));
943
+ }
944
+ else {
945
+ return this.mappings.some((m) => m.targetField.id === field.id);
946
+ }
947
+ }
948
+ getFieldMappingCount(field) {
949
+ if (this.side === 'source') {
950
+ return this.mappings.filter((m) => m.sourceFields.some((sf) => sf.id === field.id)).length;
951
+ }
952
+ else {
953
+ const mapping = this.mappings.find((m) => m.targetField.id === field.id);
954
+ return mapping ? mapping.sourceFields.length : 0;
955
+ }
956
+ }
957
+ hasDefaultValue(field) {
958
+ return this.defaultValues.some(d => d.targetField.id === field.id);
959
+ }
960
+ getDefaultValueDisplay(field) {
961
+ const defaultValue = this.defaultValues.find(d => d.targetField.id === field.id);
962
+ if (!defaultValue || defaultValue.value === null)
963
+ return '';
964
+ if (defaultValue.valueType === 'date' && defaultValue.value) {
965
+ return new Date(defaultValue.value).toLocaleDateString();
966
+ }
967
+ return String(defaultValue.value);
968
+ }
969
+ onFieldClick(event, field) {
970
+ // Only handle clicks on target fields that are leaf nodes (no children) or have specific types
971
+ if (this.side !== 'target')
972
+ return;
973
+ if (field.type === 'object' || field.type === 'array')
974
+ return;
975
+ // Don't trigger if the field is already mapped (unless it has a default value)
976
+ if (this.isFieldMapped(field) && !this.hasDefaultValue(field))
977
+ return;
978
+ // Allow clicking on unmapped fields OR fields with default values (to edit them)
979
+ if (!this.isFieldMapped(field) || this.hasDefaultValue(field)) {
980
+ event.stopPropagation();
981
+ const element = event.currentTarget;
982
+ const rect = element.getBoundingClientRect();
983
+ this.fieldDefaultValueClick.emit({ field, element, rect });
984
+ }
985
+ }
986
+ trackByFieldId(index, field) {
987
+ return field.id;
988
+ }
989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaTreeComponent, isStandalone: true, selector: "schema-tree", inputs: { schema: "schema", side: "side", mappings: "mappings", defaultValues: "defaultValues" }, outputs: { fieldDragStart: "fieldDragStart", fieldDragEnd: "fieldDragEnd", fieldDrop: "fieldDrop", fieldPositionsChanged: "fieldPositionsChanged", fieldDefaultValueClick: "fieldDefaultValueClick" }, viewQueries: [{ propertyName: "schemaFieldsContainer", first: true, predicate: ["schemaFields"], descendants: true }, { propertyName: "fieldItems", predicate: ["fieldItem"], descendants: true }], ngImport: i0, template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array')\"\n [class.droppable]=\"side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--surface-card, #ffffff);border-radius:12px;box-shadow:0 2px 8px #00000014;overflow:hidden;height:100%;min-height:0;display:flex;flex-direction:column;flex:1}.schema-tree.source .schema-header{background:linear-gradient(135deg,#6366f1,#8b5cf6)}.schema-tree.source .connection-point{right:8px}.schema-tree.target .schema-header{background:linear-gradient(135deg,#10b981,#059669)}.schema-tree.target .connection-point{left:8px}.schema-header{padding:16px 20px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.schema-title{font-size:16px;font-weight:600;letter-spacing:.3px}.schema-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;background:#fff3;padding:4px 10px;border-radius:20px}.schema-fields{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.field-item{display:flex;align-items:center;padding:10px 16px;gap:8px;cursor:default;transition:background-color .15s ease;position:relative;-webkit-user-select:none;user-select:none}.field-item:hover{background-color:var(--surface-hover, #f8fafc)}.field-item.mapped{background-color:var(--surface-mapped, #f0fdf4)}.field-item.mapped:hover{background-color:#dcfce7}.field-item.draggable{cursor:grab}.field-item.draggable:active{cursor:grabbing}.field-item.draggable:hover .connection-point .point-dot{transform:scale(1.3);background:#6366f1}.field-item.droppable{cursor:pointer}.field-item.droppable:hover .connection-point .point-dot{transform:scale(1.3);background:#10b981}.expand-btn{background:none;border:none;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#94a3b8;border-radius:4px;transition:all .15s ease}.expand-btn:hover{background-color:#e2e8f0;color:#475569}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:24px;height:24px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:1;font-size:14px;color:#1e293b;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-item.is-array{background-color:#fffbeb}.field-item.is-array:hover,.field-item.is-array.mapped{background-color:#fef3c7}.field-item.is-array .type-icon{color:#f59e0b}.connection-point.array-point .point-dot{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b4d}.connection-point.object-point .point-dot{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf64d}.mapping-indicator{display:flex;align-items:center;gap:2px;color:#10b981}.mapping-indicator mat-icon{font-size:16px;width:16px;height:16px}.mapping-indicator .mapping-count{font-size:11px;font-weight:600;background:#10b981;color:#fff;padding:1px 5px;border-radius:10px;min-width:16px;text-align:center}.connection-point{position:absolute;top:50%;transform:translateY(-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center}.connection-point.source{right:8px}.connection-point.target{left:8px}.point-dot{width:10px;height:10px;border-radius:50%;background:#cbd5e1;transition:all .2s ease;box-shadow:0 0 0 3px #cbd5e14d}.mapped .point-dot{background:#10b981;box-shadow:0 0 0 3px #10b9814d}.nested-fields{border-left:2px solid #e2e8f0;margin-left:28px}.field-item.has-default{background-color:#eff6ff}.field-item.has-default:hover{background-color:#dbeafe}.field-item.clickable{cursor:pointer}.field-item.clickable:hover:not(.has-default){background-color:#f1f5f9}.field-item.clickable:hover:not(.has-default):after{content:\"Click to set default\";position:absolute;right:32px;font-size:11px;color:#64748b;font-style:italic}.field-item.clickable.has-default:hover:after{content:\"Click to edit\";position:absolute;right:32px;font-size:11px;color:#3b82f6;font-style:italic}.default-indicator{display:flex;align-items:center;gap:4px;color:#3b82f6;font-size:12px;background:#dbeafe;padding:2px 8px;border-radius:4px;max-width:100px;overflow:hidden}.default-indicator mat-icon{font-size:14px;width:14px;height:14px}.default-indicator .default-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
991
+ }
992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, decorators: [{
993
+ type: Component,
994
+ args: [{ selector: 'schema-tree', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule], template: "<div class=\"schema-tree\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\">\n <div class=\"schema-header\">\n <span class=\"schema-title\">{{ schema.name }}</span>\n <span class=\"schema-badge\">{{ side === 'source' ? 'Source' : 'Target' }}</span>\n </div>\n\n <div class=\"schema-fields\" #schemaFields>\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: schema.fields, level: 0 }\"></ng-container>\n </div>\n</div>\n\n<ng-template #fieldList let-fields=\"fields\" let-level=\"level\">\n @for (field of fields; track trackByFieldId($index, field)) {\n <div\n #fieldItem\n class=\"field-item\"\n [class.mapped]=\"isFieldMapped(field)\"\n [class.has-default]=\"hasDefaultValue(field)\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.expanded]=\"field.expanded\"\n [class.is-array]=\"field.type === 'array'\"\n [class.draggable]=\"side === 'source' && ((!field.children || field.children.length === 0) || field.type === 'array')\"\n [class.droppable]=\"side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')\"\n [class.clickable]=\"side === 'target' && (!isFieldMapped(field) || hasDefaultValue(field)) && field.type !== 'object' && field.type !== 'array'\"\n [style.padding-left.px]=\"16 + level * 20\"\n [attr.data-field-id]=\"field.id\"\n (mousedown)=\"onDragStart($event, field)\"\n (mouseup)=\"onDrop($event, field)\"\n (click)=\"onFieldClick($event, field)\"\n >\n <!-- Expand/Collapse button for nested objects -->\n @if (field.children && field.children.length > 0) {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field, $event)\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Field type icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field name with array indicator -->\n <span class=\"field-name\">{{ field.name }}@if (field.type === 'array') {<span class=\"array-indicator\">[]</span>}</span>\n\n <!-- Mapping indicator -->\n @if (isFieldMapped(field)) {\n <span class=\"mapping-indicator\" [matTooltip]=\"getFieldMappingCount(field) + ' mapping(s)'\">\n <mat-icon>{{ field.type === 'array' ? 'loop' : 'link' }}</mat-icon>\n @if (getFieldMappingCount(field) > 1) {\n <span class=\"mapping-count\">{{ getFieldMappingCount(field) }}</span>\n }\n </span>\n }\n\n <!-- Default value indicator -->\n @if (hasDefaultValue(field)) {\n <span class=\"default-indicator\" [matTooltip]=\"'Default: ' + getDefaultValueDisplay(field)\">\n <mat-icon>edit</mat-icon>\n <span class=\"default-value\">{{ getDefaultValueDisplay(field) }}</span>\n </span>\n }\n\n <!-- Connection point - show for leaf nodes, arrays, and objects (on target for array-to-object) -->\n @if ((!field.children || field.children.length === 0) || field.type === 'array' || (side === 'target' && field.type === 'object')) {\n <div class=\"connection-point\" [class.source]=\"side === 'source'\" [class.target]=\"side === 'target'\" [class.array-point]=\"field.type === 'array'\" [class.object-point]=\"field.type === 'object'\">\n <span class=\"point-dot\"></span>\n </div>\n }\n </div>\n\n <!-- Nested children -->\n @if (field.children && field.children.length > 0 && field.expanded) {\n <div class=\"nested-fields\">\n <ng-container *ngTemplateOutlet=\"fieldList; context: { fields: field.children, level: level + 1 }\"></ng-container>\n </div>\n }\n }\n</ng-template>\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden}.schema-tree{background:var(--surface-card, #ffffff);border-radius:12px;box-shadow:0 2px 8px #00000014;overflow:hidden;height:100%;min-height:0;display:flex;flex-direction:column;flex:1}.schema-tree.source .schema-header{background:linear-gradient(135deg,#6366f1,#8b5cf6)}.schema-tree.source .connection-point{right:8px}.schema-tree.target .schema-header{background:linear-gradient(135deg,#10b981,#059669)}.schema-tree.target .connection-point{left:8px}.schema-header{padding:16px 20px;color:#fff;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-shrink:0}.schema-title{font-size:16px;font-weight:600;letter-spacing:.3px}.schema-badge{font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.5px;background:#fff3;padding:4px 10px;border-radius:20px}.schema-fields{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.field-item{display:flex;align-items:center;padding:10px 16px;gap:8px;cursor:default;transition:background-color .15s ease;position:relative;-webkit-user-select:none;user-select:none}.field-item:hover{background-color:var(--surface-hover, #f8fafc)}.field-item.mapped{background-color:var(--surface-mapped, #f0fdf4)}.field-item.mapped:hover{background-color:#dcfce7}.field-item.draggable{cursor:grab}.field-item.draggable:active{cursor:grabbing}.field-item.draggable:hover .connection-point .point-dot{transform:scale(1.3);background:#6366f1}.field-item.droppable{cursor:pointer}.field-item.droppable:hover .connection-point .point-dot{transform:scale(1.3);background:#10b981}.expand-btn{background:none;border:none;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#94a3b8;border-radius:4px;transition:all .15s ease}.expand-btn:hover{background-color:#e2e8f0;color:#475569}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:24px;height:24px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:1;font-size:14px;color:#1e293b;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-item.is-array{background-color:#fffbeb}.field-item.is-array:hover,.field-item.is-array.mapped{background-color:#fef3c7}.field-item.is-array .type-icon{color:#f59e0b}.connection-point.array-point .point-dot{background:#f59e0b;box-shadow:0 0 0 3px #f59e0b4d}.connection-point.object-point .point-dot{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf64d}.mapping-indicator{display:flex;align-items:center;gap:2px;color:#10b981}.mapping-indicator mat-icon{font-size:16px;width:16px;height:16px}.mapping-indicator .mapping-count{font-size:11px;font-weight:600;background:#10b981;color:#fff;padding:1px 5px;border-radius:10px;min-width:16px;text-align:center}.connection-point{position:absolute;top:50%;transform:translateY(-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center}.connection-point.source{right:8px}.connection-point.target{left:8px}.point-dot{width:10px;height:10px;border-radius:50%;background:#cbd5e1;transition:all .2s ease;box-shadow:0 0 0 3px #cbd5e14d}.mapped .point-dot{background:#10b981;box-shadow:0 0 0 3px #10b9814d}.nested-fields{border-left:2px solid #e2e8f0;margin-left:28px}.field-item.has-default{background-color:#eff6ff}.field-item.has-default:hover{background-color:#dbeafe}.field-item.clickable{cursor:pointer}.field-item.clickable:hover:not(.has-default){background-color:#f1f5f9}.field-item.clickable:hover:not(.has-default):after{content:\"Click to set default\";position:absolute;right:32px;font-size:11px;color:#64748b;font-style:italic}.field-item.clickable.has-default:hover:after{content:\"Click to edit\";position:absolute;right:32px;font-size:11px;color:#3b82f6;font-style:italic}.default-indicator{display:flex;align-items:center;gap:4px;color:#3b82f6;font-size:12px;background:#dbeafe;padding:2px 8px;border-radius:4px;max-width:100px;overflow:hidden}.default-indicator mat-icon{font-size:14px;width:14px;height:14px}.default-indicator .default-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}\n"] }]
995
+ }], propDecorators: { schema: [{
996
+ type: Input
997
+ }], side: [{
998
+ type: Input
999
+ }], mappings: [{
1000
+ type: Input
1001
+ }], defaultValues: [{
1002
+ type: Input
1003
+ }], fieldDragStart: [{
1004
+ type: Output
1005
+ }], fieldDragEnd: [{
1006
+ type: Output
1007
+ }], fieldDrop: [{
1008
+ type: Output
1009
+ }], fieldPositionsChanged: [{
1010
+ type: Output
1011
+ }], fieldDefaultValueClick: [{
1012
+ type: Output
1013
+ }], schemaFieldsContainer: [{
1014
+ type: ViewChild,
1015
+ args: ['schemaFields']
1016
+ }], fieldItems: [{
1017
+ type: ViewChildren,
1018
+ args: ['fieldItem']
1019
+ }] } });
1020
+
1021
+ class TransformationPopoverComponent {
1022
+ mapping;
1023
+ position = { x: 0, y: 0 };
1024
+ sampleData = {};
1025
+ save = new EventEmitter();
1026
+ delete = new EventEmitter();
1027
+ close = new EventEmitter();
1028
+ transformationService = inject(TransformationService);
1029
+ transformationType = 'direct';
1030
+ config = { type: 'direct' };
1031
+ preview = '';
1032
+ availableTransformations = this.transformationService.getAvailableTransformations();
1033
+ ngOnInit() {
1034
+ this.initFromMapping();
1035
+ }
1036
+ ngOnChanges(changes) {
1037
+ if (changes['mapping'] || changes['sampleData']) {
1038
+ this.initFromMapping();
1039
+ }
1040
+ }
1041
+ initFromMapping() {
1042
+ if (this.mapping) {
1043
+ this.config = { ...this.mapping.transformation };
1044
+ this.transformationType = this.config.type;
1045
+ this.updatePreview();
1046
+ }
1047
+ }
1048
+ onTypeChange() {
1049
+ this.config = {
1050
+ ...this.config,
1051
+ type: this.transformationType,
1052
+ };
1053
+ // Set defaults based on type
1054
+ switch (this.transformationType) {
1055
+ case 'concat':
1056
+ this.config.separator = this.config.separator ?? ' ';
1057
+ this.config.template = this.config.template ?? this.getDefaultTemplate();
1058
+ break;
1059
+ case 'substring':
1060
+ this.config.startIndex = this.config.startIndex ?? 0;
1061
+ this.config.endIndex = this.config.endIndex ?? 10;
1062
+ break;
1063
+ case 'replace':
1064
+ this.config.searchValue = this.config.searchValue ?? '';
1065
+ this.config.replaceValue = this.config.replaceValue ?? '';
1066
+ break;
1067
+ case 'dateFormat':
1068
+ this.config.outputFormat = this.config.outputFormat ?? 'YYYY-MM-DD';
1069
+ break;
1070
+ case 'numberFormat':
1071
+ this.config.decimalPlaces = this.config.decimalPlaces ?? 2;
1072
+ break;
1073
+ }
1074
+ this.updatePreview();
1075
+ }
1076
+ getDefaultTemplate() {
1077
+ return this.mapping.sourceFields.map((f) => `{${f.name}}`).join(' ');
1078
+ }
1079
+ updatePreview() {
1080
+ if (!this.mapping || !this.sampleData) {
1081
+ this.preview = '';
1082
+ return;
1083
+ }
1084
+ this.preview = this.transformationService.applyTransformation(this.sampleData, this.mapping.sourceFields, this.config);
1085
+ }
1086
+ onConfigChange() {
1087
+ this.updatePreview();
1088
+ }
1089
+ onSave() {
1090
+ this.save.emit(this.config);
1091
+ }
1092
+ onDelete() {
1093
+ this.delete.emit();
1094
+ }
1095
+ onClose() {
1096
+ this.close.emit();
1097
+ }
1098
+ getSourceFieldNames() {
1099
+ return this.mapping.sourceFields.map((f) => f.name).join(', ');
1100
+ }
1101
+ getPopoverStyle() {
1102
+ return {
1103
+ left: `${this.position.x}px`,
1104
+ top: `${this.position.y}px`,
1105
+ };
1106
+ }
1107
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1108
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: TransformationPopoverComponent, isStandalone: true, selector: "transformation-popover", inputs: { mapping: "mapping", position: "position", sampleData: "sampleData" }, outputs: { save: "save", delete: "delete", close: "close" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"transformation-popover\" [ngStyle]=\"getPopoverStyle()\">\n <div class=\"popover-arrow\"></div>\n\n <div class=\"popover-header\">\n <span class=\"popover-title\">Transformation</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <!-- Source/Target Info -->\n <div class=\"mapping-info\">\n <div class=\"info-row\">\n <span class=\"info-label\">Source:</span>\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"info-label\">Target:</span>\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\n </div>\n </div>\n\n <!-- Transformation Type -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Transformation Type</mat-label>\n <mat-select [(ngModel)]=\"transformationType\" (selectionChange)=\"onTypeChange()\">\n @for (t of availableTransformations; track t.type) {\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Type-specific options -->\n @switch (transformationType) {\n @case ('concat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"{field1} - {field2}\"\n />\n <mat-hint>Use curly braces around fieldName for values</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('substring') {\n <div class=\"config-section config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Start Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.startIndex\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>End Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.endIndex\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('replace') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Search For</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.searchValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Replace With</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.replaceValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('dateFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Output Format</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.outputFormat\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"YYYY-MM-DD\"\n />\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('numberFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Decimal Places</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.decimalPlaces\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <div class=\"config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Prefix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.prefix\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"$\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Suffix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.suffix\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n </div>\n }\n\n @case ('template') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"Hello {firstName}, your ID is {id}\"\n ></textarea>\n </mat-form-field>\n </div>\n }\n\n @case ('custom') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JavaScript Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.expression\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"fieldName.toUpperCase()\"\n ></textarea>\n <mat-hint>Use field names as variables</mat-hint>\n </mat-form-field>\n </div>\n }\n }\n\n <!-- Preview Section -->\n <div class=\"preview-section\">\n <span class=\"preview-label\">Preview:</span>\n <div class=\"preview-value\">{{ preview || '(empty)' }}</div>\n </div>\n </div>\n\n <div class=\"popover-actions\">\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\n <mat-icon>delete</mat-icon>\n Delete\n </button>\n <div class=\"action-spacer\"></div>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n</div>\n\n<!-- Backdrop -->\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\n", styles: [".popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:360px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #0003;transform:translate(-50%,-50%);animation:popoverIn .2s ease-out}@keyframes popoverIn{0%{opacity:0;transform:translate(-50%,-50%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc;border-radius:12px 12px 0 0}.popover-title{font-size:16px;font-weight:600;color:#1e293b}.close-btn{width:32px;height:32px;line-height:32px}.close-btn mat-icon{font-size:20px;width:20px;height:20px}.popover-content{padding:20px;max-height:400px;overflow-y:auto}.mapping-info{background:#f1f5f9;border-radius:8px;padding:12px 16px;margin-bottom:16px}.info-row{display:flex;align-items:center;gap:8px}.info-row+.info-row{margin-top:8px}.info-label{font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;width:60px}.info-value{font-size:14px;color:#1e293b;font-weight:500}.full-width{width:100%}.config-section{margin-top:12px}.config-row{display:flex;gap:12px}.config-row mat-form-field{flex:1}.preview-section{margin-top:16px;padding:12px 16px;background:linear-gradient(135deg,#eff6ff,#f0fdf4);border-radius:8px;border:1px solid #e0e7ff}.preview-label{font-size:11px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px;display:block;margin-bottom:6px}.preview-value{font-size:14px;color:#1e293b;font-family:Monaco,Menlo,monospace;word-break:break-all;min-height:20px}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;border-radius:0 0 12px 12px}.action-spacer{flex:1}::ng-deep .transformation-popover .mat-mdc-form-field{font-size:14px}::ng-deep .transformation-popover .mat-mdc-text-field-wrapper{background:#fff}::ng-deep .transformation-popover .mat-mdc-form-field-subscript-wrapper{font-size:11px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
1109
+ }
1110
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, decorators: [{
1111
+ type: Component,
1112
+ args: [{ selector: 'transformation-popover', standalone: true, imports: [
1113
+ CommonModule,
1114
+ FormsModule,
1115
+ MatIconModule,
1116
+ MatButtonModule,
1117
+ MatSelectModule,
1118
+ MatInputModule,
1119
+ MatFormFieldModule,
1120
+ MatTooltipModule,
1121
+ ], template: "<div class=\"transformation-popover\" [ngStyle]=\"getPopoverStyle()\">\n <div class=\"popover-arrow\"></div>\n\n <div class=\"popover-header\">\n <span class=\"popover-title\">Transformation</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <!-- Source/Target Info -->\n <div class=\"mapping-info\">\n <div class=\"info-row\">\n <span class=\"info-label\">Source:</span>\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"info-label\">Target:</span>\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\n </div>\n </div>\n\n <!-- Transformation Type -->\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Transformation Type</mat-label>\n <mat-select [(ngModel)]=\"transformationType\" (selectionChange)=\"onTypeChange()\">\n @for (t of availableTransformations; track t.type) {\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Type-specific options -->\n @switch (transformationType) {\n @case ('concat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"{field1} - {field2}\"\n />\n <mat-hint>Use curly braces around fieldName for values</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('substring') {\n <div class=\"config-section config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Start Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.startIndex\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>End Index</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.endIndex\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('replace') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Search For</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.searchValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Replace With</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.replaceValue\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n }\n\n @case ('dateFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Output Format</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.outputFormat\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"YYYY-MM-DD\"\n />\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\n </mat-form-field>\n </div>\n }\n\n @case ('numberFormat') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Decimal Places</mat-label>\n <input\n matInput\n type=\"number\"\n [(ngModel)]=\"config.decimalPlaces\"\n (ngModelChange)=\"onConfigChange()\"\n min=\"0\"\n />\n </mat-form-field>\n <div class=\"config-row\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Prefix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.prefix\"\n (ngModelChange)=\"onConfigChange()\"\n placeholder=\"$\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Suffix</mat-label>\n <input\n matInput\n [(ngModel)]=\"config.suffix\"\n (ngModelChange)=\"onConfigChange()\"\n />\n </mat-form-field>\n </div>\n </div>\n }\n\n @case ('template') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Template Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.template\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"Hello {firstName}, your ID is {id}\"\n ></textarea>\n </mat-form-field>\n </div>\n }\n\n @case ('custom') {\n <div class=\"config-section\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JavaScript Expression</mat-label>\n <textarea\n matInput\n [(ngModel)]=\"config.expression\"\n (ngModelChange)=\"onConfigChange()\"\n rows=\"3\"\n placeholder=\"fieldName.toUpperCase()\"\n ></textarea>\n <mat-hint>Use field names as variables</mat-hint>\n </mat-form-field>\n </div>\n }\n }\n\n <!-- Preview Section -->\n <div class=\"preview-section\">\n <span class=\"preview-label\">Preview:</span>\n <div class=\"preview-value\">{{ preview || '(empty)' }}</div>\n </div>\n </div>\n\n <div class=\"popover-actions\">\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\n <mat-icon>delete</mat-icon>\n Delete\n </button>\n <div class=\"action-spacer\"></div>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n</div>\n\n<!-- Backdrop -->\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\n", styles: [".popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:360px;background:#fff;border-radius:12px;box-shadow:0 10px 40px #0003;transform:translate(-50%,-50%);animation:popoverIn .2s ease-out}@keyframes popoverIn{0%{opacity:0;transform:translate(-50%,-50%) scale(.95)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0;background:#f8fafc;border-radius:12px 12px 0 0}.popover-title{font-size:16px;font-weight:600;color:#1e293b}.close-btn{width:32px;height:32px;line-height:32px}.close-btn mat-icon{font-size:20px;width:20px;height:20px}.popover-content{padding:20px;max-height:400px;overflow-y:auto}.mapping-info{background:#f1f5f9;border-radius:8px;padding:12px 16px;margin-bottom:16px}.info-row{display:flex;align-items:center;gap:8px}.info-row+.info-row{margin-top:8px}.info-label{font-size:12px;font-weight:600;color:#64748b;text-transform:uppercase;letter-spacing:.5px;width:60px}.info-value{font-size:14px;color:#1e293b;font-weight:500}.full-width{width:100%}.config-section{margin-top:12px}.config-row{display:flex;gap:12px}.config-row mat-form-field{flex:1}.preview-section{margin-top:16px;padding:12px 16px;background:linear-gradient(135deg,#eff6ff,#f0fdf4);border-radius:8px;border:1px solid #e0e7ff}.preview-label{font-size:11px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px;display:block;margin-bottom:6px}.preview-value{font-size:14px;color:#1e293b;font-family:Monaco,Menlo,monospace;word-break:break-all;min-height:20px}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;border-radius:0 0 12px 12px}.action-spacer{flex:1}::ng-deep .transformation-popover .mat-mdc-form-field{font-size:14px}::ng-deep .transformation-popover .mat-mdc-text-field-wrapper{background:#fff}::ng-deep .transformation-popover .mat-mdc-form-field-subscript-wrapper{font-size:11px}\n"] }]
1122
+ }], propDecorators: { mapping: [{
1123
+ type: Input
1124
+ }], position: [{
1125
+ type: Input
1126
+ }], sampleData: [{
1127
+ type: Input
1128
+ }], save: [{
1129
+ type: Output
1130
+ }], delete: [{
1131
+ type: Output
1132
+ }], close: [{
1133
+ type: Output
1134
+ }] } });
1135
+
1136
+ class ArrayFilterModalComponent {
1137
+ arrayMapping;
1138
+ save = new EventEmitter();
1139
+ close = new EventEmitter();
1140
+ filterEnabled = signal(false, ...(ngDevMode ? [{ debugName: "filterEnabled" }] : []));
1141
+ rootGroup = signal(this.createEmptyGroup(), ...(ngDevMode ? [{ debugName: "rootGroup" }] : []));
1142
+ // Available fields from the source array's children
1143
+ availableFields = computed(() => {
1144
+ const fields = [];
1145
+ this.collectFields(this.arrayMapping.sourceArray.children || [], '', fields);
1146
+ return fields;
1147
+ }, ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
1148
+ // Operators by type
1149
+ stringOperators = [
1150
+ { value: 'equals', label: 'equals', needsValue: true },
1151
+ { value: 'notEquals', label: 'not equals', needsValue: true },
1152
+ { value: 'contains', label: 'contains', needsValue: true },
1153
+ { value: 'notContains', label: 'does not contain', needsValue: true },
1154
+ { value: 'startsWith', label: 'starts with', needsValue: true },
1155
+ { value: 'endsWith', label: 'ends with', needsValue: true },
1156
+ { value: 'isEmpty', label: 'is empty', needsValue: false },
1157
+ { value: 'isNotEmpty', label: 'is not empty', needsValue: false },
1158
+ ];
1159
+ numberOperators = [
1160
+ { value: 'equals', label: 'equals', needsValue: true },
1161
+ { value: 'notEquals', label: 'not equals', needsValue: true },
1162
+ { value: 'greaterThan', label: 'greater than', needsValue: true },
1163
+ { value: 'lessThan', label: 'less than', needsValue: true },
1164
+ { value: 'greaterThanOrEqual', label: 'greater or equal', needsValue: true },
1165
+ { value: 'lessThanOrEqual', label: 'less or equal', needsValue: true },
1166
+ ];
1167
+ booleanOperators = [
1168
+ { value: 'isTrue', label: 'is true', needsValue: false },
1169
+ { value: 'isFalse', label: 'is false', needsValue: false },
1170
+ ];
1171
+ ngOnInit() {
1172
+ // Initialize from existing filter config
1173
+ if (this.arrayMapping.filter?.enabled && this.arrayMapping.filter.root) {
1174
+ this.filterEnabled.set(true);
1175
+ this.rootGroup.set(this.cloneGroup(this.arrayMapping.filter.root));
1176
+ }
1177
+ }
1178
+ createEmptyGroup() {
1179
+ return {
1180
+ id: this.generateId(),
1181
+ type: 'group',
1182
+ logic: 'and',
1183
+ children: [],
1184
+ };
1185
+ }
1186
+ generateId() {
1187
+ return `filter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1188
+ }
1189
+ cloneGroup(group) {
1190
+ return {
1191
+ ...group,
1192
+ children: group.children.map((child) => child.type === 'group' ? this.cloneGroup(child) : { ...child }),
1193
+ };
1194
+ }
1195
+ collectFields(fields, prefix, result) {
1196
+ for (const field of fields) {
1197
+ const path = prefix ? `${prefix}.${field.name}` : field.name;
1198
+ if (field.type !== 'object' && field.type !== 'array') {
1199
+ result.push({ path, name: field.name, type: field.type });
1200
+ }
1201
+ if (field.children) {
1202
+ this.collectFields(field.children, path, result);
1203
+ }
1204
+ }
1205
+ }
1206
+ getOperatorsForField(fieldPath) {
1207
+ const field = this.availableFields().find((f) => f.path === fieldPath);
1208
+ if (!field)
1209
+ return this.stringOperators;
1210
+ switch (field.type) {
1211
+ case 'number':
1212
+ return this.numberOperators;
1213
+ case 'boolean':
1214
+ return this.booleanOperators;
1215
+ default:
1216
+ return this.stringOperators;
1217
+ }
1218
+ }
1219
+ getFieldType(fieldPath) {
1220
+ const field = this.availableFields().find((f) => f.path === fieldPath);
1221
+ return field?.type || 'string';
1222
+ }
1223
+ operatorNeedsValue(operator) {
1224
+ const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
1225
+ const op = allOperators.find((o) => o.value === operator);
1226
+ return op?.needsValue ?? true;
1227
+ }
1228
+ isCondition(item) {
1229
+ return item.type === 'condition';
1230
+ }
1231
+ isGroup(item) {
1232
+ return item.type === 'group';
1233
+ }
1234
+ addCondition(group) {
1235
+ const fields = this.availableFields();
1236
+ const firstField = fields[0];
1237
+ const newCondition = {
1238
+ id: this.generateId(),
1239
+ type: 'condition',
1240
+ field: firstField?.path || '',
1241
+ fieldName: firstField?.name || '',
1242
+ operator: 'equals',
1243
+ value: '',
1244
+ valueType: firstField?.type || 'string',
1245
+ };
1246
+ group.children = [...group.children, newCondition];
1247
+ this.triggerUpdate();
1248
+ }
1249
+ addGroup(parentGroup) {
1250
+ const newGroup = {
1251
+ id: this.generateId(),
1252
+ type: 'group',
1253
+ logic: parentGroup.logic === 'and' ? 'or' : 'and', // Default to opposite logic
1254
+ children: [],
1255
+ };
1256
+ parentGroup.children = [...parentGroup.children, newGroup];
1257
+ this.triggerUpdate();
1258
+ }
1259
+ removeItem(parentGroup, itemId) {
1260
+ parentGroup.children = parentGroup.children.filter((c) => c.id !== itemId);
1261
+ this.triggerUpdate();
1262
+ }
1263
+ onFieldChange(condition, fieldPath) {
1264
+ const field = this.availableFields().find((f) => f.path === fieldPath);
1265
+ if (field) {
1266
+ condition.field = fieldPath;
1267
+ condition.fieldName = field.name;
1268
+ condition.valueType = field.type;
1269
+ // Reset operator to first valid option for new type
1270
+ const operators = this.getOperatorsForField(fieldPath);
1271
+ condition.operator = operators[0].value;
1272
+ condition.value = '';
1273
+ }
1274
+ this.triggerUpdate();
1275
+ }
1276
+ onOperatorChange(condition, operator) {
1277
+ condition.operator = operator;
1278
+ if (!this.operatorNeedsValue(operator)) {
1279
+ condition.value = '';
1280
+ }
1281
+ this.triggerUpdate();
1282
+ }
1283
+ onValueChange(condition, value) {
1284
+ if (condition.valueType === 'number') {
1285
+ condition.value = parseFloat(value) || 0;
1286
+ }
1287
+ else {
1288
+ condition.value = value;
1289
+ }
1290
+ this.triggerUpdate();
1291
+ }
1292
+ onLogicChange(group, logic) {
1293
+ group.logic = logic;
1294
+ this.triggerUpdate();
1295
+ }
1296
+ triggerUpdate() {
1297
+ // Trigger signal update by creating a new reference
1298
+ this.rootGroup.set(this.cloneGroup(this.rootGroup()));
1299
+ }
1300
+ hasConditions(group) {
1301
+ return group.children.length > 0;
1302
+ }
1303
+ countConditions(group) {
1304
+ let count = 0;
1305
+ for (const child of group.children) {
1306
+ if (child.type === 'condition') {
1307
+ count++;
1308
+ }
1309
+ else {
1310
+ count += this.countConditions(child);
1311
+ }
1312
+ }
1313
+ return count;
1314
+ }
1315
+ onSave() {
1316
+ if (!this.filterEnabled()) {
1317
+ this.save.emit(undefined);
1318
+ }
1319
+ else {
1320
+ this.save.emit({
1321
+ enabled: true,
1322
+ root: this.rootGroup(),
1323
+ });
1324
+ }
1325
+ }
1326
+ onClose() {
1327
+ this.close.emit();
1328
+ }
1329
+ onBackdropClick(event) {
1330
+ if (event.target.classList.contains('modal-backdrop')) {
1331
+ this.onClose();
1332
+ }
1333
+ }
1334
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1335
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArrayFilterModalComponent, isStandalone: true, selector: "array-filter-modal", inputs: { arrayMapping: "arrayMapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"filter-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>filter_list</mat-icon>\n <span>Array Filter</span>\n </div>\n <span class=\"array-path\">{{ arrayMapping.sourceArray.name }}[] &rarr; {{ arrayMapping.targetArray.name }}[]</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <!-- Filter mode selection -->\n <div class=\"filter-mode\">\n <mat-radio-group [value]=\"filterEnabled()\" (change)=\"filterEnabled.set($event.value)\">\n <mat-radio-button [value]=\"false\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>select_all</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">No filter</span>\n <span class=\"mode-desc\">Map all records from source to target</span>\n </div>\n </div>\n </mat-radio-button>\n <mat-radio-button [value]=\"true\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Filter records</span>\n <span class=\"mode-desc\">Only map records matching conditions</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Conditions builder (only when filter enabled) -->\n @if (filterEnabled()) {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <!-- Root group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <!-- Group header with logic toggle -->\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Group children -->\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n <!-- Logic connector between items -->\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <!-- Condition row -->\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <!-- Field selector -->\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Operator selector -->\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Value input (only if operator needs value) -->\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"number\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"text\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n }\n }\n\n <!-- Remove condition button -->\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <!-- Nested group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n <!-- Empty state -->\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition or group.</span>\n </div>\n }\n </div>\n\n <!-- Group actions -->\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.filter-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:600px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .array-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.filter-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.filter-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.filter-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.filter-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.filter-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.filter-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.filter-mode .mode-text{display:flex;flex-direction:column;gap:2px}.filter-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.filter-mode .mode-desc{font-size:12px;color:#64748b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#fffbeb;border-color:#f59e0b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#f59e0b}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#f59e0b;border-color:#f59e0b;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
1336
+ }
1337
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, decorators: [{
1338
+ type: Component,
1339
+ args: [{ selector: 'array-filter-modal', standalone: true, imports: [
1340
+ CommonModule,
1341
+ FormsModule,
1342
+ MatButtonModule,
1343
+ MatIconModule,
1344
+ MatSelectModule,
1345
+ MatInputModule,
1346
+ MatFormFieldModule,
1347
+ MatRadioModule,
1348
+ MatSlideToggleModule,
1349
+ MatTooltipModule,
1350
+ MatDividerModule,
1351
+ ], template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"filter-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>filter_list</mat-icon>\n <span>Array Filter</span>\n </div>\n <span class=\"array-path\">{{ arrayMapping.sourceArray.name }}[] &rarr; {{ arrayMapping.targetArray.name }}[]</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <!-- Filter mode selection -->\n <div class=\"filter-mode\">\n <mat-radio-group [value]=\"filterEnabled()\" (change)=\"filterEnabled.set($event.value)\">\n <mat-radio-button [value]=\"false\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>select_all</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">No filter</span>\n <span class=\"mode-desc\">Map all records from source to target</span>\n </div>\n </div>\n </mat-radio-button>\n <mat-radio-button [value]=\"true\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Filter records</span>\n <span class=\"mode-desc\">Only map records matching conditions</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Conditions builder (only when filter enabled) -->\n @if (filterEnabled()) {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <!-- Root group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">\n Apply\n </button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <!-- Group header with logic toggle -->\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Group children -->\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n <!-- Logic connector between items -->\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <!-- Condition row -->\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <!-- Field selector -->\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Operator selector -->\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Value input (only if operator needs value) -->\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"number\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input\n matInput\n type=\"text\"\n [value]=\"item.value\"\n (input)=\"onValueChange(item, $any($event.target).value)\"\n />\n </mat-form-field>\n }\n }\n\n <!-- Remove condition button -->\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <!-- Nested group -->\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n <!-- Empty state -->\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition or group.</span>\n </div>\n }\n </div>\n\n <!-- Group actions -->\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.filter-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:600px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .array-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.filter-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.filter-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.filter-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.filter-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.filter-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.filter-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.filter-mode .mode-text{display:flex;flex-direction:column;gap:2px}.filter-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.filter-mode .mode-desc{font-size:12px;color:#64748b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#fffbeb;border-color:#f59e0b}.filter-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#f59e0b}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#f59e0b;border-color:#f59e0b;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"] }]
1352
+ }], propDecorators: { arrayMapping: [{
1353
+ type: Input,
1354
+ args: [{ required: true }]
1355
+ }], save: [{
1356
+ type: Output
1357
+ }], close: [{
1358
+ type: Output
1359
+ }] } });
1360
+
1361
+ class ArraySelectorModalComponent {
1362
+ mapping;
1363
+ save = new EventEmitter();
1364
+ close = new EventEmitter();
1365
+ selectionMode = signal('first', ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
1366
+ conditionGroup = signal(this.createEmptyGroup(), ...(ngDevMode ? [{ debugName: "conditionGroup" }] : []));
1367
+ // Available fields from the source array's children
1368
+ availableFields = computed(() => {
1369
+ const fields = [];
1370
+ this.collectFields(this.mapping.sourceArray.children || [], '', fields);
1371
+ return fields;
1372
+ }, ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
1373
+ // Operators by type
1374
+ stringOperators = [
1375
+ { value: 'equals', label: 'equals', needsValue: true },
1376
+ { value: 'notEquals', label: 'not equals', needsValue: true },
1377
+ { value: 'contains', label: 'contains', needsValue: true },
1378
+ { value: 'notContains', label: 'does not contain', needsValue: true },
1379
+ { value: 'startsWith', label: 'starts with', needsValue: true },
1380
+ { value: 'endsWith', label: 'ends with', needsValue: true },
1381
+ { value: 'isEmpty', label: 'is empty', needsValue: false },
1382
+ { value: 'isNotEmpty', label: 'is not empty', needsValue: false },
1383
+ ];
1384
+ numberOperators = [
1385
+ { value: 'equals', label: 'equals', needsValue: true },
1386
+ { value: 'notEquals', label: 'not equals', needsValue: true },
1387
+ { value: 'greaterThan', label: 'greater than', needsValue: true },
1388
+ { value: 'lessThan', label: 'less than', needsValue: true },
1389
+ { value: 'greaterThanOrEqual', label: 'greater or equal', needsValue: true },
1390
+ { value: 'lessThanOrEqual', label: 'less or equal', needsValue: true },
1391
+ ];
1392
+ booleanOperators = [
1393
+ { value: 'isTrue', label: 'is true', needsValue: false },
1394
+ { value: 'isFalse', label: 'is false', needsValue: false },
1395
+ ];
1396
+ ngOnInit() {
1397
+ // Initialize from existing selector config
1398
+ if (this.mapping.selector) {
1399
+ this.selectionMode.set(this.mapping.selector.mode);
1400
+ if (this.mapping.selector.condition) {
1401
+ this.conditionGroup.set(this.cloneGroup(this.mapping.selector.condition));
1402
+ }
1403
+ }
1404
+ }
1405
+ createEmptyGroup() {
1406
+ return {
1407
+ id: this.generateId(),
1408
+ type: 'group',
1409
+ logic: 'and',
1410
+ children: [],
1411
+ };
1412
+ }
1413
+ generateId() {
1414
+ return `filter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1415
+ }
1416
+ cloneGroup(group) {
1417
+ return {
1418
+ ...group,
1419
+ children: group.children.map((child) => child.type === 'group' ? this.cloneGroup(child) : { ...child }),
1420
+ };
1421
+ }
1422
+ collectFields(fields, prefix, result) {
1423
+ for (const field of fields) {
1424
+ const path = prefix ? `${prefix}.${field.name}` : field.name;
1425
+ if (field.type !== 'object' && field.type !== 'array') {
1426
+ result.push({ path, name: field.name, type: field.type });
1427
+ }
1428
+ if (field.children) {
1429
+ this.collectFields(field.children, path, result);
1430
+ }
1431
+ }
1432
+ }
1433
+ getOperatorsForField(fieldPath) {
1434
+ const field = this.availableFields().find((f) => f.path === fieldPath);
1435
+ if (!field)
1436
+ return this.stringOperators;
1437
+ switch (field.type) {
1438
+ case 'number':
1439
+ return this.numberOperators;
1440
+ case 'boolean':
1441
+ return this.booleanOperators;
1442
+ default:
1443
+ return this.stringOperators;
1444
+ }
1445
+ }
1446
+ operatorNeedsValue(operator) {
1447
+ const allOperators = [...this.stringOperators, ...this.numberOperators, ...this.booleanOperators];
1448
+ const op = allOperators.find((o) => o.value === operator);
1449
+ return op?.needsValue ?? true;
1450
+ }
1451
+ isCondition(item) {
1452
+ return item.type === 'condition';
1453
+ }
1454
+ isGroup(item) {
1455
+ return item.type === 'group';
1456
+ }
1457
+ addCondition(group) {
1458
+ const fields = this.availableFields();
1459
+ const firstField = fields[0];
1460
+ const newCondition = {
1461
+ id: this.generateId(),
1462
+ type: 'condition',
1463
+ field: firstField?.path || '',
1464
+ fieldName: firstField?.name || '',
1465
+ operator: 'equals',
1466
+ value: '',
1467
+ valueType: firstField?.type || 'string',
1468
+ };
1469
+ group.children = [...group.children, newCondition];
1470
+ this.triggerUpdate();
1471
+ }
1472
+ addGroup(parentGroup) {
1473
+ const newGroup = {
1474
+ id: this.generateId(),
1475
+ type: 'group',
1476
+ logic: parentGroup.logic === 'and' ? 'or' : 'and',
1477
+ children: [],
1478
+ };
1479
+ parentGroup.children = [...parentGroup.children, newGroup];
1480
+ this.triggerUpdate();
1481
+ }
1482
+ removeItem(parentGroup, itemId) {
1483
+ parentGroup.children = parentGroup.children.filter((c) => c.id !== itemId);
1484
+ this.triggerUpdate();
1485
+ }
1486
+ onFieldChange(condition, fieldPath) {
1487
+ const field = this.availableFields().find((f) => f.path === fieldPath);
1488
+ if (field) {
1489
+ condition.field = fieldPath;
1490
+ condition.fieldName = field.name;
1491
+ condition.valueType = field.type;
1492
+ const operators = this.getOperatorsForField(fieldPath);
1493
+ condition.operator = operators[0].value;
1494
+ condition.value = '';
1495
+ }
1496
+ this.triggerUpdate();
1497
+ }
1498
+ onOperatorChange(condition, operator) {
1499
+ condition.operator = operator;
1500
+ if (!this.operatorNeedsValue(operator)) {
1501
+ condition.value = '';
1502
+ }
1503
+ this.triggerUpdate();
1504
+ }
1505
+ onValueChange(condition, value) {
1506
+ if (condition.valueType === 'number') {
1507
+ condition.value = parseFloat(value) || 0;
1508
+ }
1509
+ else {
1510
+ condition.value = value;
1511
+ }
1512
+ this.triggerUpdate();
1513
+ }
1514
+ onLogicChange(group, logic) {
1515
+ group.logic = logic;
1516
+ this.triggerUpdate();
1517
+ }
1518
+ triggerUpdate() {
1519
+ this.conditionGroup.set(this.cloneGroup(this.conditionGroup()));
1520
+ }
1521
+ onSave() {
1522
+ const config = {
1523
+ mode: this.selectionMode(),
1524
+ };
1525
+ if (this.selectionMode() === 'condition') {
1526
+ config.condition = this.conditionGroup();
1527
+ }
1528
+ this.save.emit(config);
1529
+ }
1530
+ onClose() {
1531
+ this.close.emit();
1532
+ }
1533
+ onBackdropClick(event) {
1534
+ if (event.target.classList.contains('modal-backdrop')) {
1535
+ this.onClose();
1536
+ }
1537
+ }
1538
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1539
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ArraySelectorModalComponent, isStandalone: true, selector: "array-selector-modal", inputs: { mapping: "mapping" }, outputs: { save: "save", close: "close" }, ngImport: i0, template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"selector-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>swap_horiz</mat-icon>\n <span>Array to Object</span>\n </div>\n <span class=\"mapping-path\">{{ mapping.sourceArray.name }}[] &rarr; {{ mapping.targetObject.name }}</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <p class=\"description\">\n Select which item from the array should be mapped to the target object.\n </p>\n\n <!-- Selection mode -->\n <div class=\"selection-mode\">\n <mat-radio-group [value]=\"selectionMode()\" (change)=\"selectionMode.set($event.value)\">\n <mat-radio-button value=\"first\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>first_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First item</span>\n <span class=\"mode-desc\">Use the first item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"last\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>last_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Last item</span>\n <span class=\"mode-desc\">Use the last item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"condition\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First matching condition</span>\n <span class=\"mode-desc\">Use the first item that matches the condition</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Condition builder (only when condition mode selected) -->\n @if (selectionMode() === 'condition') {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: conditionGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"number\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"text\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n }\n }\n\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition to filter.</span>\n </div>\n }\n </div>\n\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.selector-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:580px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .mapping-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.description{font-size:14px;color:#64748b;margin:0 0 16px}.selection-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.selection-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.selection-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.selection-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.selection-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.selection-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.selection-mode .mode-text{display:flex;flex-direction:column;gap:2px}.selection-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.selection-mode .mode-desc{font-size:12px;color:#64748b}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#f5f3ff;border-color:#8b5cf6}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#8b5cf6}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#8b5cf6;border-color:#8b5cf6;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
1540
+ }
1541
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, decorators: [{
1542
+ type: Component,
1543
+ args: [{ selector: 'array-selector-modal', standalone: true, imports: [
1544
+ CommonModule,
1545
+ FormsModule,
1546
+ MatButtonModule,
1547
+ MatIconModule,
1548
+ MatSelectModule,
1549
+ MatInputModule,
1550
+ MatFormFieldModule,
1551
+ MatRadioModule,
1552
+ MatSlideToggleModule,
1553
+ MatTooltipModule,
1554
+ MatDividerModule,
1555
+ ], template: "<div class=\"modal-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"selector-modal\">\n <div class=\"modal-header\">\n <div class=\"header-title\">\n <mat-icon>swap_horiz</mat-icon>\n <span>Array to Object</span>\n </div>\n <span class=\"mapping-path\">{{ mapping.sourceArray.name }}[] &rarr; {{ mapping.targetObject.name }}</span>\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"modal-body\">\n <p class=\"description\">\n Select which item from the array should be mapped to the target object.\n </p>\n\n <!-- Selection mode -->\n <div class=\"selection-mode\">\n <mat-radio-group [value]=\"selectionMode()\" (change)=\"selectionMode.set($event.value)\">\n <mat-radio-button value=\"first\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>first_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First item</span>\n <span class=\"mode-desc\">Use the first item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"last\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>last_page</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">Last item</span>\n <span class=\"mode-desc\">Use the last item in the array</span>\n </div>\n </div>\n </mat-radio-button>\n\n <mat-radio-button value=\"condition\" class=\"mode-option\">\n <div class=\"mode-content\">\n <mat-icon>filter_alt</mat-icon>\n <div class=\"mode-text\">\n <span class=\"mode-label\">First matching condition</span>\n <span class=\"mode-desc\">Use the first item that matches the condition</span>\n </div>\n </div>\n </mat-radio-button>\n </mat-radio-group>\n </div>\n\n <!-- Condition builder (only when condition mode selected) -->\n @if (selectionMode() === 'condition') {\n <mat-divider></mat-divider>\n\n <div class=\"conditions-section\">\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: conditionGroup(), isRoot: true }\"></ng-container>\n </div>\n }\n </div>\n\n <div class=\"modal-footer\">\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\n </div>\n </div>\n</div>\n\n<!-- Recursive group template -->\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\n <div class=\"group-header\">\n <div class=\"logic-toggle\">\n <span class=\"logic-label\">Match</span>\n <mat-radio-group [value]=\"group.logic\" (change)=\"onLogicChange(group, $event.value)\">\n <mat-radio-button value=\"and\">ALL (AND)</mat-radio-button>\n <mat-radio-button value=\"or\">ANY (OR)</mat-radio-button>\n </mat-radio-group>\n </div>\n @if (!isRoot) {\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <div class=\"group-children\">\n @for (item of group.children; track item.id; let i = $index) {\n @if (i > 0) {\n <div class=\"logic-connector\">\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\n {{ group.logic | uppercase }}\n </span>\n </div>\n }\n\n @if (isCondition(item)) {\n <div class=\"condition-row\">\n <div class=\"condition-inputs\">\n <mat-form-field appearance=\"outline\" class=\"field-select\">\n <mat-label>Field</mat-label>\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\n @for (field of availableFields(); track field.path) {\n <mat-option [value]=\"field.path\">\n {{ field.name }}\n <span class=\"field-type\">({{ field.type }})</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\n <mat-label>Operator</mat-label>\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\n @for (op of getOperatorsForField(item.field); track op.value) {\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n @if (operatorNeedsValue(item.operator)) {\n @if (item.valueType === 'boolean') {\n <mat-slide-toggle\n [checked]=\"item.value === true\"\n (change)=\"onValueChange(item, $event.checked)\"\n class=\"bool-toggle\"\n >\n {{ item.value ? 'true' : 'false' }}\n </mat-slide-toggle>\n } @else if (item.valueType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"number\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" class=\"value-input\">\n <mat-label>Value</mat-label>\n <input matInput type=\"text\" [value]=\"item.value\" (input)=\"onValueChange(item, $any($event.target).value)\" />\n </mat-form-field>\n }\n }\n\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove condition\" (click)=\"removeItem(group, item.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </div>\n } @else if (isGroup(item)) {\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\n }\n }\n\n @if (group.children.length === 0) {\n <div class=\"empty-group\">\n <mat-icon>info_outline</mat-icon>\n <span>No conditions. Add a condition to filter.</span>\n </div>\n }\n </div>\n\n <div class=\"group-actions\">\n <button mat-stroked-button class=\"add-condition-btn\" (click)=\"addCondition(group)\">\n <mat-icon>add</mat-icon>\n Add Condition\n </button>\n <button mat-stroked-button class=\"add-group-btn\" (click)=\"addGroup(group)\">\n <mat-icon>folder_open</mat-icon>\n Add Group\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".modal-backdrop{position:fixed;inset:0;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px}.selector-modal{position:relative;background:#fff;border-radius:12px;box-shadow:0 8px 32px #00000026;width:580px;max-width:100%;max-height:calc(100vh - 40px);display:flex;flex-direction:column;overflow:hidden}.modal-header{display:flex;align-items:center;gap:12px;padding:16px 20px;background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;flex-shrink:0}.modal-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.modal-header .mapping-path{flex:1;font-size:13px;opacity:.9;text-align:right;margin-right:8px}.modal-header .close-btn{color:#fff;opacity:.9}.modal-header .close-btn:hover{opacity:1}.modal-body{flex:1;overflow-y:auto;padding:20px;min-height:0}.description{font-size:14px;color:#64748b;margin:0 0 16px}.selection-mode mat-radio-group{display:flex;flex-direction:column;gap:12px}.selection-mode .mode-option ::ng-deep .mdc-form-field{align-items:flex-start}.selection-mode .mode-option ::ng-deep .mdc-radio{margin-top:4px}.selection-mode .mode-content{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;transition:all .2s ease;cursor:pointer}.selection-mode .mode-content:hover{background:#f1f5f9;border-color:#cbd5e1}.selection-mode .mode-content mat-icon{color:#64748b;margin-top:2px}.selection-mode .mode-text{display:flex;flex-direction:column;gap:2px}.selection-mode .mode-label{font-size:14px;font-weight:500;color:#1e293b}.selection-mode .mode-desc{font-size:12px;color:#64748b}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content{background:#f5f3ff;border-color:#8b5cf6}.selection-mode mat-radio-button.mat-mdc-radio-checked .mode-content mat-icon{color:#8b5cf6}mat-divider{margin:20px 0}.conditions-section{display:flex;flex-direction:column;gap:16px}.filter-group{border-radius:8px}.filter-group.root-group{background:#f8fafc;border:1px solid #e2e8f0;padding:16px}.filter-group.nested-group{background:#fff;border:2px dashed #cbd5e1;padding:12px;margin-top:8px}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #e2e8f0}.logic-toggle{display:flex;align-items:center;gap:12px}.logic-toggle .logic-label{font-size:13px;font-weight:500;color:#475569}.logic-toggle mat-radio-group{display:flex;gap:12px}.logic-toggle mat-radio-button{font-size:12px}.logic-toggle mat-radio-button ::ng-deep .mdc-label{font-size:12px}.remove-group-btn{color:#94a3b8}.remove-group-btn:hover{color:#ef4444}.group-children{display:flex;flex-direction:column}.logic-connector{display:flex;align-items:center;justify-content:center;padding:8px 0}.logic-connector .logic-badge{font-size:10px;font-weight:700;padding:3px 10px;border-radius:12px;letter-spacing:.5px}.logic-connector .logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-connector .logic-badge.or{background:#fef3c7;color:#b45309}.condition-row .condition-inputs{display:flex;align-items:flex-start;gap:8px;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.condition-row .condition-inputs .field-select{flex:1;min-width:120px}.condition-row .condition-inputs .operator-select{flex:1;min-width:130px}.condition-row .condition-inputs .value-input{flex:1;min-width:100px}.condition-row .condition-inputs .bool-toggle{padding-top:12px;min-width:80px}.condition-row .condition-inputs .remove-btn{color:#94a3b8;align-self:center}.condition-row .condition-inputs .remove-btn:hover{color:#ef4444}.condition-row .condition-inputs mat-form-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.nested-group .condition-row .condition-inputs{background:#f8fafc}.field-type{font-size:11px;color:#94a3b8;margin-left:4px}.empty-group{display:flex;align-items:center;gap:8px;padding:16px;background:#fef3c7;border-radius:8px;color:#92400e;font-size:13px}.empty-group mat-icon{font-size:20px;width:20px;height:20px}.group-actions{display:flex;gap:8px;margin-top:12px;padding-top:12px;border-top:1px solid #e2e8f0}.add-condition-btn{color:#8b5cf6;border-color:#8b5cf6;font-size:12px}.add-condition-btn mat-icon{font-size:16px;width:16px;height:16px}.add-group-btn{color:#6366f1;border-color:#6366f1;font-size:12px}.add-group-btn mat-icon{font-size:16px;width:16px;height:16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 20px;border-top:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0}\n"] }]
1556
+ }], propDecorators: { mapping: [{
1557
+ type: Input,
1558
+ args: [{ required: true }]
1559
+ }], save: [{
1560
+ type: Output
1561
+ }], close: [{
1562
+ type: Output
1563
+ }] } });
1564
+
1565
+ class DefaultValuePopoverComponent {
1566
+ field;
1567
+ existingValue;
1568
+ position;
1569
+ save = new EventEmitter();
1570
+ delete = new EventEmitter();
1571
+ close = new EventEmitter();
1572
+ stringValue = '';
1573
+ numberValue = null;
1574
+ booleanValue = false;
1575
+ dateValue = null;
1576
+ ngOnInit() {
1577
+ if (this.existingValue) {
1578
+ switch (this.existingValue.valueType) {
1579
+ case 'string':
1580
+ this.stringValue = this.existingValue.value || '';
1581
+ break;
1582
+ case 'number':
1583
+ this.numberValue = this.existingValue.value;
1584
+ break;
1585
+ case 'boolean':
1586
+ this.booleanValue = this.existingValue.value;
1587
+ break;
1588
+ case 'date':
1589
+ this.dateValue = this.existingValue.value ? new Date(this.existingValue.value) : null;
1590
+ break;
1591
+ }
1592
+ }
1593
+ }
1594
+ get fieldType() {
1595
+ if (this.field.type === 'date' || this.field.name.toLowerCase().includes('date')) {
1596
+ return 'date';
1597
+ }
1598
+ return this.field.type;
1599
+ }
1600
+ onSave() {
1601
+ let value;
1602
+ switch (this.fieldType) {
1603
+ case 'number':
1604
+ value = this.numberValue;
1605
+ break;
1606
+ case 'boolean':
1607
+ value = this.booleanValue;
1608
+ break;
1609
+ case 'date':
1610
+ value = this.dateValue;
1611
+ break;
1612
+ default:
1613
+ value = this.stringValue || null;
1614
+ }
1615
+ this.save.emit(value);
1616
+ }
1617
+ onDelete() {
1618
+ this.delete.emit();
1619
+ }
1620
+ onClose() {
1621
+ this.close.emit();
1622
+ }
1623
+ onBackdropClick(event) {
1624
+ if (event.target.classList.contains('popover-backdrop')) {
1625
+ this.onClose();
1626
+ }
1627
+ }
1628
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1629
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DefaultValuePopoverComponent, isStandalone: true, selector: "default-value-popover", inputs: { field: "field", existingValue: "existingValue", position: "position" }, outputs: { save: "save", delete: "delete", close: "close" }, ngImport: i0, template: "<div class=\"popover-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"popover-container\">\n <div class=\"popover-header\">\n <div class=\"header-title\">\n <mat-icon>edit</mat-icon>\n <span>Default Value</span>\n </div>\n <button mat-icon-button (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <div class=\"field-info\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-type\">{{ fieldType }}</span>\n </div>\n\n <!-- String input -->\n @if (fieldType === 'string') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput [(ngModel)]=\"stringValue\" placeholder=\"Enter default value\">\n </mat-form-field>\n }\n\n <!-- Number input -->\n @if (fieldType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"numberValue\" placeholder=\"Enter number\">\n </mat-form-field>\n }\n\n <!-- Boolean input -->\n @if (fieldType === 'boolean') {\n <div class=\"boolean-input\">\n <mat-slide-toggle [(ngModel)]=\"booleanValue\">\n {{ booleanValue ? 'True' : 'False' }}\n </mat-slide-toggle>\n </div>\n }\n\n <!-- Date input -->\n @if (fieldType === 'date') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Date</mat-label>\n <input matInput [matDatepicker]=\"picker\" [(ngModel)]=\"dateValue\">\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-datepicker #picker></mat-datepicker>\n </mat-form-field>\n }\n </div>\n\n <div class=\"popover-actions\">\n @if (existingValue) {\n <button mat-button color=\"warn\" (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n Remove\n </button>\n }\n <span class=\"spacer\"></span>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".popover-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center}.popover-container{background:#fff;border-radius:12px;box-shadow:0 8px 32px #0003;min-width:320px;max-width:400px;overflow:hidden}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:linear-gradient(135deg,#10b981,#059669);color:#fff}.popover-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.popover-header .header-title mat-icon{font-size:20px;width:20px;height:20px}.popover-header button{color:#fff}.popover-content{padding:20px}.field-info{display:flex;align-items:center;gap:8px;margin-bottom:16px;padding:10px 12px;background:#f8fafc;border-radius:8px}.field-info .field-name{font-weight:600;color:#1e293b}.field-info .field-type{font-size:12px;color:#64748b;background:#e2e8f0;padding:2px 8px;border-radius:4px;text-transform:uppercase}.full-width{width:100%}.boolean-input{padding:12px 0}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;background:#f8fafc;border-top:1px solid #e2e8f0}.popover-actions .spacer{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i6$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i6$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i6$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "ngmodule", type: MatNativeDateModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
1630
+ }
1631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, decorators: [{
1632
+ type: Component,
1633
+ args: [{ selector: 'default-value-popover', standalone: true, imports: [
1634
+ CommonModule,
1635
+ FormsModule,
1636
+ MatButtonModule,
1637
+ MatIconModule,
1638
+ MatInputModule,
1639
+ MatFormFieldModule,
1640
+ MatDatepickerModule,
1641
+ MatNativeDateModule,
1642
+ MatSelectModule,
1643
+ MatSlideToggleModule,
1644
+ ], template: "<div class=\"popover-backdrop\" (click)=\"onBackdropClick($event)\">\n <div class=\"popover-container\">\n <div class=\"popover-header\">\n <div class=\"header-title\">\n <mat-icon>edit</mat-icon>\n <span>Default Value</span>\n </div>\n <button mat-icon-button (click)=\"onClose()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"popover-content\">\n <div class=\"field-info\">\n <span class=\"field-name\">{{ field.name }}</span>\n <span class=\"field-type\">{{ fieldType }}</span>\n </div>\n\n <!-- String input -->\n @if (fieldType === 'string') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput [(ngModel)]=\"stringValue\" placeholder=\"Enter default value\">\n </mat-form-field>\n }\n\n <!-- Number input -->\n @if (fieldType === 'number') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Value</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"numberValue\" placeholder=\"Enter number\">\n </mat-form-field>\n }\n\n <!-- Boolean input -->\n @if (fieldType === 'boolean') {\n <div class=\"boolean-input\">\n <mat-slide-toggle [(ngModel)]=\"booleanValue\">\n {{ booleanValue ? 'True' : 'False' }}\n </mat-slide-toggle>\n </div>\n }\n\n <!-- Date input -->\n @if (fieldType === 'date') {\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Default Date</mat-label>\n <input matInput [matDatepicker]=\"picker\" [(ngModel)]=\"dateValue\">\n <mat-datepicker-toggle matIconSuffix [for]=\"picker\"></mat-datepicker-toggle>\n <mat-datepicker #picker></mat-datepicker>\n </mat-form-field>\n }\n </div>\n\n <div class=\"popover-actions\">\n @if (existingValue) {\n <button mat-button color=\"warn\" (click)=\"onDelete()\">\n <mat-icon>delete</mat-icon>\n Remove\n </button>\n }\n <span class=\"spacer\"></span>\n <button mat-button (click)=\"onClose()\">Cancel</button>\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Save</button>\n </div>\n </div>\n</div>\n", styles: [".popover-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:#0000004d;z-index:1000;display:flex;align-items:center;justify-content:center}.popover-container{background:#fff;border-radius:12px;box-shadow:0 8px 32px #0003;min-width:320px;max-width:400px;overflow:hidden}.popover-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;background:linear-gradient(135deg,#10b981,#059669);color:#fff}.popover-header .header-title{display:flex;align-items:center;gap:8px;font-size:16px;font-weight:600}.popover-header .header-title mat-icon{font-size:20px;width:20px;height:20px}.popover-header button{color:#fff}.popover-content{padding:20px}.field-info{display:flex;align-items:center;gap:8px;margin-bottom:16px;padding:10px 12px;background:#f8fafc;border-radius:8px}.field-info .field-name{font-weight:600;color:#1e293b}.field-info .field-type{font-size:12px;color:#64748b;background:#e2e8f0;padding:2px 8px;border-radius:4px;text-transform:uppercase}.full-width{width:100%}.boolean-input{padding:12px 0}.popover-actions{display:flex;align-items:center;gap:8px;padding:16px 20px;background:#f8fafc;border-top:1px solid #e2e8f0}.popover-actions .spacer{flex:1}\n"] }]
1645
+ }], propDecorators: { field: [{
1646
+ type: Input
1647
+ }], existingValue: [{
1648
+ type: Input
1649
+ }], position: [{
1650
+ type: Input
1651
+ }], save: [{
1652
+ type: Output
1653
+ }], delete: [{
1654
+ type: Output
1655
+ }], close: [{
1656
+ type: Output
1657
+ }] } });
1658
+
1659
+ class DataMapperComponent {
1660
+ sourceSchema;
1661
+ targetSchema;
1662
+ sampleData = {};
1663
+ mappingsChange = new EventEmitter();
1664
+ svgContainer;
1665
+ svgElement;
1666
+ mappingService = inject(MappingService);
1667
+ svgConnectorService = inject(SvgConnectorService);
1668
+ transformationService = inject(TransformationService);
1669
+ // Field positions from both trees
1670
+ sourcePositions = new Map();
1671
+ targetPositions = new Map();
1672
+ // Visual state
1673
+ connections = signal([], ...(ngDevMode ? [{ debugName: "connections" }] : []));
1674
+ dragPath = signal(null, ...(ngDevMode ? [{ debugName: "dragPath" }] : []));
1675
+ selectedMappingId = signal(null, ...(ngDevMode ? [{ debugName: "selectedMappingId" }] : []));
1676
+ popoverPosition = signal(null, ...(ngDevMode ? [{ debugName: "popoverPosition" }] : []));
1677
+ // Array filter modal state
1678
+ showArrayFilterModal = signal(false, ...(ngDevMode ? [{ debugName: "showArrayFilterModal" }] : []));
1679
+ selectedArrayMapping = signal(null, ...(ngDevMode ? [{ debugName: "selectedArrayMapping" }] : []));
1680
+ // Array selector modal state (for array-to-object)
1681
+ showArraySelectorModal = signal(false, ...(ngDevMode ? [{ debugName: "showArraySelectorModal" }] : []));
1682
+ selectedArrayToObjectMapping = signal(null, ...(ngDevMode ? [{ debugName: "selectedArrayToObjectMapping" }] : []));
1683
+ // Default value popover state
1684
+ showDefaultValuePopover = signal(false, ...(ngDevMode ? [{ debugName: "showDefaultValuePopover" }] : []));
1685
+ selectedDefaultValueField = signal(null, ...(ngDevMode ? [{ debugName: "selectedDefaultValueField" }] : []));
1686
+ defaultValuePopoverPosition = signal(null, ...(ngDevMode ? [{ debugName: "defaultValuePopoverPosition" }] : []));
1687
+ // Computed values
1688
+ mappings = computed(() => this.mappingService.allMappings(), ...(ngDevMode ? [{ debugName: "mappings" }] : []));
1689
+ arrayMappings = computed(() => this.mappingService.allArrayMappings(), ...(ngDevMode ? [{ debugName: "arrayMappings" }] : []));
1690
+ defaultValues = computed(() => this.mappingService.allDefaultValues(), ...(ngDevMode ? [{ debugName: "defaultValues" }] : []));
1691
+ selectedMapping = computed(() => this.mappingService.selectedMapping(), ...(ngDevMode ? [{ debugName: "selectedMapping" }] : []));
1692
+ showPopover = computed(() => this.selectedMappingId() !== null && this.popoverPosition() !== null && !this.showArrayFilterModal(), ...(ngDevMode ? [{ debugName: "showPopover" }] : []));
1693
+ isDragging = false;
1694
+ dragSourceField = null;
1695
+ dragStartPoint = null;
1696
+ resizeObserver;
1697
+ ngAfterViewInit() {
1698
+ this.setupResizeObserver();
1699
+ }
1700
+ ngOnDestroy() {
1701
+ if (this.resizeObserver) {
1702
+ this.resizeObserver.disconnect();
1703
+ }
1704
+ }
1705
+ setupResizeObserver() {
1706
+ this.resizeObserver = new ResizeObserver(() => {
1707
+ this.updateConnections();
1708
+ });
1709
+ if (this.svgContainer?.nativeElement) {
1710
+ this.resizeObserver.observe(this.svgContainer.nativeElement);
1711
+ }
1712
+ }
1713
+ onSourcePositionsChanged(positions) {
1714
+ this.sourcePositions = positions;
1715
+ this.updateConnections();
1716
+ }
1717
+ onTargetPositionsChanged(positions) {
1718
+ this.targetPositions = positions;
1719
+ this.updateConnections();
1720
+ }
1721
+ onFieldDragStart(event) {
1722
+ if (!this.svgContainer?.nativeElement)
1723
+ return;
1724
+ const containerRect = this.svgContainer.nativeElement.getBoundingClientRect();
1725
+ const startPoint = this.svgConnectorService.calculateConnectionPoint(event.rect, 'source', containerRect);
1726
+ this.isDragging = true;
1727
+ this.dragSourceField = event.field;
1728
+ this.dragStartPoint = startPoint;
1729
+ document.body.style.cursor = 'grabbing';
1730
+ }
1731
+ onMouseMove(event) {
1732
+ if (!this.isDragging || !this.dragStartPoint || !this.svgContainer?.nativeElement)
1733
+ return;
1734
+ const containerRect = this.svgContainer.nativeElement.getBoundingClientRect();
1735
+ const currentPoint = {
1736
+ x: event.clientX - containerRect.left,
1737
+ y: event.clientY - containerRect.top,
1738
+ };
1739
+ const path = this.svgConnectorService.createDragPath(this.dragStartPoint, currentPoint);
1740
+ this.dragPath.set(path);
1741
+ }
1742
+ onMouseUp(event) {
1743
+ if (this.isDragging) {
1744
+ this.dragPath.set(null);
1745
+ this.isDragging = false;
1746
+ this.dragSourceField = null;
1747
+ this.dragStartPoint = null;
1748
+ document.body.style.cursor = '';
1749
+ }
1750
+ }
1751
+ onFieldDrop(event) {
1752
+ if (!this.dragSourceField)
1753
+ return;
1754
+ // Create mapping
1755
+ const mapping = this.mappingService.createMapping([this.dragSourceField], event.field);
1756
+ this.mappingsChange.emit(this.mappingService.allMappings());
1757
+ this.updateConnections();
1758
+ // Reset drag state
1759
+ this.isDragging = false;
1760
+ this.dragSourceField = null;
1761
+ this.dragStartPoint = null;
1762
+ this.dragPath.set(null);
1763
+ document.body.style.cursor = '';
1764
+ }
1765
+ onConnectionClick(connection, event) {
1766
+ event.stopPropagation();
1767
+ this.selectedMappingId.set(connection.mappingId);
1768
+ this.mappingService.selectMapping(connection.mappingId);
1769
+ // Position popover at click location
1770
+ this.popoverPosition.set({
1771
+ x: event.clientX,
1772
+ y: event.clientY,
1773
+ });
1774
+ this.updateConnections();
1775
+ }
1776
+ onTransformationNodeClick(connection, event) {
1777
+ event.stopPropagation();
1778
+ // If it's an array mapping, show the filter modal
1779
+ if (connection.isArrayMapping) {
1780
+ const arrayMapping = this.mappingService.getArrayMapping(connection.mappingId);
1781
+ if (arrayMapping) {
1782
+ this.selectedArrayMapping.set(arrayMapping);
1783
+ this.showArrayFilterModal.set(true);
1784
+ return;
1785
+ }
1786
+ }
1787
+ // If it's an array-to-object mapping, show the selector modal
1788
+ if (connection.isArrayToObjectMapping) {
1789
+ const arrayToObjectMapping = this.mappingService.getArrayToObjectMapping(connection.mappingId);
1790
+ if (arrayToObjectMapping) {
1791
+ this.selectedArrayToObjectMapping.set(arrayToObjectMapping);
1792
+ this.showArraySelectorModal.set(true);
1793
+ return;
1794
+ }
1795
+ }
1796
+ this.onConnectionClick(connection, event);
1797
+ }
1798
+ onArrayFilterSave(filter) {
1799
+ const arrayMapping = this.selectedArrayMapping();
1800
+ if (arrayMapping) {
1801
+ this.mappingService.updateArrayFilter(arrayMapping.id, filter);
1802
+ this.mappingsChange.emit(this.mappingService.allMappings());
1803
+ }
1804
+ this.closeArrayFilterModal();
1805
+ this.updateConnections();
1806
+ }
1807
+ closeArrayFilterModal() {
1808
+ this.showArrayFilterModal.set(false);
1809
+ this.selectedArrayMapping.set(null);
1810
+ }
1811
+ onArraySelectorSave(selector) {
1812
+ const mapping = this.selectedArrayToObjectMapping();
1813
+ if (mapping) {
1814
+ this.mappingService.updateArrayToObjectSelector(mapping.id, selector);
1815
+ this.mappingsChange.emit(this.mappingService.allMappings());
1816
+ }
1817
+ this.closeArraySelectorModal();
1818
+ this.updateConnections();
1819
+ }
1820
+ closeArraySelectorModal() {
1821
+ this.showArraySelectorModal.set(false);
1822
+ this.selectedArrayToObjectMapping.set(null);
1823
+ }
1824
+ // Default value methods
1825
+ onDefaultValueClick(event) {
1826
+ this.selectedDefaultValueField.set(event.field);
1827
+ this.defaultValuePopoverPosition.set({
1828
+ x: event.rect.right,
1829
+ y: event.rect.top + event.rect.height / 2,
1830
+ });
1831
+ this.showDefaultValuePopover.set(true);
1832
+ }
1833
+ onDefaultValueSave(value) {
1834
+ const field = this.selectedDefaultValueField();
1835
+ if (field) {
1836
+ this.mappingService.setDefaultValue(field, value);
1837
+ this.mappingsChange.emit(this.mappingService.allMappings());
1838
+ }
1839
+ this.closeDefaultValuePopover();
1840
+ }
1841
+ onDefaultValueDelete() {
1842
+ const field = this.selectedDefaultValueField();
1843
+ if (field) {
1844
+ this.mappingService.removeDefaultValue(field.id);
1845
+ this.mappingsChange.emit(this.mappingService.allMappings());
1846
+ }
1847
+ this.closeDefaultValuePopover();
1848
+ }
1849
+ closeDefaultValuePopover() {
1850
+ this.showDefaultValuePopover.set(false);
1851
+ this.selectedDefaultValueField.set(null);
1852
+ this.defaultValuePopoverPosition.set(null);
1853
+ }
1854
+ getExistingDefaultValue(fieldId) {
1855
+ return this.mappingService.getDefaultValue(fieldId);
1856
+ }
1857
+ onPopoverSave(config) {
1858
+ const mappingId = this.selectedMappingId();
1859
+ if (mappingId) {
1860
+ this.mappingService.updateTransformation(mappingId, config);
1861
+ this.mappingsChange.emit(this.mappingService.allMappings());
1862
+ }
1863
+ this.closePopover();
1864
+ this.updateConnections();
1865
+ }
1866
+ onPopoverDelete() {
1867
+ const mappingId = this.selectedMappingId();
1868
+ if (mappingId) {
1869
+ this.mappingService.removeMapping(mappingId);
1870
+ this.mappingsChange.emit(this.mappingService.allMappings());
1871
+ }
1872
+ this.closePopover();
1873
+ this.updateConnections();
1874
+ }
1875
+ closePopover() {
1876
+ this.selectedMappingId.set(null);
1877
+ this.popoverPosition.set(null);
1878
+ this.mappingService.selectMapping(null);
1879
+ this.updateConnections();
1880
+ }
1881
+ updateConnections() {
1882
+ if (!this.svgContainer?.nativeElement)
1883
+ return;
1884
+ const containerRect = this.svgContainer.nativeElement.getBoundingClientRect();
1885
+ const mappings = this.mappingService.allMappings();
1886
+ const selectedId = this.selectedMappingId();
1887
+ const newConnections = [];
1888
+ for (const mapping of mappings) {
1889
+ const targetRect = this.targetPositions.get(mapping.targetField.id);
1890
+ if (!targetRect)
1891
+ continue;
1892
+ const targetPoint = this.svgConnectorService.calculateConnectionPoint(targetRect, 'target', containerRect);
1893
+ const sourcePoints = [];
1894
+ for (const sourceField of mapping.sourceFields) {
1895
+ const sourceRect = this.sourcePositions.get(sourceField.id);
1896
+ if (sourceRect) {
1897
+ sourcePoints.push(this.svgConnectorService.calculateConnectionPoint(sourceRect, 'source', containerRect));
1898
+ }
1899
+ }
1900
+ if (sourcePoints.length === 0)
1901
+ continue;
1902
+ let paths;
1903
+ let midPoint;
1904
+ if (sourcePoints.length === 1) {
1905
+ paths = [this.svgConnectorService.createBezierPath(sourcePoints[0], targetPoint)];
1906
+ midPoint = this.svgConnectorService.getMidPoint(sourcePoints[0], targetPoint);
1907
+ }
1908
+ else {
1909
+ const result = this.svgConnectorService.createMultiSourcePath(sourcePoints, targetPoint);
1910
+ paths = result.paths;
1911
+ midPoint = result.mergePoint;
1912
+ }
1913
+ // Check if this array mapping has a filter
1914
+ let hasFilter = false;
1915
+ if (mapping.isArrayMapping) {
1916
+ const arrayMapping = this.mappingService.getArrayMapping(mapping.id);
1917
+ hasFilter = arrayMapping?.filter?.enabled === true && (arrayMapping?.filter?.root?.children?.length ?? 0) > 0;
1918
+ }
1919
+ newConnections.push({
1920
+ id: `conn-${mapping.id}`,
1921
+ mappingId: mapping.id,
1922
+ paths,
1923
+ midPoint,
1924
+ targetPoint,
1925
+ hasTransformation: mapping.transformation.type !== 'direct',
1926
+ isSelected: mapping.id === selectedId,
1927
+ isArrayMapping: mapping.isArrayMapping || false,
1928
+ isArrayToObjectMapping: mapping.isArrayToObjectMapping || false,
1929
+ hasFilter,
1930
+ });
1931
+ }
1932
+ this.connections.set(newConnections);
1933
+ }
1934
+ getTransformationIcon(mappingId) {
1935
+ const mapping = this.mappings().find((m) => m.id === mappingId);
1936
+ if (!mapping)
1937
+ return 'settings';
1938
+ // Show filter icon for array mappings with filter, otherwise loop icon
1939
+ if (mapping.isArrayMapping) {
1940
+ const arrayMapping = this.mappingService.getArrayMapping(mappingId);
1941
+ if (arrayMapping?.filter?.enabled && (arrayMapping?.filter?.root?.children?.length ?? 0) > 0) {
1942
+ return 'filter_alt';
1943
+ }
1944
+ return 'loop';
1945
+ }
1946
+ // Show appropriate icon for array-to-object mappings
1947
+ if (mapping.isArrayToObjectMapping) {
1948
+ const atoMapping = this.mappingService.getArrayToObjectMapping(mappingId);
1949
+ if (atoMapping?.selector.mode === 'first')
1950
+ return 'first_page';
1951
+ if (atoMapping?.selector.mode === 'last')
1952
+ return 'last_page';
1953
+ if (atoMapping?.selector.mode === 'condition')
1954
+ return 'filter_alt';
1955
+ return 'swap_horiz';
1956
+ }
1957
+ const icons = {
1958
+ direct: 'arrow_forward',
1959
+ concat: 'merge',
1960
+ substring: 'content_cut',
1961
+ replace: 'find_replace',
1962
+ uppercase: 'text_fields',
1963
+ lowercase: 'text_fields',
1964
+ dateFormat: 'calendar_today',
1965
+ extractYear: 'event',
1966
+ extractMonth: 'event',
1967
+ extractDay: 'event',
1968
+ extractHour: 'schedule',
1969
+ extractMinute: 'schedule',
1970
+ extractSecond: 'schedule',
1971
+ numberFormat: 'pin',
1972
+ template: 'code',
1973
+ custom: 'functions',
1974
+ };
1975
+ return icons[mapping.transformation.type] || 'settings';
1976
+ }
1977
+ clearAllMappings() {
1978
+ this.mappingService.clearAllMappings();
1979
+ this.mappingsChange.emit([]);
1980
+ this.updateConnections();
1981
+ }
1982
+ exportMappings() {
1983
+ return this.mappingService.exportMappings();
1984
+ }
1985
+ importMappings(json) {
1986
+ this.mappingService.importMappings(json);
1987
+ this.mappingsChange.emit(this.mappingService.allMappings());
1988
+ this.updateConnections();
1989
+ }
1990
+ trackByConnectionId(index, connection) {
1991
+ return connection.id;
1992
+ }
1993
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1994
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DataMapperComponent, isStandalone: true, selector: "data-mapper", inputs: { sourceSchema: "sourceSchema", targetSchema: "targetSchema", sampleData: "sampleData" }, outputs: { mappingsChange: "mappingsChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp($event)" } }, viewQueries: [{ propertyName: "svgContainer", first: true, predicate: ["svgContainer"], descendants: true }, { propertyName: "svgElement", first: true, predicate: ["svgElement"], descendants: true }], ngImport: i0, template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchema\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchema\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SchemaTreeComponent, selector: "schema-tree", inputs: ["schema", "side", "mappings", "defaultValues"], outputs: ["fieldDragStart", "fieldDragEnd", "fieldDrop", "fieldPositionsChanged", "fieldDefaultValueClick"] }, { kind: "component", type: TransformationPopoverComponent, selector: "transformation-popover", inputs: ["mapping", "position", "sampleData"], outputs: ["save", "delete", "close"] }, { kind: "component", type: ArrayFilterModalComponent, selector: "array-filter-modal", inputs: ["arrayMapping"], outputs: ["save", "close"] }, { kind: "component", type: ArraySelectorModalComponent, selector: "array-selector-modal", inputs: ["mapping"], outputs: ["save", "close"] }, { kind: "component", type: DefaultValuePopoverComponent, selector: "default-value-popover", inputs: ["field", "existingValue", "position"], outputs: ["save", "delete", "close"] }] });
1995
+ }
1996
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, decorators: [{
1997
+ type: Component,
1998
+ args: [{ selector: 'data-mapper', standalone: true, imports: [
1999
+ CommonModule,
2000
+ MatIconModule,
2001
+ MatButtonModule,
2002
+ MatTooltipModule,
2003
+ SchemaTreeComponent,
2004
+ TransformationPopoverComponent,
2005
+ ArrayFilterModalComponent,
2006
+ ArraySelectorModalComponent,
2007
+ DefaultValuePopoverComponent,
2008
+ ], template: "<div class=\"data-mapper\">\n <!-- Header Toolbar -->\n <div class=\"mapper-toolbar\">\n <div class=\"toolbar-title\">\n <mat-icon>account_tree</mat-icon>\n <span>Data Mapper</span>\n </div>\n <div class=\"toolbar-actions\">\n <span class=\"mapping-count\">{{ mappings().length }} mapping(s)</span>\n <button\n mat-icon-button\n matTooltip=\"Clear all mappings\"\n (click)=\"clearAllMappings()\"\n [disabled]=\"mappings().length === 0\"\n >\n <mat-icon>delete_sweep</mat-icon>\n </button>\n </div>\n </div>\n\n <!-- Main Mapper Area -->\n <div class=\"mapper-container\" #svgContainer>\n <!-- Source Schema Panel -->\n <div class=\"schema-panel source-panel\">\n <schema-tree\n [schema]=\"sourceSchema\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n ></schema-tree>\n </div>\n\n <!-- SVG Connection Layer -->\n <svg class=\"connection-layer\" #svgElement>\n <!-- Existing connections -->\n @for (connection of connections(); track trackByConnectionId($index, connection)) {\n <g class=\"connection-group\" [class.selected]=\"connection.isSelected\" [class.array-mapping]=\"connection.isArrayMapping\" [class.array-to-object-mapping]=\"connection.isArrayToObjectMapping\">\n <!-- Connection paths -->\n @for (path of connection.paths; track $index) {\n <!-- Shadow path for glow effect -->\n <path\n [attr.d]=\"path\"\n fill=\"none\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n stroke-width=\"6\"\n stroke-opacity=\"0.2\"\n stroke-linecap=\"round\"\n />\n <!-- Main connection path -->\n <path\n [attr.d]=\"path\"\n class=\"connection-path\"\n [class.selected]=\"connection.isSelected\"\n [class.array-path]=\"connection.isArrayMapping\"\n [class.array-to-object-path]=\"connection.isArrayToObjectMapping\"\n [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : (connection.isSelected ? '#8b5cf6' : '#6366f1'))\"\n fill=\"none\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? '8,4' : 'none'\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n <!-- Invisible wider path for easier clicking -->\n <path\n [attr.d]=\"path\"\n class=\"connection-hitbox\"\n fill=\"none\"\n stroke=\"transparent\"\n stroke-width=\"20\"\n (click)=\"onConnectionClick(connection, $event)\"\n />\n }\n\n <!-- Transformation node -->\n <g\n class=\"transformation-node\"\n [class.has-transformation]=\"connection.hasTransformation\"\n [class.is-array-mapping]=\"connection.isArrayMapping\"\n [class.is-array-to-object-mapping]=\"connection.isArrayToObjectMapping\"\n [attr.transform]=\"'translate(' + connection.midPoint.x + ',' + connection.midPoint.y + ')'\"\n (click)=\"onTransformationNodeClick(connection, $event)\"\n >\n <circle r=\"14\" class=\"node-bg\" [attr.fill]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" [attr.stroke]=\"connection.isArrayToObjectMapping ? '#8b5cf6' : (connection.isArrayMapping ? '#f59e0b' : '')\" />\n <text\n class=\"node-icon\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n font-family=\"Material Icons\"\n font-size=\"16\"\n [attr.fill]=\"connection.isArrayMapping || connection.isArrayToObjectMapping ? 'white' : ''\"\n >\n {{ getTransformationIcon(connection.mappingId) }}\n </text>\n </g>\n </g>\n }\n\n <!-- Drag preview path -->\n @if (dragPath()) {\n <path\n [attr.d]=\"dragPath()\"\n class=\"drag-path\"\n fill=\"none\"\n stroke=\"#6366f1\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"8,4\"\n stroke-linecap=\"round\"\n />\n }\n </svg>\n\n <!-- Target Schema Panel -->\n <div class=\"schema-panel target-panel\">\n <schema-tree\n [schema]=\"targetSchema\"\n [side]=\"'target'\"\n [mappings]=\"mappings()\"\n [defaultValues]=\"defaultValues()\"\n (fieldDrop)=\"onFieldDrop($event)\"\n (fieldPositionsChanged)=\"onTargetPositionsChanged($event)\"\n (fieldDefaultValueClick)=\"onDefaultValueClick($event)\"\n ></schema-tree>\n </div>\n </div>\n\n <!-- Instructions -->\n @if (mappings().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>drag_indicator</mat-icon>\n <p>Drag fields from the source schema to the target schema to create mappings</p>\n </div>\n }\n</div>\n\n<!-- Transformation Popover -->\n@if (showPopover() && selectedMapping()) {\n <transformation-popover\n [mapping]=\"selectedMapping()!\"\n [position]=\"popoverPosition()!\"\n [sampleData]=\"sampleData\"\n (save)=\"onPopoverSave($event)\"\n (delete)=\"onPopoverDelete()\"\n (close)=\"closePopover()\"\n ></transformation-popover>\n}\n\n<!-- Array Filter Modal -->\n@if (showArrayFilterModal() && selectedArrayMapping()) {\n <array-filter-modal\n [arrayMapping]=\"selectedArrayMapping()!\"\n (save)=\"onArrayFilterSave($event)\"\n (close)=\"closeArrayFilterModal()\"\n ></array-filter-modal>\n}\n\n<!-- Array Selector Modal (for array-to-object) -->\n@if (showArraySelectorModal() && selectedArrayToObjectMapping()) {\n <array-selector-modal\n [mapping]=\"selectedArrayToObjectMapping()!\"\n (save)=\"onArraySelectorSave($event)\"\n (close)=\"closeArraySelectorModal()\"\n ></array-selector-modal>\n}\n\n<!-- Default Value Popover -->\n@if (showDefaultValuePopover() && selectedDefaultValueField()) {\n <default-value-popover\n [field]=\"selectedDefaultValueField()!\"\n [existingValue]=\"getExistingDefaultValue(selectedDefaultValueField()!.id)\"\n [position]=\"defaultValuePopoverPosition()!\"\n (save)=\"onDefaultValueSave($event)\"\n (delete)=\"onDefaultValueDelete()\"\n (close)=\"closeDefaultValuePopover()\"\n ></default-value-popover>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;--data-mapper-bg: #f8fafc;--data-mapper-border-radius: 16px;--data-mapper-shadow: 0 4px 20px rgba(0, 0, 0, .08);--data-mapper-border-color: #e2e8f0;--data-mapper-toolbar-bg: white;--data-mapper-toolbar-border: #e2e8f0;--data-mapper-panel-bg: white;--data-mapper-panel-header-bg: #f8fafc;--data-mapper-panel-width: 320px;--data-mapper-panel-border-radius: 12px;--data-mapper-text-primary: #1e293b;--data-mapper-text-secondary: #64748b;--data-mapper-text-muted: #94a3b8;--data-mapper-accent-primary: #6366f1;--data-mapper-accent-success: #22c55e;--data-mapper-accent-warning: #f59e0b;--data-mapper-accent-danger: #ef4444;--data-mapper-connector-color: #6366f1;--data-mapper-connector-width: 2px;--data-mapper-connector-hover-color: #4f46e5;--data-mapper-spacing-sm: 8px;--data-mapper-spacing-md: 16px;--data-mapper-spacing-lg: 24px;--data-mapper-font-size-sm: 12px;--data-mapper-font-size-md: 14px;--data-mapper-font-size-lg: 18px}.data-mapper{display:flex;flex-direction:column;height:100%;min-height:0;flex:1;background:var(--data-mapper-bg);border-radius:var(--data-mapper-border-radius);overflow:hidden;box-shadow:var(--data-mapper-shadow)}.mapper-toolbar{display:flex;align-items:center;justify-content:space-between;padding:var(--data-mapper-spacing-md) var(--data-mapper-spacing-lg);background:var(--data-mapper-toolbar-bg);border-bottom:1px solid var(--data-mapper-border-color);flex-shrink:0}.toolbar-title{display:flex;align-items:center;gap:12px;font-size:var(--data-mapper-font-size-lg);font-weight:600;color:var(--data-mapper-text-primary)}.toolbar-title mat-icon{color:var(--data-mapper-accent-primary)}.toolbar-actions{display:flex;align-items:center;gap:var(--data-mapper-spacing-md)}.mapping-count{font-size:var(--data-mapper-font-size-sm);color:var(--data-mapper-text-secondary);background:#f1f5f9;padding:6px 12px;border-radius:20px}.mapper-container{flex:1;display:flex;position:relative;padding:24px;gap:0;overflow:hidden;min-height:0}.schema-panel{width:var(--data-mapper-panel-width);flex-shrink:0;z-index:2;height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.schema-panel.source-panel{margin-right:auto}.schema-panel.target-panel{margin-left:auto}.schema-panel schema-tree{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.connection-layer{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1}.connection-group{pointer-events:auto;cursor:pointer}.connection-group:hover .connection-path{stroke-width:3;filter:drop-shadow(0 2px 4px rgba(99,102,241,.3))}.connection-group:hover .transformation-node .node-bg{transform:scale(1.1)}.connection-group.selected .connection-path{stroke-width:3;filter:drop-shadow(0 2px 8px rgba(139,92,246,.4))}.connection-path{transition:stroke-width .15s ease,filter .15s ease}.connection-hitbox{cursor:pointer}.transformation-node{cursor:pointer;pointer-events:auto}.transformation-node .node-bg{fill:#fff;stroke:#6366f1;stroke-width:2;transition:transform .15s ease,fill .15s ease}.transformation-node .node-icon{fill:#6366f1;pointer-events:none}.transformation-node.has-transformation .node-bg{fill:#6366f1}.transformation-node.has-transformation .node-icon{fill:#fff}.transformation-node:hover .node-bg{transform:scale(1.15);filter:drop-shadow(0 2px 6px rgba(99,102,241,.4))}.drag-path{pointer-events:none;animation:dashMove .5s linear infinite}@keyframes dashMove{0%{stroke-dashoffset:24}to{stroke-dashoffset:0}}.empty-state{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:#94a3b8;pointer-events:none}.empty-state mat-icon{font-size:48px;width:48px;height:48px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:14px;max-width:300px;line-height:1.6}@media(max-width:900px){.schema-panel{width:260px}.mapper-container{padding:16px}}@media(max-width:700px){.mapper-container{flex-direction:column;gap:24px}.schema-panel{width:100%;max-height:300px}.connection-layer{display:none}}\n"] }]
2009
+ }], propDecorators: { sourceSchema: [{
2010
+ type: Input
2011
+ }], targetSchema: [{
2012
+ type: Input
2013
+ }], sampleData: [{
2014
+ type: Input
2015
+ }], mappingsChange: [{
2016
+ type: Output
2017
+ }], svgContainer: [{
2018
+ type: ViewChild,
2019
+ args: ['svgContainer']
2020
+ }], svgElement: [{
2021
+ type: ViewChild,
2022
+ args: ['svgElement']
2023
+ }], onMouseMove: [{
2024
+ type: HostListener,
2025
+ args: ['document:mousemove', ['$event']]
2026
+ }], onMouseUp: [{
2027
+ type: HostListener,
2028
+ args: ['document:mouseup', ['$event']]
2029
+ }] } });
2030
+
2031
+ class SchemaEditorComponent {
2032
+ set schema(value) {
2033
+ if (value) {
2034
+ this.schemaName.set(value.name);
2035
+ this.fields.set(this.cloneFields(value.fields));
2036
+ }
2037
+ }
2038
+ schemaChange = new EventEmitter();
2039
+ save = new EventEmitter();
2040
+ schemaName = signal('New Schema', ...(ngDevMode ? [{ debugName: "schemaName" }] : []));
2041
+ fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
2042
+ fieldTypes = [
2043
+ { value: 'string', label: 'String', icon: 'text_fields' },
2044
+ { value: 'number', label: 'Number', icon: 'pin' },
2045
+ { value: 'boolean', label: 'Boolean', icon: 'toggle_on' },
2046
+ { value: 'date', label: 'Date', icon: 'calendar_today' },
2047
+ { value: 'object', label: 'Object', icon: 'data_object' },
2048
+ { value: 'array', label: 'Array', icon: 'data_array' },
2049
+ ];
2050
+ generateId() {
2051
+ return `field-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
2052
+ }
2053
+ cloneFields(fields) {
2054
+ return fields.map(f => ({
2055
+ ...f,
2056
+ children: f.children ? this.cloneFields(f.children) : undefined,
2057
+ }));
2058
+ }
2059
+ getTypeIcon(type) {
2060
+ return this.fieldTypes.find(t => t.value === type)?.icon || 'help_outline';
2061
+ }
2062
+ // Add a new field at the root level
2063
+ addField() {
2064
+ const newField = {
2065
+ id: this.generateId(),
2066
+ name: '',
2067
+ type: 'string',
2068
+ isEditing: true,
2069
+ expanded: false,
2070
+ };
2071
+ this.fields.update(fields => [...fields, newField]);
2072
+ this.emitChange();
2073
+ }
2074
+ // Add a child field to an object or array
2075
+ addChildField(parent) {
2076
+ if (!parent.children) {
2077
+ parent.children = [];
2078
+ }
2079
+ const newField = {
2080
+ id: this.generateId(),
2081
+ name: '',
2082
+ type: 'string',
2083
+ isEditing: true,
2084
+ };
2085
+ parent.children.push(newField);
2086
+ parent.expanded = true;
2087
+ this.fields.update(fields => [...fields]);
2088
+ this.emitChange();
2089
+ }
2090
+ // Delete a field
2091
+ deleteField(field, parentList) {
2092
+ const index = parentList.indexOf(field);
2093
+ if (index > -1) {
2094
+ parentList.splice(index, 1);
2095
+ this.fields.update(fields => [...fields]);
2096
+ this.emitChange();
2097
+ }
2098
+ }
2099
+ // Duplicate a field
2100
+ duplicateField(field, parentList) {
2101
+ const index = parentList.indexOf(field);
2102
+ if (index > -1) {
2103
+ const clone = {
2104
+ ...field,
2105
+ id: this.generateId(),
2106
+ name: field.name + '_copy',
2107
+ children: field.children ? this.cloneFields(field.children) : undefined,
2108
+ isEditing: false,
2109
+ };
2110
+ parentList.splice(index + 1, 0, clone);
2111
+ this.fields.update(fields => [...fields]);
2112
+ this.emitChange();
2113
+ }
2114
+ }
2115
+ // Toggle field expansion
2116
+ toggleExpand(field) {
2117
+ field.expanded = !field.expanded;
2118
+ this.fields.update(fields => [...fields]);
2119
+ }
2120
+ // Start editing a field
2121
+ startEdit(field) {
2122
+ field.isEditing = true;
2123
+ this.fields.update(fields => [...fields]);
2124
+ }
2125
+ // Stop editing a field
2126
+ stopEdit(field) {
2127
+ field.isEditing = false;
2128
+ if (!field.name.trim()) {
2129
+ field.name = 'unnamed';
2130
+ }
2131
+ this.fields.update(fields => [...fields]);
2132
+ this.emitChange();
2133
+ }
2134
+ // Handle field name input - only allow valid property name characters
2135
+ onFieldNameChange(field, event) {
2136
+ const input = event.target;
2137
+ // Remove invalid characters: only allow letters, numbers, underscores, and dollar signs
2138
+ // Property names should start with letter, underscore, or dollar sign
2139
+ const sanitized = input.value.replace(/[^a-zA-Z0-9_$]/g, '');
2140
+ field.name = sanitized;
2141
+ // Update input value if it was sanitized
2142
+ if (input.value !== sanitized) {
2143
+ input.value = sanitized;
2144
+ }
2145
+ }
2146
+ // Handle field type change
2147
+ onFieldTypeChange(field, type) {
2148
+ field.type = type;
2149
+ // Initialize children array for object/array types
2150
+ if ((type === 'object' || type === 'array') && !field.children) {
2151
+ field.children = [];
2152
+ }
2153
+ this.fields.update(fields => [...fields]);
2154
+ this.emitChange();
2155
+ }
2156
+ // Toggle required status
2157
+ toggleRequired(field) {
2158
+ field.required = !field.required;
2159
+ this.fields.update(fields => [...fields]);
2160
+ this.emitChange();
2161
+ }
2162
+ // Update field description
2163
+ onDescriptionChange(field, description) {
2164
+ field.description = description;
2165
+ this.fields.update(fields => [...fields]);
2166
+ this.emitChange();
2167
+ }
2168
+ // Toggle allowed values editor
2169
+ toggleValuesEditor(field) {
2170
+ field.isEditingValues = !field.isEditingValues;
2171
+ if (field.isEditingValues && !field.allowedValues) {
2172
+ field.allowedValues = [];
2173
+ }
2174
+ this.fields.update(fields => [...fields]);
2175
+ }
2176
+ // Add allowed value
2177
+ addAllowedValue(field, input) {
2178
+ const value = input.value.trim();
2179
+ if (value && !field.allowedValues?.includes(value)) {
2180
+ if (!field.allowedValues) {
2181
+ field.allowedValues = [];
2182
+ }
2183
+ field.allowedValues.push(value);
2184
+ input.value = '';
2185
+ this.fields.update(fields => [...fields]);
2186
+ this.emitChange();
2187
+ }
2188
+ }
2189
+ // Remove allowed value
2190
+ removeAllowedValue(field, index) {
2191
+ if (field.allowedValues) {
2192
+ field.allowedValues.splice(index, 1);
2193
+ if (field.allowedValues.length === 0) {
2194
+ field.allowedValues = undefined;
2195
+ }
2196
+ this.fields.update(fields => [...fields]);
2197
+ this.emitChange();
2198
+ }
2199
+ }
2200
+ // Handle Enter key in allowed value input
2201
+ onAllowedValueKeydown(event, field, input) {
2202
+ if (event.key === 'Enter') {
2203
+ event.preventDefault();
2204
+ this.addAllowedValue(field, input);
2205
+ }
2206
+ }
2207
+ // Handle keyboard events in field name input
2208
+ onFieldNameKeydown(event, field) {
2209
+ if (event.key === 'Enter') {
2210
+ this.stopEdit(field);
2211
+ }
2212
+ else if (event.key === 'Escape') {
2213
+ field.isEditing = false;
2214
+ this.fields.update(fields => [...fields]);
2215
+ }
2216
+ }
2217
+ // Move field up in list
2218
+ moveFieldUp(field, parentList) {
2219
+ const index = parentList.indexOf(field);
2220
+ if (index > 0) {
2221
+ [parentList[index - 1], parentList[index]] = [parentList[index], parentList[index - 1]];
2222
+ this.fields.update(fields => [...fields]);
2223
+ this.emitChange();
2224
+ }
2225
+ }
2226
+ // Move field down in list
2227
+ moveFieldDown(field, parentList) {
2228
+ const index = parentList.indexOf(field);
2229
+ if (index < parentList.length - 1) {
2230
+ [parentList[index], parentList[index + 1]] = [parentList[index + 1], parentList[index]];
2231
+ this.fields.update(fields => [...fields]);
2232
+ this.emitChange();
2233
+ }
2234
+ }
2235
+ // Check if field can be indented (previous sibling must be object/array)
2236
+ canIndent(field, parentList) {
2237
+ const index = parentList.indexOf(field);
2238
+ if (index <= 0)
2239
+ return false;
2240
+ const prevSibling = parentList[index - 1];
2241
+ return prevSibling.type === 'object' || prevSibling.type === 'array';
2242
+ }
2243
+ // Indent field - move into previous sibling's children
2244
+ indentField(field, parentList) {
2245
+ const index = parentList.indexOf(field);
2246
+ if (index <= 0)
2247
+ return;
2248
+ const prevSibling = parentList[index - 1];
2249
+ if (prevSibling.type !== 'object' && prevSibling.type !== 'array')
2250
+ return;
2251
+ // Remove from current list
2252
+ parentList.splice(index, 1);
2253
+ // Add to previous sibling's children
2254
+ if (!prevSibling.children) {
2255
+ prevSibling.children = [];
2256
+ }
2257
+ prevSibling.children.push(field);
2258
+ prevSibling.expanded = true;
2259
+ this.fields.update(fields => [...fields]);
2260
+ this.emitChange();
2261
+ }
2262
+ // Outdent field - move out of parent to grandparent level
2263
+ outdentField(field, parentList, level) {
2264
+ if (level === 0)
2265
+ return;
2266
+ // Find the parent object/array that contains this list
2267
+ const parent = this.findParentOfList(this.fields(), parentList);
2268
+ if (!parent)
2269
+ return;
2270
+ // Find the grandparent list
2271
+ const grandparentList = this.findParentList(this.fields(), parent);
2272
+ if (!grandparentList)
2273
+ return;
2274
+ // Remove from current list
2275
+ const index = parentList.indexOf(field);
2276
+ parentList.splice(index, 1);
2277
+ // Add to grandparent list after the parent
2278
+ const parentIndex = grandparentList.indexOf(parent);
2279
+ grandparentList.splice(parentIndex + 1, 0, field);
2280
+ this.fields.update(fields => [...fields]);
2281
+ this.emitChange();
2282
+ }
2283
+ // Find the parent field that contains a given list
2284
+ findParentOfList(searchIn, targetList) {
2285
+ for (const field of searchIn) {
2286
+ if (field.children === targetList) {
2287
+ return field;
2288
+ }
2289
+ if (field.children) {
2290
+ const found = this.findParentOfList(field.children, targetList);
2291
+ if (found)
2292
+ return found;
2293
+ }
2294
+ }
2295
+ return null;
2296
+ }
2297
+ // Find the list that contains a given field
2298
+ findParentList(searchIn, targetField) {
2299
+ if (searchIn.includes(targetField)) {
2300
+ return searchIn;
2301
+ }
2302
+ for (const field of searchIn) {
2303
+ if (field.children) {
2304
+ const found = this.findParentList(field.children, targetField);
2305
+ if (found)
2306
+ return found;
2307
+ }
2308
+ }
2309
+ return null;
2310
+ }
2311
+ // Update schema name - only allow valid characters
2312
+ onSchemaNameChange(name, input) {
2313
+ const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, '');
2314
+ this.schemaName.set(sanitized);
2315
+ if (input && input.value !== sanitized) {
2316
+ input.value = sanitized;
2317
+ }
2318
+ this.emitChange();
2319
+ }
2320
+ // Emit change event
2321
+ emitChange() {
2322
+ this.schemaChange.emit({
2323
+ name: this.schemaName(),
2324
+ fields: this.fields(),
2325
+ });
2326
+ }
2327
+ // Save the schema
2328
+ onSave() {
2329
+ this.save.emit({
2330
+ name: this.schemaName(),
2331
+ fields: this.fields(),
2332
+ });
2333
+ }
2334
+ // Convert to internal JSON format
2335
+ toJson() {
2336
+ return JSON.stringify({
2337
+ name: this.schemaName(),
2338
+ fields: this.stripEditingState(this.fields()),
2339
+ }, null, 2);
2340
+ }
2341
+ // Convert to valid JSON Schema format
2342
+ toJsonSchema() {
2343
+ const required = [];
2344
+ const properties = {};
2345
+ for (const field of this.fields()) {
2346
+ if (field.required && field.name) {
2347
+ required.push(field.name);
2348
+ }
2349
+ if (field.name) {
2350
+ properties[field.name] = this.fieldToJsonSchema(field);
2351
+ }
2352
+ }
2353
+ const schema = {
2354
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
2355
+ type: 'object',
2356
+ title: this.schemaName(),
2357
+ properties,
2358
+ };
2359
+ if (required.length > 0) {
2360
+ schema['required'] = required;
2361
+ }
2362
+ return schema;
2363
+ }
2364
+ fieldToJsonSchema(field) {
2365
+ const schema = {};
2366
+ // Map type
2367
+ if (field.type === 'date') {
2368
+ schema['type'] = 'string';
2369
+ schema['format'] = 'date-time';
2370
+ }
2371
+ else if (field.type === 'array') {
2372
+ schema['type'] = 'array';
2373
+ if (field.children && field.children.length > 0) {
2374
+ // If array has children, treat first child as item schema
2375
+ const itemProperties = {};
2376
+ const itemRequired = [];
2377
+ for (const child of field.children) {
2378
+ if (child.name) {
2379
+ itemProperties[child.name] = this.fieldToJsonSchema(child);
2380
+ if (child.required) {
2381
+ itemRequired.push(child.name);
2382
+ }
2383
+ }
2384
+ }
2385
+ const items = {
2386
+ type: 'object',
2387
+ properties: itemProperties,
2388
+ };
2389
+ if (itemRequired.length > 0) {
2390
+ items['required'] = itemRequired;
2391
+ }
2392
+ schema['items'] = items;
2393
+ }
2394
+ }
2395
+ else if (field.type === 'object') {
2396
+ schema['type'] = 'object';
2397
+ if (field.children && field.children.length > 0) {
2398
+ const childProperties = {};
2399
+ const childRequired = [];
2400
+ for (const child of field.children) {
2401
+ if (child.name) {
2402
+ childProperties[child.name] = this.fieldToJsonSchema(child);
2403
+ if (child.required) {
2404
+ childRequired.push(child.name);
2405
+ }
2406
+ }
2407
+ }
2408
+ schema['properties'] = childProperties;
2409
+ if (childRequired.length > 0) {
2410
+ schema['required'] = childRequired;
2411
+ }
2412
+ }
2413
+ }
2414
+ else {
2415
+ schema['type'] = field.type;
2416
+ }
2417
+ // Add description
2418
+ if (field.description) {
2419
+ schema['description'] = field.description;
2420
+ }
2421
+ // Add enum for allowed values
2422
+ if (field.allowedValues && field.allowedValues.length > 0) {
2423
+ schema['enum'] = field.allowedValues;
2424
+ }
2425
+ return schema;
2426
+ }
2427
+ stripEditingState(fields) {
2428
+ return fields.map(f => {
2429
+ const { isEditing, isEditingValues, ...rest } = f;
2430
+ return {
2431
+ ...rest,
2432
+ children: f.children ? this.stripEditingState(f.children) : undefined,
2433
+ };
2434
+ });
2435
+ }
2436
+ // Track by function for ngFor
2437
+ trackByFieldId(index, field) {
2438
+ return field.id;
2439
+ }
2440
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaEditorComponent, isStandalone: true, selector: "schema-editor", inputs: { schema: "schema" }, outputs: { schemaChange: "schemaChange", save: "save" }, ngImport: i0, template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n <div class=\"header-actions\">\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields List -->\n <div class=\"fields-container\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div class=\"fields-list\">\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: fields(), level: 0, parentList: fields() }\"></ng-container>\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Field Template -->\n<ng-template #fieldListTemplate let-fields=\"fields\" let-level=\"level\" let-parentList=\"parentList\">\n @for (field of fields; track trackByFieldId($index, field); let i = $index; let first = $first; let last = $last) {\n <div\n class=\"field-item\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n [style.--level]=\"level\"\n >\n <!-- Reorder Buttons -->\n <div class=\"reorder-buttons\">\n <button\n class=\"reorder-btn\"\n [disabled]=\"first\"\n (click)=\"moveFieldUp(field, parentList)\"\n matTooltip=\"Move up\"\n >\n <mat-icon>keyboard_arrow_up</mat-icon>\n </button>\n <button\n class=\"reorder-btn\"\n [disabled]=\"last\"\n (click)=\"moveFieldDown(field, parentList)\"\n matTooltip=\"Move down\"\n >\n <mat-icon>keyboard_arrow_down</mat-icon>\n </button>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n [disabled]=\"level === 0\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n matTooltip=\"{{ field.expanded ? 'Collapse' : 'Expand' }}\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n\n <!-- Description Input -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>{{ field.allowedValues?.length ? 'list' : 'list' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\" [style.--level]=\"level\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, valueInput)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded) {\n <div class=\"nested-fields\" [style.--level]=\"level + 1\">\n @if (field.children && field.children.length > 0) {\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: field.children, level: level + 1, parentList: field.children }\"></ng-container>\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n }\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-header-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .header-actions{display:flex;gap:12px}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:flex;flex-direction:column;gap:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;padding-left:calc(12px + var(--level, 0) * 24px);background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-item.cdk-drag-preview{box-shadow:0 4px 16px #00000026;border:1px solid var(--schema-editor-accent-primary)}.field-item.cdk-drag-placeholder{opacity:.3}.reorder-buttons{display:flex;flex-direction:column;gap:0}.reorder-buttons .reorder-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:14px;border-radius:3px;transition:all .15s ease}.reorder-buttons .reorder-btn:hover:not(:disabled){background:#e2e8f0;color:#3b82f6}.reorder-buttons .reorder-btn:disabled{opacity:.3;cursor:default}.reorder-buttons .reorder-btn mat-icon{font-size:16px;width:16px;height:16px}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:100px;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;gap:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{margin-left:calc(var(--level, 0) * 24px + 24px);padding-left:16px;border-left:2px solid #e2e8f0;display:flex;flex-direction:column;gap:4px;margin-top:4px;margin-bottom:8px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:calc(var(--level, 0) * 24px + 48px);margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: DragDropModule }] });
2442
+ }
2443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, decorators: [{
2444
+ type: Component,
2445
+ args: [{ selector: 'schema-editor', standalone: true, imports: [
2446
+ CommonModule,
2447
+ FormsModule,
2448
+ MatButtonModule,
2449
+ MatIconModule,
2450
+ MatInputModule,
2451
+ MatFormFieldModule,
2452
+ MatSelectModule,
2453
+ MatTooltipModule,
2454
+ MatMenuModule,
2455
+ DragDropModule,
2456
+ ], template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n <div class=\"schema-name-section\">\n <mat-form-field appearance=\"outline\" class=\"schema-name-field\">\n <mat-label>Schema Name</mat-label>\n <input\n #schemaNameInput\n matInput\n [value]=\"schemaName()\"\n (input)=\"onSchemaNameChange($any($event.target).value, schemaNameInput)\"\n placeholder=\"Enter schema name\"\n />\n </mat-form-field>\n </div>\n <div class=\"header-actions\">\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields List -->\n <div class=\"fields-container\">\n @if (fields().length === 0) {\n <div class=\"empty-state\">\n <mat-icon>schema</mat-icon>\n <p>No fields yet. Click \"Add Field\" to get started.</p>\n </div>\n } @else {\n <div class=\"fields-list\">\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: fields(), level: 0, parentList: fields() }\"></ng-container>\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Field Template -->\n<ng-template #fieldListTemplate let-fields=\"fields\" let-level=\"level\" let-parentList=\"parentList\">\n @for (field of fields; track trackByFieldId($index, field); let i = $index; let first = $first; let last = $last) {\n <div\n class=\"field-item\"\n [class.has-children]=\"field.children && field.children.length > 0\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n [style.--level]=\"level\"\n >\n <!-- Reorder Buttons -->\n <div class=\"reorder-buttons\">\n <button\n class=\"reorder-btn\"\n [disabled]=\"first\"\n (click)=\"moveFieldUp(field, parentList)\"\n matTooltip=\"Move up\"\n >\n <mat-icon>keyboard_arrow_up</mat-icon>\n </button>\n <button\n class=\"reorder-btn\"\n [disabled]=\"last\"\n (click)=\"moveFieldDown(field, parentList)\"\n matTooltip=\"Move down\"\n >\n <mat-icon>keyboard_arrow_down</mat-icon>\n </button>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, parentList)\"\n (click)=\"indentField(field, parentList)\"\n matTooltip=\"Move into previous object/array\"\n >\n <mat-icon>chevron_right</mat-icon>\n </button>\n <button\n class=\"indent-btn\"\n [disabled]=\"level === 0\"\n (click)=\"outdentField(field, parentList, level)\"\n matTooltip=\"Move out of parent\"\n >\n <mat-icon>chevron_left</mat-icon>\n </button>\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n class=\"expand-btn\"\n (click)=\"toggleExpand(field)\"\n matTooltip=\"{{ field.expanded ? 'Collapse' : 'Expand' }}\"\n >\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <!-- Type Icon -->\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\n <!-- Field Name -->\n @if (field.isEditing) {\n <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange(field, $event)\"\n (blur)=\"stopEdit(field)\"\n (keydown)=\"onFieldNameKeydown($event, field)\"\n placeholder=\"Field name\"\n autofocus\n />\n } @else {\n <span class=\"field-name\" (dblclick)=\"startEdit(field)\">\n {{ field.name || 'unnamed' }}\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n </span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required (click to make optional)' : 'Optional (click to make required)'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n\n <!-- Description Input -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange(field, $event.value)\">\n @for (type of fieldTypes; track type.value) {\n <mat-option [value]=\"type.value\">\n <mat-icon>{{ type.icon }}</mat-icon>\n {{ type.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n (click)=\"addChildField(field)\"\n matTooltip=\"Add child field\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n }\n @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValuesEditor(field)\"\n [matTooltip]=\"field.allowedValues?.length ? 'Edit allowed values (' + field.allowedValues.length + ')' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>{{ field.allowedValues?.length ? 'list' : 'list' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"fieldMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #fieldMenu=\"matMenu\">\n <button mat-menu-item (click)=\"startEdit(field)\">\n <mat-icon>edit</mat-icon>\n <span>Rename</span>\n </button>\n <button mat-menu-item (click)=\"duplicateField(field, parentList)\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, parentList)\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\n </div>\n\n <!-- Allowed Values Editor -->\n @if (field.isEditingValues && (field.type === 'string' || field.type === 'number')) {\n <div class=\"allowed-values-editor\" [style.--level]=\"level\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #valueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, field, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(field, valueInput)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (field.allowedValues && field.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of field.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(field, vi)\" matTooltip=\"Remove\">\n <mat-icon>close</mat-icon>\n </button>\n </span>\n }\n </div>\n } @else {\n <div class=\"no-values\">No values defined yet</div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded) {\n <div class=\"nested-fields\" [style.--level]=\"level + 1\">\n @if (field.children && field.children.length > 0) {\n <ng-container *ngTemplateOutlet=\"fieldListTemplate; context: { fields: field.children, level: level + 1, parentList: field.children }\"></ng-container>\n } @else {\n <div class=\"empty-nested\">\n <span>No child fields</span>\n <button mat-button (click)=\"addChildField(field)\" color=\"primary\">\n <mat-icon>add</mat-icon>\n Add field\n </button>\n </div>\n }\n </div>\n }\n }\n</ng-template>\n", styles: [":host{--schema-editor-bg: white;--schema-editor-border-radius: 12px;--schema-editor-shadow: 0 4px 20px rgba(0, 0, 0, .08);--schema-editor-border-color: #e2e8f0;--schema-editor-header-bg: white;--schema-editor-header-border: #e2e8f0;--schema-editor-field-bg: #f8fafc;--schema-editor-field-bg-hover: #f1f5f9;--schema-editor-field-bg-editing: #eff6ff;--schema-editor-field-bg-complex: #fefce8;--schema-editor-field-border-radius: 8px;--schema-editor-text-primary: #1e293b;--schema-editor-text-secondary: #64748b;--schema-editor-text-muted: #94a3b8;--schema-editor-accent-primary: #3b82f6;--schema-editor-accent-success: #22c55e;--schema-editor-accent-warning: #f59e0b;--schema-editor-accent-danger: #ef4444;--schema-editor-spacing-sm: 8px;--schema-editor-spacing-md: 16px;--schema-editor-spacing-lg: 24px;--schema-editor-font-size-sm: 12px;--schema-editor-font-size-md: 14px;--schema-editor-font-size-lg: 16px}.schema-editor{background:var(--schema-editor-bg);border-radius:var(--schema-editor-border-radius);box-shadow:var(--schema-editor-shadow);height:100%;display:flex;flex-direction:column;overflow:hidden}.editor-header{display:flex;align-items:center;justify-content:space-between;padding:var(--schema-editor-spacing-md) var(--schema-editor-spacing-lg);border-bottom:1px solid var(--schema-editor-border-color);gap:var(--schema-editor-spacing-md);flex-shrink:0;background:var(--schema-editor-header-bg)}.editor-header .schema-name-section{flex:1;max-width:400px}.editor-header .schema-name-field{width:100%}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.editor-header .header-actions{display:flex;gap:12px}.fields-container{flex:1;overflow-y:auto;padding:16px;min-height:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 24px;color:#94a3b8;text-align:center}.empty-state mat-icon{font-size:64px;width:64px;height:64px;margin-bottom:16px;opacity:.5}.empty-state p{font-size:16px;margin:0}.fields-list{display:flex;flex-direction:column;gap:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;padding-left:calc(12px + var(--level, 0) * 24px);background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease}.field-item:hover{background:var(--schema-editor-field-bg-hover);border-color:var(--schema-editor-border-color)}.field-item.is-editing{background:var(--schema-editor-field-bg-editing);border-color:var(--schema-editor-accent-primary)}.field-item.is-complex{background:var(--schema-editor-field-bg-complex)}.field-item.is-complex:hover{background:#fef9c3}.field-item.cdk-drag-preview{box-shadow:0 4px 16px #00000026;border:1px solid var(--schema-editor-accent-primary)}.field-item.cdk-drag-placeholder{opacity:.3}.reorder-buttons{display:flex;flex-direction:column;gap:0}.reorder-buttons .reorder-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:14px;border-radius:3px;transition:all .15s ease}.reorder-buttons .reorder-btn:hover:not(:disabled){background:#e2e8f0;color:#3b82f6}.reorder-buttons .reorder-btn:disabled{opacity:.3;cursor:default}.reorder-buttons .reorder-btn mat-icon{font-size:16px;width:16px;height:16px}.indent-buttons{display:flex;flex-direction:row;gap:2px}.indent-buttons .indent-btn{background:none;border:none;padding:2px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:3px;transition:all .15s ease}.indent-buttons .indent-btn:hover:not(:disabled){background:#dbeafe;color:#2563eb}.indent-buttons .indent-btn:disabled{opacity:.3;cursor:default}.indent-buttons .indent-btn mat-icon{font-size:16px;width:16px;height:16px}.expand-btn{background:none;border:none;padding:4px;cursor:pointer;color:#64748b;border-radius:4px;display:flex;align-items:center;transition:all .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.field-name{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;cursor:pointer;padding:4px 8px;border-radius:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-name:hover{background:#e2e8f0}.field-name .array-indicator{color:#f59e0b;font-weight:600;margin-left:2px}.field-name-input{flex:0 0 auto;width:150px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:2px solid #3b82f6;border-radius:6px;outline:none;background:#fff}.field-name-input::placeholder{color:#94a3b8;font-weight:400}.required-btn{background:none;border:none;padding:4px;cursor:pointer;color:#cbd5e1;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease;flex-shrink:0}.required-btn:hover{color:#f59e0b;background:#fef3c7}.required-btn.is-required{color:#f59e0b}.required-btn mat-icon{font-size:18px;width:18px;height:18px}.description-input{flex:1;font-size:13px;color:#64748b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;min-width:100px;transition:all .15s ease}.description-input:focus{border-color:#3b82f6;background:#fff}.description-input::placeholder{color:#94a3b8;font-style:italic}.type-selector{width:140px;flex-shrink:0}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important}.type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.field-actions{display:flex;align-items:center;gap:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions{opacity:1}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.nested-fields{margin-left:calc(var(--level, 0) * 24px + 24px);padding-left:16px;border-left:2px solid #e2e8f0;display:flex;flex-direction:column;gap:4px;margin-top:4px;margin-bottom:8px}.empty-nested{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#94a3b8;font-size:13px;font-style:italic;background:#f8fafc;border-radius:6px;border:1px dashed #e2e8f0}.allowed-values-editor{margin-left:calc(var(--level, 0) * 24px + 48px);margin-top:4px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px}.allowed-values-editor .values-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.allowed-values-editor .values-label{font-size:12px;font-weight:500;color:#166534}.allowed-values-editor .value-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #86efac;border-radius:4px;outline:none;background:#fff}.allowed-values-editor .value-input:focus{border-color:#22c55e}.allowed-values-editor .value-input::placeholder{color:#94a3b8}.allowed-values-editor .add-value-btn{background:#22c55e;border:none;color:#fff;width:28px;height:28px;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s ease}.allowed-values-editor .add-value-btn:hover{background:#16a34a}.allowed-values-editor .add-value-btn mat-icon{font-size:18px;width:18px;height:18px}.allowed-values-editor .values-list{display:flex;flex-wrap:wrap;gap:6px}.allowed-values-editor .value-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;background:#fff;border:1px solid #86efac;border-radius:4px;font-size:12px;color:#166534}.allowed-values-editor .value-chip .remove-value-btn{background:none;border:none;padding:0;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.allowed-values-editor .value-chip .remove-value-btn:hover{color:#ef4444}.allowed-values-editor .value-chip .remove-value-btn mat-icon{font-size:14px;width:14px;height:14px}.allowed-values-editor .no-values{font-size:12px;color:#94a3b8;font-style:italic}.field-actions .has-values{color:#22c55e!important}\n"] }]
2457
+ }], propDecorators: { schema: [{
2458
+ type: Input
2459
+ }], schemaChange: [{
2460
+ type: Output
2461
+ }], save: [{
2462
+ type: Output
2463
+ }] } });
2464
+
2465
+ /*
2466
+ * Public API Surface of ngx-data-mapper
2467
+ */
2468
+ // Models
2469
+
2470
+ /**
2471
+ * Generated bundle index. Do not edit.
2472
+ */
2473
+
2474
+ export { ArrayFilterModalComponent, ArraySelectorModalComponent, DataMapperComponent, DefaultValuePopoverComponent, MappingService, SchemaEditorComponent, SchemaParserService, SchemaTreeComponent, SvgConnectorService, TransformationPopoverComponent, TransformationService };
2475
+ //# sourceMappingURL=expeed-ngx-data-mapper.mjs.map