@vectoriox/iox-builder 1.1.5 → 1.1.8

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.
@@ -189,6 +189,11 @@ class OverlayService {
189
189
  this.scrollContainerElement = null;
190
190
  /** Emits the scroll container element whenever it is registered. */
191
191
  this.scrollContainer$ = new Subject();
192
+ /** Emits cursor coords when a canvas element is right-clicked. */
193
+ this.contextMenu$ = new Subject();
194
+ }
195
+ triggerContextMenu(x, y) {
196
+ this.contextMenu$.next({ x, y });
192
197
  }
193
198
  setContainer(container) {
194
199
  this.containerElement = container;
@@ -1526,6 +1531,13 @@ class RenderDirective {
1526
1531
  event.stopPropagation();
1527
1532
  this.onClick.emit({ element: nativeEl, node, componentRef });
1528
1533
  });
1534
+ nativeEl.addEventListener('contextmenu', (event) => {
1535
+ event.preventDefault();
1536
+ event.stopPropagation();
1537
+ // Select the element first, then open the more menu at cursor position.
1538
+ this.onClick.emit({ element: nativeEl, node, componentRef });
1539
+ this.overlayService.triggerContextMenu(event.clientX, event.clientY);
1540
+ });
1529
1541
  }
1530
1542
  // Wire up interactions (animations) after the element is in the DOM.
1531
1543
  this.interactionEngine.attach(node);
@@ -1663,11 +1675,10 @@ class OverlayComponent {
1663
1675
  this.hiPaddingStyle = {};
1664
1676
  // Select: simple IOX pink outline
1665
1677
  this.selectOutlineStyle = {};
1666
- // More dropdown (toolbar ⋯ button)
1678
+ // More dropdown opened from the ⋯ button or from a right-click.
1679
+ // Position is set to fixed viewport coords so it appears at the cursor.
1667
1680
  this.moreMenuVisible = false;
1668
- // Right-click context menu
1669
- this.contextMenuVisible = false;
1670
- this.contextMenuStyle = {};
1681
+ this.moreMenuStyle = {};
1671
1682
  this.subs = [];
1672
1683
  this.boundUpdate = () => this.updateAll();
1673
1684
  this.activeScrollContainer = null;
@@ -1708,6 +1719,12 @@ class OverlayComponent {
1708
1719
  this.activeScrollContainer = el;
1709
1720
  el.addEventListener('scroll', this.boundUpdate, { passive: true });
1710
1721
  }));
1722
+ // Open the more menu when a canvas element is right-clicked.
1723
+ this.subs.push(this.overlayService.contextMenu$.subscribe(({ x, y }) => {
1724
+ this.moreMenuStyle = { top: `${y}px`, left: `${x}px` };
1725
+ this.moreMenuVisible = true;
1726
+ this.cdr.markForCheck();
1727
+ }));
1711
1728
  }
