@vectoriox/iox-builder 1.0.3 → 1.0.5

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.
@@ -107,6 +107,8 @@ var ToolbarAction;
107
107
  ToolbarAction["Delete"] = "delete";
108
108
  ToolbarAction["Play"] = "play";
109
109
  ToolbarAction["SaveAsBlock"] = "save-as-block";
110
+ ToolbarAction["MakeGlobal"] = "make-global";
111
+ ToolbarAction["RemoveGlobal"] = "remove-global";
110
112
  })(ToolbarAction || (ToolbarAction = {}));
111
113
  var NodeAction;
112
114
  (function (NodeAction) {
@@ -1638,6 +1640,18 @@ class OverlayComponent {
1638
1640
  if (this.selectEntry)
1639
1641
  this.toolbarAction.emit({ action: ToolbarAction.SaveAsBlock, entry: this.selectEntry });
1640
1642
  }
1643
+ onMakeGlobal(event, position) {
1644
+ event.stopPropagation();
1645
+ this.closeMoreMenu();
1646
+ if (this.selectEntry)
1647
+ this.toolbarAction.emit({ action: ToolbarAction.MakeGlobal, entry: this.selectEntry, position });
1648
+ }
1649
+ onRemoveGlobal(event) {
1650
+ event.stopPropagation();
1651
+ this.closeMoreMenu();
1652
+ if (this.selectEntry)
1653
+ this.toolbarAction.emit({ action: ToolbarAction.RemoveGlobal, entry: this.selectEntry });
1654
+ }
1641
1655
  closeContextMenu() {
1642
1656
  this.contextMenuVisible = false;
1643
1657
  this.cdr.markForCheck();
@@ -1645,6 +1659,9 @@ class OverlayComponent {
1645
1659
  get hasInteractions() {
1646
1660
  return !!this.selectEntry?.node?.interactions?.length;
1647
1661
  }
1662
+ get selectedNodeIsGlobal() {
1663
+ return !!this.selectEntry?.node?.isGlobal;
1664
+ }
1648
1665
  observeSelectElement() {
1649
1666
  this.resizeObserver?.disconnect();
1650
1667
  this.mutationObserver?.disconnect();
@@ -1753,11 +1770,11 @@ class OverlayComponent {
1753
1770
  return `polygon(evenodd, 0px 0px, ${w}px 0px, ${w}px ${h}px, 0px ${h}px, 0px 0px, ${x1}px ${y1}px, ${x2}px ${y1}px, ${x2}px ${y2}px, ${x1}px ${y2}px, ${x1}px ${y1}px)`;
1754
1771
  }
1755
1772
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OverlayComponent, deps: [{ token: OverlayService }, { token: ViewportService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1756
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: OverlayComponent, isStandalone: false, selector: "app-overlay", outputs: { toolbarAction: "toolbarAction" }, ngImport: i0, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\"\n (contextmenu)=\"onContextMenu($event)\">\n\n <div class=\"select-toolbar\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 last -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n\n <!-- More dropdown (inline, positioned from toolbar) -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" (click)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n </div>\n </div>\n </div>\n\n <!-- Backdrop to close more menu or context menu -->\n <div *ngIf=\"moreMenuVisible || contextMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu(); closeContextMenu()\">\n </div>\n\n <!-- Right-click context menu -->\n <div *ngIf=\"contextMenuVisible\" class=\"overlay-context-menu\"\n [ngStyle]=\"contextMenuStyle\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"context-menu-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n </div>\n\n</div>\n", styles: [":host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%);display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.select-toolbar .toolbar-more-dropdown{position:absolute;top:100%;right:0;left:auto;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all;margin-top:2px}.select-toolbar .toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left;white-space:nowrap}.select-toolbar .toolbar-more-dropdown .more-item i{font-size:14px}.select-toolbar .toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.select-toolbar .toolbar-more-dropdown .more-item--danger{color:#c84040}.select-toolbar .toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.select-toolbar .toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.select-toolbar .toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}.overlay-context-menu{position:fixed;z-index:999;background:#fff;border:1px solid var(--p-surface-200);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.overlay-context-menu .context-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left}.overlay-context-menu .context-menu-item i{font-size:14px}.overlay-context-menu .context-menu-item:hover{background:var(--p-surface-100, #f5f5f5)}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1773
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: OverlayComponent, isStandalone: false, selector: "app-overlay", outputs: { toolbarAction: "toolbarAction" }, ngImport: i0, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\"\n (contextmenu)=\"onContextMenu($event)\">\n\n <div class=\"select-toolbar\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 last -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n\n <!-- More dropdown (inline, positioned from toolbar) -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" (click)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n </div>\n </div>\n\n <!-- Backdrop to close more menu or context menu -->\n <div *ngIf=\"moreMenuVisible || contextMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu(); closeContextMenu()\">\n </div>\n\n <!-- Right-click context menu -->\n <div *ngIf=\"contextMenuVisible\" class=\"overlay-context-menu\"\n [ngStyle]=\"contextMenuStyle\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"context-menu-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n </div>\n\n</div>\n", styles: [":host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%);display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.select-toolbar .toolbar-more-dropdown{position:absolute;top:100%;right:0;left:auto;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all;margin-top:2px}.select-toolbar .toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left;white-space:nowrap}.select-toolbar .toolbar-more-dropdown .more-item i{font-size:14px}.select-toolbar .toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.select-toolbar .toolbar-more-dropdown .more-item--danger{color:#c84040}.select-toolbar .toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.select-toolbar .toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.select-toolbar .toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}.overlay-context-menu{position:fixed;z-index:999;background:#fff;border:1px solid var(--p-surface-200);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.overlay-context-menu .context-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left}.overlay-context-menu .context-menu-item i{font-size:14px}.overlay-context-menu .context-menu-item:hover{background:var(--p-surface-100, #f5f5f5)}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1757
1774
  }
1758
1775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OverlayComponent, decorators: [{
1759
1776
  type: Component,
1760
- args: [{ selector: 'app-overlay', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\"\n (contextmenu)=\"onContextMenu($event)\">\n\n <div class=\"select-toolbar\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 last -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n\n <!-- More dropdown (inline, positioned from toolbar) -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" (click)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n </div>\n </div>\n </div>\n\n <!-- Backdrop to close more menu or context menu -->\n <div *ngIf=\"moreMenuVisible || contextMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu(); closeContextMenu()\">\n </div>\n\n <!-- Right-click context menu -->\n <div *ngIf=\"contextMenuVisible\" class=\"overlay-context-menu\"\n [ngStyle]=\"contextMenuStyle\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"context-menu-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n </div>\n\n</div>\n", styles: [":host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%);display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.select-toolbar .toolbar-more-dropdown{position:absolute;top:100%;right:0;left:auto;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all;margin-top:2px}.select-toolbar .toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left;white-space:nowrap}.select-toolbar .toolbar-more-dropdown .more-item i{font-size:14px}.select-toolbar .toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.select-toolbar .toolbar-more-dropdown .more-item--danger{color:#c84040}.select-toolbar .toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.select-toolbar .toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.select-toolbar .toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}.overlay-context-menu{position:fixed;z-index:999;background:#fff;border:1px solid var(--p-surface-200);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.overlay-context-menu .context-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left}.overlay-context-menu .context-menu-item i{font-size:14px}.overlay-context-menu .context-menu-item:hover{background:var(--p-surface-100, #f5f5f5)}\n"] }]
1777
+ args: [{ selector: 'app-overlay', standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"overlay-root\">\n\n <!-- HOVER \u2014 Chrome DevTools-style rings (content area stays transparent) -->\n <ng-container *ngIf=\"hoverEntry\">\n <div class=\"hi-outline\" [ngStyle]=\"hiOutlineStyle\"></div>\n <div class=\"hi-margin\" [ngStyle]=\"hiMarginStyle\"></div>\n <div class=\"hi-padding\" [ngStyle]=\"hiPaddingStyle\"></div>\n </ng-container>\n\n <!-- SELECT \u2014 IOX pink outline + toolbar -->\n <div class=\"select-outline\" *ngIf=\"selectEntry\" [ngStyle]=\"selectOutlineStyle\"\n (contextmenu)=\"onContextMenu($event)\">\n\n <div class=\"select-toolbar\" (click)=\"$event.stopPropagation()\" (mousedown)=\"$event.stopPropagation()\">\n\n <!-- Component name -->\n <span class=\"toolbar-name\">{{ selectEntry?.componentName }}</span>\n <span class=\"toolbar-sep\"></span>\n\n <!-- Quick actions -->\n <button class=\"toolbar-btn\" title=\"Duplicate\" (mousedown)=\"onDuplicate($event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button *ngIf=\"hasInteractions\" class=\"toolbar-btn toolbar-btn--play\" title=\"Play animations\" (mousedown)=\"onPlay($event)\">\n <i class=\"ph-thin ph-play\"></i>\n </button>\n <button class=\"toolbar-btn toolbar-btn--danger\" title=\"Delete\" (mousedown)=\"onDelete($event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n <span class=\"toolbar-sep\"></span>\n <button class=\"toolbar-btn\" title=\"Select parent\" (mousedown)=\"onSelectParent($event)\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n </button>\n\n <!-- More menu trigger \u2014 last -->\n <button class=\"toolbar-btn toolbar-btn--more\" title=\"More actions\" (mousedown)=\"onToggleMore($event)\">\n <i class=\"ph-thin ph-dots-three\"></i>\n </button>\n\n <!-- More dropdown (inline, positioned from toolbar) -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" (click)=\"$event.stopPropagation()\">\n <button class=\"more-item\" (mousedown)=\"onSelectParent($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-arrow-up\"></i>\n Select parent\n </button>\n <button class=\"more-item\" (mousedown)=\"onDuplicate($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-copy\"></i>\n Duplicate\n </button>\n <button class=\"more-item more-item--danger\" (mousedown)=\"onDelete($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-trash\"></i>\n Delete\n </button>\n <div class=\"more-divider\"></div>\n <button class=\"more-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n <button *ngIf=\"hasInteractions\" class=\"more-item more-item--play\" (mousedown)=\"onPlay($event); closeMoreMenu()\">\n <i class=\"ph-thin ph-play\"></i>\n Play animations\n </button>\n <div class=\"more-divider\"></div>\n <ng-container *ngIf=\"!selectedNodeIsGlobal; else removeGlobalItem\">\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'before')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (Before Content)\n </button>\n <button class=\"more-item\" (mousedown)=\"onMakeGlobal($event, 'after')\">\n <i class=\"ph-thin ph-globe\"></i>\n Make Global (After Content)\n </button>\n </ng-container>\n <ng-template #removeGlobalItem>\n <button class=\"more-item\" (mousedown)=\"onRemoveGlobal($event)\">\n <i class=\"ph-thin ph-globe\"></i>\n Remove from Global\n </button>\n </ng-template>\n </div>\n </div>\n </div>\n\n <!-- Backdrop to close more menu or context menu -->\n <div *ngIf=\"moreMenuVisible || contextMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu(); closeContextMenu()\">\n </div>\n\n <!-- Right-click context menu -->\n <div *ngIf=\"contextMenuVisible\" class=\"overlay-context-menu\"\n [ngStyle]=\"contextMenuStyle\"\n (click)=\"$event.stopPropagation()\">\n <button class=\"context-menu-item\" (mousedown)=\"onContextSaveAsBlock($event)\">\n <i class=\"ph-thin ph-bookmark\"></i>\n Save as reusable block\n </button>\n </div>\n\n</div>\n", styles: [":host{display:block;position:absolute;inset:0;pointer-events:none;z-index:12;overflow:visible}.hi-outline,.hi-margin,.hi-padding{position:fixed;pointer-events:none;transition:none;box-sizing:border-box}.hi-outline{box-shadow:inset 0 0 0 1.5px #cb9090}.hi-margin{background:#f6b23361}.hi-padding{background:#60c38366}.select-outline{position:fixed;z-index:999;box-shadow:inset 0 0 0 1px #cb9090;pointer-events:none;transition:none;overflow:visible;will-change:transform;box-sizing:border-box}.select-toolbar{position:absolute;top:0;left:0;transform:translateY(-100%);display:flex;align-items:center;background:#cb9090;border-radius:3px 3px 0 0;pointer-events:all;white-space:nowrap;overflow:visible}.select-toolbar .toolbar-name{padding:0 8px;font-size:11px;font-weight:500;color:#fff;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:22px}.select-toolbar .toolbar-sep{width:1px;height:14px;background:#ffffff59;margin:0 2px;flex-shrink:0}.select-toolbar .toolbar-btn{display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:none;background:transparent;color:#fff;font-size:13px;cursor:pointer;transition:background .15s;flex-shrink:0}.select-toolbar .toolbar-btn:hover{background:#ffffff2e}.select-toolbar .toolbar-btn--danger:hover{background:#c8323259}.select-toolbar .toolbar-btn--more{border-radius:0 3px 0 0}.select-toolbar .toolbar-more-dropdown{position:absolute;top:100%;right:0;left:auto;z-index:1000;background:#fff;border:1px solid var(--p-surface-200, #e5e7eb);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all;margin-top:2px}.select-toolbar .toolbar-more-dropdown .more-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left;white-space:nowrap}.select-toolbar .toolbar-more-dropdown .more-item i{font-size:14px}.select-toolbar .toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.select-toolbar .toolbar-more-dropdown .more-item--danger{color:#c84040}.select-toolbar .toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.select-toolbar .toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.select-toolbar .toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}.overlay-context-menu{position:fixed;z-index:999;background:#fff;border:1px solid var(--p-surface-200);border-radius:6px;box-shadow:0 4px 16px #0000001f;padding:4px 0;min-width:200px;pointer-events:all}.overlay-context-menu .context-menu-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 14px;border:none;background:transparent;font-size:13px;color:var(--p-text-color, #333);cursor:pointer;text-align:left}.overlay-context-menu .context-menu-item i{font-size:14px}.overlay-context-menu .context-menu-item:hover{background:var(--p-surface-100, #f5f5f5)}\n"] }]
1761
1778
  }], ctorParameters: () => [{ type: OverlayService }, { type: ViewportService }, { type: i0.ChangeDetectorRef }], propDecorators: { toolbarAction: [{
1762
1779
  type: Output
1763
1780
  }] } });
@@ -1888,16 +1905,19 @@ class LayerTreeComponent {
1888
1905
  this.registry = registry;
1889
1906
  this.overlayService = overlayService;
1890
1907
  this.layout = [];
1908
+ this.globalElements = [];
1891
1909
  this.nodeSelect = new EventEmitter();
1892
1910
  this.nodeAction = new EventEmitter();
1893
1911
  this.nodeMove = new EventEmitter();
1894
1912
  this.flatTree = [];
1913
+ this.flatGlobalTree = [];
1895
1914
  this.selectedNodeId = null;
1896
1915
  this.hoveredNodeId = null;
1897
1916
  /** Cache: component type → icon class */
1898
1917
  this.iconMap = new Map();
1899
1918
  /** Last-seen structural signature — used by ngDoCheck to skip no-op rebuilds. */
1900
1919
  this._layoutSig = '';
1920
+ this._globalSig = '';
1901
1921
  this.expandedState = new Map();
1902
1922
  }
1903
1923
  ngOnInit() {
@@ -1913,8 +1933,10 @@ class LayerTreeComponent {
1913
1933
  // Runs on every CD cycle — exit immediately if structure hasn't changed.
1914
1934
  ngDoCheck() {
1915
1935
  const sig = this.sig(this.layout);
1916
- if (sig !== this._layoutSig) {
1936
+ const globalSig = this.sig(this.globalElements);
1937
+ if (sig !== this._layoutSig || globalSig !== this._globalSig) {
1917
1938
  this._layoutSig = sig;
1939
+ this._globalSig = globalSig;
1918
1940
  this.rebuild();
1919
1941
  }
1920
1942
  }
@@ -1998,7 +2020,7 @@ class LayerTreeComponent {
1998
2020
  }
1999
2021
  rebuild() {
2000
2022
  // Save expanded state
2001
- for (const tn of this.flatTree) {
2023
+ for (const tn of [...this.flatTree, ...this.flatGlobalTree]) {
2002
2024
  if (tn.node.id) {
2003
2025
  this.expandedState.set(tn.node.id, tn.expanded);
2004
2026
  }
@@ -2006,6 +2028,9 @@ class LayerTreeComponent {
2006
2028
  const next = [];
2007
2029
  this.flattenTree(this.layout, 0, next);
2008
2030
  this.flatTree = next;
2031
+ const nextGlobal = [];
2032
+ this.flattenTree(this.globalElements, 0, nextGlobal);
2033
+ this.flatGlobalTree = nextGlobal;
2009
2034
  }
2010
2035
  flattenTree(nodes, depth, out, parentArr) {
2011
2036
  const arr = parentArr ?? nodes;
@@ -2030,13 +2055,15 @@ class LayerTreeComponent {
2030
2055
  }
2031
2056
  }
2032
2057
  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 }); }
2033
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout" }, 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</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 .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"] }] }); }
2058
+ 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"] }] }); }
2034
2059
  }
