@mosaicoo/form-angular 0.7.0 → 0.9.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.
@@ -5,27 +5,42 @@ import { collectInputs, generateKey, validateSchema, createFormEngine, getNodeAt
5
5
  import { importForm, createLegacyImporter } from '@mosaicoo/form-core/interop';
6
6
  import { FormNodeComponent, FormRendererComponent } from '@mosaicoo/form-angular';
7
7
 
8
+ function dropId(target) {
9
+ return target.column === undefined
10
+ ? target.path.join('.')
11
+ : `${target.path.join('.')}@${target.column}`;
12
+ }
13
+
8
14
  /**
9
15
  * Canvas node: draws the selection frame and structure chrome, delegating
10
16
  * leaf rendering to the RUNTIME `<mform-node>` so what you see is what the
11
17
  * renderer will produce. Canvas controls are non-interactive — testing
12
18
  * happens in Preview mode.
19
+ *
20
+ * Drop zones sit between children (and inside each column), so fields can be
21
+ * dragged INTO containers, not just reordered at the root.
13
22
  */
14
23
  class DesignerNodeComponent {
15
24
  node = input$1.required(...(ngDevMode ? [{ debugName: "node" }] : /* istanbul ignore next */ []));
16
25
  path = input$1.required(...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
17
26
  engine = input$1.required(...(ngDevMode ? [{ debugName: "engine" }] : /* istanbul ignore next */ []));
18
27
  selected = input$1(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
28
+ /** True while something is being dragged (reveals the drop zones). */
29
+ dragging = input$1(false, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
30
+ /** Id of the drop zone currently hovered. */
31
+ activeDrop = input$1(null, ...(ngDevMode ? [{ debugName: "activeDrop" }] : /* istanbul ignore next */ []));
19
32
  selectNode = output();
33
+ nodeDragStart = output();
34
+ dragOverTarget = output();
35
+ dropOnTarget = output();
36
+ dragFinished = output();
20
37
  activeTab = signal(0, ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
21
38
  container() {
22
39
  return this.node();
23
40
  }
24
41
  chip() {
25
42
  const node = this.node();
26
- const label = node.kind === 'container' || node.kind === 'static'
27
- ? node.label ?? node.key
28
- : node.label ?? node.key;
43
+ const label = node.label ?? node.key;
29
44
  return `${label} · ${node.type}`;
30
45
  }
31
46
  isSelected() {
@@ -48,8 +63,28 @@ class DesignerNodeComponent {
48
63
  event.stopPropagation();
49
64
  this.activeTab.set(index);
50
65
  }
66
+ // -- drag and drop --------------------------------------------------------
67
+ isOver(path, column) {
68
+ return this.activeDrop() === dropId({ path, column });
69
+ }
70
+ onDragStart(path, event) {
71
+ event.stopPropagation();
72
+ this.nodeDragStart.emit(path);
73
+ }
74
+ onDragOver(path, column, event) {
75
+ if (!this.dragging())
76
+ return;
77
+ event.preventDefault();
78
+ event.stopPropagation();
79
+ this.dragOverTarget.emit({ path, column });
80
+ }
81
+ onDrop(path, column, event) {
82
+ event.preventDefault();
83
+ event.stopPropagation();
84
+ this.dropOnTarget.emit({ path, column });
85
+ }
51
86
  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: `
87
+ 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 }, dragging: { classPropertyName: "dragging", publicName: "dragging", isSignal: true, isRequired: false, transformFunction: null }, activeDrop: { classPropertyName: "activeDrop", publicName: "activeDrop", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectNode: "selectNode", nodeDragStart: "nodeDragStart", dragOverTarget: "dragOverTarget", dropOnTarget: "dropOnTarget", dragFinished: "dragFinished" }, ngImport: i0, template: `
53
88
  @let n = node();
54
89
  <div
55
90
  class="dz-node"
@@ -85,24 +120,51 @@ class DesignerNodeComponent {
85
120
  [path]="childPath(i)"
86
121
  [engine]="engine()"
87
122
  [selected]="selected()"
123
+ [dragging]="dragging()"
124
+ [activeDrop]="activeDrop()"
88
125
  (selectNode)="selectNode.emit($event)"
126
+ (nodeDragStart)="nodeDragStart.emit($event)"
127
+ (dragOverTarget)="dragOverTarget.emit($event)"
128
+ (dropOnTarget)="dropOnTarget.emit($event)"
129
+ (dragFinished)="dragFinished.emit()"
89
130
  />
90
131
  </div>
91
132
  }
92
133
  }
93
134
  } @else if (n.type === 'columns' && container().columns?.length) {
94
135
  <div class="dz-columns" [style.grid-template-columns]="columnsTemplate()">
95
- @for (column of container().columns; track $index) {
136
+ @for (column of container().columns; track $index; let c = $index) {
96
137
  <div class="dz-column">
97
138
  @for (childIndex of column.children; track childIndex) {
139
+ <div
140
+ class="dz-dropzone"
141
+ [class.is-over]="isOver(childPath(childIndex), c)"
142
+ (dragover)="onDragOver(childPath(childIndex), c, $event)"
143
+ (drop)="onDrop(childPath(childIndex), c, $event)"
144
+ ></div>
98
145
  <mform-designer-node
99
146
  [node]="container().children[childIndex]!"
100
147
  [path]="childPath(childIndex)"
101
148
  [engine]="engine()"
102
149
  [selected]="selected()"
150
+ [dragging]="dragging()"
151
+ [activeDrop]="activeDrop()"
152
+ draggable="true"
153
+ (dragstart)="onDragStart(childPath(childIndex), $event)"
154
+ (dragend)="dragFinished.emit()"
103
155
  (selectNode)="selectNode.emit($event)"
156
+ (nodeDragStart)="nodeDragStart.emit($event)"
157
+ (dragOverTarget)="dragOverTarget.emit($event)"
158
+ (dropOnTarget)="dropOnTarget.emit($event)"
159
+ (dragFinished)="dragFinished.emit()"
104
160
  />
105
161
  }
162
+ <div
163
+ class="dz-dropzone is-last"
164
+ [class.is-over]="isOver(childPath(container().children.length), c)"
165
+ (dragover)="onDragOver(childPath(container().children.length), c, $event)"
166
+ (drop)="onDrop(childPath(container().children.length), c, $event)"
167
+ ></div>
106
168
  @if (column.children.length === 0) {
107
169
  <div class="dz-empty">empty column</div>
108
170
  }
@@ -112,16 +174,37 @@ class DesignerNodeComponent {
112
174
  } @else {
113
175
  <div class="dz-children">
114
176
  @for (child of container().children; track child.key; let i = $index) {
177
+ <div
178
+ class="dz-dropzone"
179
+ [class.is-over]="isOver(childPath(i))"
180
+ (dragover)="onDragOver(childPath(i), undefined, $event)"
181
+ (drop)="onDrop(childPath(i), undefined, $event)"
182
+ ></div>
115
183
  <mform-designer-node
116
184
  [node]="child"
117
185
  [path]="childPath(i)"
118
186
  [engine]="engine()"
119
187
  [selected]="selected()"
188
+ [dragging]="dragging()"
189
+ [activeDrop]="activeDrop()"
190
+ draggable="true"
191
+ (dragstart)="onDragStart(childPath(i), $event)"
192
+ (dragend)="dragFinished.emit()"
120
193
  (selectNode)="selectNode.emit($event)"
194
+ (nodeDragStart)="nodeDragStart.emit($event)"
195
+ (dragOverTarget)="dragOverTarget.emit($event)"
196
+ (dropOnTarget)="dropOnTarget.emit($event)"
197
+ (dragFinished)="dragFinished.emit()"
121
198
  />
122
199
  } @empty {
123
- <div class="dz-empty">empty — select and add fields</div>
200
+ <div class="dz-empty">empty — drop fields here</div>
124
201
  }
202
+ <div
203
+ class="dz-dropzone is-last"
204
+ [class.is-over]="isOver(childPath(container().children.length))"
205
+ (dragover)="onDragOver(childPath(container().children.length), undefined, $event)"
206
+ (drop)="onDrop(childPath(container().children.length), undefined, $event)"
207
+ ></div>
125
208
  </div>
126
209
  }
127
210
  } @else {
@@ -130,7 +213,7 @@ class DesignerNodeComponent {
130
213
  </div>
131
214
  }
132
215
  </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 });
216
+ `, isInline: true, dependencies: [{ kind: "component", type: DesignerNodeComponent, selector: "mform-designer-node", inputs: ["node", "path", "engine", "selected", "dragging", "activeDrop"], outputs: ["selectNode", "nodeDragStart", "dragOverTarget", "dropOnTarget", "dragFinished"] }, { 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
217
  }
135
218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: DesignerNodeComponent, decorators: [{
136
219
  type: Component,
@@ -175,24 +258,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
175
258
  [path]="childPath(i)"
176
259
  [engine]="engine()"
177
260
  [selected]="selected()"
261
+ [dragging]="dragging()"
262
+ [activeDrop]="activeDrop()"
178
263
  (selectNode)="selectNode.emit($event)"
264
+ (nodeDragStart)="nodeDragStart.emit($event)"
265
+ (dragOverTarget)="dragOverTarget.emit($event)"
266
+ (dropOnTarget)="dropOnTarget.emit($event)"
267
+ (dragFinished)="dragFinished.emit()"
179
268
  />
180
269
  </div>
181
270
  }
182
271
  }
183
272
  } @else if (n.type === 'columns' && container().columns?.length) {
184
273
  <div class="dz-columns" [style.grid-template-columns]="columnsTemplate()">
185
- @for (column of container().columns; track $index) {
274
+ @for (column of container().columns; track $index; let c = $index) {
186
275
  <div class="dz-column">
187
276
  @for (childIndex of column.children; track childIndex) {
277
+ <div
278
+ class="dz-dropzone"
279
+ [class.is-over]="isOver(childPath(childIndex), c)"
280
+ (dragover)="onDragOver(childPath(childIndex), c, $event)"
281
+ (drop)="onDrop(childPath(childIndex), c, $event)"
282
+ ></div>
188
283
  <mform-designer-node
189
284
  [node]="container().children[childIndex]!"
190
285
  [path]="childPath(childIndex)"
191
286
  [engine]="engine()"
192
287
  [selected]="selected()"
288
+ [dragging]="dragging()"
289
+ [activeDrop]="activeDrop()"
290
+ draggable="true"
291
+ (dragstart)="onDragStart(childPath(childIndex), $event)"
292
+ (dragend)="dragFinished.emit()"
193
293
  (selectNode)="selectNode.emit($event)"
294
+ (nodeDragStart)="nodeDragStart.emit($event)"
295
+ (dragOverTarget)="dragOverTarget.emit($event)"
296
+ (dropOnTarget)="dropOnTarget.emit($event)"
297
+ (dragFinished)="dragFinished.emit()"
194
298
  />
195
299
  }
300
+ <div
301
+ class="dz-dropzone is-last"
302
+ [class.is-over]="isOver(childPath(container().children.length), c)"
303
+ (dragover)="onDragOver(childPath(container().children.length), c, $event)"
304
+ (drop)="onDrop(childPath(container().children.length), c, $event)"
305
+ ></div>
196
306
  @if (column.children.length === 0) {
197
307
  <div class="dz-empty">empty column</div>
198
308
  }
@@ -202,16 +312,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
202
312
  } @else {
203
313
  <div class="dz-children">
204
314
  @for (child of container().children; track child.key; let i = $index) {
315
+ <div
316
+ class="dz-dropzone"
317
+ [class.is-over]="isOver(childPath(i))"
318
+ (dragover)="onDragOver(childPath(i), undefined, $event)"
319
+ (drop)="onDrop(childPath(i), undefined, $event)"
320
+ ></div>
205
321
  <mform-designer-node
206
322
  [node]="child"
207
323
  [path]="childPath(i)"
208
324
  [engine]="engine()"
209
325
  [selected]="selected()"
326
+ [dragging]="dragging()"
327
+ [activeDrop]="activeDrop()"
328
+ draggable="true"
329
+ (dragstart)="onDragStart(childPath(i), $event)"
330
+ (dragend)="dragFinished.emit()"
210
331
  (selectNode)="selectNode.emit($event)"
332
+ (nodeDragStart)="nodeDragStart.emit($event)"
333
+ (dragOverTarget)="dragOverTarget.emit($event)"
334
+ (dropOnTarget)="dropOnTarget.emit($event)"
335
+ (dragFinished)="dragFinished.emit()"
211
336
  />
212
337
  } @empty {
213
- <div class="dz-empty">empty — select and add fields</div>
338
+ <div class="dz-empty">empty — drop fields here</div>
214
339
  }
340
+ <div
341
+ class="dz-dropzone is-last"
342
+ [class.is-over]="isOver(childPath(container().children.length))"
343
+ (dragover)="onDragOver(childPath(container().children.length), undefined, $event)"
344
+ (drop)="onDrop(childPath(container().children.length), undefined, $event)"
345
+ ></div>
215
346
  </div>
216
347
  }
217
348
  } @else {
@@ -222,7 +353,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
222
353
  </div>
223
354
  `,
224
355
  }]
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"] }] } });
356
+ }], 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 }] }], dragging: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragging", required: false }] }], activeDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeDrop", required: false }] }], selectNode: [{ type: i0.Output, args: ["selectNode"] }], nodeDragStart: [{ type: i0.Output, args: ["nodeDragStart"] }], dragOverTarget: [{ type: i0.Output, args: ["dragOverTarget"] }], dropOnTarget: [{ type: i0.Output, args: ["dropOnTarget"] }], dragFinished: [{ type: i0.Output, args: ["dragFinished"] }] } });
226
357
 
