@vectoriox/iox-builder 1.7.4 → 1.7.6

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.
@@ -242,11 +242,18 @@ class OverlayService {
242
242
  return this.intersectionRootElement;
243
243
  }
244
244
  /**
245
- * Resolve the inner styled element.
246
- * Builder components apply [ngStyle] on their first child element,
247
- * so computed styles (padding, margin) live there — not on the host.
245
+ * Resolve the styled element (the one carrying `.iox-node-{id}` — where padding/margin/size live).
246
+ * Directive-host components (Image/Heading/Container/Section/…) put `iox-node-{id}` on the HOST
247
+ * itself, so the host IS the styled element. Legacy wrapper components (Card/Repeater/Slider) put
248
+ * it on the first child. Prefer the host when it's the styled element; fall back to the first
249
+ * child otherwise. (Before this, directive-host containers outlined their first child — e.g. a
250
+ * container's outline landed on the image inside it, desyncing from Moveable's resize handles.)
248
251
  */
249
252
  getStyledElement(host) {
253
+ for (let i = 0; i < host.classList.length; i++) {
254
+ if (host.classList[i].startsWith('iox-node-'))
255
+ return host;
256
+ }
250
257
  return host.firstElementChild || host;
251
258
  }
252
259
  // ── Node → Element registry ──────────────────────────────
@@ -1838,469 +1845,170 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1838
1845
  type: Injectable
1839
1846
  }] });
1840
1847
 
1848
+ var StyleCategory;
1849
+ (function (StyleCategory) {
1850
+ StyleCategory["Layout"] = "Layout";
1851
+ StyleCategory["Size"] = "Size";
1852
+ StyleCategory["Typography"] = "Typography";
1853
+ StyleCategory["Spacing"] = "Spacing";
1854
+ StyleCategory["Border"] = "Border";
1855
+ StyleCategory["Position"] = "Position";
1856
+ StyleCategory["Background"] = "Background";
1857
+ StyleCategory["Effects"] = "Effects";
1858
+ StyleCategory["List"] = "List";
1859
+ // Legacy — kept for backward compat; prefer Size
1860
+ StyleCategory["Dimensions"] = "Dimensions";
1861
+ })(StyleCategory || (StyleCategory = {}));
1862
+ var DataSourceType;
1863
+ (function (DataSourceType) {
1864
+ DataSourceType["Cms"] = "cms";
1865
+ DataSourceType["Shop"] = "shop";
1866
+ DataSourceType["ExternalApi"] = "external-api";
1867
+ DataSourceType["InternalApi"] = "internal-api";
1868
+ DataSourceType["Static"] = "static";
1869
+ })(DataSourceType || (DataSourceType = {}));
1841
1870
  /**
1842
- * MoveableCanvasService — Phase 1 of the Moveable + Selecto migration
1843
- * (see iox-ai-guidance/architecture/builder/moveable-migration.md).
1844
- *
1845
- * Drives DRAG for the SELECTED element via Moveable instead of interact.js, while REUSING the
1846
- * proven reorder machinery (`DragEngineService.handleDrop`). It is a pure INPUT device: during a
1847
- * drag it applies a TRANSIENT transform, and on drop it RESETS the transform to nothing and instead
1848
- * reorders the layout tree (constraint C1 — a persisted transform on an in-flow canvas element
1849
- * breaks Lenis scroll). Everything here runs ONLY when `moveableMode` is on; interact.js is
1850
- * untouched, so the flag-off path is byte-identical to today.
1851
- *
1852
- * Scale: Moveable reads `getBoundingClientRect()` (already post-scale) and works in viewport
1853
- * client coordinates, so drop hit-testing via `elementsFromPoint` / rects is scale-correct without
1854
- * extra math. The control box is rendered into the same overlay container the selection overlay uses.
1871
+ * Maps each action type to its logical inverse for auto-populating toggle
1872
+ * reverse actions. Types without a natural inverse are omitted.
1855
1873
  */
