@mosaicoo/form-angular 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,6 +74,17 @@ contribute a palette entry; the Inspector edits its settings through the
74
74
  **Workspace** — panels (Fields, Structure, Properties) dock left or right or
75
75
  hide, with presets and per-user persistence, like a professional IDE.
76
76
 
77
+ **Drag and drop** — drag palette entries onto the canvas and reorder nodes by
78
+ dragging; drop zones highlight the landing position.
79
+
80
+ **Reusable blocks** — save any subtree as a named block and drop it into other
81
+ forms (keys are regenerated). Blocks persist locally by default; bind
82
+ `[blocks]` / `(blocksChanged)` to store them wherever your platform keeps
83
+ shared assets.
84
+
85
+ The canvas renders leaves with the RUNTIME components and the runtime theme,
86
+ so what you design is exactly what your users get.
87
+
77
88
  ## Material Design 3 adapter
78
89
 
79
90
  Optional entry point (requires `@angular/material`, an optional peer):
@@ -976,6 +976,16 @@ const DEFAULT_CATALOG = [
976
976
  ] };
977
977
  },
978
978
  },
979
+ {
980
+ type: 'accordion', label: 'Accordion', icon: '☰',
981
+ create: (s) => {
982
+ const key = generateKey(s, 'accordion');
983
+ return { kind: 'container', key, type: 'accordion', dataScope: 'inherit', children: [
984
+ { kind: 'container', key: `${key}-section-1`, type: 'panel', dataScope: 'inherit', label: 'Section 1', children: [] },
985
+ { kind: 'container', key: `${key}-section-2`, type: 'panel', dataScope: 'inherit', label: 'Section 2', children: [] },
986
+ ] };
987
+ },
988
+ },
979
989
  {
980
990
  type: 'group', label: 'Data group', icon: '⧈',
981
991
  create: (s) => ({ kind: 'container', key: generateKey(s, 'group'), type: 'group',
@@ -1005,6 +1015,28 @@ const DEFAULT_CATALOG = [
1005
1015
  },
1006
1016
  ];
1007
1017
 
1018
+ const STORAGE_KEY$1 = 'mform-designer-blocks';
1019
+ function loadBlocks(storage) {
1020
+ if (!storage)
1021
+ return [];
1022
+ try {
1023
+ const raw = storage.getItem(STORAGE_KEY$1);
1024
+ const parsed = raw ? JSON.parse(raw) : [];
1025
+ return Array.isArray(parsed) ? parsed : [];
1026
+ }
1027
+ catch {
1028
+ return [];
1029
+ }
1030
+ }
1031
+ function saveBlocks(storage, blocks) {
1032
+ try {
1033
+ storage?.setItem(STORAGE_KEY$1, JSON.stringify(blocks));
1034
+ }
1035
+ catch {
1036
+ /* storage unavailable — blocks stay in memory for this session */
1037
+ }
1038
+ }
1039
+
1008
1040
  /**
1009
1041
  * Designer workspace layout.
1010
1042
  *
@@ -1070,6 +1102,37 @@ function saveLayout(storage, layout) {
1070
1102
 
1071
1103
  const EMPTY_SCHEMA = { version: 1, fields: [] };
1072
1104
  const HISTORY_LIMIT = 50;
1105
+ /** True when `target` is `from` itself or somewhere inside it. */
1106
+ function isSameOrInside(from, target) {
1107
+ return from.every((segment, index) => target[index] === segment);
1108
+ }
1109
+ /**
1110
+ * A move removes before it inserts: when the source sits before the target
1111
+ * under the same parent, every index after it shifts down by one.
1112
+ */
1113
+ function adjustAfterRemoval(from, target) {
1114
+ const parentDepth = from.length - 1;
1115
+ const sameParent = from.slice(0, parentDepth).every((s, i) => target[i] === s);
1116
+ if (!sameParent || target.length < from.length)
1117
+ return target;
1118
+ const adjusted = [...target];
1119
+ const fromIndex = from[parentDepth];
1120
+ const targetIndex = adjusted[parentDepth];
1121
+ if (targetIndex > fromIndex)
1122
+ adjusted[parentDepth] = targetIndex - 1;
1123
+ return adjusted;
1124
+ }
1125
+ /** Deep copy of a block with fresh keys, so it can be inserted repeatedly. */
1126
+ function rekeyBlock(node, schema) {
1127
+ const copy = JSON.parse(JSON.stringify(node));
1128
+ const assign = (target) => {
1129
+ target.key = generateKey(schema, target.key);
1130
+ if (target.kind === 'container')
1131
+ target.children.forEach(assign);
1132
+ };
1133
+ assign(copy);
1134
+ return copy;
1135
+ }
1073
1136
  /**
1074
1137
  * `<mform-designer>` — visual authoring of form schemas.
1075
1138
  *
@@ -1160,7 +1223,28 @@ class FormDesignerComponent {
1160
1223
  return next;
1161
1224
  });
1162
1225
  }
1163
- catalog = computed(() => [...DEFAULT_CATALOG, ...this.extraCatalog()], ...(ngDevMode ? [{ debugName: "catalog" }] : /* istanbul ignore next */ []));
1226
+ /** Saved blocks (host-controlled when `[blocks]` is bound). */
1227
+ blocks = input$1(null, ...(ngDevMode ? [{ debugName: "blocks" }] : /* istanbul ignore next */ []));
1228
+ blocksChanged = output();
1229
+ localBlocks = signal([], ...(ngDevMode ? [{ debugName: "localBlocks" }] : /* istanbul ignore next */ []));
1230
+ effectiveBlocks = computed(() => this.blocks() ?? this.localBlocks(), ...(ngDevMode ? [{ debugName: "effectiveBlocks" }] : /* istanbul ignore next */ []));
1231
+ /** Palette = built-ins + host extras + saved blocks as a group. */
1232
+ catalog = computed(() => {
1233
+ const groups = [...DEFAULT_CATALOG, ...this.extraCatalog()];
1234
+ const blocks = this.effectiveBlocks();
1235
+ if (blocks.length > 0) {
1236
+ groups.push({
1237
+ label: 'Blocks',
1238
+ entries: blocks.map((block) => ({
1239
+ type: `block:${block.id}`,
1240
+ label: block.label,
1241
+ icon: '⊞',
1242
+ create: (schema) => rekeyBlock(block.node, schema),
1243
+ })),
1244
+ });
1245
+ }
1246
+ return groups;
1247
+ }, ...(ngDevMode ? [{ debugName: "catalog" }] : /* istanbul ignore next */ []));
1164
1248
  diagnostics = computed(() => validateSchema(this.draft()), ...(ngDevMode ? [{ debugName: "diagnostics" }] : /* istanbul ignore next */ []));
