@vectoriox/iox-builder 5.3.1 → 5.3.2
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.
|
@@ -6260,6 +6260,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
6260
6260
|
* fitting (the frame is clipped by exactly the difference).
|
|
6261
6261
|
*/
|
|
6262
6262
|
const CANVAS_INSET = 20;
|
|
6263
|
+
/** The page-style declarations a child INHERITS (text colour, typeface) — never the background. */
|
|
6264
|
+
const INHERITED_PAGE_PROPS = ['color', 'font-family'];
|
|
6265
|
+
function pickInherited(css) {
|
|
6266
|
+
const out = {};
|
|
6267
|
+
for (const prop of INHERITED_PAGE_PROPS)
|
|
6268
|
+
if (css[prop] !== undefined)
|
|
6269
|
+
out[prop] = css[prop];
|
|
6270
|
+
return out;
|
|
6271
|
+
}
|
|
6263
6272
|
class BuilderComponent {
|
|
6264
6273
|
/** A page with nothing on it yet: offer a structure instead of a blank canvas. */
|
|
6265
6274
|
get isPageEmpty() {
|
|
@@ -6598,6 +6607,10 @@ class BuilderComponent {
|
|
|
6598
6607
|
* engine puts on <body> (compilePageStyle in @vectoriox/iox-ui). A FIELD, recomputed in
|
|
6599
6608
|
* ngOnChanges: a getter would hand [ngStyle] a fresh object every CD pass. */
|
|
6600
6609
|
this.pageStyleCss = {};
|
|
6610
|
+
/** The INHERITED half of the page style (text colour, typeface) for the global-elements layer: on
|
|
6611
|
+
* the site global elements sit inside <body> and inherit it; on the canvas they are a sibling
|
|
6612
|
+
* layer of the page, so they get it explicitly. Never the background (it would cover the page). */
|
|
6613
|
+
this.globalLayerCss = {};
|
|
6601
6614
|
/** Canvas frame height in canvas-space pixels (= previewScroll.clientHeight / scale).
|
|
6602
6615
|
* Used as iox-page-component's height so position:fixed top:0;bottom:0 fills the
|
|
6603
6616
|
* same area as height:100vh (both resolve to this value). */
|
|
@@ -6660,6 +6673,7 @@ class BuilderComponent {
|
|
|
6660
6673
|
// Background / typography: until this existed the Page panel wrote a background
|
|
6661
6674
|
// that only appeared once the site was published.
|
|
6662
6675
|
this.pageStyleCss = compilePageStyle(this.pageSettings?.style, { designSystem: this.pageSettings?.designSystem === true });
|
|
6676
|
+
this.globalLayerCss = pickInherited(this.pageStyleCss);
|
|
6663
6677
|
}
|
|
6664
6678
|
if (changes['globalElements']) {
|
|
6665
6679
|
// The *ngIf on the global overlay renders after this hook — defer so
|
|
@@ -7602,11 +7616,11 @@ class BuilderComponent {
|
|
|
7602
7616
|
}
|
|
7603
7617
|
}
|
|
7604
7618
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: FormStateService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7605
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", 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" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], 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\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\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 <div class=\"sidebar-body\">\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 </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\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 <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\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 <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\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 iox-token-scope\"\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-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\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 <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\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 .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);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(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.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;container:iox-canvas/inline-size;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 .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.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-inline-start:-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: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i16.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: i16.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i16.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i17.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: i1.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", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
7619
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", 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" }, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], 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\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\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 <div class=\"sidebar-body\">\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 </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\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 <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\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 <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\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 iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\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-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\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 <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\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 .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);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(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.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;container:iox-canvas/inline-size;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 iox-page-component.iox-token-scope,.builder .global-elements-overlay{color:CanvasText}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.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-inline-start:-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: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i16.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: i16.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i16.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i17.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: i1.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", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
7606
7620
|
}
|
|
7607
7621
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
7608
7622
|
type: Component,
|
|
7609
|
-
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\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 <div class=\"sidebar-body\">\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 </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\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 <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\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 <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\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 iox-token-scope\"\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-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\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 <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\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 .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);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(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.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;container:iox-canvas/inline-size;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 .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.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-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
7623
|
+
args: [{ selector: 'app-builder', standalone: false, providers: [{ provide: IOX_BUILDER_CANVAS, useValue: true }], template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\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 <div class=\"sidebar-body\">\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 </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\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 <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\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 <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\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 iox-token-scope\"\n [ngStyle]=\"globalLayerCss\"\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-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\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 <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\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 <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\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 .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);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(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.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;container:iox-canvas/inline-size;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 iox-page-component.iox-token-scope,.builder .global-elements-overlay{color:CanvasText}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.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-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
7610
7624
|
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: FormStateService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
7611
7625
|
type: Input
|
|
7612
7626
|
}], dataSources: [{
|