@mosaicoo/form-angular 0.2.0 → 0.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.
@@ -0,0 +1,1305 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input as input$1, output, signal, ViewEncapsulation, ChangeDetectionStrategy, Component, computed, effect } from '@angular/core';
3
+ import { JsonPipe } from '@angular/common';
4
+ import { collectInputs, generateKey, validateSchema, createFormEngine, getNodeAt, insertNode, parentOf, removeNode, duplicateNode, moveNode, updateNode } from '@mosaicoo/form-core';
5
+ import { importForm, createLegacyImporter } from '@mosaicoo/form-core/interop';
6
+ import { FormNodeComponent, FormRendererComponent } from '@mosaicoo/form-angular';
7
+
8
+ /**
9
+ * Canvas node: draws the selection frame and structure chrome, delegating
10
+ * leaf rendering to the RUNTIME `<mform-node>` so what you see is what the
11
+ * renderer will produce. Canvas controls are non-interactive — testing
12
+ * happens in Preview mode.
13
+ */
14
+ class DesignerNodeComponent {
15
+ node = input$1.required(...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
16
+ path = input$1.required(...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
17
+ engine = input$1.required(...(ngDevMode ? [{ debugName: "engine" }] : /* istanbul ignore next */ []));
18
+ selected = input$1(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
19
+ selectNode = output();
20
+ activeTab = signal(0, ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
21
+ container() {
22
+ return this.node();
23
+ }
24
+ chip() {
25
+ const node = this.node();
26
+ const label = node.kind === 'container' || node.kind === 'static'
27
+ ? node.label ?? node.key
28
+ : node.label ?? node.key;
29
+ return `${label} · ${node.type}`;
30
+ }
31
+ isSelected() {
32
+ const selected = this.selected();
33
+ return selected !== null && selected.join('.') === this.path().join('.');
34
+ }
35
+ childPath(index) {
36
+ return [...this.path(), index];
37
+ }
38
+ columnsTemplate() {
39
+ const columns = this.container().columns ?? [];
40
+ const total = columns.reduce((sum, c) => sum + c.width, 0) || 12;
41
+ return columns.map((c) => `${((c.width / total) * 100).toFixed(3)}fr`).join(' ');
42
+ }
43
+ onSelect(event) {
44
+ event.stopPropagation();
45
+ this.selectNode.emit(this.path());
46
+ }
47
+ setTab(index, event) {
48
+ event.stopPropagation();
49
+ this.activeTab.set(index);
50
+ }
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DesignerNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
52
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: DesignerNodeComponent, isStandalone: true, selector: "mform-designer-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: true, transformFunction: null }, engine: { classPropertyName: "engine", publicName: "engine", isSignal: true, isRequired: true, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectNode: "selectNode" }, ngImport: i0, template: `
53
+ @let n = node();
54
+ <div
55
+ class="dz-node"
56
+ [class.is-selected]="isSelected()"
57
+ [class.is-container]="n.kind === 'container'"
58
+ (click)="onSelect($event)"
59
+ role="button"
60
+ tabindex="0"
61
+ (keydown.enter)="onSelect($event)"
62
+ [attr.aria-label]="chip() + (isSelected() ? ' (selected)' : '')"
63
+ >
64
+ <span class="dz-chip">{{ chip() }}</span>
65
+
66
+ @if (n.kind === 'container') {
67
+ @if (n.type === 'tabs') {
68
+ <div class="dz-tabbar">
69
+ @for (tab of container().children; track tab.key; let i = $index) {
70
+ <button
71
+ type="button"
72
+ class="dz-tab"
73
+ [class.is-active]="activeTab() === i"
74
+ (click)="setTab(i, $event)"
75
+ >
76
+ {{ tab.label || 'Tab ' + (i + 1) }}
77
+ </button>
78
+ }
79
+ </div>
80
+ @for (tab of container().children; track tab.key; let i = $index) {
81
+ @if (activeTab() === i) {
82
+ <div class="dz-children">
83
+ <mform-designer-node
84
+ [node]="tab"
85
+ [path]="childPath(i)"
86
+ [engine]="engine()"
87
+ [selected]="selected()"
88
+ (selectNode)="selectNode.emit($event)"
89
+ />
90
+ </div>
91
+ }
92
+ }
93
+ } @else if (n.type === 'columns' && container().columns?.length) {
94
+ <div class="dz-columns" [style.grid-template-columns]="columnsTemplate()">
95
+ @for (column of container().columns; track $index) {
96
+ <div class="dz-column">
97
+ @for (childIndex of column.children; track childIndex) {
98
+ <mform-designer-node
99
+ [node]="container().children[childIndex]!"
100
+ [path]="childPath(childIndex)"
101
+ [engine]="engine()"
102
+ [selected]="selected()"
103
+ (selectNode)="selectNode.emit($event)"
104
+ />
105
+ }
106
+ @if (column.children.length === 0) {
107
+ <div class="dz-empty">empty column</div>
108
+ }
109
+ </div>
110
+ }
111
+ </div>
112
+ } @else {
113
+ <div class="dz-children">
114
+ @for (child of container().children; track child.key; let i = $index) {
115
+ <mform-designer-node
116
+ [node]="child"
117
+ [path]="childPath(i)"
118
+ [engine]="engine()"
119
+ [selected]="selected()"
120
+ (selectNode)="selectNode.emit($event)"
121
+ />
122
+ } @empty {
123
+ <div class="dz-empty">empty — select and add fields</div>
124
+ }
125
+ </div>
126
+ }
127
+ } @else {
128
+ <div class="dz-leaf" inert>
129
+ <mform-node [node]="n" [engine]="engine()" [tick]="0" />
130
+ </div>
131
+ }
132
+ </div>
133
+ `, isInline: true, dependencies: [{ kind: "component", type: DesignerNodeComponent, selector: "mform-designer-node", inputs: ["node", "path", "engine", "selected"], outputs: ["selectNode"] }, { kind: "component", type: FormNodeComponent, selector: "mform-node", inputs: ["node", "engine", "tick", "scope", "bare", "providers", "mode", "notify"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
134
+ }
135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DesignerNodeComponent, decorators: [{
136
+ type: Component,
137
+ args: [{
138
+ selector: 'mform-designer-node',
139
+ changeDetection: ChangeDetectionStrategy.OnPush,
140
+ encapsulation: ViewEncapsulation.None,
141
+ imports: [FormNodeComponent],
142
+ template: `
143
+ @let n = node();
144
+ <div
145
+ class="dz-node"
146
+ [class.is-selected]="isSelected()"
147
+ [class.is-container]="n.kind === 'container'"
148
+ (click)="onSelect($event)"
149
+ role="button"
150
+ tabindex="0"
151
+ (keydown.enter)="onSelect($event)"
152
+ [attr.aria-label]="chip() + (isSelected() ? ' (selected)' : '')"
153
+ >
154
+ <span class="dz-chip">{{ chip() }}</span>
155
+
156
+ @if (n.kind === 'container') {
157
+ @if (n.type === 'tabs') {
158
+ <div class="dz-tabbar">
159
+ @for (tab of container().children; track tab.key; let i = $index) {
160
+ <button
161
+ type="button"
162
+ class="dz-tab"
163
+ [class.is-active]="activeTab() === i"
164
+ (click)="setTab(i, $event)"
165
+ >
166
+ {{ tab.label || 'Tab ' + (i + 1) }}
167
+ </button>
168
+ }
169
+ </div>
170
+ @for (tab of container().children; track tab.key; let i = $index) {
171
+ @if (activeTab() === i) {
172
+ <div class="dz-children">
173
+ <mform-designer-node
174
+ [node]="tab"
175
+ [path]="childPath(i)"
176
+ [engine]="engine()"
177
+ [selected]="selected()"
178
+ (selectNode)="selectNode.emit($event)"
179
+ />
180
+ </div>
181
+ }
182
+ }
183
+ } @else if (n.type === 'columns' && container().columns?.length) {
184
+ <div class="dz-columns" [style.grid-template-columns]="columnsTemplate()">
185
+ @for (column of container().columns; track $index) {
186
+ <div class="dz-column">
187
+ @for (childIndex of column.children; track childIndex) {
188
+ <mform-designer-node
189
+ [node]="container().children[childIndex]!"
190
+ [path]="childPath(childIndex)"
191
+ [engine]="engine()"
192
+ [selected]="selected()"
193
+ (selectNode)="selectNode.emit($event)"
194
+ />
195
+ }
196
+ @if (column.children.length === 0) {
197
+ <div class="dz-empty">empty column</div>
198
+ }
199
+ </div>
200
+ }
201
+ </div>
202
+ } @else {
203
+ <div class="dz-children">
204
+ @for (child of container().children; track child.key; let i = $index) {
205
+ <mform-designer-node
206
+ [node]="child"
207
+ [path]="childPath(i)"
208
+ [engine]="engine()"
209
+ [selected]="selected()"
210
+ (selectNode)="selectNode.emit($event)"
211
+ />
212
+ } @empty {
213
+ <div class="dz-empty">empty — select and add fields</div>
214
+ }
215
+ </div>
216
+ }
217
+ } @else {
218
+ <div class="dz-leaf" inert>
219
+ <mform-node [node]="n" [engine]="engine()" [tick]="0" />
220
+ </div>
221
+ }
222
+ </div>
223
+ `,
224
+ }]
225
+ }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: true }] }], engine: [{ type: i0.Input, args: [{ isSignal: true, alias: "engine", required: true }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], selectNode: [{ type: i0.Output, args: ["selectNode"] }] } });
226
+
227
+ /**
228
+ * Property panel of the selected node. Emits immutable patches — the shell
229
+ * owns the document, history and validation.
230
+ */
231
+ class InspectorComponent {
232
+ node = input$1.required(...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
233
+ schema = input$1.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
234
+ nodePatch = output();
235
+ field() {
236
+ return this.node();
237
+ }
238
+ static_() {
239
+ return this.node();
240
+ }
241
+ hasOptions() {
242
+ return ['select', 'radio', 'checkbox-group'].includes(this.node().type);
243
+ }
244
+ otherInputPaths = computed(() => collectInputs(this.schema())
245
+ .map((entry) => entry.path)
246
+ .filter((path) => !path.includes('*')), ...(ngDevMode ? [{ debugName: "otherInputPaths" }] : /* istanbul ignore next */ []));
247
+ emitPatch(patch) {
248
+ this.nodePatch.emit({ patch });
249
+ }
250
+ inputValue(event) {
251
+ return event.target.value;
252
+ }
253
+ patchText(member, event) {
254
+ const value = this.inputValue(event).trim();
255
+ if (member === 'key' && value === '')
256
+ return; // key can never be emptied
257
+ this.emitPatch({ [member]: value === '' ? undefined : value });
258
+ }
259
+ patchFlag(member, event) {
260
+ const checked = event.target.checked;
261
+ this.emitPatch({ [member]: checked ? true : undefined });
262
+ }
263
+ defaultValueText() {
264
+ const value = this.field().defaultValue;
265
+ return value === undefined || value === null ? '' : String(value);
266
+ }
267
+ patchDefault(event) {
268
+ const raw = this.inputValue(event);
269
+ if (raw === '') {
270
+ this.emitPatch({ defaultValue: undefined });
271
+ return;
272
+ }
273
+ const field = this.field();
274
+ let value = raw;
275
+ if (field.dataType === 'number')
276
+ value = Number(raw);
277
+ if (field.dataType === 'boolean')
278
+ value = raw === 'true';
279
+ this.emitPatch({ defaultValue: value });
280
+ }
281
+ // -- validators -----------------------------------------------------------
282
+ validators() {
283
+ return this.field().validators ?? [];
284
+ }
285
+ isRequired() {
286
+ return this.validators().some((v) => v.type === 'required');
287
+ }
288
+ toggleRequired(event) {
289
+ const checked = event.target.checked;
290
+ const rest = this.validators().filter((v) => v.type !== 'required');
291
+ this.emitValidators(checked ? [{ type: 'required' }, ...rest] : rest);
292
+ }
293
+ validatorValue(type) {
294
+ const spec = this.validators().find((v) => v.type === type);
295
+ return spec && 'value' in spec ? String(spec.value) : '';
296
+ }
297
+ patchValidator(type, event) {
298
+ const raw = this.inputValue(event);
299
+ const rest = this.validators().filter((v) => v.type !== type);
300
+ this.emitValidators(raw === '' ? rest : [...rest, { type, value: Number(raw) }]);
301
+ }
302
+ patternValue() {
303
+ const spec = this.validators().find((v) => v.type === 'pattern');
304
+ return spec && 'value' in spec ? String(spec.value) : '';
305
+ }
306
+ patchPattern(event) {
307
+ const raw = this.inputValue(event);
308
+ const rest = this.validators().filter((v) => v.type !== 'pattern');
309
+ this.emitValidators(raw === '' ? rest : [...rest, { type: 'pattern', value: raw }]);
310
+ }
311
+ emitValidators(validators) {
312
+ this.emitPatch({ validators: validators.length ? validators : undefined });
313
+ }
314
+ // -- options --------------------------------------------------------------
315
+ patchOption(index, member, event) {
316
+ const options = [...(this.field().options ?? [])];
317
+ options[index] = { ...options[index], [member]: this.inputValue(event) };
318
+ this.emitPatch({ options });
319
+ }
320
+ addOption() {
321
+ const options = [...(this.field().options ?? [])];
322
+ options.push({ label: `Option ${options.length + 1}`, value: `option${options.length + 1}` });
323
+ this.emitPatch({ options });
324
+ }
325
+ removeOption(index) {
326
+ const options = (this.field().options ?? []).filter((_, i) => i !== index);
327
+ this.emitPatch({ options: options.length ? options : undefined });
328
+ }
329
+ // -- columns --------------------------------------------------------------
330
+ columnWidths() {
331
+ return (this.node().columns ?? []).map((c) => c.width).join(', ');
332
+ }
333
+ patchColumnWidths(event) {
334
+ const container = this.node();
335
+ const widths = this.inputValue(event)
336
+ .split(',')
337
+ .map((part) => Number(part.trim()))
338
+ .filter((width) => Number.isFinite(width) && width > 0);
339
+ if (widths.length === 0)
340
+ return;
341
+ const existing = container.columns ?? [];
342
+ const columns = widths.map((width, i) => ({
343
+ width,
344
+ children: existing[i]?.children ?? [],
345
+ }));
346
+ // children in dropped columns are re-attached to the last remaining one
347
+ for (let i = widths.length; i < existing.length; i++) {
348
+ columns[columns.length - 1].children.push(...existing[i].children);
349
+ }
350
+ this.emitPatch({ columns });
351
+ }
352
+ // -- conditional ----------------------------------------------------------
353
+ simpleCondition() {
354
+ const condition = this.node().conditional;
355
+ return condition && 'field' in condition ? condition : null;
356
+ }
357
+ conditionExpression() {
358
+ const condition = this.node().conditional;
359
+ return condition && 'expression' in condition ? condition.expression : '';
360
+ }
361
+ conditionValueText() {
362
+ const value = this.simpleCondition()?.value;
363
+ return value === undefined || value === null ? '' : String(value);
364
+ }
365
+ patchConditionField(event) {
366
+ const field = this.inputValue(event);
367
+ if (field === '') {
368
+ this.emitPatch({ conditional: undefined });
369
+ return;
370
+ }
371
+ const current = this.simpleCondition();
372
+ const conditional = {
373
+ show: current?.show ?? true,
374
+ op: current?.op ?? 'eq',
375
+ field,
376
+ value: current?.value ?? '',
377
+ };
378
+ this.emitPatch({ conditional });
379
+ }
380
+ patchConditionValue(event) {
381
+ const current = this.simpleCondition();
382
+ if (!current)
383
+ return;
384
+ const raw = this.inputValue(event);
385
+ const value = raw === 'true' ? true : raw === 'false' ? false : raw;
386
+ this.emitPatch({ conditional: { ...current, value } });
387
+ }
388
+ patchConditionExpression(event) {
389
+ const expression = this.inputValue(event).trim();
390
+ if (expression === '') {
391
+ // clearing the expression keeps a simple condition, if one existed
392
+ this.emitPatch({ conditional: this.simpleCondition() ?? undefined });
393
+ return;
394
+ }
395
+ this.emitPatch({ conditional: { show: true, expression } });
396
+ }
397
+ // -- advanced (calculate / mask) ------------------------------------------
398
+ patchCalculate(event) {
399
+ const expression = this.inputValue(event).trim();
400
+ this.emitPatch({ calculate: expression === '' ? undefined : expression });
401
+ }
402
+ patchMask(event) {
403
+ const mask = this.inputValue(event).trim();
404
+ this.emitPatch({ format: mask === '' ? undefined : { mask } });
405
+ }
406
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
407
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InspectorComponent, isStandalone: true, selector: "mform-inspector", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { nodePatch: "nodePatch" }, ngImport: i0, template: `
408
+ @let n = node();
409
+ <div class="dz-inspector">
410
+ <h3>{{ n.type }} <code>{{ n.key }}</code></h3>
411
+
412
+ <label class="dz-prop">
413
+ <span>Key</span>
414
+ <input type="text" [value]="n.key" (change)="patchText('key', $event)" />
415
+ </label>
416
+ <label class="dz-prop">
417
+ <span>Label</span>
418
+ <input type="text" [value]="n.label ?? ''" (change)="patchText('label', $event)" />
419
+ </label>
420
+ <label class="dz-prop">
421
+ <span>Description</span>
422
+ <input type="text" [value]="n.description ?? ''" (change)="patchText('description', $event)" />
423
+ </label>
424
+
425
+ @if (n.kind === 'input') {
426
+ <label class="dz-prop">
427
+ <span>Placeholder</span>
428
+ <input type="text" [value]="field().placeholder ?? ''" (change)="patchText('placeholder', $event)" />
429
+ </label>
430
+ <label class="dz-prop">
431
+ <span>Default value</span>
432
+ <input type="text" [value]="defaultValueText()" (change)="patchDefault($event)" />
433
+ </label>
434
+ <label class="dz-check">
435
+ <input type="checkbox" [checked]="isRequired()" (change)="toggleRequired($event)" />
436
+ <span>Required</span>
437
+ </label>
438
+
439
+ <details class="dz-section" open>
440
+ <summary>Validation</summary>
441
+ <label class="dz-prop"><span>Min length</span>
442
+ <input type="number" [value]="validatorValue('minLength')" (change)="patchValidator('minLength', $event)" /></label>
443
+ <label class="dz-prop"><span>Max length</span>
444
+ <input type="number" [value]="validatorValue('maxLength')" (change)="patchValidator('maxLength', $event)" /></label>
445
+ <label class="dz-prop"><span>Min</span>
446
+ <input type="number" [value]="validatorValue('min')" (change)="patchValidator('min', $event)" /></label>
447
+ <label class="dz-prop"><span>Max</span>
448
+ <input type="number" [value]="validatorValue('max')" (change)="patchValidator('max', $event)" /></label>
449
+ <label class="dz-prop"><span>Pattern (regex)</span>
450
+ <input type="text" [value]="patternValue()" (change)="patchPattern($event)" /></label>
451
+ </details>
452
+
453
+ @if (hasOptions()) {
454
+ <details class="dz-section" open>
455
+ <summary>Options</summary>
456
+ @for (option of field().options ?? []; track $index; let i = $index) {
457
+ <div class="dz-option-row">
458
+ <input type="text" placeholder="Label" [value]="option.label"
459
+ (change)="patchOption(i, 'label', $event)" />
460
+ <input type="text" placeholder="Value" [value]="option.value"
461
+ (change)="patchOption(i, 'value', $event)" />
462
+ <button type="button" (click)="removeOption(i)" aria-label="Remove option">✕</button>
463
+ </div>
464
+ }
465
+ <button type="button" class="dz-add" (click)="addOption()">+ Option</button>
466
+ </details>
467
+ }
468
+ }
469
+
470
+ @if (n.kind === 'static' && n.type === 'content') {
471
+ <label class="dz-prop">
472
+ <span>Content (HTML)</span>
473
+ <textarea rows="5" [value]="static_().content ?? ''" (change)="patchText('content', $event)"></textarea>
474
+ </label>
475
+ }
476
+
477
+ @if (n.kind === 'container' && n.type === 'columns') {
478
+ <label class="dz-prop">
479
+ <span>Column widths (of 12, comma-separated)</span>
480
+ <input type="text" [value]="columnWidths()" (change)="patchColumnWidths($event)" />
481
+ </label>
482
+ }
483
+
484
+ <details class="dz-section">
485
+ <summary>Visibility</summary>
486
+ <label class="dz-check">
487
+ <input type="checkbox" [checked]="n.hidden === true" (change)="patchFlag('hidden', $event)" />
488
+ <span>Hidden</span>
489
+ </label>
490
+ <label class="dz-check">
491
+ <input type="checkbox" [checked]="n.disabled === true" (change)="patchFlag('disabled', $event)" />
492
+ <span>Disabled</span>
493
+ </label>
494
+ <label class="dz-prop">
495
+ <span>Show when field…</span>
496
+ <select [value]="simpleCondition()?.field ?? ''" (change)="patchConditionField($event)">
497
+ <option value="">(always visible)</option>
498
+ @for (path of otherInputPaths(); track path) {
499
+ <option [value]="path" [selected]="simpleCondition()?.field === path">{{ path }}</option>
500
+ }
501
+ </select>
502
+ </label>
503
+ @if (simpleCondition()) {
504
+ <label class="dz-prop">
505
+ <span>…equals value</span>
506
+ <input type="text" [value]="conditionValueText()" (change)="patchConditionValue($event)" />
507
+ </label>
508
+ }
509
+ <label class="dz-prop">
510
+ <span>Show when expression (advanced)</span>
511
+ <input
512
+ type="text"
513
+ placeholder="ex.: total > 1000 && status == 'ok'"
514
+ [value]="conditionExpression()"
515
+ (change)="patchConditionExpression($event)"
516
+ />
517
+ </label>
518
+ </details>
519
+
520
+ @if (n.kind === 'input') {
521
+ <details class="dz-section">
522
+ <summary>Advanced</summary>
523
+ <label class="dz-prop">
524
+ <span>Calculated value (expression)</span>
525
+ <input
526
+ type="text"
527
+ placeholder="ex.: qty * price"
528
+ [value]="field().calculate ?? ''"
529
+ (change)="patchCalculate($event)"
530
+ />
531
+ </label>
532
+ <label class="dz-prop">
533
+ <span>Input mask (0=digit A=letter S=any)</span>
534
+ <input
535
+ type="text"
536
+ placeholder="ex.: 000.000.000-00"
537
+ [value]="field().format?.mask ?? ''"
538
+ (change)="patchMask($event)"
539
+ />
540
+ </label>
541
+ </details>
542
+ }
543
+ </div>
544
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
545
+ }
546
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InspectorComponent, decorators: [{
547
+ type: Component,
548
+ args: [{
549
+ selector: 'mform-inspector',
550
+ changeDetection: ChangeDetectionStrategy.OnPush,
551
+ encapsulation: ViewEncapsulation.None,
552
+ template: `
553
+ @let n = node();
554
+ <div class="dz-inspector">
555
+ <h3>{{ n.type }} <code>{{ n.key }}</code></h3>
556
+
557
+ <label class="dz-prop">
558
+ <span>Key</span>
559
+ <input type="text" [value]="n.key" (change)="patchText('key', $event)" />
560
+ </label>
561
+ <label class="dz-prop">
562
+ <span>Label</span>
563
+ <input type="text" [value]="n.label ?? ''" (change)="patchText('label', $event)" />
564
+ </label>
565
+ <label class="dz-prop">
566
+ <span>Description</span>
567
+ <input type="text" [value]="n.description ?? ''" (change)="patchText('description', $event)" />
568
+ </label>
569
+
570
+ @if (n.kind === 'input') {
571
+ <label class="dz-prop">
572
+ <span>Placeholder</span>
573
+ <input type="text" [value]="field().placeholder ?? ''" (change)="patchText('placeholder', $event)" />
574
+ </label>
575
+ <label class="dz-prop">
576
+ <span>Default value</span>
577
+ <input type="text" [value]="defaultValueText()" (change)="patchDefault($event)" />
578
+ </label>
579
+ <label class="dz-check">
580
+ <input type="checkbox" [checked]="isRequired()" (change)="toggleRequired($event)" />
581
+ <span>Required</span>
582
+ </label>
583
+
584
+ <details class="dz-section" open>
585
+ <summary>Validation</summary>
586
+ <label class="dz-prop"><span>Min length</span>
587
+ <input type="number" [value]="validatorValue('minLength')" (change)="patchValidator('minLength', $event)" /></label>
588
+ <label class="dz-prop"><span>Max length</span>
589
+ <input type="number" [value]="validatorValue('maxLength')" (change)="patchValidator('maxLength', $event)" /></label>
590
+ <label class="dz-prop"><span>Min</span>
591
+ <input type="number" [value]="validatorValue('min')" (change)="patchValidator('min', $event)" /></label>
592
+ <label class="dz-prop"><span>Max</span>
593
+ <input type="number" [value]="validatorValue('max')" (change)="patchValidator('max', $event)" /></label>
594
+ <label class="dz-prop"><span>Pattern (regex)</span>
595
+ <input type="text" [value]="patternValue()" (change)="patchPattern($event)" /></label>
596
+ </details>
597
+
598
+ @if (hasOptions()) {
599
+ <details class="dz-section" open>
600
+ <summary>Options</summary>
601
+ @for (option of field().options ?? []; track $index; let i = $index) {
602
+ <div class="dz-option-row">
603
+ <input type="text" placeholder="Label" [value]="option.label"
604
+ (change)="patchOption(i, 'label', $event)" />
605
+ <input type="text" placeholder="Value" [value]="option.value"
606
+ (change)="patchOption(i, 'value', $event)" />
607
+ <button type="button" (click)="removeOption(i)" aria-label="Remove option">✕</button>
608
+ </div>
609
+ }
610
+ <button type="button" class="dz-add" (click)="addOption()">+ Option</button>
611
+ </details>
612
+ }
613
+ }
614
+
615
+ @if (n.kind === 'static' && n.type === 'content') {
616
+ <label class="dz-prop">
617
+ <span>Content (HTML)</span>
618
+ <textarea rows="5" [value]="static_().content ?? ''" (change)="patchText('content', $event)"></textarea>
619
+ </label>
620
+ }
621
+
622
+ @if (n.kind === 'container' && n.type === 'columns') {
623
+ <label class="dz-prop">
624
+ <span>Column widths (of 12, comma-separated)</span>
625
+ <input type="text" [value]="columnWidths()" (change)="patchColumnWidths($event)" />
626
+ </label>
627
+ }
628
+
629
+ <details class="dz-section">
630
+ <summary>Visibility</summary>
631
+ <label class="dz-check">
632
+ <input type="checkbox" [checked]="n.hidden === true" (change)="patchFlag('hidden', $event)" />
633
+ <span>Hidden</span>
634
+ </label>
635
+ <label class="dz-check">
636
+ <input type="checkbox" [checked]="n.disabled === true" (change)="patchFlag('disabled', $event)" />
637
+ <span>Disabled</span>
638
+ </label>
639
+ <label class="dz-prop">
640
+ <span>Show when field…</span>
641
+ <select [value]="simpleCondition()?.field ?? ''" (change)="patchConditionField($event)">
642
+ <option value="">(always visible)</option>
643
+ @for (path of otherInputPaths(); track path) {
644
+ <option [value]="path" [selected]="simpleCondition()?.field === path">{{ path }}</option>
645
+ }
646
+ </select>
647
+ </label>
648
+ @if (simpleCondition()) {
649
+ <label class="dz-prop">
650
+ <span>…equals value</span>
651
+ <input type="text" [value]="conditionValueText()" (change)="patchConditionValue($event)" />
652
+ </label>
653
+ }
654
+ <label class="dz-prop">
655
+ <span>Show when expression (advanced)</span>
656
+ <input
657
+ type="text"
658
+ placeholder="ex.: total > 1000 && status == 'ok'"
659
+ [value]="conditionExpression()"
660
+ (change)="patchConditionExpression($event)"
661
+ />
662
+ </label>
663
+ </details>
664
+
665
+ @if (n.kind === 'input') {
666
+ <details class="dz-section">
667
+ <summary>Advanced</summary>
668
+ <label class="dz-prop">
669
+ <span>Calculated value (expression)</span>
670
+ <input
671
+ type="text"
672
+ placeholder="ex.: qty * price"
673
+ [value]="field().calculate ?? ''"
674
+ (change)="patchCalculate($event)"
675
+ />
676
+ </label>
677
+ <label class="dz-prop">
678
+ <span>Input mask (0=digit A=letter S=any)</span>
679
+ <input
680
+ type="text"
681
+ placeholder="ex.: 000.000.000-00"
682
+ [value]="field().format?.mask ?? ''"
683
+ (change)="patchMask($event)"
684
+ />
685
+ </label>
686
+ </details>
687
+ }
688
+ </div>
689
+ `,
690
+ }]
691
+ }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], nodePatch: [{ type: i0.Output, args: ["nodePatch"] }] } });
692
+
693
+ function input(type, dataType, label, extra) {
694
+ return (schema) => ({
695
+ kind: 'input',
696
+ key: generateKey(schema, type),
697
+ type,
698
+ label,
699
+ dataType,
700
+ ...extra,
701
+ });
702
+ }
703
+ const DEFAULT_CATALOG = [
704
+ {
705
+ label: 'Basic',
706
+ entries: [
707
+ { type: 'text', label: 'Text', icon: 'Aa', create: input('text', 'string', 'Text') },
708
+ { type: 'textarea', label: 'Text area', icon: '¶', create: input('textarea', 'string', 'Text area') },
709
+ { type: 'number', label: 'Number', icon: '#', create: input('number', 'number', 'Number') },
710
+ { type: 'currency', label: 'Currency', icon: '$', create: input('currency', 'number', 'Currency') },
711
+ { type: 'checkbox', label: 'Checkbox', icon: '☑', create: input('checkbox', 'boolean', 'Checkbox') },
712
+ { type: 'email', label: 'Email', icon: '@', create: input('email', 'string', 'Email', { validators: [{ type: 'email' }] }) },
713
+ { type: 'phone', label: 'Phone', icon: '☎', create: input('phone', 'string', 'Phone') },
714
+ { type: 'password', label: 'Password', icon: '••', create: input('password', 'string', 'Password') },
715
+ ],
716
+ },
717
+ {
718
+ label: 'Choices',
719
+ entries: [
720
+ {
721
+ type: 'select', label: 'Select', icon: '▾',
722
+ create: (s) => ({ ...input('select', 'string', 'Select')(s), options: [
723
+ { label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' },
724
+ ] }),
725
+ },
726
+ {
727
+ type: 'radio', label: 'Radio', icon: '◉',
728
+ create: (s) => ({ ...input('radio', 'string', 'Radio')(s), options: [
729
+ { label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' },
730
+ ] }),
731
+ },
732
+ {
733
+ type: 'checkbox-group', label: 'Checkbox group', icon: '☷',
734
+ create: (s) => ({ ...input('checkbox-group', 'object', 'Checkbox group')(s), options: [
735
+ { label: 'Option 1', value: 'option1' }, { label: 'Option 2', value: 'option2' },
736
+ ] }),
737
+ },
738
+ { type: 'tags', label: 'Tags', icon: '⌗', create: input('tags', 'array', 'Tags') },
739
+ {
740
+ type: 'reference', label: 'Reference', icon: '⌕',
741
+ create: (s) => ({
742
+ ...input('reference', 'string', 'Reference')(s),
743
+ optionsSource: { type: 'provider', name: 'reference' },
744
+ }),
745
+ },
746
+ ],
747
+ },
748
+ {
749
+ label: 'Date & files',
750
+ entries: [
751
+ { type: 'datetime', label: 'Date & time', icon: '📅', create: input('datetime', 'date', 'Date & time') },
752
+ { type: 'day', label: 'Date', icon: '▦', create: input('day', 'string', 'Date') },
753
+ { type: 'time', label: 'Time', icon: '◷', create: input('time', 'string', 'Time') },
754
+ { type: 'file', label: 'File', icon: '📎', create: input('file', 'array', 'File') },
755
+ ],
756
+ },
757
+ {
758
+ label: 'Layout',
759
+ entries: [
760
+ {
761
+ type: 'panel', label: 'Panel', icon: '▭',
762
+ create: (s) => ({ kind: 'container', key: generateKey(s, 'panel'), type: 'panel',
763
+ dataScope: 'inherit', label: 'Panel', children: [] }),
764
+ },
765
+ {
766
+ type: 'columns', label: 'Columns', icon: '▥',
767
+ create: (s) => ({ kind: 'container', key: generateKey(s, 'columns'), type: 'columns',
768
+ dataScope: 'inherit', children: [], columns: [
769
+ { width: 6, children: [] }, { width: 6, children: [] },
770
+ ] }),
771
+ },
772
+ {
773
+ type: 'tabs', label: 'Tabs', icon: '⧉',
774
+ create: (s) => {
775
+ const key = generateKey(s, 'tabs');
776
+ return { kind: 'container', key, type: 'tabs', dataScope: 'inherit', children: [
777
+ { kind: 'container', key: `${key}-tab-1`, type: 'panel', dataScope: 'inherit', label: 'Tab 1', children: [] },
778
+ { kind: 'container', key: `${key}-tab-2`, type: 'panel', dataScope: 'inherit', label: 'Tab 2', children: [] },
779
+ ] };
780
+ },
781
+ },
782
+ {
783
+ type: 'group', label: 'Data group', icon: '⧈',
784
+ create: (s) => ({ kind: 'container', key: generateKey(s, 'group'), type: 'group',
785
+ dataScope: 'nested', label: 'Group', children: [] }),
786
+ },
787
+ {
788
+ type: 'datagrid', label: 'Data grid', icon: '☰',
789
+ create: (s) => ({ kind: 'container', key: generateKey(s, 'datagrid'), type: 'datagrid',
790
+ dataScope: 'array', label: 'Data grid', children: [] }),
791
+ },
792
+ ],
793
+ },
794
+ {
795
+ label: 'Static',
796
+ entries: [
797
+ {
798
+ type: 'content', label: 'Content', icon: '¶¶',
799
+ create: (s) => ({ kind: 'static', key: generateKey(s, 'content'), type: 'content',
800
+ content: '<p>Content</p>' }),
801
+ },
802
+ {
803
+ type: 'button', label: 'Submit button', icon: '⏎',
804
+ create: (s) => ({ kind: 'static', key: generateKey(s, 'button'), type: 'button',
805
+ label: 'Submit', props: { action: 'submit' } }),
806
+ },
807
+ ],
808
+ },
809
+ ];
810
+
811
+ const EMPTY_SCHEMA = { version: 1, fields: [] };
812
+ const HISTORY_LIMIT = 50;
813
+ /**
814
+ * `<mform-designer>` — visual authoring of form schemas.
815
+ *
816
+ * The designer edits a DOCUMENT (the schema): every change goes through the
817
+ * core's immutable edit operations, feeding undo/redo and `schemaChanged`.
818
+ * Persistence, publishing and permissions belong to the host.
819
+ */
820
+ class FormDesignerComponent {
821
+ /** Initial native schema to edit. */
822
+ schema = input$1(null, ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
823
+ /** Initial foreign definition (converted through importers). */
824
+ source = input$1(null, ...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
825
+ /** Extra palette groups contributed by the host. */
826
+ extraCatalog = input$1([], ...(ngDevMode ? [{ debugName: "extraCatalog" }] : /* istanbul ignore next */ []));
827
+ /** Emitted after every committed edit (undo/redo included). */
828
+ schemaChanged = output();
829
+ draft = signal(EMPTY_SCHEMA, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
830
+ past = signal([], ...(ngDevMode ? [{ debugName: "past" }] : /* istanbul ignore next */ []));
831
+ future = signal([], ...(ngDevMode ? [{ debugName: "future" }] : /* istanbul ignore next */ []));
832
+ selected = signal(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
833
+ preview = signal(false, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
834
+ previewData = signal({}, ...(ngDevMode ? [{ debugName: "previewData" }] : /* istanbul ignore next */ []));
835
+ dialog = signal(null, ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
836
+ importError = signal('', ...(ngDevMode ? [{ debugName: "importError" }] : /* istanbul ignore next */ []));
837
+ showDiagnostics = signal(false, ...(ngDevMode ? [{ debugName: "showDiagnostics" }] : /* istanbul ignore next */ []));
838
+ catalog = computed(() => [...DEFAULT_CATALOG, ...this.extraCatalog()], ...(ngDevMode ? [{ debugName: "catalog" }] : /* istanbul ignore next */ []));
839
+ diagnostics = computed(() => validateSchema(this.draft()), ...(ngDevMode ? [{ debugName: "diagnostics" }] : /* istanbul ignore next */ []));
840
+ exportedJson = computed(() => JSON.stringify(this.draft(), null, 2), ...(ngDevMode ? [{ debugName: "exportedJson" }] : /* istanbul ignore next */ []));
841
+ /** Disposable engine driving the canvas leaves (defaults visible). */
842
+ canvasEngine = computed(() => createFormEngine(this.draft()), ...(ngDevMode ? [{ debugName: "canvasEngine" }] : /* istanbul ignore next */ []));
843
+ selectedNode = computed(() => {
844
+ const path = this.selected();
845
+ return path ? (getNodeAt(this.draft(), path) ?? null) : null;
846
+ }, ...(ngDevMode ? [{ debugName: "selectedNode" }] : /* istanbul ignore next */ []));
847
+ /** Flat structure tree for navigation. */
848
+ tree = computed(() => {
849
+ const items = [];
850
+ const visit = (nodes, depth, prefix) => {
851
+ nodes.forEach((node, i) => {
852
+ const path = [...prefix, i];
853
+ items.push({
854
+ id: path.join('.'),
855
+ label: `${node.label ?? node.key} · ${node.type}`,
856
+ depth,
857
+ path,
858
+ });
859
+ if (node.kind === 'container')
860
+ visit(node.children, depth + 1, path);
861
+ });
862
+ };
863
+ visit(this.draft().fields, 0, []);
864
+ return items;
865
+ }, ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
866
+ constructor() {
867
+ // (Re)load the document whenever the host hands a new definition in.
868
+ effect(() => {
869
+ const native = this.schema();
870
+ const source = this.source();
871
+ // Hosts often feed `schemaChanged` back into [schema] (controlled
872
+ // usage) — the echo of our own draft must not reset history/selection.
873
+ if (native && native === this.draft())
874
+ return;
875
+ let initial = EMPTY_SCHEMA;
876
+ if (native) {
877
+ initial = native;
878
+ }
879
+ else if (source !== null && source !== undefined) {
880
+ try {
881
+ initial = importForm(source, [createLegacyImporter()]).schema;
882
+ }
883
+ catch {
884
+ initial = EMPTY_SCHEMA;
885
+ }
886
+ }
887
+ this.draft.set(initial);
888
+ this.past.set([]);
889
+ this.future.set([]);
890
+ this.selected.set(null);
891
+ });
892
+ }
893
+ // -- document mutation ----------------------------------------------------
894
+ commit(next) {
895
+ this.past.update((stack) => [...stack.slice(-(HISTORY_LIMIT - 1)), this.draft()]);
896
+ this.future.set([]);
897
+ this.draft.set(next);
898
+ this.schemaChanged.emit(next);
899
+ }
900
+ undo() {
901
+ const past = this.past();
902
+ if (past.length === 0)
903
+ return;
904
+ this.future.update((stack) => [this.draft(), ...stack]);
905
+ this.past.set(past.slice(0, -1));
906
+ this.draft.set(past[past.length - 1]);
907
+ this.selected.set(null);
908
+ this.schemaChanged.emit(this.draft());
909
+ }
910
+ redo() {
911
+ const future = this.future();
912
+ if (future.length === 0)
913
+ return;
914
+ this.past.update((stack) => [...stack, this.draft()]);
915
+ this.future.set(future.slice(1));
916
+ this.draft.set(future[0]);
917
+ this.selected.set(null);
918
+ this.schemaChanged.emit(this.draft());
919
+ }
920
+ add(entry) {
921
+ const node = entry.create(this.draft());
922
+ const target = this.insertionPath();
923
+ this.commit(insertNode(this.draft(), target, node));
924
+ this.select(target);
925
+ }
926
+ /** New nodes land inside a selected container, after a selected leaf, or at the end. */
927
+ insertionPath() {
928
+ const selected = this.selected();
929
+ if (!selected)
930
+ return [this.draft().fields.length];
931
+ const node = getNodeAt(this.draft(), selected);
932
+ if (node?.kind === 'container' && node.type !== 'columns') {
933
+ return [...selected, node.children.length];
934
+ }
935
+ const { parent, index } = parentOf(selected);
936
+ return [...parent, index + 1];
937
+ }
938
+ removeSelected() {
939
+ const selected = this.selected();
940
+ if (!selected)
941
+ return;
942
+ this.commit(removeNode(this.draft(), selected));
943
+ this.selected.set(null);
944
+ }
945
+ duplicateSelected() {
946
+ const selected = this.selected();
947
+ if (!selected)
948
+ return;
949
+ this.commit(duplicateNode(this.draft(), selected));
950
+ }
951
+ moveSelected(delta) {
952
+ const selected = this.selected();
953
+ if (!selected)
954
+ return;
955
+ const { parent, index } = parentOf(selected);
956
+ const siblings = parent.length === 0
957
+ ? this.draft().fields
958
+ : getNodeAt(this.draft(), parent)?.children ?? [];
959
+ const target = index + delta;
960
+ if (target < 0 || target >= siblings.length)
961
+ return;
962
+ this.commit(moveNode(this.draft(), selected, [...parent, target]));
963
+ this.select([...parent, target]);
964
+ }
965
+ applyPatch(event) {
966
+ const selected = this.selected();
967
+ if (!selected)
968
+ return;
969
+ this.commit(updateNode(this.draft(), selected, event.patch));
970
+ }
971
+ // -- selection ------------------------------------------------------------
972
+ select(path) {
973
+ this.selected.set(path);
974
+ }
975
+ isSelectedPath(path) {
976
+ return this.selected()?.join('.') === path.join('.');
977
+ }
978
+ // -- import / export ------------------------------------------------------
979
+ openExport() {
980
+ this.dialog.set(this.dialog() === 'export' ? null : 'export');
981
+ }
982
+ doImport(raw) {
983
+ try {
984
+ const parsed = JSON.parse(raw);
985
+ const { schema } = importForm(parsed, [createLegacyImporter()]);
986
+ this.commit(schema);
987
+ this.selected.set(null);
988
+ this.dialog.set(null);
989
+ this.importError.set('');
990
+ }
991
+ catch (error) {
992
+ this.importError.set('Could not import: ' + String(error));
993
+ }
994
+ }
995
+ // -- keyboard -------------------------------------------------------------
996
+ onKeydown(event) {
997
+ const target = event.target;
998
+ if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName))
999
+ return;
1000
+ const key = event.key.toLowerCase();
1001
+ if (event.ctrlKey && key === 'z' && !event.shiftKey) {
1002
+ event.preventDefault();
1003
+ this.undo();
1004
+ }
1005
+ else if ((event.ctrlKey && key === 'y') || (event.ctrlKey && event.shiftKey && key === 'z')) {
1006
+ event.preventDefault();
1007
+ this.redo();
1008
+ }
1009
+ else if (event.ctrlKey && key === 'd') {
1010
+ event.preventDefault();
1011
+ this.duplicateSelected();
1012
+ }
1013
+ else if (key === 'delete') {
1014
+ event.preventDefault();
1015
+ this.removeSelected();
1016
+ }
1017
+ }
1018
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1019
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FormDesignerComponent, isStandalone: true, selector: "mform-designer", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, extraCatalog: { classPropertyName: "extraCatalog", publicName: "extraCatalog", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChanged: "schemaChanged" }, ngImport: i0, template: `
1020
+ <div class="dz-shell" (keydown)="onKeydown($event)">
1021
+ <header class="dz-toolbar">
1022
+ <div class="dz-tools">
1023
+ <button type="button" (click)="undo()" [disabled]="past().length === 0" title="Undo (Ctrl+Z)">↶</button>
1024
+ <button type="button" (click)="redo()" [disabled]="future().length === 0" title="Redo (Ctrl+Y)">↷</button>
1025
+ <span class="dz-sep"></span>
1026
+ <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" title="Move up">↑</button>
1027
+ <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1028
+ <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1029
+ <button type="button" (click)="removeSelected()" [disabled]="!selected()" title="Delete (Del)">🗑</button>
1030
+ </div>
1031
+ <div class="dz-tools">
1032
+ @if (diagnostics().length > 0) {
1033
+ <button type="button" class="dz-diag-badge" (click)="showDiagnostics.set(!showDiagnostics())">
1034
+ ⚠ {{ diagnostics().length }}
1035
+ </button>
1036
+ }
1037
+ <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1038
+ <button type="button" (click)="openExport()">Export</button>
1039
+ <button type="button" class="dz-primary" (click)="preview.set(!preview())">
1040
+ {{ preview() ? 'Design' : 'Preview' }}
1041
+ </button>
1042
+ </div>
1043
+ </header>
1044
+
1045
+ @if (showDiagnostics() && diagnostics().length > 0) {
1046
+ <div class="dz-diagnostics" role="alert">
1047
+ @for (diag of diagnostics(); track $index) {
1048
+ <div class="dz-diag" [class.is-warning]="diag.severity === 'warning'">
1049
+ <code>{{ diag.code }}</code> {{ diag.message }} <em>{{ diag.path }}</em>
1050
+ </div>
1051
+ }
1052
+ </div>
1053
+ }
1054
+
1055
+ @if (dialog() === 'export') {
1056
+ <div class="dz-dialog">
1057
+ <h4>Schema JSON</h4>
1058
+ <textarea readonly rows="12">{{ exportedJson() }}</textarea>
1059
+ <div class="dz-dialog-actions">
1060
+ <button type="button" (click)="dialog.set(null)">Close</button>
1061
+ </div>
1062
+ </div>
1063
+ }
1064
+ @if (dialog() === 'import') {
1065
+ <div class="dz-dialog">
1066
+ <h4>Import definition (native schema or supported formats)</h4>
1067
+ <textarea rows="12" #importArea placeholder='{"version":1,"fields":[…]} or a legacy definition'></textarea>
1068
+ @if (importError()) {
1069
+ <p class="dz-import-error" role="alert">{{ importError() }}</p>
1070
+ }
1071
+ <div class="dz-dialog-actions">
1072
+ <button type="button" (click)="dialog.set(null)">Cancel</button>
1073
+ <button type="button" class="dz-primary" (click)="doImport(importArea.value)">Import</button>
1074
+ </div>
1075
+ </div>
1076
+ }
1077
+
1078
+ @if (preview()) {
1079
+ <div class="dz-preview">
1080
+ <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
1081
+ <details class="dz-preview-data">
1082
+ <summary>Test data</summary>
1083
+ <pre>{{ previewData() | json }}</pre>
1084
+ </details>
1085
+ </div>
1086
+ } @else {
1087
+ <div class="dz-body">
1088
+ <aside class="dz-palette">
1089
+ <h4>Fields</h4>
1090
+ @for (group of catalog(); track group.label) {
1091
+ <div class="dz-group">
1092
+ <span class="dz-group-label">{{ group.label }}</span>
1093
+ <div class="dz-group-entries">
1094
+ @for (entry of group.entries; track entry.type) {
1095
+ <button type="button" class="dz-palette-item" (click)="add(entry)"
1096
+ [title]="'Add ' + entry.label">
1097
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1098
+ </button>
1099
+ }
1100
+ </div>
1101
+ </div>
1102
+ }
1103
+ <h4>Structure</h4>
1104
+ <div class="dz-tree" role="tree">
1105
+ @for (item of tree(); track item.id) {
1106
+ <button
1107
+ type="button"
1108
+ role="treeitem"
1109
+ class="dz-tree-item"
1110
+ [class.is-selected]="isSelectedPath(item.path)"
1111
+ [style.padding-left.px]="8 + item.depth * 14"
1112
+ (click)="select(item.path)"
1113
+ >
1114
+ {{ item.label }}
1115
+ </button>
1116
+ } @empty {
1117
+ <span class="dz-empty">no fields yet</span>
1118
+ }
1119
+ </div>
1120
+ </aside>
1121
+
1122
+ <main class="dz-canvas" (click)="select(null)">
1123
+ @for (node of draft().fields; track node.key; let i = $index) {
1124
+ <mform-designer-node
1125
+ [node]="node"
1126
+ [path]="[i]"
1127
+ [engine]="canvasEngine()"
1128
+ [selected]="selected()"
1129
+ (selectNode)="select($event)"
1130
+ />
1131
+ } @empty {
1132
+ <div class="dz-canvas-empty">
1133
+ Click a field in the palette to start building.
1134
+ </div>
1135
+ }
1136
+ </main>
1137
+
1138
+ <aside class="dz-props">
1139
+ @if (selectedNode(); as node) {
1140
+ <mform-inspector
1141
+ [node]="node"
1142
+ [schema]="draft()"
1143
+ (nodePatch)="applyPatch($event)"
1144
+ />
1145
+ } @else {
1146
+ <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1147
+ }
1148
+ </aside>
1149
+ </div>
1150
+ }
1151
+ </div>
1152
+ `, isInline: true, styles: [".dz-shell{display:flex;flex-direction:column;height:100%;min-height:480px;font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e);background:var(--mform-bg, #f6f5fa);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.dz-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-tools{display:flex;align-items:center;gap:6px}.dz-toolbar button{font:inherit;font-size:13px;padding:6px 10px;border:1px solid transparent;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-toolbar button:hover:not(:disabled){background:var(--mform-bg, #f6f5fa)}.dz-toolbar button:disabled{opacity:.35;cursor:default}.dz-toolbar .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-sep{width:1px;height:18px;background:var(--mform-border, #d9d5e6)}.dz-diag-badge{color:#b45309;font-weight:600}.dz-diagnostics{max-height:140px;overflow-y:auto;padding:8px 14px;background:#fdf5ef;border-bottom:1px solid var(--mform-border, #d9d5e6);font-size:12px}.dz-diag{padding:2px 0;color:#9a3412}.dz-diag.is-warning{color:#92400e}.dz-diag code{font-weight:600;margin-right:6px}.dz-diag em{color:var(--mform-muted, #6b6580);margin-left:6px}.dz-dialog{padding:14px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-dialog h4{margin:0 0 8px;font-size:13px}.dz-dialog textarea{width:100%;font-family:ui-monospace,Consolas,monospace;font-size:12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;box-sizing:border-box;resize:vertical}.dz-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.dz-dialog-actions button{font:inherit;font-size:13px;padding:7px 14px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:none;cursor:pointer}.dz-dialog-actions .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-color:transparent}.dz-import-error{color:var(--mform-danger, #c62842);font-size:12px}.dz-body{display:grid;grid-template-columns:240px 1fr 280px;flex:1;min-height:0}.dz-palette{border-right:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);padding:12px;overflow-y:auto}.dz-palette h4{margin:4px 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580)}.dz-group{margin-bottom:10px}.dz-group-label{font-size:11px;color:var(--mform-muted, #6b6580)}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin-top:4px}.dz-palette-item{display:flex;align-items:center;gap:8px;font:inherit;font-size:13px;text-align:left;padding:6px 8px;border:none;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-palette-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px;font-size:11px}.dz-tree{display:flex;flex-direction:column;gap:1px}.dz-tree-item{font:inherit;font-size:12px;text-align:left;padding:4px 8px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dz-tree-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-tree-item.is-selected{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-canvas{padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:10px}.dz-canvas-empty,.dz-empty{color:var(--mform-muted, #6b6580);font-size:13px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:18px;text-align:center}.dz-node{position:relative;border:1px solid transparent;border-radius:var(--mform-radius, 10px);padding:20px 10px 10px;cursor:pointer;background:var(--mform-surface, #fff)}.dz-node:hover{border-color:var(--mform-border, #d9d5e6)}.dz-node.is-selected{border-color:var(--mform-primary, #6d4bd0);box-shadow:0 0 0 1px var(--mform-primary, #6d4bd0)}.dz-chip{position:absolute;top:2px;left:10px;font-size:10px;color:var(--mform-muted, #6b6580);pointer-events:none}.dz-node.is-selected>.dz-chip{color:var(--mform-primary, #6d4bd0);font-weight:600}.dz-children{display:flex;flex-direction:column;gap:8px}.dz-columns{display:grid;gap:10px}.dz-column{display:flex;flex-direction:column;gap:8px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:8px;padding:8px;min-height:40px}.dz-tabbar{display:flex;gap:4px;margin-bottom:8px;border-bottom:2px solid var(--mform-border, #d9d5e6)}.dz-tab{font:inherit;font-size:12px;padding:5px 12px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.dz-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.dz-leaf{pointer-events:none}.dz-props{border-left:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);overflow-y:auto}.dz-props-empty{margin:12px}.dz-inspector{padding:12px;display:flex;flex-direction:column;gap:10px}.dz-inspector h3{margin:0;font-size:13px}.dz-inspector h3 code{color:var(--mform-muted, #6b6580);font-weight:400}.dz-prop{display:flex;flex-direction:column;gap:4px;font-size:12px}.dz-prop span{color:var(--mform-muted, #6b6580)}.dz-prop input,.dz-prop select,.dz-prop textarea{font:inherit;font-size:13px;padding:7px 9px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:#fff;width:100%;box-sizing:border-box}.dz-check{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer}.dz-check input{accent-color:var(--mform-primary, #6d4bd0)}.dz-section{border-top:1px solid var(--mform-border, #d9d5e6);padding-top:8px}.dz-section summary{font-size:12px;font-weight:600;cursor:pointer;margin-bottom:8px}.dz-section .dz-prop{margin-bottom:8px}.dz-option-row{display:flex;gap:6px;margin-bottom:6px}.dz-option-row input{flex:1;min-width:0;font-size:12px;padding:6px 8px;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px}.dz-option-row button{border:none;background:none;cursor:pointer;color:var(--mform-muted, #6b6580)}.dz-add{font:inherit;font-size:12px;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);background:none;border-radius:8px;padding:6px 10px;cursor:pointer}.dz-preview{padding:20px;overflow-y:auto}.dz-preview-data{margin-top:16px;font-size:12px}.dz-preview-data pre{background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: DesignerNodeComponent, selector: "mform-designer-node", inputs: ["node", "path", "engine", "selected"], outputs: ["selectNode"] }, { kind: "component", type: InspectorComponent, selector: "mform-inspector", inputs: ["node", "schema"], outputs: ["nodePatch"] }, { kind: "component", type: FormRendererComponent, selector: "mform-renderer", inputs: ["schema", "source", "initialData", "optionsProviders", "messages", "validators", "mode", "submitHandler", "labels"], outputs: ["submitted", "valueChanged"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1153
+ }
1154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, decorators: [{
1155
+ type: Component,
1156
+ args: [{ selector: 'mform-designer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [DesignerNodeComponent, InspectorComponent, FormRendererComponent, JsonPipe], template: `
1157
+ <div class="dz-shell" (keydown)="onKeydown($event)">
1158
+ <header class="dz-toolbar">
1159
+ <div class="dz-tools">
1160
+ <button type="button" (click)="undo()" [disabled]="past().length === 0" title="Undo (Ctrl+Z)">↶</button>
1161
+ <button type="button" (click)="redo()" [disabled]="future().length === 0" title="Redo (Ctrl+Y)">↷</button>
1162
+ <span class="dz-sep"></span>
1163
+ <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" title="Move up">↑</button>
1164
+ <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1165
+ <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1166
+ <button type="button" (click)="removeSelected()" [disabled]="!selected()" title="Delete (Del)">🗑</button>
1167
+ </div>
1168
+ <div class="dz-tools">
1169
+ @if (diagnostics().length > 0) {
1170
+ <button type="button" class="dz-diag-badge" (click)="showDiagnostics.set(!showDiagnostics())">
1171
+ ⚠ {{ diagnostics().length }}
1172
+ </button>
1173
+ }
1174
+ <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1175
+ <button type="button" (click)="openExport()">Export</button>
1176
+ <button type="button" class="dz-primary" (click)="preview.set(!preview())">
1177
+ {{ preview() ? 'Design' : 'Preview' }}
1178
+ </button>
1179
+ </div>
1180
+ </header>
1181
+
1182
+ @if (showDiagnostics() && diagnostics().length > 0) {
1183
+ <div class="dz-diagnostics" role="alert">
1184
+ @for (diag of diagnostics(); track $index) {
1185
+ <div class="dz-diag" [class.is-warning]="diag.severity === 'warning'">
1186
+ <code>{{ diag.code }}</code> {{ diag.message }} <em>{{ diag.path }}</em>
1187
+ </div>
1188
+ }
1189
+ </div>
1190
+ }
1191
+
1192
+ @if (dialog() === 'export') {
1193
+ <div class="dz-dialog">
1194
+ <h4>Schema JSON</h4>
1195
+ <textarea readonly rows="12">{{ exportedJson() }}</textarea>
1196
+ <div class="dz-dialog-actions">
1197
+ <button type="button" (click)="dialog.set(null)">Close</button>
1198
+ </div>
1199
+ </div>
1200
+ }
1201
+ @if (dialog() === 'import') {
1202
+ <div class="dz-dialog">
1203
+ <h4>Import definition (native schema or supported formats)</h4>
1204
+ <textarea rows="12" #importArea placeholder='{"version":1,"fields":[…]} or a legacy definition'></textarea>
1205
+ @if (importError()) {
1206
+ <p class="dz-import-error" role="alert">{{ importError() }}</p>
1207
+ }
1208
+ <div class="dz-dialog-actions">
1209
+ <button type="button" (click)="dialog.set(null)">Cancel</button>
1210
+ <button type="button" class="dz-primary" (click)="doImport(importArea.value)">Import</button>
1211
+ </div>
1212
+ </div>
1213
+ }
1214
+
1215
+ @if (preview()) {
1216
+ <div class="dz-preview">
1217
+ <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
1218
+ <details class="dz-preview-data">
1219
+ <summary>Test data</summary>
1220
+ <pre>{{ previewData() | json }}</pre>
1221
+ </details>
1222
+ </div>
1223
+ } @else {
1224
+ <div class="dz-body">
1225
+ <aside class="dz-palette">
1226
+ <h4>Fields</h4>
1227
+ @for (group of catalog(); track group.label) {
1228
+ <div class="dz-group">
1229
+ <span class="dz-group-label">{{ group.label }}</span>
1230
+ <div class="dz-group-entries">
1231
+ @for (entry of group.entries; track entry.type) {
1232
+ <button type="button" class="dz-palette-item" (click)="add(entry)"
1233
+ [title]="'Add ' + entry.label">
1234
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1235
+ </button>
1236
+ }
1237
+ </div>
1238
+ </div>
1239
+ }
1240
+ <h4>Structure</h4>
1241
+ <div class="dz-tree" role="tree">
1242
+ @for (item of tree(); track item.id) {
1243
+ <button
1244
+ type="button"
1245
+ role="treeitem"
1246
+ class="dz-tree-item"
1247
+ [class.is-selected]="isSelectedPath(item.path)"
1248
+ [style.padding-left.px]="8 + item.depth * 14"
1249
+ (click)="select(item.path)"
1250
+ >
1251
+ {{ item.label }}
1252
+ </button>
1253
+ } @empty {
1254
+ <span class="dz-empty">no fields yet</span>
1255
+ }
1256
+ </div>
1257
+ </aside>
1258
+
1259
+ <main class="dz-canvas" (click)="select(null)">
1260
+ @for (node of draft().fields; track node.key; let i = $index) {
1261
+ <mform-designer-node
1262
+ [node]="node"
1263
+ [path]="[i]"
1264
+ [engine]="canvasEngine()"
1265
+ [selected]="selected()"
1266
+ (selectNode)="select($event)"
1267
+ />
1268
+ } @empty {
1269
+ <div class="dz-canvas-empty">
1270
+ Click a field in the palette to start building.
1271
+ </div>
1272
+ }
1273
+ </main>
1274
+
1275
+ <aside class="dz-props">
1276
+ @if (selectedNode(); as node) {
1277
+ <mform-inspector
1278
+ [node]="node"
1279
+ [schema]="draft()"
1280
+ (nodePatch)="applyPatch($event)"
1281
+ />
1282
+ } @else {
1283
+ <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1284
+ }
1285
+ </aside>
1286
+ </div>
1287
+ }
1288
+ </div>
1289
+ `, styles: [".dz-shell{display:flex;flex-direction:column;height:100%;min-height:480px;font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e);background:var(--mform-bg, #f6f5fa);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.dz-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-tools{display:flex;align-items:center;gap:6px}.dz-toolbar button{font:inherit;font-size:13px;padding:6px 10px;border:1px solid transparent;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-toolbar button:hover:not(:disabled){background:var(--mform-bg, #f6f5fa)}.dz-toolbar button:disabled{opacity:.35;cursor:default}.dz-toolbar .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-sep{width:1px;height:18px;background:var(--mform-border, #d9d5e6)}.dz-diag-badge{color:#b45309;font-weight:600}.dz-diagnostics{max-height:140px;overflow-y:auto;padding:8px 14px;background:#fdf5ef;border-bottom:1px solid var(--mform-border, #d9d5e6);font-size:12px}.dz-diag{padding:2px 0;color:#9a3412}.dz-diag.is-warning{color:#92400e}.dz-diag code{font-weight:600;margin-right:6px}.dz-diag em{color:var(--mform-muted, #6b6580);margin-left:6px}.dz-dialog{padding:14px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-dialog h4{margin:0 0 8px;font-size:13px}.dz-dialog textarea{width:100%;font-family:ui-monospace,Consolas,monospace;font-size:12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;box-sizing:border-box;resize:vertical}.dz-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.dz-dialog-actions button{font:inherit;font-size:13px;padding:7px 14px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:none;cursor:pointer}.dz-dialog-actions .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-color:transparent}.dz-import-error{color:var(--mform-danger, #c62842);font-size:12px}.dz-body{display:grid;grid-template-columns:240px 1fr 280px;flex:1;min-height:0}.dz-palette{border-right:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);padding:12px;overflow-y:auto}.dz-palette h4{margin:4px 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580)}.dz-group{margin-bottom:10px}.dz-group-label{font-size:11px;color:var(--mform-muted, #6b6580)}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin-top:4px}.dz-palette-item{display:flex;align-items:center;gap:8px;font:inherit;font-size:13px;text-align:left;padding:6px 8px;border:none;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-palette-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px;font-size:11px}.dz-tree{display:flex;flex-direction:column;gap:1px}.dz-tree-item{font:inherit;font-size:12px;text-align:left;padding:4px 8px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dz-tree-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-tree-item.is-selected{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-canvas{padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:10px}.dz-canvas-empty,.dz-empty{color:var(--mform-muted, #6b6580);font-size:13px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:18px;text-align:center}.dz-node{position:relative;border:1px solid transparent;border-radius:var(--mform-radius, 10px);padding:20px 10px 10px;cursor:pointer;background:var(--mform-surface, #fff)}.dz-node:hover{border-color:var(--mform-border, #d9d5e6)}.dz-node.is-selected{border-color:var(--mform-primary, #6d4bd0);box-shadow:0 0 0 1px var(--mform-primary, #6d4bd0)}.dz-chip{position:absolute;top:2px;left:10px;font-size:10px;color:var(--mform-muted, #6b6580);pointer-events:none}.dz-node.is-selected>.dz-chip{color:var(--mform-primary, #6d4bd0);font-weight:600}.dz-children{display:flex;flex-direction:column;gap:8px}.dz-columns{display:grid;gap:10px}.dz-column{display:flex;flex-direction:column;gap:8px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:8px;padding:8px;min-height:40px}.dz-tabbar{display:flex;gap:4px;margin-bottom:8px;border-bottom:2px solid var(--mform-border, #d9d5e6)}.dz-tab{font:inherit;font-size:12px;padding:5px 12px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.dz-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.dz-leaf{pointer-events:none}.dz-props{border-left:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);overflow-y:auto}.dz-props-empty{margin:12px}.dz-inspector{padding:12px;display:flex;flex-direction:column;gap:10px}.dz-inspector h3{margin:0;font-size:13px}.dz-inspector h3 code{color:var(--mform-muted, #6b6580);font-weight:400}.dz-prop{display:flex;flex-direction:column;gap:4px;font-size:12px}.dz-prop span{color:var(--mform-muted, #6b6580)}.dz-prop input,.dz-prop select,.dz-prop textarea{font:inherit;font-size:13px;padding:7px 9px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:#fff;width:100%;box-sizing:border-box}.dz-check{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer}.dz-check input{accent-color:var(--mform-primary, #6d4bd0)}.dz-section{border-top:1px solid var(--mform-border, #d9d5e6);padding-top:8px}.dz-section summary{font-size:12px;font-weight:600;cursor:pointer;margin-bottom:8px}.dz-section .dz-prop{margin-bottom:8px}.dz-option-row{display:flex;gap:6px;margin-bottom:6px}.dz-option-row input{flex:1;min-width:0;font-size:12px;padding:6px 8px;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px}.dz-option-row button{border:none;background:none;cursor:pointer;color:var(--mform-muted, #6b6580)}.dz-add{font:inherit;font-size:12px;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);background:none;border-radius:8px;padding:6px 10px;cursor:pointer}.dz-preview{padding:20px;overflow-y:auto}.dz-preview-data{margin-top:16px;font-size:12px}.dz-preview-data pre{background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;overflow-x:auto}\n"] }]
1290
+ }], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], extraCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraCatalog", required: false }] }], schemaChanged: [{ type: i0.Output, args: ["schemaChanged"] }] } });
1291
+
1292
+ /*
1293
+ * Designer entry point (`@mosaicoo/form-angular/designer`).
1294
+ *
1295
+ * Authoring lives behind this secondary entry so applications that only
1296
+ * RENDER forms never load designer code — required for federated hosts and
1297
+ * lazy loading.
1298
+ */
1299
+
1300
+ /**
1301
+ * Generated bundle index. Do not edit.
1302
+ */
1303
+
1304
+ export { DEFAULT_CATALOG, DesignerNodeComponent, FormDesignerComponent, InspectorComponent };
1305
+ //# sourceMappingURL=mosaicoo-form-angular-designer.mjs.map