@expeed/ngx-data-mapper 1.1.0 → 1.2.1

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.
@@ -10,26 +10,28 @@ import * as i7 from '@angular/material/tooltip';
10
10
  import { MatTooltipModule } from '@angular/material/tooltip';
11
11
  import * as i2$1 from '@angular/forms';
12
12
  import { FormsModule } from '@angular/forms';
13
- import * as i5 from '@angular/material/select';
13
+ import * as i4 from '@angular/material/select';
14
14
  import { MatSelectModule } from '@angular/material/select';
15
- import * as i5$1 from '@angular/material/input';
15
+ import * as i5 from '@angular/material/input';
16
16
  import { MatInputModule } from '@angular/material/input';
17
17
  import { MatFormFieldModule } from '@angular/material/form-field';
18
18
  import * as i8 from '@angular/material/checkbox';
19
19
  import { MatCheckboxModule } from '@angular/material/checkbox';
20
- import * as i9 from '@angular/cdk/drag-drop';
20
+ import * as i10 from '@angular/cdk/drag-drop';
21
21
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
22
22
  import * as i6 from '@angular/material/radio';
23
23
  import { MatRadioModule } from '@angular/material/radio';
24
24
  import * as i7$1 from '@angular/material/slide-toggle';
25
25
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
26
- import * as i9$1 from '@angular/material/divider';
26
+ import * as i9 from '@angular/material/divider';
27
27
  import { MatDividerModule } from '@angular/material/divider';
28
28
  import * as i6$1 from '@angular/material/datepicker';
29
29
  import { MatDatepickerModule } from '@angular/material/datepicker';
30
30
  import { MatNativeDateModule } from '@angular/material/core';
31
- import * as i7$2 from '@angular/material/menu';
31
+ import * as i8$1 from '@angular/material/menu';
32
32
  import { MatMenuModule } from '@angular/material/menu';
33
+ import * as i9$1 from '@angular/material/button-toggle';
34
+ import { MatButtonToggleModule } from '@angular/material/button-toggle';
33
35
 
34
36
  /**
35
37
  * Standard JSON Schema (draft-07) TypeScript interfaces
@@ -125,16 +127,21 @@ class MappingService {
125
127
  arrayToObjectMappings = signal([], ...(ngDevMode ? [{ debugName: "arrayToObjectMappings" }] : []));
126
128
  defaultValues = signal([], ...(ngDevMode ? [{ debugName: "defaultValues" }] : []));
127
129
  selectedMappingId = signal(null, ...(ngDevMode ? [{ debugName: "selectedMappingId" }] : []));
130
+ _sourceSchemaRef = signal(null, ...(ngDevMode ? [{ debugName: "_sourceSchemaRef" }] : []));
131
+ _targetSchemaRef = signal(null, ...(ngDevMode ? [{ debugName: "_targetSchemaRef" }] : []));
128
132
  dragState = signal({
129
133
  isDragging: false,
130
134
  sourceField: null,
131
135
  startPoint: null,
132
136
  currentPoint: null,
137
+ dragMode: 'new',
133
138
  }, ...(ngDevMode ? [{ debugName: "dragState" }] : []));
134
139
  allMappings = computed(() => this.mappings(), ...(ngDevMode ? [{ debugName: "allMappings" }] : []));
135
140
  allArrayMappings = computed(() => this.arrayMappings(), ...(ngDevMode ? [{ debugName: "allArrayMappings" }] : []));
136
141
  allArrayToObjectMappings = computed(() => this.arrayToObjectMappings(), ...(ngDevMode ? [{ debugName: "allArrayToObjectMappings" }] : []));
137
142
  allDefaultValues = computed(() => this.defaultValues(), ...(ngDevMode ? [{ debugName: "allDefaultValues" }] : []));
143
+ sourceSchemaRef = computed(() => this._sourceSchemaRef(), ...(ngDevMode ? [{ debugName: "sourceSchemaRef" }] : []));
144
+ targetSchemaRef = computed(() => this._targetSchemaRef(), ...(ngDevMode ? [{ debugName: "targetSchemaRef" }] : []));
138
145
  selectedMapping = computed(() => {
139
146
  const id = this.selectedMappingId();
140
147
  return this.mappings().find((m) => m.id === id) || null;
@@ -149,6 +156,26 @@ class MappingService {
149
156
  sourceField: field,
150
157
  startPoint,
151
158
  currentPoint: startPoint,
159
+ dragMode: 'new',
160
+ });
161
+ }
162
+ startEndpointDrag(mappingId, endpointType, startPoint, sourceFieldIndex) {
163
+ const mapping = this.mappings().find(m => m.id === mappingId);
164
+ if (!mapping)
165
+ return;
166
+ // For source endpoint, we show the line from target back to cursor
167
+ // For target endpoint, we show the line from source(s) to cursor
168
+ const dragMode = endpointType === 'source' ? 'move-source' : 'move-target';
169
+ this.dragState.set({
170
+ isDragging: true,
171
+ sourceField: endpointType === 'source'
172
+ ? mapping.sourceFields[sourceFieldIndex ?? 0]
173
+ : mapping.targetField,
174
+ startPoint,
175
+ currentPoint: startPoint,
176
+ dragMode,
177
+ mappingId,
178
+ sourceFieldIndex,
152
179
  });
153
180
  }
154
181
  updateDragPosition(currentPoint) {
@@ -163,8 +190,62 @@ class MappingService {
163
190
  sourceField: null,
164
191
  startPoint: null,
165
192
  currentPoint: null,
193
+ dragMode: 'new',
166
194
  });
167
195
  }
196
+ changeSourceField(mappingId, newSourceField, sourceFieldIndex) {
197
+ const mapping = this.mappings().find(m => m.id === mappingId);
198
+ if (!mapping)
199
+ return;
200
+ // Don't allow if new source is the same as target
201
+ if (newSourceField.id === mapping.targetField.id)
202
+ return;
203
+ // Don't allow if source already exists in the mapping
204
+ if (mapping.sourceFields.some(sf => sf.id === newSourceField.id))
205
+ return;
206
+ if (sourceFieldIndex !== undefined && mapping.sourceFields.length > 1) {
207
+ // Replace specific source field in multi-source mapping
208
+ const newSourceFields = [...mapping.sourceFields];
209
+ newSourceFields[sourceFieldIndex] = newSourceField;
210
+ this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: newSourceFields } : m));
211
+ }
212
+ else {
213
+ // Single source mapping - replace the source field
214
+ this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: [newSourceField] } : m));
215
+ }
216
+ }
217
+ changeTargetField(mappingId, newTargetField) {
218
+ const mapping = this.mappings().find(m => m.id === mappingId);
219
+ if (!mapping)
220
+ return;
221
+ // Don't allow if new target is same as any source
222
+ if (mapping.sourceFields.some(sf => sf.id === newTargetField.id))
223
+ return;
224
+ // Check if another mapping already targets this field
225
+ const existingMapping = this.mappings().find(m => m.targetField.id === newTargetField.id && m.id !== mappingId);
226
+ if (existingMapping) {
227
+ // Merge into existing mapping (add sources)
228
+ const mergedSources = [
229
+ ...existingMapping.sourceFields,
230
+ ...mapping.sourceFields.filter(sf => !existingMapping.sourceFields.some(esf => esf.id === sf.id)),
231
+ ];
232
+ this.mappings.update(mappings => mappings
233
+ .filter(m => m.id !== mappingId) // Remove old mapping
234
+ .map(m => m.id === existingMapping.id
235
+ ? {
236
+ ...m,
237
+ sourceFields: mergedSources,
238
+ transformations: mergedSources.length > 1
239
+ ? [{ type: 'concat', separator: ' ' }]
240
+ : m.transformations,
241
+ }
242
+ : m));
243
+ }
244
+ else {
245
+ // Simply update the target field
246
+ this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, targetField: newTargetField } : m));
247
+ }
248
+ }
168
249
  createMapping(sourceFields, targetField, transformation) {
169
250
  // Check if this is an array-to-array mapping
170
251
  const sourceField = sourceFields[0];
@@ -375,13 +456,20 @@ class MappingService {
375
456
  this.arrayToObjectMappings.set([]);
376
457
  this.defaultValues.set([]);
377
458
  this.selectedMappingId.set(null);
459
+ this._sourceSchemaRef.set(null);
460
+ this._targetSchemaRef.set(null);
461
+ }
462
+ setSourceSchemaRef(ref) {
463
+ this._sourceSchemaRef.set(ref);
464
+ }
465
+ setTargetSchemaRef(ref) {
466
+ this._targetSchemaRef.set(ref);
378
467
  }
379
468
  // Default value methods
380
469
  setDefaultValue(targetField, value) {
381
- const valueType = this.getValueType(targetField.type);
382
470
  const existingDefault = this.defaultValues().find(d => d.targetField.id === targetField.id);
383
471
  if (existingDefault) {
384
- const updated = { ...existingDefault, value, valueType };
472
+ const updated = { ...existingDefault, value };
385
473
  this.defaultValues.update(dv => dv.map(d => d.id === existingDefault.id ? updated : d));
386
474
  return updated;
387
475
  }
@@ -389,7 +477,6 @@ class MappingService {
389
477
  id: this.generateId(),
390
478
  targetField,
391
479
  value,
392
- valueType,
393
480
  };
394
481
  this.defaultValues.update(dv => [...dv, newDefault]);
395
482
  return newDefault;
@@ -403,14 +490,6 @@ class MappingService {
403
490
  hasDefaultValue(targetFieldId) {
404
491
  return this.defaultValues().some(d => d.targetField.id === targetFieldId);
405
492
  }
406
- getValueType(fieldType) {
407
- switch (fieldType) {
408
- case 'number': return 'number';
409
- case 'boolean': return 'boolean';
410
- case 'date': return 'date';
411
- default: return 'string';
412
- }
413
- }
414
493
  exportMappings(name, description) {
415
494
  const exportData = {
416
495
  version: '1.0',
@@ -421,13 +500,20 @@ class MappingService {
421
500
  arrayToObjectMappings: this.arrayToObjectMappings(),
422
501
  defaultValues: this.defaultValues(),
423
502
  };
503
+ // Include schema refs if set
504
+ if (this._sourceSchemaRef()) {
505
+ exportData['sourceSchemaRef'] = this._sourceSchemaRef();
506
+ }
507
+ if (this._targetSchemaRef()) {
508
+ exportData['targetSchemaRef'] = this._targetSchemaRef();
509
+ }
424
510
  return JSON.stringify(exportData, null, 2);
425
511
  }
426
512
  /**
427
513
  * Export mappings as a MappingDocument object (not stringified)
428
514
  */
