@vectoriox/iox-builder 1.4.19 → 1.4.21

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.
@@ -12,6 +12,7 @@ import { ButtonModule } from 'primeng/button';
12
12
  import * as i2$1 from 'primeng/popover';
13
13
  import { PopoverModule } from 'primeng/popover';
14
14
  import { InputTextModule } from 'primeng/inputtext';
15
+ import * as i4$1 from 'primeng/select';
15
16
  import { SelectModule } from 'primeng/select';
16
17
  import { TooltipModule } from 'primeng/tooltip';
17
18
  import { DialogModule } from 'primeng/dialog';
@@ -1087,6 +1088,14 @@ class InteractionEngineService {
1087
1088
  constructor(overlayService) {
1088
1089
  this.overlayService = overlayService;
1089
1090
  this.attached = new Map();
1091
+ /** Elements that have had pre-state applied (hidden by attach). */
1092
+ this.preStatedElements = new Set();
1093
+ /** Action types that animate an element from hidden → visible. */
1094
+ this.ENTRANCE_TYPES = new Set([
1095
+ 'fadeIn', 'moveUp', 'moveDown', 'moveLeft', 'moveRight', 'scaleIn', 'show',
1096
+ ]);
1097
+ /** Action types that animate an element from visible → hidden. */
1098
+ this.EXIT_TYPES = new Set(['fadeOut', 'scaleOut', 'hide']);
1090
1099
  }
1091
1100
  /** Wire all interactions for a node to its rendered DOM element. */
1092
1101
  attach(node) {
@@ -1097,11 +1106,12 @@ class InteractionEngineService {
1097
1106
  return;
1098
1107
  const cleanups = [];
1099
1108
  for (const ix of node.interactions) {
1100
- // For entrance-type triggers, freeze the target at the animation's
1101
- // starting frame immediately so it is never briefly visible before
1102
- // the trigger fires (avoids the flash-then-animate flicker).
1103
- if (ix.trigger === 'pageLoad' || ix.trigger === 'viewportEnter') {
1104
- for (const action of ix.actions) {
1109
+ // Apply pre-state to targets of ANY entrance animation so the element
1110
+ // starts hidden regardless of whether the trigger is automatic (pageLoad,
1111
+ // viewportEnter) or user-driven (click). Without this, a click-triggered
1112
+ // fadeIn on a menu overlay would start the overlay fully visible.
1113
+ for (const action of ix.actions) {
1114
+ if (this.ENTRANCE_TYPES.has(action.type)) {
1105
1115
  const target = this.resolveTarget(node, action);
1106
1116
  if (target)
1107
1117
  this.applyPreState(target, action);
@@ -1126,9 +1136,20 @@ class InteractionEngineService {
1126
1136
  const ref = this.overlayService.getNodeRef(node);
1127
1137
  if (ref) {
1128
1138
  ref.element.getAnimations().forEach(a => a.cancel());
1129
- ref.element.style.removeProperty('opacity');
1130
- ref.element.style.removeProperty('transform');
1131
- ref.element.style.removeProperty('visibility');
1139
+ this.clearInlineAnimationStyles(ref.element);
1140
+ }
1141
+ // Also clean up pre-state on any target elements this node was animating.
1142
+ if (node.interactions) {
1143
+ for (const ix of node.interactions) {
1144
+ for (const action of ix.actions) {
1145
+ const target = this.resolveTarget(node, action);
1146
+ if (target && this.preStatedElements.has(target)) {
1147
+ target.getAnimations().forEach(a => a.cancel());
1148
+ this.clearInlineAnimationStyles(target);
1149
+ this.preStatedElements.delete(target);
1150
+ }
1151
+ }
1152
+ }
1132
1153
  }
1133
1154
  }
1134
1155
  /** Re-attach interactions after they have been edited in the panel. */
@@ -1248,23 +1269,35 @@ class InteractionEngineService {
1248
1269
  if (!keyframes.length)
1249
1270
  return;
1250
1271
  // 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
1251
- // This eliminates any flicker during the delay period.
1252
- element.animate(keyframes, {
1272
+ const anim = element.animate(keyframes, {
1253
1273
  duration: action.duration,
1254
1274
  delay: action.delay,
1255
1275
  easing: action.easing,
1256
1276
  fill: 'both',
1257
1277
  });
1278
+ if (this.ENTRANCE_TYPES.has(action.type)) {
1279
+ // Once the entrance animation finishes the element is fully visible —
1280
+ // restore pointer events so it can be interacted with.
1281
+ anim.finished.then(() => {
1282
+ element.style.removeProperty('pointer-events');
1283
+ }).catch(() => { });
1284
+ }
1285
+ else if (this.EXIT_TYPES.has(action.type)) {
1286
+ // Once the exit animation finishes the element is invisible —
1287
+ // disable pointer events so it doesn't silently block clicks underneath it.
1288
+ anim.finished.then(() => {
1289
+ element.style.setProperty('pointer-events', 'none');
1290
+ }).catch(() => { });
1291
+ }
1258
1292
  }
1259
1293
  /**
1260
1294
  * Freeze an element at the animation's starting frame immediately, before any
1261
- * trigger fires. Without this, entrance animations (fadeIn, moveUp, …) show the
1262
- * element at its natural visible state for the time between mount and trigger —
1263
- * causing a visible-then-disappear-then-animate flicker.
1295
+ * trigger fires. Called for ALL entrance-type actions regardless of trigger so
1296
+ * that a click-triggered fadeIn on a menu overlay also starts it hidden.
1264
1297
  *
1265
- * Only call this for triggers that will eventually animate the element in
1266
- * (pageLoad, viewportEnter). Click/hover triggers must NOT pre-hide the element
1267
- * because the element should remain normally visible until interacted with.
1298
+ * Also sets pointer-events:none so the invisible element does not block clicks
1299
+ * on other content beneath it. Pointer events are restored in executeAction()
1300
+ * once the entrance animation finishes.
1268
1301
  */
1269
1302
  applyPreState(element, action) {
1270
1303
  const { keyframes } = this.buildAnimation(action);
@@ -1274,6 +1307,18 @@ class InteractionEngineService {
1274
1307
  for (const [prop, val] of Object.entries(first)) {
1275
1308
  element.style[prop] = String(val);
1276
1309
  }
1310
+ // Invisible element must not intercept pointer events.
1311
+ const opacity = first['opacity'];
1312
+ if (opacity === '0' || opacity === 0) {
1313
+ element.style.setProperty('pointer-events', 'none');
1314
+ }
1315
+ this.preStatedElements.add(element);
1316
+ }
1317
+ clearInlineAnimationStyles(element) {
1318
+ element.style.removeProperty('opacity');
1319
+ element.style.removeProperty('transform');
1320
+ element.style.removeProperty('visibility');
1321
+ element.style.removeProperty('pointer-events');
1277
1322
  }
1278
1323
  reverseAction(element, action) {
1279
1324
  const { keyframes } = this.buildAnimation(action);
@@ -2791,6 +2836,8 @@ class LayerTreeComponent {
2791
2836
  this.flatGlobalTree = [];
2792
2837
  this.selectedNodeId = null;
2793
2838
  this.hoveredNodeId = null;
2839
+ this.editingNodeId = null;
2840
+ this.editingValue = '';
2794
2841
  /** Cache: component type → icon class */
2795
2842
  this.iconMap = new Map();
2796
2843
  /** Last-seen structural signature — used by ngDoCheck to skip no-op rebuilds. */
@@ -2856,6 +2903,22 @@ class LayerTreeComponent {
2856
2903
  event.stopPropagation();
2857
2904
  this.nodeAction.emit({ action: NodeAction.Duplicate, node: treeNode.node });
2858
2905
  }
2906
+ startRename(treeNode, event) {
2907
+ event.stopPropagation();
2908
+ this.editingNodeId = treeNode.node.id ?? null;
2909
+ this.editingValue = treeNode.node.label ?? '';
2910
+ }
2911
+ saveRename(treeNode) {
2912
+ const val = this.editingValue.trim();
2913
+ treeNode.node.label = val || undefined;
2914
+ treeNode.label = treeNode.node.label ?? treeNode.node.type;
2915
+ this.editingNodeId = null;
2916
+ this.editingValue = '';
2917
+ }
2918
+ cancelRename() {
2919
+ this.editingNodeId = null;
2920
+ this.editingValue = '';
2921
+ }
2859
2922
  /**
2860
2923
  * CDK drop handler for tree reorder.
2861
2924
  * Works on the flat tree indices — maps back to parent arrays to do the move.
@@ -2919,7 +2982,7 @@ class LayerTreeComponent {
2919
2982
  const treeNode = {
2920
2983
  node,
2921
2984
  icon: this.iconMap.get(node.type) || 'ph-thin ph-cube',
2922
- label: node.type,
2985
+ label: node.label ?? node.type,
2923
2986
  depth,
2924
2987
  expanded,
2925
2988
  hasChildren,
@@ -2933,11 +2996,11 @@ class LayerTreeComponent {
2933
2996
  }
2934
2997
  }
2935
2998
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LayerTreeComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }], target: i0.ɵɵFactoryTarget.Component }); }
2936
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <h3 class=\"panel-title\">Layers</h3>\n </div>\n\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Drag handle indicator -->\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- Global Elements section -->\n <ng-container *ngIf=\"flatGlobalTree.length\">\n <div class=\"tree-section-header\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>Global Elements</span>\n </div>\n <div class=\"tree-body tree-body--global\">\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n </ng-container>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{padding:.75rem 1rem;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .tree-header .panel-title{margin:0;font-size:13px;font-weight:600;text-transform:capitalize}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
2999
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <h3 class=\"panel-title\">Layers</h3>\n </div>\n\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Drag handle indicator -->\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label / inline rename -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- Global Elements section -->\n <ng-container *ngIf=\"flatGlobalTree.length\">\n <div class=\"tree-section-header\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>Global Elements</span>\n </div>\n <div class=\"tree-body tree-body--global\">\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n </ng-container>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{padding:.75rem 1rem;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .tree-header .panel-title{margin:0;font-size:13px;font-weight:600;text-transform:capitalize}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
2937
3000
  }
2938
3001
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LayerTreeComponent, decorators: [{
2939
3002
  type: Component,
2940
- args: [{ selector: 'app-layer-tree', standalone: false, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <h3 class=\"panel-title\">Layers</h3>\n </div>\n\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Drag handle indicator -->\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- Global Elements section -->\n <ng-container *ngIf=\"flatGlobalTree.length\">\n <div class=\"tree-section-header\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>Global Elements</span>\n </div>\n <div class=\"tree-body tree-body--global\">\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n </ng-container>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{padding:.75rem 1rem;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .tree-header .panel-title{margin:0;font-size:13px;font-weight:600;text-transform:capitalize}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"] }]
3003
+ args: [{ selector: 'app-layer-tree', standalone: false, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <h3 class=\"panel-title\">Layers</h3>\n </div>\n\n <div class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Drag handle indicator -->\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label / inline rename -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- Global Elements section -->\n <ng-container *ngIf=\"flatGlobalTree.length\">\n <div class=\"tree-section-header\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>Global Elements</span>\n </div>\n <div class=\"tree-body tree-body--global\">\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <!-- Expand/collapse toggle -->\n <button *ngIf=\"tn.hasChildren\"\n class=\"expand-btn\"\n (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <!-- Icon + Label -->\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <!-- Actions (visible on hover) -->\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n </ng-container>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{padding:.75rem 1rem;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .tree-header .panel-title{margin:0;font-size:13px;font-weight:600;text-transform:capitalize}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"] }]
2941
3004
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }], propDecorators: { layout: [{
2942
3005
  type: Input
2943
3006
  }], globalElements: [{
@@ -4535,17 +4598,15 @@ class InteractionsPanelComponent {
4535
4598
  refreshNodeOptions() {
4536
4599
  const opts = [{ label: 'Self', value: 'self' }];
4537
4600
  for (const [node] of this.overlayService.getAllNodeEntries()) {
4538
- if (!node.id)
4601
+ if (!node.id || !node.label || node === this.node)
4539
4602
  continue;
4540
- const isCurrent = node === this.node;
4541
- const label = isCurrent
4542
- ? `Self (${node.type})`
4543
- : `${node.type} #${node.id.slice(-4)}`;
4544
- if (!isCurrent)
4545
- opts.push({ label, value: node.id });
4603
+ opts.push({ label: node.label, value: node.id });
4546
4604
  }
4547
4605
  this.nodeOptions = opts;
4548
4606
  }
4607
+ getTargetLabel(value) {
4608
+ return this.nodeOptions.find(o => o.value === value)?.label ?? value;
4609
+ }
4549
4610
  ngOnDestroy() {
4550
4611
  this.selectSub?.unsubscribe();
4551
4612
  }
@@ -4631,11 +4692,11 @@ class InteractionsPanelComponent {
4631
4692
  }
4632
4693
  }
4633
4694
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Component }); }
4634
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: InteractionsPanelComponent, isStandalone: false, selector: "app-interactions-panel", inputs: { node: "node" }, ngImport: i0, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">Action {{ ai + 1 }}</span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <select [(ngModel)]=\"action.target\">\n <option *ngFor=\"let n of nodeOptions\" [value]=\"n.value\">{{ n.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add action\n </button>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <select [(ngModel)]=\"newTarget\">\n <option *ngFor=\"let n of nodeOptions\" [value]=\"n.value\">{{ n.label }}</option>\n </select>\n </div>\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
4695
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: InteractionsPanelComponent, isStandalone: false, selector: "app-interactions-panel", inputs: { node: "node" }, ngImport: i0, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">Action {{ ai + 1 }}</span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add action\n </button>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
4635
4696
  }
4636
4697
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, decorators: [{
4637
4698
  type: Component,
4638
- args: [{ selector: 'app-interactions-panel', standalone: false, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">Action {{ ai + 1 }}</span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <select [(ngModel)]=\"action.target\">\n <option *ngFor=\"let n of nodeOptions\" [value]=\"n.value\">{{ n.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add action\n </button>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <select [(ngModel)]=\"newTarget\">\n <option *ngFor=\"let n of nodeOptions\" [value]=\"n.value\">{{ n.label }}</option>\n </select>\n </div>\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}\n"] }]
4699
+ args: [{ selector: 'app-interactions-panel', standalone: false, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">Action {{ ai + 1 }}</span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add action\n </button>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"] }]
4639
4700
  }], ctorParameters: () => [{ type: OverlayService }], propDecorators: { node: [{
4640
4701
  type: Input
4641
4702
  }] } });