1165
1249
  exportedJson = computed(() => JSON.stringify(this.draft(), null, 2), ...(ngDevMode ? [{ debugName: "exportedJson" }] : /* istanbul ignore next */ []));
1166
1250
  /** Disposable engine driving the canvas leaves (defaults visible). */
@@ -1190,6 +1274,7 @@ class FormDesignerComponent {
1190
1274
  }, ...(ngDevMode ? [{ debugName: "tree" }] : /* istanbul ignore next */ []));
1191
1275
  constructor() {
1192
1276
  this.layout.set(loadLayout(this.storage));
1277
+ this.localBlocks.set(loadBlocks(this.storage));
1193
1278
  // First palette group starts expanded; the rest opens on demand.
1194
1279
  const first = this.catalog()[0]?.label;
1195
1280
  if (first)
@@ -1300,6 +1385,95 @@ class FormDesignerComponent {
1300
1385
  return;
1301
1386
  this.commit(updateNode(this.draft(), selected, event.patch));
1302
1387
  }
1388
+ // -- reusable blocks ------------------------------------------------------
1389
+ /** Saves the selected node (with its subtree) as a reusable block. */
1390
+ saveAsBlock() {
1391
+ const selected = this.selected();
1392
+ const node = selected ? getNodeAt(this.draft(), selected) : null;
1393
+ if (!node)
1394
+ return;
1395
+ const label = node.label ?? node.key;
1396
+ const block = {
1397
+ id: `${node.key}-${this.effectiveBlocks().length + 1}`,
1398
+ label,
1399
+ node: JSON.parse(JSON.stringify(node)),
1400
+ };
1401
+ const next = [...this.effectiveBlocks(), block];
1402
+ if (this.blocks() === null) {
1403
+ this.localBlocks.set(next);
1404
+ saveBlocks(this.storage, next);
1405
+ }
1406
+ this.blocksChanged.emit(next);
1407
+ }
1408
+ removeBlock(id) {
1409
+ const next = this.effectiveBlocks().filter((block) => block.id !== id);
1410
+ if (this.blocks() === null) {
1411
+ this.localBlocks.set(next);
1412
+ saveBlocks(this.storage, next);
1413
+ }
1414
+ this.blocksChanged.emit(next);
1415
+ }
1416
+ isBlockEntry(entry) {
1417
+ return entry.type.startsWith('block:');
1418
+ }
1419
+ blockIdOf(entry) {
1420
+ return entry.type.slice('block:'.length);
1421
+ }
1422
+ // -- drag and drop --------------------------------------------------------
1423
+ /** What is being dragged: a palette entry or an existing node. */
1424
+ dragging = signal(null, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
1425
+ /** Drop target highlighted while hovering. */
1426
+ dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : /* istanbul ignore next */ []));
1427
+ startEntryDrag(entry, event) {
1428
+ this.dragging.set({ kind: 'entry', entry });
1429
+ event.dataTransfer?.setData('text/plain', entry.type);
1430
+ if (event.dataTransfer)
1431
+ event.dataTransfer.effectAllowed = 'copy';
1432
+ }
1433
+ startNodeDrag(path, event) {
1434
+ this.dragging.set({ kind: 'node', path });
1435
+ event.dataTransfer?.setData('text/plain', path.join('.'));
1436
+ if (event.dataTransfer)
1437
+ event.dataTransfer.effectAllowed = 'move';
1438
+ }
1439
+ endDrag() {
1440
+ this.dragging.set(null);
1441
+ this.dropTarget.set(null);
1442
+ }
1443
+ allowDrop(target, event) {
1444
+ if (!this.dragging())
1445
+ return;
1446
+ event.preventDefault();
1447
+ event.stopPropagation();
1448
+ this.dropTarget.set(target.join('.'));
1449
+ }
1450
+ /** Drops onto `target`, the position the node should occupy. */
1451
+ drop(target, event) {
1452
+ event.preventDefault();
1453
+ event.stopPropagation();
1454
+ const dragging = this.dragging();
1455
+ this.endDrag();
1456
+ if (!dragging)
1457
+ return;
1458
+ if (dragging.kind === 'entry') {
1459
+ const node = dragging.entry.create(this.draft());
1460
+ this.commit(insertNode(this.draft(), target, node));
1461
+ this.select(target);
1462
+ return;
1463
+ }
1464
+ const from = dragging.path;
1465
+ if (isSameOrInside(from, target))
1466
+ return; // never drop a node into itself
1467
+ const adjusted = adjustAfterRemoval(from, target);
1468
+ this.commit(moveNode(this.draft(), from, adjusted));
1469
+ this.select(adjusted);
1470
+ }
1471
+ isDropTarget(path) {
1472
+ return this.dropTarget() === path.join('.');
1473
+ }
1474
+ isDragging() {
1475
+ return this.dragging() !== null;
1476
+ }
1303
1477
  // -- selection ------------------------------------------------------------
