@mosaicoo/form-angular 0.4.0 → 0.6.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.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input as input$1, output, signal, ViewEncapsulation, ChangeDetectionStrategy, Component, computed, effect } from '@angular/core';
3
- import { JsonPipe } from '@angular/common';
2
+ import { input as input$1, output, signal, ViewEncapsulation, ChangeDetectionStrategy, Component, computed, effect, untracked } from '@angular/core';
3
+ import { NgTemplateOutlet, JsonPipe } from '@angular/common';
4
4
  import { collectInputs, generateKey, validateSchema, createFormEngine, getNodeAt, insertNode, parentOf, removeNode, duplicateNode, moveNode, updateNode } from '@mosaicoo/form-core';
5
5
  import { importForm, createLegacyImporter } from '@mosaicoo/form-core/interop';
6
6
  import { FormNodeComponent, FormRendererComponent } from '@mosaicoo/form-angular';
@@ -403,6 +403,99 @@ class InspectorComponent {
403
403
  const mask = this.inputValue(event).trim();
404
404
  this.emitPatch({ format: mask === '' ? undefined : { mask } });
405
405
  }
406
+ patchMultiple(event) {
407
+ const checked = event.target.checked;
408
+ this.emitPatch({
409
+ multiple: checked ? true : undefined,
410
+ dataType: checked ? 'array' : 'string',
411
+ });
412
+ }
413
+ // -- options source -------------------------------------------------------
414
+ sourceKind() {
415
+ return this.field().optionsSource?.type ?? '';
416
+ }
417
+ providerName() {
418
+ const source = this.field().optionsSource;
419
+ return source?.type === 'provider' ? source.name : '';
420
+ }
421
+ remoteUrl() {
422
+ const source = this.field().optionsSource;
423
+ return source?.type === 'remote' ? source.url : '';
424
+ }
425
+ refreshOn() {
426
+ return this.field().optionsSource?.refreshOn ?? '';
427
+ }
428
+ patchSourceKind(event) {
429
+ const kind = this.inputValue(event);
430
+ if (kind === 'provider') {
431
+ this.emitPatch({
432
+ optionsSource: { type: 'provider', name: this.providerName() || this.field().key },
433
+ });
434
+ }
435
+ else if (kind === 'remote') {
436
+ this.emitPatch({
437
+ optionsSource: { type: 'remote', url: this.remoteUrl() },
438
+ });
439
+ }
440
+ else {
441
+ this.emitPatch({ optionsSource: undefined });
442
+ }
443
+ }
444
+ patchProviderName(event) {
445
+ const name = this.inputValue(event).trim();
446
+ if (name === '')
447
+ return;
448
+ this.emitPatch({
449
+ optionsSource: { ...this.field().optionsSource, type: 'provider', name },
450
+ });
451
+ }
452
+ patchRemoteUrl(event) {
453
+ const url = this.inputValue(event).trim();
454
+ this.emitPatch({
455
+ optionsSource: { ...this.field().optionsSource, type: 'remote', url },
456
+ });
457
+ }
458
+ patchRefreshOn(event) {
459
+ const source = this.field().optionsSource;
460
+ if (!source)
461
+ return;
462
+ const refreshOn = this.inputValue(event).trim();
463
+ const next = { ...source };
464
+ if (refreshOn === '')
465
+ delete next.refreshOn;
466
+ else
467
+ next.refreshOn = refreshOn;
468
+ this.emitPatch({ optionsSource: next });
469
+ }
470
+ // -- props (type-specific settings for custom components) -----------------
471
+ propsError = signal('', ...(ngDevMode ? [{ debugName: "propsError" }] : /* istanbul ignore next */ []));
472
+ hasProps() {
473
+ return Object.keys(this.node().props ?? {}).length > 0;
474
+ }
475
+ propsText() {
476
+ const props = this.node().props;
477
+ return props && Object.keys(props).length > 0 ? JSON.stringify(props, null, 2) : '';
478
+ }
479
+ patchProps(event) {
480
+ const raw = this.inputValue(event).trim();
481
+ if (raw === '') {
482
+ this.propsError.set('');
483
+ this.emitPatch({ props: undefined });
484
+ return;
485
+ }
486
+ try {
487
+ const parsed = JSON.parse(raw);
488
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
489
+ this.propsError.set('Props must be a JSON object.');
490
+ return;
491
+ }
492
+ this.propsError.set('');
493
+ this.emitPatch({ props: parsed });
494
+ }
495
+ catch (error) {
496
+ this.propsError.set('Invalid JSON: ' + String(error instanceof Error ? error.message : error));
497
+ }
498
+ }
406
499
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: InspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
407
500
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: InspectorComponent, isStandalone: true, selector: "mform-inspector", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { nodePatch: "nodePatch" }, ngImport: i0, template: `
408
501
  @let n = node();
@@ -538,8 +631,60 @@ class InspectorComponent {
538
631
  (change)="patchMask($event)"
539
632
  />
540
633
  </label>
634
+ <label class="dz-check">
635
+ <input type="checkbox" [checked]="field().multiple === true" (change)="patchMultiple($event)" />
636
+ <span>Multiple values</span>
637
+ </label>
638
+ </details>
639
+
640
+ <details class="dz-section">
641
+ <summary>Options source</summary>
642
+ <label class="dz-prop">
643
+ <span>Source</span>
644
+ <select [value]="sourceKind()" (change)="patchSourceKind($event)">
645
+ <option value="">Static list</option>
646
+ <option value="provider" [selected]="sourceKind() === 'provider'">Host provider</option>
647
+ <option value="remote" [selected]="sourceKind() === 'remote'">Remote URL</option>
648
+ </select>
649
+ </label>
650
+ @if (sourceKind() === 'provider') {
651
+ <label class="dz-prop">
652
+ <span>Provider name</span>
653
+ <input type="text" [value]="providerName()" (change)="patchProviderName($event)" />
654
+ </label>
655
+ }
656
+ @if (sourceKind() === 'remote') {
657
+ <label class="dz-prop">
658
+ <span>URL</span>
659
+ <input type="text" [value]="remoteUrl()" (change)="patchRemoteUrl($event)" />
660
+ </label>
661
+ }
662
+ @if (sourceKind()) {
663
+ <label class="dz-prop">
664
+ <span>Refresh on (field path or "data")</span>
665
+ <input type="text" [value]="refreshOn()" (change)="patchRefreshOn($event)" />
666
+ </label>
667
+ }
541
668
  </details>
542
669
  }
670
+
671
+ <!-- Type-specific configuration for CUSTOM components: the host reads
672
+ these from field.props (e.g. a board's columns, a chart's series). -->
673
+ <details class="dz-section" [open]="hasProps()">
674
+ <summary>Type settings (props)</summary>
675
+ <label class="dz-prop">
676
+ <span>JSON passed to the field component</span>
677
+ <textarea
678
+ rows="5"
679
+ spellcheck="false"
680
+ [value]="propsText()"
681
+ (change)="patchProps($event)"
682
+ ></textarea>
683
+ </label>
684
+ @if (propsError()) {
685
+ <p class="dz-import-error" role="alert">{{ propsError() }}</p>
686
+ }
687
+ </details>
543
688
  </div>
544
689
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
545
690
  }
@@ -683,8 +828,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
683
828
  (change)="patchMask($event)"
684
829
  />
685
830
  </label>
831
+ <label class="dz-check">
832
+ <input type="checkbox" [checked]="field().multiple === true" (change)="patchMultiple($event)" />
833
+ <span>Multiple values</span>
834
+ </label>
835
+ </details>
836
+
837
+ <details class="dz-section">
838
+ <summary>Options source</summary>
839
+ <label class="dz-prop">
840
+ <span>Source</span>
841
+ <select [value]="sourceKind()" (change)="patchSourceKind($event)">
842
+ <option value="">Static list</option>
843
+ <option value="provider" [selected]="sourceKind() === 'provider'">Host provider</option>
844
+ <option value="remote" [selected]="sourceKind() === 'remote'">Remote URL</option>
845
+ </select>
846
+ </label>
847
+ @if (sourceKind() === 'provider') {
848
+ <label class="dz-prop">
849
+ <span>Provider name</span>
850
+ <input type="text" [value]="providerName()" (change)="patchProviderName($event)" />
851
+ </label>
852
+ }
853
+ @if (sourceKind() === 'remote') {
854
+ <label class="dz-prop">
855
+ <span>URL</span>
856
+ <input type="text" [value]="remoteUrl()" (change)="patchRemoteUrl($event)" />
857
+ </label>
858
+ }
859
+ @if (sourceKind()) {
860
+ <label class="dz-prop">
861
+ <span>Refresh on (field path or "data")</span>
862
+ <input type="text" [value]="refreshOn()" (change)="patchRefreshOn($event)" />
863
+ </label>
864
+ }
686
865
  </details>
687
866
  }
867
+
868
+ <!-- Type-specific configuration for CUSTOM components: the host reads
869
+ these from field.props (e.g. a board's columns, a chart's series). -->
870
+ <details class="dz-section" [open]="hasProps()">
871
+ <summary>Type settings (props)</summary>
872
+ <label class="dz-prop">
873
+ <span>JSON passed to the field component</span>
874
+ <textarea
875
+ rows="5"
876
+ spellcheck="false"
877
+ [value]="propsText()"
878
+ (change)="patchProps($event)"
879
+ ></textarea>
880
+ </label>
881
+ @if (propsError()) {
882
+ <p class="dz-import-error" role="alert">{{ propsError() }}</p>
883
+ }
884
+ </details>
688
885
  </div>
689
886
  `,
