@vectoriox/iox-builder 2.12.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { Injectable, inject, NgZone, EventEmitter, createComponent, Output, Input, Inject, Directive, ChangeDetectionStrategy, Component, ViewChild, effect, Optional, InjectionToken, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
3
3
  import * as i2 from '@angular/common';
4
4
  import { DOCUMENT, CommonModule } from '@angular/common';
5
- import * as i4$1 from '@angular/forms';
5
+ import * as i14 from '@angular/forms';
6
6
  import { FormsModule } from '@angular/forms';
7
7
  import * as i4 from '@angular/cdk/drag-drop';
8
8
  import { DragDropModule } from '@angular/cdk/drag-drop';
@@ -12,7 +12,6 @@ import { ButtonModule } from 'primeng/button';
12
12
  import * as i2$1 from 'primeng/popover';
13
13
  import { PopoverModule } from 'primeng/popover';
14
14
  import { InputTextModule } from 'primeng/inputtext';
15
- import * as i5 from 'primeng/select';
16
15
  import { SelectModule } from 'primeng/select';
17
16
  import { TooltipModule } from 'primeng/tooltip';
18
17
  import { DialogModule } from 'primeng/dialog';
@@ -5615,7 +5614,7 @@ class BuilderComponent {
5615
5614
  }
5616
5615
  }
5617
5616
  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: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }], target: i0.ɵɵFactoryTarget.Component }); }
5618
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", 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 }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></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 === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></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 <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\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.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\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 }\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:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;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 ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);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:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end: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;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;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:visible!important}.builder #canvas-preview{background:#fff}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.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}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\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: i4$1.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: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i15.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: i16.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "deviceChange", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }] }); }
5617
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", 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 }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeDevice]=\"activeDevice\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (deviceChange)=\"handleToolbarDeviceChange($event)\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n </app-toolbar>\n\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></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 === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></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 <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\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.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\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 }\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:var(--p-surface-0);padding:.375rem .75rem;border-bottom:1px solid var(--p-surface-200);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:.5rem;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 ease-out;margin-inline-end:.25rem}.builder .builder-header .header-back:hover{background:var(--p-surface-50);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:.25rem}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:6px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--p-surface-50);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:#cb9090;color:#fff}.builder .panel-switcher{justify-self:end}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden}.builder .sidebar{width:260px;min-width:260px;background:var(--p-surface-0);border-inline-end: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;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;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:visible!important}.builder #canvas-preview{background:#fff}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.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}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-left:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\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: i14.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: i14.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i14.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i15.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: i16.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "deviceChange", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }] }); }
5619
5618
  }
5620
5619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderComponent, decorators: [{
5621
5620
  type: Component,
@@ -7664,299 +7663,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
7664
7663
  args: [PanelChildComponent]
7665
7664
  }] } });
7666
7665
 
