ngx-dynamic-entity-builder 1.3.0 → 1.4.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.
- package/README.md +2 -1
- package/fesm2022/ngx-dynamic-entity-builder.mjs +409 -92
- package/fesm2022/ngx-dynamic-entity-builder.mjs.map +1 -1
- package/package.json +7 -7
- package/types/ngx-dynamic-entity-builder.d.ts +523 -0
- package/esm2022/lib/builder-store.service.mjs +0 -1057
- package/esm2022/lib/clone.mjs +0 -14
- package/esm2022/lib/components/entity-builder-canvas.component.mjs +0 -84
- package/esm2022/lib/components/entity-builder-tree-node.component.mjs +0 -192
- package/esm2022/lib/components/entity-reference-config.component.mjs +0 -405
- package/esm2022/lib/components/field-inspector.component.mjs +0 -139
- package/esm2022/lib/components/field-palette.component.mjs +0 -57
- package/esm2022/lib/components/field-rules-list.component.mjs +0 -224
- package/esm2022/lib/components/referenced-field-config.component.mjs +0 -235
- package/esm2022/lib/components/rule-form.component.mjs +0 -201
- package/esm2022/lib/components/tab-manager.component.mjs +0 -354
- package/esm2022/lib/entity-builder.component.mjs +0 -159
- package/esm2022/lib/field-catalog.mjs +0 -6
- package/esm2022/ngx-dynamic-entity-builder.mjs +0 -5
- package/esm2022/public-api.mjs +0 -16
- package/index.d.ts +0 -5
- package/lib/builder-store.service.d.ts +0 -206
- package/lib/clone.d.ts +0 -9
- package/lib/components/entity-builder-canvas.component.d.ts +0 -9
- package/lib/components/entity-builder-tree-node.component.d.ts +0 -17
- package/lib/components/entity-reference-config.component.d.ts +0 -29
- package/lib/components/field-inspector.component.d.ts +0 -33
- package/lib/components/field-palette.component.d.ts +0 -13
- package/lib/components/field-rules-list.component.d.ts +0 -19
- package/lib/components/referenced-field-config.component.d.ts +0 -24
- package/lib/components/rule-form.component.d.ts +0 -16
- package/lib/components/tab-manager.component.d.ts +0 -28
- package/lib/entity-builder.component.d.ts +0 -45
- package/lib/field-catalog.d.ts +0 -5
- package/public-api.d.ts +0 -12
|
@@ -18,19 +18,18 @@ import * as i5 from '@angular/material/input';
|
|
|
18
18
|
import { MatInputModule } from '@angular/material/input';
|
|
19
19
|
import * as i8 from '@angular/material/select';
|
|
20
20
|
import { MatSelectModule } from '@angular/material/select';
|
|
21
|
-
import * as
|
|
21
|
+
import * as i9 from '@angular/material/slide-toggle';
|
|
22
22
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
23
|
-
import * as
|
|
23
|
+
import * as i10 from '@angular/material/toolbar';
|
|
24
24
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
25
25
|
import * as i4$2 from '@angular/material/tooltip';
|
|
26
26
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
27
|
-
import { normalizeConfigOptions, getFieldTypeMeta, createFieldConfig, labelToId, createFieldSnapshot, computeFieldDrift, humanizeId, findTab, resolveLabel, resolveOptionLabel, FIELD_TYPE_CATALOG, COMMON_MODULES } from '@dynamic-entity/core';
|
|
27
|
+
import { collectFieldScopes, assignFieldRefs, normalizeConfigOptions, getFieldTypeMeta, createFieldConfig, labelToId, createFieldSnapshot, computeFieldDrift, toRefToken, humanizeId, findTab, fieldRefFor, parseFieldRef, resolveLabel, refOf, resolveOptionLabel, FIELD_TYPE_CATALOG, COMMON_MODULES } from '@dynamic-entity/core';
|
|
28
28
|
export { FIELD_TYPE_CATALOG, createFieldConfig, getFieldTypeMeta, humanizeId } from '@dynamic-entity/core';
|
|
29
29
|
import { ConfigSourceService, SYSTEM_DEFAULT_CAN_EDIT } from 'ngx-dynamic-entity';
|
|
30
30
|
export { SYSTEM_DEFAULT_CAN_EDIT } from 'ngx-dynamic-entity';
|
|
31
31
|
import * as i3 from '@angular/material/divider';
|
|
32
32
|
import { MatDividerModule } from '@angular/material/divider';
|
|
33
|
-
import * as i9 from '@angular/material/core';
|
|
34
33
|
import { CommonModule } from '@angular/common';
|
|
35
34
|
import * as i1$1 from '@angular/cdk/drag-drop';
|
|
36
35
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
@@ -65,46 +64,84 @@ const ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
65
64
|
*/
|
|
66
65
|
const LEGACY_EMAIL_PATTERN = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$';
|
|
67
66
|
class BuilderStore {
|
|
68
|
-
_config = signal(this.emptyConfig());
|
|
69
|
-
_selectedFieldId = signal(null);
|
|
70
|
-
_activeLanguage = signal('en');
|
|
67
|
+
_config = signal(this.emptyConfig(), ...(ngDevMode ? [{ debugName: "_config" }] : /* istanbul ignore next */ []));
|
|
68
|
+
_selectedFieldId = signal(null, ...(ngDevMode ? [{ debugName: "_selectedFieldId" }] : /* istanbul ignore next */ []));
|
|
69
|
+
_activeLanguage = signal('en', ...(ngDevMode ? [{ debugName: "_activeLanguage" }] : /* istanbul ignore next */ []));
|
|
71
70
|
/** Rules live beside the config: they are persisted per form, not inside `EntityFormConfig`. */
|
|
72
|
-
_rules = signal([]);
|
|
71
|
+
_rules = signal([], ...(ngDevMode ? [{ debugName: "_rules" }] : /* istanbul ignore next */ []));
|
|
73
72
|
/**
|
|
74
73
|
* Field ids the author owns — set by an explicit rename, or by loading a saved config
|
|
75
74
|
* whose ids are already live data keys. Ids outside this set are still derived from the
|
|
76
75
|
* field's label. Builder-session state, deliberately not part of the exported config.
|
|
77
76
|
*/
|
|
78
77
|
manualIds = new Set();
|
|
79
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Fields whose `refererField` the config declared, rather than the builder stamping it.
|
|
80
|
+
*
|
|
81
|
+
* A declared path is a deliberate binding override and must survive a structural edit. A
|
|
82
|
+
* stamped one is just the field's address, so it has to be rewritten when the field moves.
|
|
83
|
+
* Without the distinction, restamping would destroy the override and not restamping would
|
|
84
|
+
* leave a moved field pointing at where it used to be.
|
|
85
|
+
*/
|
|
86
|
+
authoredRefs = new Set();
|
|
87
|
+
_isDirty = signal(false, ...(ngDevMode ? [{ debugName: "_isDirty" }] : /* istanbul ignore next */ []));
|
|
80
88
|
/**
|
|
81
89
|
* Roles of the person using the builder — distinct from `availableRoles`, which is the
|
|
82
90
|
* vocabulary a schema may reference. Needed so the SYSTEM_DEFAULT_CAN_EDIT predicate can
|
|
83
91
|
* be asked a real question; it was previously called with a hardcoded empty array, which
|
|
84
92
|
* made any role-checking predicate answer false for everyone.
|
|
85
93
|
*/
|
|
86
|
-
_userRoles = signal([]);
|
|
94
|
+
_userRoles = signal([], ...(ngDevMode ? [{ debugName: "_userRoles" }] : /* istanbul ignore next */ []));
|
|
87
95
|
config = this._config.asReadonly();
|
|
88
96
|
selectedFieldId = this._selectedFieldId.asReadonly();
|
|
89
97
|
activeLanguage = this._activeLanguage.asReadonly();
|
|
90
98
|
rules = this._rules.asReadonly();
|
|
91
99
|
isDirty = this._isDirty.asReadonly();
|
|
92
100
|
userRoles = this._userRoles.asReadonly();
|
|
93
|
-
tabs = computed(() => this._config().tabs ?? []);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
tabs = computed(() => this._config().tabs ?? [], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
102
|
+
/**
|
|
103
|
+
* Every field the config declares, sub-tabs included.
|
|
104
|
+
*
|
|
105
|
+
* This used to stop at top-level tabs, which quietly broke three things at once: nine of
|
|
106
|
+
* `insuranceClaims`'s twenty-eight fields never appeared on the canvas, the entity-reference
|
|
107
|
+
* picker could not offer a nested field as a source, and the drift check looked a nested
|
|
108
|
+
* field up here, got nothing, and returned without checking. The store's structural
|
|
109
|
+
* operations already walked the whole tree — only this view did not.
|
|
110
|
+
*
|
|
111
|
+
* It deliberately does not descend into a field's own `children`: `group` and `array`
|
|
112
|
+
* children are rendered by the row that owns them, not as rows of their own.
|
|
113
|
+
*/
|
|
114
|
+
fields = computed(() => this.getAllFields(this._config().tabs ?? []), ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
|
|
115
|
+
/**
|
|
116
|
+
* The same fields, kept in their owning tab.
|
|
117
|
+
*
|
|
118
|
+
* Drag-and-drop reorders by index, so the canvas needs to know which tab an index belongs
|
|
119
|
+
* to — `reorderField` has always taken a `tabId` for exactly this, and the canvas simply
|
|
120
|
+
* never passed one. Without the grouping a drag on any config with more than one tab
|
|
121
|
+
* reordered `tabs[0]` regardless of what was actually dragged.
|
|
122
|
+
*/
|
|
123
|
+
fieldGroups = computed(() => this.flattenTabs(this._config().tabs ?? [])
|
|
124
|
+
.filter(tab => (tab.fields ?? []).length > 0)
|
|
125
|
+
.map(tab => ({ tabId: tab.id, label: tab.label, fields: tab.fields ?? [] })), ...(ngDevMode ? [{ debugName: "fieldGroups" }] : /* istanbul ignore next */ []));
|
|
97
126
|
selectedField = computed(() => {
|
|
98
127
|
const id = this._selectedFieldId();
|
|
99
128
|
if (!id)
|
|
100
129
|
return null;
|
|
101
130
|
return this.findFieldInTabs(this._config().tabs, id);
|
|
102
|
-
});
|
|
103
|
-
problems = computed(() => this.validate(this._config()));
|
|
104
|
-
errors = computed(() => this.problems().filter(p => p.level === 'error'));
|
|
105
|
-
isValid = computed(() => this.errors().length === 0);
|
|
131
|
+
}, ...(ngDevMode ? [{ debugName: "selectedField" }] : /* istanbul ignore next */ []));
|
|
132
|
+
problems = computed(() => this.validate(this._config()), ...(ngDevMode ? [{ debugName: "problems" }] : /* istanbul ignore next */ []));
|
|
133
|
+
errors = computed(() => this.problems().filter(p => p.level === 'error'), ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
134
|
+
isValid = computed(() => this.errors().length === 0, ...(ngDevMode ? [{ debugName: "isValid" }] : /* istanbul ignore next */ []));
|
|
106
135
|
// ─── Initialisation ─────────────────────────────────────────────────────────
|
|
107
136
|
load(config) {
|
|
137
|
+
// Remember the paths the config declared before filling in the rest, so a deliberate
|
|
138
|
+
// binding override is never mistaken for one of ours and rewritten.
|
|
139
|
+
this.authoredRefs.clear();
|
|
140
|
+
for (const entry of collectFieldScopes(config)) {
|
|
141
|
+
if (entry.field?.id && entry.field.refererField)
|
|
142
|
+
this.authoredRefs.add(entry.field.id);
|
|
143
|
+
}
|
|
144
|
+
assignFieldRefs(config);
|
|
108
145
|
// Check if any fields carry both inline options and listName before normalizing options
|
|
109
146
|
const fieldsWithBoth = this.getAllFields(config.tabs ?? []).filter(f => Array.isArray(f.options) && f.options.length > 0 && typeof f.listName === 'string' && f.listName.trim() !== '');
|
|
110
147
|
for (const f of fieldsWithBoth) {
|
|
@@ -404,6 +441,41 @@ class BuilderStore {
|
|
|
404
441
|
}
|
|
405
442
|
});
|
|
406
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Moves a field to another tab, keeping its id.
|
|
446
|
+
*
|
|
447
|
+
* The builder could add, remove, duplicate and reorder a field but never relocate one, so
|
|
448
|
+
* a field authored on the wrong tab had to be deleted and rebuilt — losing its validators,
|
|
449
|
+
* options and any rule that pointed at it.
|
|
450
|
+
*
|
|
451
|
+
* The field's `refererField` changes, because it is an address and the field now has a new one.
|
|
452
|
+
* `mutate` restamps it and repoints the rules that named it, so a move does not silently
|
|
453
|
+
* strand them.
|
|
454
|
+
*/
|
|
455
|
+
moveFieldToTab(id, targetTabId) {
|
|
456
|
+
const target = this.flattenTabs(this._config().tabs).find(t => t.id === targetTabId);
|
|
457
|
+
if (!target)
|
|
458
|
+
return false;
|
|
459
|
+
let moved = false;
|
|
460
|
+
this.mutate(draft => {
|
|
461
|
+
const targetTab = this.flattenTabs(draft.tabs).find(t => t.id === targetTabId);
|
|
462
|
+
if (!targetTab)
|
|
463
|
+
return;
|
|
464
|
+
for (const tab of this.flattenTabs(draft.tabs)) {
|
|
465
|
+
const index = (tab.fields ?? []).findIndex(f => f.id === id);
|
|
466
|
+
if (index === -1)
|
|
467
|
+
continue;
|
|
468
|
+
if (tab.id === targetTabId)
|
|
469
|
+
return; // already there; nothing to do
|
|
470
|
+
const [field] = tab.fields.splice(index, 1);
|
|
471
|
+
targetTab.fields = targetTab.fields ?? [];
|
|
472
|
+
targetTab.fields.push(field);
|
|
473
|
+
moved = true;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return moved;
|
|
478
|
+
}
|
|
407
479
|
/** Safe reorder for drag & drop within active tab or flat list */
|
|
408
480
|
reorderField(fromIndex, toIndex, tabId) {
|
|
409
481
|
this.mutate(draft => {
|
|
@@ -817,12 +889,24 @@ class BuilderStore {
|
|
|
817
889
|
}
|
|
818
890
|
// ─── Rules ──────────────────────────────────────────────────────────────────
|
|
819
891
|
/** Rules for the field currently selected in the inspector. */
|
|
892
|
+
/**
|
|
893
|
+
* Rules attached to the selected field, whichever way they name it.
|
|
894
|
+
*
|
|
895
|
+
* New rules are authored with the field's path — `[work.address]` — because a bare id
|
|
896
|
+
* cannot distinguish two fields that share one. Rules written before paths existed name it
|
|
897
|
+
* by id, and those must keep showing up here or editing an older config would look like it
|
|
898
|
+
* had lost its rules.
|
|
899
|
+
*/
|
|
820
900
|
rulesForSelectedField = computed(() => {
|
|
821
901
|
const id = this._selectedFieldId();
|
|
822
902
|
if (!id)
|
|
823
903
|
return [];
|
|
824
|
-
|
|
825
|
-
|
|
904
|
+
const names = new Set([id]);
|
|
905
|
+
const ref = this.selectedField()?.refererField;
|
|
906
|
+
if (ref)
|
|
907
|
+
names.add(toRefToken(ref));
|
|
908
|
+
return this._rules().filter(r => names.has(r.fieldId) || r.targets.some(t => names.has(t.id)));
|
|
909
|
+
}, ...(ngDevMode ? [{ debugName: "rulesForSelectedField" }] : /* istanbul ignore next */ []));
|
|
826
910
|
loadRules(rules) {
|
|
827
911
|
this._rules.set(clone(rules));
|
|
828
912
|
}
|
|
@@ -952,13 +1036,87 @@ class BuilderStore {
|
|
|
952
1036
|
return clone(this._config());
|
|
953
1037
|
}
|
|
954
1038
|
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
1039
|
+
/**
|
|
1040
|
+
* The single choke point for a structural edit, and therefore the one place refs are kept
|
|
1041
|
+
* true.
|
|
1042
|
+
*
|
|
1043
|
+
* `refererField` is a field's address, so it changes whenever the structure around it does —
|
|
1044
|
+
* moving a field to another tab, or into a group, gives it a new one. Rules address fields
|
|
1045
|
+
* by ref, so a move that only restamped the field would leave every rule pointing at an
|
|
1046
|
+
* address nothing occupies. Both happen here, together, or the config is inconsistent
|
|
1047
|
+
* between them.
|
|
1048
|
+
*/
|
|
955
1049
|
mutate(fn) {
|
|
1050
|
+
const before = this.refsById(this._config());
|
|
956
1051
|
const draft = clone(this._config());
|
|
957
1052
|
draft.tabs = draft.tabs ?? [];
|
|
958
1053
|
fn(draft);
|
|
1054
|
+
this.restampFieldPaths(draft);
|
|
1055
|
+
this.repointRulesForMovedFields(before, this.refsById(draft));
|
|
959
1056
|
this._config.set(draft);
|
|
960
1057
|
this._isDirty.set(true);
|
|
961
1058
|
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Rewrites the address of every field the builder owns, leaving declared overrides alone.
|
|
1061
|
+
*
|
|
1062
|
+
* `assignFieldRefs` only fills in a missing path, which is right when a config arrives and
|
|
1063
|
+
* wrong after a move: the field already has a path, and it is now the wrong one.
|
|
1064
|
+
*/
|
|
1065
|
+
restampFieldPaths(config) {
|
|
1066
|
+
for (const entry of collectFieldScopes(config)) {
|
|
1067
|
+
const field = entry.field;
|
|
1068
|
+
if (!field?.id || this.authoredRefs.has(field.id))
|
|
1069
|
+
continue;
|
|
1070
|
+
field.refererField = fieldRefFor(entry.scope, field.id);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/** Field id → the refs it currently has. A duplicated id has more than one. */
|
|
1074
|
+
refsById(config) {
|
|
1075
|
+
const map = new Map();
|
|
1076
|
+
for (const entry of collectFieldScopes(config)) {
|
|
1077
|
+
if (!entry.field?.id)
|
|
1078
|
+
continue;
|
|
1079
|
+
const refs = map.get(entry.field.id) ?? [];
|
|
1080
|
+
refs.push(fieldRefFor(entry.scope, entry.field.id));
|
|
1081
|
+
map.set(entry.field.id, refs);
|
|
1082
|
+
}
|
|
1083
|
+
return map;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Rewrites rules that pointed at a ref a move has just changed.
|
|
1087
|
+
*
|
|
1088
|
+
* A field is matched by its id: if exactly one ref for that id disappeared and exactly one
|
|
1089
|
+
* appeared, that is the move and the rules follow it. Anything less clear-cut — two fields
|
|
1090
|
+
* sharing an id where one moved — is left alone rather than guessed at, because rewriting
|
|
1091
|
+
* the wrong rule is worse than leaving one to be repointed by hand.
|
|
1092
|
+
*/
|
|
1093
|
+
repointRulesForMovedFields(before, after) {
|
|
1094
|
+
const moves = new Map();
|
|
1095
|
+
for (const [id, oldRefs] of before) {
|
|
1096
|
+
const newRefs = after.get(id) ?? [];
|
|
1097
|
+
const gone = oldRefs.filter(r => !newRefs.includes(r));
|
|
1098
|
+
const added = newRefs.filter(r => !oldRefs.includes(r));
|
|
1099
|
+
if (gone.length === 1 && added.length === 1)
|
|
1100
|
+
moves.set(gone[0], added[0]);
|
|
1101
|
+
}
|
|
1102
|
+
if (moves.size === 0)
|
|
1103
|
+
return;
|
|
1104
|
+
const repoint = (reference) => {
|
|
1105
|
+
if (!reference)
|
|
1106
|
+
return reference;
|
|
1107
|
+
const parsed = parseFieldRef(reference);
|
|
1108
|
+
if (parsed.kind !== 'ref')
|
|
1109
|
+
return reference;
|
|
1110
|
+
const moved = moves.get(parsed.value);
|
|
1111
|
+
return moved ? toRefToken(moved) : reference;
|
|
1112
|
+
};
|
|
1113
|
+
this._rules.update(rules => rules.map(rule => ({
|
|
1114
|
+
...rule,
|
|
1115
|
+
fieldId: repoint(rule.fieldId) ?? rule.fieldId,
|
|
1116
|
+
conditions: rule.conditions.map(c => c.compareToField ? { ...c, compareToField: repoint(c.compareToField) } : c),
|
|
1117
|
+
targets: rule.targets.map(t => ({ ...t, id: repoint(t.id) ?? t.id })),
|
|
1118
|
+
})));
|
|
1119
|
+
}
|
|
962
1120
|
/**
|
|
963
1121
|
* Replace one field, copying only the objects on the path to it.
|
|
964
1122
|
*
|
|
@@ -1099,10 +1257,10 @@ class BuilderStore {
|
|
|
1099
1257
|
}
|
|
1100
1258
|
return problems;
|
|
1101
1259
|
}
|
|
1102
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1103
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
1260
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1261
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderStore });
|
|
1104
1262
|
}
|
|
1105
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1263
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderStore, decorators: [{
|
|
1106
1264
|
type: Injectable
|
|
1107
1265
|
}] });
|
|
1108
1266
|
|
|
@@ -1120,8 +1278,8 @@ class EntityReferenceConfigComponent {
|
|
|
1120
1278
|
parentCandidates = computed(() => {
|
|
1121
1279
|
const current = this.field();
|
|
1122
1280
|
return this.store.fields().filter(f => f.id !== current?.id);
|
|
1123
|
-
});
|
|
1124
|
-
filtersParseError = signal(null);
|
|
1281
|
+
}, ...(ngDevMode ? [{ debugName: "parentCandidates" }] : /* istanbul ignore next */ []));
|
|
1282
|
+
filtersParseError = signal(null, ...(ngDevMode ? [{ debugName: "filtersParseError" }] : /* istanbul ignore next */ []));
|
|
1125
1283
|
filtersError = this.filtersParseError.asReadonly();
|
|
1126
1284
|
ref(field) {
|
|
1127
1285
|
return field.entityReference ?? { enabled: true };
|
|
@@ -1171,8 +1329,8 @@ class EntityReferenceConfigComponent {
|
|
|
1171
1329
|
this.filtersParseError.set('Invalid JSON.');
|
|
1172
1330
|
}
|
|
1173
1331
|
}
|
|
1174
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1175
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1332
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1333
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityReferenceConfigComponent, isStandalone: true, selector: "ngx-entity-reference-config", ngImport: i0, template: `
|
|
1176
1334
|
@if (field(); as f) {
|
|
1177
1335
|
<div class="deb-entity-ref">
|
|
1178
1336
|
<span class="deb-section-title">Entity reference</span>
|
|
@@ -1322,9 +1480,9 @@ class EntityReferenceConfigComponent {
|
|
|
1322
1480
|
}
|
|
1323
1481
|
</div>
|
|
1324
1482
|
}
|
|
1325
|
-
`, isInline: true, styles: [".deb-entity-ref{display:flex;flex-direction:column;gap:12px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row--split{justify-content:space-between}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-option-row>mat-form-field{flex:1}.deb-error-hint{color:#b91c1c}\n"], dependencies: [{ 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
1483
|
+
`, isInline: true, styles: [".deb-entity-ref{display:flex;flex-direction:column;gap:12px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row--split{justify-content:space-between}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-option-row>mat-form-field{flex:1}.deb-error-hint{color:#b91c1c}\n"], dependencies: [{ 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: 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: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1326
1484
|
}
|
|
1327
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1485
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceConfigComponent, decorators: [{
|
|
1328
1486
|
type: Component,
|
|
1329
1487
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-entity-reference-config', standalone: true, imports: [
|
|
1330
1488
|
FormsModule,
|
|
@@ -1493,6 +1651,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1493
1651
|
* RuleFormComponent — dialog/panel form for creating or editing a FormRule.
|
|
1494
1652
|
*/
|
|
1495
1653
|
class RuleFormComponent {
|
|
1654
|
+
store = inject(BuilderStore);
|
|
1496
1655
|
rule = {
|
|
1497
1656
|
formConfigId: 'form-1',
|
|
1498
1657
|
fieldId: '',
|
|
@@ -1524,21 +1683,107 @@ class RuleFormComponent {
|
|
|
1524
1683
|
'HAS_ITEMS',
|
|
1525
1684
|
'VALUE_CHANGED',
|
|
1526
1685
|
];
|
|
1686
|
+
/**
|
|
1687
|
+
* Every field in the config, offered by path.
|
|
1688
|
+
*
|
|
1689
|
+
* Both pickers used to be free text — the trigger was typed by hand and the targets could
|
|
1690
|
+
* not be edited at all, so a rule only ever acted on the field it triggered from. Typing an
|
|
1691
|
+
* id is also the one way left to write an ambiguous reference: ids are unique per scope, so
|
|
1692
|
+
* `address` names nothing in particular once two tabs have one. Choosing from this list
|
|
1693
|
+
* always yields a path.
|
|
1694
|
+
*/
|
|
1695
|
+
fieldOptions = computed(() => {
|
|
1696
|
+
const language = this.store.activeLanguage();
|
|
1697
|
+
// `collectFieldScopes` rather than `fieldGroups`: it reaches fields nested inside a
|
|
1698
|
+
// `group` too, and it carries each field's scope, so `refOf` always has a path to fall
|
|
1699
|
+
// back to and this component needs no fallback of its own.
|
|
1700
|
+
return collectFieldScopes(this.store.config()).map(entry => {
|
|
1701
|
+
const path = refOf(entry.field, entry.scope);
|
|
1702
|
+
return {
|
|
1703
|
+
value: toRefToken(path),
|
|
1704
|
+
label: resolveLabel(entry.field.label, language) || entry.field.id,
|
|
1705
|
+
path,
|
|
1706
|
+
};
|
|
1707
|
+
});
|
|
1708
|
+
}, ...(ngDevMode ? [{ debugName: "fieldOptions" }] : /* istanbul ignore next */ []));
|
|
1709
|
+
/**
|
|
1710
|
+
* The options plus whatever the rule already names.
|
|
1711
|
+
*
|
|
1712
|
+
* A rule written before paths existed holds a bare id, and one written against a field that
|
|
1713
|
+
* has since been deleted holds a path nothing answers to. Neither appears in the list, and a
|
|
1714
|
+
* `mat-select` silently drops a value it has no option for — so opening such a rule and
|
|
1715
|
+
* saving it would quietly erase the reference. They are kept, and shown as they are.
|
|
1716
|
+
*/
|
|
1717
|
+
withExisting(options, current) {
|
|
1718
|
+
const known = new Set(options.map(o => o.value));
|
|
1719
|
+
const extra = current
|
|
1720
|
+
.filter(value => value && !known.has(value))
|
|
1721
|
+
.map(value => ({ value, label: value, path: 'not in this config' }));
|
|
1722
|
+
return [...options, ...extra];
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Methods, not `computed`. Both read `rule`, which is a plain `@Input` object mutated in
|
|
1726
|
+
* place rather than a signal — a computed would cache the first evaluation and never see a
|
|
1727
|
+
* different rule being edited.
|
|
1728
|
+
*/
|
|
1729
|
+
triggerOptions() {
|
|
1730
|
+
return this.withExisting(this.fieldOptions(), [this.rule.fieldId]);
|
|
1731
|
+
}
|
|
1732
|
+
targetOptions() {
|
|
1733
|
+
return this.withExisting(this.fieldOptions(), this.targetValues());
|
|
1734
|
+
}
|
|
1735
|
+
/** Field targets only — a rule may also target a tab, which this picker does not manage. */
|
|
1736
|
+
targetValues() {
|
|
1737
|
+
return this.rule.targets.filter(t => t.type === 'field').map(t => t.id);
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Replaces the field targets, leaving any tab target untouched.
|
|
1741
|
+
*
|
|
1742
|
+
* Rebuilding `targets` wholesale from this picker would drop tab targets, which it never
|
|
1743
|
+
* offered and so cannot know about.
|
|
1744
|
+
*/
|
|
1745
|
+
setTargets(ids) {
|
|
1746
|
+
const others = this.rule.targets.filter(t => t.type !== 'field');
|
|
1747
|
+
this.rule.targets = [...others, ...ids.map(id => ({ id, type: 'field' }))];
|
|
1748
|
+
}
|
|
1527
1749
|
addCondition() {
|
|
1528
1750
|
this.rule.conditions.push({ operator: 'EQUAL', compareType: 'value', value: '' });
|
|
1529
1751
|
}
|
|
1530
1752
|
removeCondition(index) {
|
|
1531
1753
|
this.rule.conditions.splice(index, 1);
|
|
1532
1754
|
}
|
|
1533
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1534
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1755
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1756
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: RuleFormComponent, isStandalone: true, selector: "ngx-rule-form", inputs: { rule: "rule" }, outputs: { save: "save", cancel: "cancel" }, ngImport: i0, template: `
|
|
1535
1757
|
<div class="deb-rule-form">
|
|
1536
1758
|
<h3 class="deb-rule-form__title">{{ rule.id ? 'Edit Rule' : 'New Form Rule' }}</h3>
|
|
1537
1759
|
|
|
1538
1760
|
<!-- Trigger Field -->
|
|
1539
1761
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1540
|
-
<mat-label>Trigger Field
|
|
1541
|
-
<
|
|
1762
|
+
<mat-label>Trigger Field</mat-label>
|
|
1763
|
+
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
1764
|
+
@for (option of triggerOptions(); track option.value) {
|
|
1765
|
+
<mat-option [value]="option.value">
|
|
1766
|
+
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
1767
|
+
</mat-option>
|
|
1768
|
+
}
|
|
1769
|
+
</mat-select>
|
|
1770
|
+
</mat-form-field>
|
|
1771
|
+
|
|
1772
|
+
<!-- Targets: the fields this rule acts on -->
|
|
1773
|
+
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1774
|
+
<mat-label>Apply to fields</mat-label>
|
|
1775
|
+
<mat-select
|
|
1776
|
+
multiple
|
|
1777
|
+
data-testid="rule-targets"
|
|
1778
|
+
[ngModel]="targetValues()"
|
|
1779
|
+
(ngModelChange)="setTargets($event)"
|
|
1780
|
+
>
|
|
1781
|
+
@for (option of targetOptions(); track option.value) {
|
|
1782
|
+
<mat-option [value]="option.value">
|
|
1783
|
+
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
1784
|
+
</mat-option>
|
|
1785
|
+
}
|
|
1786
|
+
</mat-select>
|
|
1542
1787
|
</mat-form-field>
|
|
1543
1788
|
|
|
1544
1789
|
<!-- Conditions -->
|
|
@@ -1592,9 +1837,9 @@ class RuleFormComponent {
|
|
|
1592
1837
|
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">Save Rule</button>
|
|
1593
1838
|
</div>
|
|
1594
1839
|
</div>
|
|
1595
|
-
`, isInline: true, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"], 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
1840
|
+
`, isInline: true, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-path{color:#888;font-size:11px;margin-left:6px}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"], 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: 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: MatFormFieldModule }, { 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: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1596
1841
|
}
|
|
1597
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1842
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, decorators: [{
|
|
1598
1843
|
type: Component,
|
|
1599
1844
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-rule-form', standalone: true, imports: [
|
|
1600
1845
|
CommonModule,
|
|
@@ -1610,8 +1855,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1610
1855
|
|
|
1611
1856
|
<!-- Trigger Field -->
|
|
1612
1857
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1613
|
-
<mat-label>Trigger Field
|
|
1614
|
-
<
|
|
1858
|
+
<mat-label>Trigger Field</mat-label>
|
|
1859
|
+
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
1860
|
+
@for (option of triggerOptions(); track option.value) {
|
|
1861
|
+
<mat-option [value]="option.value">
|
|
1862
|
+
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
1863
|
+
</mat-option>
|
|
1864
|
+
}
|
|
1865
|
+
</mat-select>
|
|
1866
|
+
</mat-form-field>
|
|
1867
|
+
|
|
1868
|
+
<!-- Targets: the fields this rule acts on -->
|
|
1869
|
+
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1870
|
+
<mat-label>Apply to fields</mat-label>
|
|
1871
|
+
<mat-select
|
|
1872
|
+
multiple
|
|
1873
|
+
data-testid="rule-targets"
|
|
1874
|
+
[ngModel]="targetValues()"
|
|
1875
|
+
(ngModelChange)="setTargets($event)"
|
|
1876
|
+
>
|
|
1877
|
+
@for (option of targetOptions(); track option.value) {
|
|
1878
|
+
<mat-option [value]="option.value">
|
|
1879
|
+
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
1880
|
+
</mat-option>
|
|
1881
|
+
}
|
|
1882
|
+
</mat-select>
|
|
1615
1883
|
</mat-form-field>
|
|
1616
1884
|
|
|
1617
1885
|
<!-- Conditions -->
|
|
@@ -1665,7 +1933,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1665
1933
|
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">Save Rule</button>
|
|
1666
1934
|
</div>
|
|
1667
1935
|
</div>
|
|
1668
|
-
`, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"] }]
|
|
1936
|
+
`, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-path{color:#888;font-size:11px;margin-left:6px}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"] }]
|
|
1669
1937
|
}], propDecorators: { rule: [{
|
|
1670
1938
|
type: Input
|
|
1671
1939
|
}], save: [{
|
|
@@ -1681,17 +1949,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1681
1949
|
class FieldRulesListComponent {
|
|
1682
1950
|
store = inject(BuilderStore);
|
|
1683
1951
|
/** The rule currently open in the editor — a new draft or a copy of an existing rule. */
|
|
1684
|
-
editing = signal(null);
|
|
1952
|
+
editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
1685
1953
|
startCreate() {
|
|
1686
1954
|
const fieldId = this.store.selectedFieldId();
|
|
1687
1955
|
if (!fieldId)
|
|
1688
1956
|
return;
|
|
1957
|
+
// Author the field's path, not its id. Ids are unique per scope, so `address` may exist
|
|
1958
|
+
// on two tabs and a bare id cannot say which one the rule is about; `[work.address]` can.
|
|
1959
|
+
// A rule written by hand may still use a bare id, and both resolve at runtime.
|
|
1960
|
+
const ref = this.store.selectedField()?.refererField;
|
|
1961
|
+
const name = ref ? toRefToken(ref) : fieldId;
|
|
1689
1962
|
this.editing.set({
|
|
1690
1963
|
formConfigId: this.store.config().entity || 'form-1',
|
|
1691
|
-
fieldId,
|
|
1964
|
+
fieldId: name,
|
|
1692
1965
|
conditions: [{ operator: 'EQUAL', compareType: 'value', value: '' }],
|
|
1693
1966
|
action: { type: 'visibility', value: false },
|
|
1694
|
-
targets: [{ id:
|
|
1967
|
+
targets: [{ id: name, type: 'field' }],
|
|
1695
1968
|
enabled: true,
|
|
1696
1969
|
priority: this.store.rules().length + 1,
|
|
1697
1970
|
});
|
|
@@ -1721,8 +1994,8 @@ class FieldRulesListComponent {
|
|
|
1721
1994
|
: `${rule.action.type}: ${rule.action.value}`;
|
|
1722
1995
|
return `${rule.fieldId} ${cond} → ${action}`;
|
|
1723
1996
|
}
|
|
1724
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1725
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
1997
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRulesListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1998
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldRulesListComponent, isStandalone: true, selector: "ngx-field-rules-list", ngImport: i0, template: `
|
|
1726
1999
|
<div class="deb-rules">
|
|
1727
2000
|
<div class="deb-rules__head">
|
|
1728
2001
|
<span class="deb-section-title">Rules</span>
|
|
@@ -1800,9 +2073,9 @@ class FieldRulesListComponent {
|
|
|
1800
2073
|
</div>
|
|
1801
2074
|
}
|
|
1802
2075
|
</div>
|
|
1803
|
-
`, isInline: true, styles: [".deb-rules{display:flex;flex-direction:column;gap:8px}.deb-rules__head{display:flex;align-items:center;justify-content:space-between}.deb-rule-item{display:flex;align-items:center;justify-content:space-between;gap:8px;border:1px solid #e5e7eb;border-radius:8px;padding:8px 10px}.deb-rule-item--off{opacity:.55}.deb-rule-item__body{display:flex;flex-direction:column;min-width:0}.deb-rule-item__summary{font-size:13px;font-weight:600;overflow-wrap:anywhere}.deb-rule-item__meta{font-size:11px;color:#6b7280}.deb-rule-item__actions{display:flex;align-items:center;flex:0 0 auto}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
2076
|
+
`, isInline: true, styles: [".deb-rules{display:flex;flex-direction:column;gap:8px}.deb-rules__head{display:flex;align-items:center;justify-content:space-between}.deb-rule-item{display:flex;align-items:center;justify-content:space-between;gap:8px;border:1px solid #e5e7eb;border-radius:8px;padding:8px 10px}.deb-rule-item--off{opacity:.55}.deb-rule-item__body{display:flex;flex-direction:column;min-width:0}.deb-rule-item__summary{font-size:13px;font-weight:600;overflow-wrap:anywhere}.deb-rule-item__meta{font-size:11px;color:#6b7280}.deb-rule-item__actions{display:flex;align-items:center;flex:0 0 auto}\n"], dependencies: [{ 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: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: RuleFormComponent, selector: "ngx-rule-form", inputs: ["rule"], outputs: ["save", "cancel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1804
2077
|
}
|
|
1805
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2078
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRulesListComponent, decorators: [{
|
|
1806
2079
|
type: Component,
|
|
1807
2080
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-field-rules-list', standalone: true, imports: [MatButtonModule, MatIconModule, MatSlideToggleModule, MatTooltipModule, RuleFormComponent], template: `
|
|
1808
2081
|
<div class="deb-rules">
|
|
@@ -1888,7 +2161,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1888
2161
|
class ReferencedFieldConfigComponent {
|
|
1889
2162
|
store = inject(BuilderStore);
|
|
1890
2163
|
configSource = inject(ConfigSourceService, { optional: true });
|
|
1891
|
-
field = computed(() => this.store.selectedField());
|
|
2164
|
+
field = computed(() => this.store.selectedField(), ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
1892
2165
|
toggleReferenced(field, enabled) {
|
|
1893
2166
|
if (enabled) {
|
|
1894
2167
|
this.store.updateField(field.id, { isReferenced: true });
|
|
@@ -1959,8 +2232,8 @@ class ReferencedFieldConfigComponent {
|
|
|
1959
2232
|
}
|
|
1960
2233
|
return undefined;
|
|
1961
2234
|
}
|
|
1962
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1963
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2235
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ReferencedFieldConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2236
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: ReferencedFieldConfigComponent, isStandalone: true, selector: "ngx-referenced-field-config", ngImport: i0, template: `
|
|
1964
2237
|
@if (field(); as f) {
|
|
1965
2238
|
<div class="deb-referenced-field" style="margin-top: 16px;">
|
|
1966
2239
|
<div class="deb-row" style="justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
@@ -2020,9 +2293,9 @@ class ReferencedFieldConfigComponent {
|
|
|
2020
2293
|
}
|
|
2021
2294
|
</div>
|
|
2022
2295
|
}
|
|
2023
|
-
`, 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
2296
|
+
`, 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: 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: MatDividerModule }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2024
2297
|
}
|
|
2025
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2298
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ReferencedFieldConfigComponent, decorators: [{
|
|
2026
2299
|
type: Component,
|
|
2027
2300
|
args: [{
|
|
2028
2301
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2111,7 +2384,7 @@ class FieldInspectorComponent {
|
|
|
2111
2384
|
meta = computed(() => {
|
|
2112
2385
|
const f = this.field();
|
|
2113
2386
|
return f ? getFieldTypeMeta(f.type) : undefined;
|
|
2114
|
-
});
|
|
2387
|
+
}, ...(ngDevMode ? [{ debugName: "meta" }] : /* istanbul ignore next */ []));
|
|
2115
2388
|
lang() {
|
|
2116
2389
|
return this.store.activeLanguage();
|
|
2117
2390
|
}
|
|
@@ -2190,10 +2463,10 @@ class FieldInspectorComponent {
|
|
|
2190
2463
|
return JSON.stringify(value);
|
|
2191
2464
|
return String(value);
|
|
2192
2465
|
}
|
|
2193
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2194
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FieldInspectorComponent, isStandalone: true, selector: "ngx-field-inspector", ngImport: i0, template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>The stored data key for this saved field \u2014 fixed.</mat-hint>\n } @else {\n <mat-hint>Derived from the label. Used as the data key.</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Label ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(f.id, lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Placeholder ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(f.id, lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Default value</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(f.id, { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">Validation</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(f.id, v, $event)\"\n >{{ v }}</mat-checkbox>\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(f.id, v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">Data source</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Options come from</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(f.id, $event)\"\n >\n <mat-option value=\"none\">Nothing yet</mat-option>\n <mat-option value=\"manual\">Options authored here</mat-option>\n <mat-option value=\"lookup\">A named list</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>List name</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(f.id, $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n The consuming app resolves this name through its lookup registry. Options are not\n authored here.\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between;\">\n <span class=\"deb-section-title\">Options</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(f.id)\">\n <mat-icon>add</mat-icon> Option\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Option ({{ lang() }})</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(f.id, $index, lang(), $event)\"\n />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\"\n (click)=\"store.removeOption(f.id, $index)\" matTooltip=\"Remove option\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">No options yet \u2014 add at least one.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">Display</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(f.id, { visibility: $event })\"\n >Visible</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { table: { visible: $event } })\"\n >Table column</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { readonly: $event })\"\n >Read-only</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { disabled: $event })\"\n >Disabled</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { maskData: $event })\"\n >Mask data (RBAC)</mat-slide-toggle>\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { criticalField: $event })\"\n matTooltip=\"Renders locked; editing it announces a change against the session baseline.\"\n >Critical field</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { showOnMinimize: $event })\"\n matTooltip=\"Shown in the record editor's summary panel.\"\n >Show in summary</mat-slide-toggle>\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Show when</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> Condition\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput [ngModel]=\"entry.key\" (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Equals</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove condition\"\n (click)=\"removeShowWhen(f, entry.key)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">Always visible. Add a condition to show this field only for certain values.</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Copy on true</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(f.id)\">\n <mat-icon>add</mat-icon> Mapping\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>From field</mat-label>\n <input matInput [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(f.id, $index, { from: $event })\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>To field</mat-label>\n <input matInput [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(f.id, $index, { to: $event })\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove mapping\"\n (click)=\"store.removePatchOnTrueMapping(f.id, $index)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">When this flips to true, copy one field's value into another.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>Select a field to edit its properties.</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"], dependencies: [{ 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.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"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i10.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: EntityReferenceConfigComponent, selector: "ngx-entity-reference-config" }, { kind: "component", type: FieldRulesListComponent, selector: "ngx-field-rules-list" }, { kind: "component", type: ReferencedFieldConfigComponent, selector: "ngx-referenced-field-config" }] });
|
|
2466
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2467
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldInspectorComponent, isStandalone: true, selector: "ngx-field-inspector", ngImport: i0, template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>The stored data key for this saved field \u2014 fixed.</mat-hint>\n } @else {\n <mat-hint>Derived from the label. Used as the data key.</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Label ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(f.id, lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Placeholder ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(f.id, lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Default value</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(f.id, { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">Validation</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(f.id, v, $event)\"\n >{{ v }}</mat-checkbox>\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(f.id, v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">Data source</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Options come from</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(f.id, $event)\"\n >\n <mat-option value=\"none\">Nothing yet</mat-option>\n <mat-option value=\"manual\">Options authored here</mat-option>\n <mat-option value=\"lookup\">A named list</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>List name</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(f.id, $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n The consuming app resolves this name through its lookup registry. Options are not\n authored here.\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between;\">\n <span class=\"deb-section-title\">Options</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(f.id)\">\n <mat-icon>add</mat-icon> Option\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Option ({{ lang() }})</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(f.id, $index, lang(), $event)\"\n />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\"\n (click)=\"store.removeOption(f.id, $index)\" matTooltip=\"Remove option\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">No options yet \u2014 add at least one.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">Display</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(f.id, { visibility: $event })\"\n >Visible</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { table: { visible: $event } })\"\n >Table column</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { readonly: $event })\"\n >Read-only</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { disabled: $event })\"\n >Disabled</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { maskData: $event })\"\n >Mask data (RBAC)</mat-slide-toggle>\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { criticalField: $event })\"\n matTooltip=\"Renders locked; editing it announces a change against the session baseline.\"\n >Critical field</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(f.id, { showOnMinimize: $event })\"\n matTooltip=\"Shown in the record editor's summary panel.\"\n >Show in summary</mat-slide-toggle>\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Show when</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> Condition\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput [ngModel]=\"entry.key\" (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Equals</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove condition\"\n (click)=\"removeShowWhen(f, entry.key)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">Always visible. Add a condition to show this field only for certain values.</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Copy on true</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(f.id)\">\n <mat-icon>add</mat-icon> Mapping\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>From field</mat-label>\n <input matInput [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(f.id, $index, { from: $event })\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>To field</mat-label>\n <input matInput [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(f.id, $index, { to: $event })\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove mapping\"\n (click)=\"store.removePatchOnTrueMapping(f.id, $index)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">When this flips to true, copy one field's value into another.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>Select a field to edit its properties.</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"], dependencies: [{ 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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: 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: MatCheckboxModule }, { kind: "component", type: i3$1.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: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: EntityReferenceConfigComponent, selector: "ngx-entity-reference-config" }, { kind: "component", type: FieldRulesListComponent, selector: "ngx-field-rules-list" }, { kind: "component", type: ReferencedFieldConfigComponent, selector: "ngx-referenced-field-config" }] });
|
|
2195
2468
|
}
|
|
2196
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2469
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, decorators: [{
|
|
2197
2470
|
type: Component,
|
|
2198
2471
|
args: [{ selector: 'ngx-field-inspector', standalone: true, imports: [
|
|
2199
2472
|
FormsModule,
|
|
@@ -2218,8 +2491,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2218
2491
|
class FieldPaletteComponent {
|
|
2219
2492
|
catalog = FIELD_TYPE_CATALOG;
|
|
2220
2493
|
pick = new EventEmitter();
|
|
2221
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2222
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2494
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldPaletteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2495
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldPaletteComponent, isStandalone: true, selector: "ngx-field-palette", outputs: { pick: "pick" }, ngImport: i0, template: `
|
|
2223
2496
|
<div class="deb-palette">
|
|
2224
2497
|
@for (meta of catalog; track meta.type) {
|
|
2225
2498
|
<button
|
|
@@ -2235,9 +2508,9 @@ class FieldPaletteComponent {
|
|
|
2235
2508
|
</button>
|
|
2236
2509
|
}
|
|
2237
2510
|
</div>
|
|
2238
|
-
`, isInline: true, styles: [".deb-palette{display:grid;grid-template-columns:1fr 1fr;gap:8px}.deb-palette__item{justify-content:flex-start;overflow:hidden;border-radius:8px!important;border:1px solid var(--border, #e2e8f0)!important;padding:8px 12px!important;font-family:var(--font-heading, inherit)!important;font-weight:600!important;font-size:13px!important;transition:all .18s cubic-bezier(.16,1,.3,1)!important;background:#fff!important}.deb-palette__item:hover{border-color:#6366f1!important;background:#f5f3ff!important;color:#4f46e5!important;transform:translateY(-1px);box-shadow:0 4px 12px #6366f126!important}.deb-palette__item mat-icon{color:#6366f1;margin-right:6px;font-size:18px;width:18px;height:18px}.deb-palette__item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
2511
|
+
`, isInline: true, styles: [".deb-palette{display:grid;grid-template-columns:1fr 1fr;gap:8px}.deb-palette__item{justify-content:flex-start;overflow:hidden;border-radius:8px!important;border:1px solid var(--border, #e2e8f0)!important;padding:8px 12px!important;font-family:var(--font-heading, inherit)!important;font-weight:600!important;font-size:13px!important;transition:all .18s cubic-bezier(.16,1,.3,1)!important;background:#fff!important}.deb-palette__item:hover{border-color:#6366f1!important;background:#f5f3ff!important;color:#4f46e5!important;transform:translateY(-1px);box-shadow:0 4px 12px #6366f126!important}.deb-palette__item mat-icon{color:#6366f1;margin-right:6px;font-size:18px;width:18px;height:18px}.deb-palette__item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ 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: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2239
2512
|
}
|
|
2240
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2513
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldPaletteComponent, decorators: [{
|
|
2241
2514
|
type: Component,
|
|
2242
2515
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-field-palette', standalone: true, imports: [MatButtonModule, MatIconModule, MatTooltipModule], template: `
|
|
2243
2516
|
<div class="deb-palette">
|
|
@@ -2283,8 +2556,8 @@ class TabManagerComponent {
|
|
|
2283
2556
|
}
|
|
2284
2557
|
return true;
|
|
2285
2558
|
}
|
|
2286
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2287
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2559
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TabManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2560
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TabManagerComponent, isStandalone: true, selector: "ngx-tab-manager", ngImport: i0, template: `
|
|
2288
2561
|
<div class="deb-tabs">
|
|
2289
2562
|
<div class="deb-tabs__head">
|
|
2290
2563
|
<span class="deb-section-title">Tabs Manager</span>
|
|
@@ -2426,9 +2699,9 @@ class TabManagerComponent {
|
|
|
2426
2699
|
</div>
|
|
2427
2700
|
}
|
|
2428
2701
|
</div>
|
|
2429
|
-
`, isInline: true, styles: [".deb-tabs__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.deb-tab-card{border:1px solid var(--deb-border, #e5e7eb);border-radius:8px;padding:10px;margin-bottom:10px;background:var(--deb-bg-surface, #ffffff)}.deb-tab-card--system{border-left:4px solid var(--deb-accent, #6366f1)}.deb-tabs__row{display:flex;align-items:center;gap:4px}.deb-tab-settings{display:flex;flex-wrap:wrap;gap:8px 16px;align-items:center;margin-top:8px;font-size:13px}.deb-tab-module{margin-top:8px}.deb-subtabs{margin-top:8px;padding-left:16px;border-left:2px dashed var(--deb-border, #e5e7eb)}.deb-subtabs__title{font-size:12px;font-weight:600;color:var(--deb-muted, #6b7280)}.deb-subtab-row{margin-top:6px}.deb-grow{flex:1}\n"], dependencies: [{ 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button],
|
|
2702
|
+
`, isInline: true, styles: [".deb-tabs__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.deb-tab-card{border:1px solid var(--deb-border, #e5e7eb);border-radius:8px;padding:10px;margin-bottom:10px;background:var(--deb-bg-surface, #ffffff)}.deb-tab-card--system{border-left:4px solid var(--deb-accent, #6366f1)}.deb-tabs__row{display:flex;align-items:center;gap:4px}.deb-tab-settings{display:flex;flex-wrap:wrap;gap:8px 16px;align-items:center;margin-top:8px;font-size:13px}.deb-tab-module{margin-top:8px}.deb-subtabs{margin-top:8px;padding-left:16px;border-left:2px dashed var(--deb-border, #e5e7eb)}.deb-subtabs__title{font-size:12px;font-weight:600;color:var(--deb-muted, #6b7280)}.deb-subtab-row{margin-top:6px}.deb-grow{flex:1}\n"], dependencies: [{ 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: 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: MatCheckboxModule }, { kind: "component", type: i3$1.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: MatFormFieldModule }, { 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: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSlideToggleModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2430
2703
|
}
|
|
2431
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2704
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TabManagerComponent, decorators: [{
|
|
2432
2705
|
type: Component,
|
|
2433
2706
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-tab-manager', standalone: true, imports: [
|
|
2434
2707
|
FormsModule,
|
|
@@ -2599,8 +2872,8 @@ class EntityBuilderTreeNodeComponent {
|
|
|
2599
2872
|
const lang = this.store.activeLanguage();
|
|
2600
2873
|
return resolveLabel(field.label, lang) || field.id;
|
|
2601
2874
|
}
|
|
2602
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2603
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
2875
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2876
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderTreeNodeComponent, isStandalone: true, selector: "ngx-entity-builder-tree-node", inputs: { field: "field", index: "index", totalCount: "totalCount" }, ngImport: i0, template: `
|
|
2604
2877
|
<div
|
|
2605
2878
|
cdkDrag
|
|
2606
2879
|
class="deb-field-row"
|
|
@@ -2667,9 +2940,9 @@ class EntityBuilderTreeNodeComponent {
|
|
|
2667
2940
|
}
|
|
2668
2941
|
</div>
|
|
2669
2942
|
}
|
|
2670
|
-
`, 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$1.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$1.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: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2943
|
+
`, 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$1.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: i1$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { 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: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2671
2944
|
}
|
|
2672
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2945
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderTreeNodeComponent, decorators: [{
|
|
2673
2946
|
type: Component,
|
|
2674
2947
|
args: [{
|
|
2675
2948
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2764,11 +3037,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2764
3037
|
|
|
2765
3038
|
class EntityBuilderCanvasComponent {
|
|
2766
3039
|
store = inject(BuilderStore);
|
|
2767
|
-
|
|
2768
|
-
|
|
3040
|
+
/**
|
|
3041
|
+
* Headings only earn their space once the fields are actually split across tabs. A
|
|
3042
|
+
* single-tab config — the common case, and every existing spec — renders exactly as before.
|
|
3043
|
+
*/
|
|
3044
|
+
showTabHeadings() {
|
|
3045
|
+
return this.store.fieldGroups().length > 1;
|
|
3046
|
+
}
|
|
3047
|
+
groupLabel(group) {
|
|
3048
|
+
return resolveLabel(group.label, this.store.activeLanguage()) || group.tabId;
|
|
2769
3049
|
}
|
|
2770
|
-
|
|
2771
|
-
|
|
3050
|
+
onDrop(tabId, event) {
|
|
3051
|
+
this.store.reorderField(event.previousIndex, event.currentIndex, tabId);
|
|
3052
|
+
}
|
|
3053
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3054
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderCanvasComponent, isStandalone: true, selector: "ngx-entity-builder-canvas", ngImport: i0, template: `
|
|
2772
3055
|
<mat-card class="deb-canvas">
|
|
2773
3056
|
<mat-card-header>
|
|
2774
3057
|
<mat-card-title>Fields ({{ store.fields().length }})</mat-card-title>
|
|
@@ -2781,20 +3064,37 @@ class EntityBuilderCanvasComponent {
|
|
|
2781
3064
|
</div>
|
|
2782
3065
|
}
|
|
2783
3066
|
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3067
|
+
@for (group of store.fieldGroups(); track group.tabId) {
|
|
3068
|
+
<!--
|
|
3069
|
+
One drop list per tab. A single list spanning every tab would hand reorderField
|
|
3070
|
+
an index into the combined view, which is not the index of anything in the tab
|
|
3071
|
+
that actually owns the field.
|
|
3072
|
+
-->
|
|
3073
|
+
@if (showTabHeadings()) {
|
|
3074
|
+
<h4 class="deb-canvas__group" [attr.data-testid]="'builder-group-' + group.tabId">
|
|
3075
|
+
{{ groupLabel(group) }}
|
|
3076
|
+
</h4>
|
|
2791
3077
|
}
|
|
2792
|
-
|
|
3078
|
+
<div
|
|
3079
|
+
cdkDropList
|
|
3080
|
+
class="deb-field-list"
|
|
3081
|
+
[attr.data-testid]="'builder-field-list-' + group.tabId"
|
|
3082
|
+
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3083
|
+
>
|
|
3084
|
+
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3085
|
+
<ngx-entity-builder-tree-node
|
|
3086
|
+
[field]="f"
|
|
3087
|
+
[index]="i"
|
|
3088
|
+
[totalCount]="count"
|
|
3089
|
+
/>
|
|
3090
|
+
}
|
|
3091
|
+
</div>
|
|
3092
|
+
}
|
|
2793
3093
|
</mat-card-content>
|
|
2794
3094
|
</mat-card>
|
|
2795
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.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$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3095
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.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: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2796
3096
|
}
|
|
2797
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3097
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderCanvasComponent, decorators: [{
|
|
2798
3098
|
type: Component,
|
|
2799
3099
|
args: [{
|
|
2800
3100
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2820,15 +3120,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2820
3120
|
</div>
|
|
2821
3121
|
}
|
|
2822
3122
|
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3123
|
+
@for (group of store.fieldGroups(); track group.tabId) {
|
|
3124
|
+
<!--
|
|
3125
|
+
One drop list per tab. A single list spanning every tab would hand reorderField
|
|
3126
|
+
an index into the combined view, which is not the index of anything in the tab
|
|
3127
|
+
that actually owns the field.
|
|
3128
|
+
-->
|
|
3129
|
+
@if (showTabHeadings()) {
|
|
3130
|
+
<h4 class="deb-canvas__group" [attr.data-testid]="'builder-group-' + group.tabId">
|
|
3131
|
+
{{ groupLabel(group) }}
|
|
3132
|
+
</h4>
|
|
2830
3133
|
}
|
|
2831
|
-
|
|
3134
|
+
<div
|
|
3135
|
+
cdkDropList
|
|
3136
|
+
class="deb-field-list"
|
|
3137
|
+
[attr.data-testid]="'builder-field-list-' + group.tabId"
|
|
3138
|
+
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3139
|
+
>
|
|
3140
|
+
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3141
|
+
<ngx-entity-builder-tree-node
|
|
3142
|
+
[field]="f"
|
|
3143
|
+
[index]="i"
|
|
3144
|
+
[totalCount]="count"
|
|
3145
|
+
/>
|
|
3146
|
+
}
|
|
3147
|
+
</div>
|
|
3148
|
+
}
|
|
2832
3149
|
</mat-card-content>
|
|
2833
3150
|
</mat-card>
|
|
2834
3151
|
`,
|
|
@@ -2922,10 +3239,10 @@ class EntityBuilderComponent {
|
|
|
2922
3239
|
.filter(Boolean)));
|
|
2923
3240
|
this.store.setPermission(action, roles);
|
|
2924
3241
|
}
|
|
2925
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2926
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityBuilderComponent, isStandalone: true, selector: "ngx-entity-builder", inputs: { config: "config", languages: "languages", availableRoles: "availableRoles", userRoles: "userRoles", commonModules: "commonModules" }, outputs: { configChange: "configChange", save: "save" }, providers: [BuilderStore], usesOnChanges: true, ngImport: i0, template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">Entity Builder</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"store.problems().length + ' issue(s) \u2014 hover items in the list'\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button mat-button type=\"button\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon> Copy JSON\n </button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> Save\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Entity name</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n placeholder=\"e.g. clients\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>Editing language</mat-label>\n <mat-select\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle\n [ngModel]=\"store.config().maskData ?? false\"\n (ngModelChange)=\"store.setMaskData($event)\"\n >Mask this entity (RBAC)</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>Permissions</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles (comma-separated)</mat-label>\n <input\n matInput\n [ngModel]=\"rolesFor(action).join(', ')\"\n (ngModelChange)=\"setRolesFromText(action, $event)\"\n />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">Empty = no restriction for that action.</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header><mat-card-title>Add field</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header><mat-card-title>Field properties</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> Config JSON</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px}@media (max-width: 1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}\n"], dependencies: [{ 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: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: MatIconModule }, { kind: "component", type: i4$1.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"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i10.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i11.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FieldPaletteComponent, selector: "ngx-field-palette", outputs: ["pick"] }, { kind: "component", type: TabManagerComponent, selector: "ngx-tab-manager" }, { kind: "component", type: FieldInspectorComponent, selector: "ngx-field-inspector" }, { kind: "component", type: EntityBuilderCanvasComponent, selector: "ngx-entity-builder-canvas" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3242
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3243
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderComponent, isStandalone: true, selector: "ngx-entity-builder", inputs: { config: "config", languages: "languages", availableRoles: "availableRoles", userRoles: "userRoles", commonModules: "commonModules" }, outputs: { configChange: "configChange", save: "save" }, providers: [BuilderStore], usesOnChanges: true, ngImport: i0, template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">Entity Builder</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"store.problems().length + ' issue(s) \u2014 hover items in the list'\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button mat-button type=\"button\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon> Copy JSON\n </button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> Save\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Entity name</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n placeholder=\"e.g. clients\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>Editing language</mat-label>\n <mat-select\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle\n [ngModel]=\"store.config().maskData ?? false\"\n (ngModelChange)=\"store.setMaskData($event)\"\n >Mask this entity (RBAC)</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>Permissions</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles (comma-separated)</mat-label>\n <input\n matInput\n [ngModel]=\"rolesFor(action).join(', ')\"\n (ngModelChange)=\"setRolesFromText(action, $event)\"\n />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">Empty = no restriction for that action.</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header><mat-card-title>Add field</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header><mat-card-title>Field properties</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> Config JSON</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px}@media(max-width:1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}\n"], dependencies: [{ 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: 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: MatCardModule }, { kind: "component", type: i2$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { 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: MatIconModule }, { kind: "component", type: i4$1.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: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: MatToolbarModule }, { kind: "component", type: i10.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FieldPaletteComponent, selector: "ngx-field-palette", outputs: ["pick"] }, { kind: "component", type: TabManagerComponent, selector: "ngx-tab-manager" }, { kind: "component", type: FieldInspectorComponent, selector: "ngx-field-inspector" }, { kind: "component", type: EntityBuilderCanvasComponent, selector: "ngx-entity-builder-canvas" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2927
3244
|
}
|
|
2928
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3245
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, decorators: [{
|
|
2929
3246
|
type: Component,
|
|
2930
3247
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-entity-builder', standalone: true, providers: [BuilderStore], imports: [
|
|
2931
3248
|
FormsModule,
|
|
@@ -2944,7 +3261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2944
3261
|
TabManagerComponent,
|
|
2945
3262
|
FieldInspectorComponent,
|
|
2946
3263
|
EntityBuilderCanvasComponent,
|
|
2947
|
-
], template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">Entity Builder</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"store.problems().length + ' issue(s) \u2014 hover items in the list'\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button mat-button type=\"button\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon> Copy JSON\n </button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> Save\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Entity name</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n placeholder=\"e.g. clients\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>Editing language</mat-label>\n <mat-select\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle\n [ngModel]=\"store.config().maskData ?? false\"\n (ngModelChange)=\"store.setMaskData($event)\"\n >Mask this entity (RBAC)</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>Permissions</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles (comma-separated)</mat-label>\n <input\n matInput\n [ngModel]=\"rolesFor(action).join(', ')\"\n (ngModelChange)=\"setRolesFromText(action, $event)\"\n />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">Empty = no restriction for that action.</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header><mat-card-title>Add field</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header><mat-card-title>Field properties</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> Config JSON</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px}@media
|
|
3264
|
+
], template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">Entity Builder</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"store.problems().length + ' issue(s) \u2014 hover items in the list'\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button mat-button type=\"button\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon> Copy JSON\n </button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> Save\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Entity name</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n placeholder=\"e.g. clients\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>Editing language</mat-label>\n <mat-select\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle\n [ngModel]=\"store.config().maskData ?? false\"\n (ngModelChange)=\"store.setMaskData($event)\"\n >Mask this entity (RBAC)</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>Permissions</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles (comma-separated)</mat-label>\n <input\n matInput\n [ngModel]=\"rolesFor(action).join(', ')\"\n (ngModelChange)=\"setRolesFromText(action, $event)\"\n />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">Empty = no restriction for that action.</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header><mat-card-title>Add field</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header><mat-card-title>Field properties</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> Config JSON</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px}@media(max-width:1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}\n"] }]
|
|
2948
3265
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
2949
3266
|
type: Input
|
|
2950
3267
|
}], languages: [{
|