227
358
  /**
228
359
  * Property panel of the selected node. Emits immutable patches — the shell
@@ -1037,6 +1168,43 @@ function saveBlocks(storage, blocks) {
1037
1168
  }
1038
1169
  }
1039
1170
 
1171
+ const DESIGNER_LABELS_EN = {
1172
+ undo: 'Undo (Ctrl+Z)',
1173
+ redo: 'Redo (Ctrl+Y)',
1174
+ moveUp: 'Move up (Alt+↑)',
1175
+ moveDown: 'Move down (Alt+↓)',
1176
+ nest: 'Nest into the container above (Alt+→)',
1177
+ unnest: 'Move out of the container (Alt+←)',
1178
+ duplicate: 'Duplicate (Ctrl+D)',
1179
+ delete: 'Delete (Del)',
1180
+ saveBlock: 'Save as reusable block',
1181
+ viewport: 'Preview width',
1182
+ workspace: 'Workspace',
1183
+ panels: 'Panels',
1184
+ presets: 'Presets',
1185
+ resetLayout: 'Reset layout',
1186
+ moveLeft: 'Move left',
1187
+ moveRight: 'Move right',
1188
+ hidePanel: 'Hide panel',
1189
+ import: 'Import',
1190
+ export: 'Export',
1191
+ preview: 'Preview',
1192
+ design: 'Design',
1193
+ cancel: 'Cancel',
1194
+ close: 'Close',
1195
+ importTitle: 'Import definition (native schema or supported formats)',
1196
+ importPlaceholder: '{"version":1,"fields":[…]} or a legacy definition',
1197
+ importError: 'Could not import: ',
1198
+ exportTitle: 'Schema JSON',
1199
+ canvasEmpty: 'Click a field in the palette — or drag it here — to start building.',
1200
+ selectField: 'Select a field to edit its properties.',
1201
+ testData: 'Test data',
1202
+ panelFields: 'Fields',
1203
+ panelStructure: 'Structure',
1204
+ panelProperties: 'Properties',
1205
+ noFields: 'no fields yet',
1206
+ };
1207
+
1040
1208
  /**
1041
1209
  * Designer workspace layout.
1042
1210
  *
@@ -1147,6 +1315,10 @@ class FormDesignerComponent {
1147
1315
  source = input$1(null, ...(ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []));
1148
1316
  /** Extra palette groups contributed by the host. */
1149
1317
  extraCatalog = input$1([], ...(ngDevMode ? [{ debugName: "extraCatalog" }] : /* istanbul ignore next */ []));
1318
+ /** Replaces the built-in palette (e.g. a translated DEFAULT_CATALOG). */
1319
+ catalogOverride = input$1(null, { ...(ngDevMode ? { debugName: "catalogOverride" } : /* istanbul ignore next */ {}), alias: 'catalog' });
1320
+ /** Designer texts; partial overrides fall back to English. */
1321
+ labels = input$1(DESIGNER_LABELS_EN, { ...(ngDevMode ? { debugName: "labels" } : /* istanbul ignore next */ {}), transform: (value) => ({ ...DESIGNER_LABELS_EN, ...value }) });
1150
1322
  /** Emitted after every committed edit (undo/redo included). */