7667
- class InteractionsPanelComponent {
7668
- constructor(overlayService, componentRegistry) {
7669
- this.overlayService = overlayService;
7670
- this.componentRegistry = componentRegistry;
7671
- this.node = null;
7672
- /** Base + component-specific trigger options for the selected node. */
7673
- this.triggerOptions = [...TRIGGER_OPTIONS];
7674
- /** Base + component-specific action type options. */
7675
- this.actionTypeOptions = [...ACTION_TYPE_OPTIONS];
7676
- this.easingOptions = EASING_OPTIONS$1;
7677
- this.interactions = [];
7678
- /** All canvas nodes available as animation targets. */
7679
- this.nodeOptions = [];
7680
- /** Slot names already used anywhere on the canvas — offered as suggestions, not a closed list.
7681
- * A slot needs no declaration to work: naming it in a setContext action creates it. */
7682
- this.knownSlots = [];
7683
- /** Data-source aliases, for the "Pick a value…" mode. */
7684
- this.sourceOptions = [];
7685
- this.dsRegistry = inject(DataSourceRegistryService);
7686
- /** State for the "add interaction" form */
7687
- this.isAdding = false;
7688
- this.newTrigger = 'pageLoad';
7689
- this.newActionType = 'fadeIn';
7690
- this.newTarget = 'self';
7691
- this.newDuration = 600;
7692
- this.newDelay = 0;
7693
- this.newEasing = 'ease-out';
7694
- this.newOnce = true;
7695
- /** Slide index param for swiperGoToSlide action. */
7696
- this.newSlideIndex = 0;
7697
- /** Expanded interaction ID for editing */
7698
- this.expandedId = null;
7699
- }
7700
- get isSwiperGoToSlide() {
7701
- return this.newActionType === 'swiperGoToSlide';
7702
- }
7703
- get isSwiperAction() {
7704
- return this.newActionType.startsWith('swiper');
7705
- }
7706
- ngOnInit() {
7707
- this.selectSub = this.overlayService.select$.subscribe(entry => {
7708
- this.node = entry?.node ?? null;
7709
- this.interactions = this.node?.interactions ?? [];
7710
- this.isAdding = false;
7711
- this.expandedId = null;
7712
- this.refreshNodeOptions();
7713
- this.refreshDynamicOptions();
7714
- this.refreshKnownSlots();
7715
- });
7716
- }
7717
- /** Merge component-specific events into trigger options and methods into action options. */
7718
- refreshDynamicOptions() {
7719
- // Build trigger options: base + events from selected node's component type
7720
- const baseTriggers = [...TRIGGER_OPTIONS];
7721
- if (this.node?.type) {
7722
- const config = this.componentRegistry.createConfig(this.node.type);
7723
- if (config?.componentEvents?.length) {
7724
- for (const ev of config.componentEvents) {
7725
- baseTriggers.push({ value: ev.value, label: ev.label, icon: ev.icon ?? 'ph-thin ph-lightning' });
7726
- }
7727
- }
7728
- }
7729
- this.triggerOptions = baseTriggers;
7730
- // Build action options: base + methods from any SliderContainer-type labeled nodes in the tree
7731
- const baseActions = [...ACTION_TYPE_OPTIONS];
7732
- const swiperMethodsAdded = new Set();
7733
- for (const [n] of this.overlayService.getAllNodeEntries()) {
7734
- if (!n.id || !n.label)
7735
- continue;
7736
- const config = this.componentRegistry.createConfig(n.type);
7737
- if (!config?.componentMethods?.length)
7738
- continue;
7739
- for (const method of config.componentMethods) {
7740
- if (!swiperMethodsAdded.has(method.value)) {
7741
- baseActions.push({ value: method.value, label: method.label });
7742
- swiperMethodsAdded.add(method.value);
7743
- }
7744
- }
7745
- }
7746
- this.actionTypeOptions = baseActions;
7747
- }
7748
- refreshNodeOptions() {
7749
- const opts = [{ label: 'Self', value: 'self' }];
7750
- for (const [node] of this.overlayService.getAllNodeEntries()) {
7751
- if (!node.id || !node.label || node === this.node)
7752
- continue;
7753
- opts.push({ label: node.label, value: node.id });
7754
- }
7755
- this.nodeOptions = opts;
7756
- }
7757
- getTargetLabel(value) {
7758
- return this.nodeOptions.find(o => o.value === value)?.label ?? value;
7759
- }
7760
- ngOnDestroy() {
7761
- this.selectSub?.unsubscribe();
7762
- }
7763
- getTriggerLabel(trigger) {
7764
- return this.triggerOptions.find(t => t.value === trigger)?.label ?? trigger;
7765
- }
7766
- getTriggerIcon(trigger) {
7767
- return this.triggerOptions.find(t => t.value === trigger)?.icon ?? 'ph-thin ph-lightning';
7768
- }
7769
- getActionLabel(type) {
7770
- return this.actionTypeOptions.find(a => a.value === type)?.label ?? type;
7771
- }
7772
- getActionsSummary(interaction) {
7773
- return interaction.actions.map(a => this.getActionLabel(a.type)).join(', ');
7774
- }
7775
- toggleExpand(id) {
7776
- this.expandedId = this.expandedId === id ? null : id;
7777
- }
7778
- // ── Add interaction ──────────────────────────────────────
7779
- startAdd() {
7780
- this.isAdding = true;
7781
- this.newTrigger = 'pageLoad';
7782
- this.newActionType = 'fadeIn';
7783
- this.newTarget = 'self';
7784
- this.newDuration = 600;
7785
- this.newDelay = 0;
7786
- this.newEasing = 'ease-out';
7787
- this.newOnce = true;
7788
- this.newSlideIndex = 0;
7789
- this.refreshNodeOptions();
7790
- this.refreshDynamicOptions();
7791
- }
7792
- cancelAdd() {
7793
- this.isAdding = false;
7794
- }
7795
- confirmAdd() {
7796
- if (!this.node)
7797
- return;
7798
- const params = this.isSwiperGoToSlide
7799
- ? { index: this.newSlideIndex }
7800
- : undefined;
7801
- const action = {
7802
- type: this.newActionType,
7803
- target: this.newTarget,
7804
- duration: this.newDuration,
7805
- delay: this.newDelay,
7806
- easing: this.newEasing,
7807
- once: this.newOnce,
7808
- ...(params ? { params } : {}),
7809
- };
7810
- const interaction = {
7811
- id: generateNodeId(),
7812
- trigger: this.newTrigger,
7813
- actions: [action],
7814
- };
7815
- if (!this.node.interactions) {
7816
- this.node.interactions = [];
7817
- }
7818
- this.node.interactions.push(interaction);
7819
- this.interactions = this.node.interactions;
7820
- this.isAdding = false;
7821
- }
7822
- // ── Remove interaction ───────────────────────────────────
7823
- removeInteraction(id) {
7824
- if (!this.node?.interactions)
7825
- return;
7826
- this.node.interactions = this.node.interactions.filter(i => i.id !== id);
7827
- this.interactions = this.node.interactions;
7828
- if (this.expandedId === id) {
7829
- this.expandedId = null;
7830
- }
7831
- }
7832
- // ── Add action to existing interaction ───────────────────
7833
- addAction(interaction) {
7834
- interaction.actions.push({
7835
- type: 'fadeIn',
7836
- target: 'self',
7837
- duration: 600,
7838
- delay: 0,
7839
- easing: 'ease-out',
7840
- once: true,
7841
- });
7842
- }
7843
- removeAction(interaction, index) {
7844
- interaction.actions.splice(index, 1);
7845
- // Remove the interaction if it has no actions left
7846
- if (interaction.actions.length === 0) {
7847
- this.removeInteraction(interaction.id);
7848
- }
7849
- }
7850
- // ── Context actions ──────────────────────────────────────
7851
- /** True for setContext/clearContext — they get a slot editor instead of a target + easing. */
7852
- isContextAction(action) {
7853
- return isContextAction(action);
7854
- }
7855
- ctxParams(action) {
7856
- action.params ??= {};
7857
- return action.params;
7858
- }
7859
- slotOf(action) {
7860
- return this.ctxParams(action).slot ?? '';
7861
- }
7862
- setSlot(action, slot) {
7863
- this.ctxParams(action).slot = (slot ?? '').trim();
7864
- this.refreshKnownSlots();
7865
- }
7866
- valueKindOf(action) {
7867
- return this.ctxParams(action).value?.kind ?? 'item';
7868
- }
7869
- setValueKind(action, kind) {
7870
- const params = this.ctxParams(action);
7871
- params.value = kind === 'source'
7872
- ? { kind: 'source', source: this.sourceOptions[0]?.value ?? '', path: '' }
7873
- : { kind: 'item' };
7874
- }
7875
- valueSourceOf(action) {
7876
- const value = this.ctxParams(action).value;
7877
- return value?.source ?? '';
7878
- }
7879
- setValueSource(action, source) {
7880
- const params = this.ctxParams(action);
7881
- params.value = { kind: 'source', source, path: this.valuePathOf(action) };
7882
- }
7883
- valuePathOf(action) {
7884
- const value = this.ctxParams(action).value;
7885
- return value?.path ?? '';
7886
- }
7887
- setValuePath(action, path) {
7888
- const params = this.ctxParams(action);
7889
- params.value = { kind: 'source', source: this.valueSourceOf(action), path };
7890
- }
7891
- /**
7892
- * "Keep after leave" — ON means the slot survives mouseleave (stored as `noReverse`).
7893
- * Defaults to ON: a repeater row that cleared itself on leave would blink on every row-to-row
7894
- * move. Turning it OFF is the list-container behaviour (leave clears).
7895
- */
7896
- keepAfterLeave(action) {
7897
- return this.ctxParams(action).noReverse !== false;
7898
- }
7899
- setKeepAfterLeave(action, keep) {
7900
- this.ctxParams(action).noReverse = keep;
7901
- }
7902
- refreshKnownSlots() {
7903
- const slots = new Set();
7904
- for (const [node] of this.overlayService.getAllNodeEntries()) {
7905
- for (const ix of node.interactions ?? []) {
7906
- for (const a of [...(ix.actions ?? []), ...(ix.reverseActions ?? [])]) {
7907
- const slot = a.params?.slot;
7908
- if (slot)
7909
- slots.add(slot);
7910
- }
7911
- }
7912
- }
7913
- this.knownSlots = [...slots].sort();
7914
- this.sourceOptions = this.dsRegistry.getAliasOptions?.() ?? [];
7915
- }
7916
- // ── Toggle (reverse actions) ─────────────────────────────
7917
- hasToggle(interaction) {
7918
- return !!interaction.reverseActions?.length;
7919
- }
7920
- enableToggle(interaction) {
7921
- // Auto-populate reverse actions as the logical inverse of primary actions.
7922
- interaction.reverseActions = interaction.actions.map(a => ({
7923
- ...a,
7924
- type: (INVERSE_ACTION[a.type] ?? a.type),
7925
- }));
7926
- }
7927
- disableToggle(interaction) {
7928
- interaction.reverseActions = undefined;
7929
- }
7930
- addReverseAction(interaction) {
7931
- if (!interaction.reverseActions)
7932
- interaction.reverseActions = [];
7933
- interaction.reverseActions.push({
7934
- type: 'fadeOut',
7935
- target: 'self',
7936
- duration: 600,
7937
- delay: 0,
7938
- easing: 'ease-out',
7939
- once: false,
7940
- });
7941
- }
7942
- removeReverseAction(interaction, index) {
7943
- if (!interaction.reverseActions)
7944
- return;
7945
- interaction.reverseActions.splice(index, 1);
7946
- if (interaction.reverseActions.length === 0) {
7947
- interaction.reverseActions = undefined;
7948
- }
7949
- }
7950
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, deps: [{ token: OverlayService }, { token: ComponentRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
7951
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: InteractionsPanelComponent, isStandalone: false, selector: "app-interactions-panel", inputs: { node: "node" }, ngImport: i0, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <!-- Context actions: slot + what to write. Not an animation, so duration/easing\n below are irrelevant to them. -->\n <ng-container *ngIf=\"isContextAction(action)\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\"\n [ngModel]=\"slotOf(action)\"\n (ngModelChange)=\"setSlot(action, $event)\"\n list=\"ix-slot-options\"\n placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ slotOf(action) || 'slot' }}.field</strong>\n </span>\n </div>\n\n <ng-container *ngIf=\"action.type === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [ngModel]=\"valueKindOf(action)\" (ngModelChange)=\"setValueKind(action, $event)\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\" *ngIf=\"valueKindOf(action) === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [ngModel]=\"valueSourceOf(action)\"\n (ngModelChange)=\"setValueSource(action, $event)\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\"\n [ngModel]=\"valuePathOf(action)\"\n (ngModelChange)=\"setValuePath(action, $event)\"\n placeholder=\"(whole item)\">\n </div>\n </div>\n\n <div class=\"ix-field ix-field-inline\">\n <label>\n <input type=\"checkbox\"\n [ngModel]=\"keepAfterLeave(action)\"\n (ngModelChange)=\"setKeepAfterLeave(action, $event)\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isContextAction(action)\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4$1.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: i4$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i4$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i4$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i5.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
7952
- }
7953
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: InteractionsPanelComponent, decorators: [{
7954
- type: Component,
7955
- args: [{ selector: 'app-interactions-panel', standalone: false, template: "<div class=\"interactions-panel\">\n <!-- No element selected -->\n <div *ngIf=\"!node\" class=\"section-empty\">\n Select an element to manage interactions\n </div>\n\n <ng-container *ngIf=\"node\">\n <!-- Interaction cards -->\n <div *ngFor=\"let ix of interactions\" class=\"ix-card\" [class.is-expanded]=\"expandedId === ix.id\">\n <!-- Summary row -->\n <div class=\"ix-card-header\" (click)=\"toggleExpand(ix.id)\">\n <i [class]=\"getTriggerIcon(ix.trigger)\" class=\"ix-trigger-icon\"></i>\n <div class=\"ix-card-summary\">\n <span class=\"ix-trigger-label\">{{ getTriggerLabel(ix.trigger) }}</span>\n <span class=\"ix-arrow\">\u2192</span>\n <span class=\"ix-action-label\">{{ getActionsSummary(ix) }}</span>\n </div>\n <button class=\"ix-remove-btn\" title=\"Remove\" (click)=\"removeInteraction(ix.id); $event.stopPropagation()\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <!-- Expanded detail -->\n <div *ngIf=\"expandedId === ix.id\" class=\"ix-card-body\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"ix.trigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n\n <div *ngFor=\"let action of ix.actions; let ai = index\" class=\"ix-action-row\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ai + 1 }}\n <span *ngIf=\"ix.actions.length > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(action.target) }}\n </span>\n </span>\n <button *ngIf=\"ix.actions.length > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove action\"\n (click)=\"removeAction(ix, ai)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"action.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <!-- Context actions: slot + what to write. Not an animation, so duration/easing\n below are irrelevant to them. -->\n <ng-container *ngIf=\"isContextAction(action)\">\n <div class=\"ix-field\">\n <label>Slot</label>\n <input type=\"text\"\n [ngModel]=\"slotOf(action)\"\n (ngModelChange)=\"setSlot(action, $event)\"\n list=\"ix-slot-options\"\n placeholder=\"currentItem\">\n <datalist id=\"ix-slot-options\">\n <option *ngFor=\"let s of knownSlots\" [value]=\"s\"></option>\n </datalist>\n <span class=\"ix-target-hint\">\n Elements bind to it as <strong>{{ slotOf(action) || 'slot' }}.field</strong>\n </span>\n </div>\n\n <ng-container *ngIf=\"action.type === 'setContext'\">\n <div class=\"ix-field\">\n <label>Value</label>\n <select [ngModel]=\"valueKindOf(action)\" (ngModelChange)=\"setValueKind(action, $event)\">\n <option value=\"item\">This item \u2014 the row being hovered</option>\n <option value=\"source\">Pick a value\u2026</option>\n </select>\n </div>\n\n <div class=\"ix-field-row\" *ngIf=\"valueKindOf(action) === 'source'\">\n <div class=\"ix-field\">\n <label>Holds an item from</label>\n <p-select\n size=\"small\"\n [ngModel]=\"valueSourceOf(action)\"\n (ngModelChange)=\"setValueSource(action, $event)\"\n [options]=\"sourceOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n appendTo=\"body\">\n </p-select>\n </div>\n <div class=\"ix-field\">\n <label>Path</label>\n <input type=\"text\"\n [ngModel]=\"valuePathOf(action)\"\n (ngModelChange)=\"setValuePath(action, $event)\"\n placeholder=\"(whole item)\">\n </div>\n </div>\n\n <div class=\"ix-field ix-field-inline\">\n <label>\n <input type=\"checkbox\"\n [ngModel]=\"keepAfterLeave(action)\"\n (ngModelChange)=\"setKeepAfterLeave(action, $event)\">\n Keep after leave\n </label>\n <span class=\"ix-target-hint\">\n On for rows in a list \u2014 otherwise moving between rows blinks.\n </span>\n </div>\n </ng-container>\n </ng-container>\n\n <div class=\"ix-field\" *ngIf=\"!isContextAction(action)\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"action.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"action.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"action.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"action.once\">\n Play once\n </label>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n\n <!-- Toggle section (click only) -->\n <ng-container *ngIf=\"ix.trigger === 'click'\">\n <div class=\"ix-toggle-header\">\n <span class=\"ix-toggle-label\">\n <i class=\"ph-thin ph-arrows-clockwise\"></i> 2nd click\n </span>\n <label class=\"ix-toggle-switch\">\n <input type=\"checkbox\"\n [checked]=\"hasToggle(ix)\"\n (change)=\"hasToggle(ix) ? disableToggle(ix) : enableToggle(ix)\">\n <span class=\"ix-toggle-track\"></span>\n </label>\n </div>\n\n <ng-container *ngIf=\"hasToggle(ix)\">\n <div *ngFor=\"let ra of ix.reverseActions; let ri = index\" class=\"ix-action-row ix-action-row--reverse\">\n <div class=\"ix-action-header\">\n <span class=\"ix-action-index\">\n Target {{ ri + 1 }}\n <span *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\" class=\"ix-action-target-badge\">\n \u2192 {{ getTargetLabel(ra.target) }}\n </span>\n </span>\n <button *ngIf=\"(ix.reverseActions?.length ?? 0) > 1\"\n class=\"ix-remove-btn small\"\n title=\"Remove\"\n (click)=\"removeReverseAction(ix, ri)\">\n <i class=\"ph-thin ph-x\"></i>\n </button>\n </div>\n\n <div class=\"ix-field\">\n <label>Type</label>\n <select [(ngModel)]=\"ra.type\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"ra.target\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n </div>\n\n <div class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.duration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"ra.delay\" min=\"0\" step=\"50\">\n </div>\n </div>\n\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"ra.easing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n </div>\n\n <button class=\"ix-add-action-btn\" (click)=\"addReverseAction(ix)\">\n <i class=\"ph-thin ph-plus\"></i> Add target\n </button>\n </ng-container>\n </ng-container>\n </div>\n </div>\n\n <!-- Add interaction form -->\n <div *ngIf=\"isAdding\" class=\"ix-add-form\">\n <div class=\"ix-field\">\n <label>Trigger</label>\n <select [(ngModel)]=\"newTrigger\">\n <option *ngFor=\"let t of triggerOptions\" [value]=\"t.value\">{{ t.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Action</label>\n <select [(ngModel)]=\"newActionType\">\n <option *ngFor=\"let a of actionTypeOptions\" [value]=\"a.value\">{{ a.label }}</option>\n </select>\n </div>\n <div class=\"ix-field\">\n <label>Target</label>\n <p-select\n size=\"small\"\n [(ngModel)]=\"newTarget\"\n [options]=\"nodeOptions\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [filter]=\"true\"\n filterPlaceholder=\"Search\u2026\"\n styleClass=\"ix-target-select\"\n appendTo=\"body\">\n </p-select>\n <span *ngIf=\"nodeOptions.length === 1\" class=\"ix-target-hint\">\n Name elements in the Layers panel to target them\n </span>\n </div>\n <div *ngIf=\"isSwiperGoToSlide\" class=\"ix-field\">\n <label>Slide index (0-based)</label>\n <input type=\"number\" [(ngModel)]=\"newSlideIndex\" min=\"0\" step=\"1\">\n </div>\n <div *ngIf=\"!isSwiperAction\" class=\"ix-field-row\">\n <div class=\"ix-field\">\n <label>Duration (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDuration\" min=\"0\" step=\"50\">\n </div>\n <div class=\"ix-field\">\n <label>Delay (ms)</label>\n <input type=\"number\" [(ngModel)]=\"newDelay\" min=\"0\" step=\"50\">\n </div>\n </div>\n <div class=\"ix-field\">\n <label>Easing</label>\n <select [(ngModel)]=\"newEasing\">\n <option *ngFor=\"let e of easingOptions\" [value]=\"e\">{{ e }}</option>\n </select>\n </div>\n <div class=\"ix-field ix-checkbox-field\">\n <label>\n <input type=\"checkbox\" [(ngModel)]=\"newOnce\">\n Play once\n </label>\n </div>\n <div class=\"ix-form-actions\">\n <button class=\"ix-btn ix-btn--secondary\" (click)=\"cancelAdd()\">Cancel</button>\n <button class=\"ix-btn ix-btn--primary\" (click)=\"confirmAdd()\">Add</button>\n </div>\n </div>\n\n <!-- Add button -->\n <button *ngIf=\"!isAdding\" class=\"ix-add-btn\" (click)=\"startAdd()\">\n <i class=\"ph-thin ph-plus\"></i> Add interaction\n </button>\n </ng-container>\n</div>\n", styles: [".interactions-panel{padding:.5rem}.section-empty{color:var(--p-text-muted-color);font-size:12px;text-align:center;padding:1rem}.ix-card{border:1px solid var(--p-surface-200);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .15s}.ix-card.is-expanded{border-color:#cb9090}.ix-card-header{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;font-size:12px;transition:background .12s}.ix-card-header:hover{background:var(--p-surface-50)}.ix-trigger-icon{font-size:14px;color:#cb9090;flex-shrink:0}.ix-card-summary{flex:1;min-width:0;display:flex;align-items:center;gap:4px;overflow:hidden}.ix-trigger-label{font-weight:500;white-space:nowrap}.ix-arrow{color:var(--p-text-muted-color);flex-shrink:0}.ix-action-label{color:var(--p-text-muted-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ix-remove-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;flex-shrink:0;font-size:12px}.ix-remove-btn:hover{background:#dc26261a;color:#dc2626}.ix-remove-btn.small{width:16px;height:16px;font-size:10px}.ix-card-body{padding:0 10px 10px;border-top:1px solid var(--p-surface-100)}.ix-action-row{padding:8px 0;border-bottom:1px dashed var(--p-surface-100)}.ix-action-row:last-of-type{border-bottom:none}.ix-action-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.ix-action-index{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.03em}.ix-action-target-badge{font-weight:400;text-transform:none;letter-spacing:0;color:#cb9090}.ix-action-row--reverse{background:#638fcb0a;border-radius:6px;padding:6px 8px;border-bottom:none!important;margin-bottom:4px}.ix-toggle-header{display:flex;align-items:center;justify-content:space-between;padding:8px 0 4px;border-top:1px solid var(--p-surface-200);margin-top:4px}.ix-toggle-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);display:flex;align-items:center;gap:5px}.ix-toggle-label i{font-size:12px}.ix-toggle-switch{position:relative;display:inline-flex;align-items:center;cursor:pointer}.ix-toggle-switch input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}.ix-toggle-track{display:block;width:28px;height:16px;border-radius:8px;background:var(--p-surface-300);transition:background .2s;position:relative}.ix-toggle-track:after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:transform .2s}.ix-toggle-switch input:checked~.ix-toggle-track{background:#638fcb}.ix-toggle-switch input:checked~.ix-toggle-track:after{transform:translate(12px)}.ix-add-action-btn{display:flex;align-items:center;gap:4px;margin-top:8px;padding:4px 8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:6px;font-size:11px;transition:border-color .15s,color .15s}.ix-add-action-btn:hover{border-color:#cb9090;color:#cb9090}.ix-field{margin-bottom:8px}.ix-field label{display:block;font-size:11px;color:var(--p-text-muted-color);margin-bottom:3px;font-weight:500}.ix-field select,.ix-field input[type=number]{width:100%;padding:5px 8px;border:1px solid var(--p-surface-200);border-radius:6px;font-size:12px;background:var(--p-surface-0);color:var(--p-text-color);outline:none;transition:border-color .15s}.ix-field select:focus,.ix-field input[type=number]:focus{border-color:#cb9090}.ix-field-row{display:flex;gap:8px}.ix-field-row .ix-field{flex:1}.ix-checkbox-field label{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--p-text-color)}.ix-checkbox-field input[type=checkbox]{accent-color:#cb9090}.ix-add-form{border:1px solid var(--p-surface-200);border-radius:8px;padding:10px;margin-bottom:6px}.ix-form-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}.ix-btn{padding:5px 12px;border:none;border-radius:6px;font-size:12px;cursor:pointer;transition:background .12s,color .12s}.ix-btn--primary{background:#cb9090;color:#fff}.ix-btn--primary:hover{background:#be7474}.ix-btn--secondary{background:transparent;color:var(--p-text-muted-color)}.ix-btn--secondary:hover{background:var(--p-surface-100);color:var(--p-text-color)}.ix-add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border:1px dashed var(--p-surface-300);background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:8px;font-size:12px;transition:border-color .15s,color .15s}.ix-add-btn:hover{border-color:#cb9090;color:#cb9090}.ix-target-hint{display:block;font-size:10px;color:var(--p-text-muted-color);margin-top:4px;font-style:italic}::ng-deep .ix-target-select{width:100%}::ng-deep .ix-target-select .p-select-label{font-size:12px;padding:4px 8px}::ng-deep .ix-target-select .p-select{border-radius:6px}\n"] }]
7956
- }], ctorParameters: () => [{ type: OverlayService }, { type: ComponentRegistryService }], propDecorators: { node: [{
7957
- type: Input
7958
- }] } });
7959
-
7960
7666
  const MAX_HISTORY = 50;