1856
- class MoveableCanvasService {
1857
- constructor(dragEngine) {
1858
- this.dragEngine = dragEngine;
1859
- this.moveable = null;
1860
- this.container = null;
1861
- this.scaleGetter = () => 1;
1862
- this.target = null;
1863
- this.node = null;
1864
- this.sourceId = null;
1865
- /** Called after a successful reorder so the host can emit LAYOUT_CHANGED / refresh selection. */
1866
- this.onReorder = null;
1867
- }
1868
- /** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom. */
1869
- init(container, scaleGetter) {
1870
- this.container = container;
1871
- this.scaleGetter = scaleGetter;
1874
+ const INVERSE_ACTION = {
1875
+ fadeIn: 'fadeOut',
1876
+ fadeOut: 'fadeIn',
1877
+ show: 'hide',
1878
+ hide: 'show',
1879
+ scaleIn: 'scaleOut',
1880
+ scaleOut: 'scaleIn',
1881
+ moveUp: 'moveDown',
1882
+ moveDown: 'moveUp',
1883
+ moveLeft: 'moveRight',
1884
+ moveRight: 'moveLeft',
1885
+ };
1886
+ const TRIGGER_OPTIONS = [
1887
+ { value: 'pageLoad', label: 'Page load', icon: 'ph-thin ph-play' },
1888
+ { value: 'viewportEnter', label: 'Enter viewport', icon: 'ph-thin ph-eye' },
1889
+ { value: 'click', label: 'Click', icon: 'ph-thin ph-cursor-click' },
1890
+ { value: 'hover', label: 'Hover', icon: 'ph-thin ph-hand-pointing' },
1891
+ { value: 'scrollProgress', label: 'Scroll progress', icon: 'ph-thin ph-arrow-fat-lines-down' },
1892
+ ];
1893
+ const ACTION_TYPE_OPTIONS = [
1894
+ { value: 'fadeIn', label: 'Fade in' },
1895
+ { value: 'fadeOut', label: 'Fade out' },
1896
+ { value: 'moveUp', label: 'Move up' },
1897
+ { value: 'moveDown', label: 'Move down' },
1898
+ { value: 'moveLeft', label: 'Move left' },
1899
+ { value: 'moveRight', label: 'Move right' },
1900
+ { value: 'scaleIn', label: 'Scale in' },
1901
+ { value: 'scaleOut', label: 'Scale out' },
1902
+ { value: 'rotate', label: 'Rotate' },
1903
+ { value: 'show', label: 'Show' },
1904
+ { value: 'hide', label: 'Hide' },
1905
+ { value: 'toggleVisibility', label: 'Toggle visibility' },
1906
+ ];
1907
+ const EASING_OPTIONS = ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'];
1908
+ /** Interaction pseudo-classes — compiled to .iox-node-{id}:state */
1909
+ const INTERACTION_STATES = ['hover', 'active', 'focus'];
1910
+ /** Structural pseudo-classes — compiled to .iox-outer-{id}:state */
1911
+ const STRUCTURAL_STATES = ['first-child', 'last-child', 'nth-child(odd)', 'nth-child(even)'];
1912
+ /**
1913
+ * Relational pseudo-classes — the styled element is a descendant, the pseudo-class
1914
+ * lives on an ANCESTOR. Compiled to `.iox-node-{parentId}:{base} .iox-node-{id} { … }`,
1915
+ * so they require the ancestor's css id (`relativeToId`) at compile time.
1916
+ * `RELATIONAL_STATE_BASE` maps each relational state to the ancestor pseudo-class.
1917
+ */
1918
+ const RELATIONAL_STATES = ['parent-hover'];
1919
+ const RELATIONAL_STATE_BASE = {
1920
+ 'parent-hover': 'hover',
1921
+ };
1922
+ const SUPPORTED_STATES = [...INTERACTION_STATES, ...STRUCTURAL_STATES, ...RELATIONAL_STATES];
1923
+ /**
1924
+ * Build a full StyleTraitGroup[] schema populated with values from a flat
1925
+ * styleProps map. Used when editing a preset in the Style panel — the panel
1926
+ * receives the same `StyleTraitGroup[]` shape regardless of context.
1927
+ */
1928
+ function buildPresetStyleTraits(styleProps) {
1929
+ const allGroups = [
1930
+ new LayoutGroupStyleConfig(),
1931
+ new SizeGroupStyleConfig(),
1932
+ new BackgroundGroupStyleConfig(),
1933
+ new SpacingGroupStyleConfig(),
1934
+ new BorderGroupStyleConfig(),
1935
+ new TypographyGroupStyleConfig(),
1936
+ new EffectsGroupStyleConfig(),
1937
+ new PositionGroupStyleConfig(),
1938
+ ];
1939
+ return allGroups.map(group => ({
1940
+ ...group,
1941
+ traits: group.traits.map(trait => ({
1942
+ ...trait,
1943
+ default: styleProps[trait.name] !== undefined ? styleProps[trait.name] : trait.default,
1944
+ })),
1945
+ }));
1946
+ }
1947
+ var TraitInputType;
1948
+ (function (TraitInputType) {
1949
+ TraitInputType["Text"] = "text";
1950
+ TraitInputType["Number"] = "number";
1951
+ TraitInputType["Select"] = "select";
1952
+ TraitInputType["Checkbox"] = "checkbox";
1953
+ TraitInputType["Color"] = "colorPicker";
1954
+ TraitInputType["DirectionalSize"] = "directionalSize";
1955
+ TraitInputType["SelectButton"] = "selectButton";
1956
+ TraitInputType["Scrub"] = "scrub";
1957
+ TraitInputType["Range"] = "range";
1958
+ TraitInputType["Icon"] = "icon";
1959
+ TraitInputType["Media"] = "media";
1960
+ TraitInputType["FontFamily"] = "fontFamily";
1961
+ TraitInputType["GridTemplate"] = "gridTemplate";
1962
+ TraitInputType["SectionLabel"] = "sectionLabel";
1963
+ TraitInputType["Textarea"] = "textarea";
1964
+ })(TraitInputType || (TraitInputType = {}));
1965
+ function generateNodeId() {
1966
+ return Math.random().toString(36).substr(2, 9);
1967
+ }
1968
+ class ComponentConfig {
1969
+ constructor(type, selector, icon = 'ph-thin ph-cube', category = 'General') {
1970
+ this.id = generateNodeId();
1971
+ this.type = type;
1972
+ this.selector = selector;
1973
+ this.icon = icon;
1974
+ this.category = category;
1975
+ this.inputs = {};
1976
+ this.traits = [];
1977
+ this.styleTraits = [];
1872
1978
  }
1873
- /** Point Moveable at the selected element (skips inline-edit + preview-clone targets). */
1874
- select(element, node) {
1875
- this.target = null;
1876
- this.node = null;
1877
- this.sourceId = null;
1878
- if (!element || !node || !this.container) {
1879
- this.destroyMoveable();
1880
- return;
1979
+ /** Override specific trait defaults after styleTraits are set.
1980
+ * Call this at the end of each subclass constructor. */
1981
+ applyStyleDefaults(defaults) {
1982
+ // Single source of truth for container-type width defaults: when a subclass does
1983
+ // not set width explicitly, fill it from the shared DEFAULT_WIDTH_BY_TYPE map — the
1984
+ // same map the SSR engine resolves from. See render-parity.md.
1985
+ if (defaults['width'] === undefined && DEFAULT_WIDTH_BY_TYPE[this.type] !== undefined) {
1986
+ defaults = { ...defaults, width: DEFAULT_WIDTH_BY_TYPE[this.type] };
1881
1987
  }
1882
- // Preview clones (repeater/slider rows) and hidden nodes are not editable.
1883
- if (element.closest('[data-iox-hidden]') || node.__previewClone) {
1884
- this.destroyMoveable();
1885
- return;
1988
+ for (const group of this.styleTraits) {
1989
+ for (const trait of group.traits) {
1990
+ if (defaults[trait.name] !== undefined) {
1991
+ trait.default = defaults[trait.name];
1992
+ }
1993
+ }
1886
1994
  }
1887
- this.target = element;
1888
- this.node = node;
1889
- this.sourceId = this.resolveDropzoneId(element);
1890
- this.ensureMoveable();
1891
- this.moveable.target = element;
1892
- // Drag is initiated by grabbing the ELEMENT itself (like interact.js) — not a Moveable
1893
- // overlay area — so a plain click still passes through to selection/child-selection and only
1894
- // a movement past the threshold starts a drag. Keeps Moveable visually silent (no hit-area box).
1895
- this.moveable.dragTarget = element;
1896
- this.moveable.updateRect();
1897
1995
  }
1898
- /** Recompute the control-box position (call on scroll/zoom/layout changes). */
1899
- refresh() { this.moveable?.updateRect(); }
1900
- destroy() { this.destroyMoveable(); this.container = null; }
1901
- // ── internals ────────────────────────────────────────────────────────────
1902
- ensureMoveable() {
1903
- if (this.moveable)
1904
- return;
1905
- this.moveable = new Moveable(this.container, {
1906
- target: undefined,
1907
- draggable: true,
1908
- resizable: false, // Phase 3
1909
- rotatable: false, // Phase 3 (out-of-flow only)
1910
- origin: false,
1911
- zoom: 1,
1912
- // INVISIBLE interaction layer: the builder's OverlayService owns ALL selection visuals
1913
- // (pink outline, hover margin/padding rings, toolbar, badges). Moveable must draw nothing
1914
- // here — it only provides the draggable hit-area. `iox-moveable-silent` + the SCSS in
1915
- // builder.component.scss hide any residual control chrome and drop it below the overlay.
1916
- hideDefaultLines: true,
1917
- renderDirections: [],
1918
- className: 'iox-moveable-silent',
1919
- });
1920
- this.moveable.on('dragStart', (e) => {
1921
- // Never start a drag from inside inline text editing.
1922
- if (e.inputEvent?.target?.closest?.('[contenteditable="true"]')) {
1923
- e.stop?.();
1924
- }
1925
- });
1926
- // Transient transform only — visual feedback during the drag.
1927
- this.moveable.on('drag', (e) => { e.target.style.transform = e.transform; });
1928
- this.moveable.on('dragEnd', (e) => this.commitDrag(e));
1996
+ }
1997
+ class TraitConfig {
1998
+ constructor(name, label, type, options, defaultValue, inline, showWhen) {
1999
+ this.name = name;
2000
+ this.label = label;
2001
+ this.type = type;
2002
+ this.options = options;
2003
+ this.default = defaultValue;
2004
+ this.inline = inline;
2005
+ this.showWhen = showWhen;
1929
2006
  }
1930
- destroyMoveable() {
1931
- if (this.moveable) {
1932
- this.moveable.destroy();
1933
- this.moveable = null;
1934
- }
1935
- }
1936
- commitDrag(e) {
1937
- const el = e.target;
1938
- // C1: never persist the transform — reset it; the result is a tree reorder, not a position.
1939
- el.style.transform = '';
1940
- if (!this.node)
1941
- return;
1942
- const pointer = this.lastPointer(e);
1943
- if (!pointer) {
1944
- this.onReorder?.();
1945
- return;
1946
- }
1947
- const targetId = this.dropzoneUnderPoint(pointer.x, pointer.y, el);
1948
- if (!targetId) {
1949
- this.onReorder?.();
1950
- return;
1951
- }
1952
- const children = this.dragEngine.getData(targetId);
1953
- const cdr = this.dragEngine.getCdr(targetId);
1954
- if (!children || !cdr) {
1955
- this.onReorder?.();
1956
- return;
1957
- }
1958
- const insertIndex = this.calcInsertIndex(targetId, pointer.y);
1959
- const srcChildren = this.sourceId ? this.dragEngine.getData(this.sourceId) : undefined;
1960
- const sourceIndex = srcChildren ? srcChildren.indexOf(this.node) : -1;
1961
- const payload = { type: 'internal', data: this.node, sourceId: this.sourceId ?? null, sourceIndex };
1962
- this.dragEngine.handleDrop(children, { payload, targetId, insertIndex }, targetId, cdr, () => {
1963
- this.onReorder?.();
1964
- // Re-point the control box at the moved element's (possibly new) host.
1965
- setTimeout(() => this.moveable?.updateRect(), 0);
1966
- });
1967
- }
1968
- /** Final pointer position from the Moveable end event (client/viewport px). */
1969
- lastPointer(e) {
1970
- const ie = e.inputEvent;
1971
- if (ie && typeof ie.clientX === 'number')
1972
- return { x: ie.clientX, y: ie.clientY };
1973
- if (Array.isArray(e.clientX))
1974
- return { x: e.clientX[0], y: e.clientY[0] };
1975
- if (typeof e.clientX === 'number')
1976
- return { x: e.clientX, y: e.clientY };
1977
- return null;
1978
- }
1979
- /** Walk up from an element to the nearest registered dropzone id (el.id is stamped by DragEngine). */
1980
- resolveDropzoneId(el) {
1981
- let e = el;
1982
- while (e) {
1983
- if (e.id && this.dragEngine.getData(e.id) !== undefined)
1984
- return e.id;
1985
- e = e.parentElement;
1986
- }
1987
- return null;
1988
- }
1989
- /** Topmost registered dropzone under the pointer that is NOT inside the dragged element's subtree. */
1990
- dropzoneUnderPoint(x, y, draggedEl) {
1991
- const stack = document.elementsFromPoint(x, y);
1992
- for (const el of stack) {
1993
- const id = this.resolveDropzoneId(el);
1994
- if (!id)
1995
- continue;
1996
- const zoneEl = document.getElementById(id);
1997
- if (zoneEl && draggedEl.contains(zoneEl))
1998
- continue; // don't drop into own subtree
1999
- return id;
2000
- }
2001
- return null;
2002
- }
2003
- /** Insertion index within a dropzone by comparing pointer Y to each child's vertical midpoint. */
2004
- calcInsertIndex(targetId, y) {
2005
- const zoneEl = document.getElementById(targetId);
2006
- if (!zoneEl)
2007
- return 0;
2008
- const childWraps = Array.from(zoneEl.children).filter((c) => c.matches('.container-child, .section-child, [data-iox-draggable]'));
2009
- let idx = 0;
2010
- for (const w of childWraps) {
2011
- const r = w.getBoundingClientRect();
2012
- if (y > r.top + r.height / 2)
2013
- idx++;
2014
- }
2015
- return idx;
2016
- }
2017
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, deps: [{ token: DragEngineService }], target: i0.ɵɵFactoryTarget.Injectable }); }
2018
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, providedIn: 'root' }); }
2019
- }
2020
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, decorators: [{
2021
- type: Injectable,
2022
- args: [{ providedIn: 'root' }]
2023
- }], ctorParameters: () => [{ type: DragEngineService }] });
2024
-
2025
- // Virtual trait → composed CSS property helpers.
2026
- //
2027
- // Several style traits are "virtual" — they don't map 1:1 to a CSS property but
2028
- // are components of a multi-function CSS value. composeVirtualTraits() strips
2029
- // these virtual keys from the style map and replaces them with the correctly
2030
- // composed CSS properties (filter, backdrop-filter, transform, transition).
2031
- //
2032
- // The same function is used by the render directive (initial mount + state
2033
- // restore) and by the style panel's onUpdate / setStateOverride paths, so
2034
- // the CSS output is always consistent regardless of how styles are applied.
2035
- // ─── Filter ──────────────────────────────────────────────────────────────────
2036
- const FILTER_FUNS = [
2037
- ['filterBlur', 'blur', '0px'],
2038
- ['filterBrightness', 'brightness', '1'],
2039
- ['filterContrast', 'contrast', '1'],
2040
- ['filterGrayscale', 'grayscale', '0'],
2041
- ['filterSaturate', 'saturate', '1'],
2042
- ['filterHueRotate', 'hue-rotate', '0deg'],
2043
- ['filterSepia', 'sepia', '0'],
2044
- ['filterInvert', 'invert', '0'],
2045
- ];
2046
- // ─── Backdrop-filter ─────────────────────────────────────────────────────────
2047
- const BACKDROP_FUNS = [
2048
- ['backdropBlur', 'blur', '0px'],
2049
- ['backdropBrightness', 'brightness', '1'],
2050
- ['backdropContrast', 'contrast', '1'],
2051
- ['backdropSaturate', 'saturate', '1'],
2052
- ];
2053
- // ─── Transform ───────────────────────────────────────────────────────────────
2054
- const TRANSFORM_FUNS = [
2055
- ['translateX', 'translateX', '0px'],
2056
- ['translateY', 'translateY', '0px'],
2057
- ['scaleX', 'scaleX', '1'],
2058
- ['scaleY', 'scaleY', '1'],
2059
- ['rotate', 'rotate', '0deg'],
2060
- ['skewX', 'skewX', '0deg'],
2061
- ['skewY', 'skewY', '0deg'],
2062
- ];
2063
- // ─── ::marker pseudo-element ─────────────────────────────────────────────────
2064
- const MARKER_FUNS = [
2065
- ['markerColor', 'color'],
2066
- ['markerFontSize', 'fontSize'],
2067
- ];
2068
- // ─── All virtual trait names ──────────────────────────────────────────────────
2069
- const VIRTUAL_TRAIT_KEYS = new Set([
2070
- ...FILTER_FUNS.map(([t]) => t),
2071
- ...BACKDROP_FUNS.map(([t]) => t),
2072
- ...TRANSFORM_FUNS.map(([t]) => t),
2073
- 'transitionDuration', 'transitionTimingFunction', 'transitionDelay',
2074
- ...MARKER_FUNS.map(([t]) => t),
2075
- ]);
2076
- // ─── Helpers ─────────────────────────────────────────────────────────────────
2077
- function buildFns(funs, src) {
2078
- return funs
2079
- .filter(([t, , d]) => src[t] != null && src[t] !== '' && src[t] !== d)
2080
- .map(([t, fn]) => `${fn}(${src[t]})`)
2081
- .join(' ');
2082
- }
2083
- // ─── Public API ───────────────────────────────────────────────────────────────
2084
- /**
2085
- * Strip virtual trait keys from `raw` and emit their composed CSS equivalents.
2086
- *
2087
- * When composing a partial state-override map (hover, active…), pass the full
2088
- * base style map as `base` so non-overridden components (e.g. filterBrightness
2089
- * when only filterBlur is overridden) are preserved in the composed output.
2090
- * When operating on a complete style map, omit `base` — it defaults to `raw`.
2091
- */
2092
- function composeVirtualTraits(raw, base = raw) {
2093
- // Copy all non-virtual properties as-is
2094
- const result = {};
2095
- for (const [k, v] of Object.entries(raw)) {
2096
- if (!VIRTUAL_TRAIT_KEYS.has(k))
2097
- result[k] = v;
2098
- }
2099
- // Merge base values for any virtual key absent from the partial override map
2100
- const merged = (funs) => {
2101
- const m = {};
2102
- for (const [t] of funs)
2103
- m[t] = raw[t] ?? base[t];
2104
- return m;
2105
- };
2106
- // filter
2107
- const filterStr = buildFns(FILTER_FUNS, merged(FILTER_FUNS));
2108
- if (filterStr)
2109
- result['filter'] = filterStr;
2110
- // backdrop-filter
2111
- const backdropStr = buildFns(BACKDROP_FUNS, merged(BACKDROP_FUNS));
2112
- if (backdropStr)
2113
- result['backdropFilter'] = backdropStr;
2114
- // transform
2115
- const transformStr = buildFns(TRANSFORM_FUNS, merged(TRANSFORM_FUNS));
2116
- if (transformStr)
2117
- result['transform'] = transformStr;
2118
- // transition — only emit when duration is non-zero
2119
- const dur = raw['transitionDuration'] ?? base['transitionDuration'];
2120
- const ease = raw['transitionTimingFunction'] ?? base['transitionTimingFunction'];
2121
- const del = raw['transitionDelay'] ?? base['transitionDelay'];
2122
- if (dur && ease && dur !== '0ms' && dur !== '0s') {
2123
- const delPart = del && del !== '0ms' && del !== '0s' ? ` ${del}` : '';
2124
- result['transition'] = `all ${dur} ${ease}${delPart}`;
2125
- }
2126
- // ::marker pseudo-element — collected into __markerStyles so StyleRegistryService
2127
- // can emit a separate `.iox-node-{id}::marker { … }` rule.
2128
- const markerStyles = {};
2129
- for (const [trait, cssProp] of MARKER_FUNS) {
2130
- const val = raw[trait] ?? base[trait];
2131
- if (val != null && val !== '')
2132
- markerStyles[cssProp] = val;
2133
- }
2134
- if (Object.keys(markerStyles).length) {
2135
- result['__markerStyles'] = markerStyles;
2136
- }
2137
- return result;
2138
- }
2139
-
2140
- var StyleCategory;
2141
- (function (StyleCategory) {
2142
- StyleCategory["Layout"] = "Layout";
2143
- StyleCategory["Size"] = "Size";
2144
- StyleCategory["Typography"] = "Typography";
2145
- StyleCategory["Spacing"] = "Spacing";
2146
- StyleCategory["Border"] = "Border";
2147
- StyleCategory["Position"] = "Position";
2148
- StyleCategory["Background"] = "Background";
2149
- StyleCategory["Effects"] = "Effects";
2150
- StyleCategory["List"] = "List";
2151
- // Legacy — kept for backward compat; prefer Size
2152
- StyleCategory["Dimensions"] = "Dimensions";
2153
- })(StyleCategory || (StyleCategory = {}));
2154
- var DataSourceType;
2155
- (function (DataSourceType) {
2156
- DataSourceType["Cms"] = "cms";
2157
- DataSourceType["Shop"] = "shop";
2158
- DataSourceType["ExternalApi"] = "external-api";
2159
- DataSourceType["InternalApi"] = "internal-api";
2160
- DataSourceType["Static"] = "static";
2161
- })(DataSourceType || (DataSourceType = {}));
2162
- /**
2163
- * Maps each action type to its logical inverse for auto-populating toggle
2164
- * reverse actions. Types without a natural inverse are omitted.
2165
- */
2166
- const INVERSE_ACTION = {
2167
- fadeIn: 'fadeOut',
2168
- fadeOut: 'fadeIn',
2169
- show: 'hide',
2170
- hide: 'show',
2171
- scaleIn: 'scaleOut',
2172
- scaleOut: 'scaleIn',
2173
- moveUp: 'moveDown',
2174
- moveDown: 'moveUp',
2175
- moveLeft: 'moveRight',
2176
- moveRight: 'moveLeft',
2177
- };
2178
- const TRIGGER_OPTIONS = [
2179
- { value: 'pageLoad', label: 'Page load', icon: 'ph-thin ph-play' },
2180
- { value: 'viewportEnter', label: 'Enter viewport', icon: 'ph-thin ph-eye' },
2181
- { value: 'click', label: 'Click', icon: 'ph-thin ph-cursor-click' },
2182
- { value: 'hover', label: 'Hover', icon: 'ph-thin ph-hand-pointing' },
2183
- { value: 'scrollProgress', label: 'Scroll progress', icon: 'ph-thin ph-arrow-fat-lines-down' },
2184
- ];
2185
- const ACTION_TYPE_OPTIONS = [
2186
- { value: 'fadeIn', label: 'Fade in' },
2187
- { value: 'fadeOut', label: 'Fade out' },
2188
- { value: 'moveUp', label: 'Move up' },
2189
- { value: 'moveDown', label: 'Move down' },
2190
- { value: 'moveLeft', label: 'Move left' },
2191
- { value: 'moveRight', label: 'Move right' },
2192
- { value: 'scaleIn', label: 'Scale in' },
2193
- { value: 'scaleOut', label: 'Scale out' },
2194
- { value: 'rotate', label: 'Rotate' },
2195
- { value: 'show', label: 'Show' },
2196
- { value: 'hide', label: 'Hide' },
2197
- { value: 'toggleVisibility', label: 'Toggle visibility' },
2198
- ];
2199
- const EASING_OPTIONS = ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'];
2200
- /** Interaction pseudo-classes — compiled to .iox-node-{id}:state */
2201
- const INTERACTION_STATES = ['hover', 'active', 'focus'];
2202
- /** Structural pseudo-classes — compiled to .iox-outer-{id}:state */
2203
- const STRUCTURAL_STATES = ['first-child', 'last-child', 'nth-child(odd)', 'nth-child(even)'];
2204
- /**
2205
- * Relational pseudo-classes — the styled element is a descendant, the pseudo-class
2206
- * lives on an ANCESTOR. Compiled to `.iox-node-{parentId}:{base} .iox-node-{id} { … }`,
2207
- * so they require the ancestor's css id (`relativeToId`) at compile time.
2208
- * `RELATIONAL_STATE_BASE` maps each relational state to the ancestor pseudo-class.
2209
- */
2210
- const RELATIONAL_STATES = ['parent-hover'];
2211
- const RELATIONAL_STATE_BASE = {
2212
- 'parent-hover': 'hover',
2213
- };
2214
- const SUPPORTED_STATES = [...INTERACTION_STATES, ...STRUCTURAL_STATES, ...RELATIONAL_STATES];
2215
- /**
2216
- * Build a full StyleTraitGroup[] schema populated with values from a flat
2217
- * styleProps map. Used when editing a preset in the Style panel — the panel
2218
- * receives the same `StyleTraitGroup[]` shape regardless of context.
2219
- */
2220
- function buildPresetStyleTraits(styleProps) {
2221
- const allGroups = [
2222
- new LayoutGroupStyleConfig(),
2223
- new SizeGroupStyleConfig(),
2224
- new BackgroundGroupStyleConfig(),
2225
- new SpacingGroupStyleConfig(),
2226
- new BorderGroupStyleConfig(),
2227
- new TypographyGroupStyleConfig(),
2228
- new EffectsGroupStyleConfig(),
2229
- new PositionGroupStyleConfig(),
2230
- ];
2231
- return allGroups.map(group => ({
2232
- ...group,
2233
- traits: group.traits.map(trait => ({
2234
- ...trait,
2235
- default: styleProps[trait.name] !== undefined ? styleProps[trait.name] : trait.default,
2236
- })),
2237
- }));
2238
- }
2239
- var TraitInputType;
2240
- (function (TraitInputType) {
2241
- TraitInputType["Text"] = "text";
2242
- TraitInputType["Number"] = "number";
2243
- TraitInputType["Select"] = "select";
2244
- TraitInputType["Checkbox"] = "checkbox";
2245
- TraitInputType["Color"] = "colorPicker";
2246
- TraitInputType["DirectionalSize"] = "directionalSize";
2247
- TraitInputType["SelectButton"] = "selectButton";
2248
- TraitInputType["Scrub"] = "scrub";
2249
- TraitInputType["Range"] = "range";
2250
- TraitInputType["Icon"] = "icon";
2251
- TraitInputType["Media"] = "media";
2252
- TraitInputType["FontFamily"] = "fontFamily";
2253
- TraitInputType["GridTemplate"] = "gridTemplate";
2254
- TraitInputType["SectionLabel"] = "sectionLabel";
2255
- TraitInputType["Textarea"] = "textarea";
2256
- })(TraitInputType || (TraitInputType = {}));
2257
- function generateNodeId() {
2258
- return Math.random().toString(36).substr(2, 9);
2259
- }
2260
- class ComponentConfig {
2261
- constructor(type, selector, icon = 'ph-thin ph-cube', category = 'General') {
2262
- this.id = generateNodeId();
2263
- this.type = type;
2264
- this.selector = selector;
2265
- this.icon = icon;
2266
- this.category = category;
2267
- this.inputs = {};
2268
- this.traits = [];
2269
- this.styleTraits = [];
2270
- }
2271
- /** Override specific trait defaults after styleTraits are set.
2272
- * Call this at the end of each subclass constructor. */
2273
- applyStyleDefaults(defaults) {
2274
- // Single source of truth for container-type width defaults: when a subclass does
2275
- // not set width explicitly, fill it from the shared DEFAULT_WIDTH_BY_TYPE map — the
2276
- // same map the SSR engine resolves from. See render-parity.md.
2277
- if (defaults['width'] === undefined && DEFAULT_WIDTH_BY_TYPE[this.type] !== undefined) {
2278
- defaults = { ...defaults, width: DEFAULT_WIDTH_BY_TYPE[this.type] };
2279
- }
2280
- for (const group of this.styleTraits) {
2281
- for (const trait of group.traits) {
2282
- if (defaults[trait.name] !== undefined) {
2283
- trait.default = defaults[trait.name];
2284
- }
2285
- }
2286
- }
2287
- }
2288
- }
2289
- class TraitConfig {
2290
- constructor(name, label, type, options, defaultValue, inline, showWhen) {
2291
- this.name = name;
2292
- this.label = label;
2293
- this.type = type;
2294
- this.options = options;
2295
- this.default = defaultValue;
2296
- this.inline = inline;
2297
- this.showWhen = showWhen;
2298
- }
2299
- }
2300
- class GroupStyleConfig {
2301
- constructor(category, traits) {
2302
- this.category = category;
2303
- this.traits = traits;
2007
+ }
2008
+ class GroupStyleConfig {
2009
+ constructor(category, traits) {
2010
+ this.category = category;
2011
+ this.traits = traits;
2304
2012
  }
2305
2013
  }
2306
2014
  // ─── Unit constants ───────────────────────────────────────────────────────────
@@ -2773,78 +2481,421 @@ class StyleRegistryService {
2773
2481
  else {
2774
2482
  this.rules.delete(nodeId);
2775
2483
  }
2776
- this.flush();
2484
+ this.flush();
2485
+ }
2486
+ /**
2487
+ * Write the org-level design token variables as a `:root { … }` block.
2488
+ * Token names should be CSS custom property names without the `--` prefix
2489
+ * (e.g., `{ 'iox-primary-color': '#cb9090' }`).
2490
+ * Pass an empty object to clear all tokens.
2491
+ */
2492
+ upsertTokens(tokens) {
2493
+ const entries = Object.entries(tokens)
2494
+ .filter(([, v]) => v !== undefined && v !== null && v !== '')
2495
+ .map(([k, v]) => ` --${k}: ${v};`);
2496
+ if (entries.length) {
2497
+ this.rules.set('__tokens__', `:root {\n${entries.join('\n')}\n}`);
2498
+ }
2499
+ else {
2500
+ this.rules.delete('__tokens__');
2501
+ }
2502
+ this.flush();
2503
+ }
2504
+ remove(nodeId) {
2505
+ if (!nodeId)
2506
+ return;
2507
+ // Remove base rule and all state rules for this node
2508
+ this.rules.delete(nodeId);
2509
+ for (const key of this.rules.keys()) {
2510
+ if (key.startsWith(`${nodeId}:`)) {
2511
+ this.rules.delete(key);
2512
+ }
2513
+ }
2514
+ this.flush();
2515
+ }
2516
+ destroy() {
2517
+ this.rules.clear();
2518
+ if (this.styleEl) {
2519
+ this.styleEl.remove();
2520
+ this.styleEl = null;
2521
+ }
2522
+ }
2523
+ compile(className, styles) {
2524
+ // Declaration compilation (kebab-case, viewport-unit rewrite, !important) lives in
2525
+ // the shared style-compiler so the SSR engine produces byte-identical output.
2526
+ // The inner/outer partition + selector emission stay here (builder-only concern).
2527
+ const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS);
2528
+ if (!decls.length)
2529
+ return '';
2530
+ const body = decls.map(d => ` ${d};`).join('\n');
2531
+ return `.${className} {\n${body}\n}`;
2532
+ }
2533
+ flush() {
2534
+ if (!this.styleEl || this._flushPending)
2535
+ return;
2536
+ this._flushPending = true;
2537
+ requestAnimationFrame(() => {
2538
+ this._flushPending = false;
2539
+ if (!this.styleEl)
2540
+ return;
2541
+ // __tokens__ always first so variables are available to all subsequent rules
2542
+ const tokenBlock = this.rules.get('__tokens__');
2543
+ const rest = Array.from(this.rules.entries())
2544
+ .filter(([k]) => k !== '__tokens__')
2545
+ .map(([, v]) => v);
2546
+ this.styleEl.textContent = [tokenBlock, ...rest].filter(Boolean).join('\n');
2547
+ this.changes$.next();
2548
+ });
2549
+ }
2550
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2551
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService }); }
2552
+ }
2553
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, decorators: [{
2554
+ type: Injectable
2555
+ }] });
2556
+
2557
+ /**
2558
+ * MoveableCanvasService — Phase 1 of the Moveable + Selecto migration
2559
+ * (see iox-ai-guidance/architecture/builder/moveable-migration.md).
2560
+ *
2561
+ * Drives DRAG for the SELECTED element via Moveable instead of interact.js, while REUSING the
2562
+ * proven reorder machinery (`DragEngineService.handleDrop`). It is a pure INPUT device: during a
2563
+ * drag it applies a TRANSIENT transform, and on drop it RESETS the transform to nothing and instead
2564
+ * reorders the layout tree (constraint C1 — a persisted transform on an in-flow canvas element
2565
+ * breaks Lenis scroll). Everything here runs ONLY when `moveableMode` is on; interact.js is
2566
+ * untouched, so the flag-off path is byte-identical to today.
2567
+ *
2568
+ * Scale: Moveable reads `getBoundingClientRect()` (already post-scale) and works in viewport
2569
+ * client coordinates, so drop hit-testing via `elementsFromPoint` / rects is scale-correct without
2570
+ * extra math. The control box is rendered into the same overlay container the selection overlay uses.
2571
+ */
2572
+ class MoveableCanvasService {
2573
+ constructor(dragEngine, styleRegistry) {
2574
+ this.dragEngine = dragEngine;
2575
+ this.styleRegistry = styleRegistry;
2576
+ this.moveable = null;
2577
+ this.container = null;
2578
+ this.scaleGetter = () => 1;
2579
+ this.target = null;
2580
+ this.node = null;
2581
+ this.sourceId = null;
2582
+ /** Called after a successful reorder so the host can emit LAYOUT_CHANGED / refresh selection. */
2583
+ this.onReorder = null;
2584
+ /** Called after a geometry commit (resize) so the host can emit LAYOUT_CHANGED (undo/save). */
2585
+ this.onLayoutChanged = null;
2586
+ }
2587
+ /** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom. */
2588
+ init(container, scaleGetter) {
2589
+ this.container = container;
2590
+ this.scaleGetter = scaleGetter;
2591
+ }
2592
+ /** Point Moveable at the selected element (skips inline-edit + preview-clone targets). */
2593
+ select(element, node) {
2594
+ this.target = null;
2595
+ this.node = null;
2596
+ this.sourceId = null;
2597
+ if (!element || !node || !this.container) {
2598
+ this.destroyMoveable();
2599
+ return;
2600
+ }
2601
+ // Preview clones (repeater/slider rows) and hidden nodes are not editable.
2602
+ if (element.closest('[data-iox-hidden]') || node.__previewClone) {
2603
+ this.destroyMoveable();
2604
+ return;
2605
+ }
2606
+ this.target = element;
2607
+ this.node = node;
2608
+ this.sourceId = this.resolveDropzoneId(element);
2609
+ this.ensureMoveable();
2610
+ this.moveable.target = element;
2611
+ // Drag is initiated by grabbing the ELEMENT itself (like interact.js) — not a Moveable
2612
+ // overlay area — so a plain click still passes through to selection/child-selection and only
2613
+ // a movement past the threshold starts a drag. Keeps Moveable visually silent (no hit-area box).
2614
+ this.moveable.dragTarget = element;
2615
+ this.moveable.updateRect();
2616
+ }
2617
+ /** Recompute the control-box position (call on scroll/zoom/layout changes). */
2618
+ refresh() { this.moveable?.updateRect(); }
2619
+ destroy() { this.destroyMoveable(); this.container = null; }
2620
+ // ── internals ────────────────────────────────────────────────────────────
2621
+ ensureMoveable() {
2622
+ if (this.moveable)
2623
+ return;
2624
+ this.moveable = new Moveable(this.container, {
2625
+ target: undefined,
2626
+ draggable: true,
2627
+ resizable: true, // Phase 3 — corner handles → styleProps width/height (C2)
2628
+ rotatable: false, // (out-of-flow only — C1)
2629
+ origin: false,
2630
+ zoom: 1,
2631
+ // The OverlayService owns ALL selection visuals (pink outline, hover margin/padding rings,
2632
+ // toolbar, badges) — Moveable draws NO box/lines. It shows ONLY the 4 corner RESIZE handles
2633
+ // (renderDirections) and provides the drag hit-area. `iox-moveable-silent` + SCSS in
2634
+ // builder.component.scss hide the lines and style the handles to match, below the overlay.
2635
+ hideDefaultLines: true,
2636
+ renderDirections: ['nw', 'ne', 'sw', 'se'],
2637
+ className: 'iox-moveable-silent',
2638
+ });
2639
+ this.moveable.on('dragStart', (e) => {
2640
+ // Never start a drag from inside inline text editing.
2641
+ if (e.inputEvent?.target?.closest?.('[contenteditable="true"]')) {
2642
+ e.stop?.();
2643
+ }
2644
+ });
2645
+ // Transient transform only — visual feedback during the drag (C1: reset on drop).
2646
+ this.moveable.on('drag', (e) => { e.target.style.transform = e.transform; });
2647
+ this.moveable.on('dragEnd', (e) => this.commitDrag(e));
2648
+ // Resize: live inline width/height for feedback, then commit to styleProps on end (C2 — never
2649
+ // a transform:scale, so no Lenis-scroll break; the panel + save + SSR all see the real size).
2650
+ this.moveable.on('resize', (e) => {
2651
+ e.target.style.width = `${Math.round(e.width)}px`;
2652
+ e.target.style.height = `${Math.round(e.height)}px`;
2653
+ e.target.style.transform = e.drag.transform; // keeps the box anchored while resizing
2654
+ });
2655
+ this.moveable.on('resizeEnd', (e) => this.commitResize(e));
2656
+ }
2657
+ destroyMoveable() {
2658
+ if (this.moveable) {
2659
+ this.moveable.destroy();
2660
+ this.moveable = null;
2661
+ }
2777
2662
  }
2778
- /**
2779
- * Write the org-level design token variables as a `:root { … }` block.
2780
- * Token names should be CSS custom property names without the `--` prefix
2781
- * (e.g., `{ 'iox-primary-color': '#cb9090' }`).
2782
- * Pass an empty object to clear all tokens.
2783
- */
2784
- upsertTokens(tokens) {
2785
- const entries = Object.entries(tokens)
2786
- .filter(([, v]) => v !== undefined && v !== null && v !== '')
2787
- .map(([k, v]) => ` --${k}: ${v};`);
2788
- if (entries.length) {
2789
- this.rules.set('__tokens__', `:root {\n${entries.join('\n')}\n}`);
2663
+ commitDrag(e) {
2664
+ const el = e.target;
2665
+ // C1: never persist the transform — reset it; the result is a tree reorder, not a position.
2666
+ el.style.transform = '';
2667
+ if (!this.node)
2668
+ return;
2669
+ const pointer = this.lastPointer(e);
2670
+ if (!pointer) {
2671
+ this.onReorder?.();
2672
+ return;
2790
2673
  }
2791
- else {
2792
- this.rules.delete('__tokens__');
2674
+ const targetId = this.dropzoneUnderPoint(pointer.x, pointer.y, el);
2675
+ if (!targetId) {
2676
+ this.onReorder?.();
2677
+ return;
2793
2678
  }
2794
- this.flush();
2679
+ const children = this.dragEngine.getData(targetId);
2680
+ const cdr = this.dragEngine.getCdr(targetId);
2681
+ if (!children || !cdr) {
2682
+ this.onReorder?.();
2683
+ return;
2684
+ }
2685
+ const insertIndex = this.calcInsertIndex(targetId, pointer.y);
2686
+ const srcChildren = this.sourceId ? this.dragEngine.getData(this.sourceId) : undefined;
2687
+ const sourceIndex = srcChildren ? srcChildren.indexOf(this.node) : -1;
2688
+ const payload = { type: 'internal', data: this.node, sourceId: this.sourceId ?? null, sourceIndex };
2689
+ this.dragEngine.handleDrop(children, { payload, targetId, insertIndex }, targetId, cdr, () => {
2690
+ this.onReorder?.();
2691
+ // Re-point the control box at the moved element's (possibly new) host.
2692
+ setTimeout(() => this.moveable?.updateRect(), 0);
2693
+ });
2795
2694
  }
2796
- remove(nodeId) {
2797
- if (!nodeId)
2695
+ commitResize(e) {
2696
+ const el = e.target;
2697
+ el.style.transform = ''; // C1: never persist a transform on an in-flow canvas element
2698
+ if (!this.node) {
2798
2699
  return;
2799
- // Remove base rule and all state rules for this node
2800
- this.rules.delete(nodeId);
2801
- for (const key of this.rules.keys()) {
2802
- if (key.startsWith(`${nodeId}:`)) {
2803
- this.rules.delete(key);
2700
+ }
2701
+ const last = e.lastEvent ?? e;
2702
+ const w = Math.round(last.width ?? el.getBoundingClientRect().width);
2703
+ const h = Math.round(last.height ?? el.getBoundingClientRect().height);
2704
+ // C2: geometry goes to styleProps (width/height) — never a transform:scale.
2705
+ this.applyStyle(this.node, { width: `${w}px`, height: `${h}px` });
2706
+ // Clear the transient inline size so the styleProps `.iox-node` rule drives it (single source).
2707
+ el.style.width = '';
2708
+ el.style.height = '';
2709
+ this.onLayoutChanged?.();
2710
+ setTimeout(() => this.moveable?.updateRect(), 0);
2711
+ }
2712
+ /** Write geometry changes into the node's styleTraits (the serialized source of truth) and the
2713
+ * central stylesheet, so the style panel, save (self-describing), and SSR engine all agree. */
2714
+ applyStyle(node, changes) {
2715
+ const styles = {};
2716
+ for (const group of (node.styleTraits ?? [])) {
2717
+ for (const trait of (group.traits ?? [])) {
2718
+ if (trait.name in changes)
2719
+ trait.default = changes[trait.name];
2720
+ if (trait.name !== undefined && trait.default !== undefined)
2721
+ styles[trait.name] = trait.default;
2804
2722
  }
2805
2723
  }
2806
- this.flush();
2724
+ const cssId = node.styleId ?? node.id;
2725
+ if (cssId)
2726
+ this.styleRegistry.upsert(cssId, styles);
2807
2727
  }
2808
- destroy() {
2809
- this.rules.clear();
2810
- if (this.styleEl) {
2811
- this.styleEl.remove();
2812
- this.styleEl = null;
2728
+ /** Final pointer position from the Moveable end event (client/viewport px). */
2729
+ lastPointer(e) {
2730
+ const ie = e.inputEvent;
2731
+ if (ie && typeof ie.clientX === 'number')
2732
+ return { x: ie.clientX, y: ie.clientY };
2733
+ if (Array.isArray(e.clientX))
2734
+ return { x: e.clientX[0], y: e.clientY[0] };
2735
+ if (typeof e.clientX === 'number')
2736
+ return { x: e.clientX, y: e.clientY };
2737
+ return null;
2738
+ }
2739
+ /** Walk up from an element to the nearest registered dropzone id (el.id is stamped by DragEngine). */
2740
+ resolveDropzoneId(el) {
2741
+ let e = el;
2742
+ while (e) {
2743
+ if (e.id && this.dragEngine.getData(e.id) !== undefined)
2744
+ return e.id;
2745
+ e = e.parentElement;
2813
2746
  }
2747
+ return null;
2814
2748
  }
2815
- compile(className, styles) {
2816
- // Declaration compilation (kebab-case, viewport-unit rewrite, !important) lives in
2817
- // the shared style-compiler so the SSR engine produces byte-identical output.
2818
- // The inner/outer partition + selector emission stay here (builder-only concern).
2819
- const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS);
2820
- if (!decls.length)
2821
- return '';
2822
- const body = decls.map(d => ` ${d};`).join('\n');
2823
- return `.${className} {\n${body}\n}`;
2749
+ /** Topmost registered dropzone under the pointer that is NOT inside the dragged element's subtree. */
2750
+ dropzoneUnderPoint(x, y, draggedEl) {
2751
+ const stack = document.elementsFromPoint(x, y);
2752
+ for (const el of stack) {
2753
+ const id = this.resolveDropzoneId(el);
2754
+ if (!id)
2755
+ continue;
2756
+ const zoneEl = document.getElementById(id);
2757
+ if (zoneEl && draggedEl.contains(zoneEl))
2758
+ continue; // don't drop into own subtree
2759
+ return id;
2760
+ }
2761
+ return null;
2824
2762
  }
2825
- flush() {
2826
- if (!this.styleEl || this._flushPending)
2827
- return;
2828
- this._flushPending = true;
2829
- requestAnimationFrame(() => {
2830
- this._flushPending = false;
2831
- if (!this.styleEl)
2832
- return;
2833
- // __tokens__ always first so variables are available to all subsequent rules
2834
- const tokenBlock = this.rules.get('__tokens__');
2835
- const rest = Array.from(this.rules.entries())
2836
- .filter(([k]) => k !== '__tokens__')
2837
- .map(([, v]) => v);
2838
- this.styleEl.textContent = [tokenBlock, ...rest].filter(Boolean).join('\n');
2839
- this.changes$.next();
2840
- });
2763
+ /** Insertion index within a dropzone by comparing pointer Y to each child's vertical midpoint. */
2764
+ calcInsertIndex(targetId, y) {
2765
+ const zoneEl = document.getElementById(targetId);
2766
+ if (!zoneEl)
2767
+ return 0;
2768
+ const childWraps = Array.from(zoneEl.children).filter((c) => c.matches('.container-child, .section-child, [data-iox-draggable]'));
2769
+ let idx = 0;
2770
+ for (const w of childWraps) {
2771
+ const r = w.getBoundingClientRect();
2772
+ if (y > r.top + r.height / 2)
2773
+ idx++;
2774
+ }
2775
+ return idx;
2841
2776
  }
2842
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2843
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService }); }
2777
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, deps: [{ token: DragEngineService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Injectable }); }
2778
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, providedIn: 'root' }); }
2779
+ }
2780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, decorators: [{
2781
+ type: Injectable,
2782
+ args: [{ providedIn: 'root' }]
2783
+ }], ctorParameters: () => [{ type: DragEngineService }, { type: StyleRegistryService }] });
2784
+
2785
+ // Virtual trait → composed CSS property helpers.
2786
+ //
2787
+ // Several style traits are "virtual" — they don't map 1:1 to a CSS property but
2788
+ // are components of a multi-function CSS value. composeVirtualTraits() strips
2789
+ // these virtual keys from the style map and replaces them with the correctly
2790
+ // composed CSS properties (filter, backdrop-filter, transform, transition).
2791
+ //
2792
+ // The same function is used by the render directive (initial mount + state
2793
+ // restore) and by the style panel's onUpdate / setStateOverride paths, so
2794
+ // the CSS output is always consistent regardless of how styles are applied.
2795
+ // ─── Filter ──────────────────────────────────────────────────────────────────
2796
+ const FILTER_FUNS = [
2797
+ ['filterBlur', 'blur', '0px'],
2798
+ ['filterBrightness', 'brightness', '1'],
2799
+ ['filterContrast', 'contrast', '1'],
2800
+ ['filterGrayscale', 'grayscale', '0'],
2801
+ ['filterSaturate', 'saturate', '1'],
2802
+ ['filterHueRotate', 'hue-rotate', '0deg'],
2803
+ ['filterSepia', 'sepia', '0'],
2804
+ ['filterInvert', 'invert', '0'],
2805
+ ];
2806
+ // ─── Backdrop-filter ─────────────────────────────────────────────────────────
2807
+ const BACKDROP_FUNS = [
2808
+ ['backdropBlur', 'blur', '0px'],
2809
+ ['backdropBrightness', 'brightness', '1'],
2810
+ ['backdropContrast', 'contrast', '1'],
2811
+ ['backdropSaturate', 'saturate', '1'],
2812
+ ];
2813
+ // ─── Transform ───────────────────────────────────────────────────────────────
2814
+ const TRANSFORM_FUNS = [
2815
+ ['translateX', 'translateX', '0px'],
2816
+ ['translateY', 'translateY', '0px'],
2817
+ ['scaleX', 'scaleX', '1'],
2818
+ ['scaleY', 'scaleY', '1'],
2819
+ ['rotate', 'rotate', '0deg'],
2820
+ ['skewX', 'skewX', '0deg'],
2821
+ ['skewY', 'skewY', '0deg'],
2822
+ ];
2823
+ // ─── ::marker pseudo-element ─────────────────────────────────────────────────
2824
+ const MARKER_FUNS = [
2825
+ ['markerColor', 'color'],
2826
+ ['markerFontSize', 'fontSize'],
2827
+ ];
2828
+ // ─── All virtual trait names ──────────────────────────────────────────────────
2829
+ const VIRTUAL_TRAIT_KEYS = new Set([
2830
+ ...FILTER_FUNS.map(([t]) => t),
2831
+ ...BACKDROP_FUNS.map(([t]) => t),
2832
+ ...TRANSFORM_FUNS.map(([t]) => t),
2833
+ 'transitionDuration', 'transitionTimingFunction', 'transitionDelay',
2834
+ ...MARKER_FUNS.map(([t]) => t),
2835
+ ]);
2836
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
2837
+ function buildFns(funs, src) {
2838
+ return funs
2839
+ .filter(([t, , d]) => src[t] != null && src[t] !== '' && src[t] !== d)
2840
+ .map(([t, fn]) => `${fn}(${src[t]})`)
2841
+ .join(' ');
2842
+ }
2843
+ // ─── Public API ───────────────────────────────────────────────────────────────
2844
+ /**
2845
+ * Strip virtual trait keys from `raw` and emit their composed CSS equivalents.
2846
+ *
2847
+ * When composing a partial state-override map (hover, active…), pass the full
2848
+ * base style map as `base` so non-overridden components (e.g. filterBrightness
2849
+ * when only filterBlur is overridden) are preserved in the composed output.
2850
+ * When operating on a complete style map, omit `base` — it defaults to `raw`.
2851
+ */
2852
+ function composeVirtualTraits(raw, base = raw) {
2853
+ // Copy all non-virtual properties as-is
2854
+ const result = {};
2855
+ for (const [k, v] of Object.entries(raw)) {
2856
+ if (!VIRTUAL_TRAIT_KEYS.has(k))
2857
+ result[k] = v;
2858
+ }
2859
+ // Merge base values for any virtual key absent from the partial override map
2860
+ const merged = (funs) => {
2861
+ const m = {};
2862
+ for (const [t] of funs)
2863
+ m[t] = raw[t] ?? base[t];
2864
+ return m;
2865
+ };
2866
+ // filter
2867
+ const filterStr = buildFns(FILTER_FUNS, merged(FILTER_FUNS));
2868
+ if (filterStr)
2869
+ result['filter'] = filterStr;
2870
+ // backdrop-filter
2871
+ const backdropStr = buildFns(BACKDROP_FUNS, merged(BACKDROP_FUNS));
2872
+ if (backdropStr)
2873
+ result['backdropFilter'] = backdropStr;
2874
+ // transform
2875
+ const transformStr = buildFns(TRANSFORM_FUNS, merged(TRANSFORM_FUNS));
2876
+ if (transformStr)
2877
+ result['transform'] = transformStr;
2878
+ // transition — only emit when duration is non-zero
2879
+ const dur = raw['transitionDuration'] ?? base['transitionDuration'];
2880
+ const ease = raw['transitionTimingFunction'] ?? base['transitionTimingFunction'];
2881
+ const del = raw['transitionDelay'] ?? base['transitionDelay'];
2882
+ if (dur && ease && dur !== '0ms' && dur !== '0s') {
2883
+ const delPart = del && del !== '0ms' && del !== '0s' ? ` ${del}` : '';
2884
+ result['transition'] = `all ${dur} ${ease}${delPart}`;
2885
+ }
2886
+ // ::marker pseudo-element — collected into __markerStyles so StyleRegistryService
2887
+ // can emit a separate `.iox-node-{id}::marker { … }` rule.
2888
+ const markerStyles = {};
2889
+ for (const [trait, cssProp] of MARKER_FUNS) {
2890
+ const val = raw[trait] ?? base[trait];
2891
+ if (val != null && val !== '')
2892
+ markerStyles[cssProp] = val;
2893
+ }
2894
+ if (Object.keys(markerStyles).length) {
2895
+ result['__markerStyles'] = markerStyles;
2896
+ }
2897
+ return result;
2844
2898
  }