690
887
  }]
@@ -808,6 +1005,69 @@ const DEFAULT_CATALOG = [
808
1005
  },
809
1006
  ];
810
1007
 
1008
+ /**
1009
+ * Designer workspace layout.
1010
+ *
1011
+ * Panels are dockable like in professional tools: each one can sit on the
1012
+ * left, on the right, or be hidden — the canvas always keeps the center. The
1013
+ * arrangement is a plain serializable object so hosts can persist it per user
1014
+ * (the designer also remembers it locally by default).
1015
+ */
1016
+ const DEFAULT_LAYOUT = {
1017
+ palette: 'left',
1018
+ structure: 'left',
1019
+ properties: 'right',
1020
+ leftWidth: 240,
1021
+ rightWidth: 280,
1022
+ };
1023
+ /** Ready-made arrangements offered in the workspace menu. */
1024
+ const WORKSPACE_PRESETS = [
1025
+ { id: 'default', label: 'Default (fields left, properties right)', layout: DEFAULT_LAYOUT },
1026
+ {
1027
+ id: 'authoring',
1028
+ label: 'Authoring (everything left)',
1029
+ layout: { ...DEFAULT_LAYOUT, properties: 'left', leftWidth: 300 },
1030
+ },
1031
+ {
1032
+ id: 'inspect',
1033
+ label: 'Inspect (structure + properties right)',
1034
+ layout: { ...DEFAULT_LAYOUT, structure: 'right', properties: 'right', rightWidth: 320 },
1035
+ },
1036
+ {
1037
+ id: 'canvas',
1038
+ label: 'Canvas only (panels hidden)',
1039
+ layout: { ...DEFAULT_LAYOUT, palette: 'hidden', structure: 'hidden', properties: 'hidden' },
1040
+ },
1041
+ ];
1042
+ const PANEL_LABELS = {
1043
+ palette: 'Fields',
1044
+ structure: 'Structure',
1045
+ properties: 'Properties',
1046
+ };
1047
+ const STORAGE_KEY = 'mform-designer-layout';
1048
+ function loadLayout(storage) {
1049
+ if (!storage)
1050
+ return DEFAULT_LAYOUT;
1051
+ try {
1052
+ const raw = storage.getItem(STORAGE_KEY);
1053
+ if (!raw)
1054
+ return DEFAULT_LAYOUT;
1055
+ const parsed = JSON.parse(raw);
1056
+ return { ...DEFAULT_LAYOUT, ...parsed };
1057
+ }
1058
+ catch {
1059
+ return DEFAULT_LAYOUT;
1060
+ }
1061
+ }
1062
+ function saveLayout(storage, layout) {
1063
+ try {
1064
+ storage?.setItem(STORAGE_KEY, JSON.stringify(layout));
1065
+ }
1066
+ catch {
1067
+ /* storage unavailable (private mode, SSR) — layout stays in memory */
1068
+ }
1069
+ }
1070
+
811
1071
  const EMPTY_SCHEMA = { version: 1, fields: [] };