2035
2060
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: LayerTreeComponent, decorators: [{
2036
2061
  type: Component,
2037
- 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</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 .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"] }]
2062
+ 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"] }]
2038
2063
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }], propDecorators: { layout: [{
2039
2064
  type: Input
2065
+ }], globalElements: [{
2066
+ type: Input
2040
2067
  }], nodeSelect: [{
2041
2068
  type: Output
2042
2069
  }], nodeAction: [{
@@ -2056,6 +2083,12 @@ class BuilderComponent {
2056
2083
  handleToolbarZoomChange(value) {
2057
2084
  this.onZoomChange(value);
2058
2085
  }
2086
+ get globalElementsBefore() {
2087
+ return this.globalElements.filter(n => n.globalPosition === 'before');
2088
+ }
2089
+ get globalElementsAfter() {
2090
+ return this.globalElements.filter(n => n.globalPosition === 'after');
2091
+ }
2059
2092
  /** Canvas width in CSS pixels (driven by ViewportService). */
2060
2093
  get viewportWidth() {
2061
2094
  return this.viewportService.getState().width;
@@ -2112,6 +2145,7 @@ class BuilderComponent {
2112
2145
  this.isSaving = false;
2113
2146
  this.pageStatus = 'draft';
2114
2147
  this.pageSettings = null;
2148
+ this.globalElements = [];
2115
2149
  this.save = new EventEmitter();
2116
2150
  this.back = new EventEmitter();
2117
2151
  this.preview = new EventEmitter();
@@ -2119,6 +2153,7 @@ class BuilderComponent {
2119
2153
  this.pageSettingsChange = new EventEmitter();
2120
2154
  this.applyAnimationToAll = new EventEmitter();
2121
2155
  this.saveBlock = new EventEmitter();
2156
+ this.globalElementsChange = new EventEmitter();
2122
2157
  this.components = [];
2123
2158
  this.activePanel = PanelTypes.STYLES;
2124
2159
  this.isPanelOpen = false;
@@ -2427,6 +2462,17 @@ class BuilderComponent {
2427
2462
  }
2428
2463
  onTreeNodeAction(event) {
2429
2464
  const { action, node } = event;
2465
+ // Check global elements first
2466
+ const globalIdx = this.globalElements.findIndex(n => n === node);
2467
+ if (globalIdx !== -1) {
2468
+ if (action === NodeAction.Delete) {
2469
+ this.globalElements.splice(globalIdx, 1);
2470
+ this.globalElementsChange.emit([...this.globalElements]);
2471
+ this.selectedItem = null;
2472
+ this.overlayService.clearSelect();
2473
+ }
2474
+ return;
2475
+ }
2430
2476
  const result = this.findNodeInTree(node, this.layout);
2431
2477
  if (!result)
2432
2478
  return;
@@ -2491,12 +2537,39 @@ class BuilderComponent {
2491
2537
  }
2492
2538
  }
2493
2539
  else {
2494
- // Already at root level — deselect
2495
2540
  this.selectedItem = null;
2496
2541
  this.overlayService.clearSelect();
2497
2542
  }
2498
2543
  break;
2499
2544
  }
2545
+ case ToolbarAction.MakeGlobal: {
2546
+ const result = this.findNodeInTree(node, this.layout);
2547
+ if (result) {
2548
+ result.parent.splice(result.index, 1);
2549
+ node.isGlobal = true;
2550
+ node.globalPosition = event.position ?? 'before';
2551
+ this.globalElements.push(node);
2552
+ this.globalElementsChange.emit([...this.globalElements]);
2553
+ this.selectedItem = null;
2554
+ this.overlayService.clearSelect();
2555
+ this.cdr.markForCheck();
2556
+ }
2557
+ break;
2558
+ }
2559
+ case ToolbarAction.RemoveGlobal: {
2560
+ const idx = this.globalElements.findIndex(n => n.id === node.id);
2561
+ if (idx !== -1) {
2562
+ this.globalElements.splice(idx, 1);
2563
+ node.isGlobal = false;
2564
+ node.globalPosition = undefined;
2565
+ this.layout.push(node);
2566
+ this.globalElementsChange.emit([...this.globalElements]);
2567
+ this.selectedItem = null;
2568
+ this.overlayService.clearSelect();
2569
+ this.cdr.markForCheck();
2570
+ }
2571
+ break;
2572
+ }
2500
2573
  }
2501
2574
  }
2502
2575
  confirmSaveBlock() {
@@ -2585,11 +2658,11 @@ class BuilderComponent {
2585
2658
  }
2586
2659
  }
2587
2660
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: InteractionEngineService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }], target: i0.ɵɵFactoryTarget.Component }); }
2588
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings" }, outputs: { save: "save", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n </div>\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\">\n <i class=\"ph-thin ph-file\"></i>\n </button>\n </div>\n </header>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\" [layout]=\"layout\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Custom Lenis scrollbar \u2014 inside .preview-scroll (Lenis wrapper).\n position:absolute shifts with scrollTop, so we counteract with\n transform:translateY(scrollTop) to keep it visually anchored. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\"\n [style.transform]=\"'translateY(' + scrollbarTranslate + 'px)'\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <div *ngFor=\"let item of layout\"\n class=\"preview-node\"\n [class.is-selected]=\"selectedItem === item\"\n [style.width]=\"getNodeWidth(item)\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\">\n </app-toolbar>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-dialog\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:#fff;padding:.5rem 1rem;border-bottom:1px solid var(--p-surface-200);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-100, #f1f5f9);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.5rem}.builder .sidebar-switcher p-button ::ng-deep .p-button,.builder .panel-switcher p-button ::ng-deep .p-button{color:#000;background:transparent;border:1px solid transparent;transition:border-color .2s}.builder .sidebar-switcher p-button ::ng-deep .p-button:hover,.builder .panel-switcher p-button ::ng-deep .p-button:hover{border-color:#cb9090}.builder .sidebar-switcher p-button.active ::ng-deep .p-button,.builder .panel-switcher p-button.active ::ng-deep .p-button{background:#cb9090;color:#fff;border-color:#cb9090}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .builder-main app-toolbar{position:fixed;bottom:1.25rem;left:50%;transform:translate(-50%);z-index:1000;pointer-events:auto}.builder .sidebar{width:220px;min-width:220px;background:var(--p-surface-0);border-right:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;background:#fff;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:hidden!important}.builder .preview-node{position:relative}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}\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.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i10.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i11.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "deviceChange", "zoomChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }] }); }
2661
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements" }, outputs: { save: "save", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n </div>\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\">\n <i class=\"ph-thin ph-file\"></i>\n </button>\n </div>\n </header>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Custom Lenis scrollbar \u2014 inside .preview-scroll (Lenis wrapper).\n position:absolute shifts with scrollTop, so we counteract with\n transform:translateY(scrollTop) to keep it visually anchored. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\"\n [style.transform]=\"'translateY(' + scrollbarTranslate + 'px)'\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.transform]=\"viewportTransform\">\n\n <!-- Global elements: before page content -->\n <div *ngFor=\"let item of globalElementsBefore\"\n class=\"preview-node preview-node--global\"\n [class.is-selected]=\"selectedItem === item\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <div *ngFor=\"let item of layout\"\n class=\"preview-node\"\n [class.is-selected]=\"selectedItem === item\"\n [style.width]=\"getNodeWidth(item)\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n\n <!-- Global elements: after page content -->\n <div *ngFor=\"let item of globalElementsAfter\"\n class=\"preview-node preview-node--global\"\n [class.is-selected]=\"selectedItem === item\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\">\n </app-toolbar>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-dialog\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:#fff;padding:.5rem 1rem;border-bottom:1px solid var(--p-surface-200);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-100, #f1f5f9);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.5rem}.builder .sidebar-switcher p-button ::ng-deep .p-button,.builder .panel-switcher p-button ::ng-deep .p-button{color:#000;background:transparent;border:1px solid transparent;transition:border-color .2s}.builder .sidebar-switcher p-button ::ng-deep .p-button:hover,.builder .panel-switcher p-button ::ng-deep .p-button:hover{border-color:#cb9090}.builder .sidebar-switcher p-button.active ::ng-deep .p-button,.builder .panel-switcher p-button.active ::ng-deep .p-button{background:#cb9090;color:#fff;border-color:#cb9090}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .builder-main app-toolbar{position:fixed;bottom:1.25rem;left:50%;transform:translate(-50%);z-index:1000;pointer-events:auto}.builder .sidebar{width:220px;min-width:220px;background:var(--p-surface-0);border-right:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;background:#fff;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:hidden!important}.builder .preview-node{position:relative}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}\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.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i10.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i11.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "deviceChange", "zoomChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }] }); }
2589
2662
  }