429
515
  exportMappingsAsObject(name, description) {
430
- return {
516
+ const exportData = {
431
517
  version: '1.0',
432
518
  name: name || 'Mapping Configuration',
433
519
  description: description || '',
@@ -436,6 +522,14 @@ class MappingService {
436
522
  arrayToObjectMappings: this.arrayToObjectMappings(),
437
523
  defaultValues: this.defaultValues(),
438
524
  };
525
+ // Include schema refs if set
526
+ if (this._sourceSchemaRef()) {
527
+ exportData['sourceSchemaRef'] = this._sourceSchemaRef();
528
+ }
529
+ if (this._targetSchemaRef()) {
530
+ exportData['targetSchemaRef'] = this._targetSchemaRef();
531
+ }
532
+ return exportData;
439
533
  }
440
534
  importMappings(json) {
441
535
  try {
@@ -445,6 +539,8 @@ class MappingService {
445
539
  this.arrayMappings.set(data.arrayMappings || []);
446
540
  this.arrayToObjectMappings.set(data.arrayToObjectMappings || []);
447
541
  this.defaultValues.set(data.defaultValues || []);
542
+ this._sourceSchemaRef.set(data.sourceSchemaRef || null);
543
+ this._targetSchemaRef.set(data.targetSchemaRef || null);
448
544
  }
449
545
  else {
450
546
  // Legacy format
@@ -842,15 +938,17 @@ class SvgConnectorService {
842
938
  }
843
939
  calculateConnectionPoint(rect, side, containerRect) {
844
940
  const relativeY = rect.top - containerRect.top + rect.height / 2;
941
+ // Offset to position endpoint circles outside the schema panels
942
+ const endpointOffset = 8;
845
943
  if (side === 'source') {
846
944
  return {
847
- x: rect.right - containerRect.left,
945
+ x: rect.right - containerRect.left + endpointOffset,
848
946
  y: relativeY,
849
947
  };
850
948
  }
851
949
  else {
852
950
  return {
853
- x: rect.left - containerRect.left,
951
+ x: rect.left - containerRect.left - endpointOffset,
854
952
  y: relativeY,
855
953
  };
856
954
  }
@@ -1116,6 +1214,7 @@ class SchemaTreeComponent {
1116
1214
  fieldDragStart = new EventEmitter();
1117
1215
  fieldDragEnd = new EventEmitter();
1118
1216
  fieldDrop = new EventEmitter();
1217
+ sourceDrop = new EventEmitter(); // For endpoint dragging - drop on source field
1119
1218
  fieldPositionsChanged = new EventEmitter();
1120
1219
  fieldDefaultValueClick = new EventEmitter();
1121
1220
  schemaFieldsContainer;
@@ -1167,21 +1266,45 @@ class SchemaTreeComponent {
1167
1266
  onDragStart(event, field) {
1168
1267
  if (this.side !== 'source')
1169
1268
  return;
1269
+ // Don't start new drag if endpoint dragging is in progress
1270
+ const dragState = this.mappingService.currentDragState();
1271
+ if (dragState.isDragging)
1272
+ return;
1170
1273
  const element = event.currentTarget;
1171
1274
  const rect = element.getBoundingClientRect();
1172
1275
  this.fieldDragStart.emit({ field, element, rect });
1173
1276
  }
1277
+ isEndpointDragMode() {
1278
+ const dragState = this.mappingService.currentDragState();
1279
+ return dragState.isDragging && (dragState.dragMode === 'move-source' || dragState.dragMode === 'move-target');
1280
+ }
1281
+ isSourceEndpointDragging() {
1282
+ const dragState = this.mappingService.currentDragState();
1283
+ return dragState.isDragging && dragState.dragMode === 'move-source';
1284
+ }
1285
+ isTargetEndpointDragging() {
1286
+ const dragState = this.mappingService.currentDragState();
1287
+ return dragState.isDragging && dragState.dragMode === 'move-target';
1288
+ }
1174
1289
  onDragOver(event) {
1175
1290
  if (this.side === 'target') {
1176
1291
  event.preventDefault();
1177
1292
  }
1178
1293
  }
1179
1294
  onDrop(event, field) {
1180
- if (this.side !== 'target')
1181
- return;
1182
1295
  const element = event.currentTarget;
1183
1296
  const rect = element.getBoundingClientRect();
1184
- this.fieldDrop.emit({ field, element, rect });
1297
+ // Check if endpoint dragging is in progress (via MappingService drag state)
1298
+ const dragState = this.mappingService.currentDragState();
1299
+ // Handle source drop during endpoint dragging (moving source endpoint)
1300
+ if (this.side === 'source' && dragState.isDragging && dragState.dragMode === 'move-source') {
1301
+ this.sourceDrop.emit({ field, element, rect });
1302
+ return;
1303
+ }
1304
+ // Handle target drop (either endpoint dragging or new mapping)
1305
+ if (this.side === 'target') {
1306
+ this.fieldDrop.emit({ field, element, rect });
1307
+ }
1185
1308
  }
1186
1309
  getTypeIcon(type) {
1187
1310
  const icons = {
@@ -1218,7 +1341,7 @@ class SchemaTreeComponent {
1218
1341
  const defaultValue = this.defaultValues.find(d => d.targetField.id === field.id);
1219
1342
  if (!defaultValue || defaultValue.value === null)
1220
1343
  return '';
1221
- if (defaultValue.valueType === 'date' && defaultValue.value) {
1344
+ if (defaultValue.targetField.type === 'date' && defaultValue.value) {
1222
1345
  return new Date(defaultValue.value).toLocaleDateString();
1223
1346
  }
1224
1347
  return String(defaultValue.value);
@@ -1244,11 +1367,11 @@ class SchemaTreeComponent {
1244
1367
  return field.id;
1245
1368
  }
1246
1369
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1247
- 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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
1370
+ 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", sourceDrop: "sourceDrop", 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') && !isEndpointDragMode()\"\n [class.droppable]=\"(side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')) || (side === 'source' && isSourceEndpointDragging() && ((!field.children || field.children.length === 0) || field.type === 'array'))\"\n [class.endpoint-drop-target]=\"(side === 'source' && isSourceEndpointDragging()) || (side === 'target' && isTargetEndpointDragging())\"\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(--data-mapper-panel-bg, var(--surface-card, #ffffff));border-radius:var(--data-mapper-panel-border-radius, 12px);border:var(--data-mapper-panel-border, none);box-shadow:var(--data-mapper-panel-shadow, 0 2px 8px rgba(0, 0, 0, .08));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}.field-item.endpoint-drop-target{background-color:#fef3c7}.field-item.endpoint-drop-target:hover{background-color:#fde68a}.field-item.endpoint-drop-target .connection-point .point-dot{animation:pulse-drop-target 1s ease-in-out infinite}@keyframes pulse-drop-target{0%,to{transform:scale(1);box-shadow:0 0 0 3px #f59e0b4d}50%{transform:scale(1.2);box-shadow:0 0 0 6px #f59e0b33}}.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
1248
1371
  }
1249
1372
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaTreeComponent, decorators: [{
1250
1373
  type: Component,
1251
- 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"] }]
1374
+ 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') && !isEndpointDragMode()\"\n [class.droppable]=\"(side === 'target' && ((!field.children || field.children.length === 0) || field.type === 'array' || field.type === 'object')) || (side === 'source' && isSourceEndpointDragging() && ((!field.children || field.children.length === 0) || field.type === 'array'))\"\n [class.endpoint-drop-target]=\"(side === 'source' && isSourceEndpointDragging()) || (side === 'target' && isTargetEndpointDragging())\"\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(--data-mapper-panel-bg, var(--surface-card, #ffffff));border-radius:var(--data-mapper-panel-border-radius, 12px);border:var(--data-mapper-panel-border, none);box-shadow:var(--data-mapper-panel-shadow, 0 2px 8px rgba(0, 0, 0, .08));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}.field-item.endpoint-drop-target{background-color:#fef3c7}.field-item.endpoint-drop-target:hover{background-color:#fde68a}.field-item.endpoint-drop-target .connection-point .point-dot{animation:pulse-drop-target 1s ease-in-out infinite}@keyframes pulse-drop-target{0%,to{transform:scale(1);box-shadow:0 0 0 3px #f59e0b4d}50%{transform:scale(1.2);box-shadow:0 0 0 6px #f59e0b33}}.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"] }]
1252
1375
  }], propDecorators: { schema: [{
1253
1376
  type: Input
1254
1377
  }], side: [{
@@ -1263,6 +1386,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1263
1386
  type: Output
1264
1387
  }], fieldDrop: [{
1265
1388
  type: Output
1389
+ }], sourceDrop: [{
1390
+ type: Output
1266
1391
  }], fieldPositionsChanged: [{
1267
1392
  type: Output
1268
1393
  }], fieldDefaultValueClick: [{
@@ -1458,7 +1583,7 @@ class ConditionBuilderComponent {
1458
1583
  return op?.label || operator;
1459
1584
  }
1460
1585
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ConditionBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1461
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ConditionBuilderComponent, isStandalone: true, selector: "condition-builder", inputs: { fields: "fields", condition: "condition", compact: "compact" }, outputs: { conditionChange: "conditionChange" }, ngImport: i0, template: "<div class=\"condition-builder\" [class.compact]=\"compact\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<!-- Recursive group template -->\r\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\r\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\r\n <!-- Group header with logic toggle (only show if multiple children or nested) -->\r\n @if (group.children.length > 1 || !isRoot) {\r\n <div class=\"group-header\">\r\n <div class=\"logic-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'and'\"\r\n (click)=\"onLogicChange(group, 'and')\"\r\n >\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'or'\"\r\n (click)=\"onLogicChange(group, 'or')\"\r\n >\r\n OR\r\n </button>\r\n </div>\r\n @if (!isRoot) {\r\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group children -->\r\n <div class=\"group-children\">\r\n @for (item of group.children; track item.id; let i = $index) {\r\n <!-- Logic connector between items -->\r\n @if (i > 0) {\r\n <div class=\"logic-connector\">\r\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\r\n {{ group.logic | uppercase }}\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (isCondition(item)) {\r\n <!-- Condition row -->\r\n <div class=\"condition-row\">\r\n <!-- Field selector (only if multiple fields) -->\r\n @if (showFieldSelector) {\r\n <mat-form-field appearance=\"outline\" class=\"field-select\">\r\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\r\n @for (field of availableFields; track field.path) {\r\n <mat-option [value]=\"field.path\">{{ field.name }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- Operator selector -->\r\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\r\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\r\n @for (op of getOperatorsForField(item.field); track op.value) {\r\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Value input (only if operator needs value) -->\r\n @if (operatorNeedsValue(item.operator)) {\r\n @if (item.valueType === 'boolean') {\r\n <mat-slide-toggle\r\n [checked]=\"item.value === true\"\r\n (change)=\"onValueChange(item, $event.checked)\"\r\n class=\"bool-toggle\"\r\n >\r\n </mat-slide-toggle>\r\n } @else if (item.valueType === 'number') {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n } @else {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"text\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- Remove condition button -->\r\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove\" (click)=\"removeItem(group, item.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n } @else if (isGroup(item)) {\r\n <!-- Nested group -->\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\r\n }\r\n }\r\n\r\n <!-- Empty state -->\r\n @if (group.children.length === 0) {\r\n <div class=\"empty-group\">\r\n <span>No conditions defined</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Group actions -->\r\n <div class=\"group-actions\">\r\n <button type=\"button\" class=\"add-btn\" (click)=\"addCondition(group)\">\r\n <mat-icon>add</mat-icon>\r\n @if (!compact) {\r\n <span>Add condition</span>\r\n }\r\n </button>\r\n @if (!compact && group.children.length > 0) {\r\n <button type=\"button\" class=\"add-btn add-group-btn\" (click)=\"addGroup(group)\">\r\n <mat-icon>folder_open</mat-icon>\r\n <span>Add group</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".condition-builder.compact .filter-group{padding:8px}.condition-builder.compact .condition-row{gap:6px}.condition-builder.compact .operator-select{min-width:100px}.condition-builder.compact .value-input{min-width:80px}.condition-builder.compact .add-btn{padding:4px 8px;font-size:12px}.filter-group{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px}.filter-group.nested-group{margin-top:8px;background:#f1f5f9;border-color:#cbd5e1}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.logic-toggle{display:flex;gap:4px}.logic-btn{padding:4px 12px;border:1px solid #cbd5e1;background:#fff;border-radius:4px;font-size:11px;font-weight:600;color:#64748b;cursor:pointer;transition:all .15s ease}.logic-btn:hover{border-color:#94a3b8}.logic-btn.active{background:#6366f1;border-color:#6366f1;color:#fff}.group-children{display:flex;flex-direction:column;gap:8px}.logic-connector{display:flex;align-items:center;justify-content:center;padding:4px 0}.logic-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px}.logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-badge.or{background:#fef3c7;color:#b45309}.condition-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.field-select{min-width:120px}.operator-select{min-width:130px}.value-input{min-width:100px;flex:1}.bool-toggle{margin:0 8px}.remove-btn,.remove-group-btn{width:28px;height:28px;line-height:28px;flex-shrink:0}.remove-btn mat-icon,.remove-group-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-btn:hover mat-icon,.remove-group-btn:hover mat-icon{color:#ef4444}.empty-group{display:flex;align-items:center;justify-content:center;padding:12px;color:#94a3b8;font-size:13px;font-style:italic}.group-actions{display:flex;gap:8px;margin-top:8px;padding-top:8px;border-top:1px dashed #e2e8f0}.add-btn{display:flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px dashed #cbd5e1;border-radius:6px;font-size:13px;color:#64748b;cursor:pointer;transition:all .15s ease}.add-btn mat-icon{font-size:16px;width:16px;height:16px}.add-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.add-btn.add-group-btn{border-style:solid}::ng-deep .condition-builder .mat-mdc-form-field{font-size:13px}::ng-deep .condition-builder .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .condition-builder .mdc-text-field--outlined{--mdc-outlined-text-field-container-shape: 6px}::ng-deep .condition-builder .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}\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.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: "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: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
1586
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ConditionBuilderComponent, isStandalone: true, selector: "condition-builder", inputs: { fields: "fields", condition: "condition", compact: "compact" }, outputs: { conditionChange: "conditionChange" }, ngImport: i0, template: "<div class=\"condition-builder\" [class.compact]=\"compact\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: rootGroup, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<!-- Recursive group template -->\r\n<ng-template #groupTemplate let-group=\"group\" let-isRoot=\"isRoot\" let-parentGroup=\"parentGroup\">\r\n <div class=\"filter-group\" [class.root-group]=\"isRoot\" [class.nested-group]=\"!isRoot\">\r\n <!-- Group header with logic toggle (only show if multiple children or nested) -->\r\n @if (group.children.length > 1 || !isRoot) {\r\n <div class=\"group-header\">\r\n <div class=\"logic-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'and'\"\r\n (click)=\"onLogicChange(group, 'and')\"\r\n >\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"logic-btn\"\r\n [class.active]=\"group.logic === 'or'\"\r\n (click)=\"onLogicChange(group, 'or')\"\r\n >\r\n OR\r\n </button>\r\n </div>\r\n @if (!isRoot) {\r\n <button mat-icon-button class=\"remove-group-btn\" matTooltip=\"Remove group\" (click)=\"removeItem(parentGroup, group.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Group children -->\r\n <div class=\"group-children\">\r\n @for (item of group.children; track item.id; let i = $index) {\r\n <!-- Logic connector between items -->\r\n @if (i > 0) {\r\n <div class=\"logic-connector\">\r\n <span class=\"logic-badge\" [class.and]=\"group.logic === 'and'\" [class.or]=\"group.logic === 'or'\">\r\n {{ group.logic | uppercase }}\r\n </span>\r\n </div>\r\n }\r\n\r\n @if (isCondition(item)) {\r\n <!-- Condition row -->\r\n <div class=\"condition-row\">\r\n <!-- Field selector (only if multiple fields) -->\r\n @if (showFieldSelector) {\r\n <mat-form-field appearance=\"outline\" class=\"field-select\">\r\n <mat-select [value]=\"item.field\" (selectionChange)=\"onFieldChange(item, $event.value)\">\r\n @for (field of availableFields; track field.path) {\r\n <mat-option [value]=\"field.path\">{{ field.name }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n }\r\n\r\n <!-- Operator selector -->\r\n <mat-form-field appearance=\"outline\" class=\"operator-select\">\r\n <mat-select [value]=\"item.operator\" (selectionChange)=\"onOperatorChange(item, $event.value)\">\r\n @for (op of getOperatorsForField(item.field); track op.value) {\r\n <mat-option [value]=\"op.value\">{{ op.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Value input (only if operator needs value) -->\r\n @if (operatorNeedsValue(item.operator)) {\r\n @if (item.valueType === 'boolean') {\r\n <mat-slide-toggle\r\n [checked]=\"item.value === true\"\r\n (change)=\"onValueChange(item, $event.checked)\"\r\n class=\"bool-toggle\"\r\n >\r\n </mat-slide-toggle>\r\n } @else if (item.valueType === 'number') {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"number\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n } @else {\r\n <mat-form-field appearance=\"outline\" class=\"value-input\">\r\n <input\r\n matInput\r\n type=\"text\"\r\n [value]=\"item.value\"\r\n (input)=\"onValueChange(item, $any($event.target).value)\"\r\n placeholder=\"value\"\r\n />\r\n </mat-form-field>\r\n }\r\n }\r\n\r\n <!-- Remove condition button -->\r\n <button mat-icon-button class=\"remove-btn\" matTooltip=\"Remove\" (click)=\"removeItem(group, item.id)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n } @else if (isGroup(item)) {\r\n <!-- Nested group -->\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { group: item, isRoot: false, parentGroup: group }\"></ng-container>\r\n }\r\n }\r\n\r\n <!-- Empty state -->\r\n @if (group.children.length === 0) {\r\n <div class=\"empty-group\">\r\n <span>No conditions defined</span>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Group actions -->\r\n <div class=\"group-actions\">\r\n <button type=\"button\" class=\"add-btn\" (click)=\"addCondition(group)\">\r\n <mat-icon>add</mat-icon>\r\n @if (!compact) {\r\n <span>Add condition</span>\r\n }\r\n </button>\r\n @if (!compact && group.children.length > 0) {\r\n <button type=\"button\" class=\"add-btn add-group-btn\" (click)=\"addGroup(group)\">\r\n <mat-icon>folder_open</mat-icon>\r\n <span>Add group</span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n</ng-template>\r\n", styles: [".condition-builder.compact .filter-group{padding:8px}.condition-builder.compact .condition-row{gap:6px}.condition-builder.compact .operator-select{min-width:100px}.condition-builder.compact .value-input{min-width:80px}.condition-builder.compact .add-btn{padding:4px 8px;font-size:12px}.filter-group{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px}.filter-group.nested-group{margin-top:8px;background:#f1f5f9;border-color:#cbd5e1}.group-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.logic-toggle{display:flex;gap:4px}.logic-btn{padding:4px 12px;border:1px solid #cbd5e1;background:#fff;border-radius:4px;font-size:11px;font-weight:600;color:#64748b;cursor:pointer;transition:all .15s ease}.logic-btn:hover{border-color:#94a3b8}.logic-btn.active{background:#6366f1;border-color:#6366f1;color:#fff}.group-children{display:flex;flex-direction:column;gap:8px}.logic-connector{display:flex;align-items:center;justify-content:center;padding:4px 0}.logic-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px}.logic-badge.and{background:#dbeafe;color:#1d4ed8}.logic-badge.or{background:#fef3c7;color:#b45309}.condition-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.field-select{min-width:120px}.operator-select{min-width:130px}.value-input{min-width:100px;flex:1}.bool-toggle{margin:0 8px}.remove-btn,.remove-group-btn{width:28px;height:28px;line-height:28px;flex-shrink:0}.remove-btn mat-icon,.remove-group-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-btn:hover mat-icon,.remove-group-btn:hover mat-icon{color:#ef4444}.empty-group{display:flex;align-items:center;justify-content:center;padding:12px;color:#94a3b8;font-size:13px;font-style:italic}.group-actions{display:flex;gap:8px;margin-top:8px;padding-top:8px;border-top:1px dashed #e2e8f0}.add-btn{display:flex;align-items:center;gap:4px;padding:6px 12px;background:#fff;border:1px dashed #cbd5e1;border-radius:6px;font-size:13px;color:#64748b;cursor:pointer;transition:all .15s ease}.add-btn mat-icon{font-size:16px;width:16px;height:16px}.add-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.add-btn.add-group-btn{border-style:solid}::ng-deep .condition-builder .mat-mdc-form-field{font-size:13px}::ng-deep .condition-builder .mat-mdc-form-field-subscript-wrapper{display:none}::ng-deep .condition-builder .mdc-text-field--outlined{--mdc-outlined-text-field-container-shape: 6px}::ng-deep .condition-builder .mat-mdc-form-field-infix{padding-top:8px!important;padding-bottom:8px!important;min-height:36px}\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.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "component", type: i4.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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7$1.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
1462
1587
  }
1463
1588
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ConditionBuilderComponent, decorators: [{
1464
1589
  type: Component,
@@ -1759,7 +1884,7 @@ class TransformationPopoverComponent {
1759
1884
  return labels[operator] || operator;
1760
1885
  }
1761
1886
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1762
- 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()\">\r\n <div class=\"popover-arrow\"></div>\r\n\r\n <div class=\"popover-header\">\r\n <span class=\"popover-title\">Transformation</span>\r\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-content\">\r\n <!-- Source/Target Info -->\r\n <div class=\"mapping-info\">\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Source:</span>\r\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\r\n </div>\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Target:</span>\r\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Single Step Mode (default, clean UI) -->\r\n @if (!isMultiStep) {\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: steps[0], index: 0, showHeader: false }\"></ng-container>\r\n\r\n <!-- Preview Section -->\r\n <div class=\"preview-section\">\r\n <span class=\"preview-label\">Preview:</span>\r\n <div class=\"preview-value\">{{ stepPreviews[0] || '(empty)' }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Multi-Step Mode -->\r\n @if (isMultiStep) {\r\n <div class=\"steps-container\" cdkDropList (cdkDropListDropped)=\"onStepDrop($event)\">\r\n @for (step of steps; track $index; let i = $index) {\r\n <div class=\"step-card\" [class.expanded]=\"isStepExpanded(i)\" cdkDrag>\r\n <!-- Collapsed View -->\r\n @if (!isStepExpanded(i)) {\r\n <div class=\"step-collapsed\" (click)=\"toggleStep(i)\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle (click)=\"$event.stopPropagation()\">drag_indicator</mat-icon>\r\n <div class=\"step-collapsed-header\">\r\n <div class=\"step-title-row\">\r\n <span class=\"step-number\">Step {{ i + 1 }}: {{ getStepTypeLabel(step.type) }}</span>\r\n @if (hasCondition(step)) {\r\n <span class=\"condition-badge\" matTooltip=\"{{ getConditionSummary(step) }}\">\r\n <mat-icon>filter_alt</mat-icon>\r\n if\r\n </span>\r\n }\r\n </div>\r\n <div class=\"step-collapsed-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"expand-btn\"\r\n (click)=\"toggleStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Edit step\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"step-collapsed-preview\">\r\n <span class=\"step-input\">\"{{ (stepInputs[i] || '') | slice:0:20 }}{{ (stepInputs[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n <mat-icon class=\"arrow-icon\">arrow_forward</mat-icon>\r\n <span class=\"step-output\">\"{{ (stepPreviews[i] || '') | slice:0:20 }}{{ (stepPreviews[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Expanded View -->\r\n @if (isStepExpanded(i)) {\r\n <div class=\"step-expanded\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle>drag_indicator</mat-icon>\r\n <div class=\"step-header\">\r\n <span class=\"step-number\">Step {{ i + 1 }}</span>\r\n <div class=\"step-header-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"collapse-btn\"\r\n (click)=\"toggleStep(i)\"\r\n matTooltip=\"Collapse\"\r\n >\r\n <mat-icon>expand_less</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i)\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-content\">\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: step, index: i, showHeader: false }\"></ng-container>\r\n </div>\r\n\r\n <!-- Step Preview -->\r\n <div class=\"step-preview\">\r\n <mat-icon class=\"preview-arrow\">arrow_downward</mat-icon>\r\n <span class=\"step-preview-value\">{{ stepPreviews[i] || '(empty)' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Final Preview -->\r\n <div class=\"preview-section final-preview\">\r\n <span class=\"preview-label\">Final Result:</span>\r\n <div class=\"preview-value\">{{ finalPreview || '(empty)' }}</div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Add Step Button - Always visible -->\r\n <div class=\"add-step-section\">\r\n <button mat-stroked-button class=\"add-step-btn\" (click)=\"addStep()\">\r\n <mat-icon>add</mat-icon>\r\n @if (isMultiStep) {\r\n Add Step\r\n } @else {\r\n Add Transformation Step\r\n }\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-actions\">\r\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n <div class=\"action-spacer\"></div>\r\n <button mat-button (click)=\"onClose()\">Cancel</button>\r\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n<!-- Backdrop -->\r\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\r\n\r\n<!-- Step Configuration Template -->\r\n<ng-template #stepConfig let-step=\"step\" let-index=\"index\" let-showHeader=\"showHeader\">\r\n <!-- Transformation Type -->\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Transformation Type</mat-label>\r\n <mat-select [(ngModel)]=\"step.type\" (selectionChange)=\"onStepTypeChange(index)\">\r\n @for (t of availableTransformations; track t.type) {\r\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Type-specific options -->\r\n @switch (step.type) {\r\n @case ('concat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Separator</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.separator\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\" \"\r\n />\r\n <mat-hint>Join values with this (default: space)</mat-hint>\r\n </mat-form-field>\r\n <div class=\"or-divider\">or use template for custom format</div>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n [placeholder]=\"'{0} - {1}'\"\r\n />\r\n <mat-hint>Overrides separator if set</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('substring') {\r\n <div class=\"config-section config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Start Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.startIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>End Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.endIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('replace') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Search For</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.searchValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Replace With</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.replaceValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('dateFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Output Format</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.outputFormat\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"YYYY-MM-DD\"\r\n />\r\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('numberFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Decimal Places</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.decimalPlaces\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <div class=\"config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Prefix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.prefix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"$\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Suffix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.suffix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('mask') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Pattern</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.pattern\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"(###) ###-####\"\r\n />\r\n <mat-hint># = character from input</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('template') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template Expression</mat-label>\r\n <textarea\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n rows=\"3\"\r\n [placeholder]=\"'Hello {0}, your ID is {1}'\"\r\n ></textarea>\r\n @if (index === 0) {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }}, {{ '{' }}1{{ '}' }}, etc. for source fields</mat-hint>\r\n } @else {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }} for the value from previous step</mat-hint>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Condition Section -->\r\n <div class=\"condition-section\">\r\n <mat-checkbox\r\n [checked]=\"hasCondition(step)\"\r\n (change)=\"toggleCondition(step, $event.checked)\"\r\n class=\"condition-checkbox\"\r\n >\r\n Apply conditionally\r\n </mat-checkbox>\r\n\r\n @if (hasCondition(step)) {\r\n <div class=\"condition-content\">\r\n <condition-builder\r\n [condition]=\"step.condition?.root || null\"\r\n [compact]=\"true\"\r\n (conditionChange)=\"onConditionChange(step, $event)\"\r\n ></condition-builder>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:420px;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:500px;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}.or-divider{text-align:center;font-size:11px;color:#94a3b8;margin:8px 0}.condition-section{margin-top:16px;padding-top:12px;border-top:1px dashed #e2e8f0}.condition-checkbox{font-size:13px;color:#64748b}.condition-content{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}.steps-container{display:flex;flex-direction:column;gap:12px}.step-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;transition:all .2s ease}.step-card.expanded{border-color:#6366f1;border-width:2px;background:#fff}.step-collapsed{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px;cursor:pointer;transition:background .15s ease}.step-collapsed:hover{background:#f1f5f9}.step-collapsed .drag-handle{grid-row:span 2;align-self:center}.step-collapsed-header{display:flex;align-items:center;flex-wrap:wrap;gap:4px}.step-collapsed-header .step-title-row{display:flex;align-items:center;gap:8px;flex:1}.step-collapsed-header .step-number{flex-shrink:0}.condition-badge{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;background:#fef3c7;border:1px solid #f59e0b;border-radius:4px;font-size:10px;font-weight:600;color:#b45309;cursor:help}.condition-badge mat-icon{font-size:12px;width:12px;height:12px}.step-collapsed-actions{display:flex;align-items:center;gap:4px}.step-collapsed-preview{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:12px;font-family:Monaco,Menlo,monospace}.step-input{color:#64748b;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.arrow-icon{font-size:14px;width:14px;height:14px;color:#6366f1}.step-output{color:#059669;font-weight:500;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.step-expanded{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px}.step-expanded .drag-handle{grid-row:1;align-self:center}.step-expanded .step-header,.step-expanded .step-content,.step-expanded .step-preview{grid-column:2}.step-header{display:flex;align-items:center;margin-bottom:12px}.step-header .step-number{flex:1}.step-header-actions{display:flex;align-items:center;gap:4px}.step-number{font-size:12px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px}.expand-btn,.collapse-btn{width:28px;height:28px;line-height:28px}.expand-btn mat-icon,.collapse-btn mat-icon{font-size:18px;width:18px;height:18px;color:#64748b}.expand-btn:hover mat-icon,.collapse-btn:hover mat-icon{color:#6366f1}.remove-step-btn{width:28px;height:28px;line-height:28px}.remove-step-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-step-btn:hover mat-icon{color:#ef4444}.step-preview{display:flex;align-items:center;gap:8px;margin-top:12px;padding:8px 12px;background:#f1f5f9;border-radius:6px}.preview-arrow{font-size:16px;width:16px;height:16px;color:#6366f1}.step-preview-value{font-size:13px;color:#475569;font-family:Monaco,Menlo,monospace;word-break:break-all;flex:1}.add-step-section{padding:12px 20px;border-top:1px solid #e2e8f0;background:#fafafa}.add-step-btn{width:100%;border-style:dashed;color:#64748b}.add-step-btn mat-icon{font-size:18px;width:18px;height:18px;margin-right:4px}.add-step-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.final-preview{margin-top:16px;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:2px solid #10b981;border-radius:8px;position:relative}.final-preview .preview-label{color:#059669;font-size:12px;font-weight:700}.final-preview .preview-value{color:#065f46;font-weight:600}.final-preview:before{content:\"\\2713\";position:absolute;right:12px;top:10px;color:#10b981;font-size:16px;font-weight:700}.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}.drag-handle{cursor:grab;color:#94a3b8;font-size:20px;width:20px;height:20px;flex-shrink:0}.drag-handle:hover{color:#6366f1}.drag-handle:active{cursor:grabbing}.step-card.cdk-drag-preview{box-shadow:0 5px 20px #00000040;border-radius:8px;background:#fff}.step-card.cdk-drag-placeholder{opacity:.4;background:#e2e8f0;border:2px dashed #94a3b8}.step-card.cdk-drag-animating{transition:transform .2s ease}.steps-container.cdk-drop-list-dragging .step-card:not(.cdk-drag-placeholder){transition:transform .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2$1.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.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.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i9.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i9.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i9.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionBuilderComponent, selector: "condition-builder", inputs: ["fields", "condition", "compact"], outputs: ["conditionChange"] }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }] });
1887
+ 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()\">\r\n <div class=\"popover-arrow\"></div>\r\n\r\n <div class=\"popover-header\">\r\n <span class=\"popover-title\">Transformation</span>\r\n <button mat-icon-button class=\"close-btn\" (click)=\"onClose()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-content\">\r\n <!-- Source/Target Info -->\r\n <div class=\"mapping-info\">\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Source:</span>\r\n <span class=\"info-value\">{{ getSourceFieldNames() }}</span>\r\n </div>\r\n <div class=\"info-row\">\r\n <span class=\"info-label\">Target:</span>\r\n <span class=\"info-value\">{{ mapping.targetField.name }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Single Step Mode (default, clean UI) -->\r\n @if (!isMultiStep) {\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: steps[0], index: 0, showHeader: false }\"></ng-container>\r\n\r\n <!-- Preview Section -->\r\n <div class=\"preview-section\">\r\n <span class=\"preview-label\">Preview:</span>\r\n <div class=\"preview-value\">{{ stepPreviews[0] || '(empty)' }}</div>\r\n </div>\r\n }\r\n\r\n <!-- Multi-Step Mode -->\r\n @if (isMultiStep) {\r\n <div class=\"steps-container\" cdkDropList (cdkDropListDropped)=\"onStepDrop($event)\">\r\n @for (step of steps; track $index; let i = $index) {\r\n <div class=\"step-card\" [class.expanded]=\"isStepExpanded(i)\" cdkDrag>\r\n <!-- Collapsed View -->\r\n @if (!isStepExpanded(i)) {\r\n <div class=\"step-collapsed\" (click)=\"toggleStep(i)\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle (click)=\"$event.stopPropagation()\">drag_indicator</mat-icon>\r\n <div class=\"step-collapsed-header\">\r\n <div class=\"step-title-row\">\r\n <span class=\"step-number\">Step {{ i + 1 }}: {{ getStepTypeLabel(step.type) }}</span>\r\n @if (hasCondition(step)) {\r\n <span class=\"condition-badge\" matTooltip=\"{{ getConditionSummary(step) }}\">\r\n <mat-icon>filter_alt</mat-icon>\r\n if\r\n </span>\r\n }\r\n </div>\r\n <div class=\"step-collapsed-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"expand-btn\"\r\n (click)=\"toggleStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Edit step\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i); $event.stopPropagation()\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n <div class=\"step-collapsed-preview\">\r\n <span class=\"step-input\">\"{{ (stepInputs[i] || '') | slice:0:20 }}{{ (stepInputs[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n <mat-icon class=\"arrow-icon\">arrow_forward</mat-icon>\r\n <span class=\"step-output\">\"{{ (stepPreviews[i] || '') | slice:0:20 }}{{ (stepPreviews[i] || '').length > 20 ? '...' : '' }}\"</span>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Expanded View -->\r\n @if (isStepExpanded(i)) {\r\n <div class=\"step-expanded\">\r\n <mat-icon class=\"drag-handle\" cdkDragHandle>drag_indicator</mat-icon>\r\n <div class=\"step-header\">\r\n <span class=\"step-number\">Step {{ i + 1 }}</span>\r\n <div class=\"step-header-actions\">\r\n <button\r\n mat-icon-button\r\n class=\"collapse-btn\"\r\n (click)=\"toggleStep(i)\"\r\n matTooltip=\"Collapse\"\r\n >\r\n <mat-icon>expand_less</mat-icon>\r\n </button>\r\n <button\r\n mat-icon-button\r\n class=\"remove-step-btn\"\r\n (click)=\"removeStep(i)\"\r\n matTooltip=\"Remove step\"\r\n >\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"step-content\">\r\n <ng-container *ngTemplateOutlet=\"stepConfig; context: { step: step, index: i, showHeader: false }\"></ng-container>\r\n </div>\r\n\r\n <!-- Step Preview -->\r\n <div class=\"step-preview\">\r\n <mat-icon class=\"preview-arrow\">arrow_downward</mat-icon>\r\n <span class=\"step-preview-value\">{{ stepPreviews[i] || '(empty)' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Final Preview -->\r\n <div class=\"preview-section final-preview\">\r\n <span class=\"preview-label\">Final Result:</span>\r\n <div class=\"preview-value\">{{ finalPreview || '(empty)' }}</div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Add Step Button - Always visible -->\r\n <div class=\"add-step-section\">\r\n <button mat-stroked-button class=\"add-step-btn\" (click)=\"addStep()\">\r\n <mat-icon>add</mat-icon>\r\n @if (isMultiStep) {\r\n Add Step\r\n } @else {\r\n Add Transformation Step\r\n }\r\n </button>\r\n </div>\r\n\r\n <div class=\"popover-actions\">\r\n <button mat-button color=\"warn\" (click)=\"onDelete()\" matTooltip=\"Remove this mapping\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n <div class=\"action-spacer\"></div>\r\n <button mat-button (click)=\"onClose()\">Cancel</button>\r\n <button mat-flat-button color=\"primary\" (click)=\"onSave()\">Apply</button>\r\n </div>\r\n</div>\r\n\r\n<!-- Backdrop -->\r\n<div class=\"popover-backdrop\" (click)=\"onClose()\"></div>\r\n\r\n<!-- Step Configuration Template -->\r\n<ng-template #stepConfig let-step=\"step\" let-index=\"index\" let-showHeader=\"showHeader\">\r\n <!-- Transformation Type -->\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Transformation Type</mat-label>\r\n <mat-select [(ngModel)]=\"step.type\" (selectionChange)=\"onStepTypeChange(index)\">\r\n @for (t of availableTransformations; track t.type) {\r\n <mat-option [value]=\"t.type\">{{ t.label }}</mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <!-- Type-specific options -->\r\n @switch (step.type) {\r\n @case ('concat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Separator</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.separator\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\" \"\r\n />\r\n <mat-hint>Join values with this (default: space)</mat-hint>\r\n </mat-form-field>\r\n <div class=\"or-divider\">or use template for custom format</div>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n [placeholder]=\"'{0} - {1}'\"\r\n />\r\n <mat-hint>Overrides separator if set</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('substring') {\r\n <div class=\"config-section config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Start Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.startIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>End Index</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.endIndex\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('replace') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Search For</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.searchValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Replace With</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.replaceValue\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('dateFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Output Format</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.outputFormat\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"YYYY-MM-DD\"\r\n />\r\n <mat-hint>YYYY, MM, DD, HH, mm, ss</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('numberFormat') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Decimal Places</mat-label>\r\n <input\r\n matInput\r\n type=\"number\"\r\n [(ngModel)]=\"step.decimalPlaces\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n min=\"0\"\r\n />\r\n </mat-form-field>\r\n <div class=\"config-row\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Prefix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.prefix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"$\"\r\n />\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Suffix</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.suffix\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n />\r\n </mat-form-field>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('mask') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Pattern</mat-label>\r\n <input\r\n matInput\r\n [(ngModel)]=\"step.pattern\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n placeholder=\"(###) ###-####\"\r\n />\r\n <mat-hint># = character from input</mat-hint>\r\n </mat-form-field>\r\n </div>\r\n }\r\n\r\n @case ('template') {\r\n <div class=\"config-section\">\r\n <mat-form-field appearance=\"outline\" class=\"full-width\">\r\n <mat-label>Template Expression</mat-label>\r\n <textarea\r\n matInput\r\n [(ngModel)]=\"step.template\"\r\n (ngModelChange)=\"onConfigChange()\"\r\n rows=\"3\"\r\n [placeholder]=\"'Hello {0}, your ID is {1}'\"\r\n ></textarea>\r\n @if (index === 0) {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }}, {{ '{' }}1{{ '}' }}, etc. for source fields</mat-hint>\r\n } @else {\r\n <mat-hint>Use {{ '{' }}0{{ '}' }} for the value from previous step</mat-hint>\r\n }\r\n </mat-form-field>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Condition Section -->\r\n <div class=\"condition-section\">\r\n <mat-checkbox\r\n [checked]=\"hasCondition(step)\"\r\n (change)=\"toggleCondition(step, $event.checked)\"\r\n class=\"condition-checkbox\"\r\n >\r\n Apply conditionally\r\n </mat-checkbox>\r\n\r\n @if (hasCondition(step)) {\r\n <div class=\"condition-content\">\r\n <condition-builder\r\n [condition]=\"step.condition?.root || null\"\r\n [compact]=\"true\"\r\n (conditionChange)=\"onConditionChange(step, $event)\"\r\n ></condition-builder>\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";.popover-backdrop{position:fixed;inset:0;background:#0000004d;z-index:999}.transformation-popover{position:fixed;z-index:1000;width:420px;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:500px;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}.or-divider{text-align:center;font-size:11px;color:#94a3b8;margin:8px 0}.condition-section{margin-top:16px;padding-top:12px;border-top:1px dashed #e2e8f0}.condition-checkbox{font-size:13px;color:#64748b}.condition-content{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}.steps-container{display:flex;flex-direction:column;gap:12px}.step-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;transition:all .2s ease}.step-card.expanded{border-color:#6366f1;border-width:2px;background:#fff}.step-collapsed{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px;cursor:pointer;transition:background .15s ease}.step-collapsed:hover{background:#f1f5f9}.step-collapsed .drag-handle{grid-row:span 2;align-self:center}.step-collapsed-header{display:flex;align-items:center;flex-wrap:wrap;gap:4px}.step-collapsed-header .step-title-row{display:flex;align-items:center;gap:8px;flex:1}.step-collapsed-header .step-number{flex-shrink:0}.condition-badge{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;background:#fef3c7;border:1px solid #f59e0b;border-radius:4px;font-size:10px;font-weight:600;color:#b45309;cursor:help}.condition-badge mat-icon{font-size:12px;width:12px;height:12px}.step-collapsed-actions{display:flex;align-items:center;gap:4px}.step-collapsed-preview{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:12px;font-family:Monaco,Menlo,monospace}.step-input{color:#64748b;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.arrow-icon{font-size:14px;width:14px;height:14px;color:#6366f1}.step-output{color:#059669;font-weight:500;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.step-expanded{display:grid;grid-template-columns:auto 1fr;gap:0 12px;padding:12px}.step-expanded .drag-handle{grid-row:1;align-self:center}.step-expanded .step-header,.step-expanded .step-content,.step-expanded .step-preview{grid-column:2}.step-header{display:flex;align-items:center;margin-bottom:12px}.step-header .step-number{flex:1}.step-header-actions{display:flex;align-items:center;gap:4px}.step-number{font-size:12px;font-weight:600;color:#6366f1;text-transform:uppercase;letter-spacing:.5px}.expand-btn,.collapse-btn{width:28px;height:28px;line-height:28px}.expand-btn mat-icon,.collapse-btn mat-icon{font-size:18px;width:18px;height:18px;color:#64748b}.expand-btn:hover mat-icon,.collapse-btn:hover mat-icon{color:#6366f1}.remove-step-btn{width:28px;height:28px;line-height:28px}.remove-step-btn mat-icon{font-size:18px;width:18px;height:18px;color:#94a3b8}.remove-step-btn:hover mat-icon{color:#ef4444}.step-preview{display:flex;align-items:center;gap:8px;margin-top:12px;padding:8px 12px;background:#f1f5f9;border-radius:6px}.preview-arrow{font-size:16px;width:16px;height:16px;color:#6366f1}.step-preview-value{font-size:13px;color:#475569;font-family:Monaco,Menlo,monospace;word-break:break-all;flex:1}.add-step-section{padding:12px 20px;border-top:1px solid #e2e8f0;background:#fafafa}.add-step-btn{width:100%;border-style:dashed;color:#64748b}.add-step-btn mat-icon{font-size:18px;width:18px;height:18px;margin-right:4px}.add-step-btn:hover{border-color:#6366f1;color:#6366f1;background:#f5f3ff}.final-preview{margin-top:16px;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:2px solid #10b981;border-radius:8px;position:relative}.final-preview .preview-label{color:#059669;font-size:12px;font-weight:700}.final-preview .preview-value{color:#065f46;font-weight:600}.final-preview:before{content:\"\\2713\";position:absolute;right:12px;top:10px;color:#10b981;font-size:16px;font-weight:700}.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}.drag-handle{cursor:grab;color:#94a3b8;font-size:20px;width:20px;height:20px;flex-shrink:0}.drag-handle:hover{color:#6366f1}.drag-handle:active{cursor:grabbing}.step-card.cdk-drag-preview{box-shadow:0 5px 20px #00000040;border-radius:8px;background:#fff}.step-card.cdk-drag-placeholder{opacity:.4;background:#e2e8f0;border:2px dashed #94a3b8}.step-card.cdk-drag-animating{transition:transform .2s ease}.steps-container.cdk-drop-list-dragging .step-card:not(.cdk-drag-placeholder){transition:transform .2s ease}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i2$1.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.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.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i4.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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i8.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ConditionBuilderComponent, selector: "condition-builder", inputs: ["fields", "condition", "compact"], outputs: ["conditionChange"] }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }] });
1763
1888
  }
1764
1889
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationPopoverComponent, decorators: [{
1765
1890
  type: Component,
@@ -1989,7 +2114,7 @@ class ArrayFilterModalComponent {
1989
2114
  }
1990
2115
  }
1991
2116
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1992
- 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.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.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$1.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
2117
+ 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.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.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i4.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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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$1.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: i7.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" }] });
1993
2118
  }
1994
2119
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArrayFilterModalComponent, decorators: [{
1995
2120
  type: Component,
@@ -2193,7 +2318,7 @@ class ArraySelectorModalComponent {
2193
2318
  }
2194
2319
  }
2195
2320
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2196
- 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.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.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$1.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i9$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
2321
+ 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.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.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i4.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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.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$1.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: i7.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" }] });
2197
2322
  }
2198
2323
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ArraySelectorModalComponent, decorators: [{
2199
2324
  type: Component,
@@ -2232,7 +2357,7 @@ class DefaultValuePopoverComponent {
2232
2357
  dateValue = null;
2233
2358
  ngOnInit() {
2234
2359
  if (this.existingValue) {
2235
- switch (this.existingValue.valueType) {
2360
+ switch (this.fieldType) {
2236
2361
  case 'string':
2237
2362
  this.stringValue = this.existingValue.value || '';
2238
2363
  break;
@@ -2283,7 +2408,7 @@ class DefaultValuePopoverComponent {
2283
2408
  }
2284
2409
  }
2285
2410
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2286
- 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$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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.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$1.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"] }] });
2411
+ 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$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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.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.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.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$1.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"] }] });
2287
2412
  }
2288
2413
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultValuePopoverComponent, decorators: [{
2289
2414
  type: Component,
@@ -2324,6 +2449,12 @@ class DataMapperComponent {
2324
2449
  this._targetSchemaInput.set(value);
2325
2450
  }
2326
2451
  }
2452
+ set sourceSchemaRef(value) {
2453
+ this.mappingService.setSourceSchemaRef(value ?? null);
2454
+ }
2455
+ set targetSchemaRef(value) {
2456
+ this.mappingService.setTargetSchemaRef(value ?? null);
2457
+ }
2327
2458
  sampleData = {};
2328
2459
  mappingsChange = new EventEmitter();
2329
2460
  svgContainer;
@@ -2376,6 +2507,12 @@ class DataMapperComponent {
2376
2507
  dragSourceField = null;
2377
2508
  dragStartPoint = null;
2378
2509
  resizeObserver;
2510
+ // Endpoint drag state
2511
+ isEndpointDragging = false;
2512
+ endpointDragMappingId = null;
2513
+ endpointDragType = null;
2514
+ endpointDragSourceIndex = null;
2515
+ endpointDragAnchorPoint = null;
2379
2516
  ngAfterViewInit() {
2380
2517
  this.setupResizeObserver();
2381
2518
  }
@@ -2411,15 +2548,27 @@ class DataMapperComponent {
2411
2548
  document.body.style.cursor = 'grabbing';
2412
2549
  }
2413
2550
  onMouseMove(event) {
2414
- if (!this.isDragging || !this.dragStartPoint || !this.svgContainer?.nativeElement)
2551
+ if (!this.svgContainer?.nativeElement)
2415
2552
  return;
2416
2553
  const containerRect = this.svgContainer.nativeElement.getBoundingClientRect();
2417
2554
  const currentPoint = {
2418
2555
  x: event.clientX - containerRect.left,
2419
2556
  y: event.clientY - containerRect.top,
2420
2557
  };
2421
- const path = this.svgConnectorService.createDragPath(this.dragStartPoint, currentPoint);
2422
- this.dragPath.set(path);
2558
+ // Handle new connection dragging
2559
+ if (this.isDragging && this.dragStartPoint) {
2560
+ const path = this.svgConnectorService.createDragPath(this.dragStartPoint, currentPoint);
2561
+ this.dragPath.set(path);
2562
+ return;
2563
+ }
2564
+ // Handle endpoint dragging
2565
+ if (this.isEndpointDragging && this.endpointDragAnchorPoint) {
2566
+ // Show drag path from anchor to cursor
2567
+ const path = this.endpointDragType === 'source'
2568
+ ? this.svgConnectorService.createDragPath(currentPoint, this.endpointDragAnchorPoint)
2569
+ : this.svgConnectorService.createDragPath(this.endpointDragAnchorPoint, currentPoint);
2570
+ this.dragPath.set(path);
2571
+ }
2423
2572
  }
2424
2573
  onMouseUp(event) {
2425
2574
  if (this.isDragging) {
@@ -2429,8 +2578,53 @@ class DataMapperComponent {
2429
2578
  this.dragStartPoint = null;
2430
2579
  document.body.style.cursor = '';
2431
2580
  }
2581
+ if (this.isEndpointDragging) {
2582
+ this.cancelEndpointDrag();
2583
+ }
2584
+ }
2585
+ onEndpointDragStart(connection, endpointType, sourceIndex, event) {
2586
+ event.stopPropagation();
2587
+ event.preventDefault();
2588
+ if (!this.svgContainer?.nativeElement)
2589
+ return;
2590
+ this.isEndpointDragging = true;
2591
+ this.endpointDragMappingId = connection.mappingId;
2592
+ this.endpointDragType = endpointType;
2593
+ this.endpointDragSourceIndex = sourceIndex;
2594
+ // Set anchor point (the point that stays fixed)
2595
+ if (endpointType === 'source') {
2596
+ // Moving source, anchor is the target
2597
+ this.endpointDragAnchorPoint = connection.targetPoint;
2598
+ }
2599
+ else {
2600
+ // Moving target, anchor is the first source (or merge point for multi-source)
2601
+ this.endpointDragAnchorPoint = connection.sourcePoints[0];
2602
+ }
2603
+ // Update MappingService drag state so schema-tree can detect endpoint dragging
2604
+ this.mappingService.startEndpointDrag(connection.mappingId, endpointType, endpointType === 'source' ? connection.sourcePoints[sourceIndex] : connection.targetPoint, sourceIndex);
2605
+ document.body.style.cursor = 'grabbing';
2606
+ this.updateConnections(); // Update to show connection as being dragged
2607
+ }
2608
+ cancelEndpointDrag() {
2609
+ this.dragPath.set(null);
2610
+ this.isEndpointDragging = false;
2611
+ this.endpointDragMappingId = null;
2612
+ this.endpointDragType = null;
2613
+ this.endpointDragSourceIndex = null;
2614
+ this.endpointDragAnchorPoint = null;
2615
+ document.body.style.cursor = '';
2616
+ this.mappingService.endDrag(); // Reset MappingService drag state
2617
+ this.updateConnections(); // Update to restore connection visibility
2432
2618
  }
2433
2619
  onFieldDrop(event) {
2620
+ // Handle endpoint drag completion (drop on target field)
2621
+ if (this.isEndpointDragging && this.endpointDragMappingId && this.endpointDragType === 'target') {
2622
+ this.mappingService.changeTargetField(this.endpointDragMappingId, event.field);
2623
+ this.mappingsChange.emit(this.mappingService.allMappings());
2624
+ this.updateConnections();
2625
+ this.cancelEndpointDrag();
2626
+ return;
2627
+ }
2434
2628
  if (!this.dragSourceField)
2435
2629
  return;
2436
2630
  // Create mapping
@@ -2444,6 +2638,15 @@ class DataMapperComponent {
2444
2638
  this.dragPath.set(null);
2445
2639
  document.body.style.cursor = '';
2446
2640
  }
2641
+ onSourceFieldDrop(event) {
2642
+ // Handle endpoint drag completion (drop on source field)
2643
+ if (this.isEndpointDragging && this.endpointDragMappingId && this.endpointDragType === 'source') {
2644
+ this.mappingService.changeSourceField(this.endpointDragMappingId, event.field, this.endpointDragSourceIndex ?? undefined);
2645
+ this.mappingsChange.emit(this.mappingService.allMappings());
2646
+ this.updateConnections();
2647
+ this.cancelEndpointDrag();
2648
+ }
2649
+ }
2447
2650
  onConnectionClick(connection, event) {
2448
2651
  event.stopPropagation();
2449
2652
  this.selectedMappingId.set(connection.mappingId);
@@ -2602,6 +2805,7 @@ class DataMapperComponent {
2602
2805
  id: `conn-${mapping.id}`,
2603
2806
  mappingId: mapping.id,
2604
2807
  paths,
2808
+ sourcePoints,
2605
2809
  midPoint,
2606
2810
  targetPoint,
2607
2811
  hasTransformation: mapping.transformations.length > 1 || mapping.transformations[0]?.type !== 'direct',
@@ -2609,6 +2813,7 @@ class DataMapperComponent {
2609
2813
  isArrayMapping: mapping.isArrayMapping || false,
2610
2814
  isArrayToObjectMapping: mapping.isArrayToObjectMapping || false,
2611
2815
  hasFilter,
2816
+ isBeingDragged: mapping.id === this.endpointDragMappingId,
2612
2817
  });
2613
2818
  }
2614
2819
  this.connections.set(newConnections);
@@ -2677,7 +2882,7 @@ class DataMapperComponent {
2677
2882
  return connection.id;
2678
2883
  }
2679
2884
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2680
- 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]=\"sourceSchemaForTree()\"\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]=\"targetSchemaForTree()\"\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.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.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"] }] });
2885
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DataMapperComponent, isStandalone: true, selector: "data-mapper", inputs: { sourceSchema: "sourceSchema", targetSchema: "targetSchema", sourceSchemaRef: "sourceSchemaRef", targetSchemaRef: "targetSchemaRef", 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]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n (sourceDrop)=\"onSourceFieldDrop($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\" [class.being-dragged]=\"connection.isBeingDragged\">\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\n <!-- Source endpoint circles (draggable) -->\n @for (sourcePoint of connection.sourcePoints; track $index) {\n <circle\n class=\"endpoint-handle source-endpoint\"\n [attr.cx]=\"sourcePoint.x\"\n [attr.cy]=\"sourcePoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'source', $index, $event)\"\n />\n }\n\n <!-- Target endpoint circle (draggable) -->\n <circle\n class=\"endpoint-handle target-endpoint\"\n [attr.cx]=\"connection.targetPoint.x\"\n [attr.cy]=\"connection.targetPoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'target', 0, $event)\"\n />\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]=\"targetSchemaForTree()\"\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:3}.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-group.being-dragged{opacity:.3;pointer-events:none}.connection-group.being-dragged .endpoint-handle{opacity:0}.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}}.endpoint-handle{fill:#fff;stroke:#6366f1;stroke-width:2;cursor:grab;opacity:0;transition:opacity .15s ease,transform .15s ease,r .15s ease;pointer-events:auto}.endpoint-handle:hover{opacity:1;r:8;fill:#6366f1;cursor:grab}.endpoint-handle:active{cursor:grabbing}.endpoint-handle.selected,.connection-group:hover .endpoint-handle{opacity:.7}.connection-group.selected .endpoint-handle{opacity:1}.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.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.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", "sourceDrop", "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"] }] });
2681
2886
  }
2682
2887
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DataMapperComponent, decorators: [{
2683
2888
  type: Component,
@@ -2691,11 +2896,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2691
2896
  ArrayFilterModalComponent,
2692
2897
  ArraySelectorModalComponent,
2693
2898
  DefaultValuePopoverComponent,
2694
- ], 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]=\"sourceSchemaForTree()\"\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]=\"targetSchemaForTree()\"\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"] }]
2899
+ ], 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]=\"sourceSchemaForTree()\"\n [side]=\"'source'\"\n [mappings]=\"mappings()\"\n (fieldDragStart)=\"onFieldDragStart($event)\"\n (fieldPositionsChanged)=\"onSourcePositionsChanged($event)\"\n (sourceDrop)=\"onSourceFieldDrop($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\" [class.being-dragged]=\"connection.isBeingDragged\">\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\n <!-- Source endpoint circles (draggable) -->\n @for (sourcePoint of connection.sourcePoints; track $index) {\n <circle\n class=\"endpoint-handle source-endpoint\"\n [attr.cx]=\"sourcePoint.x\"\n [attr.cy]=\"sourcePoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'source', $index, $event)\"\n />\n }\n\n <!-- Target endpoint circle (draggable) -->\n <circle\n class=\"endpoint-handle target-endpoint\"\n [attr.cx]=\"connection.targetPoint.x\"\n [attr.cy]=\"connection.targetPoint.y\"\n r=\"6\"\n [class.selected]=\"connection.isSelected\"\n (mousedown)=\"onEndpointDragStart(connection, 'target', 0, $event)\"\n />\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]=\"targetSchemaForTree()\"\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:3}.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-group.being-dragged{opacity:.3;pointer-events:none}.connection-group.being-dragged .endpoint-handle{opacity:0}.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}}.endpoint-handle{fill:#fff;stroke:#6366f1;stroke-width:2;cursor:grab;opacity:0;transition:opacity .15s ease,transform .15s ease,r .15s ease;pointer-events:auto}.endpoint-handle:hover{opacity:1;r:8;fill:#6366f1;cursor:grab}.endpoint-handle:active{cursor:grabbing}.endpoint-handle.selected,.connection-group:hover .endpoint-handle{opacity:.7}.connection-group.selected .endpoint-handle{opacity:1}.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"] }]
2695
2900
  }], propDecorators: { sourceSchema: [{
2696
2901
  type: Input
2697
2902
  }], targetSchema: [{
2698
2903
  type: Input
2904
+ }], sourceSchemaRef: [{
2905
+ type: Input
2906
+ }], targetSchemaRef: [{
2907
+ type: Input
2699
2908
  }], sampleData: [{
2700
2909
  type: Input
2701
2910
  }], mappingsChange: [{
@@ -2717,14 +2926,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
2717
2926
  class SchemaEditorComponent {
2718
2927
  set schema(value) {
2719
2928
  if (value) {
2929
+ // Don't overwrite fields if we have uncommitted changes (fields being edited or with empty names)
2930
+ const hasUncommittedChanges = this.fields().some(f => f.isEditing || f.isEditingDefault || f.isEditingValues || !f.name) || this.hasUncommittedChildFields(this.fields());
2720
2931
  this.schemaName.set(value.title || 'New Schema');
2721
- this.fields.set(this.jsonSchemaToEditorFields(value));
2932
+ if (!hasUncommittedChanges) {
2933
+ this.fields.set(this.jsonSchemaToEditorFields(value));
2934
+ }
2722
2935
  }
2723
2936
  }
2937
+ hasUncommittedChildFields(fields) {
2938
+ for (const field of fields) {
2939
+ if (field.children) {
2940
+ if (field.children.some(c => c.isEditing || c.isEditingDefault || c.isEditingValues || !c.name)) {
2941
+ return true;
2942
+ }
2943
+ if (this.hasUncommittedChildFields(field.children)) {
2944
+ return true;
2945
+ }
2946
+ }
2947
+ }
2948
+ return false;
2949
+ }
2950
+ showJsonToggle = true;
2724
2951
  schemaChange = new EventEmitter();
2725
2952
  save = new EventEmitter();
2726
2953
  schemaName = signal('New Schema', ...(ngDevMode ? [{ debugName: "schemaName" }] : []));
2727
2954
  fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
2955
+ // View mode: 'visual' or 'json'
2956
+ viewMode = signal('visual', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
2957
+ jsonText = signal('', ...(ngDevMode ? [{ debugName: "jsonText" }] : []));
2958
+ jsonError = signal(null, ...(ngDevMode ? [{ debugName: "jsonError" }] : []));
2728
2959
  fieldTypes = [
2729
2960
  { value: 'string', label: 'String', icon: 'text_fields' },
2730
2961
  { value: 'number', label: 'Number', icon: 'pin' },
@@ -2755,7 +2986,8 @@ class SchemaEditorComponent {
2755
2986
  expanded: false,
2756
2987
  };
2757
2988
  this.fields.update(fields => [...fields, newField]);
2758
- this.emitChange();
2989
+ // Don't emit change here - field has empty name and would be filtered out
2990
+ // Change will be emitted in stopEdit() when user provides a name
2759
2991
  }
2760
2992
  // Add a child field to an object or array
2761
2993
  addChildField(parent) {
@@ -2771,7 +3003,8 @@ class SchemaEditorComponent {
2771
3003
  parent.children.push(newField);
2772
3004
  parent.expanded = true;
2773
3005
  this.fields.update(fields => [...fields]);
2774
- this.emitChange();
3006
+ // Don't emit change here - field has empty name and would be filtered out
3007
+ // Change will be emitted in stopEdit() when user provides a name
2775
3008
  }
2776
3009
  // Delete a field
2777
3010
  deleteField(field, parentList) {
@@ -2853,21 +3086,43 @@ class SchemaEditorComponent {
2853
3086
  }
2854
3087
  // Toggle allowed values editor
2855
3088
  toggleValuesEditor(field) {
3089
+ const wasEditingDefault = field.isEditingDefault;
2856
3090
  field.isEditingValues = !field.isEditingValues;
2857
- if (field.isEditingValues && !field.allowedValues) {
2858
- field.allowedValues = [];
3091
+ if (field.isEditingValues) {
3092
+ // Close default editor when opening values editor
3093
+ field.isEditingDefault = false;
3094
+ if (!field.allowedValues) {
3095
+ field.allowedValues = [];
3096
+ }
2859
3097
  }
2860
3098
  this.fields.update(fields => [...fields]);
3099
+ // Emit change if we closed the default editor (to save any default value)
3100
+ if (wasEditingDefault) {
3101
+ this.emitChange();
3102
+ }
2861
3103
  }
2862
3104
  // Add allowed value
2863
3105
  addAllowedValue(field, input) {
2864
- const value = input.value.trim();
3106
+ // Handle both direct input reference and event from button click
3107
+ let inputEl = null;
3108
+ if (input instanceof HTMLInputElement) {
3109
+ inputEl = input;
3110
+ }
3111
+ else {
3112
+ // Find the input by traversing up to .values-header and then querying for .value-input
3113
+ const target = input.target;
3114
+ const header = target.closest('.values-header');
3115
+ inputEl = header?.querySelector('.value-input');
3116
+ }
3117
+ if (!inputEl)
3118
+ return;
3119
+ const value = inputEl.value.trim();
2865
3120
  if (value && !field.allowedValues?.includes(value)) {
2866
3121
  if (!field.allowedValues) {
2867
3122
  field.allowedValues = [];
2868
3123
  }
2869
3124
  field.allowedValues.push(value);
2870
- input.value = '';
3125
+ inputEl.value = '';
2871
3126
  this.fields.update(fields => [...fields]);
2872
3127
  this.emitChange();
2873
3128
  }
@@ -2890,6 +3145,52 @@ class SchemaEditorComponent {
2890
3145
  this.addAllowedValue(field, input);
2891
3146
  }
2892
3147
  }
3148
+ // Toggle default value editor
3149
+ toggleDefaultEditor(field) {
3150
+ const wasEditingValues = field.isEditingValues;
3151
+ field.isEditingDefault = !field.isEditingDefault;
3152
+ if (field.isEditingDefault) {
3153
+ // Close values editor when opening default editor
3154
+ field.isEditingValues = false;
3155
+ }
3156
+ this.fields.update(fields => [...fields]);
3157
+ // Emit change if we closed the values editor (to save any allowed values)
3158
+ if (wasEditingValues) {
3159
+ this.emitChange();
3160
+ }
3161
+ }
3162
+ // Update default value
3163
+ onDefaultValueChange(field, value) {
3164
+ if (value === '') {
3165
+ field.defaultValue = undefined;
3166
+ }
3167
+ else if (field.type === 'number') {
3168
+ const num = parseFloat(value);
3169
+ field.defaultValue = isNaN(num) ? undefined : num;
3170
+ }
3171
+ else if (field.type === 'boolean') {
3172
+ field.defaultValue = value === 'true';
3173
+ }
3174
+ else {
3175
+ field.defaultValue = value;
3176
+ }
3177
+ this.fields.update(fields => [...fields]);
3178
+ this.emitChange();
3179
+ }
3180
+ // Clear default value
3181
+ clearDefaultValue(field) {
3182
+ field.defaultValue = undefined;
3183
+ field.isEditingDefault = false;
3184
+ this.fields.update(fields => [...fields]);
3185
+ this.emitChange();
3186
+ }
3187
+ // Handle Enter key in default value input
3188
+ onDefaultValueKeydown(event, field) {
3189
+ if (event.key === 'Enter' || event.key === 'Escape') {
3190
+ field.isEditingDefault = false;
3191
+ this.fields.update(fields => [...fields]);
3192
+ }
3193
+ }
2893
3194
  // Handle keyboard events in field name input
2894
3195
  onFieldNameKeydown(event, field) {
2895
3196
  if (event.key === 'Enter') {
@@ -2918,6 +3219,15 @@ class SchemaEditorComponent {
2918
3219
  this.emitChange();
2919
3220
  }
2920
3221
  }
3222
+ // Handle drag and drop reorder
3223
+ onFieldDrop(event) {
3224
+ if (event.previousIndex !== event.currentIndex) {
3225
+ moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
3226
+ this.fields.update(fields => [...fields]);
3227
+ // Don't emit change here - it causes parent to reset expanded state
3228
+ // Order change will be captured on next edit or save
3229
+ }
3230
+ }
2921
3231
  // Check if field can be indented (previous sibling must be object/array)
2922
3232
  canIndent(field, parentList) {
2923
3233
  const index = parentList.indexOf(field);
@@ -2941,9 +3251,11 @@ class SchemaEditorComponent {
2941
3251
  prevSibling.children = [];
2942
3252
  }
2943
3253
  prevSibling.children.push(field);
3254
+ // Always expand the target (keep open if already open, open if closed)
2944
3255
  prevSibling.expanded = true;
2945
3256
  this.fields.update(fields => [...fields]);
2946
- this.emitChange();
3257
+ // Don't emit change here - it causes parent to reset expanded state
3258
+ // Structure change will be captured on next edit or save
2947
3259
  }
2948
3260
  // Outdent field - move out of parent to grandparent level
2949
3261
  outdentField(field, parentList, level) {
@@ -2964,7 +3276,8 @@ class SchemaEditorComponent {
2964
3276
  const parentIndex = grandparentList.indexOf(parent);
2965
3277
  grandparentList.splice(parentIndex + 1, 0, field);
2966
3278
  this.fields.update(fields => [...fields]);
2967
- this.emitChange();
3279
+ // Don't emit change here - it causes parent to reset expanded state
3280
+ // Structure change will be captured on next edit or save
2968
3281
  }
2969
3282
  // Find the parent field that contains a given list
2970
3283
  findParentOfList(searchIn, targetList) {
@@ -3030,6 +3343,7 @@ class SchemaEditorComponent {
3030
3343
  description: schema.description,
3031
3344
  required: isRequired,
3032
3345
  allowedValues: schema.enum,
3346
+ defaultValue: schema.default,
3033
3347
  expanded: false,
3034
3348
  };
3035
3349
  if (schema.type === 'object' && schema.properties) {
@@ -3148,11 +3462,15 @@ class SchemaEditorComponent {
3148
3462
  if (field.allowedValues && field.allowedValues.length > 0) {
3149
3463
  schema['enum'] = field.allowedValues;
3150
3464
  }
3465
+ // Add default value
3466
+ if (field.defaultValue !== undefined) {
3467
+ schema['default'] = field.defaultValue;
3468
+ }
3151
3469
  return schema;
3152
3470
  }
3153
3471
  stripEditingState(fields) {
3154
3472
  return fields.map(f => {
3155
- const { isEditing, isEditingValues, ...rest } = f;
3473
+ const { isEditing, isEditingValues, isEditingDefault, ...rest } = f;
3156
3474
  return {
3157
3475
  ...rest,
3158
3476
  children: f.children ? this.stripEditingState(f.children) : undefined,
@@ -3163,8 +3481,56 @@ class SchemaEditorComponent {
3163
3481
  trackByFieldId(index, field) {
3164
3482
  return field.id;
3165
3483
  }
3484
+ // --- JSON View Methods ---
3485
+ setViewMode(mode) {
3486
+ if (mode === 'json') {
3487
+ // Sync JSON text from current schema state
3488
+ this.jsonText.set(JSON.stringify(this.toJsonSchema(), null, 2));
3489
+ this.jsonError.set(null);
3490
+ }
3491
+ this.viewMode.set(mode);
3492
+ }
3493
+ onJsonTextChange(text) {
3494
+ this.jsonText.set(text);
3495
+ try {
3496
+ JSON.parse(text);
3497
+ this.jsonError.set(null);
3498
+ }
3499
+ catch (e) {
3500
+ this.jsonError.set(e.message);
3501
+ }
3502
+ }
3503
+ applyJsonChanges() {
3504
+ try {
3505
+ const parsed = JSON.parse(this.jsonText());
3506
+ // Update internal state from JSON
3507
+ this.schemaName.set(parsed.title || 'New Schema');
3508
+ this.fields.set(this.jsonSchemaToEditorFields(parsed));
3509
+ this.jsonError.set(null);
3510
+ this.emitChange();
3511
+ }
3512
+ catch (e) {
3513
+ this.jsonError.set(e.message);
3514
+ }
3515
+ }
3516
+ formatJson() {
3517
+ try {
3518
+ const parsed = JSON.parse(this.jsonText());
3519
+ this.jsonText.set(JSON.stringify(parsed, null, 2));
3520
+ this.jsonError.set(null);
3521
+ }
3522
+ catch (e) {
3523
+ this.jsonError.set(e.message);
3524
+ }
3525
+ }
3526
+ copyJson() {
3527
+ const text = this.jsonText();
3528
+ navigator.clipboard.writeText(text).catch(err => {
3529
+ console.error('Failed to copy JSON to clipboard:', err);
3530
+ });
3531
+ }
3166
3532
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3167
- 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.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.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: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7$2.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$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7$2.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 }] });
3533
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaEditorComponent, isStandalone: true, selector: "schema-editor", inputs: { schema: "schema", showJsonToggle: "showJsonToggle" }, 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\n @if (showJsonToggle) {\n <mat-button-toggle-group [value]=\"viewMode()\" (change)=\"setViewMode($event.value)\" class=\"view-toggle\">\n <mat-button-toggle value=\"visual\">Visual</mat-button-toggle>\n <mat-button-toggle value=\"json\">JSON</mat-button-toggle>\n </mat-button-toggle-group>\n }\n\n <div class=\"header-actions\">\n @if (viewMode() === 'visual') {\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n } @else {\n <button mat-stroked-button (click)=\"copyJson()\" matTooltip=\"Copy JSON to clipboard\">\n <mat-icon>content_copy</mat-icon>\n Copy\n </button>\n <button mat-stroked-button (click)=\"formatJson()\">\n <mat-icon>auto_fix_high</mat-icon>\n Format\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"applyJsonChanges()\" [disabled]=\"jsonError()\">\n <mat-icon>check</mat-icon>\n Apply\n </button>\n }\n </div>\n </div>\n\n <!-- JSON View -->\n @if (viewMode() === 'json') {\n <div class=\"json-view\">\n @if (jsonError()) {\n <div class=\"json-error\">\n <mat-icon>error</mat-icon>\n {{ jsonError() }}\n </div>\n }\n <textarea\n class=\"json-textarea\"\n [value]=\"jsonText()\"\n (input)=\"onJsonTextChange($any($event.target).value)\"\n spellcheck=\"false\"\n ></textarea>\n </div>\n }\n\n <!-- Fields List (Visual View) -->\n <div class=\"fields-container\" [class.hidden]=\"viewMode() === 'json'\">\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\n class=\"fields-list\"\n cdkDropList\n [cdkDropListData]=\"fields()\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @for (field of fields(); track field.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, fields())\"\n (click)=\"indentField(field, fields())\"\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\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 </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\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>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</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, fields())\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, fields())\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\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\">\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, $event)\" 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 <!-- Default Value Editor -->\n @if (field.isEditingDefault && field.type !== 'object' && field.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (field.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"field.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(field, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"field.type === 'number' ? 'number' : 'text'\"\n [value]=\"field.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(field, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, field)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(field)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded && field.children) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n <ng-container *ngTemplateOutlet=\"fieldItemTemplate; context: { field: child, parentList: field.children, level: 1 }\"></ng-container>\n\n <!-- Allowed Values Editor for nested field -->\n @if (child.isEditingValues && (child.type === 'string' || child.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #childValueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, child, childValueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(child, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (child.allowedValues && child.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of child.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(child, 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 <!-- Default Value Editor for nested field -->\n @if (child.isEditingDefault && child.type !== 'object' && child.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (child.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"child.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(child, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"child.type === 'number' ? 'number' : 'text'\"\n [value]=\"child.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(child, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, child)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (child.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(child)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\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 </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Field Item Template (for nested fields) -->\n<ng-template #fieldItemTemplate let-field=\"field\" let-parentList=\"parentList\" let-level=\"level\">\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\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 (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 @if (field.type === 'object' || field.type === 'array') {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field)\" [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\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 <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required' : 'Optional'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\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 <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button mat-icon-button (click)=\"addChildField(field)\" matTooltip=\"Add child field\">\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' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"nestedMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #nestedMenu=\"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 </div>\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-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 .schema-name-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px;padding-bottom:8px;min-height:36px}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__leading,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__trailing{border-color:var(--schema-editor-border-color)!important}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch{border-right:none}.editor-header .schema-name-field ::ng-deep input.mat-mdc-input-element{font-size:14px}.editor-header .schema-name-field ::ng-deep .mat-mdc-floating-label{font-size:13px}.editor-header .header-actions{display:flex;gap:12px}.editor-header .header-actions button{display:flex;align-items:center;gap:6px}.editor-header .view-toggle{border:1px solid var(--schema-editor-border-color);border-radius:8px;overflow:hidden}.editor-header .view-toggle ::ng-deep .mat-button-toggle{background:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle.mat-button-toggle-checked{background:var(--schema-editor-accent-primary);color:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle .mat-button-toggle-label-content{padding:0 16px;font-size:13px;font-weight:500;line-height:32px}.json-view{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.json-error{display:flex;align-items:center;gap:8px;padding:10px 16px;background:#fef2f2;border-bottom:1px solid #fecaca;color:#dc2626;font-size:12px;flex-shrink:0}.json-error mat-icon{font-size:16px;width:16px;height:16px}.json-textarea{flex:1;width:100%;padding:16px;border:none;outline:none;resize:none;font-family:SF Mono,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:13px;line-height:1.6;color:var(--schema-editor-text-primary);background:var(--schema-editor-bg);tab-size:2;box-sizing:border-box}.json-textarea:focus{outline:none}.hidden{display:none!important}.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:block;width:100%;min-height:50px}.fields-list .field-wrapper{margin-bottom:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.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-left{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0}.field-right{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0;margin-left:auto}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:grab;color:#94a3b8;padding:4px;border-radius:4px;transition:all .15s ease}.drag-handle:hover{background:#e2e8f0;color:#64748b}.drag-handle:active{cursor:grabbing}.drag-handle mat-icon{font-size:20px;width:20px;height:20px}.field-wrapper{display:block;width:100%;box-sizing:border-box}::ng-deep .drag-preview{display:flex!important;align-items:center!important;gap:8px!important;padding:12px 16px!important;background:#fff!important;border:2px solid #3b82f6!important;border-radius:8px!important;box-shadow:0 8px 24px #0003!important;font-size:14px!important;font-weight:500!important;color:#1e293b!important;overflow:hidden!important;white-space:nowrap!important;box-sizing:border-box!important}::ng-deep .drag-preview mat-icon{color:#3b82f6!important;font-size:20px!important;width:20px!important;height:20px!important;flex-shrink:0!important;overflow:hidden!important}.drag-placeholder{background:#e2e8f0;border:2px dashed var(--schema-editor-accent-primary);border-radius:var(--schema-editor-field-border-radius);min-height:48px;margin-bottom:4px}.drag-placeholder .placeholder-content{height:100%;min-height:48px}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging .field-wrapper:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.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:80px;max-width:100%;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;justify-content:flex-end;gap:4px;width:128px;flex-shrink:0;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right: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{display:block;min-height:30px;margin-top:4px;margin-bottom:8px;padding-left:32px}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor{margin-left:16px}.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: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}.field-actions .has-default{color:#8b5cf6!important}.default-value-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px}.default-value-editor .default-header{display:flex;align-items:center;gap:8px}.default-value-editor .default-label{font-size:12px;font-weight:500;color:#6d28d9}.default-value-editor .default-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;max-width:200px}.default-value-editor .default-input:focus{border-color:#8b5cf6}.default-value-editor .default-input::placeholder{color:#94a3b8}.default-value-editor .default-select{padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;cursor:pointer}.default-value-editor .default-select:focus{border-color:#8b5cf6}.default-value-editor .clear-default-btn{background:none;border:none;padding:4px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.default-value-editor .clear-default-btn:hover{color:#ef4444}.default-value-editor .clear-default-btn mat-icon{font-size:16px;width:16px;height:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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.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.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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i8$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: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$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: MatButtonToggleModule }, { kind: "directive", type: i9$1.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i9$1.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i10.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i10.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }] });
3168
3534
  }
3169
3535
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, decorators: [{
3170
3536
  type: Component,
@@ -3178,10 +3544,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
3178
3544
  MatSelectModule,
3179
3545
  MatTooltipModule,
3180
3546
  MatMenuModule,
3547
+ MatButtonToggleModule,
3181
3548
  DragDropModule,
3182
- ], 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"] }]
3549
+ ], 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\n @if (showJsonToggle) {\n <mat-button-toggle-group [value]=\"viewMode()\" (change)=\"setViewMode($event.value)\" class=\"view-toggle\">\n <mat-button-toggle value=\"visual\">Visual</mat-button-toggle>\n <mat-button-toggle value=\"json\">JSON</mat-button-toggle>\n </mat-button-toggle-group>\n }\n\n <div class=\"header-actions\">\n @if (viewMode() === 'visual') {\n <button mat-flat-button color=\"primary\" (click)=\"addField()\">\n <mat-icon>add</mat-icon>\n Add Field\n </button>\n } @else {\n <button mat-stroked-button (click)=\"copyJson()\" matTooltip=\"Copy JSON to clipboard\">\n <mat-icon>content_copy</mat-icon>\n Copy\n </button>\n <button mat-stroked-button (click)=\"formatJson()\">\n <mat-icon>auto_fix_high</mat-icon>\n Format\n </button>\n <button mat-flat-button color=\"primary\" (click)=\"applyJsonChanges()\" [disabled]=\"jsonError()\">\n <mat-icon>check</mat-icon>\n Apply\n </button>\n }\n </div>\n </div>\n\n <!-- JSON View -->\n @if (viewMode() === 'json') {\n <div class=\"json-view\">\n @if (jsonError()) {\n <div class=\"json-error\">\n <mat-icon>error</mat-icon>\n {{ jsonError() }}\n </div>\n }\n <textarea\n class=\"json-textarea\"\n [value]=\"jsonText()\"\n (input)=\"onJsonTextChange($any($event.target).value)\"\n spellcheck=\"false\"\n ></textarea>\n </div>\n }\n\n <!-- Fields List (Visual View) -->\n <div class=\"fields-container\" [class.hidden]=\"viewMode() === 'json'\">\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\n class=\"fields-list\"\n cdkDropList\n [cdkDropListData]=\"fields()\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @for (field of fields(); track field.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Buttons -->\n <div class=\"indent-buttons\">\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent(field, fields())\"\n (click)=\"indentField(field, fields())\"\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\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 </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\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>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</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, fields())\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField(field, fields())\" class=\"delete-action\">\n <mat-icon>delete</mat-icon>\n <span>Delete</span>\n </button>\n </mat-menu>\n </div>\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\">\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, $event)\" 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 <!-- Default Value Editor -->\n @if (field.isEditingDefault && field.type !== 'object' && field.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (field.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"field.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(field, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"field.type === 'number' ? 'number' : 'text'\"\n [value]=\"field.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(field, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, field)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(field)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && field.expanded && field.children) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <div class=\"field-wrapper\" cdkDrag>\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n <ng-container *ngTemplateOutlet=\"fieldItemTemplate; context: { field: child, parentList: field.children, level: 1 }\"></ng-container>\n\n <!-- Allowed Values Editor for nested field -->\n @if (child.isEditingValues && (child.type === 'string' || child.type === 'number')) {\n <div class=\"allowed-values-editor\">\n <div class=\"values-header\">\n <span class=\"values-label\">Allowed values:</span>\n <input\n #childValueInput\n class=\"value-input\"\n type=\"text\"\n placeholder=\"Type value and press Enter\"\n (keydown)=\"onAllowedValueKeydown($event, child, childValueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue(child, $event)\" matTooltip=\"Add value\">\n <mat-icon>add</mat-icon>\n </button>\n </div>\n @if (child.allowedValues && child.allowedValues.length > 0) {\n <div class=\"values-list\">\n @for (value of child.allowedValues; track value; let vi = $index) {\n <span class=\"value-chip\">\n {{ value }}\n <button class=\"remove-value-btn\" (click)=\"removeAllowedValue(child, 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 <!-- Default Value Editor for nested field -->\n @if (child.isEditingDefault && child.type !== 'object' && child.type !== 'array') {\n <div class=\"default-value-editor\">\n <div class=\"default-header\">\n <span class=\"default-label\">Default value:</span>\n @if (child.type === 'boolean') {\n <select\n class=\"default-select\"\n [value]=\"child.defaultValue?.toString() || ''\"\n (change)=\"onDefaultValueChange(child, $any($event.target).value)\"\n >\n <option value=\"\">No default</option>\n <option value=\"true\">true</option>\n <option value=\"false\">false</option>\n </select>\n } @else {\n <input\n class=\"default-input\"\n [type]=\"child.type === 'number' ? 'number' : 'text'\"\n [value]=\"child.defaultValue ?? ''\"\n (input)=\"onDefaultValueChange(child, $any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event, child)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (child.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue(child)\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\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 </div>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Field Item Template (for nested fields) -->\n<ng-template #fieldItemTemplate let-field=\"field\" let-parentList=\"parentList\" let-level=\"level\">\n <div\n class=\"field-item\"\n [class.is-editing]=\"field.isEditing\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\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 (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 @if (field.type === 'object' || field.type === 'array') {\n <button class=\"expand-btn\" (click)=\"toggleExpand(field)\" [matTooltip]=\"field.expanded ? 'Collapse' : 'Expand'\">\n <mat-icon>{{ field.expanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n } @else {\n <span class=\"expand-placeholder\"></span>\n }\n\n <mat-icon class=\"type-icon\" [matTooltip]=\"field.type\">{{ getTypeIcon(field.type) }}</mat-icon>\n\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 <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired(field)\"\n [matTooltip]=\"field.required ? 'Required' : 'Optional'\"\n >\n <mat-icon>{{ field.required ? 'star' : 'star_border' }}</mat-icon>\n </button>\n </div>\n\n <!-- Middle section: description (flexible) -->\n <input\n class=\"description-input\"\n [value]=\"field.description || ''\"\n (input)=\"onDescriptionChange(field, $any($event.target).value)\"\n placeholder=\"Description...\"\n />\n\n <!-- Right section: type and actions (fixed position) -->\n <div class=\"field-right\">\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 <div class=\"field-actions\">\n @if (field.type === 'object' || field.type === 'array') {\n <button mat-icon-button (click)=\"addChildField(field)\" matTooltip=\"Add child field\">\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' : 'Add allowed values'\"\n [class.has-values]=\"field.allowedValues?.length\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n @if (field.type !== 'object' && field.type !== 'array') {\n <button\n mat-icon-button\n (click)=\"toggleDefaultEditor(field)\"\n [matTooltip]=\"field.defaultValue !== undefined ? 'Default: ' + field.defaultValue : 'Set default value'\"\n [class.has-default]=\"field.defaultValue !== undefined\"\n >\n <mat-icon>{{ field.defaultValue !== undefined ? 'label' : 'label_outline' }}</mat-icon>\n </button>\n }\n <button mat-icon-button [matMenuTriggerFor]=\"nestedMenu\" matTooltip=\"More options\">\n <mat-icon>more_vert</mat-icon>\n </button>\n <mat-menu #nestedMenu=\"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 </div>\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-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 .schema-name-field ::ng-deep .mat-mdc-text-field-wrapper{padding:0 12px}.editor-header .schema-name-field ::ng-deep .mat-mdc-form-field-infix{padding-top:8px;padding-bottom:8px;min-height:36px}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__leading,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch,.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__trailing{border-color:var(--schema-editor-border-color)!important}.editor-header .schema-name-field ::ng-deep .mdc-notched-outline__notch{border-right:none}.editor-header .schema-name-field ::ng-deep input.mat-mdc-input-element{font-size:14px}.editor-header .schema-name-field ::ng-deep .mat-mdc-floating-label{font-size:13px}.editor-header .header-actions{display:flex;gap:12px}.editor-header .header-actions button{display:flex;align-items:center;gap:6px}.editor-header .view-toggle{border:1px solid var(--schema-editor-border-color);border-radius:8px;overflow:hidden}.editor-header .view-toggle ::ng-deep .mat-button-toggle{background:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle.mat-button-toggle-checked{background:var(--schema-editor-accent-primary);color:#fff}.editor-header .view-toggle ::ng-deep .mat-button-toggle .mat-button-toggle-label-content{padding:0 16px;font-size:13px;font-weight:500;line-height:32px}.json-view{flex:1;display:flex;flex-direction:column;min-height:0;overflow:hidden}.json-error{display:flex;align-items:center;gap:8px;padding:10px 16px;background:#fef2f2;border-bottom:1px solid #fecaca;color:#dc2626;font-size:12px;flex-shrink:0}.json-error mat-icon{font-size:16px;width:16px;height:16px}.json-textarea{flex:1;width:100%;padding:16px;border:none;outline:none;resize:none;font-family:SF Mono,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:13px;line-height:1.6;color:var(--schema-editor-text-primary);background:var(--schema-editor-bg);tab-size:2;box-sizing:border-box}.json-textarea:focus{outline:none}.hidden{display:none!important}.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:block;width:100%;min-height:50px}.fields-list .field-wrapper{margin-bottom:4px}.field-item{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);padding:var(--schema-editor-spacing-sm) 12px;background:var(--schema-editor-field-bg);border-radius:var(--schema-editor-field-border-radius);border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.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-left{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0}.field-right{display:flex;align-items:center;gap:var(--schema-editor-spacing-sm);flex-shrink:0;margin-left:auto}.drag-handle{display:flex;align-items:center;justify-content:center;cursor:grab;color:#94a3b8;padding:4px;border-radius:4px;transition:all .15s ease}.drag-handle:hover{background:#e2e8f0;color:#64748b}.drag-handle:active{cursor:grabbing}.drag-handle mat-icon{font-size:20px;width:20px;height:20px}.field-wrapper{display:block;width:100%;box-sizing:border-box}::ng-deep .drag-preview{display:flex!important;align-items:center!important;gap:8px!important;padding:12px 16px!important;background:#fff!important;border:2px solid #3b82f6!important;border-radius:8px!important;box-shadow:0 8px 24px #0003!important;font-size:14px!important;font-weight:500!important;color:#1e293b!important;overflow:hidden!important;white-space:nowrap!important;box-sizing:border-box!important}::ng-deep .drag-preview mat-icon{color:#3b82f6!important;font-size:20px!important;width:20px!important;height:20px!important;flex-shrink:0!important;overflow:hidden!important}.drag-placeholder{background:#e2e8f0;border:2px dashed var(--schema-editor-accent-primary);border-radius:var(--schema-editor-field-border-radius);min-height:48px;margin-bottom:4px}.drag-placeholder .placeholder-content{height:100%;min-height:48px}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging .field-wrapper:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.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:80px;max-width:100%;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;justify-content:flex-end;gap:4px;width:128px;flex-shrink:0;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right: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{display:block;min-height:30px;margin-top:4px;margin-bottom:8px;padding-left:32px}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor{margin-left:16px}.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: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}.field-actions .has-default{color:#8b5cf6!important}.default-value-editor{margin-left:48px;margin-top:4px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px}.default-value-editor .default-header{display:flex;align-items:center;gap:8px}.default-value-editor .default-label{font-size:12px;font-weight:500;color:#6d28d9}.default-value-editor .default-input{flex:1;padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;max-width:200px}.default-value-editor .default-input:focus{border-color:#8b5cf6}.default-value-editor .default-input::placeholder{color:#94a3b8}.default-value-editor .default-select{padding:6px 10px;font-size:13px;border:1px solid #a78bfa;border-radius:4px;outline:none;background:#fff;cursor:pointer}.default-value-editor .default-select:focus{border-color:#8b5cf6}.default-value-editor .clear-default-btn{background:none;border:none;padding:4px;cursor:pointer;color:#94a3b8;display:flex;align-items:center;transition:color .15s ease}.default-value-editor .clear-default-btn:hover{color:#ef4444}.default-value-editor .clear-default-btn mat-icon{font-size:16px;width:16px;height:16px}\n"] }]
3183
3550
  }], propDecorators: { schema: [{
3184
3551
  type: Input
3552
+ }], showJsonToggle: [{
3553
+ type: Input
3185
3554
  }], schemaChange: [{
3186
3555
  type: Output
3187
3556
  }], save: [{