@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
|
|
246
|
-
*
|
|
247
|
-
* so
|
|
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
|
-
*
|
|
1843
|
-
*
|
|
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
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
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
|
-
/**
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
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
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
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
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
this.
|
|
1906
|
-
|
|
1907
|
-
|
|
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
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
.
|
|
2787
|
-
|
|
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
|
-
|
|
2792
|
-
|
|
2674
|
+
const targetId = this.dropzoneUnderPoint(pointer.x, pointer.y, el);
|
|
2675
|
+
if (!targetId) {
|
|
2676
|
+
this.onReorder?.();
|
|
2677
|
+
return;
|
|
2793
2678
|
}
|
|
2794
|
-
this.
|
|
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
|
-
|
|
2797
|
-
|
|
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
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
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
|
-
|
|
2724
|
+
const cssId = node.styleId ?? node.id;
|
|
2725
|
+
if (cssId)
|
|
2726
|
+
this.styleRegistry.upsert(cssId, styles);
|
|
2807
2727
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
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
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
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
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
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:
|
|
2843
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type:
|
|
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: [{
|