1304
1478
  select(path) {
1305
1479
  this.selected.set(path);
@@ -1348,7 +1522,7 @@ class FormDesignerComponent {
1348
1522
  }
1349
1523
  }
1350
1524
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1351
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.19", type: FormDesignerComponent, isStandalone: true, selector: "mform-designer", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, extraCatalog: { classPropertyName: "extraCatalog", publicName: "extraCatalog", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { schemaChanged: "schemaChanged" }, ngImport: i0, template: `
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: `
1352
1526
  <div class="dz-shell" (keydown)="onKeydown($event)">
1353
1527
  <header class="dz-toolbar">
1354
1528
  <div class="dz-tools">
@@ -1359,6 +1533,7 @@ class FormDesignerComponent {
1359
1533
  <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1360
1534
  <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1361
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>
1362
1537
  </div>
1363
1538
  <div class="dz-tools">
1364
1539
  @if (diagnostics().length > 0) {
@@ -1456,20 +1631,35 @@ class FormDesignerComponent {
1456
1631
  </aside>
1457
1632
  }
1458
1633
 
1459
- <main class="dz-canvas" (click)="select(null)">
1634
+ <main class="dz-canvas" [class.is-dragging]="isDragging()" (click)="select(null)">
1460
1635
  @for (node of draft().fields; track node.key; let i = $index) {
1636
+ <div
1637
+ class="dz-dropzone"
1638
+ [class.is-over]="isDropTarget([i])"
1639
+ (dragover)="allowDrop([i], $event)"
1640
+ (drop)="drop([i], $event)"
1641
+ ></div>
1461
1642
  <mform-designer-node
1462
1643
  [node]="node"
1463
1644
  [path]="[i]"
1464
1645
  [engine]="canvasEngine()"
1465
1646
  [selected]="selected()"
1466
1647
  (selectNode)="select($event)"
1648
+ draggable="true"
1649
+ (dragstart)="startNodeDrag([i], $event)"
1650
+ (dragend)="endDrag()"
1467
1651
  />
1468
1652
  } @empty {
1469
1653
  <div class="dz-canvas-empty">
1470
- Click a field in the palette to start building.
1654
+ Click a field in the palette — or drag it here — to start building.
1471
1655
  </div>
1472
1656
  }
1657
+ <div
1658
+ 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)"
1662
+ ></div>
1473
1663
  </main>
1474
1664
 
1475
1665
  @if (dockedPanels('right').length > 0) {
@@ -1516,10 +1706,27 @@ class FormDesignerComponent {
1516
1706
  @if (isGroupOpen(group.label)) {
1517
1707
  <div class="dz-group-entries">
1518
1708
  @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>
1709
+ <div class="dz-palette-row">
1710
+ <button
1711
+ type="button"
1712
+ class="dz-palette-item"
1713
+ draggable="true"
1714
+ (dragstart)="startEntryDrag(entry, $event)"
1715
+ (dragend)="endDrag()"
1716
+ (click)="add(entry)"
1717
+ [title]="'Add ' + entry.label + ' (or drag onto the canvas)'"
1718
+ >
1719
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1720
+ </button>
1721
+ @if (isBlockEntry(entry)) {
1722
+ <button
1723
+ type="button"
1724
+ class="dz-block-remove"
1725
+ (click)="removeBlock(blockIdOf(entry))"
1726
+ [attr.aria-label]="'Remove block ' + entry.label"
1727
+ >✕</button>
1728
+ }
1729
+ </div>
1523
1730
  }
1524
1731
  </div>
1525
1732
  }
@@ -1559,7 +1766,7 @@ class FormDesignerComponent {
1559
1766
  </div>
1560
1767
  </section>
1561
1768
  </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 });
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 });
1563
1770
  }
1564
1771
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormDesignerComponent, decorators: [{
1565
1772
  type: Component,
@@ -1580,6 +1787,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1580
1787
  <button type="button" (click)="moveSelected(1)" [disabled]="!selected()" title="Move down">↓</button>
1581
1788
  <button type="button" (click)="duplicateSelected()" [disabled]="!selected()" title="Duplicate (Ctrl+D)">⧉</button>
1582
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>
1583
1791
  </div>
1584
1792
  <div class="dz-tools">
1585
1793
  @if (diagnostics().length > 0) {
@@ -1677,20 +1885,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1677
1885
  </aside>
1678
1886
  }
1679
1887
 
1680
- <main class="dz-canvas" (click)="select(null)">
1888
+ <main class="dz-canvas" [class.is-dragging]="isDragging()" (click)="select(null)">
1681
1889
  @for (node of draft().fields; track node.key; let i = $index) {
1890
+ <div
1891
+ class="dz-dropzone"
1892
+ [class.is-over]="isDropTarget([i])"
1893
+ (dragover)="allowDrop([i], $event)"
1894
+ (drop)="drop([i], $event)"
1895
+ ></div>
1682
1896
  <mform-designer-node
1683
1897
  [node]="node"
1684
1898
  [path]="[i]"
1685
1899
  [engine]="canvasEngine()"
1686
1900
  [selected]="selected()"
1687
1901
  (selectNode)="select($event)"
1902
+ draggable="true"
1903
+ (dragstart)="startNodeDrag([i], $event)"
1904
+ (dragend)="endDrag()"
1688
1905
  />
1689
1906
  } @empty {
1690
1907
  <div class="dz-canvas-empty">
1691
- Click a field in the palette to start building.
1908
+ Click a field in the palette — or drag it here — to start building.
1692
1909
  </div>
1693
1910
  }
1911
+ <div
1912
+ 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)"
1916
+ ></div>
1694
1917
  </main>
1695
1918
 
1696
1919
  @if (dockedPanels('right').length > 0) {
@@ -1737,10 +1960,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1737
1960
  @if (isGroupOpen(group.label)) {
1738
1961
  <div class="dz-group-entries">
1739
1962
  @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>
1963
+ <div class="dz-palette-row">
1964
+ <button
1965
+ type="button"
1966
+ class="dz-palette-item"
1967
+ draggable="true"
1968
+ (dragstart)="startEntryDrag(entry, $event)"
1969
+ (dragend)="endDrag()"
1970
+ (click)="add(entry)"
1971
+ [title]="'Add ' + entry.label + ' (or drag onto the canvas)'"
1972
+ >
1973
+ <span class="dz-icon">{{ entry.icon }}</span>{{ entry.label }}
1974
+ </button>
1975
+ @if (isBlockEntry(entry)) {
1976
+ <button
1977
+ type="button"
1978
+ class="dz-block-remove"
1979
+ (click)="removeBlock(blockIdOf(entry))"
1980
+ [attr.aria-label]="'Remove block ' + entry.label"
1981
+ >✕</button>
1982
+ }
1983
+ </div>
1744
1984
  }
1745
1985
  </div>
1746
1986
  }
@@ -1780,8 +2020,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1780
2020
  </div>
1781
2021
  </section>
1782
2022
  </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"] }]
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"] }] } });
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"] }] } });
1785
2025
 
1786
2026
  /*
1787
2027
  * Designer entry point (`@mosaicoo/form-angular/designer`).