2590
2663
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderComponent, decorators: [{
2591
2664
  type: Component,
2592
- args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n </div>\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\">\n <i class=\"ph-thin ph-file\"></i>\n </button>\n </div>\n </header>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\" [layout]=\"layout\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Custom Lenis scrollbar \u2014 inside .preview-scroll (Lenis wrapper).\n position:absolute shifts with scrollTop, so we counteract with\n transform:translateY(scrollTop) to keep it visually anchored. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\"\n [style.transform]=\"'translateY(' + scrollbarTranslate + 'px)'\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <div *ngFor=\"let item of layout\"\n class=\"preview-node\"\n [class.is-selected]=\"selectedItem === item\"\n [style.width]=\"getNodeWidth(item)\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\">\n </app-toolbar>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-dialog\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:#fff;padding:.5rem 1rem;border-bottom:1px solid var(--p-surface-200);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-100, #f1f5f9);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.5rem}.builder .sidebar-switcher p-button ::ng-deep .p-button,.builder .panel-switcher p-button ::ng-deep .p-button{color:#000;background:transparent;border:1px solid transparent;transition:border-color .2s}.builder .sidebar-switcher p-button ::ng-deep .p-button:hover,.builder .panel-switcher p-button ::ng-deep .p-button:hover{border-color:#cb9090}.builder .sidebar-switcher p-button.active ::ng-deep .p-button,.builder .panel-switcher p-button.active ::ng-deep .p-button{background:#cb9090;color:#fff;border-color:#cb9090}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .builder-main app-toolbar{position:fixed;bottom:1.25rem;left:50%;transform:translate(-50%);z-index:1000;pointer-events:auto}.builder .sidebar{width:220px;min-width:220px;background:var(--p-surface-0);border-right:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;background:#fff;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:hidden!important}.builder .preview-node{position:relative}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}\n"] }]
2665
+ args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n </div>\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\">\n <i class=\"ph-thin ph-file\"></i>\n </button>\n </div>\n </header>\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <aside class=\"sidebar\">\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </aside>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Custom Lenis scrollbar \u2014 inside .preview-scroll (Lenis wrapper).\n position:absolute shifts with scrollTop, so we counteract with\n transform:translateY(scrollTop) to keep it visually anchored. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\"\n [style.transform]=\"'translateY(' + scrollbarTranslate + 'px)'\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.transform]=\"viewportTransform\">\n\n <!-- Global elements: before page content -->\n <div *ngFor=\"let item of globalElementsBefore\"\n class=\"preview-node preview-node--global\"\n [class.is-selected]=\"selectedItem === item\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <div *ngFor=\"let item of layout\"\n class=\"preview-node\"\n [class.is-selected]=\"selectedItem === item\"\n [style.width]=\"getNodeWidth(item)\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n\n <!-- Global elements: after page content -->\n <div *ngFor=\"let item of globalElementsAfter\"\n class=\"preview-node preview-node--global\"\n [class.is-selected]=\"selectedItem === item\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\">\n </app-toolbar>\n\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div *ngIf=\"showSaveBlockDialog\" class=\"save-block-dialog\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .builder-header{background:#fff;padding:.5rem 1rem;border-bottom:1px solid var(--p-surface-200);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.builder .builder-header .header-back{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--p-text-muted-color);font-size:16px;transition:background .15s;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-100, #f1f5f9);color:var(--p-text-color)}.builder .builder-header .header-page-name{flex:1;text-align:center;font-size:.8rem;font-weight:500;color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-inline:1rem}.builder .sidebar-switcher,.builder .panel-switcher{display:flex;gap:.5rem}.builder .sidebar-switcher p-button ::ng-deep .p-button,.builder .panel-switcher p-button ::ng-deep .p-button{color:#000;background:transparent;border:1px solid transparent;transition:border-color .2s}.builder .sidebar-switcher p-button ::ng-deep .p-button:hover,.builder .panel-switcher p-button ::ng-deep .p-button:hover{border-color:#cb9090}.builder .sidebar-switcher p-button.active ::ng-deep .p-button,.builder .panel-switcher p-button.active ::ng-deep .p-button{background:#cb9090;color:#fff;border-color:#cb9090}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .builder-main app-toolbar{position:fixed;bottom:1.25rem;left:50%;transform:translate(-50%);z-index:1000;pointer-events:auto}.builder .sidebar{width:220px;min-width:220px;background:var(--p-surface-0);border-right:1px solid var(--p-surface-200);overflow-y:auto;overflow-x:hidden;flex-shrink:0}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;background:#fff;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:hidden!important}.builder .preview-node{position:relative}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}\n"] }]
2593
2666
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: InteractionEngineService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }], propDecorators: { layout: [{
2594
2667
  type: Input
2595
2668
  }], dataSources: [{
@@ -2606,6 +2679,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
2606
2679
  type: Input
2607
2680
  }], pageSettings: [{
2608
2681
  type: Input
2682
+ }], globalElements: [{
2683
+ type: Input
2609
2684
  }], save: [{
2610
2685
  type: Output
2611
2686
  }], back: [{
@@ -2620,6 +2695,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
2620
2695
  type: Output
2621
2696
  }], saveBlock: [{
2622
2697
  type: Output
2698
+ }], globalElementsChange: [{
2699
+ type: Output
2623
2700
  }], previewCanvas: [{
2624
2701
  type: ViewChild,
2625
2702
  args: ['previewCanvas']