1151
1323
  schemaChanged = output();
1152
1324
  draft = signal(EMPTY_SCHEMA, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
@@ -1155,6 +1327,16 @@ class FormDesignerComponent {
1155
1327
  selected = signal(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
1156
1328
  preview = signal(false, ...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
1157
1329
  previewData = signal({}, ...(ngDevMode ? [{ debugName: "previewData" }] : /* istanbul ignore next */ []));
1330
+ /** Preview viewport: the frame drives the form's container queries. */
1331
+ viewport = signal('desktop', ...(ngDevMode ? [{ debugName: "viewport" }] : /* istanbul ignore next */ []));
1332
+ viewports = [
1333
+ { id: 'desktop', label: 'Desktop', icon: '🖥', width: null },
1334
+ { id: 'tablet', label: 'Tablet', icon: '▭', width: 768 },
1335
+ { id: 'mobile', label: 'Mobile', icon: '▯', width: 375 },
1336
+ ];
1337
+ viewportWidth() {
1338
+ return this.viewports.find((option) => option.id === this.viewport())?.width ?? null;
1339
+ }
1158
1340
  dialog = signal(null, ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
1159
1341
  importError = signal('', ...(ngDevMode ? [{ debugName: "importError" }] : /* istanbul ignore next */ []));
1160
1342
  showDiagnostics = signal(false, ...(ngDevMode ? [{ debugName: "showDiagnostics" }] : /* istanbul ignore next */ []));
@@ -1178,7 +1360,12 @@ class FormDesignerComponent {
1178
1360
  }
1179
1361
  })();
1180
1362
  panelLabel(panel) {
1181
- return PANEL_LABELS[panel];
1363
+ const labels = this.labels();
1364
+ if (panel === 'palette')
1365
+ return labels.panelFields;
1366
+ if (panel === 'structure')
1367
+ return labels.panelStructure;
1368
+ return labels.panelProperties;
1182
1369
  }
1183
1370
  panelSide(panel) {
1184
1371
  return this.layout()[panel];
@@ -1210,6 +1397,34 @@ class FormDesignerComponent {
1210
1397
  saveLayout(this.storage, this.layout());
1211
1398
  this.workspaceMenu.set(false);
1212
1399
  }
1400
+ /** Live drag state of a dock splitter. */
1401
+ resizing = null;
1402
+ startResize(side, event) {
1403
+ event.preventDefault();
1404
+ const layout = this.layout();
1405
+ this.resizing = {
1406
+ side,
1407
+ startX: event.clientX,
1408
+ startWidth: side === 'left' ? layout.leftWidth : layout.rightWidth,
1409
+ };
1410
+ event.target.setPointerCapture?.(event.pointerId);
1411
+ }
1412
+ onResizeMove(event) {
1413
+ const resizing = this.resizing;
1414
+ if (!resizing)
1415
+ return;
1416
+ // dragging right widens a left dock and narrows a right one
1417
+ const delta = event.clientX - resizing.startX;
1418
+ const raw = resizing.startWidth + (resizing.side === 'left' ? delta : -delta);
1419
+ const width = Math.max(160, Math.min(520, Math.round(raw)));
1420
+ this.layout.update((current) => resizing.side === 'left' ? { ...current, leftWidth: width } : { ...current, rightWidth: width });
1421
+ }
1422
+ endResize() {
1423
+ if (!this.resizing)
1424
+ return;
1425
+ this.resizing = null;
1426
+ saveLayout(this.storage, this.layout());
1427
+ }
1213
1428
  isGroupOpen(label) {
1214
1429
  return this.openGroups().has(label);
1215
1430
  }
@@ -1230,7 +1445,7 @@ class FormDesignerComponent {
1230
1445
  effectiveBlocks = computed(() => this.blocks() ?? this.localBlocks(), ...(ngDevMode ? [{ debugName: "effectiveBlocks" }] : /* istanbul ignore next */ []));
1231
1446
  /** Palette = built-ins + host extras + saved blocks as a group. */
1232
1447
  catalog = computed(() => {
1233
- const groups = [...DEFAULT_CATALOG, ...this.extraCatalog()];
1448
+ const groups = [...(this.catalogOverride() ?? DEFAULT_CATALOG), ...this.extraCatalog()];
1234
1449
  const blocks = this.effectiveBlocks();
1235
1450
  if (blocks.length > 0) {
1236
1451
  groups.push({
@@ -1385,6 +1600,49 @@ class FormDesignerComponent {
1385
1600
  return;
1386
1601
  this.commit(updateNode(this.draft(), selected, event.patch));
1387
1602
  }
1603
+ /** Whether the selection can be nested into the container above it. */
1604
+ canNest() {
1605
+ const selected = this.selected();
1606
+ if (!selected)
1607
+ return false;
1608
+ const { parent, index } = parentOf(selected);
1609
+ if (index === 0)
1610
+ return false;
1611
+ return getNodeAt(this.draft(), [...parent, index - 1])?.kind === 'container';
1612
+ }
1613
+ canUnnest() {
1614
+ return (this.selected()?.length ?? 0) > 1;
1615
+ }
1616
+ /**
1617
+ * Nests the selection into the container right above it (Alt+→) — the
1618
+ * keyboard equivalent of dragging a field into a panel.
1619
+ */
1620
+ nestSelected() {
1621
+ const selected = this.selected();
1622
+ if (!selected)
1623
+ return;
1624
+ const { parent, index } = parentOf(selected);
1625
+ if (index === 0)
1626
+ return;
1627
+ const previous = getNodeAt(this.draft(), [...parent, index - 1]);
1628
+ if (!previous || previous.kind !== 'container')
1629
+ return;
1630
+ // after the removal the container keeps its position (it sits before)
1631
+ const target = [...parent, index - 1, previous.children.length];
1632
+ this.commit(moveNode(this.draft(), selected, target));
1633
+ this.select(target);
1634
+ }
1635
+ /** Moves the selection out of its container, right after it (Alt+←). */
1636
+ unnestSelected() {
1637
+ const selected = this.selected();
1638
+ if (!selected || selected.length < 2)
1639
+ return;
1640
+ const { parent } = parentOf(selected);
1641
+ const { parent: grandparent, index: parentIndex } = parentOf(parent);
1642
+ const target = [...grandparent, parentIndex + 1];
1643
+ this.commit(moveNode(this.draft(), selected, target));
1644
+ this.select(target);
1645
+ }
1388
1646
  // -- reusable blocks ------------------------------------------------------
1389
1647
  /** Saves the selected node (with its subtree) as a reusable block. */
1390
1648
  saveAsBlock() {
@@ -1422,7 +1680,7 @@ class FormDesignerComponent {
1422
1680
  // -- drag and drop --------------------------------------------------------
1423
1681
  /** What is being dragged: a palette entry or an existing node. */
1424
1682
  dragging = signal(null, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
1425
- /** Drop target highlighted while hovering. */
1683
+ /** Drop zone highlighted while hovering (see `dropId`). */
1426
1684
  dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : /* istanbul ignore next */ []));
1427
1685
  startEntryDrag(entry, event) {
1428
1686
  this.dragging.set({ kind: 'entry', entry });
@@ -1432,8 +1690,8 @@ class FormDesignerComponent {
1432
1690
  }
1433
1691
  startNodeDrag(path, event) {
1434
1692
  this.dragging.set({ kind: 'node', path });
1435
- event.dataTransfer?.setData('text/plain', path.join('.'));
1436
- if (event.dataTransfer)
1693
+ event?.dataTransfer?.setData('text/plain', path.join('.'));
1694
+ if (event?.dataTransfer)
1437
1695
  event.dataTransfer.effectAllowed = 'move';
1438
1696
  }
1439
1697
  endDrag() {
@@ -1443,33 +1701,34 @@ class FormDesignerComponent {
1443
1701
  allowDrop(target, event) {
1444
1702
  if (!this.dragging())
1445
1703
  return;
1446
- event.preventDefault();
1447
- event.stopPropagation();
1448
- this.dropTarget.set(target.join('.'));
1704
+ event?.preventDefault();
1705
+ event?.stopPropagation();
1706
+ this.dropTarget.set(dropId(target));
1449
1707
  }
1450
- /** Drops onto `target`, the position the node should occupy. */
1708
+ /** Drops onto `target` the position (and column) the node should occupy. */
1451
1709
  drop(target, event) {
1452
- event.preventDefault();
1453
- event.stopPropagation();
1710
+ event?.preventDefault();
1711
+ event?.stopPropagation();
1454
1712
  const dragging = this.dragging();
1455
1713
  this.endDrag();
1456
1714
  if (!dragging)
1457
1715
  return;
1716
+ const options = target.column === undefined ? undefined : { column: target.column };
1458
1717
  if (dragging.kind === 'entry') {
1459
1718
  const node = dragging.entry.create(this.draft());
1460
- this.commit(insertNode(this.draft(), target, node));
1461
- this.select(target);
1719
+ this.commit(insertNode(this.draft(), target.path, node, options));
1720
+ this.select(target.path);
1462
1721
  return;
1463
1722
  }
1464
1723
  const from = dragging.path;
1465
- if (isSameOrInside(from, target))
1724
+ if (isSameOrInside(from, target.path))
1466
1725
  return; // never drop a node into itself
1467
- const adjusted = adjustAfterRemoval(from, target);
1468
- this.commit(moveNode(this.draft(), from, adjusted));
1726
+ const adjusted = adjustAfterRemoval(from, target.path);
1727
+ this.commit(moveNode(this.draft(), from, adjusted, options));
1469
1728
  this.select(adjusted);
1470
1729
  }
1471
- isDropTarget(path) {
1472
- return this.dropTarget() === path.join('.');
1730
+ isDropTarget(target) {
1731
+ return this.dropTarget() === dropId(target);
1473
1732
  }
1474
1733
  isDragging() {
1475
1734
  return this.dragging() !== null;
@@ -1495,7 +1754,7 @@ class FormDesignerComponent {
1495
1754
  this.importError.set('');
1496
1755
  }
1497
1756
  catch (error) {
1498
- this.importError.set('Could not import: ' + String(error));
1757
+ this.importError.set(this.labels().importError + String(error));
1499
1758
  }
1500
1759
  }
1501
1760
  // -- keyboard -------------------------------------------------------------
@@ -1520,20 +1779,44 @@ class FormDesignerComponent {
1520
1779
  event.preventDefault();
1521
1780
  this.removeSelected();
1522
1781
  }
1782
+ else if (key === 'escape') {
1783
+ event.preventDefault();
1784
+ this.workspaceMenu.set(false);
1785
+ this.dialog.set(null);
1786
+ this.select(null);
1787
+ }
1788
+ else if (event.altKey && key === 'arrowup') {
1789
+ event.preventDefault();
1790
+ this.moveSelected(-1);
1791
+ }
1792
+ else if (event.altKey && key === 'arrowdown') {
1793
+ event.preventDefault();
1794
+ this.moveSelected(1);
1795
+ }
1796
+ else if (event.altKey && key === 'arrowright') {
1797
+ event.preventDefault();
1798
+ this.nestSelected();
1799
+ }
1800
+ else if (event.altKey && key === 'arrowleft') {
1801
+ event.preventDefault();
1802
+ this.unnestSelected();
1803
+ }
1523
1804
  }
1524
1805
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1525
- 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 }, blocks: { classPropertyName: "blocks", publicName: "blocks", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChanged: "schemaChanged", blocksChanged: "blocksChanged" }, ngImport: i0, template: `
1806
+ 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 }, catalogOverride: { classPropertyName: "catalogOverride", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, blocks: { classPropertyName: "blocks", publicName: "blocks", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChanged: "schemaChanged", blocksChanged: "blocksChanged" }, ngImport: i0, template: `
1526
1807
  <div class="dz-shell" (keydown)="onKeydown($event)">
1527
1808
  <header class="dz-toolbar">
1528
1809
  <div class="dz-tools">
1529
- <button type="button" (click)="undo()" [disabled]="past().length === 0" title="Undo (Ctrl+Z)">↶</button>
1530
- <button type="button" (click)="redo()" [disabled]="future().length === 0" title="Redo (Ctrl+Y)">↷</button>
1810
+ <button type="button" (click)="undo()" [disabled]="past().length === 0" [title]="labels().undo">↶</button>
1811
+ <button type="button" (click)="redo()" [disabled]="future().length === 0" [title]="labels().redo">↷</button>
1531
1812
  <span class="dz-sep"></span>
1532
- <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" title="Move up">↑</button>
1533
- <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1534
- <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1535
- <button type="button" (click)="removeSelected()" [disabled]="!selected()" title="Delete (Del)">🗑</button>
1536
- <button type="button" (click)="saveAsBlock()" [disabled]="!selected()" title="Save as reusable block">⊞</button>
1813
+ <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" [title]="labels().moveUp">↑</button>
1814
+ <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" [title]="labels().moveDown">↓</button>
1815
+ <button type="button" (click)="unnestSelected()" [disabled]="!canUnnest()" [title]="labels().unnest">⇤</button>
1816
+ <button type="button" (click)="nestSelected()" [disabled]="!canNest()" [title]="labels().nest">⇥</button>
1817
+ <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" [title]="labels().duplicate">⧉</button>
1818
+ <button type="button" (click)="removeSelected()" [disabled]="!selected()" [title]="labels().delete">🗑</button>
1819
+ <button type="button" (click)="saveAsBlock()" [disabled]="!selected()" [title]="labels().saveBlock">⊞</button>
1537
1820
  </div>
1538
1821
  <div class="dz-tools">
1539
1822
  @if (diagnostics().length > 0) {
@@ -1542,12 +1825,12 @@ class FormDesignerComponent {
1542
1825
  </button>
1543
1826
  }
1544
1827
  <div class="dz-menu">
1545
- <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" title="Workspace layout">
1546
- Workspace
1828
+ <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" [title]="labels().workspace">
1829
+ {{ labels().workspace }}
1547
1830
  </button>
1548
1831
  @if (workspaceMenu()) {
1549
1832
  <div class="dz-menu-panel" role="menu">
1550
- <span class="dz-menu-title">Panels</span>
1833
+ <span class="dz-menu-title">{{ labels().panels }}</span>
1551
1834
  @for (panel of panelIds; track panel) {
1552
1835
  <div class="dz-menu-row">
1553
1836
  <span>{{ panelLabel(panel) }}</span>
@@ -1562,20 +1845,20 @@ class FormDesignerComponent {
1562
1845
  </span>
1563
1846
  </div>
1564
1847
  }
1565
- <span class="dz-menu-title">Presets</span>
1848
+ <span class="dz-menu-title">{{ labels().presets }}</span>
1566
1849
  @for (preset of presets; track preset.id) {
1567
1850
  <button type="button" class="dz-menu-item" (click)="applyPreset(preset.id)">
1568
1851
  {{ preset.label }}
1569
1852
  </button>
1570
1853
  }
1571
- <button type="button" class="dz-menu-item" (click)="resetLayout()">Reset layout</button>
1854
+ <button type="button" class="dz-menu-item" (click)="resetLayout()">{{ labels().resetLayout }}</button>
1572
1855
  </div>
1573
1856
  }
1574
1857
  </div>
1575
- <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1576
- <button type="button" (click)="openExport()">Export</button>
1858
+ <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">{{ labels().import }}</button>
1859
+ <button type="button" (click)="openExport()">{{ labels().export }}</button>
1577
1860
  <button type="button" class="dz-primary" (click)="preview.set(!preview())">
1578
- {{ preview() ? 'Design' : 'Preview' }}
1861
+ {{ preview() ? labels().design : labels().preview }}
1579
1862
  </button>
1580
1863
  </div>
1581
1864
  </header>
@@ -1592,42 +1875,70 @@ class FormDesignerComponent {
1592
1875
 
1593
1876
  @if (dialog() === 'export') {
1594
1877
  <div class="dz-dialog">
1595
- <h4>Schema JSON</h4>
1878
+ <h4>{{ labels().exportTitle }}</h4>
1596
1879
  <textarea readonly rows="12">{{ exportedJson() }}</textarea>
1597
1880
  <div class="dz-dialog-actions">
1598
- <button type="button" (click)="dialog.set(null)">Close</button>
1881
+ <button type="button" (click)="dialog.set(null)">{{ labels().close }}</button>
1599
1882
  </div>
1600
1883
  </div>
1601
1884
  }
1602
1885
  @if (dialog() === 'import') {
1603
1886
  <div class="dz-dialog">
1604
- <h4>Import definition (native schema or supported formats)</h4>
1605
- <textarea rows="12" #importArea placeholder='{"version":1,"fields":[…]} or a legacy definition'></textarea>
1887
+ <h4>{{ labels().importTitle }}</h4>
1888
+ <textarea rows="12" #importArea [placeholder]="labels().importPlaceholder"></textarea>
1606
1889
  @if (importError()) {
1607
1890
  <p class="dz-import-error" role="alert">{{ importError() }}</p>
1608
1891
  }
1609
1892
  <div class="dz-dialog-actions">
1610
- <button type="button" (click)="dialog.set(null)">Cancel</button>
1611
- <button type="button" class="dz-primary" (click)="doImport(importArea.value)">Import</button>
1893
+ <button type="button" (click)="dialog.set(null)">{{ labels().cancel }}</button>
1894
+ <button type="button" class="dz-primary" (click)="doImport(importArea.value)">{{ labels().import }}</button>
1612
1895
  </div>
1613
1896
  </div>
1614
1897
  }
1615
1898
 
1616
1899
  @if (preview()) {
1617
1900
  <div class="dz-preview">
1618
- <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
1901
+ <div class="dz-viewports" role="group" [attr.aria-label]="labels().viewport">
1902
+ @for (option of viewports; track option.id) {
1903
+ <button
1904
+ type="button"
1905
+ [class.is-on]="viewport() === option.id"
1906
+ (click)="viewport.set(option.id)"
1907
+ [title]="option.width ? option.label + ' · ' + option.width + 'px' : option.label"
1908
+ >{{ option.icon }} {{ option.label }}</button>
1909
+ }
1910
+ </div>
1911
+ <div
1912
+ class="dz-preview-frame"
1913
+ [class.is-constrained]="viewportWidth() !== null"
1914
+ [style.max-width.px]="viewportWidth()"
1915
+ >
1916
+ <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
1917
+ </div>
1619
1918
  <details class="dz-preview-data">
1620
- <summary>Test data</summary>
1919
+ <summary>{{ labels().testData }}</summary>
1621
1920
  <pre>{{ previewData() | json }}</pre>
1622
1921
  </details>
1623
1922
  </div>
1624
1923
  } @else {
1625
- <div class="dz-body" [style.grid-template-columns]="bodyColumns()">
1924
+ <div
1925
+ class="dz-body"
1926
+ [style.grid-template-columns]="bodyColumns()"
1927
+ (pointermove)="onResizeMove($event)"
1928
+ (pointerup)="endResize()"
1929
+ (pointercancel)="endResize()"
1930
+ >
1626
1931
  @if (dockedPanels('left').length > 0) {
1627
1932
  <aside class="dz-dock dz-dock-left">
1628
1933
  @for (panel of dockedPanels('left'); track panel) {
1629
1934
  <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1630
1935
  }
1936
+ <div
1937
+ class="dz-splitter dz-splitter-left"
1938
+ role="separator"
1939
+ aria-orientation="vertical"
1940
+ (pointerdown)="startResize('left', $event)"
1941
+ ></div>
1631
1942
  </aside>
1632
1943
  }
1633
1944
 
@@ -1635,35 +1946,47 @@ class FormDesignerComponent {
1635
1946
  @for (node of draft().fields; track node.key; let i = $index) {
1636
1947
  <div
1637
1948
  class="dz-dropzone"
1638
- [class.is-over]="isDropTarget([i])"
1639
- (dragover)="allowDrop([i], $event)"
1640
- (drop)="drop([i], $event)"
1949
+ [class.is-over]="isDropTarget({ path: [i] })"
1950
+ (dragover)="allowDrop({ path: [i] }, $event)"
1951
+ (drop)="drop({ path: [i] }, $event)"
1641
1952
  ></div>
1642
1953
  <mform-designer-node
1643
1954
  [node]="node"
1644
1955
  [path]="[i]"
1645
1956
  [engine]="canvasEngine()"
1646
1957
  [selected]="selected()"
1647
- (selectNode)="select($event)"
1958
+ [dragging]="isDragging()"
1959
+ [activeDrop]="dropTarget()"
1648
1960
  draggable="true"
1649
1961
  (dragstart)="startNodeDrag([i], $event)"
1650
1962
  (dragend)="endDrag()"
1963
+ (selectNode)="select($event)"
1964
+ (nodeDragStart)="startNodeDrag($event)"
1965
+ (dragOverTarget)="allowDrop($event)"
1966
+ (dropOnTarget)="drop($event)"
1967
+ (dragFinished)="endDrag()"
1651
1968
  />
1652
1969
  } @empty {
1653
1970
  <div class="dz-canvas-empty">
1654
- Click a field in the palette — or drag it here — to start building.
1971
+ {{ labels().canvasEmpty }}
1655
1972
  </div>
1656
1973
  }
1657
1974
  <div
1658
1975
  class="dz-dropzone is-last"
1659
- [class.is-over]="isDropTarget([draft().fields.length])"
1660
- (dragover)="allowDrop([draft().fields.length], $event)"
1661
- (drop)="drop([draft().fields.length], $event)"
1976
+ [class.is-over]="isDropTarget({ path: [draft().fields.length] })"
1977
+ (dragover)="allowDrop({ path: [draft().fields.length] }, $event)"
1978
+ (drop)="drop({ path: [draft().fields.length] }, $event)"
1662
1979
  ></div>
1663
1980
  </main>
1664
1981
 
1665
1982
  @if (dockedPanels('right').length > 0) {
1666
1983
  <aside class="dz-dock dz-dock-right">
1984
+ <div
1985
+ class="dz-splitter dz-splitter-right"
1986
+ role="separator"
1987
+ aria-orientation="vertical"
1988
+ (pointerdown)="startResize('right', $event)"
1989
+ ></div>
1667
1990
  @for (panel of dockedPanels('right'); track panel) {
1668
1991
  <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1669
1992
  }
@@ -1681,10 +2004,10 @@ class FormDesignerComponent {
1681
2004
  <span class="dz-panel-actions">
1682
2005
  <button
1683
2006
  type="button"
1684
- [title]="panelSide(panel) === 'left' ? 'Move right' : 'Move left'"
2007
+ [title]="panelSide(panel) === 'left' ? labels().moveRight : labels().moveLeft"
1685
2008
  (click)="dock(panel, panelSide(panel) === 'left' ? 'right' : 'left')"
1686
2009
  >{{ panelSide(panel) === 'left' ? '⇥' : '⇤' }}</button>
1687
- <button type="button" title="Hide panel" (click)="dock(panel, 'hidden')">✕</button>
2010
+ <button type="button" [title]="labels().hidePanel" (click)="dock(panel, 'hidden')">✕</button>
1688
2011
  </span>
1689
2012
  </header>
1690
2013
  <div class="dz-panel-body">
@@ -1747,7 +2070,7 @@ class FormDesignerComponent {
1747
2070
  {{ item.label }}
1748
2071
  </button>
1749
2072
  } @empty {
1750
- <span class="dz-empty">no fields yet</span>
2073
+ <span class="dz-empty">{{ labels().noFields }}</span>
1751
2074
  }
1752
2075
  </div>
1753
2076
  }
@@ -1759,14 +2082,14 @@ class FormDesignerComponent {
1759
2082
  (nodePatch)="applyPatch($event)"
1760
2083
  />
1761
2084
  } @else {
1762
- <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
2085
+ <div class="dz-empty dz-props-empty">{{ labels().selectField }}</div>
1763
2086
  }
1764
2087
  }
1765
2088
  }
1766
2089
  </div>
1767
2090
  </section>
1768
2091
  </ng-template>
1769
- `, 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;flex:1;min-height:0}.dz-dock{display:flex;flex-direction:column;min-height:0;overflow-y:auto;background:var(--mform-surface, #fff)}.dz-dock-left{border-right:1px solid var(--mform-border, #d9d5e6)}.dz-dock-right{border-left:1px solid var(--mform-border, #d9d5e6)}.dz-panel{display:flex;flex-direction:column;border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-panel:last-child{border-bottom:none;flex:1}.dz-panel-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);position:sticky;top:0;z-index:1}.dz-panel-actions{display:flex;gap:2px;opacity:0;transition:opacity .12s}.dz-panel:hover .dz-panel-actions,.dz-panel:focus-within .dz-panel-actions{opacity:1}.dz-panel-actions button{border:none;background:none;color:inherit;cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px}.dz-panel-actions button:hover{background:var(--mform-surface, #fff)}.dz-panel-body{padding:10px}.dz-menu{position:relative}.dz-menu-panel{position:absolute;top:calc(100% + 6px);right:0;z-index:20;min-width:260px;padding:8px;background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);box-shadow:0 10px 30px #1f1b2e26}.dz-menu-title{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);padding:6px 6px 4px}.dz-menu-row{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;font-size:13px}.dz-seg{display:flex;gap:2px}.dz-seg button{border:1px solid var(--mform-border, #d9d5e6);background:none;color:var(--mform-muted, #6b6580);border-radius:6px;font-size:11px;padding:3px 8px;cursor:pointer}.dz-seg button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-menu-item{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-menu-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-group{margin-bottom:4px}.dz-group-toggle{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:12px;font-weight:600;text-align:left;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-group-toggle:hover{background:var(--mform-bg, #f6f5fa)}.dz-caret{font-size:10px;color:var(--mform-muted, #6b6580);width:10px}.dz-group-count{margin-left:auto;font-size:10px;font-weight:400;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);border-radius:999px;padding:1px 7px}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px 10px}.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-dropzone{height:4px;border-radius:999px;transition:height .1s,background .1s}.dz-canvas.is-dragging .dz-dropzone{height:14px;background:repeating-linear-gradient(90deg,var(--mform-border, #d9d5e6) 0 6px,transparent 6px 12px)}.dz-canvas.is-dragging .dz-dropzone.is-over{height:22px;background:var(--mform-primary, #6d4bd0)}.dz-palette-row{display:flex;align-items:center;gap:2px}.dz-palette-row .dz-palette-item{flex:1;cursor:grab}.dz-block-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:11px;padding:2px 4px;opacity:0}.dz-palette-row:hover .dz-block-remove{opacity:1}.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-empty{margin:4px 0}.dz-inspector{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: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2092
+ `, 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;flex:1;min-height:0}.dz-dock{display:flex;flex-direction:column;min-height:0;overflow-y:auto;background:var(--mform-surface, #fff)}.dz-dock-left{border-right:1px solid var(--mform-border, #d9d5e6);position:relative}.dz-dock-right{border-left:1px solid var(--mform-border, #d9d5e6);position:relative}.dz-splitter{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;touch-action:none;z-index:2}.dz-splitter-left{right:-3px}.dz-splitter-right{left:-3px}.dz-splitter:hover,.dz-splitter:active{background:var(--mform-primary, #6d4bd0);opacity:.35}.dz-panel{display:flex;flex-direction:column;border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-panel:last-child{border-bottom:none;flex:1}.dz-panel-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);position:sticky;top:0;z-index:1}.dz-panel-actions{display:flex;gap:2px;opacity:0;transition:opacity .12s}.dz-panel:hover .dz-panel-actions,.dz-panel:focus-within .dz-panel-actions{opacity:1}.dz-panel-actions button{border:none;background:none;color:inherit;cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px}.dz-panel-actions button:hover{background:var(--mform-surface, #fff)}.dz-panel-body{padding:10px}.dz-menu{position:relative}.dz-menu-panel{position:absolute;top:calc(100% + 6px);right:0;z-index:20;min-width:260px;padding:8px;background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);box-shadow:0 10px 30px #1f1b2e26}.dz-menu-title{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);padding:6px 6px 4px}.dz-menu-row{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;font-size:13px}.dz-seg{display:flex;gap:2px}.dz-seg button{border:1px solid var(--mform-border, #d9d5e6);background:none;color:var(--mform-muted, #6b6580);border-radius:6px;font-size:11px;padding:3px 8px;cursor:pointer}.dz-seg button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-menu-item{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-menu-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-group{margin-bottom:4px}.dz-group-toggle{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:12px;font-weight:600;text-align:left;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-group-toggle:hover{background:var(--mform-bg, #f6f5fa)}.dz-caret{font-size:10px;color:var(--mform-muted, #6b6580);width:10px}.dz-group-count{margin-left:auto;font-size:10px;font-weight:400;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);border-radius:999px;padding:1px 7px}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px 10px}.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-dropzone{height:4px;border-radius:999px;transition:height .1s,background .1s}.dz-canvas.is-dragging .dz-dropzone{height:14px;background:repeating-linear-gradient(90deg,var(--mform-border, #d9d5e6) 0 6px,transparent 6px 12px)}.dz-canvas.is-dragging .dz-dropzone.is-over{height:22px;background:var(--mform-primary, #6d4bd0)}.dz-palette-row{display:flex;align-items:center;gap:2px}.dz-palette-row .dz-palette-item{flex:1;cursor:grab}.dz-block-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:11px;padding:2px 4px;opacity:0}.dz-palette-row:hover .dz-block-remove{opacity:1}.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-empty{margin:4px 0}.dz-inspector{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-viewports{display:flex;gap:4px;justify-content:center;margin-bottom:16px}.dz-viewports button{font:inherit;font-size:12px;padding:5px 12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:999px;background:var(--mform-surface, #fff);color:var(--mform-muted, #6b6580);cursor:pointer}.dz-viewports button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-preview-frame{margin:0 auto;transition:max-width .16s}.dz-preview-frame.is-constrained{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:16px;background:var(--mform-surface, #fff);box-shadow:0 8px 28px #1f1b2e14}.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", "dragging", "activeDrop"], outputs: ["selectNode", "nodeDragStart", "dragOverTarget", "dropOnTarget", "dragFinished"] }, { 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: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1770
2093
  }
1771
2094
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, decorators: [{
1772
2095
  type: Component,
@@ -1780,14 +2103,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1780
2103
  <div class="dz-shell" (keydown)="onKeydown($event)">
1781
2104
  <header class="dz-toolbar">
1782
2105
  <div class="dz-tools">
1783
- <button type="button" (click)="undo()" [disabled]="past().length === 0" title="Undo (Ctrl+Z)">↶</button>
1784
- <button type="button" (click)="redo()" [disabled]="future().length === 0" title="Redo (Ctrl+Y)">↷</button>
2106
+ <button type="button" (click)="undo()" [disabled]="past().length === 0" [title]="labels().undo">↶</button>
2107
+ <button type="button" (click)="redo()" [disabled]="future().length === 0" [title]="labels().redo">↷</button>
1785
2108
  <span class="dz-sep"></span>
1786
- <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" title="Move up">↑</button>
1787
- <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1788
- <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1789
- <button type="button" (click)="removeSelected()" [disabled]="!selected()" title="Delete (Del)">🗑</button>
1790
- <button type="button" (click)="saveAsBlock()" [disabled]="!selected()" title="Save as reusable block">⊞</button>
2109
+ <button type="button" (click)="moveSelected(-1)" [disabled]="!selected()" [title]="labels().moveUp">↑</button>
2110
+ <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" [title]="labels().moveDown">↓</button>
2111
+ <button type="button" (click)="unnestSelected()" [disabled]="!canUnnest()" [title]="labels().unnest">⇤</button>
2112
+ <button type="button" (click)="nestSelected()" [disabled]="!canNest()" [title]="labels().nest">⇥</button>
2113
+ <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" [title]="labels().duplicate">⧉</button>
2114
+ <button type="button" (click)="removeSelected()" [disabled]="!selected()" [title]="labels().delete">🗑</button>
2115
+ <button type="button" (click)="saveAsBlock()" [disabled]="!selected()" [title]="labels().saveBlock">⊞</button>
1791
2116
  </div>
1792
2117
  <div class="dz-tools">
1793
2118
  @if (diagnostics().length > 0) {
@@ -1796,12 +2121,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1796
2121
  </button>
1797
2122
  }
1798
2123
  <div class="dz-menu">
1799
- <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" title="Workspace layout">
1800
- Workspace
2124
+ <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" [title]="labels().workspace">
2125
+ {{ labels().workspace }}
1801
2126
  </button>
1802
2127
  @if (workspaceMenu()) {
1803
2128
  <div class="dz-menu-panel" role="menu">
1804
- <span class="dz-menu-title">Panels</span>
2129
+ <span class="dz-menu-title">{{ labels().panels }}</span>
1805
2130
  @for (panel of panelIds; track panel) {
1806
2131
  <div class="dz-menu-row">
1807
2132
  <span>{{ panelLabel(panel) }}</span>
@@ -1816,20 +2141,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1816
2141
  </span>
1817
2142
  </div>
1818
2143
  }
1819
- <span class="dz-menu-title">Presets</span>
2144
+ <span class="dz-menu-title">{{ labels().presets }}</span>
1820
2145
  @for (preset of presets; track preset.id) {
1821
2146
  <button type="button" class="dz-menu-item" (click)="applyPreset(preset.id)">
1822
2147
  {{ preset.label }}
1823
2148
  </button>
1824
2149
  }
1825
- <button type="button" class="dz-menu-item" (click)="resetLayout()">Reset layout</button>
2150
+ <button type="button" class="dz-menu-item" (click)="resetLayout()">{{ labels().resetLayout }}</button>
1826
2151
  </div>
1827
2152
  }
1828
2153
  </div>
1829
- <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1830
- <button type="button" (click)="openExport()">Export</button>
2154
+ <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">{{ labels().import }}</button>
2155
+ <button type="button" (click)="openExport()">{{ labels().export }}</button>
1831
2156
  <button type="button" class="dz-primary" (click)="preview.set(!preview())">
1832
- {{ preview() ? 'Design' : 'Preview' }}
2157
+ {{ preview() ? labels().design : labels().preview }}
1833
2158
  </button>
1834
2159
  </div>
1835
2160
  </header>
@@ -1846,42 +2171,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1846
2171
 
1847
2172
  @if (dialog() === 'export') {
1848
2173
  <div class="dz-dialog">
1849
- <h4>Schema JSON</h4>
2174
+ <h4>{{ labels().exportTitle }}</h4>
1850
2175
  <textarea readonly rows="12">{{ exportedJson() }}</textarea>
1851
2176
  <div class="dz-dialog-actions">
1852
- <button type="button" (click)="dialog.set(null)">Close</button>
2177
+ <button type="button" (click)="dialog.set(null)">{{ labels().close }}</button>
1853
2178
  </div>
1854
2179
  </div>
1855
2180
  }
1856
2181
  @if (dialog() === 'import') {
1857
2182
  <div class="dz-dialog">
1858
- <h4>Import definition (native schema or supported formats)</h4>
1859
- <textarea rows="12" #importArea placeholder='{"version":1,"fields":[…]} or a legacy definition'></textarea>
2183
+ <h4>{{ labels().importTitle }}</h4>
2184
+ <textarea rows="12" #importArea [placeholder]="labels().importPlaceholder"></textarea>
1860
2185
  @if (importError()) {
1861
2186
  <p class="dz-import-error" role="alert">{{ importError() }}</p>
1862
2187
  }
1863
2188
  <div class="dz-dialog-actions">
1864
- <button type="button" (click)="dialog.set(null)">Cancel</button>
1865
- <button type="button" class="dz-primary" (click)="doImport(importArea.value)">Import</button>
2189
+ <button type="button" (click)="dialog.set(null)">{{ labels().cancel }}</button>
2190
+ <button type="button" class="dz-primary" (click)="doImport(importArea.value)">{{ labels().import }}</button>
1866
2191
  </div>
1867
2192
  </div>
1868
2193
  }
1869
2194
 
1870
2195
  @if (preview()) {
1871
2196
  <div class="dz-preview">
1872
- <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
2197
+ <div class="dz-viewports" role="group" [attr.aria-label]="labels().viewport">
2198
+ @for (option of viewports; track option.id) {
2199
+ <button
2200
+ type="button"
2201
+ [class.is-on]="viewport() === option.id"
2202
+ (click)="viewport.set(option.id)"
2203
+ [title]="option.width ? option.label + ' · ' + option.width + 'px' : option.label"
2204
+ >{{ option.icon }} {{ option.label }}</button>
2205
+ }
2206
+ </div>
2207
+ <div
2208
+ class="dz-preview-frame"
2209
+ [class.is-constrained]="viewportWidth() !== null"
2210
+ [style.max-width.px]="viewportWidth()"
2211
+ >
2212
+ <mform-renderer [schema]="draft()" (valueChanged)="previewData.set($event)" />
2213
+ </div>
1873
2214
  <details class="dz-preview-data">
1874
- <summary>Test data</summary>
2215
+ <summary>{{ labels().testData }}</summary>
1875
2216
  <pre>{{ previewData() | json }}</pre>
1876
2217
  </details>
1877
2218
  </div>
1878
2219
  } @else {
1879
- <div class="dz-body" [style.grid-template-columns]="bodyColumns()">
2220
+ <div
2221
+ class="dz-body"
2222
+ [style.grid-template-columns]="bodyColumns()"
2223
+ (pointermove)="onResizeMove($event)"
2224
+ (pointerup)="endResize()"
2225
+ (pointercancel)="endResize()"
2226
+ >
1880
2227
  @if (dockedPanels('left').length > 0) {
1881
2228
  <aside class="dz-dock dz-dock-left">
1882
2229
  @for (panel of dockedPanels('left'); track panel) {
1883
2230
  <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1884
2231
  }
2232
+ <div
2233
+ class="dz-splitter dz-splitter-left"
2234
+ role="separator"
2235
+ aria-orientation="vertical"
2236
+ (pointerdown)="startResize('left', $event)"
2237
+ ></div>
1885
2238
  </aside>
1886
2239
  }
1887
2240
 
@@ -1889,35 +2242,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1889
2242
  @for (node of draft().fields; track node.key; let i = $index) {
1890
2243
  <div
1891
2244
  class="dz-dropzone"
1892
- [class.is-over]="isDropTarget([i])"
1893
- (dragover)="allowDrop([i], $event)"
1894
- (drop)="drop([i], $event)"
2245
+ [class.is-over]="isDropTarget({ path: [i] })"
2246
+ (dragover)="allowDrop({ path: [i] }, $event)"
2247
+ (drop)="drop({ path: [i] }, $event)"
1895
2248
  ></div>
1896
2249
  <mform-designer-node
1897
2250
  [node]="node"
1898
2251
  [path]="[i]"
1899
2252
  [engine]="canvasEngine()"
1900
2253
  [selected]="selected()"
1901
- (selectNode)="select($event)"
2254
+ [dragging]="isDragging()"
2255
+ [activeDrop]="dropTarget()"
1902
2256
  draggable="true"
1903
2257
  (dragstart)="startNodeDrag([i], $event)"
1904
2258
  (dragend)="endDrag()"
2259
+ (selectNode)="select($event)"
2260
+ (nodeDragStart)="startNodeDrag($event)"
2261
+ (dragOverTarget)="allowDrop($event)"
2262
+ (dropOnTarget)="drop($event)"
2263
+ (dragFinished)="endDrag()"
1905
2264
  />
1906
2265
  } @empty {
1907
2266
  <div class="dz-canvas-empty">
1908
- Click a field in the palette — or drag it here — to start building.
2267
+ {{ labels().canvasEmpty }}
1909
2268
  </div>
1910
2269
  }
1911
2270
  <div
1912
2271
  class="dz-dropzone is-last"
1913
- [class.is-over]="isDropTarget([draft().fields.length])"
1914
- (dragover)="allowDrop([draft().fields.length], $event)"
1915
- (drop)="drop([draft().fields.length], $event)"
2272
+ [class.is-over]="isDropTarget({ path: [draft().fields.length] })"
2273
+ (dragover)="allowDrop({ path: [draft().fields.length] }, $event)"
2274
+ (drop)="drop({ path: [draft().fields.length] }, $event)"
1916
2275
  ></div>
1917
2276
  </main>
1918
2277
 
1919
2278
  @if (dockedPanels('right').length > 0) {
1920
2279
  <aside class="dz-dock dz-dock-right">
2280
+ <div
2281
+ class="dz-splitter dz-splitter-right"
2282
+ role="separator"
2283
+ aria-orientation="vertical"
2284
+ (pointerdown)="startResize('right', $event)"
2285
+ ></div>
1921
2286
  @for (panel of dockedPanels('right'); track panel) {
1922
2287
  <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1923
2288
  }
@@ -1935,10 +2300,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1935
2300
  <span class="dz-panel-actions">
1936
2301
  <button
1937
2302
  type="button"
1938
- [title]="panelSide(panel) === 'left' ? 'Move right' : 'Move left'"
2303
+ [title]="panelSide(panel) === 'left' ? labels().moveRight : labels().moveLeft"
1939
2304
  (click)="dock(panel, panelSide(panel) === 'left' ? 'right' : 'left')"
1940
2305
  >{{ panelSide(panel) === 'left' ? '⇥' : '⇤' }}</button>
1941
- <button type="button" title="Hide panel" (click)="dock(panel, 'hidden')">✕</button>
2306
+ <button type="button" [title]="labels().hidePanel" (click)="dock(panel, 'hidden')">✕</button>
1942
2307
  </span>
1943
2308
  </header>
1944
2309
  <div class="dz-panel-body">
@@ -2001,7 +2366,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2001
2366
  {{ item.label }}
2002
2367
  </button>
2003
2368
  } @empty {
2004
- <span class="dz-empty">no fields yet</span>
2369
+ <span class="dz-empty">{{ labels().noFields }}</span>
2005
2370
  }
2006
2371
  </div>
2007
2372
  }
@@ -2013,15 +2378,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2013
2378
  (nodePatch)="applyPatch($event)"
2014
2379
  />
2015
2380
  } @else {
2016
- <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
2381
+ <div class="dz-empty dz-props-empty">{{ labels().selectField }}</div>
2017
2382
  }
2018
2383
  }
2019
2384
  }
2020
2385
  </div>
2021
2386
  </section>
2022
2387
  </ng-template>
2023
- `, 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;flex:1;min-height:0}.dz-dock{display:flex;flex-direction:column;min-height:0;overflow-y:auto;background:var(--mform-surface, #fff)}.dz-dock-left{border-right:1px solid var(--mform-border, #d9d5e6)}.dz-dock-right{border-left:1px solid var(--mform-border, #d9d5e6)}.dz-panel{display:flex;flex-direction:column;border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-panel:last-child{border-bottom:none;flex:1}.dz-panel-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);position:sticky;top:0;z-index:1}.dz-panel-actions{display:flex;gap:2px;opacity:0;transition:opacity .12s}.dz-panel:hover .dz-panel-actions,.dz-panel:focus-within .dz-panel-actions{opacity:1}.dz-panel-actions button{border:none;background:none;color:inherit;cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px}.dz-panel-actions button:hover{background:var(--mform-surface, #fff)}.dz-panel-body{padding:10px}.dz-menu{position:relative}.dz-menu-panel{position:absolute;top:calc(100% + 6px);right:0;z-index:20;min-width:260px;padding:8px;background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);box-shadow:0 10px 30px #1f1b2e26}.dz-menu-title{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);padding:6px 6px 4px}.dz-menu-row{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;font-size:13px}.dz-seg{display:flex;gap:2px}.dz-seg button{border:1px solid var(--mform-border, #d9d5e6);background:none;color:var(--mform-muted, #6b6580);border-radius:6px;font-size:11px;padding:3px 8px;cursor:pointer}.dz-seg button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-menu-item{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-menu-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-group{margin-bottom:4px}.dz-group-toggle{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:12px;font-weight:600;text-align:left;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-group-toggle:hover{background:var(--mform-bg, #f6f5fa)}.dz-caret{font-size:10px;color:var(--mform-muted, #6b6580);width:10px}.dz-group-count{margin-left:auto;font-size:10px;font-weight:400;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);border-radius:999px;padding:1px 7px}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px 10px}.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-dropzone{height:4px;border-radius:999px;transition:height .1s,background .1s}.dz-canvas.is-dragging .dz-dropzone{height:14px;background:repeating-linear-gradient(90deg,var(--mform-border, #d9d5e6) 0 6px,transparent 6px 12px)}.dz-canvas.is-dragging .dz-dropzone.is-over{height:22px;background:var(--mform-primary, #6d4bd0)}.dz-palette-row{display:flex;align-items:center;gap:2px}.dz-palette-row .dz-palette-item{flex:1;cursor:grab}.dz-block-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:11px;padding:2px 4px;opacity:0}.dz-palette-row:hover .dz-block-remove{opacity:1}.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-empty{margin:4px 0}.dz-inspector{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"] }]
2024
- }], 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"] }], blocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "blocks", required: false }] }], blocksChanged: [{ type: i0.Output, args: ["blocksChanged"] }] } });
2388
+ `, 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;flex:1;min-height:0}.dz-dock{display:flex;flex-direction:column;min-height:0;overflow-y:auto;background:var(--mform-surface, #fff)}.dz-dock-left{border-right:1px solid var(--mform-border, #d9d5e6);position:relative}.dz-dock-right{border-left:1px solid var(--mform-border, #d9d5e6);position:relative}.dz-splitter{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;touch-action:none;z-index:2}.dz-splitter-left{right:-3px}.dz-splitter-right{left:-3px}.dz-splitter:hover,.dz-splitter:active{background:var(--mform-primary, #6d4bd0);opacity:.35}.dz-panel{display:flex;flex-direction:column;border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-panel:last-child{border-bottom:none;flex:1}.dz-panel-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);position:sticky;top:0;z-index:1}.dz-panel-actions{display:flex;gap:2px;opacity:0;transition:opacity .12s}.dz-panel:hover .dz-panel-actions,.dz-panel:focus-within .dz-panel-actions{opacity:1}.dz-panel-actions button{border:none;background:none;color:inherit;cursor:pointer;font-size:11px;padding:2px 4px;border-radius:4px}.dz-panel-actions button:hover{background:var(--mform-surface, #fff)}.dz-panel-body{padding:10px}.dz-menu{position:relative}.dz-menu-panel{position:absolute;top:calc(100% + 6px);right:0;z-index:20;min-width:260px;padding:8px;background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);box-shadow:0 10px 30px #1f1b2e26}.dz-menu-title{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580);padding:6px 6px 4px}.dz-menu-row{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;font-size:13px}.dz-seg{display:flex;gap:2px}.dz-seg button{border:1px solid var(--mform-border, #d9d5e6);background:none;color:var(--mform-muted, #6b6580);border-radius:6px;font-size:11px;padding:3px 8px;cursor:pointer}.dz-seg button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-menu-item{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-menu-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-group{margin-bottom:4px}.dz-group-toggle{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:12px;font-weight:600;text-align:left;padding:6px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.dz-group-toggle:hover{background:var(--mform-bg, #f6f5fa)}.dz-caret{font-size:10px;color:var(--mform-muted, #6b6580);width:10px}.dz-group-count{margin-left:auto;font-size:10px;font-weight:400;color:var(--mform-muted, #6b6580);background:var(--mform-bg, #f6f5fa);border-radius:999px;padding:1px 7px}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px 10px}.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-dropzone{height:4px;border-radius:999px;transition:height .1s,background .1s}.dz-canvas.is-dragging .dz-dropzone{height:14px;background:repeating-linear-gradient(90deg,var(--mform-border, #d9d5e6) 0 6px,transparent 6px 12px)}.dz-canvas.is-dragging .dz-dropzone.is-over{height:22px;background:var(--mform-primary, #6d4bd0)}.dz-palette-row{display:flex;align-items:center;gap:2px}.dz-palette-row .dz-palette-item{flex:1;cursor:grab}.dz-block-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:11px;padding:2px 4px;opacity:0}.dz-palette-row:hover .dz-block-remove{opacity:1}.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-empty{margin:4px 0}.dz-inspector{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-viewports{display:flex;gap:4px;justify-content:center;margin-bottom:16px}.dz-viewports button{font:inherit;font-size:12px;padding:5px 12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:999px;background:var(--mform-surface, #fff);color:var(--mform-muted, #6b6580);cursor:pointer}.dz-viewports button.is-on{background:var(--mform-primary, #6d4bd0);border-color:transparent;color:var(--mform-primary-contrast, #fff)}.dz-preview-frame{margin:0 auto;transition:max-width .16s}.dz-preview-frame.is-constrained{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:16px;background:var(--mform-surface, #fff);box-shadow:0 8px 28px #1f1b2e14}.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"] }]
2389
+ }], 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 }] }], catalogOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], schemaChanged: [{ type: i0.Output, args: ["schemaChanged"] }], blocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "blocks", required: false }] }], blocksChanged: [{ type: i0.Output, args: ["blocksChanged"] }] } });
2025
2390
 
2026
2391
  /*
2027
2392
  * Designer entry point (`@mosaicoo/form-angular/designer`).
@@ -2035,5 +2400,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
2035
2400
  * Generated bundle index. Do not edit.
2036
2401
  */
2037
2402
 
2038
- export { DEFAULT_CATALOG, DEFAULT_LAYOUT, DesignerNodeComponent, FormDesignerComponent, InspectorComponent, PANEL_LABELS, WORKSPACE_PRESETS };
2403
+ export { DEFAULT_CATALOG, DEFAULT_LAYOUT, DESIGNER_LABELS_EN, DesignerNodeComponent, FormDesignerComponent, InspectorComponent, PANEL_LABELS, WORKSPACE_PRESETS, dropId };
2039
2404
  //# sourceMappingURL=mosaicoo-form-angular-designer.mjs.map