2845
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, decorators: [{
2846
- type: Injectable
2847
- }] });
2848
2899
 
2849
2900
  // Module-level counter — unique drop-list ID per Section instance across the whole app session.
2850
2901
  let dropListCounter = 0;
@@ -4254,6 +4305,8 @@ class BuilderComponent {
4254
4305
  // iox-ai-guidance/architecture/builder/moveable-migration.md.
4255
4306
  if (this.moveableMode && this.previewCanvas?.nativeElement) {
4256
4307
  this.moveableCanvas.init(this.previewCanvas.nativeElement, () => this.viewportService.getScale());
4308
+ // Resize commits geometry to styleProps → emit LAYOUT_CHANGED so undo/save pick it up.
4309
+ this.moveableCanvas.onLayoutChanged = () => this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
4257
4310
  this.sub?.add(this.overlayService.select$.subscribe(entry => {
4258
4311
  this.moveableCanvas.select(entry?.element ?? null, entry?.node ?? null);
4259
4312
  }));
@@ -4845,11 +4898,11 @@ class BuilderComponent {
4845
4898
  }
4846
4899
  }
4847
4900
  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 }], target: i0.ɵɵFactoryTarget.Component }); }
4848
- 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 = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"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\">\n <i class=\"ph-thin ph-file\"></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 === '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 <div *ngFor=\"let item of layout\"\n [ngClass]=\"['preview-node', 'iox-outer-' + (item.styleId ?? item.id)]\"\n [class.is-selected]=\"selectedItem === item\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <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:5!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-direction{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4$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: i14.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: i15.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "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"] }] }); }
4901
+ 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 = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"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\">\n <i class=\"ph-thin ph-file\"></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 === '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 <div *ngFor=\"let item of layout\"\n [ngClass]=\"['preview-node', 'iox-outer-' + (item.styleId ?? item.id)]\"\n [class.is-selected]=\"selectedItem === item\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <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.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4$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: i14.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: i15.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeDevice", "activeZoom", "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"] }] }); }
4849
4902
  }
4850
4903
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderComponent, decorators: [{
4851
4904
  type: Component,
4852
- args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"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\">\n <i class=\"ph-thin ph-file\"></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 === '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 <div *ngFor=\"let item of layout\"\n [ngClass]=\"['preview-node', 'iox-outer-' + (item.styleId ?? item.id)]\"\n [class.is-selected]=\"selectedItem === item\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <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:5!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-direction{display:none!important}\n"] }]
4905
+ args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n <header class=\"builder-header\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"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\">\n <i class=\"ph-thin ph-file\"></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 === '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 <div *ngFor=\"let item of layout\"\n [ngClass]=\"['preview-node', 'iox-outer-' + (item.styleId ?? item.id)]\"\n [class.is-selected]=\"selectedItem === item\"\n ioxDraggable\n [ioxDragData]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n\n <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"] }]
4853
4906
  }], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }], propDecorators: { layout: [{
4854
4907
  type: Input
4855
4908
  }], dataSources: [{