1712
1729
  ngOnDestroy() {
1713
1730
  this.subs.forEach(s => s.unsubscribe());
@@ -1746,7 +1763,14 @@ class OverlayComponent {
1746
1763
  }
1747
1764
  onToggleMore(event) {
1748
1765
  event.stopPropagation();
1749
- this.moreMenuVisible = !this.moreMenuVisible;
1766
+ event.preventDefault();
1767
+ if (this.moreMenuVisible) {
1768
+ this.moreMenuVisible = false;
1769
+ }
1770
+ else {
1771
+ this.moreMenuStyle = { top: `${event.clientY}px`, left: `${event.clientX}px` };
1772
+ this.moreMenuVisible = true;
1773
+ }
1750
1774
  this.cdr.markForCheck();
1751
1775
  }
1752
1776
  closeMoreMenu() {
@@ -1756,20 +1780,10 @@ class OverlayComponent {
1756
1780
  onContextMenu(event) {
1757
1781
  event.preventDefault();
1758
1782
  event.stopPropagation();
1759
- this.contextMenuStyle = {
1760
- position: 'fixed',
1761
- top: `${event.clientY}px`,
1762
- left: `${event.clientX}px`,
1763
- };
1764
- this.contextMenuVisible = true;
1783
+ this.moreMenuStyle = { top: `${event.clientY}px`, left: `${event.clientX}px` };
1784
+ this.moreMenuVisible = true;
1765
1785
  this.cdr.markForCheck();
1766
1786
  }
1767
- onContextSaveAsBlock(event) {
1768
- event.stopPropagation();
1769
- this.closeContextMenu();
1770
- if (this.selectEntry)
1771
- this.toolbarAction.emit({ action: ToolbarAction.SaveAsBlock, entry: this.selectEntry });
1772
- }
1773
1787
  onMakeGlobal(event, position) {
1774
1788
  event.stopPropagation();
1775
1789
  this.closeMoreMenu();
@@ -1782,10 +1796,6 @@ class OverlayComponent {
1782
1796
  if (this.selectEntry)
1783
1797
  this.toolbarAction.emit({ action: ToolbarAction.RemoveGlobal, entry: this.selectEntry });
1784
1798
  }
1785
- closeContextMenu() {
1786
- this.contextMenuVisible = false;
1787
- this.cdr.markForCheck();
1788
- }
1789
1799
  get hasInteractions() {
1790
1800
  return !!this.selectEntry?.node?.interactions?.length;
1791
1801
  }
@@ -1912,11 +1922,11 @@ class OverlayComponent {
1912
1922
  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)`;
1913
1923
  }
1914
1924
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OverlayComponent, deps: [{ token: OverlayService }, { token: ViewportService }, { token: PresetRegistryService }, { token: StyleRegistryService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1915
- 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 <!-- Preset badge \u2014 centered below the outline, shown when a preset is applied -->\n <div *ngIf=\"selectedPresetName\" class=\"preset-badge\" (mousedown)=\"onEditPreset($event)\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\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}.preset-badge{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;white-space:nowrap;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s}.preset-badge i{font-size:12px}.preset-badge:hover{background:#be7474}.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 }); }
1925
+ 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\n <!-- Preset badge \u2014 centered below the outline, shown when a preset is applied -->\n <div *ngIf=\"selectedPresetName\" class=\"preset-badge\" (mousedown)=\"onEditPreset($event)\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\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 opens a fixed-position dropdown at the cursor -->\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 </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$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)=\"onSaveAsBlock($event); closeMoreMenu()\">\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\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\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}.toolbar-more-dropdown{position:fixed;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}.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}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.preset-badge{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;white-space:nowrap;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s}.preset-badge i{font-size:12px}.preset-badge:hover{background:#be7474}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\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 }); }
1916
1926
  }
1917
1927
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OverlayComponent, decorators: [{
1918
1928
  type: Component,
1919
- 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 <!-- Preset badge \u2014 centered below the outline, shown when a preset is applied -->\n <div *ngIf=\"selectedPresetName\" class=\"preset-badge\" (mousedown)=\"onEditPreset($event)\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\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}.preset-badge{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;white-space:nowrap;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s}.preset-badge i{font-size:12px}.preset-badge:hover{background:#be7474}.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"] }]
1929
+ 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\n <!-- Preset badge \u2014 centered below the outline, shown when a preset is applied -->\n <div *ngIf=\"selectedPresetName\" class=\"preset-badge\" (mousedown)=\"onEditPreset($event)\">\n <i class=\"ph-fill ph-paint-bucket\"></i>\n <span>{{ selectedPresetName }}</span>\n </div>\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 opens a fixed-position dropdown at the cursor -->\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 </div>\n </div>\n\n <!-- Shared more/context menu \u2014 fixed-position, opened by \u22EF button or right-click -->\n <div *ngIf=\"moreMenuVisible\" class=\"toolbar-more-dropdown\" [ngStyle]=\"moreMenuStyle\"\n (click)=\"$event.stopPropagation()\" (mousedown)=\"$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)=\"onSaveAsBlock($event); closeMoreMenu()\">\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\n <!-- Backdrop to close the more menu -->\n <div *ngIf=\"moreMenuVisible\" class=\"overlay-context-backdrop\"\n (mousedown)=\"closeMoreMenu()\">\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}.toolbar-more-dropdown{position:fixed;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}.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}.toolbar-more-dropdown .more-item i{font-size:14px}.toolbar-more-dropdown .more-item:hover{background:var(--p-surface-100, #f5f5f5)}.toolbar-more-dropdown .more-item--danger{color:#c84040}.toolbar-more-dropdown .more-item--danger:hover{background:#c8404014}.toolbar-more-dropdown .more-item--play{color:var(--p-text-color, #333)}.toolbar-more-dropdown .more-divider{height:1px;background:var(--p-surface-200, #e5e7eb);margin:4px 0}.preset-badge{position:absolute;bottom:-26px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:5px;padding:3px 9px;background:#cb9090;color:#fff;font-size:11px;font-weight:500;border-radius:20px;white-space:nowrap;pointer-events:all;cursor:pointer;box-shadow:0 2px 6px #0000002e;transition:background .15s}.preset-badge i{font-size:12px}.preset-badge:hover{background:#be7474}.overlay-context-backdrop{position:fixed;inset:0;z-index:998;pointer-events:all}\n"] }]
1920
1930
  }], ctorParameters: () => [{ type: OverlayService }, { type: ViewportService }, { type: PresetRegistryService }, { type: StyleRegistryService }, { type: i0.ChangeDetectorRef }], propDecorators: { toolbarAction: [{
1921
1931
  type: Output
1922
1932
  }] } });
@@ -2649,7 +2659,7 @@ class BuilderComponent {
2649
2659
  else if (action === NodeAction.Duplicate) {
2650
2660
  const clone = this.deepCloneWithNewIds(node);
2651
2661
  result.parent.splice(result.index + 1, 0, clone);
2652
- this.cdr.markForCheck();
2662
+ this.cdr.detectChanges();
2653
2663
  }
2654
2664
  }
2655
2665
  onTreeNodeMove() {
@@ -2666,20 +2676,21 @@ class BuilderComponent {
2666
2676
  return;
2667
2677
  switch (action) {
2668
2678
  case ToolbarAction.Delete: {
2669
- const result = this.findNodeInTree(node, this.layout);
2679
+ const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
2670
2680
  if (result) {
2671
2681
  result.parent.splice(result.index, 1);
2672
2682
  this.selectedItem = null;
2673
2683
  this.overlayService.clearSelect();
2684
+ this.cdr.detectChanges();
2674
2685
  }
2675
2686
  break;
2676
2687
  }
2677
2688
  case ToolbarAction.Duplicate: {
2678
- const result = this.findNodeInTree(node, this.layout);
2689
+ const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
2679
2690
  if (result) {
2680
2691
  const clone = this.deepCloneWithNewIds(node);
2681
2692
  result.parent.splice(result.index + 1, 0, clone);
2682
- this.cdr.markForCheck();
2693
+ this.cdr.detectChanges();
2683
2694
  }
2684
2695
  break;
2685
2696
  }
@@ -2694,7 +2705,7 @@ class BuilderComponent {
2694
2705
  break;
2695
2706
  }
2696
2707
  case ToolbarAction.SelectParent: {
2697
- const parentNode = this.findParentNode(node, this.layout);
2708
+ const parentNode = this.findParentNode(node, this.layout) ?? this.findParentNode(node, this.globalElements);
2698
2709
  if (parentNode) {
2699
2710
  const ref = this.overlayService.getNodeRef(parentNode);
2700
2711
  if (ref) {