@kubex/zinc 1.1.28 → 1.1.31

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.
Files changed (52) hide show
  1. package/custom-elements-manifest.config.js +2 -2
  2. package/dist/custom-elements.json +1910 -362
  3. package/dist/vscode.html-custom-data.json +55 -14
  4. package/dist/web-types.json +185 -32
  5. package/dist/zn.d.ts +684 -16
  6. package/dist/zn.min.js +1325 -929
  7. package/docs/pages/components/flow-builder-troubleshooter-demo.njk +106 -78
  8. package/docs/pages/components/flow-builder.md +422 -66
  9. package/docs/pages/components/page-builder.md +167 -0
  10. package/docs/pages/components/settings-container.md +37 -2
  11. package/package.json +1 -1
  12. package/src/components/datepicker/datepicker.scss +0 -10
  13. package/src/components/flow-builder/flow-builder.component.ts +377 -16
  14. package/src/components/flow-builder/flow-builder.scss +398 -250
  15. package/src/components/flow-builder/flow-builder.test.ts +241 -4
  16. package/src/components/flow-builder/flow-layout.ts +42 -17
  17. package/src/components/flow-builder/flow.types.ts +168 -43
  18. package/src/components/flow-builder/modules/flow-branch-conditions/flow-branch-conditions.component.ts +300 -0
  19. package/src/components/flow-builder/modules/flow-branch-conditions/flow-branch-conditions.scss +222 -0
  20. package/src/components/flow-builder/modules/flow-branch-conditions/flow-branch-conditions.test.ts +125 -0
  21. package/src/components/flow-builder/modules/flow-branch-conditions/index.ts +12 -0
  22. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +184 -26
  23. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +170 -120
  24. package/src/components/flow-builder/modules/flow-node/flow-node.scss +42 -42
  25. package/src/components/flow-builder/modules/flow-step/flow-step.component.ts +2 -1
  26. package/src/components/flow-builder/modules/flow-step/flow-step.scss +25 -17
  27. package/src/components/header/header.scss +3 -1
  28. package/src/components/icon-picker/icon-picker.component.ts +20 -37
  29. package/src/components/icon-picker/icon-picker.scss +5 -45
  30. package/src/components/page-builder/index.ts +14 -0
  31. package/src/components/page-builder/modules/page-palette-item/index.ts +12 -0
  32. package/src/components/page-builder/modules/page-palette-item/page-palette-item.component.ts +71 -0
  33. package/src/components/page-builder/modules/page-palette-item/page-palette-item.scss +70 -0
  34. package/src/components/page-builder/modules/page-palette-item/page-palette-item.test.ts +20 -0
  35. package/src/components/page-builder/modules/page-section-card/index.ts +12 -0
  36. package/src/components/page-builder/modules/page-section-card/page-section-card.component.ts +93 -0
  37. package/src/components/page-builder/modules/page-section-card/page-section-card.scss +92 -0
  38. package/src/components/page-builder/modules/page-section-card/page-section-card.test.ts +50 -0
  39. package/src/components/page-builder/page-builder.component.ts +1053 -0
  40. package/src/components/page-builder/page-builder.scss +494 -0
  41. package/src/components/page-builder/page-builder.test.ts +464 -0
  42. package/src/components/page-builder/page-registry.ts +48 -0
  43. package/src/components/page-builder/page.types.ts +75 -0
  44. package/src/components/panel/panel.scss +1 -0
  45. package/src/components/settings-container/settings-container.component.ts +74 -9
  46. package/src/components/settings-container/settings-container.scss +75 -0
  47. package/src/components/settings-container/settings-container.test.ts +35 -0
  48. package/src/events/events.ts +2 -0
  49. package/src/events/zn-page-change.ts +9 -0
  50. package/src/events/zn-page-selection-change.ts +7 -0
  51. package/src/zinc.ts +4 -0
  52. package/web-test-runner.config.js +6 -1
@@ -9,6 +9,7 @@ import ZnIcon from '../../../icon';
9
9
 
