@expeed/ngx-data-mapper 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +284 -0
- package/fesm2022/expeed-ngx-data-mapper.mjs +180 -1622
- package/fesm2022/expeed-ngx-data-mapper.mjs.map +1 -1
- package/package.json +2 -3
- package/types/expeed-ngx-data-mapper.d.ts +40 -619
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, EventEmitter, inject, ViewChildren, ViewChild, Output, Input, Component, HostListener
|
|
2
|
+
import { signal, computed, Injectable, EventEmitter, inject, ViewChildren, ViewChild, Output, Input, Component, HostListener } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import * as i3 from '@angular/material/icon';
|
|
@@ -8,7 +8,7 @@ import * as i2 from '@angular/material/button';
|
|
|
8
8
|
import { MatButtonModule } from '@angular/material/button';
|
|
9
9
|
import * as i7 from '@angular/material/tooltip';
|
|
10
10
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
11
|
-
import * as
|
|
11
|
+
import * as i2$1 from '@angular/forms';
|
|
12
12
|
import { FormsModule } from '@angular/forms';
|
|
13
13
|
import * as i4 from '@angular/material/select';
|
|
14
14
|
import { MatSelectModule } from '@angular/material/select';
|
|
@@ -17,109 +17,17 @@ 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
|
|
20
|
+
import * as i9 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 from '@angular/material/divider';
|
|
26
|
+
import * as i9$1 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 i6$2 from '@angular/material/menu';
|
|
32
|
-
import { MatMenuModule } from '@angular/material/menu';
|
|
33
|
-
import * as i6$3 from '@angular/material/button-toggle';
|
|
34
|
-
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Standard JSON Schema (draft-07) TypeScript interfaces
|
|
38
|
-
*/
|
|
39
|
-
/**
|
|
40
|
-
* Convert JSON Schema to flat field list for UI rendering
|
|
41
|
-
*/
|
|
42
|
-
function schemaToFields(schema, parentPath = '') {
|
|
43
|
-
const fields = [];
|
|
44
|
-
if (schema.type === 'object' && schema.properties) {
|
|
45
|
-
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
46
|
-
const path = parentPath ? `${parentPath}.${name}` : name;
|
|
47
|
-
const field = {
|
|
48
|
-
name,
|
|
49
|
-
path,
|
|
50
|
-
schema: propSchema,
|
|
51
|
-
expanded: false,
|
|
52
|
-
};
|
|
53
|
-
if (propSchema.type === 'object' && propSchema.properties) {
|
|
54
|
-
field.children = schemaToFields(propSchema, path);
|
|
55
|
-
}
|
|
56
|
-
else if (propSchema.type === 'array' && propSchema.items) {
|
|
57
|
-
field.children = schemaToFields(propSchema.items, `${path}[]`);
|
|
58
|
-
}
|
|
59
|
-
fields.push(field);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return fields;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Get the simple type for display purposes
|
|
66
|
-
*/
|
|
67
|
-
function getSchemaType(schema) {
|
|
68
|
-
if (Array.isArray(schema.type)) {
|
|
69
|
-
return schema.type.filter(t => t !== 'null').join(' | ');
|
|
70
|
-
}
|
|
71
|
-
if (schema.type === 'integer') {
|
|
72
|
-
return 'number';
|
|
73
|
-
}
|
|
74
|
-
if (schema.format === 'date' || schema.format === 'date-time') {
|
|
75
|
-
return 'date';
|
|
76
|
-
}
|
|
77
|
-
return schema.type || 'any';
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Create an empty JSON Schema for a new schema definition
|
|
81
|
-
*/
|
|
82
|
-
function createEmptySchema(title = 'New Schema') {
|
|
83
|
-
return {
|
|
84
|
-
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
85
|
-
title,
|
|
86
|
-
type: 'object',
|
|
87
|
-
properties: {},
|
|
88
|
-
required: [],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Add a property to a schema
|
|
93
|
-
*/
|
|
94
|
-
function addProperty(schema, name, type, options) {
|
|
95
|
-
const newSchema = { ...schema };
|
|
96
|
-
newSchema.properties = { ...newSchema.properties };
|
|
97
|
-
const propSchema = { type };
|
|
98
|
-
if (options?.description) {
|
|
99
|
-
propSchema.description = options.description;
|
|
100
|
-
}
|
|
101
|
-
if (type === 'object') {
|
|
102
|
-
propSchema.properties = {};
|
|
103
|
-
}
|
|
104
|
-
else if (type === 'array') {
|
|
105
|
-
propSchema.items = { type: 'string' };
|
|
106
|
-
}
|
|
107
|
-
newSchema.properties[name] = propSchema;
|
|
108
|
-
if (options?.required) {
|
|
109
|
-
newSchema.required = [...(newSchema.required || []), name];
|
|
110
|
-
}
|
|
111
|
-
return newSchema;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Remove a property from a schema
|
|
115
|
-
*/
|
|
116
|
-
function removeProperty(schema, name) {
|
|
117
|
-
const newSchema = { ...schema };
|
|
118
|
-
newSchema.properties = { ...newSchema.properties };
|
|
119
|
-
delete newSchema.properties[name];
|
|
120
|
-
newSchema.required = (newSchema.required || []).filter(r => r !== name);
|
|
121
|
-
return newSchema;
|
|
122
|
-
}
|
|
123
31
|
|
|
124
32
|
class MappingService {
|
|
125
33
|
mappings = signal([], ...(ngDevMode ? [{ debugName: "mappings" }] : []));
|
|
@@ -150,6 +58,14 @@ class MappingService {
|
|
|
150
58
|
generateId() {
|
|
151
59
|
return `mapping-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
152
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Strip UI-only properties from a field node for storage.
|
|
63
|
+
* Removes: children, expanded (not needed in mapping output)
|
|
64
|
+
*/
|
|
65
|
+
stripFieldForStorage(field) {
|
|
66
|
+
const { children, expanded, ...rest } = field;
|
|
67
|
+
return rest;
|
|
68
|
+
}
|
|
153
69
|
startDrag(field, startPoint) {
|
|
154
70
|
this.dragState.set({
|
|
155
71
|
isDragging: true,
|
|
@@ -203,15 +119,16 @@ class MappingService {
|
|
|
203
119
|
// Don't allow if source already exists in the mapping
|
|
204
120
|
if (mapping.sourceFields.some(sf => sf.id === newSourceField.id))
|
|
205
121
|
return;
|
|
122
|
+
const strippedField = this.stripFieldForStorage(newSourceField);
|
|
206
123
|
if (sourceFieldIndex !== undefined && mapping.sourceFields.length > 1) {
|
|
207
124
|
// Replace specific source field in multi-source mapping
|
|
208
125
|
const newSourceFields = [...mapping.sourceFields];
|
|
209
|
-
newSourceFields[sourceFieldIndex] =
|
|
126
|
+
newSourceFields[sourceFieldIndex] = strippedField;
|
|
210
127
|
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: newSourceFields } : m));
|
|
211
128
|
}
|
|
212
129
|
else {
|
|
213
130
|
// Single source mapping - replace the source field
|
|
214
|
-
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: [
|
|
131
|
+
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, sourceFields: [strippedField] } : m));
|
|
215
132
|
}
|
|
216
133
|
}
|
|
217
134
|
changeTargetField(mappingId, newTargetField) {
|
|
@@ -243,7 +160,7 @@ class MappingService {
|
|
|
243
160
|
}
|
|
244
161
|
else {
|
|
245
162
|
// Simply update the target field
|
|
246
|
-
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, targetField: newTargetField } : m));
|
|
163
|
+
this.mappings.update(mappings => mappings.map(m => m.id === mappingId ? { ...m, targetField: this.stripFieldForStorage(newTargetField) } : m));
|
|
247
164
|
}
|
|
248
165
|
}
|
|
249
166
|
createMapping(sourceFields, targetField, transformation) {
|
|
@@ -263,11 +180,14 @@ class MappingService {
|
|
|
263
180
|
const existingMapping = this.mappings().find((m) => m.targetField.id === targetField.id);
|
|
264
181
|
if (existingMapping) {
|
|
265
182
|
// Add source fields to existing mapping (for concat scenarios)
|
|
183
|
+
const newSources = sourceFields
|
|
184
|
+
.filter((sf) => !existingMapping.sourceFields.some((esf) => esf.id === sf.id))
|
|
185
|
+
.map(f => this.stripFieldForStorage(f));
|
|
266
186
|
const updatedMapping = {
|
|
267
187
|
...existingMapping,
|
|
268
188
|
sourceFields: [
|
|
269
189
|
...existingMapping.sourceFields,
|
|
270
|
-
...
|
|
190
|
+
...newSources,
|
|
271
191
|
],
|
|
272
192
|
transformations: existingMapping.sourceFields.length + sourceFields.length > 1
|
|
273
193
|
? [{ type: 'concat', separator: ' ', ...transformation }]
|
|
@@ -278,8 +198,8 @@ class MappingService {
|
|
|
278
198
|
}
|
|
279
199
|
const newMapping = {
|
|
280
200
|
id: this.generateId(),
|
|
281
|
-
sourceFields,
|
|
282
|
-
targetField,
|
|
201
|
+
sourceFields: sourceFields.map(f => this.stripFieldForStorage(f)),
|
|
202
|
+
targetField: this.stripFieldForStorage(targetField),
|
|
283
203
|
transformations: transformation ? [transformation] : [{ type: 'direct' }],
|
|
284
204
|
isArrayMapping: false, // Only true for array-to-array connections
|
|
285
205
|
arrayMappingId, // Links to parent array mapping if within array context
|
|
@@ -320,8 +240,8 @@ class MappingService {
|
|
|
320
240
|
// Also create a field mapping to visualize the array connection
|
|
321
241
|
const fieldMapping = {
|
|
322
242
|
id: arrayMapping.id,
|
|
323
|
-
sourceFields: [sourceArray],
|
|
324
|
-
targetField: targetArray,
|
|
243
|
+
sourceFields: [this.stripFieldForStorage(sourceArray)],
|
|
244
|
+
targetField: this.stripFieldForStorage(targetArray),
|
|
325
245
|
transformations: [{ type: 'direct' }],
|
|
326
246
|
isArrayMapping: true,
|
|
327
247
|
};
|
|
@@ -347,8 +267,8 @@ class MappingService {
|
|
|
347
267
|
// Create a field mapping to visualize the connection
|
|
348
268
|
const fieldMapping = {
|
|
349
269
|
id: arrayToObjectMapping.id,
|
|
350
|
-
sourceFields: [sourceArray],
|
|
351
|
-
targetField: targetObject,
|
|
270
|
+
sourceFields: [this.stripFieldForStorage(sourceArray)],
|
|
271
|
+
targetField: this.stripFieldForStorage(targetObject),
|
|
352
272
|
transformations: [{ type: 'direct' }],
|
|
353
273
|
isArrayToObjectMapping: true,
|
|
354
274
|
};
|
|
@@ -551,10 +471,108 @@ class MappingService {
|
|
|
551
471
|
console.error('Failed to import mappings:', e);
|
|
552
472
|
}
|
|
553
473
|
}
|
|
554
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
555
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
474
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MappingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
475
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MappingService, providedIn: 'root' });
|
|
476
|
+
}
|
|
477
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: MappingService, decorators: [{
|
|
478
|
+
type: Injectable,
|
|
479
|
+
args: [{
|
|
480
|
+
providedIn: 'root',
|
|
481
|
+
}]
|
|
482
|
+
}] });
|
|
483
|
+
|
|
484
|
+
class SvgConnectorService {
|
|
485
|
+
createBezierPath(start, end) {
|
|
486
|
+
const dx = end.x - start.x;
|
|
487
|
+
const controlPointOffset = Math.min(Math.abs(dx) * 0.5, 150);
|
|
488
|
+
const cp1x = start.x + controlPointOffset;
|
|
489
|
+
const cp1y = start.y;
|
|
490
|
+
const cp2x = end.x - controlPointOffset;
|
|
491
|
+
const cp2y = end.y;
|
|
492
|
+
return `M ${start.x} ${start.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${end.x} ${end.y}`;
|
|
493
|
+
}
|
|
494
|
+
createMultiSourcePath(sources, target) {
|
|
495
|
+
const mergeX = target.x - 80;
|
|
496
|
+
const mergeY = target.y;
|
|
497
|
+
const mergePoint = { x: mergeX, y: mergeY };
|
|
498
|
+
const paths = sources.map((source) => {
|
|
499
|
+
// Path from source to merge point
|
|
500
|
+
const dx1 = mergeX - source.x;
|
|
501
|
+
const cp1Offset = Math.min(Math.abs(dx1) * 0.4, 100);
|
|
502
|
+
return `M ${source.x} ${source.y} C ${source.x + cp1Offset} ${source.y}, ${mergeX - cp1Offset} ${mergeY}, ${mergeX} ${mergeY}`;
|
|
503
|
+
});
|
|
504
|
+
// Add path from merge point to target
|
|
505
|
+
const finalPath = `M ${mergeX} ${mergeY} L ${target.x} ${target.y}`;
|
|
506
|
+
paths.push(finalPath);
|
|
507
|
+
return { paths, mergePoint };
|
|
508
|
+
}
|
|
509
|
+
getMidPoint(start, end) {
|
|
510
|
+
return {
|
|
511
|
+
x: (start.x + end.x) / 2,
|
|
512
|
+
y: (start.y + end.y) / 2,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
getMultiSourceMidPoint(sources, target) {
|
|
516
|
+
const mergeX = target.x - 80;
|
|
517
|
+
return {
|
|
518
|
+
x: mergeX,
|
|
519
|
+
y: target.y,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
calculateConnectionPoint(rect, side, containerRect) {
|
|
523
|
+
const relativeY = rect.top - containerRect.top + rect.height / 2;
|
|
524
|
+
// Offset to position endpoint circles outside the schema panels
|
|
525
|
+
const endpointOffset = 8;
|
|
526
|
+
if (side === 'source') {
|
|
527
|
+
return {
|
|
528
|
+
x: rect.right - containerRect.left + endpointOffset,
|
|
529
|
+
y: relativeY,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
return {
|
|
534
|
+
x: rect.left - containerRect.left - endpointOffset,
|
|
535
|
+
y: relativeY,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
isPointNearPath(point, pathStart, pathEnd, threshold = 10) {
|
|
540
|
+
// Simplified hit detection using distance to line segment
|
|
541
|
+
const A = point.x - pathStart.x;
|
|
542
|
+
const B = point.y - pathStart.y;
|
|
543
|
+
const C = pathEnd.x - pathStart.x;
|
|
544
|
+
const D = pathEnd.y - pathStart.y;
|
|
545
|
+
const dot = A * C + B * D;
|
|
546
|
+
const lenSq = C * C + D * D;
|
|
547
|
+
let param = -1;
|
|
548
|
+
if (lenSq !== 0) {
|
|
549
|
+
param = dot / lenSq;
|
|
550
|
+
}
|
|
551
|
+
let xx, yy;
|
|
552
|
+
if (param < 0) {
|
|
553
|
+
xx = pathStart.x;
|
|
554
|
+
yy = pathStart.y;
|
|
555
|
+
}
|
|
556
|
+
else if (param > 1) {
|
|
557
|
+
xx = pathEnd.x;
|
|
558
|
+
yy = pathEnd.y;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
xx = pathStart.x + param * C;
|
|
562
|
+
yy = pathStart.y + param * D;
|
|
563
|
+
}
|
|
564
|
+
const dx = point.x - xx;
|
|
565
|
+
const dy = point.y - yy;
|
|
566
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
567
|
+
return distance <= threshold;
|
|
568
|
+
}
|
|
569
|
+
createDragPath(start, end) {
|
|
570
|
+
return this.createBezierPath(start, end);
|
|
571
|
+
}
|
|
572
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SvgConnectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
573
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SvgConnectorService, providedIn: 'root' });
|
|
556
574
|
}
|
|
557
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SvgConnectorService, decorators: [{
|
|
558
576
|
type: Injectable,
|
|
559
577
|
args: [{
|
|
560
578
|
providedIn: 'root',
|
|
@@ -888,108 +906,10 @@ class TransformationService {
|
|
|
888
906
|
}
|
|
889
907
|
return this.evaluateCondition(value, config.condition.root);
|
|
890
908
|
}
|
|
891
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
892
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.
|
|
893
|
-
}
|
|
894
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: TransformationService, decorators: [{
|
|
895
|
-
type: Injectable,
|
|
896
|
-
args: [{
|
|
897
|
-
providedIn: 'root',
|
|
898
|
-
}]
|
|
899
|
-
}] });
|
|
900
|
-
|
|
901
|
-
class SvgConnectorService {
|
|
902
|
-
createBezierPath(start, end) {
|
|
903
|
-
const dx = end.x - start.x;
|
|
904
|
-
const controlPointOffset = Math.min(Math.abs(dx) * 0.5, 150);
|
|
905
|
-
const cp1x = start.x + controlPointOffset;
|
|
906
|
-
const cp1y = start.y;
|
|
907
|
-
const cp2x = end.x - controlPointOffset;
|
|
908
|
-
const cp2y = end.y;
|
|
909
|
-
return `M ${start.x} ${start.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${end.x} ${end.y}`;
|
|
910
|
-
}
|
|
911
|
-
createMultiSourcePath(sources, target) {
|
|
912
|
-
const mergeX = target.x - 80;
|
|
913
|
-
const mergeY = target.y;
|
|
914
|
-
const mergePoint = { x: mergeX, y: mergeY };
|
|
915
|
-
const paths = sources.map((source) => {
|
|
916
|
-
// Path from source to merge point
|
|
917
|
-
const dx1 = mergeX - source.x;
|
|
918
|
-
const cp1Offset = Math.min(Math.abs(dx1) * 0.4, 100);
|
|
919
|
-
return `M ${source.x} ${source.y} C ${source.x + cp1Offset} ${source.y}, ${mergeX - cp1Offset} ${mergeY}, ${mergeX} ${mergeY}`;
|
|
920
|
-
});
|
|
921
|
-
// Add path from merge point to target
|
|
922
|
-
const finalPath = `M ${mergeX} ${mergeY} L ${target.x} ${target.y}`;
|
|
923
|
-
paths.push(finalPath);
|
|
924
|
-
return { paths, mergePoint };
|
|
925
|
-
}
|
|
926
|
-
getMidPoint(start, end) {
|
|
927
|
-
return {
|
|
928
|
-
x: (start.x + end.x) / 2,
|
|
929
|
-
y: (start.y + end.y) / 2,
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
getMultiSourceMidPoint(sources, target) {
|
|
933
|
-
const mergeX = target.x - 80;
|
|
934
|
-
return {
|
|
935
|
-
x: mergeX,
|
|
936
|
-
y: target.y,
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
calculateConnectionPoint(rect, side, containerRect) {
|
|
940
|
-
const relativeY = rect.top - containerRect.top + rect.height / 2;
|
|
941
|
-
// Offset to position endpoint circles outside the schema panels
|
|
942
|
-
const endpointOffset = 8;
|
|
943
|
-
if (side === 'source') {
|
|
944
|
-
return {
|
|
945
|
-
x: rect.right - containerRect.left + endpointOffset,
|
|
946
|
-
y: relativeY,
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
else {
|
|
950
|
-
return {
|
|
951
|
-
x: rect.left - containerRect.left - endpointOffset,
|
|
952
|
-
y: relativeY,
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
isPointNearPath(point, pathStart, pathEnd, threshold = 10) {
|
|
957
|
-
// Simplified hit detection using distance to line segment
|
|
958
|
-
const A = point.x - pathStart.x;
|
|
959
|
-
const B = point.y - pathStart.y;
|
|
960
|
-
const C = pathEnd.x - pathStart.x;
|
|
961
|
-
const D = pathEnd.y - pathStart.y;
|
|
962
|
-
const dot = A * C + B * D;
|
|
963
|
-
const lenSq = C * C + D * D;
|
|
964
|
-
let param = -1;
|
|
965
|
-
if (lenSq !== 0) {
|
|
966
|
-
param = dot / lenSq;
|
|
967
|
-
}
|
|
968
|
-
let xx, yy;
|
|
969
|
-
if (param < 0) {
|
|
970
|
-
xx = pathStart.x;
|
|
971
|
-
yy = pathStart.y;
|
|
972
|
-
}
|
|
973
|
-
else if (param > 1) {
|
|
974
|
-
xx = pathEnd.x;
|
|
975
|
-
yy = pathEnd.y;
|
|
976
|
-
}
|
|
977
|
-
else {
|
|
978
|
-
xx = pathStart.x + param * C;
|
|
979
|
-
yy = pathStart.y + param * D;
|
|
980
|
-
}
|
|
981
|
-
const dx = point.x - xx;
|
|
982
|
-
const dy = point.y - yy;
|
|
983
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
984
|
-
return distance <= threshold;
|
|
985
|
-
}
|
|
986
|
-
createDragPath(start, end) {
|
|
987
|
-
return this.createBezierPath(start, end);
|
|
988
|
-
}
|
|
989
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SvgConnectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
990
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SvgConnectorService, providedIn: 'root' });
|
|
909
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TransformationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
910
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TransformationService, providedIn: 'root' });
|
|
991
911
|
}
|
|
992
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
912
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TransformationService, decorators: [{
|
|
993
913
|
type: Injectable,
|
|
994
914
|
args: [{
|
|
995
915
|
providedIn: 'root',
|
|
@@ -997,24 +917,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
997
917
|
}] });
|
|
998
918
|
|
|
999
919
|
class SchemaParserService {
|
|
1000
|
-
modelRegistry = {};
|
|
1001
920
|
idCounter = 0;
|
|
1002
|
-
|
|
1003
|
-
this.modelRegistry = { ...this.modelRegistry, ...models };
|
|
1004
|
-
}
|
|
1005
|
-
clearRegistry() {
|
|
1006
|
-
this.modelRegistry = {};
|
|
1007
|
-
}
|
|
1008
|
-
parseSchema(schemaJson, schemaName = 'Schema') {
|
|
1009
|
-
const schema = typeof schemaJson === 'string' ? JSON.parse(schemaJson) : schemaJson;
|
|
921
|
+
parseSchema(schema, schemaName = 'Schema') {
|
|
1010
922
|
this.idCounter = 0;
|
|
1011
923
|
// Extract definitions from the schema document itself
|
|
1012
924
|
const localDefs = schema.$defs || schema.definitions || {};
|
|
1013
|
-
const combinedRegistry = { ...this.modelRegistry, ...localDefs };
|
|
1014
925
|
let resolvedSchema;
|
|
1015
926
|
if (schema.$ref) {
|
|
1016
927
|
// Resolve the reference
|
|
1017
|
-
resolvedSchema = this.resolveRef(schema.$ref,
|
|
928
|
+
resolvedSchema = this.resolveRef(schema.$ref, localDefs);
|
|
1018
929
|
}
|
|
1019
930
|
else if (schema.properties) {
|
|
1020
931
|
// Direct schema with properties
|
|
@@ -1024,23 +935,15 @@ class SchemaParserService {
|
|
|
1024
935
|
throw new Error('Schema must have either $ref or properties');
|
|
1025
936
|
}
|
|
1026
937
|
// Build fields from the resolved schema
|
|
1027
|
-
|
|
1028
|
-
// Apply exclude filter
|
|
1029
|
-
if (schema.exclude && schema.exclude.length > 0) {
|
|
1030
|
-
fields = this.applyExclude(fields, schema.exclude);
|
|
1031
|
-
}
|
|
1032
|
-
// Apply include filter (only if specified)
|
|
1033
|
-
if (schema.include && schema.include.length > 0) {
|
|
1034
|
-
fields = this.applyInclude(fields, schema.include);
|
|
1035
|
-
}
|
|
938
|
+
const fields = this.buildFields(resolvedSchema, localDefs, '');
|
|
1036
939
|
return {
|
|
1037
940
|
name: schema.title || schemaName,
|
|
1038
941
|
fields,
|
|
1039
942
|
};
|
|
1040
943
|
}
|
|
1041
|
-
resolveRef(ref,
|
|
944
|
+
resolveRef(ref, definitions) {
|
|
1042
945
|
// Handle different ref formats:
|
|
1043
|
-
//
|
|
946
|
+
// #/$defs/model, #/definitions/model
|
|
1044
947
|
let modelName;
|
|
1045
948
|
if (ref.startsWith('#/$defs/')) {
|
|
1046
949
|
modelName = ref.substring(8);
|
|
@@ -1048,39 +951,39 @@ class SchemaParserService {
|
|
|
1048
951
|
else if (ref.startsWith('#/definitions/')) {
|
|
1049
952
|
modelName = ref.substring(14);
|
|
1050
953
|
}
|
|
1051
|
-
else if (ref.startsWith('#')) {
|
|
1052
|
-
modelName = ref.substring(1);
|
|
1053
|
-
}
|
|
1054
954
|
else {
|
|
1055
|
-
|
|
955
|
+
throw new Error(`Invalid $ref format: ${ref}. Use #/$defs/name or #/definitions/name`);
|
|
1056
956
|
}
|
|
1057
|
-
const resolved =
|
|
1058
|
-
if (!resolved) {
|
|
1059
|
-
throw new Error(`Cannot resolve reference: ${ref}.
|
|
957
|
+
const resolved = definitions[modelName];
|
|
958
|
+
if (!resolved || typeof resolved === 'boolean') {
|
|
959
|
+
throw new Error(`Cannot resolve reference: ${ref}. Definition "${modelName}" not found.`);
|
|
1060
960
|
}
|
|
1061
961
|
// If the resolved schema also has a $ref, resolve it recursively
|
|
1062
962
|
if (resolved.$ref) {
|
|
1063
|
-
return this.resolveRef(resolved.$ref,
|
|
963
|
+
return this.resolveRef(resolved.$ref, definitions);
|
|
1064
964
|
}
|
|
1065
965
|
return resolved;
|
|
1066
966
|
}
|
|
1067
|
-
buildFields(schema,
|
|
967
|
+
buildFields(schema, definitions, parentPath, arrayContext) {
|
|
1068
968
|
const fields = [];
|
|
1069
969
|
if (!schema.properties) {
|
|
1070
970
|
return fields;
|
|
1071
971
|
}
|
|
1072
972
|
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
973
|
+
// Skip boolean schema definitions
|
|
974
|
+
if (typeof propSchema === 'boolean')
|
|
975
|
+
continue;
|
|
1073
976
|
const path = parentPath ? `${parentPath}.${name}` : name;
|
|
1074
|
-
const field = this.buildField(name, propSchema,
|
|
977
|
+
const field = this.buildField(name, propSchema, definitions, path, arrayContext);
|
|
1075
978
|
fields.push(field);
|
|
1076
979
|
}
|
|
1077
980
|
return fields;
|
|
1078
981
|
}
|
|
1079
|
-
buildField(name, schema,
|
|
982
|
+
buildField(name, schema, definitions, path, arrayContext) {
|
|
1080
983
|
// Resolve $ref if present
|
|
1081
984
|
let resolvedSchema = schema;
|
|
1082
985
|
if (schema.$ref) {
|
|
1083
|
-
resolvedSchema = { ...this.resolveRef(schema.$ref,
|
|
986
|
+
resolvedSchema = { ...this.resolveRef(schema.$ref, definitions), ...schema };
|
|
1084
987
|
delete resolvedSchema.$ref;
|
|
1085
988
|
}
|
|
1086
989
|
const fieldType = this.mapType(resolvedSchema);
|
|
@@ -1095,18 +998,22 @@ class SchemaParserService {
|
|
|
1095
998
|
};
|
|
1096
999
|
// Handle nested objects
|
|
1097
1000
|
if (fieldType === 'object' && resolvedSchema.properties) {
|
|
1098
|
-
field.children = this.buildFields(resolvedSchema,
|
|
1001
|
+
field.children = this.buildFields(resolvedSchema, definitions, path, arrayContext);
|
|
1099
1002
|
field.expanded = true;
|
|
1100
1003
|
}
|
|
1101
1004
|
// Handle arrays with object items
|
|
1102
1005
|
if (fieldType === 'array' && resolvedSchema.items) {
|
|
1006
|
+
// Skip boolean or array schema definitions for items
|
|
1007
|
+
if (typeof resolvedSchema.items === 'boolean' || Array.isArray(resolvedSchema.items)) {
|
|
1008
|
+
return field;
|
|
1009
|
+
}
|
|
1103
1010
|
let itemSchema = resolvedSchema.items;
|
|
1104
1011
|
if (itemSchema.$ref) {
|
|
1105
|
-
itemSchema = this.resolveRef(itemSchema.$ref,
|
|
1012
|
+
itemSchema = this.resolveRef(itemSchema.$ref, definitions);
|
|
1106
1013
|
}
|
|
1107
1014
|
if (itemSchema.properties) {
|
|
1108
1015
|
// Mark children as array items with reference to parent array
|
|
1109
|
-
field.children = this.buildFields(itemSchema,
|
|
1016
|
+
field.children = this.buildFields(itemSchema, definitions, `${path}[]`, {
|
|
1110
1017
|
isArrayItem: true,
|
|
1111
1018
|
parentArrayPath: path,
|
|
1112
1019
|
});
|
|
@@ -1142,64 +1049,10 @@ class SchemaParserService {
|
|
|
1142
1049
|
return 'string';
|
|
1143
1050
|
}
|
|
1144
1051
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
.filter((field) => !exclude.includes(field.name) && !exclude.includes(field.path))
|
|
1148
|
-
.map((field) => {
|
|
1149
|
-
if (field.children) {
|
|
1150
|
-
// Filter nested fields by checking both full path and relative name
|
|
1151
|
-
const filteredChildren = this.applyExclude(field.children, exclude);
|
|
1152
|
-
return { ...field, children: filteredChildren };
|
|
1153
|
-
}
|
|
1154
|
-
return field;
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
applyInclude(fields, include) {
|
|
1158
|
-
return fields
|
|
1159
|
-
.filter((field) => {
|
|
1160
|
-
// Include if field name or path matches
|
|
1161
|
-
if (include.includes(field.name) || include.includes(field.path)) {
|
|
1162
|
-
return true;
|
|
1163
|
-
}
|
|
1164
|
-
// Include if any child path matches
|
|
1165
|
-
if (field.children) {
|
|
1166
|
-
return this.hasIncludedChild(field.children, include);
|
|
1167
|
-
}
|
|
1168
|
-
return false;
|
|
1169
|
-
})
|
|
1170
|
-
.map((field) => {
|
|
1171
|
-
if (field.children) {
|
|
1172
|
-
// Keep parent but filter children
|
|
1173
|
-
const filteredChildren = this.applyInclude(field.children, include);
|
|
1174
|
-
return { ...field, children: filteredChildren.length > 0 ? filteredChildren : field.children };
|
|
1175
|
-
}
|
|
1176
|
-
return field;
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
hasIncludedChild(fields, include) {
|
|
1180
|
-
return fields.some((field) => {
|
|
1181
|
-
if (include.includes(field.name) || include.includes(field.path)) {
|
|
1182
|
-
return true;
|
|
1183
|
-
}
|
|
1184
|
-
if (field.children) {
|
|
1185
|
-
return this.hasIncludedChild(field.children, include);
|
|
1186
|
-
}
|
|
1187
|
-
return false;
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
// Utility method to create a schema document from model name
|
|
1191
|
-
createSchemaFromRef(modelRef, options) {
|
|
1192
|
-
return {
|
|
1193
|
-
$ref: modelRef,
|
|
1194
|
-
title: options?.title,
|
|
1195
|
-
exclude: options?.exclude,
|
|
1196
|
-
include: options?.include,
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaParserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1200
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaParserService, providedIn: 'root' });
|
|
1052
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaParserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1053
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaParserService, providedIn: 'root' });
|
|
1201
1054
|
}
|
|
1202
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1055
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaParserService, decorators: [{
|
|
1203
1056
|
type: Injectable,
|
|
1204
1057
|
args: [{
|
|
1205
1058
|
providedIn: 'root',
|
|
@@ -1367,10 +1220,10 @@ class SchemaTreeComponent {
|
|
|
1367
1220
|
trackByFieldId(index, field) {
|
|
1368
1221
|
return field.id;
|
|
1369
1222
|
}
|
|
1370
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1371
|
-
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", showSchemaName: "showSchemaName" }, 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 @if (showSchemaName) {\n <span class=\"schema-title\">{{ schema.name }}</span>\n }\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"] }] });
|
|
1223
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1224
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", type: SchemaTreeComponent, isStandalone: true, selector: "schema-tree", inputs: { schema: "schema", side: "side", mappings: "mappings", defaultValues: "defaultValues", showSchemaName: "showSchemaName" }, 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 @if (showSchemaName) {\n <span class=\"schema-title\">{{ schema.name }}</span>\n }\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"] }] });
|
|
1372
1225
|
}
|
|
1373
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1226
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SchemaTreeComponent, decorators: [{
|
|
1374
1227
|
type: Component,
|
|
1375
1228
|
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 @if (showSchemaName) {\n <span class=\"schema-title\">{{ schema.name }}</span>\n }\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"] }]
|
|
1376
1229
|
}], propDecorators: { schema: [{
|
|
@@ -1585,10 +1438,10 @@ class ConditionBuilderComponent {
|
|
|
1585
1438
|
const op = allOperators.find((o) => o.value === operator);
|
|
1586
1439
|
return op?.label || operator;
|
|
1587
1440
|
}
|
|
1588
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1589
|
-
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" }] });
|
|
1441
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ConditionBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1442
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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" }] });
|
|
1590
1443
|
}
|
|
1591
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ConditionBuilderComponent, decorators: [{
|
|
1592
1445
|
type: Component,
|
|
1593
1446
|
args: [{ selector: 'condition-builder', standalone: true, imports: [
|
|
1594
1447
|
CommonModule,
|
|
@@ -1886,10 +1739,10 @@ class TransformationPopoverComponent {
|
|
|
1886
1739
|
};
|
|
1887
1740
|
return labels[operator] || operator;
|
|
1888
1741
|
}
|
|
1889
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
1890
|
-
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: i1$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: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$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: i7$2.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: i7$2.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: i7$2.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" }] });
|
|
1742
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TransformationPopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1743
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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: 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" }] });
|
|
1891
1744
|
}
|
|
1892
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1745
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: TransformationPopoverComponent, decorators: [{
|
|
1893
1746
|
type: Component,
|
|
1894
1747
|
args: [{ selector: 'transformation-popover', standalone: true, imports: [
|
|
1895
1748
|
CommonModule,
|
|
@@ -2116,10 +1969,10 @@ class ArrayFilterModalComponent {
|
|
|
2116
1969
|
this.onClose();
|
|
2117
1970
|
}
|
|
2118
1971
|
}
|
|
2119
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
2120
|
-
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 }}[] → {{ 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" }] });
|
|
1972
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArrayFilterModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1973
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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 }}[] → {{ 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$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
2121
1974
|
}
|
|
2122
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
1975
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArrayFilterModalComponent, decorators: [{
|
|
2123
1976
|
type: Component,
|
|
2124
1977
|
args: [{ selector: 'array-filter-modal', standalone: true, imports: [
|
|
2125
1978
|
CommonModule,
|
|
@@ -2320,10 +2173,10 @@ class ArraySelectorModalComponent {
|
|
|
2320
2173
|
this.onClose();
|
|
2321
2174
|
}
|
|
2322
2175
|
}
|
|
2323
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
2324
|
-
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 }}[] → {{ 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" }] });
|
|
2176
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArraySelectorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2177
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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 }}[] → {{ 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$1.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }] });
|
|
2325
2178
|
}
|
|
2326
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
2179
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: ArraySelectorModalComponent, decorators: [{
|
|
2327
2180
|
type: Component,
|
|
2328
2181
|
args: [{ selector: 'array-selector-modal', standalone: true, imports: [
|
|
2329
2182
|
CommonModule,
|
|
@@ -2410,10 +2263,10 @@ class DefaultValuePopoverComponent {
|
|
|
2410
2263
|
this.onClose();
|
|
2411
2264
|
}
|
|
2412
2265
|
}
|
|
2413
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
2414
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.
|
|
2266
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DefaultValuePopoverComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2267
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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"] }] });
|
|
2415
2268
|
}
|
|
2416
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
2269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DefaultValuePopoverComponent, decorators: [{
|
|
2417
2270
|
type: Component,
|
|
2418
2271
|
args: [{ selector: 'default-value-popover', standalone: true, imports: [
|
|
2419
2272
|
CommonModule,
|
|
@@ -2884,10 +2737,10 @@ class DataMapperComponent {
|
|
|
2884
2737
|
trackByConnectionId(index, connection) {
|
|
2885
2738
|
return connection.id;
|
|
2886
2739
|
}
|
|
2887
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.
|
|
2888
|
-
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", "showSchemaName"], 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"] }] });
|
|
2740
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DataMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2741
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.8", 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", "showSchemaName"], 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"] }] });
|
|
2889
2742
|
}
|
|
2890
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.
|
|
2743
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: DataMapperComponent, decorators: [{
|
|
2891
2744
|
type: Component,
|
|
2892
2745
|
args: [{ selector: 'data-mapper', standalone: true, imports: [
|
|
2893
2746
|
CommonModule,
|
|
@@ -2926,1309 +2779,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
2926
2779
|
args: ['document:mouseup', ['$event']]
|
|
2927
2780
|
}] } });
|
|
2928
2781
|
|
|
2929
|
-
class FieldItemComponent {
|
|
2930
|
-
cdr = inject(ChangeDetectorRef);
|
|
2931
|
-
appRef = inject(ApplicationRef);
|
|
2932
|
-
// Local expanded state to bypass change detection issues - using signal for better reactivity
|
|
2933
|
-
localExpanded = signal(false, ...(ngDevMode ? [{ debugName: "localExpanded" }] : []));
|
|
2934
|
-
field;
|
|
2935
|
-
parentList;
|
|
2936
|
-
level = 0;
|
|
2937
|
-
showDisplayType = false;
|
|
2938
|
-
fieldChange = new EventEmitter();
|
|
2939
|
-
delete = new EventEmitter();
|
|
2940
|
-
duplicate = new EventEmitter();
|
|
2941
|
-
outdent = new EventEmitter();
|
|
2942
|
-
fieldTypes = [
|
|
2943
|
-
{ value: 'string', label: 'String', icon: 'text_fields' },
|
|
2944
|
-
{ value: 'number', label: 'Number', icon: 'pin' },
|
|
2945
|
-
{ value: 'boolean', label: 'Boolean', icon: 'toggle_on' },
|
|
2946
|
-
{ value: 'date', label: 'Date', icon: 'calendar_today' },
|
|
2947
|
-
{ value: 'time', label: 'Time', icon: 'schedule' },
|
|
2948
|
-
{ value: 'object', label: 'Object', icon: 'data_object' },
|
|
2949
|
-
{ value: 'array', label: 'Array', icon: 'data_array' },
|
|
2950
|
-
];
|
|
2951
|
-
stringDisplayTypes = [
|
|
2952
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
2953
|
-
{ value: 'dropdown', label: 'Dropdown', icon: 'arrow_drop_down_circle' },
|
|
2954
|
-
{ value: 'textarea', label: 'Textarea', icon: 'notes' },
|
|
2955
|
-
{ value: 'richtext', label: 'Rich Text', icon: 'format_color_text' },
|
|
2956
|
-
];
|
|
2957
|
-
dateDisplayTypes = [
|
|
2958
|
-
{ value: 'datepicker', label: 'Date Picker', icon: 'calendar_today' },
|
|
2959
|
-
{ value: 'datetimepicker', label: 'DateTime Picker', icon: 'schedule' },
|
|
2960
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
2961
|
-
];
|
|
2962
|
-
timeDisplayTypes = [
|
|
2963
|
-
{ value: 'timepicker', label: 'Time Picker', icon: 'schedule' },
|
|
2964
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
2965
|
-
];
|
|
2966
|
-
numberDisplayTypes = [
|
|
2967
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
2968
|
-
{ value: 'stepper', label: 'Stepper', icon: 'unfold_more' },
|
|
2969
|
-
];
|
|
2970
|
-
booleanDisplayTypes = [
|
|
2971
|
-
{ value: 'checkbox', label: 'Checkbox', icon: 'check_box' },
|
|
2972
|
-
{ value: 'toggle', label: 'Toggle', icon: 'toggle_on' },
|
|
2973
|
-
];
|
|
2974
|
-
stringFormats = [
|
|
2975
|
-
{ value: '', label: '(none)' },
|
|
2976
|
-
{ value: 'email', label: 'Email' },
|
|
2977
|
-
{ value: 'uri', label: 'URI (URL)' },
|
|
2978
|
-
{ value: 'uuid', label: 'UUID' },
|
|
2979
|
-
];
|
|
2980
|
-
getDisplayTypes(fieldType) {
|
|
2981
|
-
if (fieldType === 'date')
|
|
2982
|
-
return this.dateDisplayTypes;
|
|
2983
|
-
if (fieldType === 'time')
|
|
2984
|
-
return this.timeDisplayTypes;
|
|
2985
|
-
if (fieldType === 'number')
|
|
2986
|
-
return this.numberDisplayTypes;
|
|
2987
|
-
if (fieldType === 'boolean')
|
|
2988
|
-
return this.booleanDisplayTypes;
|
|
2989
|
-
return this.stringDisplayTypes;
|
|
2990
|
-
}
|
|
2991
|
-
getTypeIcon(type) {
|
|
2992
|
-
return this.fieldTypes.find(t => t.value === type)?.icon || 'help_outline';
|
|
2993
|
-
}
|
|
2994
|
-
ngOnInit() {
|
|
2995
|
-
// Initialize localExpanded from field.expanded if it exists
|
|
2996
|
-
if (this.field?.expanded !== undefined) {
|
|
2997
|
-
this.localExpanded.set(this.field.expanded);
|
|
2998
|
-
}
|
|
2999
|
-
// Initialize children array for object/array types if needed
|
|
3000
|
-
if ((this.field?.type === 'object' || this.field?.type === 'array') && !this.field.children) {
|
|
3001
|
-
this.field.children = [];
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
ngOnChanges(changes) {
|
|
3005
|
-
// When field input changes, sync localExpanded with field.expanded
|
|
3006
|
-
// But PRESERVE localExpanded if field.expanded is false/undefined and we have a true value
|
|
3007
|
-
if (changes['field']) {
|
|
3008
|
-
if (this.field) {
|
|
3009
|
-
// If field.expanded is explicitly true, use it (we just set it)
|
|
3010
|
-
if (this.field.expanded === true) {
|
|
3011
|
-
this.localExpanded.set(true);
|
|
3012
|
-
}
|
|
3013
|
-
// If field.expanded is explicitly false, use it (user collapsed)
|
|
3014
|
-
else if (this.field.expanded === false) {
|
|
3015
|
-
this.localExpanded.set(false);
|
|
3016
|
-
}
|
|
3017
|
-
// If field.expanded is undefined, preserve current localExpanded state
|
|
3018
|
-
// This happens when parent updates and field object is new but doesn't have expanded set yet
|
|
3019
|
-
else {
|
|
3020
|
-
// Preserve current localExpanded, and sync field.expanded to match
|
|
3021
|
-
if (this.field.expanded !== this.localExpanded()) {
|
|
3022
|
-
this.field.expanded = this.localExpanded();
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
|
-
// Initialize children array for object/array types if needed
|
|
3026
|
-
if ((this.field.type === 'object' || this.field.type === 'array') && !this.field.children) {
|
|
3027
|
-
this.field.children = [];
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
|
-
generateId() {
|
|
3033
|
-
return `field-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3034
|
-
}
|
|
3035
|
-
toggleExpand() {
|
|
3036
|
-
this.handleExpandClick();
|
|
3037
|
-
}
|
|
3038
|
-
handleExpandClick(event) {
|
|
3039
|
-
if (event) {
|
|
3040
|
-
event.stopPropagation();
|
|
3041
|
-
}
|
|
3042
|
-
// Ensure children array exists
|
|
3043
|
-
if (!this.field.children) {
|
|
3044
|
-
this.field.children = [];
|
|
3045
|
-
}
|
|
3046
|
-
// Toggle local expanded state (using signal)
|
|
3047
|
-
const newExpandedState = !this.localExpanded();
|
|
3048
|
-
this.localExpanded.set(newExpandedState);
|
|
3049
|
-
// Sync to field.expanded (mutation persists because field object reference stays the same)
|
|
3050
|
-
this.field.expanded = newExpandedState;
|
|
3051
|
-
// Force immediate change detection - this should trigger template re-render
|
|
3052
|
-
this.cdr.markForCheck();
|
|
3053
|
-
this.cdr.detectChanges();
|
|
3054
|
-
// Don't emit fieldChange - expand/collapse is local UI state only
|
|
3055
|
-
// We only emit when actual field data changes (name, type, children, etc.)
|
|
3056
|
-
}
|
|
3057
|
-
onFieldNameChange(event) {
|
|
3058
|
-
const input = event.target;
|
|
3059
|
-
const sanitized = input.value.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
3060
|
-
this.field.name = sanitized;
|
|
3061
|
-
if (input.value !== sanitized) {
|
|
3062
|
-
input.value = sanitized;
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
onFieldNameBlur() {
|
|
3066
|
-
if (!this.field.name.trim()) {
|
|
3067
|
-
this.field.name = 'unnamed';
|
|
3068
|
-
}
|
|
3069
|
-
this.fieldChange.emit();
|
|
3070
|
-
}
|
|
3071
|
-
onFieldTypeChange(type) {
|
|
3072
|
-
this.field.type = type;
|
|
3073
|
-
if ((type === 'object' || type === 'array') && !this.field.children) {
|
|
3074
|
-
this.field.children = [];
|
|
3075
|
-
}
|
|
3076
|
-
if (type === 'string')
|
|
3077
|
-
this.field.displayType = 'textbox';
|
|
3078
|
-
else if (type === 'number')
|
|
3079
|
-
this.field.displayType = 'textbox';
|
|
3080
|
-
else if (type === 'boolean')
|
|
3081
|
-
this.field.displayType = 'checkbox';
|
|
3082
|
-
else if (type === 'date')
|
|
3083
|
-
this.field.displayType = 'datepicker';
|
|
3084
|
-
else if (type === 'time')
|
|
3085
|
-
this.field.displayType = 'timepicker';
|
|
3086
|
-
else
|
|
3087
|
-
this.field.displayType = undefined;
|
|
3088
|
-
this.fieldChange.emit();
|
|
3089
|
-
}
|
|
3090
|
-
onDisplayTypeChange(displayType) {
|
|
3091
|
-
this.field.displayType = displayType;
|
|
3092
|
-
this.fieldChange.emit();
|
|
3093
|
-
}
|
|
3094
|
-
toggleRequired() {
|
|
3095
|
-
this.field.required = !this.field.required;
|
|
3096
|
-
this.fieldChange.emit();
|
|
3097
|
-
}
|
|
3098
|
-
onLabelChange(label) {
|
|
3099
|
-
this.field.label = label;
|
|
3100
|
-
}
|
|
3101
|
-
onLabelBlur() {
|
|
3102
|
-
this.fieldChange.emit();
|
|
3103
|
-
}
|
|
3104
|
-
addChildField() {
|
|
3105
|
-
if (!this.field.children) {
|
|
3106
|
-
this.field.children = [];
|
|
3107
|
-
}
|
|
3108
|
-
const newField = {
|
|
3109
|
-
id: this.generateId(),
|
|
3110
|
-
name: '',
|
|
3111
|
-
type: 'string',
|
|
3112
|
-
displayType: 'textbox',
|
|
3113
|
-
};
|
|
3114
|
-
this.field.children.push(newField);
|
|
3115
|
-
this.localExpanded.set(true);
|
|
3116
|
-
this.field.expanded = true;
|
|
3117
|
-
this.cdr.detectChanges();
|
|
3118
|
-
this.fieldChange.emit();
|
|
3119
|
-
}
|
|
3120
|
-
deleteField() {
|
|
3121
|
-
this.delete.emit(this.field);
|
|
3122
|
-
}
|
|
3123
|
-
duplicateField() {
|
|
3124
|
-
this.duplicate.emit(this.field);
|
|
3125
|
-
}
|
|
3126
|
-
onChildFieldChange() {
|
|
3127
|
-
// Just propagate the change up - don't force detectChanges as it can reset expanded state
|
|
3128
|
-
this.fieldChange.emit();
|
|
3129
|
-
}
|
|
3130
|
-
onChildDelete(child) {
|
|
3131
|
-
if (this.field.children) {
|
|
3132
|
-
const index = this.field.children.indexOf(child);
|
|
3133
|
-
if (index > -1) {
|
|
3134
|
-
this.field.children.splice(index, 1);
|
|
3135
|
-
this.cdr.detectChanges();
|
|
3136
|
-
this.fieldChange.emit();
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
onChildDuplicate(child) {
|
|
3141
|
-
if (this.field.children) {
|
|
3142
|
-
const index = this.field.children.indexOf(child);
|
|
3143
|
-
if (index > -1) {
|
|
3144
|
-
const clone = {
|
|
3145
|
-
...child,
|
|
3146
|
-
id: this.generateId(),
|
|
3147
|
-
name: child.name + '_copy',
|
|
3148
|
-
children: child.children ? this.cloneFields(child.children) : undefined,
|
|
3149
|
-
};
|
|
3150
|
-
this.field.children.splice(index + 1, 0, clone);
|
|
3151
|
-
this.cdr.detectChanges();
|
|
3152
|
-
this.fieldChange.emit();
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
cloneFields(fields) {
|
|
3157
|
-
return fields.map(f => ({
|
|
3158
|
-
...f,
|
|
3159
|
-
id: this.generateId(),
|
|
3160
|
-
children: f.children ? this.cloneFields(f.children) : undefined,
|
|
3161
|
-
}));
|
|
3162
|
-
}
|
|
3163
|
-
onFieldDrop(event) {
|
|
3164
|
-
if (event.previousIndex !== event.currentIndex && this.field.children) {
|
|
3165
|
-
moveItemInArray(this.field.children, event.previousIndex, event.currentIndex);
|
|
3166
|
-
// Don't emit fieldChange for reorder - array is mutated in place
|
|
3167
|
-
// and emitting causes parent re-render which resets expanded state
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
canIndent() {
|
|
3171
|
-
const index = this.parentList.indexOf(this.field);
|
|
3172
|
-
if (index <= 0)
|
|
3173
|
-
return false;
|
|
3174
|
-
const prevSibling = this.parentList[index - 1];
|
|
3175
|
-
return prevSibling.type === 'object' || prevSibling.type === 'array';
|
|
3176
|
-
}
|
|
3177
|
-
indentField() {
|
|
3178
|
-
const index = this.parentList.indexOf(this.field);
|
|
3179
|
-
if (index <= 0)
|
|
3180
|
-
return;
|
|
3181
|
-
const prevSibling = this.parentList[index - 1];
|
|
3182
|
-
if (prevSibling.type !== 'object' && prevSibling.type !== 'array')
|
|
3183
|
-
return;
|
|
3184
|
-
this.parentList.splice(index, 1);
|
|
3185
|
-
if (!prevSibling.children)
|
|
3186
|
-
prevSibling.children = [];
|
|
3187
|
-
prevSibling.children.push(this.field);
|
|
3188
|
-
prevSibling.expanded = true;
|
|
3189
|
-
// Don't emit fieldChange - arrays are mutated in place
|
|
3190
|
-
}
|
|
3191
|
-
canOutdent() {
|
|
3192
|
-
return this.level > 0;
|
|
3193
|
-
}
|
|
3194
|
-
outdentField() {
|
|
3195
|
-
if (this.level <= 0)
|
|
3196
|
-
return;
|
|
3197
|
-
this.outdent.emit(this.field);
|
|
3198
|
-
}
|
|
3199
|
-
onChildOutdent(child) {
|
|
3200
|
-
if (!this.field.children)
|
|
3201
|
-
return;
|
|
3202
|
-
const childIndex = this.field.children.indexOf(child);
|
|
3203
|
-
if (childIndex === -1)
|
|
3204
|
-
return;
|
|
3205
|
-
// Remove child from this field's children
|
|
3206
|
-
this.field.children.splice(childIndex, 1);
|
|
3207
|
-
// Add to parent list after this field
|
|
3208
|
-
const myIndex = this.parentList.indexOf(this.field);
|
|
3209
|
-
this.parentList.splice(myIndex + 1, 0, child);
|
|
3210
|
-
// Don't emit fieldChange - arrays are mutated in place
|
|
3211
|
-
}
|
|
3212
|
-
toggleValuesEditor() {
|
|
3213
|
-
this.field.isEditingValues = !this.field.isEditingValues;
|
|
3214
|
-
if (this.field.isEditingValues) {
|
|
3215
|
-
this.field.isEditingDefault = false;
|
|
3216
|
-
this.field.isEditingValidators = false;
|
|
3217
|
-
if (!this.field.allowedValues)
|
|
3218
|
-
this.field.allowedValues = [];
|
|
3219
|
-
}
|
|
3220
|
-
this.fieldChange.emit();
|
|
3221
|
-
}
|
|
3222
|
-
addAllowedValue(input) {
|
|
3223
|
-
let inputEl = null;
|
|
3224
|
-
if (input instanceof HTMLInputElement) {
|
|
3225
|
-
inputEl = input;
|
|
3226
|
-
}
|
|
3227
|
-
else {
|
|
3228
|
-
const target = input.target;
|
|
3229
|
-
const header = target.closest('.values-header');
|
|
3230
|
-
inputEl = header?.querySelector('.value-input');
|
|
3231
|
-
}
|
|
3232
|
-
if (!inputEl)
|
|
3233
|
-
return;
|
|
3234
|
-
const value = inputEl.value.trim();
|
|
3235
|
-
if (value && !this.field.allowedValues?.includes(value)) {
|
|
3236
|
-
if (!this.field.allowedValues)
|
|
3237
|
-
this.field.allowedValues = [];
|
|
3238
|
-
this.field.allowedValues.push(value);
|
|
3239
|
-
inputEl.value = '';
|
|
3240
|
-
this.fieldChange.emit();
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
|
-
removeAllowedValue(index) {
|
|
3244
|
-
if (this.field.allowedValues) {
|
|
3245
|
-
this.field.allowedValues.splice(index, 1);
|
|
3246
|
-
if (this.field.allowedValues.length === 0)
|
|
3247
|
-
this.field.allowedValues = undefined;
|
|
3248
|
-
this.fieldChange.emit();
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
onAllowedValueKeydown(event, input) {
|
|
3252
|
-
if (event.key === 'Enter') {
|
|
3253
|
-
event.preventDefault();
|
|
3254
|
-
this.addAllowedValue(input);
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
toggleDefaultEditor() {
|
|
3258
|
-
this.field.isEditingDefault = !this.field.isEditingDefault;
|
|
3259
|
-
if (this.field.isEditingDefault) {
|
|
3260
|
-
this.field.isEditingValues = false;
|
|
3261
|
-
this.field.isEditingValidators = false;
|
|
3262
|
-
}
|
|
3263
|
-
this.fieldChange.emit();
|
|
3264
|
-
}
|
|
3265
|
-
onDefaultValueChange(value) {
|
|
3266
|
-
if (value === '') {
|
|
3267
|
-
this.field.defaultValue = undefined;
|
|
3268
|
-
}
|
|
3269
|
-
else if (this.field.type === 'number') {
|
|
3270
|
-
const num = parseFloat(value);
|
|
3271
|
-
this.field.defaultValue = isNaN(num) ? undefined : num;
|
|
3272
|
-
}
|
|
3273
|
-
else if (this.field.type === 'boolean') {
|
|
3274
|
-
this.field.defaultValue = value === 'true';
|
|
3275
|
-
}
|
|
3276
|
-
else {
|
|
3277
|
-
this.field.defaultValue = value;
|
|
3278
|
-
}
|
|
3279
|
-
this.fieldChange.emit();
|
|
3280
|
-
}
|
|
3281
|
-
clearDefaultValue() {
|
|
3282
|
-
this.field.defaultValue = undefined;
|
|
3283
|
-
this.field.isEditingDefault = false;
|
|
3284
|
-
this.fieldChange.emit();
|
|
3285
|
-
}
|
|
3286
|
-
onDefaultValueKeydown(event) {
|
|
3287
|
-
if (event.key === 'Enter' || event.key === 'Escape') {
|
|
3288
|
-
this.field.isEditingDefault = false;
|
|
3289
|
-
this.fieldChange.emit();
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
toggleValidatorsEditor() {
|
|
3293
|
-
this.field.isEditingValidators = !this.field.isEditingValidators;
|
|
3294
|
-
if (this.field.isEditingValidators) {
|
|
3295
|
-
this.field.isEditingValues = false;
|
|
3296
|
-
this.field.isEditingDefault = false;
|
|
3297
|
-
}
|
|
3298
|
-
this.fieldChange.emit();
|
|
3299
|
-
}
|
|
3300
|
-
hasValidators() {
|
|
3301
|
-
if (this.field.type === 'string') {
|
|
3302
|
-
return !!(this.field.minLength || this.field.maxLength || this.field.pattern || this.field.format);
|
|
3303
|
-
}
|
|
3304
|
-
if (this.field.type === 'number') {
|
|
3305
|
-
return this.field.minimum !== undefined || this.field.maximum !== undefined;
|
|
3306
|
-
}
|
|
3307
|
-
return false;
|
|
3308
|
-
}
|
|
3309
|
-
onFormatChange(format) {
|
|
3310
|
-
this.field.format = format || undefined;
|
|
3311
|
-
if (format)
|
|
3312
|
-
this.field.pattern = undefined;
|
|
3313
|
-
this.fieldChange.emit();
|
|
3314
|
-
}
|
|
3315
|
-
onMinLengthChange(value) {
|
|
3316
|
-
this.field.minLength = value ? parseInt(value, 10) : undefined;
|
|
3317
|
-
this.fieldChange.emit();
|
|
3318
|
-
}
|
|
3319
|
-
onMaxLengthChange(value) {
|
|
3320
|
-
this.field.maxLength = value ? parseInt(value, 10) : undefined;
|
|
3321
|
-
this.fieldChange.emit();
|
|
3322
|
-
}
|
|
3323
|
-
onPatternChange(value) {
|
|
3324
|
-
this.field.pattern = value || undefined;
|
|
3325
|
-
this.fieldChange.emit();
|
|
3326
|
-
}
|
|
3327
|
-
onMinimumChange(value) {
|
|
3328
|
-
this.field.minimum = value ? parseFloat(value) : undefined;
|
|
3329
|
-
this.fieldChange.emit();
|
|
3330
|
-
}
|
|
3331
|
-
onMaximumChange(value) {
|
|
3332
|
-
this.field.maximum = value ? parseFloat(value) : undefined;
|
|
3333
|
-
this.fieldChange.emit();
|
|
3334
|
-
}
|
|
3335
|
-
canAddChildToArray() {
|
|
3336
|
-
return this.field.type === 'array' && (!this.field.children || this.field.children.length === 0);
|
|
3337
|
-
}
|
|
3338
|
-
getEmptyMessage() {
|
|
3339
|
-
return this.field.type === 'array' ? 'No item schema defined' : 'No child fields';
|
|
3340
|
-
}
|
|
3341
|
-
getAddButtonLabel() {
|
|
3342
|
-
return this.field.type === 'array' ? 'Define item schema' : 'Add field';
|
|
3343
|
-
}
|
|
3344
|
-
shouldShowNestedFields() {
|
|
3345
|
-
return (this.field.type === 'object' || this.field.type === 'array') && this.localExpanded();
|
|
3346
|
-
}
|
|
3347
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FieldItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3348
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FieldItemComponent, isStandalone: true, selector: "field-item", inputs: { field: "field", parentList: "parentList", level: "level", showDisplayType: "showDisplayType" }, outputs: { fieldChange: "fieldChange", delete: "delete", duplicate: "duplicate", outdent: "outdent" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"field-wrapper\">\n <div\n class=\"field-item\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle (leftmost) -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Button (show one or the other) -->\n <div class=\"indent-buttons\">\n @if (level > 0) {\n <button\n class=\"indent-btn\"\n (click)=\"outdentField()\"\n matTooltip=\"Outdent (move to parent level)\"\n >\n <mat-icon>format_indent_decrease</mat-icon>\n </button>\n } @else {\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent()\"\n (click)=\"indentField()\"\n matTooltip=\"Indent (make child of previous sibling)\"\n >\n <mat-icon>format_indent_increase</mat-icon>\n </button>\n }\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-btn\"\n (click)=\"handleExpandClick()\"\n [matTooltip]=\"localExpanded() ? 'Collapse' : 'Expand'\"\n >\n <mat-icon>{{ localExpanded() ? '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 <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange($event)\"\n (blur)=\"onFieldNameBlur()\"\n placeholder=\"Field name\"\n matTooltip=\"Property name in JSON Schema\"\n />\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired()\"\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: label (flexible) -->\n <input\n class=\"label-input\"\n [value]=\"field.label || ''\"\n (input)=\"onLabelChange($any($event.target).value)\"\n (blur)=\"onLabelBlur()\"\n placeholder=\"Label...\"\n matTooltip=\"Display label (stored as title)\"\n />\n\n <!-- Right section: type and actions -->\n <div class=\"field-right\">\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\" matTooltip=\"Data type\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange($event.value)\" panelClass=\"schema-editor-select-panel\">\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 <!-- Display Type Selector -->\n @if (showDisplayType) {\n @if (field.type === 'string' || field.type === 'number' || field.type === 'boolean' || field.type === 'date' || field.type === 'time') {\n <mat-form-field appearance=\"outline\" class=\"display-type-selector\" matTooltip=\"Display Type\">\n <mat-select [value]=\"field.displayType\" (selectionChange)=\"onDisplayTypeChange($event.value)\" panelClass=\"schema-editor-select-panel\">\n @for (dt of getDisplayTypes(field.type); track dt.value) {\n <mat-option [value]=\"dt.value\">\n <mat-icon>{{ dt.icon }}</mat-icon>\n {{ dt.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <div class=\"display-type-placeholder\"></div>\n }\n }\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || canAddChildToArray()) {\n <button\n mat-icon-button\n (click)=\"addChildField()\"\n [matTooltip]=\"field.type === 'array' ? 'Define item schema' : '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()\"\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()\"\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 @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValidatorsEditor()\"\n [matTooltip]=\"hasValidators() ? 'Edit validators' : 'Add validators'\"\n [class.has-validators]=\"hasValidators()\"\n >\n <mat-icon>rule</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)=\"duplicateField()\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField()\" 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, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue($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(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($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($any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue()\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validators Editor -->\n @if (field.isEditingValidators && (field.type === 'string' || field.type === 'number')) {\n <div class=\"validators-editor\">\n <div class=\"validators-header\">\n <span class=\"validators-label\">Validators:</span>\n </div>\n @if (field.type === 'string') {\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Format</label>\n <select\n class=\"validator-select\"\n [value]=\"field.format || ''\"\n (change)=\"onFormatChange($any($event.target).value)\"\n >\n @for (fmt of stringFormats; track fmt.value) {\n <option [value]=\"fmt.value\">{{ fmt.label }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Min Length</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.minLength ?? ''\"\n (input)=\"onMinLengthChange($any($event.target).value)\"\n placeholder=\"0\"\n min=\"0\"\n />\n </div>\n <div class=\"validator-field\">\n <label>Max Length</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.maxLength ?? ''\"\n (input)=\"onMaxLengthChange($any($event.target).value)\"\n placeholder=\"\u221E\"\n min=\"0\"\n />\n </div>\n @if (!field.format) {\n <div class=\"validator-field pattern-field\">\n <label>Pattern (regex)</label>\n <input\n type=\"text\"\n class=\"validator-input\"\n [value]=\"field.pattern ?? ''\"\n (input)=\"onPatternChange($any($event.target).value)\"\n placeholder=\"^[a-z]+$\"\n />\n </div>\n }\n </div>\n }\n @if (field.type === 'number') {\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Minimum</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.minimum ?? ''\"\n (input)=\"onMinimumChange($any($event.target).value)\"\n placeholder=\"-\u221E\"\n />\n </div>\n <div class=\"validator-field\">\n <label>Maximum</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.maximum ?? ''\"\n (input)=\"onMaximumChange($any($event.target).value)\"\n placeholder=\"\u221E\"\n />\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && (localExpanded() || field.expanded)) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children || []\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children && field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <field-item\n cdkDrag\n [field]=\"child\"\n [parentList]=\"field.children\"\n [level]=\"level + 1\"\n [showDisplayType]=\"showDisplayType\"\n (fieldChange)=\"onChildFieldChange()\"\n (delete)=\"onChildDelete($event)\"\n (duplicate)=\"onChildDuplicate($event)\"\n (outdent)=\"onChildOutdent($event)\"\n >\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n </field-item>\n }\n } @else {\n <div class=\"empty-nested\">\n <span>{{ getEmptyMessage() }}</span>\n <button mat-button (click)=\"addChildField()\" color=\"primary\">\n <mat-icon>add</mat-icon>\n {{ getAddButtonLabel() }}\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".field-wrapper{display:block;width:100%;max-width:100%;box-sizing:border-box;overflow:hidden;margin-bottom:4px}.field-wrapper ::ng-deep .mat-mdc-icon-button{width:32px!important;height:32px!important;padding:4px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;flex-shrink:0!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-touch-target{width:32px!important;height:32px!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.field-wrapper ::ng-deep .mat-mdc-icon-button mat-icon,.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-icon{font-size:20px!important;width:20px!important;height:20px!important;line-height:20px!important;overflow:hidden!important}.field-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f8fafc;border-radius:8px;border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.field-item:hover{background:#f1f5f9;border-color:#e2e8f0}.field-item:focus-within{background:#eff6ff;border-color:#3b82f6}.field-item.is-complex{background:#fefce8}.field-item.is-complex:hover{background:#fef9c3}.field-item.is-complex:focus-within{background:#fef3c7;border-color:#3b82f6}.field-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.field-right{display:flex;align-items:center;gap:8px;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}.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:background-color .15s ease,color .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px;transition:transform .15s ease}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.array-indicator{color:#f59e0b;font-weight:600;font-size:14px;margin-left:2px}.field-name-input{flex:0 0 auto;width:120px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;transition:all .15s ease}.field-name-input:focus{border-color:#3b82f6;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}.label-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;transition:all .15s ease}.label-input:focus{border-color:#3b82f6;background:#fff}.label-input::placeholder{color:#94a3b8;font-style:italic}.type-selector,.display-type-selector{width:130px;flex-shrink:0;margin-right:4px}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper,.display-type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.type-selector ::ng-deep .mdc-text-field--outlined,.display-type-selector ::ng-deep .mdc-text-field--outlined{height:32px!important}.type-selector ::ng-deep .mat-mdc-text-field-wrapper,.display-type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important;height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-flex,.display-type-selector ::ng-deep .mat-mdc-form-field-flex{height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix,.display-type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important;height:28px!important}.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#e2e8f0!important}.type-selector ::ng-deep .mat-mdc-select,.display-type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text,.display-type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper,.display-type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.display-type-selector{width:120px}.display-type-placeholder{width:120px;flex-shrink:0;margin-right:4px}::ng-deep .schema-editor-select-panel .mat-mdc-option{font-size:12px!important;min-height:36px!important;padding:0 12px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:12px!important;display:flex!important;align-items:center!important;gap:8px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option mat-icon{font-size:16px!important;width:16px!important;height:16px!important;margin-right:0!important;color:#64748b}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:2px;width:168px;flex-shrink:0;margin-left:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right:hover .field-actions{opacity:1}.field-actions button[mat-icon-button]{width:32px;height:32px;padding:4px;line-height:1;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.field-actions button[mat-icon-button] mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.field-actions .has-values,.field-actions .has-default,.field-actions .has-validators{color:#22c55e!important}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.allowed-values-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;box-sizing:border-box}.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}.default-value-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px;box-sizing:border-box}.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}.validators-editor{margin:12px 12px 8px 48px;padding:12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;box-sizing:border-box;overflow:hidden}.validators-editor .validators-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.validators-editor .validators-label{font-size:12px;font-weight:500;color:#1d4ed8}.validators-editor .validators-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px;overflow:hidden;width:100%;box-sizing:border-box}.validators-editor .validators-row:last-child{margin-bottom:0}.validators-editor .validator-field{display:flex;flex-direction:column;gap:4px;flex-shrink:0;box-sizing:border-box}.validators-editor .validator-field label{font-size:11px;font-weight:500;color:#3b82f6}.validators-editor .validator-field.pattern-field{flex:1 1 auto;min-width:80px;overflow:hidden}.validators-editor .validator-input,.validators-editor .validator-select{padding:6px 10px;font-size:13px;border:1px solid #93c5fd;border-radius:4px;outline:none;background:#fff;width:100px;box-sizing:border-box!important}.validators-editor .validator-input:focus,.validators-editor .validator-select:focus{border-color:#3b82f6}.validators-editor .validator-input::placeholder,.validators-editor .validator-select::placeholder{color:#94a3b8}.validators-editor .validator-select{cursor:pointer;width:120px}.validators-editor .pattern-field .validator-input{width:100%;box-sizing:border-box!important}.nested-fields{display:block;min-height:30px;margin-top:8px;margin-bottom:8px;padding-left:32px;box-sizing:border-box}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor,.nested-fields .validators-editor{margin-left:16px;margin-top: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}.drag-placeholder{background:#e2e8f0;border:2px dashed #3b82f6;border-radius:8px;min-height:48px;margin-bottom:4px}::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}.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)}\n"], dependencies: [{ kind: "component", type: FieldItemComponent, selector: "field-item", inputs: ["field", "parentList", "level", "showDisplayType"], outputs: ["fieldChange", "delete", "duplicate", "outdent"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$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: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { 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: i6$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: i6$2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i6$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 }, { kind: "directive", type: i7$2.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: i7$2.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: i7$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i7$2.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i7$2.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }] });
|
|
3349
|
-
}
|
|
3350
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FieldItemComponent, decorators: [{
|
|
3351
|
-
type: Component,
|
|
3352
|
-
args: [{ selector: 'field-item', standalone: true, imports: [
|
|
3353
|
-
CommonModule,
|
|
3354
|
-
FormsModule,
|
|
3355
|
-
MatButtonModule,
|
|
3356
|
-
MatIconModule,
|
|
3357
|
-
MatFormFieldModule,
|
|
3358
|
-
MatSelectModule,
|
|
3359
|
-
MatTooltipModule,
|
|
3360
|
-
MatMenuModule,
|
|
3361
|
-
DragDropModule,
|
|
3362
|
-
FieldItemComponent,
|
|
3363
|
-
], template: "<div class=\"field-wrapper\">\n <div\n class=\"field-item\"\n [class.is-complex]=\"field.type === 'object' || field.type === 'array'\"\n >\n <!-- Left section: field controls -->\n <div class=\"field-left\">\n <!-- Drag Handle (leftmost) -->\n <div class=\"drag-handle\" cdkDragHandle matTooltip=\"Drag to reorder\">\n <mat-icon>drag_indicator</mat-icon>\n </div>\n\n <!-- Indent/Outdent Button (show one or the other) -->\n <div class=\"indent-buttons\">\n @if (level > 0) {\n <button\n class=\"indent-btn\"\n (click)=\"outdentField()\"\n matTooltip=\"Outdent (move to parent level)\"\n >\n <mat-icon>format_indent_decrease</mat-icon>\n </button>\n } @else {\n <button\n class=\"indent-btn\"\n [disabled]=\"!canIndent()\"\n (click)=\"indentField()\"\n matTooltip=\"Indent (make child of previous sibling)\"\n >\n <mat-icon>format_indent_increase</mat-icon>\n </button>\n }\n </div>\n\n <!-- Expand/Collapse -->\n @if (field.type === 'object' || field.type === 'array') {\n <button\n mat-icon-button\n type=\"button\"\n class=\"expand-btn\"\n (click)=\"handleExpandClick()\"\n [matTooltip]=\"localExpanded() ? 'Collapse' : 'Expand'\"\n >\n <mat-icon>{{ localExpanded() ? '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 <input\n class=\"field-name-input\"\n [value]=\"field.name\"\n (input)=\"onFieldNameChange($event)\"\n (blur)=\"onFieldNameBlur()\"\n placeholder=\"Field name\"\n matTooltip=\"Property name in JSON Schema\"\n />\n @if (field.type === 'array') {\n <span class=\"array-indicator\">[]</span>\n }\n\n <!-- Required Toggle -->\n <button\n class=\"required-btn\"\n [class.is-required]=\"field.required\"\n (click)=\"toggleRequired()\"\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: label (flexible) -->\n <input\n class=\"label-input\"\n [value]=\"field.label || ''\"\n (input)=\"onLabelChange($any($event.target).value)\"\n (blur)=\"onLabelBlur()\"\n placeholder=\"Label...\"\n matTooltip=\"Display label (stored as title)\"\n />\n\n <!-- Right section: type and actions -->\n <div class=\"field-right\">\n <!-- Type Selector -->\n <mat-form-field appearance=\"outline\" class=\"type-selector\" matTooltip=\"Data type\">\n <mat-select [value]=\"field.type\" (selectionChange)=\"onFieldTypeChange($event.value)\" panelClass=\"schema-editor-select-panel\">\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 <!-- Display Type Selector -->\n @if (showDisplayType) {\n @if (field.type === 'string' || field.type === 'number' || field.type === 'boolean' || field.type === 'date' || field.type === 'time') {\n <mat-form-field appearance=\"outline\" class=\"display-type-selector\" matTooltip=\"Display Type\">\n <mat-select [value]=\"field.displayType\" (selectionChange)=\"onDisplayTypeChange($event.value)\" panelClass=\"schema-editor-select-panel\">\n @for (dt of getDisplayTypes(field.type); track dt.value) {\n <mat-option [value]=\"dt.value\">\n <mat-icon>{{ dt.icon }}</mat-icon>\n {{ dt.label }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <div class=\"display-type-placeholder\"></div>\n }\n }\n\n <!-- Actions -->\n <div class=\"field-actions\">\n @if (field.type === 'object' || canAddChildToArray()) {\n <button\n mat-icon-button\n (click)=\"addChildField()\"\n [matTooltip]=\"field.type === 'array' ? 'Define item schema' : '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()\"\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()\"\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 @if (field.type === 'string' || field.type === 'number') {\n <button\n mat-icon-button\n (click)=\"toggleValidatorsEditor()\"\n [matTooltip]=\"hasValidators() ? 'Edit validators' : 'Add validators'\"\n [class.has-validators]=\"hasValidators()\"\n >\n <mat-icon>rule</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)=\"duplicateField()\">\n <mat-icon>content_copy</mat-icon>\n <span>Duplicate</span>\n </button>\n <button mat-menu-item (click)=\"deleteField()\" 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, valueInput)\"\n />\n <button class=\"add-value-btn\" (click)=\"addAllowedValue($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(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($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($any($event.target).value)\"\n (keydown)=\"onDefaultValueKeydown($event)\"\n placeholder=\"Enter default value\"\n />\n }\n @if (field.defaultValue !== undefined) {\n <button class=\"clear-default-btn\" (click)=\"clearDefaultValue()\" matTooltip=\"Clear default\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validators Editor -->\n @if (field.isEditingValidators && (field.type === 'string' || field.type === 'number')) {\n <div class=\"validators-editor\">\n <div class=\"validators-header\">\n <span class=\"validators-label\">Validators:</span>\n </div>\n @if (field.type === 'string') {\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Format</label>\n <select\n class=\"validator-select\"\n [value]=\"field.format || ''\"\n (change)=\"onFormatChange($any($event.target).value)\"\n >\n @for (fmt of stringFormats; track fmt.value) {\n <option [value]=\"fmt.value\">{{ fmt.label }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Min Length</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.minLength ?? ''\"\n (input)=\"onMinLengthChange($any($event.target).value)\"\n placeholder=\"0\"\n min=\"0\"\n />\n </div>\n <div class=\"validator-field\">\n <label>Max Length</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.maxLength ?? ''\"\n (input)=\"onMaxLengthChange($any($event.target).value)\"\n placeholder=\"\u221E\"\n min=\"0\"\n />\n </div>\n @if (!field.format) {\n <div class=\"validator-field pattern-field\">\n <label>Pattern (regex)</label>\n <input\n type=\"text\"\n class=\"validator-input\"\n [value]=\"field.pattern ?? ''\"\n (input)=\"onPatternChange($any($event.target).value)\"\n placeholder=\"^[a-z]+$\"\n />\n </div>\n }\n </div>\n }\n @if (field.type === 'number') {\n <div class=\"validators-row\">\n <div class=\"validator-field\">\n <label>Minimum</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.minimum ?? ''\"\n (input)=\"onMinimumChange($any($event.target).value)\"\n placeholder=\"-\u221E\"\n />\n </div>\n <div class=\"validator-field\">\n <label>Maximum</label>\n <input\n type=\"number\"\n class=\"validator-input\"\n [value]=\"field.maximum ?? ''\"\n (input)=\"onMaximumChange($any($event.target).value)\"\n placeholder=\"\u221E\"\n />\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Nested Children -->\n @if ((field.type === 'object' || field.type === 'array') && (localExpanded() || field.expanded)) {\n <div\n class=\"nested-fields\"\n cdkDropList\n [cdkDropListData]=\"field.children || []\"\n (cdkDropListDropped)=\"onFieldDrop($event)\"\n >\n @if (field.children && field.children.length > 0) {\n @for (child of field.children; track child.id) {\n <field-item\n cdkDrag\n [field]=\"child\"\n [parentList]=\"field.children\"\n [level]=\"level + 1\"\n [showDisplayType]=\"showDisplayType\"\n (fieldChange)=\"onChildFieldChange()\"\n (delete)=\"onChildDelete($event)\"\n (duplicate)=\"onChildDuplicate($event)\"\n (outdent)=\"onChildOutdent($event)\"\n >\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(child.type) }}</mat-icon>\n {{ child.name || 'unnamed' }}\n </div>\n </field-item>\n }\n } @else {\n <div class=\"empty-nested\">\n <span>{{ getEmptyMessage() }}</span>\n <button mat-button (click)=\"addChildField()\" color=\"primary\">\n <mat-icon>add</mat-icon>\n {{ getAddButtonLabel() }}\n </button>\n </div>\n }\n </div>\n }\n</div>\n", styles: [".field-wrapper{display:block;width:100%;max-width:100%;box-sizing:border-box;overflow:hidden;margin-bottom:4px}.field-wrapper ::ng-deep .mat-mdc-icon-button{width:32px!important;height:32px!important;padding:4px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;flex-shrink:0!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-touch-target{width:32px!important;height:32px!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.field-wrapper ::ng-deep .mat-mdc-icon-button mat-icon,.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-icon{font-size:20px!important;width:20px!important;height:20px!important;line-height:20px!important;overflow:hidden!important}.field-item{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#f8fafc;border-radius:8px;border:1px solid transparent;transition:all .15s ease;box-sizing:border-box}.field-item:hover{background:#f1f5f9;border-color:#e2e8f0}.field-item:focus-within{background:#eff6ff;border-color:#3b82f6}.field-item.is-complex{background:#fefce8}.field-item.is-complex:hover{background:#fef9c3}.field-item.is-complex:focus-within{background:#fef3c7;border-color:#3b82f6}.field-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.field-right{display:flex;align-items:center;gap:8px;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}.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:background-color .15s ease,color .15s ease}.expand-btn:hover{background:#e2e8f0;color:#1e293b}.expand-btn mat-icon{font-size:20px;width:20px;height:20px;transition:transform .15s ease}.expand-placeholder{width:28px;flex-shrink:0}.type-icon{font-size:18px;width:18px;height:18px;color:#64748b;flex-shrink:0}.array-indicator{color:#f59e0b;font-weight:600;font-size:14px;margin-left:2px}.field-name-input{flex:0 0 auto;width:120px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;transition:all .15s ease}.field-name-input:focus{border-color:#3b82f6;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}.label-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;transition:all .15s ease}.label-input:focus{border-color:#3b82f6;background:#fff}.label-input::placeholder{color:#94a3b8;font-style:italic}.type-selector,.display-type-selector{width:130px;flex-shrink:0;margin-right:4px}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper,.display-type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.type-selector ::ng-deep .mdc-text-field--outlined,.display-type-selector ::ng-deep .mdc-text-field--outlined{height:32px!important}.type-selector ::ng-deep .mat-mdc-text-field-wrapper,.display-type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important;height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-flex,.display-type-selector ::ng-deep .mat-mdc-form-field-flex{height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix,.display-type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important;height:28px!important}.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#e2e8f0!important}.type-selector ::ng-deep .mat-mdc-select,.display-type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text,.display-type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper,.display-type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.display-type-selector{width:120px}.display-type-placeholder{width:120px;flex-shrink:0;margin-right:4px}::ng-deep .schema-editor-select-panel .mat-mdc-option{font-size:12px!important;min-height:36px!important;padding:0 12px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:12px!important;display:flex!important;align-items:center!important;gap:8px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option mat-icon{font-size:16px!important;width:16px!important;height:16px!important;margin-right:0!important;color:#64748b}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:2px;width:168px;flex-shrink:0;margin-left:4px;opacity:0;transition:opacity .15s ease}.field-item:hover .field-actions,.field-right:hover .field-actions{opacity:1}.field-actions button[mat-icon-button]{width:32px;height:32px;padding:4px;line-height:1;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.field-actions button[mat-icon-button] mat-icon{font-size:20px;width:20px;height:20px;line-height:20px}.field-actions button{color:#64748b}.field-actions button:hover{color:#1e293b}.field-actions .has-values,.field-actions .has-default,.field-actions .has-validators{color:#22c55e!important}.delete-action{color:#ef4444!important}.delete-action mat-icon{color:#ef4444}.allowed-values-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;box-sizing:border-box}.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}.default-value-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px;box-sizing:border-box}.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}.validators-editor{margin:12px 12px 8px 48px;padding:12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;box-sizing:border-box;overflow:hidden}.validators-editor .validators-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.validators-editor .validators-label{font-size:12px;font-weight:500;color:#1d4ed8}.validators-editor .validators-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px;overflow:hidden;width:100%;box-sizing:border-box}.validators-editor .validators-row:last-child{margin-bottom:0}.validators-editor .validator-field{display:flex;flex-direction:column;gap:4px;flex-shrink:0;box-sizing:border-box}.validators-editor .validator-field label{font-size:11px;font-weight:500;color:#3b82f6}.validators-editor .validator-field.pattern-field{flex:1 1 auto;min-width:80px;overflow:hidden}.validators-editor .validator-input,.validators-editor .validator-select{padding:6px 10px;font-size:13px;border:1px solid #93c5fd;border-radius:4px;outline:none;background:#fff;width:100px;box-sizing:border-box!important}.validators-editor .validator-input:focus,.validators-editor .validator-select:focus{border-color:#3b82f6}.validators-editor .validator-input::placeholder,.validators-editor .validator-select::placeholder{color:#94a3b8}.validators-editor .validator-select{cursor:pointer;width:120px}.validators-editor .pattern-field .validator-input{width:100%;box-sizing:border-box!important}.nested-fields{display:block;min-height:30px;margin-top:8px;margin-bottom:8px;padding-left:32px;box-sizing:border-box}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor,.nested-fields .validators-editor{margin-left:16px;margin-top: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}.drag-placeholder{background:#e2e8f0;border:2px dashed #3b82f6;border-radius:8px;min-height:48px;margin-bottom:4px}::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}.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)}\n"] }]
|
|
3364
|
-
}], propDecorators: { field: [{
|
|
3365
|
-
type: Input
|
|
3366
|
-
}], parentList: [{
|
|
3367
|
-
type: Input
|
|
3368
|
-
}], level: [{
|
|
3369
|
-
type: Input
|
|
3370
|
-
}], showDisplayType: [{
|
|
3371
|
-
type: Input
|
|
3372
|
-
}], fieldChange: [{
|
|
3373
|
-
type: Output
|
|
3374
|
-
}], delete: [{
|
|
3375
|
-
type: Output
|
|
3376
|
-
}], duplicate: [{
|
|
3377
|
-
type: Output
|
|
3378
|
-
}], outdent: [{
|
|
3379
|
-
type: Output
|
|
3380
|
-
}] } });
|
|
3381
|
-
|
|
3382
|
-
class SchemaEditorComponent {
|
|
3383
|
-
appRef = inject(ApplicationRef);
|
|
3384
|
-
set schema(value) {
|
|
3385
|
-
if (value) {
|
|
3386
|
-
// Don't overwrite fields if we have uncommitted changes (fields with editors open or empty names)
|
|
3387
|
-
const hasUncommittedChanges = this.fields().some(f => f.isEditingDefault || f.isEditingValues || f.isEditingValidators || !f.name) || this.hasUncommittedChildFields(this.fields());
|
|
3388
|
-
this.schemaName.set(value.title || 'New Schema');
|
|
3389
|
-
if (!hasUncommittedChanges) {
|
|
3390
|
-
this.fields.set(this.jsonSchemaToEditorFields(value));
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
hasUncommittedChildFields(fields) {
|
|
3395
|
-
for (const field of fields) {
|
|
3396
|
-
if (field.children) {
|
|
3397
|
-
if (field.children.some(c => c.isEditingDefault || c.isEditingValues || c.isEditingValidators || !c.name)) {
|
|
3398
|
-
return true;
|
|
3399
|
-
}
|
|
3400
|
-
if (this.hasUncommittedChildFields(field.children)) {
|
|
3401
|
-
return true;
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
return false;
|
|
3406
|
-
}
|
|
3407
|
-
showJsonToggle = true;
|
|
3408
|
-
showSchemaName = true;
|
|
3409
|
-
showDisplayType = false;
|
|
3410
|
-
schemaChange = new EventEmitter();
|
|
3411
|
-
save = new EventEmitter();
|
|
3412
|
-
schemaName = signal('New Schema', ...(ngDevMode ? [{ debugName: "schemaName" }] : []));
|
|
3413
|
-
fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
|
|
3414
|
-
// View mode: 'visual' or 'json'
|
|
3415
|
-
viewMode = signal('visual', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
|
|
3416
|
-
jsonText = signal('', ...(ngDevMode ? [{ debugName: "jsonText" }] : []));
|
|
3417
|
-
jsonError = signal(null, ...(ngDevMode ? [{ debugName: "jsonError" }] : []));
|
|
3418
|
-
fieldTypes = [
|
|
3419
|
-
{ value: 'string', label: 'String', icon: 'text_fields' },
|
|
3420
|
-
{ value: 'number', label: 'Number', icon: 'pin' },
|
|
3421
|
-
{ value: 'boolean', label: 'Boolean', icon: 'toggle_on' },
|
|
3422
|
-
{ value: 'date', label: 'Date', icon: 'calendar_today' },
|
|
3423
|
-
{ value: 'time', label: 'Time', icon: 'schedule' },
|
|
3424
|
-
{ value: 'object', label: 'Object', icon: 'data_object' },
|
|
3425
|
-
{ value: 'array', label: 'Array', icon: 'data_array' },
|
|
3426
|
-
];
|
|
3427
|
-
stringDisplayTypes = [
|
|
3428
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
3429
|
-
{ value: 'dropdown', label: 'Dropdown', icon: 'arrow_drop_down_circle' },
|
|
3430
|
-
{ value: 'textarea', label: 'Textarea', icon: 'notes' },
|
|
3431
|
-
{ value: 'richtext', label: 'Rich Text', icon: 'format_color_text' },
|
|
3432
|
-
];
|
|
3433
|
-
dateDisplayTypes = [
|
|
3434
|
-
{ value: 'datepicker', label: 'Date Picker', icon: 'calendar_today' },
|
|
3435
|
-
{ value: 'datetimepicker', label: 'DateTime Picker', icon: 'schedule' },
|
|
3436
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
3437
|
-
];
|
|
3438
|
-
timeDisplayTypes = [
|
|
3439
|
-
{ value: 'timepicker', label: 'Time Picker', icon: 'schedule' },
|
|
3440
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
3441
|
-
];
|
|
3442
|
-
numberDisplayTypes = [
|
|
3443
|
-
{ value: 'textbox', label: 'Textbox', icon: 'edit' },
|
|
3444
|
-
{ value: 'stepper', label: 'Stepper', icon: 'unfold_more' },
|
|
3445
|
-
];
|
|
3446
|
-
booleanDisplayTypes = [
|
|
3447
|
-
{ value: 'checkbox', label: 'Checkbox', icon: 'check_box' },
|
|
3448
|
-
{ value: 'toggle', label: 'Toggle', icon: 'toggle_on' },
|
|
3449
|
-
];
|
|
3450
|
-
stringFormats = [
|
|
3451
|
-
{ value: '', label: '(none)' },
|
|
3452
|
-
{ value: 'email', label: 'Email' },
|
|
3453
|
-
{ value: 'uri', label: 'URI (URL)' },
|
|
3454
|
-
{ value: 'uuid', label: 'UUID' },
|
|
3455
|
-
];
|
|
3456
|
-
getDisplayTypes(fieldType) {
|
|
3457
|
-
if (fieldType === 'date')
|
|
3458
|
-
return this.dateDisplayTypes;
|
|
3459
|
-
if (fieldType === 'time')
|
|
3460
|
-
return this.timeDisplayTypes;
|
|
3461
|
-
if (fieldType === 'number')
|
|
3462
|
-
return this.numberDisplayTypes;
|
|
3463
|
-
if (fieldType === 'boolean')
|
|
3464
|
-
return this.booleanDisplayTypes;
|
|
3465
|
-
return this.stringDisplayTypes;
|
|
3466
|
-
}
|
|
3467
|
-
generateId() {
|
|
3468
|
-
return `field-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3469
|
-
}
|
|
3470
|
-
cloneFields(fields) {
|
|
3471
|
-
return fields.map(f => ({
|
|
3472
|
-
...f,
|
|
3473
|
-
children: f.children ? this.cloneFields(f.children) : undefined,
|
|
3474
|
-
}));
|
|
3475
|
-
}
|
|
3476
|
-
getTypeIcon(type) {
|
|
3477
|
-
return this.fieldTypes.find(t => t.value === type)?.icon || 'help_outline';
|
|
3478
|
-
}
|
|
3479
|
-
// Add a new field at the root level
|
|
3480
|
-
addField() {
|
|
3481
|
-
const newField = {
|
|
3482
|
-
id: this.generateId(),
|
|
3483
|
-
name: '',
|
|
3484
|
-
type: 'string',
|
|
3485
|
-
displayType: 'textbox',
|
|
3486
|
-
expanded: false,
|
|
3487
|
-
};
|
|
3488
|
-
this.fields.update(fields => [...fields, newField]);
|
|
3489
|
-
}
|
|
3490
|
-
// Add a child field to an object or array
|
|
3491
|
-
addChildField(parent) {
|
|
3492
|
-
if (!parent.children) {
|
|
3493
|
-
parent.children = [];
|
|
3494
|
-
}
|
|
3495
|
-
const newField = {
|
|
3496
|
-
id: this.generateId(),
|
|
3497
|
-
name: '',
|
|
3498
|
-
type: 'string',
|
|
3499
|
-
displayType: 'textbox',
|
|
3500
|
-
};
|
|
3501
|
-
parent.children.push(newField);
|
|
3502
|
-
parent.expanded = true;
|
|
3503
|
-
this.fields.update(fields => [...fields]);
|
|
3504
|
-
}
|
|
3505
|
-
// Handle field change from FieldItemComponent
|
|
3506
|
-
onFieldChange() {
|
|
3507
|
-
// Just emit the change - field objects are mutated in place
|
|
3508
|
-
// Don't recreate array or force tick as it causes expanded state to reset
|
|
3509
|
-
this.emitChange();
|
|
3510
|
-
}
|
|
3511
|
-
// Handle field delete from FieldItemComponent
|
|
3512
|
-
onFieldDelete(field) {
|
|
3513
|
-
const index = this.fields().indexOf(field);
|
|
3514
|
-
if (index > -1) {
|
|
3515
|
-
this.fields.update(fields => {
|
|
3516
|
-
const newFields = [...fields];
|
|
3517
|
-
newFields.splice(index, 1);
|
|
3518
|
-
return newFields;
|
|
3519
|
-
});
|
|
3520
|
-
this.emitChange();
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
// Handle field duplicate from FieldItemComponent
|
|
3524
|
-
onFieldDuplicate(field) {
|
|
3525
|
-
const index = this.fields().indexOf(field);
|
|
3526
|
-
if (index > -1) {
|
|
3527
|
-
const clone = {
|
|
3528
|
-
...field,
|
|
3529
|
-
id: this.generateId(),
|
|
3530
|
-
name: field.name + '_copy',
|
|
3531
|
-
children: field.children ? this.cloneFields(field.children) : undefined,
|
|
3532
|
-
};
|
|
3533
|
-
this.fields.update(fields => {
|
|
3534
|
-
const newFields = [...fields];
|
|
3535
|
-
newFields.splice(index + 1, 0, clone);
|
|
3536
|
-
return newFields;
|
|
3537
|
-
});
|
|
3538
|
-
this.emitChange();
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
// Delete a field
|
|
3542
|
-
deleteField(field, parentList) {
|
|
3543
|
-
const index = parentList.indexOf(field);
|
|
3544
|
-
if (index > -1) {
|
|
3545
|
-
parentList.splice(index, 1);
|
|
3546
|
-
this.fields.update(fields => [...fields]);
|
|
3547
|
-
this.emitChange();
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
// Duplicate a field
|
|
3551
|
-
duplicateField(field, parentList) {
|
|
3552
|
-
const index = parentList.indexOf(field);
|
|
3553
|
-
if (index > -1) {
|
|
3554
|
-
const clone = {
|
|
3555
|
-
...field,
|
|
3556
|
-
id: this.generateId(),
|
|
3557
|
-
name: field.name + '_copy',
|
|
3558
|
-
children: field.children ? this.cloneFields(field.children) : undefined,
|
|
3559
|
-
};
|
|
3560
|
-
parentList.splice(index + 1, 0, clone);
|
|
3561
|
-
this.fields.update(fields => [...fields]);
|
|
3562
|
-
this.emitChange();
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
// Toggle field expansion
|
|
3566
|
-
toggleExpand(field) {
|
|
3567
|
-
field.expanded = !field.expanded;
|
|
3568
|
-
this.fields.update(fields => [...fields]);
|
|
3569
|
-
}
|
|
3570
|
-
// Handle field name input - only allow valid property name characters
|
|
3571
|
-
onFieldNameChange(field, event) {
|
|
3572
|
-
const input = event.target;
|
|
3573
|
-
// Remove invalid characters: only allow letters, numbers, underscores, and dollar signs
|
|
3574
|
-
// Property names should start with letter, underscore, or dollar sign
|
|
3575
|
-
const sanitized = input.value.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
3576
|
-
field.name = sanitized;
|
|
3577
|
-
// Update input value if it was sanitized
|
|
3578
|
-
if (input.value !== sanitized) {
|
|
3579
|
-
input.value = sanitized;
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
// Handle field name blur - ensure name is valid and emit change
|
|
3583
|
-
onFieldNameBlur(field) {
|
|
3584
|
-
if (!field.name.trim()) {
|
|
3585
|
-
field.name = 'unnamed';
|
|
3586
|
-
}
|
|
3587
|
-
this.fields.update(fields => [...fields]);
|
|
3588
|
-
this.emitChange();
|
|
3589
|
-
}
|
|
3590
|
-
// Handle field type change
|
|
3591
|
-
onFieldTypeChange(field, type) {
|
|
3592
|
-
field.type = type;
|
|
3593
|
-
// Initialize children array for object/array types
|
|
3594
|
-
if ((type === 'object' || type === 'array') && !field.children) {
|
|
3595
|
-
field.children = [];
|
|
3596
|
-
}
|
|
3597
|
-
// Set default display type based on field type
|
|
3598
|
-
if (type === 'string') {
|
|
3599
|
-
field.displayType = 'textbox';
|
|
3600
|
-
}
|
|
3601
|
-
else if (type === 'number') {
|
|
3602
|
-
field.displayType = 'textbox';
|
|
3603
|
-
}
|
|
3604
|
-
else if (type === 'boolean') {
|
|
3605
|
-
field.displayType = 'checkbox';
|
|
3606
|
-
}
|
|
3607
|
-
else if (type === 'date') {
|
|
3608
|
-
field.displayType = 'datepicker';
|
|
3609
|
-
}
|
|
3610
|
-
else if (type === 'time') {
|
|
3611
|
-
field.displayType = 'timepicker';
|
|
3612
|
-
}
|
|
3613
|
-
else {
|
|
3614
|
-
field.displayType = undefined;
|
|
3615
|
-
}
|
|
3616
|
-
this.fields.update(fields => [...fields]);
|
|
3617
|
-
this.emitChange();
|
|
3618
|
-
}
|
|
3619
|
-
// Handle display type change
|
|
3620
|
-
onDisplayTypeChange(field, displayType) {
|
|
3621
|
-
field.displayType = displayType;
|
|
3622
|
-
this.fields.update(fields => [...fields]);
|
|
3623
|
-
this.emitChange();
|
|
3624
|
-
}
|
|
3625
|
-
// Toggle required status
|
|
3626
|
-
toggleRequired(field) {
|
|
3627
|
-
field.required = !field.required;
|
|
3628
|
-
this.fields.update(fields => [...fields]);
|
|
3629
|
-
this.emitChange();
|
|
3630
|
-
}
|
|
3631
|
-
// Update field label (only update the field, don't trigger re-render)
|
|
3632
|
-
onLabelChange(field, label) {
|
|
3633
|
-
field.label = label;
|
|
3634
|
-
}
|
|
3635
|
-
// Emit change when label input loses focus
|
|
3636
|
-
onLabelBlur() {
|
|
3637
|
-
this.emitChange();
|
|
3638
|
-
}
|
|
3639
|
-
// Toggle allowed values editor
|
|
3640
|
-
toggleValuesEditor(field) {
|
|
3641
|
-
field.isEditingValues = !field.isEditingValues;
|
|
3642
|
-
if (field.isEditingValues) {
|
|
3643
|
-
// Close other editors
|
|
3644
|
-
field.isEditingDefault = false;
|
|
3645
|
-
field.isEditingValidators = false;
|
|
3646
|
-
if (!field.allowedValues) {
|
|
3647
|
-
field.allowedValues = [];
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
this.fields.update(fields => [...fields]);
|
|
3651
|
-
}
|
|
3652
|
-
// Add allowed value
|
|
3653
|
-
addAllowedValue(field, input) {
|
|
3654
|
-
// Handle both direct input reference and event from button click
|
|
3655
|
-
let inputEl = null;
|
|
3656
|
-
if (input instanceof HTMLInputElement) {
|
|
3657
|
-
inputEl = input;
|
|
3658
|
-
}
|
|
3659
|
-
else {
|
|
3660
|
-
// Find the input by traversing up to .values-header and then querying for .value-input
|
|
3661
|
-
const target = input.target;
|
|
3662
|
-
const header = target.closest('.values-header');
|
|
3663
|
-
inputEl = header?.querySelector('.value-input');
|
|
3664
|
-
}
|
|
3665
|
-
if (!inputEl)
|
|
3666
|
-
return;
|
|
3667
|
-
const value = inputEl.value.trim();
|
|
3668
|
-
if (value && !field.allowedValues?.includes(value)) {
|
|
3669
|
-
if (!field.allowedValues) {
|
|
3670
|
-
field.allowedValues = [];
|
|
3671
|
-
}
|
|
3672
|
-
field.allowedValues.push(value);
|
|
3673
|
-
inputEl.value = '';
|
|
3674
|
-
this.fields.update(fields => [...fields]);
|
|
3675
|
-
this.emitChange();
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
// Remove allowed value
|
|
3679
|
-
removeAllowedValue(field, index) {
|
|
3680
|
-
if (field.allowedValues) {
|
|
3681
|
-
field.allowedValues.splice(index, 1);
|
|
3682
|
-
if (field.allowedValues.length === 0) {
|
|
3683
|
-
field.allowedValues = undefined;
|
|
3684
|
-
}
|
|
3685
|
-
this.fields.update(fields => [...fields]);
|
|
3686
|
-
this.emitChange();
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
// Handle Enter key in allowed value input
|
|
3690
|
-
onAllowedValueKeydown(event, field, input) {
|
|
3691
|
-
if (event.key === 'Enter') {
|
|
3692
|
-
event.preventDefault();
|
|
3693
|
-
this.addAllowedValue(field, input);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
// Toggle default value editor
|
|
3697
|
-
toggleDefaultEditor(field) {
|
|
3698
|
-
field.isEditingDefault = !field.isEditingDefault;
|
|
3699
|
-
if (field.isEditingDefault) {
|
|
3700
|
-
// Close other editors
|
|
3701
|
-
field.isEditingValues = false;
|
|
3702
|
-
field.isEditingValidators = false;
|
|
3703
|
-
}
|
|
3704
|
-
this.fields.update(fields => [...fields]);
|
|
3705
|
-
}
|
|
3706
|
-
// Update default value
|
|
3707
|
-
onDefaultValueChange(field, value) {
|
|
3708
|
-
if (value === '') {
|
|
3709
|
-
field.defaultValue = undefined;
|
|
3710
|
-
}
|
|
3711
|
-
else if (field.type === 'number') {
|
|
3712
|
-
const num = parseFloat(value);
|
|
3713
|
-
field.defaultValue = isNaN(num) ? undefined : num;
|
|
3714
|
-
}
|
|
3715
|
-
else if (field.type === 'boolean') {
|
|
3716
|
-
field.defaultValue = value === 'true';
|
|
3717
|
-
}
|
|
3718
|
-
else {
|
|
3719
|
-
field.defaultValue = value;
|
|
3720
|
-
}
|
|
3721
|
-
this.fields.update(fields => [...fields]);
|
|
3722
|
-
this.emitChange();
|
|
3723
|
-
}
|
|
3724
|
-
// Clear default value
|
|
3725
|
-
clearDefaultValue(field) {
|
|
3726
|
-
field.defaultValue = undefined;
|
|
3727
|
-
field.isEditingDefault = false;
|
|
3728
|
-
this.fields.update(fields => [...fields]);
|
|
3729
|
-
this.emitChange();
|
|
3730
|
-
}
|
|
3731
|
-
// Handle Enter key in default value input
|
|
3732
|
-
onDefaultValueKeydown(event, field) {
|
|
3733
|
-
if (event.key === 'Enter' || event.key === 'Escape') {
|
|
3734
|
-
field.isEditingDefault = false;
|
|
3735
|
-
this.fields.update(fields => [...fields]);
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
// Toggle validators editor
|
|
3739
|
-
toggleValidatorsEditor(field) {
|
|
3740
|
-
field.isEditingValidators = !field.isEditingValidators;
|
|
3741
|
-
if (field.isEditingValidators) {
|
|
3742
|
-
// Close other editors
|
|
3743
|
-
field.isEditingValues = false;
|
|
3744
|
-
field.isEditingDefault = false;
|
|
3745
|
-
}
|
|
3746
|
-
this.fields.update(fields => [...fields]);
|
|
3747
|
-
}
|
|
3748
|
-
// Check if field has any validators set
|
|
3749
|
-
hasValidators(field) {
|
|
3750
|
-
if (field.type === 'string') {
|
|
3751
|
-
return !!(field.minLength || field.maxLength || field.pattern || field.format);
|
|
3752
|
-
}
|
|
3753
|
-
if (field.type === 'number') {
|
|
3754
|
-
return field.minimum !== undefined || field.maximum !== undefined;
|
|
3755
|
-
}
|
|
3756
|
-
return false;
|
|
3757
|
-
}
|
|
3758
|
-
// Update string format
|
|
3759
|
-
onFormatChange(field, format) {
|
|
3760
|
-
field.format = format || undefined;
|
|
3761
|
-
// Clear pattern when format is set (they're mutually exclusive)
|
|
3762
|
-
if (format) {
|
|
3763
|
-
field.pattern = undefined;
|
|
3764
|
-
}
|
|
3765
|
-
this.fields.update(fields => [...fields]);
|
|
3766
|
-
this.emitChange();
|
|
3767
|
-
}
|
|
3768
|
-
// Update minLength
|
|
3769
|
-
onMinLengthChange(field, value) {
|
|
3770
|
-
field.minLength = value ? parseInt(value, 10) : undefined;
|
|
3771
|
-
this.fields.update(fields => [...fields]);
|
|
3772
|
-
this.emitChange();
|
|
3773
|
-
}
|
|
3774
|
-
// Update maxLength
|
|
3775
|
-
onMaxLengthChange(field, value) {
|
|
3776
|
-
field.maxLength = value ? parseInt(value, 10) : undefined;
|
|
3777
|
-
this.fields.update(fields => [...fields]);
|
|
3778
|
-
this.emitChange();
|
|
3779
|
-
}
|
|
3780
|
-
// Update pattern
|
|
3781
|
-
onPatternChange(field, value) {
|
|
3782
|
-
field.pattern = value || undefined;
|
|
3783
|
-
this.fields.update(fields => [...fields]);
|
|
3784
|
-
this.emitChange();
|
|
3785
|
-
}
|
|
3786
|
-
// Update minimum
|
|
3787
|
-
onMinimumChange(field, value) {
|
|
3788
|
-
field.minimum = value ? parseFloat(value) : undefined;
|
|
3789
|
-
this.fields.update(fields => [...fields]);
|
|
3790
|
-
this.emitChange();
|
|
3791
|
-
}
|
|
3792
|
-
// Update maximum
|
|
3793
|
-
onMaximumChange(field, value) {
|
|
3794
|
-
field.maximum = value ? parseFloat(value) : undefined;
|
|
3795
|
-
this.fields.update(fields => [...fields]);
|
|
3796
|
-
this.emitChange();
|
|
3797
|
-
}
|
|
3798
|
-
// Move field up in list
|
|
3799
|
-
moveFieldUp(field, parentList) {
|
|
3800
|
-
const index = parentList.indexOf(field);
|
|
3801
|
-
if (index > 0) {
|
|
3802
|
-
[parentList[index - 1], parentList[index]] = [parentList[index], parentList[index - 1]];
|
|
3803
|
-
this.fields.update(fields => [...fields]);
|
|
3804
|
-
this.emitChange();
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
// Move field down in list
|
|
3808
|
-
moveFieldDown(field, parentList) {
|
|
3809
|
-
const index = parentList.indexOf(field);
|
|
3810
|
-
if (index < parentList.length - 1) {
|
|
3811
|
-
[parentList[index], parentList[index + 1]] = [parentList[index + 1], parentList[index]];
|
|
3812
|
-
this.fields.update(fields => [...fields]);
|
|
3813
|
-
this.emitChange();
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
// Handle drag and drop reorder
|
|
3817
|
-
onFieldDrop(event) {
|
|
3818
|
-
if (event.previousIndex !== event.currentIndex) {
|
|
3819
|
-
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
|
|
3820
|
-
this.fields.update(fields => [...fields]);
|
|
3821
|
-
// Don't emit change here - it causes parent to reset expanded state
|
|
3822
|
-
// Order change will be captured on next edit or save
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
// Check if field can be indented (previous sibling must be object/array)
|
|
3826
|
-
canIndent(field, parentList) {
|
|
3827
|
-
const index = parentList.indexOf(field);
|
|
3828
|
-
if (index <= 0)
|
|
3829
|
-
return false;
|
|
3830
|
-
const prevSibling = parentList[index - 1];
|
|
3831
|
-
return prevSibling.type === 'object' || prevSibling.type === 'array';
|
|
3832
|
-
}
|
|
3833
|
-
// Indent field - move into previous sibling's children
|
|
3834
|
-
indentField(field, parentList) {
|
|
3835
|
-
const index = parentList.indexOf(field);
|
|
3836
|
-
if (index <= 0)
|
|
3837
|
-
return;
|
|
3838
|
-
const prevSibling = parentList[index - 1];
|
|
3839
|
-
if (prevSibling.type !== 'object' && prevSibling.type !== 'array')
|
|
3840
|
-
return;
|
|
3841
|
-
// Remove from current list
|
|
3842
|
-
parentList.splice(index, 1);
|
|
3843
|
-
// Add to previous sibling's children
|
|
3844
|
-
if (!prevSibling.children) {
|
|
3845
|
-
prevSibling.children = [];
|
|
3846
|
-
}
|
|
3847
|
-
prevSibling.children.push(field);
|
|
3848
|
-
// Always expand the target (keep open if already open, open if closed)
|
|
3849
|
-
prevSibling.expanded = true;
|
|
3850
|
-
this.fields.update(fields => [...fields]);
|
|
3851
|
-
// Don't emit change here - it causes parent to reset expanded state
|
|
3852
|
-
// Structure change will be captured on next edit or save
|
|
3853
|
-
}
|
|
3854
|
-
// Outdent field - move out of parent to grandparent level
|
|
3855
|
-
outdentField(field, parentList, level) {
|
|
3856
|
-
if (level === 0)
|
|
3857
|
-
return;
|
|
3858
|
-
// Find the parent object/array that contains this list
|
|
3859
|
-
const parent = this.findParentOfList(this.fields(), parentList);
|
|
3860
|
-
if (!parent)
|
|
3861
|
-
return;
|
|
3862
|
-
// Find the grandparent list
|
|
3863
|
-
const grandparentList = this.findParentList(this.fields(), parent);
|
|
3864
|
-
if (!grandparentList)
|
|
3865
|
-
return;
|
|
3866
|
-
// Remove from current list
|
|
3867
|
-
const index = parentList.indexOf(field);
|
|
3868
|
-
parentList.splice(index, 1);
|
|
3869
|
-
// Add to grandparent list after the parent
|
|
3870
|
-
const parentIndex = grandparentList.indexOf(parent);
|
|
3871
|
-
grandparentList.splice(parentIndex + 1, 0, field);
|
|
3872
|
-
this.fields.update(fields => [...fields]);
|
|
3873
|
-
// Don't emit change here - it causes parent to reset expanded state
|
|
3874
|
-
// Structure change will be captured on next edit or save
|
|
3875
|
-
}
|
|
3876
|
-
// Find the parent field that contains a given list
|
|
3877
|
-
findParentOfList(searchIn, targetList) {
|
|
3878
|
-
for (const field of searchIn) {
|
|
3879
|
-
if (field.children === targetList) {
|
|
3880
|
-
return field;
|
|
3881
|
-
}
|
|
3882
|
-
if (field.children) {
|
|
3883
|
-
const found = this.findParentOfList(field.children, targetList);
|
|
3884
|
-
if (found)
|
|
3885
|
-
return found;
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
return null;
|
|
3889
|
-
}
|
|
3890
|
-
// Find the list that contains a given field
|
|
3891
|
-
findParentList(searchIn, targetField) {
|
|
3892
|
-
if (searchIn.includes(targetField)) {
|
|
3893
|
-
return searchIn;
|
|
3894
|
-
}
|
|
3895
|
-
for (const field of searchIn) {
|
|
3896
|
-
if (field.children) {
|
|
3897
|
-
const found = this.findParentList(field.children, targetField);
|
|
3898
|
-
if (found)
|
|
3899
|
-
return found;
|
|
3900
|
-
}
|
|
3901
|
-
}
|
|
3902
|
-
return null;
|
|
3903
|
-
}
|
|
3904
|
-
// Update schema name - only allow valid characters
|
|
3905
|
-
onSchemaNameChange(name, input) {
|
|
3906
|
-
const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
3907
|
-
this.schemaName.set(sanitized);
|
|
3908
|
-
if (input && input.value !== sanitized) {
|
|
3909
|
-
input.value = sanitized;
|
|
3910
|
-
}
|
|
3911
|
-
this.emitChange();
|
|
3912
|
-
}
|
|
3913
|
-
// Emit change event
|
|
3914
|
-
emitChange() {
|
|
3915
|
-
this.schemaChange.emit(this.toJsonSchema());
|
|
3916
|
-
}
|
|
3917
|
-
// Save the schema
|
|
3918
|
-
onSave() {
|
|
3919
|
-
this.save.emit(this.toJsonSchema());
|
|
3920
|
-
}
|
|
3921
|
-
// Convert JSON Schema to internal EditorField format
|
|
3922
|
-
jsonSchemaToEditorFields(schema, requiredFields = []) {
|
|
3923
|
-
const fields = [];
|
|
3924
|
-
if (schema.type === 'object' && schema.properties) {
|
|
3925
|
-
const required = schema.required || requiredFields;
|
|
3926
|
-
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
3927
|
-
fields.push(this.jsonSchemaPropertyToEditorField(name, propSchema, required.includes(name)));
|
|
3928
|
-
}
|
|
3929
|
-
}
|
|
3930
|
-
return fields;
|
|
3931
|
-
}
|
|
3932
|
-
jsonSchemaPropertyToEditorField(name, schema, isRequired) {
|
|
3933
|
-
const fieldType = this.jsonSchemaTypeToEditorType(schema);
|
|
3934
|
-
// Set displayType for string, number, boolean, date, and time fields
|
|
3935
|
-
let displayType;
|
|
3936
|
-
if (fieldType === 'string') {
|
|
3937
|
-
displayType = schema['x-display-type'] || 'textbox';
|
|
3938
|
-
}
|
|
3939
|
-
else if (fieldType === 'number') {
|
|
3940
|
-
displayType = schema['x-display-type'] || 'textbox';
|
|
3941
|
-
}
|
|
3942
|
-
else if (fieldType === 'boolean') {
|
|
3943
|
-
displayType = schema['x-display-type'] || 'checkbox';
|
|
3944
|
-
}
|
|
3945
|
-
else if (fieldType === 'date') {
|
|
3946
|
-
displayType = schema['x-display-type'] || 'datepicker';
|
|
3947
|
-
}
|
|
3948
|
-
else if (fieldType === 'time') {
|
|
3949
|
-
displayType = schema['x-display-type'] || 'timepicker';
|
|
3950
|
-
}
|
|
3951
|
-
// Preserve format for string types and date types
|
|
3952
|
-
let format;
|
|
3953
|
-
if (fieldType === 'string' && schema.format) {
|
|
3954
|
-
format = schema.format;
|
|
3955
|
-
}
|
|
3956
|
-
else if (fieldType === 'date' && schema.format) {
|
|
3957
|
-
format = schema.format; // Preserve 'date' vs 'date-time'
|
|
3958
|
-
}
|
|
3959
|
-
const field = {
|
|
3960
|
-
id: this.generateId(),
|
|
3961
|
-
name,
|
|
3962
|
-
type: fieldType,
|
|
3963
|
-
format,
|
|
3964
|
-
displayType,
|
|
3965
|
-
label: schema.title,
|
|
3966
|
-
required: isRequired,
|
|
3967
|
-
allowedValues: schema.enum,
|
|
3968
|
-
defaultValue: schema.default,
|
|
3969
|
-
// Validators
|
|
3970
|
-
minLength: schema.minLength,
|
|
3971
|
-
maxLength: schema.maxLength,
|
|
3972
|
-
pattern: schema.pattern,
|
|
3973
|
-
minimum: schema.minimum,
|
|
3974
|
-
maximum: schema.maximum,
|
|
3975
|
-
expanded: false,
|
|
3976
|
-
};
|
|
3977
|
-
if (schema.type === 'object' && schema.properties) {
|
|
3978
|
-
field.children = this.jsonSchemaToEditorFields(schema, schema.required);
|
|
3979
|
-
}
|
|
3980
|
-
else if (schema.type === 'array' && schema.items) {
|
|
3981
|
-
if (schema.items.type === 'object' && schema.items.properties) {
|
|
3982
|
-
field.children = this.jsonSchemaToEditorFields(schema.items, schema.items.required);
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
return field;
|
|
3986
|
-
}
|
|
3987
|
-
jsonSchemaTypeToEditorType(schema) {
|
|
3988
|
-
if (schema.format === 'date' || schema.format === 'date-time') {
|
|
3989
|
-
return 'date';
|
|
3990
|
-
}
|
|
3991
|
-
if (schema.format === 'time') {
|
|
3992
|
-
return 'time';
|
|
3993
|
-
}
|
|
3994
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
3995
|
-
switch (type) {
|
|
3996
|
-
case 'string': return 'string';
|
|
3997
|
-
case 'number':
|
|
3998
|
-
case 'integer': return 'number';
|
|
3999
|
-
case 'boolean': return 'boolean';
|
|
4000
|
-
case 'object': return 'object';
|
|
4001
|
-
case 'array': return 'array';
|
|
4002
|
-
default: return 'string';
|
|
4003
|
-
}
|
|
4004
|
-
}
|
|
4005
|
-
// Convert to internal JSON format
|
|
4006
|
-
toJson() {
|
|
4007
|
-
return JSON.stringify({
|
|
4008
|
-
name: this.schemaName(),
|
|
4009
|
-
fields: this.stripEditingState(this.fields()),
|
|
4010
|
-
}, null, 2);
|
|
4011
|
-
}
|
|
4012
|
-
// Convert to valid JSON Schema format
|
|
4013
|
-
toJsonSchema() {
|
|
4014
|
-
const required = [];
|
|
4015
|
-
const properties = {};
|
|
4016
|
-
for (const field of this.fields()) {
|
|
4017
|
-
if (field.required && field.name) {
|
|
4018
|
-
required.push(field.name);
|
|
4019
|
-
}
|
|
4020
|
-
if (field.name) {
|
|
4021
|
-
properties[field.name] = this.fieldToJsonSchema(field);
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
const schema = {
|
|
4025
|
-
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
4026
|
-
type: 'object',
|
|
4027
|
-
title: this.schemaName(),
|
|
4028
|
-
properties,
|
|
4029
|
-
};
|
|
4030
|
-
if (required.length > 0) {
|
|
4031
|
-
schema['required'] = required;
|
|
4032
|
-
}
|
|
4033
|
-
return schema;
|
|
4034
|
-
}
|
|
4035
|
-
fieldToJsonSchema(field) {
|
|
4036
|
-
const schema = {};
|
|
4037
|
-
// Map type
|
|
4038
|
-
if (field.type === 'date') {
|
|
4039
|
-
schema['type'] = 'string';
|
|
4040
|
-
schema['format'] = field.format || 'date-time'; // Use preserved format or default
|
|
4041
|
-
}
|
|
4042
|
-
else if (field.type === 'time') {
|
|
4043
|
-
schema['type'] = 'string';
|
|
4044
|
-
schema['format'] = 'time';
|
|
4045
|
-
}
|
|
4046
|
-
else if (field.type === 'array') {
|
|
4047
|
-
schema['type'] = 'array';
|
|
4048
|
-
if (field.children && field.children.length > 0) {
|
|
4049
|
-
// If array has children, treat first child as item schema
|
|
4050
|
-
const itemProperties = {};
|
|
4051
|
-
const itemRequired = [];
|
|
4052
|
-
for (const child of field.children) {
|
|
4053
|
-
if (child.name) {
|
|
4054
|
-
itemProperties[child.name] = this.fieldToJsonSchema(child);
|
|
4055
|
-
if (child.required) {
|
|
4056
|
-
itemRequired.push(child.name);
|
|
4057
|
-
}
|
|
4058
|
-
}
|
|
4059
|
-
}
|
|
4060
|
-
const items = {
|
|
4061
|
-
type: 'object',
|
|
4062
|
-
properties: itemProperties,
|
|
4063
|
-
};
|
|
4064
|
-
if (itemRequired.length > 0) {
|
|
4065
|
-
items['required'] = itemRequired;
|
|
4066
|
-
}
|
|
4067
|
-
schema['items'] = items;
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
else if (field.type === 'object') {
|
|
4071
|
-
schema['type'] = 'object';
|
|
4072
|
-
if (field.children && field.children.length > 0) {
|
|
4073
|
-
const childProperties = {};
|
|
4074
|
-
const childRequired = [];
|
|
4075
|
-
for (const child of field.children) {
|
|
4076
|
-
if (child.name) {
|
|
4077
|
-
childProperties[child.name] = this.fieldToJsonSchema(child);
|
|
4078
|
-
if (child.required) {
|
|
4079
|
-
childRequired.push(child.name);
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
schema['properties'] = childProperties;
|
|
4084
|
-
if (childRequired.length > 0) {
|
|
4085
|
-
schema['required'] = childRequired;
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
}
|
|
4089
|
-
else {
|
|
4090
|
-
schema['type'] = field.type;
|
|
4091
|
-
// Preserve format for string types (email, uri, uuid, etc.)
|
|
4092
|
-
if (field.type === 'string' && field.format) {
|
|
4093
|
-
schema['format'] = field.format;
|
|
4094
|
-
}
|
|
4095
|
-
}
|
|
4096
|
-
// Add label as title
|
|
4097
|
-
if (field.label) {
|
|
4098
|
-
schema['title'] = field.label;
|
|
4099
|
-
}
|
|
4100
|
-
// Add enum for allowed values
|
|
4101
|
-
if (field.allowedValues && field.allowedValues.length > 0) {
|
|
4102
|
-
schema['enum'] = field.allowedValues;
|
|
4103
|
-
}
|
|
4104
|
-
// Add default value
|
|
4105
|
-
if (field.defaultValue !== undefined) {
|
|
4106
|
-
schema['default'] = field.defaultValue;
|
|
4107
|
-
}
|
|
4108
|
-
// Add validators
|
|
4109
|
-
if (field.minLength !== undefined) {
|
|
4110
|
-
schema['minLength'] = field.minLength;
|
|
4111
|
-
}
|
|
4112
|
-
if (field.maxLength !== undefined) {
|
|
4113
|
-
schema['maxLength'] = field.maxLength;
|
|
4114
|
-
}
|
|
4115
|
-
if (field.pattern) {
|
|
4116
|
-
schema['pattern'] = field.pattern;
|
|
4117
|
-
}
|
|
4118
|
-
if (field.minimum !== undefined) {
|
|
4119
|
-
schema['minimum'] = field.minimum;
|
|
4120
|
-
}
|
|
4121
|
-
if (field.maximum !== undefined) {
|
|
4122
|
-
schema['maximum'] = field.maximum;
|
|
4123
|
-
}
|
|
4124
|
-
// Add display type (custom extension)
|
|
4125
|
-
if (field.displayType) {
|
|
4126
|
-
schema['x-display-type'] = field.displayType;
|
|
4127
|
-
}
|
|
4128
|
-
return schema;
|
|
4129
|
-
}
|
|
4130
|
-
stripEditingState(fields) {
|
|
4131
|
-
return fields.map(f => {
|
|
4132
|
-
const { isEditingValues, isEditingDefault, isEditingValidators, ...rest } = f;
|
|
4133
|
-
return {
|
|
4134
|
-
...rest,
|
|
4135
|
-
children: f.children ? this.stripEditingState(f.children) : undefined,
|
|
4136
|
-
};
|
|
4137
|
-
});
|
|
4138
|
-
}
|
|
4139
|
-
// Track by function for ngFor
|
|
4140
|
-
trackByFieldId(index, field) {
|
|
4141
|
-
return field.id;
|
|
4142
|
-
}
|
|
4143
|
-
// --- JSON View Methods ---
|
|
4144
|
-
setViewMode(mode) {
|
|
4145
|
-
if (mode === 'json') {
|
|
4146
|
-
// Sync JSON text from current schema state
|
|
4147
|
-
this.jsonText.set(JSON.stringify(this.toJsonSchema(), null, 2));
|
|
4148
|
-
this.jsonError.set(null);
|
|
4149
|
-
}
|
|
4150
|
-
this.viewMode.set(mode);
|
|
4151
|
-
}
|
|
4152
|
-
onJsonTextChange(text) {
|
|
4153
|
-
this.jsonText.set(text);
|
|
4154
|
-
try {
|
|
4155
|
-
JSON.parse(text);
|
|
4156
|
-
this.jsonError.set(null);
|
|
4157
|
-
}
|
|
4158
|
-
catch (e) {
|
|
4159
|
-
this.jsonError.set(e.message);
|
|
4160
|
-
}
|
|
4161
|
-
}
|
|
4162
|
-
applyJsonChanges() {
|
|
4163
|
-
try {
|
|
4164
|
-
const parsed = JSON.parse(this.jsonText());
|
|
4165
|
-
// Update internal state from JSON
|
|
4166
|
-
this.schemaName.set(parsed.title || 'New Schema');
|
|
4167
|
-
this.fields.set(this.jsonSchemaToEditorFields(parsed));
|
|
4168
|
-
this.jsonError.set(null);
|
|
4169
|
-
this.emitChange();
|
|
4170
|
-
}
|
|
4171
|
-
catch (e) {
|
|
4172
|
-
this.jsonError.set(e.message);
|
|
4173
|
-
}
|
|
4174
|
-
}
|
|
4175
|
-
formatJson() {
|
|
4176
|
-
try {
|
|
4177
|
-
const parsed = JSON.parse(this.jsonText());
|
|
4178
|
-
this.jsonText.set(JSON.stringify(parsed, null, 2));
|
|
4179
|
-
this.jsonError.set(null);
|
|
4180
|
-
}
|
|
4181
|
-
catch (e) {
|
|
4182
|
-
this.jsonError.set(e.message);
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
copyJson() {
|
|
4186
|
-
const text = this.jsonText();
|
|
4187
|
-
navigator.clipboard.writeText(text).catch(err => {
|
|
4188
|
-
console.error('Failed to copy JSON to clipboard:', err);
|
|
4189
|
-
});
|
|
4190
|
-
}
|
|
4191
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4192
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SchemaEditorComponent, isStandalone: true, selector: "schema-editor", inputs: { schema: "schema", showJsonToggle: "showJsonToggle", showSchemaName: "showSchemaName", showDisplayType: "showDisplayType" }, outputs: { schemaChange: "schemaChange", save: "save" }, ngImport: i0, template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n @if (showSchemaName) {\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\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 <field-item\n cdkDrag\n [field]=\"field\"\n [parentList]=\"fields()\"\n [level]=\"0\"\n [showDisplayType]=\"showDisplayType\"\n (fieldChange)=\"onFieldChange()\"\n (delete)=\"onFieldDelete($event)\"\n (duplicate)=\"onFieldDuplicate($event)\"\n >\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n </field-item>\n }\n </div>\n }\n </div>\n</div>\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:8px;padding:8px 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:focus-within{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.is-complex:focus-within{background:#fef3c7;border-color:var(--schema-editor-accent-primary)}.field-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.field-right{display:flex;align-items:center;gap:8px;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%;max-width:100%;box-sizing:border-box;overflow:hidden}.field-wrapper ::ng-deep .mat-mdc-icon-button{width:32px!important;height:32px!important;padding:4px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;flex-shrink:0!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-touch-target{width:32px!important;height:32px!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.field-wrapper ::ng-deep .mat-mdc-icon-button mat-icon,.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-icon{font-size:20px!important;width:20px!important;height:20px!important;line-height:20px!important;overflow:hidden!important}::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}.array-indicator{color:#f59e0b;font-weight:600;font-size:14px;margin-left:2px}.field-name-input{flex:0 0 auto;width:120px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;transition:all .15s ease}.field-name-input:focus{border-color:#3b82f6;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}.label-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;transition:all .15s ease}.label-input:focus{border-color:#3b82f6;background:#fff}.label-input::placeholder{color:#94a3b8;font-style:italic}.type-selector,.display-type-selector{width:130px;flex-shrink:0;margin-right:4px}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper,.display-type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.type-selector ::ng-deep .mdc-text-field--outlined,.display-type-selector ::ng-deep .mdc-text-field--outlined{height:32px!important}.type-selector ::ng-deep .mat-mdc-text-field-wrapper,.display-type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important;height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-flex,.display-type-selector ::ng-deep .mat-mdc-form-field-flex{height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix,.display-type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important;height:28px!important}.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#e2e8f0!important}.type-selector ::ng-deep .mat-mdc-select,.display-type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text,.display-type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper,.display-type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.display-type-selector{width:120px}.display-type-placeholder{width:120px;flex-shrink:0;margin-right:4px}::ng-deep .schema-editor-select-panel .mat-mdc-option{font-size:12px!important;min-height:36px!important;padding:0 12px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:12px!important;display:flex!important;align-items:center!important;gap:8px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option mat-icon{font-size:16px!important;width:16px!important;height:16px!important;margin-right:0!important;color:#64748b}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:2px;width:168px;flex-shrink:0;margin-left:4px;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:8px;margin-bottom:8px;padding-left:32px;box-sizing:border-box}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor,.nested-fields .validators-editor{margin-left:16px;margin-top: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:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;box-sizing:border-box}.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}.field-actions .has-validators{color:#3b82f6!important}.validators-editor{margin:12px 12px 8px 48px;padding:12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;box-sizing:border-box;overflow:hidden;width:calc(100% - 60px)}.validators-editor .validators-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.validators-editor .validators-label{font-size:12px;font-weight:500;color:#1d4ed8}.validators-editor .validators-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px;overflow:hidden;width:100%;box-sizing:border-box}.validators-editor .validators-row:last-child{margin-bottom:0}.validators-editor .validator-field{display:flex;flex-direction:column;gap:4px;flex-shrink:0;box-sizing:border-box}.validators-editor .validator-field label{font-size:11px;font-weight:500;color:#3b82f6}.validators-editor .validator-field.pattern-field{flex:1 1 auto;min-width:80px;overflow:hidden}.validators-editor .validator-input,.validators-editor .validator-select{padding:6px 10px;font-size:13px;border:1px solid #93c5fd;border-radius:4px;outline:none;background:#fff;width:100px;box-sizing:border-box!important}.validators-editor .validator-input:focus,.validators-editor .validator-select:focus{border-color:#3b82f6}.validators-editor .validator-input::placeholder,.validators-editor .validator-select::placeholder{color:#94a3b8}.validators-editor .validator-select{cursor:pointer;width:120px}.validators-editor .pattern-field .validator-input{width:100%;box-sizing:border-box!important}.default-value-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px;box-sizing:border-box}.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: "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: "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: "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: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i6$3.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i6$3.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: i7$2.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: i7$2.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: i7$2.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i7$2.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "component", type: FieldItemComponent, selector: "field-item", inputs: ["field", "parentList", "level", "showDisplayType"], outputs: ["fieldChange", "delete", "duplicate", "outdent"] }] });
|
|
4193
|
-
}
|
|
4194
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SchemaEditorComponent, decorators: [{
|
|
4195
|
-
type: Component,
|
|
4196
|
-
args: [{ selector: 'schema-editor', standalone: true, imports: [
|
|
4197
|
-
CommonModule,
|
|
4198
|
-
FormsModule,
|
|
4199
|
-
MatButtonModule,
|
|
4200
|
-
MatIconModule,
|
|
4201
|
-
MatInputModule,
|
|
4202
|
-
MatFormFieldModule,
|
|
4203
|
-
MatSelectModule,
|
|
4204
|
-
MatTooltipModule,
|
|
4205
|
-
MatMenuModule,
|
|
4206
|
-
MatButtonToggleModule,
|
|
4207
|
-
DragDropModule,
|
|
4208
|
-
FieldItemComponent,
|
|
4209
|
-
], template: "<div class=\"schema-editor\">\n <!-- Schema Header -->\n <div class=\"editor-header\">\n @if (showSchemaName) {\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\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 <field-item\n cdkDrag\n [field]=\"field\"\n [parentList]=\"fields()\"\n [level]=\"0\"\n [showDisplayType]=\"showDisplayType\"\n (fieldChange)=\"onFieldChange()\"\n (delete)=\"onFieldDelete($event)\"\n (duplicate)=\"onFieldDuplicate($event)\"\n >\n <!-- Drag Placeholder -->\n <div class=\"drag-placeholder\" *cdkDragPlaceholder></div>\n <!-- Drag Preview -->\n <div *cdkDragPreview class=\"drag-preview\">\n <mat-icon>{{ getTypeIcon(field.type) }}</mat-icon>\n {{ field.name || 'unnamed' }}\n </div>\n </field-item>\n }\n </div>\n }\n </div>\n</div>\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:8px;padding:8px 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:focus-within{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.is-complex:focus-within{background:#fef3c7;border-color:var(--schema-editor-accent-primary)}.field-left{display:flex;align-items:center;gap:8px;flex-shrink:0}.field-right{display:flex;align-items:center;gap:8px;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%;max-width:100%;box-sizing:border-box;overflow:hidden}.field-wrapper ::ng-deep .mat-mdc-icon-button{width:32px!important;height:32px!important;padding:4px!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;flex-shrink:0!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-touch-target{width:32px!important;height:32px!important}.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-mdc-button-persistent-ripple{border-radius:50%}.field-wrapper ::ng-deep .mat-mdc-icon-button mat-icon,.field-wrapper ::ng-deep .mat-mdc-icon-button .mat-icon{font-size:20px!important;width:20px!important;height:20px!important;line-height:20px!important;overflow:hidden!important}::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}.array-indicator{color:#f59e0b;font-weight:600;font-size:14px;margin-left:2px}.field-name-input{flex:0 0 auto;width:120px;font-size:14px;font-weight:500;color:#1e293b;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;outline:none;background:#f8fafc;transition:all .15s ease}.field-name-input:focus{border-color:#3b82f6;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}.label-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;transition:all .15s ease}.label-input:focus{border-color:#3b82f6;background:#fff}.label-input::placeholder{color:#94a3b8;font-style:italic}.type-selector,.display-type-selector{width:130px;flex-shrink:0;margin-right:4px}.type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper,.display-type-selector ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.type-selector ::ng-deep .mdc-text-field--outlined,.display-type-selector ::ng-deep .mdc-text-field--outlined{height:32px!important}.type-selector ::ng-deep .mat-mdc-text-field-wrapper,.display-type-selector ::ng-deep .mat-mdc-text-field-wrapper{padding:0 8px!important;height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-flex,.display-type-selector ::ng-deep .mat-mdc-form-field-flex{height:32px!important}.type-selector ::ng-deep .mat-mdc-form-field-infix,.display-type-selector ::ng-deep .mat-mdc-form-field-infix{padding-top:4px!important;padding-bottom:4px!important;min-height:28px!important;height:28px!important}.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__leading,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__notch,.display-type-selector ::ng-deep .mdc-notched-outline .mdc-notched-outline__trailing{border-color:#e2e8f0!important}.type-selector ::ng-deep .mat-mdc-select,.display-type-selector ::ng-deep .mat-mdc-select{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-value-text,.display-type-selector ::ng-deep .mat-mdc-select-value-text{font-size:12px!important}.type-selector ::ng-deep .mat-mdc-select-arrow-wrapper,.display-type-selector ::ng-deep .mat-mdc-select-arrow-wrapper{transform:scale(.8)}.display-type-selector{width:120px}.display-type-placeholder{width:120px;flex-shrink:0;margin-right:4px}::ng-deep .schema-editor-select-panel .mat-mdc-option{font-size:12px!important;min-height:36px!important;padding:0 12px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option .mdc-list-item__primary-text{font-size:12px!important;display:flex!important;align-items:center!important;gap:8px!important}::ng-deep .schema-editor-select-panel .mat-mdc-option mat-icon{font-size:16px!important;width:16px!important;height:16px!important;margin-right:0!important;color:#64748b}.field-actions{display:flex;align-items:center;justify-content:flex-end;gap:2px;width:168px;flex-shrink:0;margin-left:4px;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:8px;margin-bottom:8px;padding-left:32px;box-sizing:border-box}.nested-fields .field-wrapper{margin-bottom:4px}.nested-fields .allowed-values-editor,.nested-fields .default-value-editor,.nested-fields .validators-editor{margin-left:16px;margin-top: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:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f0fdf4;border:1px solid #bbf7d0;border-radius:8px;box-sizing:border-box}.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}.field-actions .has-validators{color:#3b82f6!important}.validators-editor{margin:12px 12px 8px 48px;padding:12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;box-sizing:border-box;overflow:hidden;width:calc(100% - 60px)}.validators-editor .validators-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}.validators-editor .validators-label{font-size:12px;font-weight:500;color:#1d4ed8}.validators-editor .validators-row{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:8px;overflow:hidden;width:100%;box-sizing:border-box}.validators-editor .validators-row:last-child{margin-bottom:0}.validators-editor .validator-field{display:flex;flex-direction:column;gap:4px;flex-shrink:0;box-sizing:border-box}.validators-editor .validator-field label{font-size:11px;font-weight:500;color:#3b82f6}.validators-editor .validator-field.pattern-field{flex:1 1 auto;min-width:80px;overflow:hidden}.validators-editor .validator-input,.validators-editor .validator-select{padding:6px 10px;font-size:13px;border:1px solid #93c5fd;border-radius:4px;outline:none;background:#fff;width:100px;box-sizing:border-box!important}.validators-editor .validator-input:focus,.validators-editor .validator-select:focus{border-color:#3b82f6}.validators-editor .validator-input::placeholder,.validators-editor .validator-select::placeholder{color:#94a3b8}.validators-editor .validator-select{cursor:pointer;width:120px}.validators-editor .pattern-field .validator-input{width:100%;box-sizing:border-box!important}.default-value-editor{margin-left:48px;margin-top:12px;margin-bottom:8px;padding:12px;background:#f5f3ff;border:1px solid #c4b5fd;border-radius:8px;box-sizing:border-box}.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"] }]
|
|
4210
|
-
}], propDecorators: { schema: [{
|
|
4211
|
-
type: Input
|
|
4212
|
-
}], showJsonToggle: [{
|
|
4213
|
-
type: Input
|
|
4214
|
-
}], showSchemaName: [{
|
|
4215
|
-
type: Input
|
|
4216
|
-
}], showDisplayType: [{
|
|
4217
|
-
type: Input
|
|
4218
|
-
}], schemaChange: [{
|
|
4219
|
-
type: Output
|
|
4220
|
-
}], save: [{
|
|
4221
|
-
type: Output
|
|
4222
|
-
}] } });
|
|
4223
|
-
|
|
4224
2782
|
/*
|
|
4225
2783
|
* Public API Surface of ngx-data-mapper
|
|
4226
2784
|
*/
|
|
4227
|
-
//
|
|
2785
|
+
// Main Component
|
|
4228
2786
|
|
|
4229
2787
|
/**
|
|
4230
2788
|
* Generated bundle index. Do not edit.
|
|
4231
2789
|
*/
|
|
4232
2790
|
|
|
4233
|
-
export {
|
|
2791
|
+
export { DataMapperComponent };
|
|
4234
2792
|
//# sourceMappingURL=expeed-ngx-data-mapper.mjs.map
|