ngx-dynamic-entity-builder 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +54 -0
  2. package/esm2022/lib/builder-store.service.mjs +923 -0
  3. package/esm2022/lib/clone.mjs +14 -0
  4. package/esm2022/lib/components/connection-source-config.component.mjs +121 -0
  5. package/esm2022/lib/components/entity-builder-canvas.component.mjs +83 -0
  6. package/esm2022/lib/components/entity-builder-tree-node.component.mjs +173 -0
  7. package/esm2022/lib/components/entity-reference-config.component.mjs +405 -0
  8. package/esm2022/lib/components/field-inspector.component.mjs +139 -0
  9. package/esm2022/lib/components/field-palette.component.mjs +57 -0
  10. package/esm2022/lib/components/field-rules-list.component.mjs +224 -0
  11. package/esm2022/lib/components/referenced-field-config.component.mjs +228 -0
  12. package/esm2022/lib/components/rule-form.component.mjs +201 -0
  13. package/esm2022/lib/components/tab-manager.component.mjs +341 -0
  14. package/esm2022/lib/entity-builder.component.mjs +153 -0
  15. package/esm2022/lib/field-catalog.mjs +6 -0
  16. package/esm2022/ngx-dynamic-entity-builder.mjs +5 -0
  17. package/esm2022/public-api.mjs +17 -0
  18. package/fesm2022/ngx-dynamic-entity-builder.mjs +2910 -0
  19. package/fesm2022/ngx-dynamic-entity-builder.mjs.map +1 -0
  20. package/index.d.ts +5 -0
  21. package/lib/builder-store.service.d.ts +165 -0
  22. package/lib/clone.d.ts +9 -0
  23. package/lib/components/connection-source-config.component.d.ts +16 -0
  24. package/lib/components/entity-builder-canvas.component.d.ts +9 -0
  25. package/lib/components/entity-builder-tree-node.component.d.ts +17 -0
  26. package/lib/components/entity-reference-config.component.d.ts +29 -0
  27. package/lib/components/field-inspector.component.d.ts +33 -0
  28. package/lib/components/field-palette.component.d.ts +13 -0
  29. package/lib/components/field-rules-list.component.d.ts +19 -0
  30. package/lib/components/referenced-field-config.component.d.ts +18 -0
  31. package/lib/components/rule-form.component.d.ts +16 -0
  32. package/lib/components/tab-manager.component.d.ts +19 -0
  33. package/lib/entity-builder.component.d.ts +42 -0
  34. package/lib/field-catalog.d.ts +5 -0
  35. package/package.json +42 -0
  36. package/public-api.d.ts +13 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Deep clone with a JSON fallback.