10
10
  import {
11
11
  branchDropXs,
12
+ branchPillTop,
12
13
  BUS_OFFSET,
13
14
  firstInputId,
14
15
  FLOW_TYPE_MIME,
@@ -27,7 +28,7 @@ import {
27
28
  NOTE_MIN_HEIGHT,
28
29
  NOTE_MIN_WIDTH,
29
30
  NOTE_WIDTH,
30
- PILL_DROP,
31
+ pillsCollide,
31
32
  pillSize,
32
33
  portAnchor,
33
34
  snapToGrid,
@@ -46,6 +47,10 @@ const ADD_POINT_OFFSET = 40;
46
47
  // whole grid units so wire runs sit on grid lines.
47
48
  const WIRE_STUB = 20;
48
49
  const WIRE_APPROACH = 20;
50
+ // Half the wire "+"'s footprint.
51
+ const WIRE_PLUS_RADIUS = 10;
52
+ // How far a node's input port circle pokes up into the wire's last segment.
53
+ const WIRE_PORT_POKE = 8;
49
54
  const WIRE_DETOUR = 40;
50
55
  // Vertical spacing between the horizontal runs of wires heading to different
51
56
  // targets, so unrelated wires never share a line (which reads as a join).
@@ -54,7 +59,7 @@ const TYPE_MIME = FLOW_TYPE_MIME;
54
59
 
55
60
  type DragState =
56
61
  | { kind: 'pan'; ox: number; oy: number; px: number; py: number }
57
- | { kind: 'node'; id: string; offX: number; offY: number }
62
+ | { kind: 'node'; id: string; offX: number; offY: number; startX: number; startY: number }
58
63
  | { kind: 'note'; id: string; offX: number; offY: number }
59
64
  | { kind: 'note-resize'; id: string };
60
65
 
@@ -86,6 +91,7 @@ type DragState =
86
91
  *
87
92
  * @csspart base - The canvas viewport.
88
93
  * @csspart toolbar - The floating toolbar.
94
+ * @csspart bin - The delete drop-zone shown bottom-right while dragging a node or step.
89
95
  */
90
96
  export default class ZnFlowCanvas extends ZincElement {
91
97
  static styles: CSSResultGroup = unsafeCSS(styles);
@@ -113,6 +119,8 @@ export default class ZnFlowCanvas extends ZincElement {
113
119
  @state() private panX = 0;
114
120
  @state() private panY = 0;
115
121
  @state() private drag: DragState | null = null;
122
+ /** Whether the current drag (node or step) is over the delete bin. */
123
+ @state() private _overBin = false;
116
124
  /** Canvas-space top-left where a step, if dropped now, would be placed. */
117
125
  @state() private _dropGhost: { x: number; y: number } | null = null;
118
126
  /** The stray branch being drawn from an output port until it attaches or cancels. */
@@ -273,7 +281,7 @@ export default class ZnFlowCanvas extends ZincElement {
273
281
  const node = this.nodes.find(n => n.id === e.detail.nodeId);
274
282
  if (!node) return;
275
283
  const p = this.screenToCanvas(e.detail.clientX, e.detail.clientY);
276
- this.drag = {kind: 'node', id: node.id, offX: p.x - node.x, offY: p.y - node.y};
284
+ this.drag = {kind: 'node', id: node.id, offX: p.x - node.x, offY: p.y - node.y, startX: node.x, startY: node.y};
277
285
  this._dragMoved = false;
278
286
  this._setupWindow('grabbing');
279
287
  };
@@ -307,6 +315,23 @@ export default class ZnFlowCanvas extends ZincElement {
307
315
  this._startLink(nodeId, open?.id ?? NEW_OUTPUT_PORT);
308
316
  };
309
317
 
318
+ /**
319
+ * An orphaned branch pill's port was clicked. If a branch from another node
320
+ * is already in flight, attach it to this pill's node; otherwise start a
321
+ * stray branch that continues from this pill.
322
+ */
323
+ private _onBranchPortClick(nodeId: string, port: string) {
324
+ if (this._linking) {
325
+ const link = this._linking;
326
+ this._cancelLink();
327
+ if (link.nodeId !== nodeId) {
328
+ this._emit('flow-link-assign', {nodeId: link.nodeId, port: link.port, targetId: nodeId});
329
+ }
330
+ return;
331
+ }
332
+ this._startLink(nodeId, port);
333
+ }
334
+
310
335
  private _startLink(nodeId: string, port: string) {
311
336
  this._linking = {nodeId, port};
312
337
  this._linkPos = null;
@@ -398,17 +423,31 @@ export default class ZnFlowCanvas extends ZincElement {
398
423
  }
399
424
  const p = this.screenToCanvas(e.clientX, e.clientY);
400
425
  if (this.drag.kind === 'node') {
426
+ this._overBin = this._binHit(e.clientX, e.clientY);
401
427
  const node = this.nodes.find(n => n.id === (this.drag as { id: string }).id);
402
428
  if (node) {
403
429
  // Snap to the grid, and refuse positions where any part of the node's
404
430
  // footprint (card or branch pills) would overlap another's — falling
405
- // back to one axis at a time so the node slides along edges.
431
+ // back to one axis at a time so the node slides along edges. The
432
+ // candidate position substitutes into the node list, because moving a
433
+ // node also moves its parent's pill (it centres between the two): the
434
+ // pills must be checked where they WOULD be, not where they were.
406
435
  const desired = {x: snapToGrid(p.x - this.drag.offX), y: snapToGrid(p.y - this.drag.offY)};
407
- const others = this.nodes.filter(o => o.id !== node.id);
436
+ const parents = new Set(
437
+ this.connections.filter(c => c.target.node === node.id && c.source.node !== node.id).map(c => c.source.node));
408
438
  const fits = (pos: { x: number; y: number }) => {
409
439
  const moved = {...node, ...pos};
410
- return !others.some(o =>
411
- nodesCollide(moved, o, t => this.registry?.get(t), this.nodes, this.connections));
440
+ const placed = this.nodes.map(n => (n.id === node.id ? moved : n));
441
+ const typeOf = (t: string) => this.registry?.get(t);
442
+ if (placed.some(o => o !== moved && nodesCollide(moved, o, typeOf, placed, this.connections))) {
443
+ return false;
444
+ }
445
+ // The parents' pills re-centre with this move — they must land clear
446
+ // of everything too (a pair that doesn't involve the moved node).
447
+ return ![...parents].some(pid => {
448
+ const parent = placed.find(n => n.id === pid);
449
+ return !!parent && pillsCollide(parent, typeOf, placed, this.connections);
450
+ });
412
451
  };
413
452
  const next = fits(desired) ? desired
414
453
  : fits({x: desired.x, y: node.y}) ? {x: desired.x, y: node.y}
@@ -442,15 +481,35 @@ export default class ZnFlowCanvas extends ZincElement {
442
481
 
443
482
  private _onPointerUp = () => {
444
483
  const drag = this.drag;
484
+ const overBin = this._overBin;
445
485
  this.drag = null;
486
+ this._overBin = false;
446
487
  this._teardownWindow();
447
488
  if (!drag) return;
448
489
 
490
+ // Dropped on the bin: put the node back where the drag started (so the
491
+ // delete's undo restores the pre-drag layout) and delete it.
492
+ if (drag.kind === 'node' && this._dragMoved && overBin) {
493
+ const node = this.nodes.find(n => n.id === drag.id);
494
+ if (node) {
495
+ node.x = drag.startX;
496
+ node.y = drag.startY;
497
+ }
498
+ this._emit('flow-node-action', {nodeId: drag.id, action: 'delete'});
499
+ return;
500
+ }
501
+
449
502
  if ((drag.kind === 'node' || drag.kind === 'note' || drag.kind === 'note-resize') && this._dragMoved) {
450
503
  this._emit('flow-change-commit');
451
504
  }
452
505
  };
453
506
 
507
+ /** Whether a client-space point is over the delete bin. */
508
+ private _binHit(clientX: number, clientY: number): boolean {
509
+ const rect = this.shadowRoot?.querySelector('.bin')?.getBoundingClientRect();
510
+ return !!rect && clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
511
+ }
512
+
454
513
  private _beginMove() {
455
514
  this._dragMoved = true;
456
515
  this._emit('flow-interaction-start');
@@ -469,17 +528,20 @@ export default class ZnFlowCanvas extends ZincElement {
469
528
  const cx = node.x + NODE_WIDTH / 2;
470
529
  const by = node.y + NODE_HEIGHT;
471
530
  const busY = by + BUS_OFFSET;
472
- // Drop positions prefer a straight line above each branch's child.
473
- const dropXs = branchDropXs(node, t => this.registry?.get(t), this.nodes, this.connections);
531
+ // Drops always sit at the natural fan position under the source, so the
532
+ // arrow into a pill is a straight vertical; the wire below the pill takes
533
+ // up any lateral offset to the child.
534
+ const dropXs = branchDropXs(node, t => this.registry?.get(t));
474
535
  const branches = outputs.map((port, i) => {
475
536
  const conn = this.connections.find(c => c.source.node === node.id && c.source.port === port.id);
476
537
  const child = conn ? this.nodes.find(n => n.id === conn.target.node) : undefined;
477
538
  const x = dropXs[i];
478
- // Labelled outputs show their branch pill on the drop; the wire continues
479
- // from below it. Long names wrap, so the pill height (and the exit where
480
- // the +/child wire begins) grows with the text.
481
- const pillTop = port.label ? busY + PILL_DROP : null;
539
+ // Labelled outputs show their branch pill on the drop, centred along the
540
+ // wire when a child sits below; the wire continues from below it. Long
541
+ // names wrap, so the pill height (and the exit where the +/child wire
542
+ // begins) grows with the text.
482
543
  const pillH = port.label ? pillSize(port.label).h : 0;
544
+ const pillTop = port.label ? branchPillTop(node, pillH, child, outputs.length === 1) : null;
483
545
  const exitY = pillTop === null ? busY : pillTop + pillH;
484
546
  return {port, conn, child, x, pillTop, pillH, exitY};
485
547
  });
@@ -527,9 +589,15 @@ export default class ZnFlowCanvas extends ZincElement {
527
589
  return offsets;
528
590
  }
529
591
 
530
- /** Whether an orthogonal segment passes through any node card (with margin). */
592
+ /**
593
+ * Whether an orthogonal segment passes through any node card (with margin) —
594
+ * or the approach zone above one, where incoming arrows land. A foreign wire
595
+ * running just over a card's input port reads as connecting to it, so routes
596
+ * keep well clear of that strip too.
597
+ */
531
598
  private _segmentBlocked(a: { x: number; y: number }, b: { x: number; y: number }, ignore: Set<string>): boolean {
532
599
  const M = 8;
600
+ const APPROACH_ZONE = 48;
533
601
  const minX = Math.min(a.x, b.x);
534
602
  const maxX = Math.max(a.x, b.x);
535
603
  const minY = Math.min(a.y, b.y);
@@ -537,7 +605,7 @@ export default class ZnFlowCanvas extends ZincElement {
537
605
  return this.nodes.some(n =>
538
606
  !ignore.has(n.id)
539
607
  && minX < n.x + NODE_WIDTH + M && maxX > n.x - M
540
- && minY < n.y + NODE_HEIGHT + M && maxY > n.y - M
608
+ && minY < n.y + NODE_HEIGHT + M && maxY > n.y - APPROACH_ZONE
541
609
  );
542
610
  }
543
611
 
@@ -688,7 +756,7 @@ export default class ZnFlowCanvas extends ZincElement {
688
756
  if (this.movingNodeId) return [];
689
757
  const midOffsets = this._elbowMidOffsets();
690
758
  const loops = loopConnections(this.nodes, this.connections);
691
- const points: { connectionId: string; px: number; py: number }[] = [];
759
+ const candidates: { connectionId: string; at: (d: number) => { px: number; py: number }; d: number; len: number }[] = [];
692
760
  for (const node of this.nodes) {
693
761
  const {branches} = this._outputLayout(node);
694
762
  branches.forEach(b => {
@@ -707,13 +775,47 @@ export default class ZnFlowCanvas extends ZincElement {
707
775
  seg = i;
708
776
  }
709
777
  }
710
- points.push({
711
- connectionId: b.conn.id,
712
- px: (pts[seg].x + pts[seg + 1].x) / 2,
713
- py: (pts[seg].y + pts[seg + 1].y) / 2,
714
- });
778
+ // The "+" prefers the segment's free run centre — the child's input
779
+ // port circle pokes into the last segment's end, so the "+" splits the
780
+ // pill-to-port gap evenly however tight it is.
781
+ const a = pts[seg];
782
+ const c = pts[seg + 1];
783
+ const len = Math.abs(c.x - a.x) + Math.abs(c.y - a.y);
784
+ const dx = Math.sign(c.x - a.x);
785
+ const dy = Math.sign(c.y - a.y);
786
+ const at = (d: number) => ({px: a.x + dx * d, py: a.y + dy * d});
787
+ const d = Math.max(0, (seg === pts.length - 2 ? len - WIRE_PORT_POKE : len) / 2);
788
+ candidates.push({connectionId: b.conn.id, at, d, len});
715
789
  });
716
790
  }
791
+
792
+ // Place each "+" — always rendered, sliding along its own wire away from
793
+ // node cards and from "+"s already placed, so wires sharing a corridor
794
+ // never stack their buttons on top of each other.
795
+ const M = WIRE_PLUS_RADIUS;
796
+ const onCard = (px: number, py: number) => this.nodes.some(n =>
797
+ px > n.x - M && px < n.x + NODE_WIDTH + M && py > n.y - M && py < n.y + NODE_HEIGHT + M);
798
+ const points: { connectionId: string; px: number; py: number }[] = [];
799
+ for (const cand of candidates) {
800
+ const clash = (p: { px: number; py: number }) =>
801
+ onCard(p.px, p.py) || points.some(f => Math.hypot(f.px - p.px, f.py - p.py) < WIRE_PLUS_RADIUS * 2 + 6);
802
+ let spot = cand.at(cand.d);
803
+ if (clash(spot)) {
804
+ // eslint-disable-next-line no-labels
805
+ outer: for (let step = GRID_SIZE / 2; step <= cand.len; step += GRID_SIZE / 2) {
806
+ for (const dd of [cand.d - step, cand.d + step]) {
807
+ if (dd < 0 || dd > cand.len) continue;
808
+ const p = cand.at(dd);
809
+ if (!clash(p)) {
810
+ spot = p;
811
+ // eslint-disable-next-line no-labels
812
+ break outer;
813
+ }
814
+ }
815
+ }
816
+ }
817
+ points.push({connectionId: cand.connectionId, ...spot});
818
+ }
717
819
  return points;
718
820
  }
719
821
 
@@ -788,6 +890,31 @@ export default class ZnFlowCanvas extends ZincElement {
788
890
  this._emit('flow-wire-assign', {connectionId, type});
789
891
  }
790
892
 
893
+ // --- Delete bin ------------------------------------------------------------
894
+ // Shown while a node is dragged (drop it there to delete) or while a step is
895
+ // dragged in from the panel (drop it there to discard instead of placing).
896
+
897
+ private _onBinDragOver = (e: DragEvent) => {
898
+ if (!e.dataTransfer?.types.includes(TYPE_MIME)) return;
899
+ e.preventDefault();
900
+ e.stopPropagation();
901
+ e.dataTransfer.dropEffect = 'move';
902
+ this._overBin = true;
903
+ this._dropGhost = null; // nothing will be placed from here
904
+ };
905
+
906
+ private _onBinDragLeave = () => {
907
+ this._overBin = false;
908
+ };
909
+
910
+ private _onBinDrop = (e: DragEvent) => {
911
+ // Swallow the drop — the step is discarded, never reaching the canvas.
912
+ e.preventDefault();
913
+ e.stopPropagation();
914
+ this._overBin = false;
915
+ this._dropGhost = null;
916
+ };
917
+
791
918
  // --- Toolbar ----------------------------------------------------------------
792
919
 
793
920
  private _zoomBy(delta: number) {
@@ -882,7 +1009,9 @@ export default class ZnFlowCanvas extends ZincElement {
882
1009
  // (arrowhead), then continue from the pill to the child / open "+".
883
1010
  active.forEach(b => {
884
1011
  if (b.pillTop !== null) {
885
- paths.push(svg`<path class="wire" d="M ${b.x} ${busY} L ${b.x} ${b.pillTop}" marker-end="url(#flow-arrow)"></path>`);
1012
+ // Start no lower than 10 above the pill, so the arrowhead keeps a
1013
+ // tail even when a tight gap pins the pill right up against the bus.
1014
+ paths.push(svg`<path class="wire" d="M ${b.x} ${Math.min(busY, b.pillTop - 10)} L ${b.x} ${b.pillTop}" marker-end="url(#flow-arrow)"></path>`);
886
1015
  }
887
1016
  if (b.conn && b.child) {
888
1017
  const offset = midOffsets.get(b.conn.id) ?? 0;
@@ -900,12 +1029,17 @@ export default class ZnFlowCanvas extends ZincElement {
900
1029
  });
901
1030
  }
902
1031
 
903
- // The in-progress branch follows the cursor from the node's bottom port,
904
- // snapping onto the hovered target's input until it attaches or cancels.
1032
+ // The in-progress branch follows the cursor from the node's bottom port
1033
+ // or from the branch's pill when it has one (an orphaned branch continues
1034
+ // from its pill) — snapping onto the hovered target's input until it
1035
+ // attaches or cancels.
905
1036
  if (this._linking && this._linkPos) {
906
1037
  const src = this.nodes.find(n => n.id === this._linking!.nodeId);
907
1038
  if (src) {
908
- const from = {x: src.x + NODE_WIDTH / 2, y: src.y + NODE_HEIGHT};
1039
+ const branch = this._outputLayout(src).branches.find(b => b.port.id === this._linking!.port);
1040
+ const from = branch && branch.pillTop !== null
1041
+ ? {x: branch.x, y: branch.exitY}
1042
+ : {x: src.x + NODE_WIDTH / 2, y: src.y + NODE_HEIGHT};
909
1043
  const target = this._linkTarget ? this.nodes.find(n => n.id === this._linkTarget) : undefined;
910
1044
  const end = target
911
1045
  ? this._inputAnchor(target, firstInputId(target, this._typeFor(target)) ?? '')
@@ -940,7 +1074,8 @@ export default class ZnFlowCanvas extends ZincElement {
940
1074
  private _renderBranchPills() {
941
1075
  const loops = loopConnections(this.nodes, this.connections);
942
1076
  const items: {
943
- key: string; nodeId: string; portId: string; label: string; x: number; top: number; height: number; loop: boolean;
1077
+ key: string; nodeId: string; portId: string; label: string; x: number; top: number; height: number;
1078
+ loop: boolean; open: boolean;
944
1079
  }[] = [];
945
1080
  for (const node of this.nodes) {
946
1081
  const {branches} = this._outputLayout(node);
@@ -955,6 +1090,7 @@ export default class ZnFlowCanvas extends ZincElement {
955
1090
  top: b.pillTop,
956
1091
  height: b.pillH,
957
1092
  loop: !!b.conn && loops.has(b.conn),
1093
+ open: !b.conn,
958
1094
  });
959
1095
  }
960
1096
  }
@@ -986,6 +1122,16 @@ export default class ZnFlowCanvas extends ZincElement {
986
1122
  >
987
1123
  <zn-icon src="x@lu" size="14"></zn-icon>
988
1124
  </button>
1125
+ ${i.open ? html`
1126
+ <span
1127
+ class="branch-pill-port"
1128
+ title="Start a branch"
1129
+ @pointerdown="${(e: PointerEvent) => e.button === 0 && e.stopPropagation()}"
1130
+ @click="${(e: Event) => {
1131
+ e.stopPropagation();
1132
+ this._onBranchPortClick(i.nodeId, i.portId);
1133
+ }}"
1134
+ ></span>` : ''}
989
1135
  </div>
990
1136
  `
991
1137
  );
@@ -1150,6 +1296,18 @@ export default class ZnFlowCanvas extends ZincElement {
1150
1296
  <zn-button icon="minus@lu" icon-size="18" icon-button="small" plain
1151
1297
  @click="${() => this._zoomBy(-ZOOM_STEP)}"></zn-button>
1152
1298
  </div>
1299
+
1300
+ ${(this.drag?.kind === 'node' && this._dragMoved) || this.dragType ? html`
1301
+ <div
1302
+ part="bin"
1303
+ class="bin ${this._overBin ? 'bin--over' : ''}"
1304
+ title="Drop here to delete"
1305
+ @dragover="${this._onBinDragOver}"
1306
+ @dragleave="${this._onBinDragLeave}"
1307
+ @drop="${this._onBinDrop}"
1308
+ >
1309
+ <zn-icon src="trash-2@lu" size="22"></zn-icon>
1310
+ </div>` : ''}
1153
1311
  </div>
1154
1312
  `;
1155
1313
  }