812
1072
  const HISTORY_LIMIT = 50;
813
1073
  /**
@@ -835,6 +1095,71 @@ class FormDesignerComponent {
835
1095
  dialog = signal(null, ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
836
1096
  importError = signal('', ...(ngDevMode ? [{ debugName: "importError" }] : /* istanbul ignore next */ []));
837
1097
  showDiagnostics = signal(false, ...(ngDevMode ? [{ debugName: "showDiagnostics" }] : /* istanbul ignore next */ []));
1098
+ // -- workspace ------------------------------------------------------------
1099
+ /** Panel arrangement; persisted locally unless the host controls it. */
1100
+ layout = signal(DEFAULT_LAYOUT, ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1101
+ workspaceMenu = signal(false, ...(ngDevMode ? [{ debugName: "workspaceMenu" }] : /* istanbul ignore next */ []));
1102
+ panelIds = ['palette', 'structure', 'properties'];
1103
+ dockSides = ['left', 'right', 'hidden'];
1104
+ panelLabels = PANEL_LABELS;
1105
+ sideLabels = { left: '⇤', right: '⇥', hidden: '✕' };
1106
+ presets = WORKSPACE_PRESETS;
1107
+ /** Collapsed palette groups (only the first stays open by default). */
1108
+ openGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "openGroups" }] : /* istanbul ignore next */ []));
1109
+ storage = (() => {
1110
+ try {
1111
+ return typeof localStorage === 'undefined' ? null : localStorage;
1112
+ }
1113
+ catch {
1114
+ return null;
1115
+ }
1116
+ })();
1117
+ panelLabel(panel) {
1118
+ return PANEL_LABELS[panel];
1119
+ }
1120
+ panelSide(panel) {
1121
+ return this.layout()[panel];
1122
+ }
1123
+ dockedPanels(side) {
1124
+ const layout = this.layout();
1125
+ return this.panelIds.filter((panel) => layout[panel] === side);
1126
+ }
1127
+ bodyColumns() {
1128
+ const layout = this.layout();
1129
+ const left = this.dockedPanels('left').length > 0 ? `${layout.leftWidth}px ` : '';
1130
+ const right = this.dockedPanels('right').length > 0 ? ` ${layout.rightWidth}px` : '';
1131
+ return `${left}1fr${right}`;
1132
+ }
1133
+ dock(panel, side) {
1134
+ this.layout.update((current) => ({ ...current, [panel]: side }));
1135
+ saveLayout(this.storage, this.layout());
1136
+ }
1137
+ applyPreset(id) {
1138
+ const preset = WORKSPACE_PRESETS.find((p) => p.id === id);
1139
+ if (!preset)
1140
+ return;
1141
+ this.layout.set({ ...preset.layout });
1142
+ saveLayout(this.storage, this.layout());
1143
+ this.workspaceMenu.set(false);
1144
+ }
1145
+ resetLayout() {
1146
+ this.layout.set({ ...DEFAULT_LAYOUT });
1147
+ saveLayout(this.storage, this.layout());
1148
+ this.workspaceMenu.set(false);
1149
+ }
1150
+ isGroupOpen(label) {
1151
+ return this.openGroups().has(label);
1152
+ }
1153
+ toggleGroup(label) {
1154
+ this.openGroups.update((open) => {
1155
+ const next = new Set(open);
1156
+ if (next.has(label))
1157
+ next.delete(label);
1158
+ else
1159
+ next.add(label);
1160
+ return next;
1161
+ });
1162
+ }
838
1163
  catalog = computed(() => [...DEFAULT_CATALOG, ...this.extraCatalog()], ...(ngDevMode ? [{ debugName: "catalog" }] : /* istanbul ignore next */ []));
839
1164
  diagnostics = computed(() => validateSchema(this.draft()), ...(ngDevMode ? [{ debugName: "diagnostics" }] : /* istanbul ignore next */ []));
840
1165
  exportedJson = computed(() => JSON.stringify(this.draft(), null, 2), ...(ngDevMode ? [{ debugName: "exportedJson" }] : /* istanbul ignore next */ []));
@@ -864,13 +1189,20 @@ class FormDesignerComponent {
864
1189
  return items;
865
1190
  }, ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