7961
7667
  /**
7962
7668
  * BuilderHistoryService — snapshot-based undo/redo for the page builder.
@@ -8023,7 +7729,6 @@ const DECLARATIONS = [
8023
7729
  PanelComponent,
8024
7730
  PanelChildComponent,
8025
7731
  LayerTreeComponent,
8026
- InteractionsPanelComponent,
8027
7732
  ];
8028
7733
  class IoxBuilderModule {
8029
7734
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxBuilderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -8042,8 +7747,7 @@ class IoxBuilderModule {
8042
7747
  SliderSlideComponent,
8043
7748
  PanelComponent,
8044
7749
  PanelChildComponent,
8045
- LayerTreeComponent,
8046
- InteractionsPanelComponent], imports: [CommonModule,
7750
+ LayerTreeComponent], imports: [CommonModule,
8047
7751
  FormsModule,
8048
7752
  DragDropModule,
8049
7753
  AccordionModule,
@@ -8082,8 +7786,7 @@ class IoxBuilderModule {
8082
7786
  SliderSlideComponent,
8083
7787
  PanelComponent,
8084
7788
  PanelChildComponent,
8085
- LayerTreeComponent,
8086
- InteractionsPanelComponent] }); }
7789
+ LayerTreeComponent] }); }
8087
7790
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxBuilderModule, providers: [
8088
7791
  DragEngineService,
8089
7792
  OverlayService,
@@ -8186,7 +7889,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
8186
7889
  PanelComponent,
8187
7890
  PanelChildComponent,
8188
7891
  LayerTreeComponent,
8189
- InteractionsPanelComponent,
8190
7892
  ],
8191
7893
  schemas: [NO_ERRORS_SCHEMA],
8192
7894
  }]
@@ -8755,5 +8457,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
8755
8457
  * Generated bundle index. Do not edit.
8756
8458
  */
8757
8459
 
8758
- export { ACTION_TYPE_OPTIONS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEVICE_OPTIONS, DataSourceRegistryService, DataSourceType, DeviceMode, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EffectsGroupStyleConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, InteractionsPanelComponent, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, buildFullStyleTraits, buildPresetStyleTraits, defaultPageSettings, findNodeLocation, flattenSchemaFields, generateNodeId, resolveTraitControllerType, resolveTraitOptions, wrapNode };
8460
+ export { ACTION_TYPE_OPTIONS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEVICE_OPTIONS, DataSourceRegistryService, DataSourceType, DeviceMode, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EffectsGroupStyleConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, buildFullStyleTraits, buildPresetStyleTraits, defaultPageSettings, findNodeLocation, flattenSchemaFields, generateNodeId, resolveTraitControllerType, resolveTraitOptions, wrapNode };
8759
8461
  //# sourceMappingURL=vectoriox-iox-builder.mjs.map