3
+ *
4
+ * `structuredClone` is not universally available (older browsers, and jsdom before v22),
5
+ * so it must never be called unguarded — an unguarded call throws a ReferenceError and the
6
+ * caller silently does nothing. Config and rule objects are JSON-safe, so the fallback is
7
+ * lossless here.
8
+ */
9
+ export function deepClone(value) {
10
+ if (typeof structuredClone === 'function')
11
+ return structuredClone(value);
12
+ return JSON.parse(JSON.stringify(value));
13
+ }
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL2Nsb25lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUsU0FBUyxDQUFJLEtBQVE7SUFDbkMsSUFBSSxPQUFPLGVBQWUsS0FBSyxVQUFVO1FBQUUsT0FBTyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDekUsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQU0sQ0FBQztBQUNoRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXHJcbiAqIERlZXAgY2xvbmUgd2l0aCBhIEpTT04gZmFsbGJhY2suXHJcbiAqXHJcbiAqIGBzdHJ1Y3R1cmVkQ2xvbmVgIGlzIG5vdCB1bml2ZXJzYWxseSBhdmFpbGFibGUgKG9sZGVyIGJyb3dzZXJzLCBhbmQganNkb20gYmVmb3JlIHYyMiksXHJcbiAqIHNvIGl0IG11c3QgbmV2ZXIgYmUgY2FsbGVkIHVuZ3VhcmRlZCDigJQgYW4gdW5ndWFyZGVkIGNhbGwgdGhyb3dzIGEgUmVmZXJlbmNlRXJyb3IgYW5kIHRoZVxyXG4gKiBjYWxsZXIgc2lsZW50bHkgZG9lcyBub3RoaW5nLiBDb25maWcgYW5kIHJ1bGUgb2JqZWN0cyBhcmUgSlNPTi1zYWZlLCBzbyB0aGUgZmFsbGJhY2sgaXNcclxuICogbG9zc2xlc3MgaGVyZS5cclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBkZWVwQ2xvbmU8VD4odmFsdWU6IFQpOiBUIHtcclxuICBpZiAodHlwZW9mIHN0cnVjdHVyZWRDbG9uZSA9PT0gJ2Z1bmN0aW9uJykgcmV0dXJuIHN0cnVjdHVyZWRDbG9uZSh2YWx1ZSk7XHJcbiAgcmV0dXJuIEpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkodmFsdWUpKSBhcyBUO1xyXG59XHJcbiJdfQ==
@@ -0,0 +1,121 @@
1
+ import { Component, computed, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MatFormFieldModule } from '@angular/material/form-field';
5
+ import { MatInputModule } from '@angular/material/input';
6
+ import { MatSelectModule } from '@angular/material/select';
7
+ import { BuilderStore } from '../builder-store.service';
8
+ import * as i0 from "@angular/core";
9
+ import * as i1 from "@angular/forms";
10
+ import * as i2 from "@angular/material/form-field";
11
+ import * as i3 from "@angular/material/input";
12
+ /**
13
+ * ConnectionSourceConfigComponent — allows configuring connection source settings
14
+ * for an entity or field (Phase 7.2).
15
+ */
16
+ export class ConnectionSourceConfigComponent {
17
+ store = inject(BuilderStore);
18
+ field = computed(() => this.store.selectedField());
19
+ connectionSourceEntity(field) {
20
+ return field.connectionSource?.entity ?? '';
21
+ }
22
+ setConnectionSourceEntity(field, entity) {
23
+ const trimmed = entity.trim();
24
+ const existing = field.connectionSource ?? {};
25
+ if (!trimmed && !existing.targetField) {
26
+ this.store.updateField(field.id, { connectionSource: undefined });
27
+ }
28
+ else {
29
+ this.store.updateField(field.id, {
30
+ connectionSource: { ...existing, entity: trimmed },
31
+ });
32
+ }
33
+ }
34
+ connectionTargetField(field) {
35
+ return field.connectionSource?.targetField ?? '';
36
+ }
37
+ setConnectionTargetField(field, targetField) {
38
+ const trimmed = targetField.trim();
39
+ const existing = field.connectionSource ?? {};
40
+ if (!trimmed && !existing.entity) {
41
+ this.store.updateField(field.id, { connectionSource: undefined });
42
+ }
43
+ else {
44
+ this.store.updateField(field.id, {
45
+ connectionSource: { ...existing, targetField: trimmed },
46
+ });
47
+ }
48
+ }
49
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionSourceConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
50
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ConnectionSourceConfigComponent, isStandalone: true, selector: "ngx-connection-source-config", ngImport: i0, template: `
51
+ @if (field(); as f) {
52
+ <div class="deb-connection-source">
53
+ <span class="deb-section-title">Connection Source</span>
54
+ <mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
55
+ <mat-label>Source Entity</mat-label>
56
+ <input
57
+ matInput
58
+ data-testid="connection-source-entity"
59
+ [ngModel]="connectionSourceEntity(f)"
60
+ (ngModelChange)="setConnectionSourceEntity(f, $event)"
61
+ placeholder="e.g. organizations"
62
+ />
63
+ </mat-form-field>
64
+
65
+ <mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
66
+ <mat-label>Target Field ID</mat-label>
67
+ <input
68
+ matInput
69
+ data-testid="connection-target-field"
70
+ [ngModel]="connectionTargetField(f)"
71
+ (ngModelChange)="setConnectionTargetField(f, $event)"
72
+ placeholder="e.g. companyId"
73
+ />
74
+ </mat-form-field>
75
+ </div>
76
+ }
77
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }] });
78
+ }
79
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionSourceConfigComponent, decorators: [{
80
+ type: Component,
81
+ args: [{
82
+ selector: 'ngx-connection-source-config',
83
+ standalone: true,
84
+ imports: [
85
+ CommonModule,
86
+ FormsModule,
87
+ MatFormFieldModule,
88
+ MatInputModule,
89
+ MatSelectModule,
90
+ ],
91
+ template: `
92
+ @if (field(); as f) {
93
+ <div class="deb-connection-source">
94
+ <span class="deb-section-title">Connection Source</span>
95
+ <mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
96
+ <mat-label>Source Entity</mat-label>
97
+ <input
98
+ matInput
99
+ data-testid="connection-source-entity"
100
+ [ngModel]="connectionSourceEntity(f)"
101
+ (ngModelChange)="setConnectionSourceEntity(f, $event)"
102
+ placeholder="e.g. organizations"
103
+ />
104
+ </mat-form-field>
105
+
106
+ <mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
107
+ <mat-label>Target Field ID</mat-label>
108
+ <input
109
+ matInput
110
+ data-testid="connection-target-field"
111
+ [ngModel]="connectionTargetField(f)"
112
+ (ngModelChange)="setConnectionTargetField(f, $event)"
113
+ placeholder="e.g. companyId"
114
+ />
115
+ </mat-form-field>
116
+ </div>
117
+ }
118
+ `,
119
+ }]
120
+ }] });
121
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29ubmVjdGlvbi1zb3VyY2UtY29uZmlnLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9jb25uZWN0aW9uLXNvdXJjZS1jb25maWcuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUM1RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzdDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ2xFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDM0QsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDBCQUEwQixDQUFDOzs7OztBQUV4RDs7O0dBR0c7QUF3Q0gsTUFBTSxPQUFPLCtCQUErQjtJQUN2QixLQUFLLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQzdCLEtBQUssR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDO0lBRTVELHNCQUFzQixDQUFDLEtBQVU7UUFDekMsT0FBTyxLQUFLLENBQUMsZ0JBQWdCLEVBQUUsTUFBTSxJQUFJLEVBQUUsQ0FBQztJQUM5QyxDQUFDO0lBRVMseUJBQXlCLENBQUMsS0FBVSxFQUFFLE1BQWM7UUFDNUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzlCLE1BQU0sUUFBUSxHQUFHLEtBQUssQ0FBQyxnQkFBZ0IsSUFBSSxFQUFFLENBQUM7UUFDOUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN0QyxJQUFJLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsU0FBUyxFQUFTLENBQUMsQ0FBQztRQUMzRSxDQUFDO2FBQU0sQ0FBQztZQUNOLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxFQUFFLEVBQUU7Z0JBQy9CLGdCQUFnQixFQUFFLEVBQUUsR0FBRyxRQUFRLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRTthQUM1QyxDQUFDLENBQUM7UUFDWixDQUFDO0lBQ0gsQ0FBQztJQUVTLHFCQUFxQixDQUFDLEtBQVU7UUFDeEMsT0FBTyxLQUFLLENBQUMsZ0JBQWdCLEVBQUUsV0FBVyxJQUFJLEVBQUUsQ0FBQztJQUNuRCxDQUFDO0lBRVMsd0JBQXdCLENBQUMsS0FBVSxFQUFFLFdBQW1CO1FBQ2hFLE1BQU0sT0FBTyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsZ0JBQWdCLElBQUksRUFBRSxDQUFDO1FBQzlDLElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDakMsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxFQUFFLGdCQUFnQixFQUFFLFNBQVMsRUFBUyxDQUFDLENBQUM7UUFDM0UsQ0FBQzthQUFNLENBQUM7WUFDTixJQUFJLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFO2dCQUMvQixnQkFBZ0IsRUFBRSxFQUFFLEdBQUcsUUFBUSxFQUFFLFdBQVcsRUFBRSxPQUFPLEVBQUU7YUFDakQsQ0FBQyxDQUFDO1FBQ1osQ0FBQztJQUNILENBQUM7d0dBbENVLCtCQUErQjs0RkFBL0IsK0JBQStCLHdGQTdCaEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJCVCwyREFqQ0MsWUFBWSw4QkFDWixXQUFXLDhtQkFDWCxrQkFBa0IsMFNBQ2xCLGNBQWMsMFdBQ2QsZUFBZTs7NEZBK0JOLCtCQUErQjtrQkF2QzNDLFNBQVM7bUJBQUM7b0JBQ1QsUUFBUSxFQUFFLDhCQUE4QjtvQkFDeEMsVUFBVSxFQUFFLElBQUk7b0JBQ2hCLE9BQU8sRUFBRTt3QkFDUCxZQUFZO3dCQUNaLFdBQVc7d0JBQ1gsa0JBQWtCO3dCQUNsQixjQUFjO3dCQUNkLGVBQWU7cUJBQ2hCO29CQUNELFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMkJUO2lCQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBjb21wdXRlZCwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHsgRm9ybXNNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5pbXBvcnQgeyBNYXRGb3JtRmllbGRNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9tYXRlcmlhbC9mb3JtLWZpZWxkJztcbmltcG9ydCB7IE1hdElucHV0TW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvaW5wdXQnO1xuaW1wb3J0IHsgTWF0U2VsZWN0TW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvc2VsZWN0JztcbmltcG9ydCB7IEJ1aWxkZXJTdG9yZSB9IGZyb20gJy4uL2J1aWxkZXItc3RvcmUuc2VydmljZSc7XG5cbi8qKlxuICogQ29ubmVjdGlvblNvdXJjZUNvbmZpZ0NvbXBvbmVudCDigJQgYWxsb3dzIGNvbmZpZ3VyaW5nIGNvbm5lY3Rpb24gc291cmNlIHNldHRpbmdzXG4gKiBmb3IgYW4gZW50aXR5IG9yIGZpZWxkIChQaGFzZSA3LjIpLlxuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtY29ubmVjdGlvbi1zb3VyY2UtY29uZmlnJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIENvbW1vbk1vZHVsZSxcbiAgICBGb3Jtc01vZHVsZSxcbiAgICBNYXRGb3JtRmllbGRNb2R1bGUsXG4gICAgTWF0SW5wdXRNb2R1bGUsXG4gICAgTWF0U2VsZWN0TW9kdWxlLFxuICBdLFxuICB0ZW1wbGF0ZTogYFxuICAgIEBpZiAoZmllbGQoKTsgYXMgZikge1xuICAgICAgPGRpdiBjbGFzcz1cImRlYi1jb25uZWN0aW9uLXNvdXJjZVwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImRlYi1zZWN0aW9uLXRpdGxlXCI+Q29ubmVjdGlvbiBTb3VyY2U8L3NwYW4+XG4gICAgICAgIDxtYXQtZm9ybS1maWVsZCBhcHBlYXJhbmNlPVwib3V0bGluZVwiIHN1YnNjcmlwdFNpemluZz1cImR5bmFtaWNcIiBjbGFzcz1cImRlYi1mdWxsXCI+XG4gICAgICAgICAgPG1hdC1sYWJlbD5Tb3VyY2UgRW50aXR5PC9tYXQtbGFiZWw+XG4gICAgICAgICAgPGlucHV0XG4gICAgICAgICAgICBtYXRJbnB1dFxuICAgICAgICAgICAgZGF0YS10ZXN0aWQ9XCJjb25uZWN0aW9uLXNvdXJjZS1lbnRpdHlcIlxuICAgICAgICAgICAgW25nTW9kZWxdPVwiY29ubmVjdGlvblNvdXJjZUVudGl0eShmKVwiXG4gICAgICAgICAgICAobmdNb2RlbENoYW5nZSk9XCJzZXRDb25uZWN0aW9uU291cmNlRW50aXR5KGYsICRldmVudClcIlxuICAgICAgICAgICAgcGxhY2Vob2xkZXI9XCJlLmcuIG9yZ2FuaXphdGlvbnNcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvbWF0LWZvcm0tZmllbGQ+XG5cbiAgICAgICAgPG1hdC1mb3JtLWZpZWxkIGFwcGVhcmFuY2U9XCJvdXRsaW5lXCIgc3Vic2NyaXB0U2l6aW5nPVwiZHluYW1pY1wiIGNsYXNzPVwiZGViLWZ1bGxcIj5cbiAgICAgICAgICA8bWF0LWxhYmVsPlRhcmdldCBGaWVsZCBJRDwvbWF0LWxhYmVsPlxuICAgICAgICAgIDxpbnB1dFxuICAgICAgICAgICAgbWF0SW5wdXRcbiAgICAgICAgICAgIGRhdGEtdGVzdGlkPVwiY29ubmVjdGlvbi10YXJnZXQtZmllbGRcIlxuICAgICAgICAgICAgW25nTW9kZWxdPVwiY29ubmVjdGlvblRhcmdldEZpZWxkKGYpXCJcbiAgICAgICAgICAgIChuZ01vZGVsQ2hhbmdlKT1cInNldENvbm5lY3Rpb25UYXJnZXRGaWVsZChmLCAkZXZlbnQpXCJcbiAgICAgICAgICAgIHBsYWNlaG9sZGVyPVwiZS5nLiBjb21wYW55SWRcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvbWF0LWZvcm0tZmllbGQ+XG4gICAgICA8L2Rpdj5cbiAgICB9XG4gIGAsXG59KVxuZXhwb3J0IGNsYXNzIENvbm5lY3Rpb25Tb3VyY2VDb25maWdDb21wb25lbnQge1xuICBwcm90ZWN0ZWQgcmVhZG9ubHkgc3RvcmUgPSBpbmplY3QoQnVpbGRlclN0b3JlKTtcbiAgcHJvdGVjdGVkIHJlYWRvbmx5IGZpZWxkID0gY29tcHV0ZWQoKCkgPT4gdGhpcy5zdG9yZS5zZWxlY3RlZEZpZWxkKCkpO1xuXG4gIHByb3RlY3RlZCBjb25uZWN0aW9uU291cmNlRW50aXR5KGZpZWxkOiBhbnkpOiBzdHJpbmcge1xuICAgIHJldHVybiBmaWVsZC5jb25uZWN0aW9uU291cmNlPy5lbnRpdHkgPz8gJyc7XG4gIH1cblxuICBwcm90ZWN0ZWQgc2V0Q29ubmVjdGlvblNvdXJjZUVudGl0eShmaWVsZDogYW55LCBlbnRpdHk6IHN0cmluZyk6IHZvaWQge1xuICAgIGNvbnN0IHRyaW1tZWQgPSBlbnRpdHkudHJpbSgpO1xuICAgIGNvbnN0IGV4aXN0aW5nID0gZmllbGQuY29ubmVjdGlvblNvdXJjZSA/PyB7fTtcbiAgICBpZiAoIXRyaW1tZWQgJiYgIWV4aXN0aW5nLnRhcmdldEZpZWxkKSB7XG4gICAgICB0aGlzLnN0b3JlLnVwZGF0ZUZpZWxkKGZpZWxkLmlkLCB7IGNvbm5lY3Rpb25Tb3VyY2U6IHVuZGVmaW5lZCB9IGFzIGFueSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc3RvcmUudXBkYXRlRmllbGQoZmllbGQuaWQsIHtcbiAgICAgICAgY29ubmVjdGlvblNvdXJjZTogeyAuLi5leGlzdGluZywgZW50aXR5OiB0cmltbWVkIH0sXG4gICAgICB9IGFzIGFueSk7XG4gICAgfVxuICB9XG5cbiAgcHJvdGVjdGVkIGNvbm5lY3Rpb25UYXJnZXRGaWVsZChmaWVsZDogYW55KTogc3RyaW5nIHtcbiAgICByZXR1cm4gZmllbGQuY29ubmVjdGlvblNvdXJjZT8udGFyZ2V0RmllbGQgPz8gJyc7XG4gIH1cblxuICBwcm90ZWN0ZWQgc2V0Q29ubmVjdGlvblRhcmdldEZpZWxkKGZpZWxkOiBhbnksIHRhcmdldEZpZWxkOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCB0cmltbWVkID0gdGFyZ2V0RmllbGQudHJpbSgpO1xuICAgIGNvbnN0IGV4aXN0aW5nID0gZmllbGQuY29ubmVjdGlvblNvdXJjZSA/PyB7fTtcbiAgICBpZiAoIXRyaW1tZWQgJiYgIWV4aXN0aW5nLmVudGl0eSkge1xuICAgICAgdGhpcy5zdG9yZS51cGRhdGVGaWVsZChmaWVsZC5pZCwgeyBjb25uZWN0aW9uU291cmNlOiB1bmRlZmluZWQgfSBhcyBhbnkpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLnN0b3JlLnVwZGF0ZUZpZWxkKGZpZWxkLmlkLCB7XG4gICAgICAgIGNvbm5lY3Rpb25Tb3VyY2U6IHsgLi4uZXhpc3RpbmcsIHRhcmdldEZpZWxkOiB0cmltbWVkIH0sXG4gICAgICB9IGFzIGFueSk7XG4gICAgfVxuICB9XG59XG4iXX0=
@@ -0,0 +1,83 @@
1
+ import { Component, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { DragDropModule } from '@angular/cdk/drag-drop';
4
+ import { MatCardModule } from '@angular/material/card';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import { BuilderStore } from '../builder-store.service';
7
+ import { EntityBuilderTreeNodeComponent } from './entity-builder-tree-node.component';
8
+ import * as i0 from "@angular/core";
9
+ import * as i1 from "@angular/cdk/drag-drop";
10
+ import * as i2 from "@angular/material/card";
11
+ import * as i3 from "@angular/material/icon";
12
+ export class EntityBuilderCanvasComponent {
13
+ store = inject(BuilderStore);
14
+ onDrop(event) {
15
+ this.store.reorderField(event.previousIndex, event.currentIndex);
16
+ }
17
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityBuilderCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
18
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityBuilderCanvasComponent, isStandalone: true, selector: "ngx-entity-builder-canvas", ngImport: i0, template: `
19
+ <mat-card class="deb-canvas">
20
+ <mat-card-header>
21
+ <mat-card-title>Fields ({{ store.fields().length }})</mat-card-title>
22
+ </mat-card-header>
23
+ <mat-card-content>
24
+ @if (store.fields().length === 0) {
25
+ <div class="deb-empty">
26
+ <mat-icon>widgets</mat-icon>
27
+ <p>No fields yet. Pick a type from <strong>Add field</strong> to get started.</p>
28
+ </div>
29
+ }
30
+
31
+ <div cdkDropList class="deb-field-list" data-testid="builder-field-list" (cdkDropListDropped)="onDrop($event)">
32
+ @for (f of store.fields(); track f.id; let i = $index, count = $count) {
33
+ <ngx-entity-builder-tree-node
34
+ [field]="f"
35
+ [index]="i"
36
+ [totalCount]="count"
37
+ />
38
+ }
39
+ </div>
40
+ </mat-card-content>
41
+ </mat-card>
42
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: EntityBuilderTreeNodeComponent, selector: "ngx-entity-builder-tree-node", inputs: ["field", "index", "totalCount"] }] });
43
+ }
44
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityBuilderCanvasComponent, decorators: [{
45
+ type: Component,
46
+ args: [{
47
+ selector: 'ngx-entity-builder-canvas',
48
+ standalone: true,
49
+ imports: [
50
+ CommonModule,
51
+ DragDropModule,
52
+ MatCardModule,
53
+ MatIconModule,
54
+ EntityBuilderTreeNodeComponent,
55
+ ],
56
+ template: `
57
+ <mat-card class="deb-canvas">
58
+ <mat-card-header>
59
+ <mat-card-title>Fields ({{ store.fields().length }})</mat-card-title>
60
+ </mat-card-header>
61
+ <mat-card-content>
62
+ @if (store.fields().length === 0) {
63
+ <div class="deb-empty">
64
+ <mat-icon>widgets</mat-icon>
65
+ <p>No fields yet. Pick a type from <strong>Add field</strong> to get started.</p>
66
+ </div>
67
+ }
68
+
69
+ <div cdkDropList class="deb-field-list" data-testid="builder-field-list" (cdkDropListDropped)="onDrop($event)">
70
+ @for (f of store.fields(); track f.id; let i = $index, count = $count) {
71
+ <ngx-entity-builder-tree-node
72
+ [field]="f"
73
+ [index]="i"
74
+ [totalCount]="count"
75
+ />
76
+ }
77
+ </div>
78
+ </mat-card-content>
79
+ </mat-card>
80
+ `,
81
+ }]
82
+ }] });
83
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXR5LWJ1aWxkZXItY2FudmFzLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9lbnRpdHktYnVpbGRlci1jYW52YXMuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ2xELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMvQyxPQUFPLEVBQWUsY0FBYyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDckUsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDeEQsT0FBTyxFQUFFLDhCQUE4QixFQUFFLE1BQU0sc0NBQXNDLENBQUM7Ozs7O0FBc0N0RixNQUFNLE9BQU8sNEJBQTRCO0lBQ3BCLEtBQUssR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7SUFFdEMsTUFBTSxDQUFDLEtBQTJCO1FBQzFDLElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxhQUFhLEVBQUUsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ25FLENBQUM7d0dBTFUsNEJBQTRCOzRGQUE1Qiw0QkFBNEIscUZBMUI3Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JULDJEQTlCQyxZQUFZLDhCQUNaLGNBQWMsMmdCQUNkLGFBQWEsa1pBQ2IsYUFBYSxvTEFDYiw4QkFBOEI7OzRGQTRCckIsNEJBQTRCO2tCQXBDeEMsU0FBUzttQkFBQztvQkFDVCxRQUFRLEVBQUUsMkJBQTJCO29CQUNyQyxVQUFVLEVBQUUsSUFBSTtvQkFDaEIsT0FBTyxFQUFFO3dCQUNQLFlBQVk7d0JBQ1osY0FBYzt3QkFDZCxhQUFhO3dCQUNiLGFBQWE7d0JBQ2IsOEJBQThCO3FCQUMvQjtvQkFDRCxRQUFRLEVBQUU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCVDtpQkFDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHsgQ2RrRHJhZ0Ryb3AsIERyYWdEcm9wTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY2RrL2RyYWctZHJvcCc7XG5pbXBvcnQgeyBNYXRDYXJkTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvY2FyZCc7XG5pbXBvcnQgeyBNYXRJY29uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvaWNvbic7XG5pbXBvcnQgeyBCdWlsZGVyU3RvcmUgfSBmcm9tICcuLi9idWlsZGVyLXN0b3JlLnNlcnZpY2UnO1xuaW1wb3J0IHsgRW50aXR5QnVpbGRlclRyZWVOb2RlQ29tcG9uZW50IH0gZnJvbSAnLi9lbnRpdHktYnVpbGRlci10cmVlLW5vZGUuY29tcG9uZW50JztcblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWVudGl0eS1idWlsZGVyLWNhbnZhcycsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtcbiAgICBDb21tb25Nb2R1bGUsXG4gICAgRHJhZ0Ryb3BNb2R1bGUsXG4gICAgTWF0Q2FyZE1vZHVsZSxcbiAgICBNYXRJY29uTW9kdWxlLFxuICAgIEVudGl0eUJ1aWxkZXJUcmVlTm9kZUNvbXBvbmVudCxcbiAgXSxcbiAgdGVtcGxhdGU6IGBcbiAgICA8bWF0LWNhcmQgY2xhc3M9XCJkZWItY2FudmFzXCI+XG4gICAgICA8bWF0LWNhcmQtaGVhZGVyPlxuICAgICAgICA8bWF0LWNhcmQtdGl0bGU+RmllbGRzICh7eyBzdG9yZS5maWVsZHMoKS5sZW5ndGggfX0pPC9tYXQtY2FyZC10aXRsZT5cbiAgICAgIDwvbWF0LWNhcmQtaGVhZGVyPlxuICAgICAgPG1hdC1jYXJkLWNvbnRlbnQ+XG4gICAgICAgIEBpZiAoc3RvcmUuZmllbGRzKCkubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgPGRpdiBjbGFzcz1cImRlYi1lbXB0eVwiPlxuICAgICAgICAgICAgPG1hdC1pY29uPndpZGdldHM8L21hdC1pY29uPlxuICAgICAgICAgICAgPHA+Tm8gZmllbGRzIHlldC4gUGljayBhIHR5cGUgZnJvbSA8c3Ryb25nPkFkZCBmaWVsZDwvc3Ryb25nPiB0byBnZXQgc3RhcnRlZC48L3A+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIH1cblxuICAgICAgICA8ZGl2IGNka0Ryb3BMaXN0IGNsYXNzPVwiZGViLWZpZWxkLWxpc3RcIiBkYXRhLXRlc3RpZD1cImJ1aWxkZXItZmllbGQtbGlzdFwiIChjZGtEcm9wTGlzdERyb3BwZWQpPVwib25Ecm9wKCRldmVudClcIj5cbiAgICAgICAgICBAZm9yIChmIG9mIHN0b3JlLmZpZWxkcygpOyB0cmFjayBmLmlkOyBsZXQgaSA9ICRpbmRleCwgY291bnQgPSAkY291bnQpIHtcbiAgICAgICAgICAgIDxuZ3gtZW50aXR5LWJ1aWxkZXItdHJlZS1ub2RlXG4gICAgICAgICAgICAgIFtmaWVsZF09XCJmXCJcbiAgICAgICAgICAgICAgW2luZGV4XT1cImlcIlxuICAgICAgICAgICAgICBbdG90YWxDb3VudF09XCJjb3VudFwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIH1cbiAgICAgICAgPC9kaXY+XG4gICAgICA8L21hdC1jYXJkLWNvbnRlbnQ+XG4gICAgPC9tYXQtY2FyZD5cbiAgYCxcbn0pXG5leHBvcnQgY2xhc3MgRW50aXR5QnVpbGRlckNhbnZhc0NvbXBvbmVudCB7XG4gIHByb3RlY3RlZCByZWFkb25seSBzdG9yZSA9IGluamVjdChCdWlsZGVyU3RvcmUpO1xuXG4gIHByb3RlY3RlZCBvbkRyb3AoZXZlbnQ6IENka0RyYWdEcm9wPHVua25vd24+KTogdm9pZCB7XG4gICAgdGhpcy5zdG9yZS5yZW9yZGVyRmllbGQoZXZlbnQucHJldmlvdXNJbmRleCwgZXZlbnQuY3VycmVudEluZGV4KTtcbiAgfVxufVxuIl19
@@ -0,0 +1,173 @@
1
+ import { Component, Input, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { DragDropModule } from '@angular/cdk/drag-drop';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import { MatTooltipModule } from '@angular/material/tooltip';
7
+ import { resolveLabel } from '@dynamic-entity/core';
8
+ import { BuilderStore } from '../builder-store.service';
9
+ import { getFieldTypeMeta } from '../field-catalog';
10
+ import * as i0 from "@angular/core";
11
+ import * as i1 from "@angular/cdk/drag-drop";
12
+ import * as i2 from "@angular/material/button";
13
+ import * as i3 from "@angular/material/icon";
14
+ import * as i4 from "@angular/material/tooltip";
15
+ export class EntityBuilderTreeNodeComponent {
16
+ store = inject(BuilderStore);
17
+ field;
18
+ index;
19
+ totalCount;
20
+ fieldTypeLabel(type) {
21
+ return getFieldTypeMeta(type)?.label ?? type;
22
+ }
23
+ fieldTypeIcon(type) {
24
+ return getFieldTypeMeta(type)?.icon ?? 'help_outline';
25
+ }
26
+ fieldLabel(field) {
27
+ const lang = this.store.activeLanguage();
28
+ return resolveLabel(field.label, lang) || field.id;
29
+ }
30
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityBuilderTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
31
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityBuilderTreeNodeComponent, isStandalone: true, selector: "ngx-entity-builder-tree-node", inputs: { field: "field", index: "index", totalCount: "totalCount" }, ngImport: i0, template: `
32
+ <div
33
+ cdkDrag
34
+ class="deb-field-row"
35
+ data-testid="builder-field-row"
36
+ [class.deb-field-row--active]="store.selectedFieldId() === field.id"
37
+ (click)="store.selectField(field.id)"
38
+ >
39
+ <mat-icon class="deb-drag-handle" cdkDragHandle title="Drag to reorder">drag_indicator</mat-icon>
40
+ <mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
41
+ <div class="deb-field-meta">
42
+ <div class="deb-row" style="gap:6px">
43
+ <span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
44
+ <span class="deb-type-badge">{{ field.type }}</span>
45
+ @if (field.isReferenced) {
46
+ <mat-icon style="font-size: 16px; width: 16px; height: 16px; color: #1976d2;" matTooltip="Referenced field (linked to {{ field.referencedEntityKey }})">link</mat-icon>
47
+ }
48
+ @if (field.hasDrift) {
49
+ <mat-icon style="font-size: 16px; width: 16px; height: 16px; color: #d32f2f;" data-testid="drift-warning-icon" matTooltip="Drift detected: source field has evolved">sync_problem</mat-icon>
50
+ }
51
+ </div>
52
+ <span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
53
+ </div>
54
+ @if (field.validators?.required) { <span class="deb-req" title="Required">*</span> }
55
+
56
+ <button mat-icon-button type="button" [disabled]="index === 0"
57
+ [attr.data-testid]="'row-up-' + field.id"
58
+ (click)="store.moveField(field.id, -1); $event.stopPropagation()" matTooltip="Move up">
59
+ <mat-icon>arrow_upward</mat-icon>
60
+ </button>
61
+ <button mat-icon-button type="button" [disabled]="index === totalCount - 1"
62
+ [attr.data-testid]="'row-down-' + field.id"
63
+ (click)="store.moveField(field.id, 1); $event.stopPropagation()" matTooltip="Move down">
64
+ <mat-icon>arrow_downward</mat-icon>
65
+ </button>
66
+ <button mat-icon-button type="button"
67
+ [attr.data-testid]="'row-duplicate-' + field.id"
68
+ (click)="store.duplicateField(field.id); $event.stopPropagation()" matTooltip="Duplicate">
69
+ <mat-icon>content_copy</mat-icon>
70
+ </button>
71
+ <button mat-icon-button type="button" color="warn"
72
+ [attr.data-testid]="'row-delete-' + field.id"
73
+ (click)="store.removeField(field.id); $event.stopPropagation()" matTooltip="Delete">
74
+ <mat-icon>delete</mat-icon>
75
+ </button>
76
+ </div>
77
+
78
+ @if (field.children && field.children.length > 0) {
79
+ <div class="deb-tree-children" style="padding-left: 20px; border-left: 2px solid rgba(0,0,0,0.08); margin-left: 12px; margin-top: 4px;">
80
+ @for (child of field.children; track child.id; let ci = $index, cCount = $count) {
81
+ <ngx-entity-builder-tree-node
82
+ [field]="child"
83
+ [index]="ci"
84
+ [totalCount]="cCount"
85
+ />
86
+ }
87
+ </div>
88
+ }
89
+ `, isInline: true, dependencies: [{ kind: "component", type: EntityBuilderTreeNodeComponent, selector: "ngx-entity-builder-tree-node", inputs: ["field", "index", "totalCount"] }, { kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { 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: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
90
+ }
91
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityBuilderTreeNodeComponent, decorators: [{
92
+ type: Component,
93
+ args: [{
94
+ selector: 'ngx-entity-builder-tree-node',
95
+ standalone: true,
96
+ imports: [
97
+ CommonModule,
98
+ DragDropModule,
99
+ MatButtonModule,
100
+ MatIconModule,
101
+ MatTooltipModule,
102
+ ],
103
+ template: `
104
+ <div
105
+ cdkDrag
106
+ class="deb-field-row"
107
+ data-testid="builder-field-row"
108
+ [class.deb-field-row--active]="store.selectedFieldId() === field.id"
109
+ (click)="store.selectField(field.id)"
110
+ >
111
+ <mat-icon class="deb-drag-handle" cdkDragHandle title="Drag to reorder">drag_indicator</mat-icon>
112
+ <mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
113
+ <div class="deb-field-meta">
114
+ <div class="deb-row" style="gap:6px">
115
+ <span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
116
+ <span class="deb-type-badge">{{ field.type }}</span>
117
+ @if (field.isReferenced) {
118
+ <mat-icon style="font-size: 16px; width: 16px; height: 16px; color: #1976d2;" matTooltip="Referenced field (linked to {{ field.referencedEntityKey }})">link</mat-icon>
119
+ }
120
+ @if (field.hasDrift) {
121
+ <mat-icon style="font-size: 16px; width: 16px; height: 16px; color: #d32f2f;" data-testid="drift-warning-icon" matTooltip="Drift detected: source field has evolved">sync_problem</mat-icon>
122
+ }
123
+ </div>
124
+ <span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
125
+ </div>
126
+ @if (field.validators?.required) { <span class="deb-req" title="Required">*</span> }
127
+
128
+ <button mat-icon-button type="button" [disabled]="index === 0"
129
+ [attr.data-testid]="'row-up-' + field.id"
130
+ (click)="store.moveField(field.id, -1); $event.stopPropagation()" matTooltip="Move up">
131
+ <mat-icon>arrow_upward</mat-icon>
132
+ </button>
133
+ <button mat-icon-button type="button" [disabled]="index === totalCount - 1"
134
+ [attr.data-testid]="'row-down-' + field.id"
135
+ (click)="store.moveField(field.id, 1); $event.stopPropagation()" matTooltip="Move down">
136
+ <mat-icon>arrow_downward</mat-icon>
137
+ </button>
138
+ <button mat-icon-button type="button"
139
+ [attr.data-testid]="'row-duplicate-' + field.id"
140
+ (click)="store.duplicateField(field.id); $event.stopPropagation()" matTooltip="Duplicate">
141
+ <mat-icon>content_copy</mat-icon>
142
+ </button>
143
+ <button mat-icon-button type="button" color="warn"
144
+ [attr.data-testid]="'row-delete-' + field.id"
145
+ (click)="store.removeField(field.id); $event.stopPropagation()" matTooltip="Delete">
146
+ <mat-icon>delete</mat-icon>
147
+ </button>
148
+ </div>
149
+
150
+ @if (field.children && field.children.length > 0) {
151
+ <div class="deb-tree-children" style="padding-left: 20px; border-left: 2px solid rgba(0,0,0,0.08); margin-left: 12px; margin-top: 4px;">
152
+ @for (child of field.children; track child.id; let ci = $index, cCount = $count) {
153
+ <ngx-entity-builder-tree-node
154
+ [field]="child"
155
+ [index]="ci"
156
+ [totalCount]="cCount"
157
+ />
158
+ }
159
+ </div>
160
+ }
161
+ `,
162
+ }]
163
+ }], propDecorators: { field: [{
164
+ type: Input,
165
+ args: [{ required: true }]
166
+ }], index: [{
167
+ type: Input,
168
+ args: [{ required: true }]
169
+ }], totalCount: [{
170
+ type: Input,
171
+ args: [{ required: true }]
172
+ }] } });
173
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXR5LWJ1aWxkZXItdHJlZS1ub2RlLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9lbnRpdHktYnVpbGRlci10cmVlLW5vZGUuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLGNBQWMsRUFBZSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDdkQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDN0QsT0FBTyxFQUFFLFlBQVksRUFBMEIsTUFBTSxzQkFBc0IsQ0FBQztBQUM1RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDeEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sa0JBQWtCLENBQUM7Ozs7OztBQXdFcEQsTUFBTSxPQUFPLDhCQUE4QjtJQUN0QixLQUFLLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBRXJCLEtBQUssQ0FBcUI7SUFDMUIsS0FBSyxDQUFVO0lBQ2YsVUFBVSxDQUFVO0lBRXJDLGNBQWMsQ0FBQyxJQUFZO1FBQ25DLE9BQU8sZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxJQUFJLElBQUksQ0FBQztJQUMvQyxDQUFDO0lBRVMsYUFBYSxDQUFDLElBQVk7UUFDbEMsT0FBTyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLElBQUksY0FBYyxDQUFDO0lBQ3hELENBQUM7SUFFUyxVQUFVLENBQUMsS0FBa0M7UUFDckQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN6QyxPQUFPLFlBQVksQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUM7SUFDckQsQ0FBQzt3R0FsQlUsOEJBQThCOzRGQUE5Qiw4QkFBOEIsOEpBNUQvQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBEVCw0REFFVSw4QkFBOEIsa0hBbEV2QyxZQUFZLDhCQUNaLGNBQWMsK2tCQUNkLGVBQWUsMklBQ2YsYUFBYSxtTEFDYixnQkFBZ0I7OzRGQThEUCw4QkFBOEI7a0JBdEUxQyxTQUFTO21CQUFDO29CQUNULFFBQVEsRUFBRSw4QkFBOEI7b0JBQ3hDLFVBQVUsRUFBRSxJQUFJO29CQUNoQixPQUFPLEVBQUU7d0JBQ1AsWUFBWTt3QkFDWixjQUFjO3dCQUNkLGVBQWU7d0JBQ2YsYUFBYTt3QkFDYixnQkFBZ0I7cUJBQ2pCO29CQUNELFFBQVEsRUFBRTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBEVDtpQkFDRjs4QkFJNEIsS0FBSztzQkFBL0IsS0FBSzt1QkFBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUU7Z0JBQ0UsS0FBSztzQkFBL0IsS0FBSzt1QkFBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUU7Z0JBQ0UsVUFBVTtzQkFBcEMsS0FBSzt1QkFBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0LCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XG5pbXBvcnQgeyBEcmFnRHJvcE1vZHVsZSwgQ2RrRHJhZ0Ryb3AgfSBmcm9tICdAYW5ndWxhci9jZGsvZHJhZy1kcm9wJztcbmltcG9ydCB7IE1hdEJ1dHRvbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsL2J1dHRvbic7XG5pbXBvcnQgeyBNYXRJY29uTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvaWNvbic7XG5pbXBvcnQgeyBNYXRUb29sdGlwTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvdG9vbHRpcCc7XG5pbXBvcnQgeyByZXNvbHZlTGFiZWwsIHR5cGUgTmVzdGVkRmllbGRDb25maWcgfSBmcm9tICdAZHluYW1pYy1lbnRpdHkvY29yZSc7XG5pbXBvcnQgeyBCdWlsZGVyU3RvcmUgfSBmcm9tICcuLi9idWlsZGVyLXN0b3JlLnNlcnZpY2UnO1xuaW1wb3J0IHsgZ2V0RmllbGRUeXBlTWV0YSB9IGZyb20gJy4uL2ZpZWxkLWNhdGFsb2cnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtZW50aXR5LWJ1aWxkZXItdHJlZS1ub2RlJyxcbiAgc3RhbmRhbG9uZTogdHJ1ZSxcbiAgaW1wb3J0czogW1xuICAgIENvbW1vbk1vZHVsZSxcbiAgICBEcmFnRHJvcE1vZHVsZSxcbiAgICBNYXRCdXR0b25Nb2R1bGUsXG4gICAgTWF0SWNvbk1vZHVsZSxcbiAgICBNYXRUb29sdGlwTW9kdWxlLFxuICBdLFxuICB0ZW1wbGF0ZTogYFxuICAgIDxkaXZcbiAgICAgIGNka0RyYWdcbiAgICAgIGNsYXNzPVwiZGViLWZpZWxkLXJvd1wiXG4gICAgICBkYXRhLXRlc3RpZD1cImJ1aWxkZXItZmllbGQtcm93XCJcbiAgICAgIFtjbGFzcy5kZWItZmllbGQtcm93LS1hY3RpdmVdPVwic3RvcmUuc2VsZWN0ZWRGaWVsZElkKCkgPT09IGZpZWxkLmlkXCJcbiAgICAgIChjbGljayk9XCJzdG9yZS5zZWxlY3RGaWVsZChmaWVsZC5pZClcIlxuICAgID5cbiAgICAgIDxtYXQtaWNvbiBjbGFzcz1cImRlYi1kcmFnLWhhbmRsZVwiIGNka0RyYWdIYW5kbGUgdGl0bGU9XCJEcmFnIHRvIHJlb3JkZXJcIj5kcmFnX2luZGljYXRvcjwvbWF0LWljb24+XG4gICAgICA8bWF0LWljb24gY2xhc3M9XCJkZWItZmllbGQtdHlwZVwiIFttYXRUb29sdGlwXT1cImZpZWxkVHlwZUxhYmVsKGZpZWxkLnR5cGUpXCI+e3sgZmllbGRUeXBlSWNvbihmaWVsZC50eXBlKSB9fTwvbWF0LWljb24+XG4gICAgICA8ZGl2IGNsYXNzPVwiZGViLWZpZWxkLW1ldGFcIj5cbiAgICAgICAgPGRpdiBjbGFzcz1cImRlYi1yb3dcIiBzdHlsZT1cImdhcDo2cHhcIj5cbiAgICAgICAgICA8c3BhbiBjbGFzcz1cImRlYi1maWVsZC1sYWJlbFwiIFthdHRyLmRhdGEtdGVzdGlkXT1cIidyb3ctbGFiZWwtJyArIGZpZWxkLmlkXCI+e3sgZmllbGRMYWJlbChmaWVsZCkgfX08L3NwYW4+XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJkZWItdHlwZS1iYWRnZVwiPnt7IGZpZWxkLnR5cGUgfX08L3NwYW4+XG4gICAgICAgICAgQGlmIChmaWVsZC5pc1JlZmVyZW5jZWQpIHtcbiAgICAgICAgICAgIDxtYXQtaWNvbiBzdHlsZT1cImZvbnQtc2l6ZTogMTZweDsgd2lkdGg6IDE2cHg7IGhlaWdodDogMTZweDsgY29sb3I6ICMxOTc2ZDI7XCIgbWF0VG9vbHRpcD1cIlJlZmVyZW5jZWQgZmllbGQgKGxpbmtlZCB0byB7eyBmaWVsZC5yZWZlcmVuY2VkRW50aXR5S2V5IH19KVwiPmxpbms8L21hdC1pY29uPlxuICAgICAgICAgIH1cbiAgICAgICAgICBAaWYgKGZpZWxkLmhhc0RyaWZ0KSB7XG4gICAgICAgICAgICA8bWF0LWljb24gc3R5bGU9XCJmb250LXNpemU6IDE2cHg7IHdpZHRoOiAxNnB4OyBoZWlnaHQ6IDE2cHg7IGNvbG9yOiAjZDMyZjJmO1wiIGRhdGEtdGVzdGlkPVwiZHJpZnQtd2FybmluZy1pY29uXCIgbWF0VG9vbHRpcD1cIkRyaWZ0IGRldGVjdGVkOiBzb3VyY2UgZmllbGQgaGFzIGV2b2x2ZWRcIj5zeW5jX3Byb2JsZW08L21hdC1pY29uPlxuICAgICAgICAgIH1cbiAgICAgICAgPC9kaXY+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZGViLWZpZWxkLWlkXCIgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ3Jvdy1pZC0nICsgZmllbGQuaWRcIj57eyBmaWVsZC5pZCB9fTwvc3Bhbj5cbiAgICAgIDwvZGl2PlxuICAgICAgQGlmIChmaWVsZC52YWxpZGF0b3JzPy5yZXF1aXJlZCkgeyA8c3BhbiBjbGFzcz1cImRlYi1yZXFcIiB0aXRsZT1cIlJlcXVpcmVkXCI+Kjwvc3Bhbj4gfVxuXG4gICAgICA8YnV0dG9uIG1hdC1pY29uLWJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgW2Rpc2FibGVkXT1cImluZGV4ID09PSAwXCJcbiAgICAgICAgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ3Jvdy11cC0nICsgZmllbGQuaWRcIlxuICAgICAgICAoY2xpY2spPVwic3RvcmUubW92ZUZpZWxkKGZpZWxkLmlkLCAtMSk7ICRldmVudC5zdG9wUHJvcGFnYXRpb24oKVwiIG1hdFRvb2x0aXA9XCJNb3ZlIHVwXCI+XG4gICAgICAgIDxtYXQtaWNvbj5hcnJvd191cHdhcmQ8L21hdC1pY29uPlxuICAgICAgPC9idXR0b24+XG4gICAgICA8YnV0dG9uIG1hdC1pY29uLWJ1dHRvbiB0eXBlPVwiYnV0dG9uXCIgW2Rpc2FibGVkXT1cImluZGV4ID09PSB0b3RhbENvdW50IC0gMVwiXG4gICAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidyb3ctZG93bi0nICsgZmllbGQuaWRcIlxuICAgICAgICAoY2xpY2spPVwic3RvcmUubW92ZUZpZWxkKGZpZWxkLmlkLCAxKTsgJGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXCIgbWF0VG9vbHRpcD1cIk1vdmUgZG93blwiPlxuICAgICAgICA8bWF0LWljb24+YXJyb3dfZG93bndhcmQ8L21hdC1pY29uPlxuICAgICAgPC9idXR0b24+XG4gICAgICA8YnV0dG9uIG1hdC1pY29uLWJ1dHRvbiB0eXBlPVwiYnV0dG9uXCJcbiAgICAgICAgW2F0dHIuZGF0YS10ZXN0aWRdPVwiJ3Jvdy1kdXBsaWNhdGUtJyArIGZpZWxkLmlkXCJcbiAgICAgICAgKGNsaWNrKT1cInN0b3JlLmR1cGxpY2F0ZUZpZWxkKGZpZWxkLmlkKTsgJGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXCIgbWF0VG9vbHRpcD1cIkR1cGxpY2F0ZVwiPlxuICAgICAgICA8bWF0LWljb24+Y29udGVudF9jb3B5PC9tYXQtaWNvbj5cbiAgICAgIDwvYnV0dG9uPlxuICAgICAgPGJ1dHRvbiBtYXQtaWNvbi1idXR0b24gdHlwZT1cImJ1dHRvblwiIGNvbG9yPVwid2FyblwiXG4gICAgICAgIFthdHRyLmRhdGEtdGVzdGlkXT1cIidyb3ctZGVsZXRlLScgKyBmaWVsZC5pZFwiXG4gICAgICAgIChjbGljayk9XCJzdG9yZS5yZW1vdmVGaWVsZChmaWVsZC5pZCk7ICRldmVudC5zdG9wUHJvcGFnYXRpb24oKVwiIG1hdFRvb2x0aXA9XCJEZWxldGVcIj5cbiAgICAgICAgPG1hdC1pY29uPmRlbGV0ZTwvbWF0LWljb24+XG4gICAgICA8L2J1dHRvbj5cbiAgICA8L2Rpdj5cblxuICAgIEBpZiAoZmllbGQuY2hpbGRyZW4gJiYgZmllbGQuY2hpbGRyZW4ubGVuZ3RoID4gMCkge1xuICAgICAgPGRpdiBjbGFzcz1cImRlYi10cmVlLWNoaWxkcmVuXCIgc3R5bGU9XCJwYWRkaW5nLWxlZnQ6IDIwcHg7IGJvcmRlci1sZWZ0OiAycHggc29saWQgcmdiYSgwLDAsMCwwLjA4KTsgbWFyZ2luLWxlZnQ6IDEycHg7IG1hcmdpbi10b3A6IDRweDtcIj5cbiAgICAgICAgQGZvciAoY2hpbGQgb2YgZmllbGQuY2hpbGRyZW47IHRyYWNrIGNoaWxkLmlkOyBsZXQgY2kgPSAkaW5kZXgsIGNDb3VudCA9ICRjb3VudCkge1xuICAgICAgICAgIDxuZ3gtZW50aXR5LWJ1aWxkZXItdHJlZS1ub2RlXG4gICAgICAgICAgICBbZmllbGRdPVwiY2hpbGRcIlxuICAgICAgICAgICAgW2luZGV4XT1cImNpXCJcbiAgICAgICAgICAgIFt0b3RhbENvdW50XT1cImNDb3VudFwiXG4gICAgICAgICAgLz5cbiAgICAgICAgfVxuICAgICAgPC9kaXY+XG4gICAgfVxuICBgLFxufSlcbmV4cG9ydCBjbGFzcyBFbnRpdHlCdWlsZGVyVHJlZU5vZGVDb21wb25lbnQge1xuICBwcm90ZWN0ZWQgcmVhZG9ubHkgc3RvcmUgPSBpbmplY3QoQnVpbGRlclN0b3JlKTtcblxuICBASW5wdXQoeyByZXF1aXJlZDogdHJ1ZSB9KSBmaWVsZCE6IE5lc3RlZEZpZWxkQ29uZmlnO1xuICBASW5wdXQoeyByZXF1aXJlZDogdHJ1ZSB9KSBpbmRleCE6IG51bWJlcjtcbiAgQElucHV0KHsgcmVxdWlyZWQ6IHRydWUgfSkgdG90YWxDb3VudCE6IG51bWJlcjtcblxuICBwcm90ZWN0ZWQgZmllbGRUeXBlTGFiZWwodHlwZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gZ2V0RmllbGRUeXBlTWV0YSh0eXBlKT8ubGFiZWwgPz8gdHlwZTtcbiAgfVxuXG4gIHByb3RlY3RlZCBmaWVsZFR5cGVJY29uKHR5cGU6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGdldEZpZWxkVHlwZU1ldGEodHlwZSk/Lmljb24gPz8gJ2hlbHBfb3V0bGluZSc7XG4gIH1cblxuICBwcm90ZWN0ZWQgZmllbGRMYWJlbChmaWVsZDogeyBpZDogc3RyaW5nOyBsYWJlbD86IGFueSB9KTogc3RyaW5nIHtcbiAgICBjb25zdCBsYW5nID0gdGhpcy5zdG9yZS5hY3RpdmVMYW5ndWFnZSgpO1xuICAgIHJldHVybiByZXNvbHZlTGFiZWwoZmllbGQubGFiZWwsIGxhbmcpIHx8IGZpZWxkLmlkO1xuICB9XG59XG4iXX0=