866
1191
  constructor() {
1192
+ this.layout.set(loadLayout(this.storage));
1193
+ // First palette group starts expanded; the rest opens on demand.
1194
+ const first = this.catalog()[0]?.label;
1195
+ if (first)
1196
+ this.openGroups.set(new Set([first]));
867
1197
  // (Re)load the document whenever the host hands a new definition in.
868
1198
  effect(() => {
869
1199
  const native = this.schema();
870
1200
  const source = this.source();
871
1201
  // Hosts often feed `schemaChanged` back into [schema] (controlled
872
1202
  // usage) — the echo of our own draft must not reset history/selection.
873
- if (native && native === this.draft())
1203
+ // `untracked` is essential: reading the draft as a dependency would make
1204
+ // every edit re-run this effect and revert the document.
1205
+ if (native && native === untracked(() => this.draft()))
874
1206
  return;
875
1207
  let initial = EMPTY_SCHEMA;
876
1208
  if (native) {
@@ -1034,6 +1366,37 @@ class FormDesignerComponent {
1034
1366
  ⚠ {{ diagnostics().length }}
1035
1367
  </button>
1036
1368
  }
1369
+ <div class="dz-menu">
1370
+ <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" title="Workspace layout">
1371
+ ▦ Workspace
1372
+ </button>
1373
+ @if (workspaceMenu()) {
1374
+ <div class="dz-menu-panel" role="menu">
1375
+ <span class="dz-menu-title">Panels</span>
1376
+ @for (panel of panelIds; track panel) {
1377
+ <div class="dz-menu-row">
1378
+ <span>{{ panelLabel(panel) }}</span>
1379
+ <span class="dz-seg">
1380
+ @for (side of dockSides; track side) {
1381
+ <button
1382
+ type="button"
1383
+ [class.is-on]="layout()[panel] === side"
1384
+ (click)="dock(panel, side)"
1385
+ >{{ sideLabels[side] }}</button>
1386
+ }
1387
+ </span>
1388
+ </div>
1389
+ }
1390
+ <span class="dz-menu-title">Presets</span>
1391
+ @for (preset of presets; track preset.id) {
1392
+ <button type="button" class="dz-menu-item" (click)="applyPreset(preset.id)">
1393
+ {{ preset.label }}
1394
+ </button>
1395
+ }
1396
+ <button type="button" class="dz-menu-item" (click)="resetLayout()">Reset layout</button>
1397
+ </div>
1398
+ }
1399
+ </div>
1037
1400
  <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1038
1401
  <button type="button" (click)="openExport()">Export</button>
1039
1402
  <button type="button" class="dz-primary" (click)="preview.set(!preview())">
@@ -1084,40 +1447,14 @@ class FormDesignerComponent {
1084
1447
  </details>
1085
1448
  </div>
1086
1449
  } @else {
1087
- <div class="dz-body">
1088
- <aside class="dz-palette">
1089
- <h4>Fields</h4>
1090
- @for (group of catalog(); track group.label) {
1091
- <div class="dz-group">
1092
- <span class="dz-group-label">{{ group.label }}</span>
1093
- <div class="dz-group-entries">
1094
- @for (entry of group.entries; track entry.type) {
1095
- <button type="button" class="dz-palette-item" (click)="add(entry)"
1096
- [title]="'Add ' + entry.label">
1097
- <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1098
- </button>
1099
- }
1100
- </div>
1101
- </div>
1102
- }
1103
- <h4>Structure</h4>
1104
- <div class="dz-tree" role="tree">
1105
- @for (item of tree(); track item.id) {
1106
- <button
1107
- type="button"
1108
- role="treeitem"
1109
- class="dz-tree-item"
1110
- [class.is-selected]="isSelectedPath(item.path)"
1111
- [style.padding-left.px]="8 + item.depth * 14"
1112
- (click)="select(item.path)"
1113
- >
1114
- {{ item.label }}
1115
- </button>
1116
- } @empty {
1117
- <span class="dz-empty">no fields yet</span>
1450
+ <div class="dz-body" [style.grid-template-columns]="bodyColumns()">
1451
+ @if (dockedPanels('left').length > 0) {
1452
+ <aside class="dz-dock dz-dock-left">
1453
+ @for (panel of dockedPanels('left'); track panel) {
1454
+ <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1118
1455
  }
1119
- </div>
1120
- </aside>
1456
+ </aside>
1457
+ }
1121
1458
 
1122
1459
  <main class="dz-canvas" (click)="select(null)">
1123
1460
  @for (node of draft().fields; track node.key; let i = $index) {
@@ -1135,25 +1472,104 @@ class FormDesignerComponent {
1135
1472
  }
1136
1473
  </main>
1137
1474
 
1138
- <aside class="dz-props">
1139
- @if (selectedNode(); as node) {
1140
- <mform-inspector
1141
- [node]="node"
1142
- [schema]="draft()"
1143
- (nodePatch)="applyPatch($event)"
1144
- />
1145
- } @else {
1146
- <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1147
- }
1148
- </aside>
1475
+ @if (dockedPanels('right').length > 0) {
1476
+ <aside class="dz-dock dz-dock-right">
1477
+ @for (panel of dockedPanels('right'); track panel) {
1478
+ <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1479
+ }
1480
+ </aside>
1481
+ }
1149
1482
  </div>
1150
1483
  }
1151
1484
  </div>
1152
- `, isInline: true, styles: [".dz-shell{display:flex;flex-direction:column;height:100%;min-height:480px;font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e);background:var(--mform-bg, #f6f5fa);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.dz-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-tools{display:flex;align-items:center;gap:6px}.dz-toolbar button{font:inherit;font-size:13px;padding:6px 10px;border:1px solid transparent;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-toolbar button:hover:not(:disabled){background:var(--mform-bg, #f6f5fa)}.dz-toolbar button:disabled{opacity:.35;cursor:default}.dz-toolbar .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-sep{width:1px;height:18px;background:var(--mform-border, #d9d5e6)}.dz-diag-badge{color:#b45309;font-weight:600}.dz-diagnostics{max-height:140px;overflow-y:auto;padding:8px 14px;background:#fdf5ef;border-bottom:1px solid var(--mform-border, #d9d5e6);font-size:12px}.dz-diag{padding:2px 0;color:#9a3412}.dz-diag.is-warning{color:#92400e}.dz-diag code{font-weight:600;margin-right:6px}.dz-diag em{color:var(--mform-muted, #6b6580);margin-left:6px}.dz-dialog{padding:14px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-dialog h4{margin:0 0 8px;font-size:13px}.dz-dialog textarea{width:100%;font-family:ui-monospace,Consolas,monospace;font-size:12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;box-sizing:border-box;resize:vertical}.dz-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.dz-dialog-actions button{font:inherit;font-size:13px;padding:7px 14px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:none;cursor:pointer}.dz-dialog-actions .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-color:transparent}.dz-import-error{color:var(--mform-danger, #c62842);font-size:12px}.dz-body{display:grid;grid-template-columns:240px 1fr 280px;flex:1;min-height:0}.dz-palette{border-right:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);padding:12px;overflow-y:auto}.dz-palette h4{margin:4px 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580)}.dz-group{margin-bottom:10px}.dz-group-label{font-size:11px;color:var(--mform-muted, #6b6580)}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin-top:4px}.dz-palette-item{display:flex;align-items:center;gap:8px;font:inherit;font-size:13px;text-align:left;padding:6px 8px;border:none;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-palette-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px;font-size:11px}.dz-tree{display:flex;flex-direction:column;gap:1px}.dz-tree-item{font:inherit;font-size:12px;text-align:left;padding:4px 8px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dz-tree-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-tree-item.is-selected{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-canvas{padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:10px}.dz-canvas-empty,.dz-empty{color:var(--mform-muted, #6b6580);font-size:13px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:18px;text-align:center}.dz-node{position:relative;border:1px solid transparent;border-radius:var(--mform-radius, 10px);padding:20px 10px 10px;cursor:pointer;background:var(--mform-surface, #fff)}.dz-node:hover{border-color:var(--mform-border, #d9d5e6)}.dz-node.is-selected{border-color:var(--mform-primary, #6d4bd0);box-shadow:0 0 0 1px var(--mform-primary, #6d4bd0)}.dz-chip{position:absolute;top:2px;left:10px;font-size:10px;color:var(--mform-muted, #6b6580);pointer-events:none}.dz-node.is-selected>.dz-chip{color:var(--mform-primary, #6d4bd0);font-weight:600}.dz-children{display:flex;flex-direction:column;gap:8px}.dz-columns{display:grid;gap:10px}.dz-column{display:flex;flex-direction:column;gap:8px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:8px;padding:8px;min-height:40px}.dz-tabbar{display:flex;gap:4px;margin-bottom:8px;border-bottom:2px solid var(--mform-border, #d9d5e6)}.dz-tab{font:inherit;font-size:12px;padding:5px 12px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.dz-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.dz-leaf{pointer-events:none}.dz-props{border-left:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);overflow-y:auto}.dz-props-empty{margin:12px}.dz-inspector{padding:12px;display:flex;flex-direction:column;gap:10px}.dz-inspector h3{margin:0;font-size:13px}.dz-inspector h3 code{color:var(--mform-muted, #6b6580);font-weight:400}.dz-prop{display:flex;flex-direction:column;gap:4px;font-size:12px}.dz-prop span{color:var(--mform-muted, #6b6580)}.dz-prop input,.dz-prop select,.dz-prop textarea{font:inherit;font-size:13px;padding:7px 9px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:#fff;width:100%;box-sizing:border-box}.dz-check{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer}.dz-check input{accent-color:var(--mform-primary, #6d4bd0)}.dz-section{border-top:1px solid var(--mform-border, #d9d5e6);padding-top:8px}.dz-section summary{font-size:12px;font-weight:600;cursor:pointer;margin-bottom:8px}.dz-section .dz-prop{margin-bottom:8px}.dz-option-row{display:flex;gap:6px;margin-bottom:6px}.dz-option-row input{flex:1;min-width:0;font-size:12px;padding:6px 8px;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px}.dz-option-row button{border:none;background:none;cursor:pointer;color:var(--mform-muted, #6b6580)}.dz-add{font:inherit;font-size:12px;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);background:none;border-radius:8px;padding:6px 10px;cursor:pointer}.dz-preview{padding:20px;overflow-y:auto}.dz-preview-data{margin-top:16px;font-size:12px}.dz-preview-data pre{background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: DesignerNodeComponent, selector: "mform-designer-node", inputs: ["node", "path", "engine", "selected"], outputs: ["selectNode"] }, { kind: "component", type: InspectorComponent, selector: "mform-inspector", inputs: ["node", "schema"], outputs: ["nodePatch"] }, { kind: "component", type: FormRendererComponent, selector: "mform-renderer", inputs: ["schema", "source", "initialData", "optionsProviders", "messages", "validators", "mode", "submitHandler", "labels"], outputs: ["submitted", "valueChanged"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1485
+
1486
+ <!-- One template per panel, rendered into whichever dock holds it. -->
1487
+ <ng-template #panelTemplate let-panel>
1488
+ <section class="dz-panel" [attr.data-panel]="panel">
1489
+ <header class="dz-panel-head">
1490
+ <span>{{ panelLabel(panel) }}</span>
1491
+ <span class="dz-panel-actions">
1492
+ <button
1493
+ type="button"
1494
+ [title]="panelSide(panel) === 'left' ? 'Move right' : 'Move left'"
1495
+ (click)="dock(panel, panelSide(panel) === 'left' ? 'right' : 'left')"
1496
+ >{{ panelSide(panel) === 'left' ? '⇥' : '⇤' }}</button>
1497
+ <button type="button" title="Hide panel" (click)="dock(panel, 'hidden')">✕</button>
1498
+ </span>
1499
+ </header>
1500
+ <div class="dz-panel-body">
1501
+ @switch (panel) {
1502
+ @case ('palette') {
1503
+ @for (group of catalog(); track group.label) {
1504
+ <div class="dz-group">
1505
+ <button
1506
+ type="button"
1507
+ class="dz-group-toggle"
1508
+ [class.is-open]="isGroupOpen(group.label)"
1509
+ [attr.aria-expanded]="isGroupOpen(group.label)"
1510
+ (click)="toggleGroup(group.label)"
1511
+ >
1512
+ <span class="dz-caret" aria-hidden="true">{{ isGroupOpen(group.label) ? '▾' : '▸' }}</span>
1513
+ {{ group.label }}
1514
+ <span class="dz-group-count">{{ group.entries.length }}</span>
1515
+ </button>
1516
+ @if (isGroupOpen(group.label)) {
1517
+ <div class="dz-group-entries">
1518
+ @for (entry of group.entries; track entry.type) {
1519
+ <button type="button" class="dz-palette-item" (click)="add(entry)"
1520
+ [title]="'Add ' + entry.label">
1521
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1522
+ </button>
1523
+ }
1524
+ </div>
1525
+ }
1526
+ </div>
1527
+ }
1528
+ }
1529
+ @case ('structure') {
1530
+ <div class="dz-tree" role="tree">
1531
+ @for (item of tree(); track item.id) {
1532
+ <button
1533
+ type="button"
1534
+ role="treeitem"
1535
+ class="dz-tree-item"
1536
+ [class.is-selected]="isSelectedPath(item.path)"
1537
+ [style.padding-left.px]="8 + item.depth * 14"
1538
+ (click)="select(item.path)"
1539
+ >
1540
+ {{ item.label }}
1541
+ </button>
1542
+ } @empty {
1543
+ <span class="dz-empty">no fields yet</span>
1544
+ }
1545
+ </div>
1546
+ }
1547
+ @case ('properties') {
1548
+ @if (selectedNode(); as node) {
1549
+ <mform-inspector
1550
+ [node]="node"
1551
+ [schema]="draft()"
1552
+ (nodePatch)="applyPatch($event)"
1553
+ />
1554
+ } @else {
1555
+ <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1556
+ }
1557
+ }
1558
+ }
1559
+ </div>
1560
+ </section>
1561
+ </ng-template>
1562
+ `, 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-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 });
1153
1563
  }
1154
1564
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, decorators: [{
1155
1565
  type: Component,
1156
- args: [{ selector: 'mform-designer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [DesignerNodeComponent, InspectorComponent, FormRendererComponent, JsonPipe], template: `
1566
+ args: [{ selector: 'mform-designer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
1567
+ DesignerNodeComponent,
1568
+ InspectorComponent,
1569
+ FormRendererComponent,
1570
+ JsonPipe,
1571
+ NgTemplateOutlet,
1572
+ ], template: `
1157
1573
  <div class="dz-shell" (keydown)="onKeydown($event)">
1158
1574
  <header class="dz-toolbar">
1159
1575
  <div class="dz-tools">
@@ -1171,6 +1587,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1171
1587
  ⚠ {{ diagnostics().length }}
1172
1588
  </button>
1173
1589
  }
1590
+ <div class="dz-menu">
1591
+ <button type="button" (click)="workspaceMenu.set(!workspaceMenu())" title="Workspace layout">
1592
+ ▦ Workspace
1593
+ </button>
1594
+ @if (workspaceMenu()) {
1595
+ <div class="dz-menu-panel" role="menu">
1596
+ <span class="dz-menu-title">Panels</span>
1597
+ @for (panel of panelIds; track panel) {
1598
+ <div class="dz-menu-row">
1599
+ <span>{{ panelLabel(panel) }}</span>
1600
+ <span class="dz-seg">
1601
+ @for (side of dockSides; track side) {
1602
+ <button
1603
+ type="button"
1604
+ [class.is-on]="layout()[panel] === side"
1605
+ (click)="dock(panel, side)"
1606
+ >{{ sideLabels[side] }}</button>
1607
+ }
1608
+ </span>
1609
+ </div>
1610
+ }
1611
+ <span class="dz-menu-title">Presets</span>
1612
+ @for (preset of presets; track preset.id) {
1613
+ <button type="button" class="dz-menu-item" (click)="applyPreset(preset.id)">
1614
+ {{ preset.label }}
1615
+ </button>
1616
+ }
1617
+ <button type="button" class="dz-menu-item" (click)="resetLayout()">Reset layout</button>
1618
+ </div>
1619
+ }
1620
+ </div>
1174
1621
  <button type="button" (click)="dialog.set(dialog() === 'import' ? null : 'import')">Import</button>
1175
1622
  <button type="button" (click)="openExport()">Export</button>
1176
1623
  <button type="button" class="dz-primary" (click)="preview.set(!preview())">
@@ -1221,40 +1668,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1221
1668
  </details>
1222
1669
  </div>
1223
1670
  } @else {
1224
- <div class="dz-body">
1225
- <aside class="dz-palette">
1226
- <h4>Fields</h4>
1227
- @for (group of catalog(); track group.label) {
1228
- <div class="dz-group">
1229
- <span class="dz-group-label">{{ group.label }}</span>
1230
- <div class="dz-group-entries">
1231
- @for (entry of group.entries; track entry.type) {
1232
- <button type="button" class="dz-palette-item" (click)="add(entry)"
1233
- [title]="'Add ' + entry.label">
1234
- <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1235
- </button>
1236
- }
1237
- </div>
1238
- </div>
1239
- }
1240
- <h4>Structure</h4>
1241
- <div class="dz-tree" role="tree">
1242
- @for (item of tree(); track item.id) {
1243
- <button
1244
- type="button"
1245
- role="treeitem"
1246
- class="dz-tree-item"
1247
- [class.is-selected]="isSelectedPath(item.path)"
1248
- [style.padding-left.px]="8 + item.depth * 14"
1249
- (click)="select(item.path)"
1250
- >
1251
- {{ item.label }}
1252
- </button>
1253
- } @empty {
1254
- <span class="dz-empty">no fields yet</span>
1671
+ <div class="dz-body" [style.grid-template-columns]="bodyColumns()">
1672
+ @if (dockedPanels('left').length > 0) {
1673
+ <aside class="dz-dock dz-dock-left">
1674
+ @for (panel of dockedPanels('left'); track panel) {
1675
+ <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1255
1676
  }
1256
- </div>
1257
- </aside>
1677
+ </aside>
1678
+ }
1258
1679
 
1259
1680
  <main class="dz-canvas" (click)="select(null)">
1260
1681
  @for (node of draft().fields; track node.key; let i = $index) {
@@ -1272,21 +1693,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1272
1693
  }
1273
1694
  </main>
1274
1695
 
1275
- <aside class="dz-props">
1276
- @if (selectedNode(); as node) {
1277
- <mform-inspector
1278
- [node]="node"
1279
- [schema]="draft()"
1280
- (nodePatch)="applyPatch($event)"
1281
- />
1282
- } @else {
1283
- <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1284
- }
1285
- </aside>
1696
+ @if (dockedPanels('right').length > 0) {
1697
+ <aside class="dz-dock dz-dock-right">
1698
+ @for (panel of dockedPanels('right'); track panel) {
1699
+ <ng-container *ngTemplateOutlet="panelTemplate; context: { $implicit: panel }" />
1700
+ }
1701
+ </aside>
1702
+ }
1286
1703
  </div>
1287
1704
  }
1288
1705
  </div>
1289
- `, styles: [".dz-shell{display:flex;flex-direction:column;height:100%;min-height:480px;font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e);background:var(--mform-bg, #f6f5fa);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.dz-toolbar{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 12px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-tools{display:flex;align-items:center;gap:6px}.dz-toolbar button{font:inherit;font-size:13px;padding:6px 10px;border:1px solid transparent;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-toolbar button:hover:not(:disabled){background:var(--mform-bg, #f6f5fa)}.dz-toolbar button:disabled{opacity:.35;cursor:default}.dz-toolbar .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-sep{width:1px;height:18px;background:var(--mform-border, #d9d5e6)}.dz-diag-badge{color:#b45309;font-weight:600}.dz-diagnostics{max-height:140px;overflow-y:auto;padding:8px 14px;background:#fdf5ef;border-bottom:1px solid var(--mform-border, #d9d5e6);font-size:12px}.dz-diag{padding:2px 0;color:#9a3412}.dz-diag.is-warning{color:#92400e}.dz-diag code{font-weight:600;margin-right:6px}.dz-diag em{color:var(--mform-muted, #6b6580);margin-left:6px}.dz-dialog{padding:14px;background:var(--mform-surface, #fff);border-bottom:1px solid var(--mform-border, #d9d5e6)}.dz-dialog h4{margin:0 0 8px;font-size:13px}.dz-dialog textarea{width:100%;font-family:ui-monospace,Consolas,monospace;font-size:12px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;box-sizing:border-box;resize:vertical}.dz-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.dz-dialog-actions button{font:inherit;font-size:13px;padding:7px 14px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:none;cursor:pointer}.dz-dialog-actions .dz-primary{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-color:transparent}.dz-import-error{color:var(--mform-danger, #c62842);font-size:12px}.dz-body{display:grid;grid-template-columns:240px 1fr 280px;flex:1;min-height:0}.dz-palette{border-right:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);padding:12px;overflow-y:auto}.dz-palette h4{margin:4px 0 8px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--mform-muted, #6b6580)}.dz-group{margin-bottom:10px}.dz-group-label{font-size:11px;color:var(--mform-muted, #6b6580)}.dz-group-entries{display:flex;flex-direction:column;gap:2px;margin-top:4px}.dz-palette-item{display:flex;align-items:center;gap:8px;font:inherit;font-size:13px;text-align:left;padding:6px 8px;border:none;border-radius:8px;background:none;color:inherit;cursor:pointer}.dz-palette-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-icon{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px;font-size:11px}.dz-tree{display:flex;flex-direction:column;gap:1px}.dz-tree-item{font:inherit;font-size:12px;text-align:left;padding:4px 8px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dz-tree-item:hover{background:var(--mform-bg, #f6f5fa)}.dz-tree-item.is-selected{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.dz-canvas{padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:10px}.dz-canvas-empty,.dz-empty{color:var(--mform-muted, #6b6580);font-size:13px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:18px;text-align:center}.dz-node{position:relative;border:1px solid transparent;border-radius:var(--mform-radius, 10px);padding:20px 10px 10px;cursor:pointer;background:var(--mform-surface, #fff)}.dz-node:hover{border-color:var(--mform-border, #d9d5e6)}.dz-node.is-selected{border-color:var(--mform-primary, #6d4bd0);box-shadow:0 0 0 1px var(--mform-primary, #6d4bd0)}.dz-chip{position:absolute;top:2px;left:10px;font-size:10px;color:var(--mform-muted, #6b6580);pointer-events:none}.dz-node.is-selected>.dz-chip{color:var(--mform-primary, #6d4bd0);font-weight:600}.dz-children{display:flex;flex-direction:column;gap:8px}.dz-columns{display:grid;gap:10px}.dz-column{display:flex;flex-direction:column;gap:8px;border:1px dashed var(--mform-border, #d9d5e6);border-radius:8px;padding:8px;min-height:40px}.dz-tabbar{display:flex;gap:4px;margin-bottom:8px;border-bottom:2px solid var(--mform-border, #d9d5e6)}.dz-tab{font:inherit;font-size:12px;padding:5px 12px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.dz-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.dz-leaf{pointer-events:none}.dz-props{border-left:1px solid var(--mform-border, #d9d5e6);background:var(--mform-surface, #fff);overflow-y:auto}.dz-props-empty{margin:12px}.dz-inspector{padding:12px;display:flex;flex-direction:column;gap:10px}.dz-inspector h3{margin:0;font-size:13px}.dz-inspector h3 code{color:var(--mform-muted, #6b6580);font-weight:400}.dz-prop{display:flex;flex-direction:column;gap:4px;font-size:12px}.dz-prop span{color:var(--mform-muted, #6b6580)}.dz-prop input,.dz-prop select,.dz-prop textarea{font:inherit;font-size:13px;padding:7px 9px;border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;background:#fff;width:100%;box-sizing:border-box}.dz-check{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer}.dz-check input{accent-color:var(--mform-primary, #6d4bd0)}.dz-section{border-top:1px solid var(--mform-border, #d9d5e6);padding-top:8px}.dz-section summary{font-size:12px;font-weight:600;cursor:pointer;margin-bottom:8px}.dz-section .dz-prop{margin-bottom:8px}.dz-option-row{display:flex;gap:6px;margin-bottom:6px}.dz-option-row input{flex:1;min-width:0;font-size:12px;padding:6px 8px;border:1px solid var(--mform-border, #d9d5e6);border-radius:6px}.dz-option-row button{border:none;background:none;cursor:pointer;color:var(--mform-muted, #6b6580)}.dz-add{font:inherit;font-size:12px;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);background:none;border-radius:8px;padding:6px 10px;cursor:pointer}.dz-preview{padding:20px;overflow-y:auto}.dz-preview-data{margin-top:16px;font-size:12px}.dz-preview-data pre{background:var(--mform-surface, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:8px;padding:10px;overflow-x:auto}\n"] }]
1706
+
1707
+ <!-- One template per panel, rendered into whichever dock holds it. -->
1708
+ <ng-template #panelTemplate let-panel>
1709
+ <section class="dz-panel" [attr.data-panel]="panel">
1710
+ <header class="dz-panel-head">
1711
+ <span>{{ panelLabel(panel) }}</span>
1712
+ <span class="dz-panel-actions">
1713
+ <button
1714
+ type="button"
1715
+ [title]="panelSide(panel) === 'left' ? 'Move right' : 'Move left'"
1716
+ (click)="dock(panel, panelSide(panel) === 'left' ? 'right' : 'left')"
1717
+ >{{ panelSide(panel) === 'left' ? '⇥' : '⇤' }}</button>
1718
+ <button type="button" title="Hide panel" (click)="dock(panel, 'hidden')">✕</button>
1719
+ </span>
1720
+ </header>
1721
+ <div class="dz-panel-body">
1722
+ @switch (panel) {
1723
+ @case ('palette') {
1724
+ @for (group of catalog(); track group.label) {
1725
+ <div class="dz-group">
1726
+ <button
1727
+ type="button"
1728
+ class="dz-group-toggle"
1729
+ [class.is-open]="isGroupOpen(group.label)"
1730
+ [attr.aria-expanded]="isGroupOpen(group.label)"
1731
+ (click)="toggleGroup(group.label)"
1732
+ >
1733
+ <span class="dz-caret" aria-hidden="true">{{ isGroupOpen(group.label) ? '▾' : '▸' }}</span>
1734
+ {{ group.label }}
1735
+ <span class="dz-group-count">{{ group.entries.length }}</span>
1736
+ </button>
1737
+ @if (isGroupOpen(group.label)) {
1738
+ <div class="dz-group-entries">
1739
+ @for (entry of group.entries; track entry.type) {
1740
+ <button type="button" class="dz-palette-item" (click)="add(entry)"
1741
+ [title]="'Add ' + entry.label">
1742
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1743
+ </button>
1744
+ }
1745
+ </div>
1746
+ }
1747
+ </div>
1748
+ }
1749
+ }
1750
+ @case ('structure') {
1751
+ <div class="dz-tree" role="tree">
1752
+ @for (item of tree(); track item.id) {
1753
+ <button
1754
+ type="button"
1755
+ role="treeitem"
1756
+ class="dz-tree-item"
1757
+ [class.is-selected]="isSelectedPath(item.path)"
1758
+ [style.padding-left.px]="8 + item.depth * 14"
1759
+ (click)="select(item.path)"
1760
+ >
1761
+ {{ item.label }}
1762
+ </button>
1763
+ } @empty {
1764
+ <span class="dz-empty">no fields yet</span>
1765
+ }
1766
+ </div>
1767
+ }
1768
+ @case ('properties') {
1769
+ @if (selectedNode(); as node) {
1770
+ <mform-inspector
1771
+ [node]="node"
1772
+ [schema]="draft()"
1773
+ (nodePatch)="applyPatch($event)"
1774
+ />
1775
+ } @else {
1776
+ <div class="dz-empty dz-props-empty">Select a field to edit its properties.</div>
1777
+ }
1778
+ }
1779
+ }
1780
+ </div>
1781
+ </section>
1782
+ </ng-template>
1783
+ `, 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-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"] }]
1290
1784
  }], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], extraCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraCatalog", required: false }] }], schemaChanged: [{ type: i0.Output, args: ["schemaChanged"] }] } });
1291
1785
 
1292
1786
  /*
@@ -1301,5 +1795,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1301
1795
  * Generated bundle index. Do not edit.
1302
1796
  */
1303
1797
 
1304
- export { DEFAULT_CATALOG, DesignerNodeComponent, FormDesignerComponent, InspectorComponent };
1798
+ export { DEFAULT_CATALOG, DEFAULT_LAYOUT, DesignerNodeComponent, FormDesignerComponent, InspectorComponent, PANEL_LABELS, WORKSPACE_PRESETS };
1305
1799
  //# sourceMappingURL=mosaicoo-form-angular-designer.mjs.map