@vectoriox/iox-builder 1.7.3 → 1.7.5
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.
|
@@ -1838,305 +1838,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
1838
1838
|
type: Injectable
|
|
1839
1839
|
}] });
|
|
1840
1840
|
|
|
1841
|
-
/**
|
|
1842
|
-
* MoveableCanvasService — Phase 1 of the Moveable + Selecto migration
|
|
1843
|
-
* (see iox-ai-guidance/architecture/builder/moveable-migration.md).
|
|
1844
|
-
*
|
|
1845
|
-
* Drives DRAG for the SELECTED element via Moveable instead of interact.js, while REUSING the
|
|
1846
|
-
* proven reorder machinery (`DragEngineService.handleDrop`). It is a pure INPUT device: during a
|
|
1847
|
-
* drag it applies a TRANSIENT transform, and on drop it RESETS the transform to nothing and instead
|
|
1848
|
-
* reorders the layout tree (constraint C1 — a persisted transform on an in-flow canvas element
|
|
1849
|
-
* breaks Lenis scroll). Everything here runs ONLY when `moveableMode` is on; interact.js is
|
|
1850
|
-
* untouched, so the flag-off path is byte-identical to today.
|
|
1851
|
-
*
|
|
1852
|
-
* Scale: Moveable reads `getBoundingClientRect()` (already post-scale) and works in viewport
|
|
1853
|
-
* client coordinates, so drop hit-testing via `elementsFromPoint` / rects is scale-correct without
|
|
1854
|
-
* extra math. The control box is rendered into the same overlay container the selection overlay uses.
|
|
1855
|
-
*/
|
|
1856
|
-
class MoveableCanvasService {
|
|
1857
|
-
constructor(dragEngine) {
|
|
1858
|
-
this.dragEngine = dragEngine;
|
|
1859
|
-
this.moveable = null;
|
|
1860
|
-
this.container = null;
|
|
1861
|
-
this.scaleGetter = () => 1;
|
|
1862
|
-
this.target = null;
|
|
1863
|
-
this.node = null;
|
|
1864
|
-
this.sourceId = null;
|
|
1865
|
-
/** Called after a successful reorder so the host can emit LAYOUT_CHANGED / refresh selection. */
|
|
1866
|
-
this.onReorder = null;
|
|
1867
|
-
}
|
|
1868
|
-
/** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom. */
|
|
1869
|
-
init(container, scaleGetter) {
|
|
1870
|
-
this.container = container;
|
|
1871
|
-
this.scaleGetter = scaleGetter;
|
|
1872
|
-
}
|
|
1873
|
-
/** Point Moveable at the selected element (skips inline-edit + preview-clone targets). */
|
|
1874
|
-
select(element, node) {
|
|
1875
|
-
this.target = null;
|
|
1876
|
-
this.node = null;
|
|
1877
|
-
this.sourceId = null;
|
|
1878
|
-
if (!element || !node || !this.container) {
|
|
1879
|
-
this.destroyMoveable();
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1882
|
-
// Preview clones (repeater/slider rows) and hidden nodes are not editable.
|
|
1883
|
-
if (element.closest('[data-iox-hidden]') || node.__previewClone) {
|
|
1884
|
-
this.destroyMoveable();
|
|
1885
|
-
return;
|
|
1886
|
-
}
|
|
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
|
-
}
|
|
1898
|
-
/** Recompute the control-box position (call on scroll/zoom/layout changes). */
|
|
1899
|
-
refresh() { this.moveable?.updateRect(); }
|
|
1900
|
-
destroy() { this.destroyMoveable(); this.container = null; }
|
|
1901
|
-
// ── internals ────────────────────────────────────────────────────────────
|
|
1902
|
-
ensureMoveable() {
|
|
1903
|
-
if (this.moveable)
|
|
1904
|
-
return;
|
|
1905
|
-
this.moveable = new Moveable(this.container, {
|
|
1906
|
-
target: undefined,
|
|
1907
|
-
draggable: true,
|
|
1908
|
-
resizable: false, // Phase 3
|
|
1909
|
-
rotatable: false, // Phase 3 (out-of-flow only)
|
|
1910
|
-
origin: false,
|
|
1911
|
-
zoom: 1,
|
|
1912
|
-
// INVISIBLE interaction layer: the builder's OverlayService owns ALL selection visuals
|
|
1913
|
-
// (pink outline, hover margin/padding rings, toolbar, badges). Moveable must draw nothing
|
|
1914
|
-
// here — it only provides the draggable hit-area. `iox-moveable-silent` + the SCSS in
|
|
1915
|
-
// builder.component.scss hide any residual control chrome and drop it below the overlay.
|
|
1916
|
-
hideDefaultLines: true,
|
|
1917
|
-
renderDirections: [],
|
|
1918
|
-
className: 'iox-moveable-silent',
|
|
1919
|
-
});
|
|
1920
|
-
this.moveable.on('dragStart', (e) => {
|
|
1921
|
-
// Never start a drag from inside inline text editing.
|
|
1922
|
-
if (e.inputEvent?.target?.closest?.('[contenteditable="true"]')) {
|
|
1923
|
-
e.stop?.();
|
|
1924
|
-
}
|
|
1925
|
-
});
|
|
1926
|
-
// Transient transform only — visual feedback during the drag.
|
|
1927
|
-
this.moveable.on('drag', (e) => { e.target.style.transform = e.transform; });
|
|
1928
|
-
this.moveable.on('dragEnd', (e) => this.commitDrag(e));
|
|
1929
|
-
}
|
|
1930
|
-
destroyMoveable() {
|
|
1931
|
-
if (this.moveable) {
|
|
1932
|
-
this.moveable.destroy();
|
|
1933
|
-
this.moveable = null;
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
commitDrag(e) {
|
|
1937
|
-
const el = e.target;
|
|
1938
|
-
// C1: never persist the transform — reset it; the result is a tree reorder, not a position.
|
|
1939
|
-
el.style.transform = '';
|
|
1940
|
-
if (!this.node)
|
|
1941
|
-
return;
|
|
1942
|
-
const pointer = this.lastPointer(e);
|
|
1943
|
-
if (!pointer) {
|
|
1944
|
-
this.onReorder?.();
|
|
1945
|
-
return;
|
|
1946
|
-
}
|
|
1947
|
-
const targetId = this.dropzoneUnderPoint(pointer.x, pointer.y, el);
|
|
1948
|
-
if (!targetId) {
|
|
1949
|
-
this.onReorder?.();
|
|
1950
|
-
return;
|
|
1951
|
-
}
|
|
1952
|
-
const children = this.dragEngine.getData(targetId);
|
|
1953
|
-
const cdr = this.dragEngine.getCdr(targetId);
|
|
1954
|
-
if (!children || !cdr) {
|
|
1955
|
-
this.onReorder?.();
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
const insertIndex = this.calcInsertIndex(targetId, pointer.y);
|
|
1959
|
-
const srcChildren = this.sourceId ? this.dragEngine.getData(this.sourceId) : undefined;
|
|
1960
|
-
const sourceIndex = srcChildren ? srcChildren.indexOf(this.node) : -1;
|
|
1961
|
-
const payload = { type: 'internal', data: this.node, sourceId: this.sourceId ?? null, sourceIndex };
|
|
1962
|
-
this.dragEngine.handleDrop(children, { payload, targetId, insertIndex }, targetId, cdr, () => {
|
|
1963
|
-
this.onReorder?.();
|
|
1964
|
-
// Re-point the control box at the moved element's (possibly new) host.
|
|
1965
|
-
setTimeout(() => this.moveable?.updateRect(), 0);
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
|
-
/** Final pointer position from the Moveable end event (client/viewport px). */
|
|
1969
|
-
lastPointer(e) {
|
|
1970
|
-
const ie = e.inputEvent;
|
|
1971
|
-
if (ie && typeof ie.clientX === 'number')
|
|
1972
|
-
return { x: ie.clientX, y: ie.clientY };
|
|
1973
|
-
if (Array.isArray(e.clientX))
|
|
1974
|
-
return { x: e.clientX[0], y: e.clientY[0] };
|
|
1975
|
-
if (typeof e.clientX === 'number')
|
|
1976
|
-
return { x: e.clientX, y: e.clientY };
|
|
1977
|
-
return null;
|
|
1978
|
-
}
|
|
1979
|
-
/** Walk up from an element to the nearest registered dropzone id (el.id is stamped by DragEngine). */
|
|
1980
|
-
resolveDropzoneId(el) {
|
|
1981
|
-
let e = el;
|
|
1982
|
-
while (e) {
|
|
1983
|
-
if (e.id && this.dragEngine.getData(e.id) !== undefined)
|
|
1984
|
-
return e.id;
|
|
1985
|
-
e = e.parentElement;
|
|
1986
|
-
}
|
|
1987
|
-
return null;
|
|
1988
|
-
}
|
|
1989
|
-
/** Topmost registered dropzone under the pointer that is NOT inside the dragged element's subtree. */
|
|
1990
|
-
dropzoneUnderPoint(x, y, draggedEl) {
|
|
1991
|
-
const stack = document.elementsFromPoint(x, y);
|
|
1992
|
-
for (const el of stack) {
|
|
1993
|
-
const id = this.resolveDropzoneId(el);
|
|
1994
|
-
if (!id)
|
|
1995
|
-
continue;
|
|
1996
|
-
const zoneEl = document.getElementById(id);
|
|
1997
|
-
if (zoneEl && draggedEl.contains(zoneEl))
|
|
1998
|
-
continue; // don't drop into own subtree
|
|
1999
|
-
return id;
|
|
2000
|
-
}
|
|
2001
|
-
return null;
|
|
2002
|
-
}
|
|
2003
|
-
/** Insertion index within a dropzone by comparing pointer Y to each child's vertical midpoint. */
|
|
2004
|
-
calcInsertIndex(targetId, y) {
|
|
2005
|
-
const zoneEl = document.getElementById(targetId);
|
|
2006
|
-
if (!zoneEl)
|
|
2007
|
-
return 0;
|
|
2008
|
-
const childWraps = Array.from(zoneEl.children).filter((c) => c.matches('.container-child, .section-child, [data-iox-draggable]'));
|
|
2009
|
-
let idx = 0;
|
|
2010
|
-
for (const w of childWraps) {
|
|
2011
|
-
const r = w.getBoundingClientRect();
|
|
2012
|
-
if (y > r.top + r.height / 2)
|
|
2013
|
-
idx++;
|
|
2014
|
-
}
|
|
2015
|
-
return idx;
|
|
2016
|
-
}
|
|
2017
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, deps: [{ token: DragEngineService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2018
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, providedIn: 'root' }); }
|
|
2019
|
-
}
|
|
2020
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, decorators: [{
|
|
2021
|
-
type: Injectable,
|
|
2022
|
-
args: [{ providedIn: 'root' }]
|
|
2023
|
-
}], ctorParameters: () => [{ type: DragEngineService }] });
|
|
2024
|
-
|
|
2025
|
-
// Virtual trait → composed CSS property helpers.
|
|
2026
|
-
//
|
|
2027
|
-
// Several style traits are "virtual" — they don't map 1:1 to a CSS property but
|
|
2028
|
-
// are components of a multi-function CSS value. composeVirtualTraits() strips
|
|
2029
|
-
// these virtual keys from the style map and replaces them with the correctly
|
|
2030
|
-
// composed CSS properties (filter, backdrop-filter, transform, transition).
|
|
2031
|
-
//
|
|
2032
|
-
// The same function is used by the render directive (initial mount + state
|
|
2033
|
-
// restore) and by the style panel's onUpdate / setStateOverride paths, so
|
|
2034
|
-
// the CSS output is always consistent regardless of how styles are applied.
|
|
2035
|
-
// ─── Filter ──────────────────────────────────────────────────────────────────
|
|
2036
|
-
const FILTER_FUNS = [
|
|
2037
|
-
['filterBlur', 'blur', '0px'],
|
|
2038
|
-
['filterBrightness', 'brightness', '1'],
|
|
2039
|
-
['filterContrast', 'contrast', '1'],
|
|
2040
|
-
['filterGrayscale', 'grayscale', '0'],
|
|
2041
|
-
['filterSaturate', 'saturate', '1'],
|
|
2042
|
-
['filterHueRotate', 'hue-rotate', '0deg'],
|
|
2043
|
-
['filterSepia', 'sepia', '0'],
|
|
2044
|
-
['filterInvert', 'invert', '0'],
|
|
2045
|
-
];
|
|
2046
|
-
// ─── Backdrop-filter ─────────────────────────────────────────────────────────
|
|
2047
|
-
const BACKDROP_FUNS = [
|
|
2048
|
-
['backdropBlur', 'blur', '0px'],
|
|
2049
|
-
['backdropBrightness', 'brightness', '1'],
|
|
2050
|
-
['backdropContrast', 'contrast', '1'],
|
|
2051
|
-
['backdropSaturate', 'saturate', '1'],
|
|
2052
|
-
];
|
|
2053
|
-
// ─── Transform ───────────────────────────────────────────────────────────────
|
|
2054
|
-
const TRANSFORM_FUNS = [
|
|
2055
|
-
['translateX', 'translateX', '0px'],
|
|
2056
|
-
['translateY', 'translateY', '0px'],
|
|
2057
|
-
['scaleX', 'scaleX', '1'],
|
|
2058
|
-
['scaleY', 'scaleY', '1'],
|
|
2059
|
-
['rotate', 'rotate', '0deg'],
|
|
2060
|
-
['skewX', 'skewX', '0deg'],
|
|
2061
|
-
['skewY', 'skewY', '0deg'],
|
|
2062
|
-
];
|
|
2063
|
-
// ─── ::marker pseudo-element ─────────────────────────────────────────────────
|
|
2064
|
-
const MARKER_FUNS = [
|
|
2065
|
-
['markerColor', 'color'],
|
|
2066
|
-
['markerFontSize', 'fontSize'],
|
|
2067
|
-
];
|
|
2068
|
-
// ─── All virtual trait names ──────────────────────────────────────────────────
|
|
2069
|
-
const VIRTUAL_TRAIT_KEYS = new Set([
|
|
2070
|
-
...FILTER_FUNS.map(([t]) => t),
|
|
2071
|
-
...BACKDROP_FUNS.map(([t]) => t),
|
|
2072
|
-
...TRANSFORM_FUNS.map(([t]) => t),
|
|
2073
|
-
'transitionDuration', 'transitionTimingFunction', 'transitionDelay',
|
|
2074
|
-
...MARKER_FUNS.map(([t]) => t),
|
|
2075
|
-
]);
|
|
2076
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
2077
|
-
function buildFns(funs, src) {
|
|
2078
|
-
return funs
|
|
2079
|
-
.filter(([t, , d]) => src[t] != null && src[t] !== '' && src[t] !== d)
|
|
2080
|
-
.map(([t, fn]) => `${fn}(${src[t]})`)
|
|
2081
|
-
.join(' ');
|
|
2082
|
-
}
|
|
2083
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
2084
|
-
/**
|
|
2085
|
-
* Strip virtual trait keys from `raw` and emit their composed CSS equivalents.
|
|
2086
|
-
*
|
|
2087
|
-
* When composing a partial state-override map (hover, active…), pass the full
|
|
2088
|
-
* base style map as `base` so non-overridden components (e.g. filterBrightness
|
|
2089
|
-
* when only filterBlur is overridden) are preserved in the composed output.
|
|
2090
|
-
* When operating on a complete style map, omit `base` — it defaults to `raw`.
|
|
2091
|
-
*/
|
|
2092
|
-
function composeVirtualTraits(raw, base = raw) {
|
|
2093
|
-
// Copy all non-virtual properties as-is
|
|
2094
|
-
const result = {};
|
|
2095
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
2096
|
-
if (!VIRTUAL_TRAIT_KEYS.has(k))
|
|
2097
|
-
result[k] = v;
|
|
2098
|
-
}
|
|
2099
|
-
// Merge base values for any virtual key absent from the partial override map
|
|
2100
|
-
const merged = (funs) => {
|
|
2101
|
-
const m = {};
|
|
2102
|
-
for (const [t] of funs)
|
|
2103
|
-
m[t] = raw[t] ?? base[t];
|
|
2104
|
-
return m;
|
|
2105
|
-
};
|
|
2106
|
-
// filter
|
|
2107
|
-
const filterStr = buildFns(FILTER_FUNS, merged(FILTER_FUNS));
|
|
2108
|
-
if (filterStr)
|
|
2109
|
-
result['filter'] = filterStr;
|
|
2110
|
-
// backdrop-filter
|
|
2111
|
-
const backdropStr = buildFns(BACKDROP_FUNS, merged(BACKDROP_FUNS));
|
|
2112
|
-
if (backdropStr)
|
|
2113
|
-
result['backdropFilter'] = backdropStr;
|
|
2114
|
-
// transform
|
|
2115
|
-
const transformStr = buildFns(TRANSFORM_FUNS, merged(TRANSFORM_FUNS));
|
|
2116
|
-
if (transformStr)
|
|
2117
|
-
result['transform'] = transformStr;
|
|
2118
|
-
// transition — only emit when duration is non-zero
|
|
2119
|
-
const dur = raw['transitionDuration'] ?? base['transitionDuration'];
|
|
2120
|
-
const ease = raw['transitionTimingFunction'] ?? base['transitionTimingFunction'];
|
|
2121
|
-
const del = raw['transitionDelay'] ?? base['transitionDelay'];
|
|
2122
|
-
if (dur && ease && dur !== '0ms' && dur !== '0s') {
|
|
2123
|
-
const delPart = del && del !== '0ms' && del !== '0s' ? ` ${del}` : '';
|
|
2124
|
-
result['transition'] = `all ${dur} ${ease}${delPart}`;
|
|
2125
|
-
}
|
|
2126
|
-
// ::marker pseudo-element — collected into __markerStyles so StyleRegistryService
|
|
2127
|
-
// can emit a separate `.iox-node-{id}::marker { … }` rule.
|
|
2128
|
-
const markerStyles = {};
|
|
2129
|
-
for (const [trait, cssProp] of MARKER_FUNS) {
|
|
2130
|
-
const val = raw[trait] ?? base[trait];
|
|
2131
|
-
if (val != null && val !== '')
|
|
2132
|
-
markerStyles[cssProp] = val;
|
|
2133
|
-
}
|
|
2134
|
-
if (Object.keys(markerStyles).length) {
|
|
2135
|
-
result['__markerStyles'] = markerStyles;
|
|
2136
|
-
}
|
|
2137
|
-
return result;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
1841
|
var StyleCategory;
|
|
2141
1842
|
(function (StyleCategory) {
|
|
2142
1843
|
StyleCategory["Layout"] = "Layout";
|
|
@@ -2805,46 +2506,389 @@ class StyleRegistryService {
|
|
|
2805
2506
|
}
|
|
2806
2507
|
this.flush();
|
|
2807
2508
|
}
|
|
2808
|
-
destroy() {
|
|
2809
|
-
this.rules.clear();
|
|
2810
|
-
if (this.styleEl) {
|
|
2811
|
-
this.styleEl.remove();
|
|
2812
|
-
this.styleEl = null;
|
|
2813
|
-
}
|
|
2509
|
+
destroy() {
|
|
2510
|
+
this.rules.clear();
|
|
2511
|
+
if (this.styleEl) {
|
|
2512
|
+
this.styleEl.remove();
|
|
2513
|
+
this.styleEl = null;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
compile(className, styles) {
|
|
2517
|
+
// Declaration compilation (kebab-case, viewport-unit rewrite, !important) lives in
|
|
2518
|
+
// the shared style-compiler so the SSR engine produces byte-identical output.
|
|
2519
|
+
// The inner/outer partition + selector emission stay here (builder-only concern).
|
|
2520
|
+
const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS);
|
|
2521
|
+
if (!decls.length)
|
|
2522
|
+
return '';
|
|
2523
|
+
const body = decls.map(d => ` ${d};`).join('\n');
|
|
2524
|
+
return `.${className} {\n${body}\n}`;
|
|
2525
|
+
}
|
|
2526
|
+
flush() {
|
|
2527
|
+
if (!this.styleEl || this._flushPending)
|
|
2528
|
+
return;
|
|
2529
|
+
this._flushPending = true;
|
|
2530
|
+
requestAnimationFrame(() => {
|
|
2531
|
+
this._flushPending = false;
|
|
2532
|
+
if (!this.styleEl)
|
|
2533
|
+
return;
|
|
2534
|
+
// __tokens__ always first so variables are available to all subsequent rules
|
|
2535
|
+
const tokenBlock = this.rules.get('__tokens__');
|
|
2536
|
+
const rest = Array.from(this.rules.entries())
|
|
2537
|
+
.filter(([k]) => k !== '__tokens__')
|
|
2538
|
+
.map(([, v]) => v);
|
|
2539
|
+
this.styleEl.textContent = [tokenBlock, ...rest].filter(Boolean).join('\n');
|
|
2540
|
+
this.changes$.next();
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2544
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService }); }
|
|
2545
|
+
}
|
|
2546
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, decorators: [{
|
|
2547
|
+
type: Injectable
|
|
2548
|
+
}] });
|
|
2549
|
+
|
|
2550
|
+
/**
|
|
2551
|
+
* MoveableCanvasService — Phase 1 of the Moveable + Selecto migration
|
|
2552
|
+
* (see iox-ai-guidance/architecture/builder/moveable-migration.md).
|
|
2553
|
+
*
|
|
2554
|
+
* Drives DRAG for the SELECTED element via Moveable instead of interact.js, while REUSING the
|
|
2555
|
+
* proven reorder machinery (`DragEngineService.handleDrop`). It is a pure INPUT device: during a
|
|
2556
|
+
* drag it applies a TRANSIENT transform, and on drop it RESETS the transform to nothing and instead
|
|
2557
|
+
* reorders the layout tree (constraint C1 — a persisted transform on an in-flow canvas element
|
|
2558
|
+
* breaks Lenis scroll). Everything here runs ONLY when `moveableMode` is on; interact.js is
|
|
2559
|
+
* untouched, so the flag-off path is byte-identical to today.
|
|
2560
|
+
*
|
|
2561
|
+
* Scale: Moveable reads `getBoundingClientRect()` (already post-scale) and works in viewport
|
|
2562
|
+
* client coordinates, so drop hit-testing via `elementsFromPoint` / rects is scale-correct without
|
|
2563
|
+
* extra math. The control box is rendered into the same overlay container the selection overlay uses.
|
|
2564
|
+
*/
|
|
2565
|
+
class MoveableCanvasService {
|
|
2566
|
+
constructor(dragEngine, styleRegistry) {
|
|
2567
|
+
this.dragEngine = dragEngine;
|
|
2568
|
+
this.styleRegistry = styleRegistry;
|
|
2569
|
+
this.moveable = null;
|
|
2570
|
+
this.container = null;
|
|
2571
|
+
this.scaleGetter = () => 1;
|
|
2572
|
+
this.target = null;
|
|
2573
|
+
this.node = null;
|
|
2574
|
+
this.sourceId = null;
|
|
2575
|
+
/** Called after a successful reorder so the host can emit LAYOUT_CHANGED / refresh selection. */
|
|
2576
|
+
this.onReorder = null;
|
|
2577
|
+
/** Called after a geometry commit (resize) so the host can emit LAYOUT_CHANGED (undo/save). */
|
|
2578
|
+
this.onLayoutChanged = null;
|
|
2579
|
+
}
|
|
2580
|
+
/** Wire the Moveable control box into `container`; `scaleGetter` returns the live canvas zoom. */
|
|
2581
|
+
init(container, scaleGetter) {
|
|
2582
|
+
this.container = container;
|
|
2583
|
+
this.scaleGetter = scaleGetter;
|
|
2584
|
+
}
|
|
2585
|
+
/** Point Moveable at the selected element (skips inline-edit + preview-clone targets). */
|
|
2586
|
+
select(element, node) {
|
|
2587
|
+
this.target = null;
|
|
2588
|
+
this.node = null;
|
|
2589
|
+
this.sourceId = null;
|
|
2590
|
+
if (!element || !node || !this.container) {
|
|
2591
|
+
this.destroyMoveable();
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
// Preview clones (repeater/slider rows) and hidden nodes are not editable.
|
|
2595
|
+
if (element.closest('[data-iox-hidden]') || node.__previewClone) {
|
|
2596
|
+
this.destroyMoveable();
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
this.target = element;
|
|
2600
|
+
this.node = node;
|
|
2601
|
+
this.sourceId = this.resolveDropzoneId(element);
|
|
2602
|
+
this.ensureMoveable();
|
|
2603
|
+
this.moveable.target = element;
|
|
2604
|
+
// Drag is initiated by grabbing the ELEMENT itself (like interact.js) — not a Moveable
|
|
2605
|
+
// overlay area — so a plain click still passes through to selection/child-selection and only
|
|
2606
|
+
// a movement past the threshold starts a drag. Keeps Moveable visually silent (no hit-area box).
|
|
2607
|
+
this.moveable.dragTarget = element;
|
|
2608
|
+
this.moveable.updateRect();
|
|
2609
|
+
}
|
|
2610
|
+
/** Recompute the control-box position (call on scroll/zoom/layout changes). */
|
|
2611
|
+
refresh() { this.moveable?.updateRect(); }
|
|
2612
|
+
destroy() { this.destroyMoveable(); this.container = null; }
|
|
2613
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
2614
|
+
ensureMoveable() {
|
|
2615
|
+
if (this.moveable)
|
|
2616
|
+
return;
|
|
2617
|
+
this.moveable = new Moveable(this.container, {
|
|
2618
|
+
target: undefined,
|
|
2619
|
+
draggable: true,
|
|
2620
|
+
resizable: true, // Phase 3 — corner handles → styleProps width/height (C2)
|
|
2621
|
+
rotatable: false, // (out-of-flow only — C1)
|
|
2622
|
+
origin: false,
|
|
2623
|
+
zoom: 1,
|
|
2624
|
+
// The OverlayService owns ALL selection visuals (pink outline, hover margin/padding rings,
|
|
2625
|
+
// toolbar, badges) — Moveable draws NO box/lines. It shows ONLY the 4 corner RESIZE handles
|
|
2626
|
+
// (renderDirections) and provides the drag hit-area. `iox-moveable-silent` + SCSS in
|
|
2627
|
+
// builder.component.scss hide the lines and style the handles to match, below the overlay.
|
|
2628
|
+
hideDefaultLines: true,
|
|
2629
|
+
renderDirections: ['nw', 'ne', 'sw', 'se'],
|
|
2630
|
+
className: 'iox-moveable-silent',
|
|
2631
|
+
});
|
|
2632
|
+
this.moveable.on('dragStart', (e) => {
|
|
2633
|
+
// Never start a drag from inside inline text editing.
|
|
2634
|
+
if (e.inputEvent?.target?.closest?.('[contenteditable="true"]')) {
|
|
2635
|
+
e.stop?.();
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
// Transient transform only — visual feedback during the drag (C1: reset on drop).
|
|
2639
|
+
this.moveable.on('drag', (e) => { e.target.style.transform = e.transform; });
|
|
2640
|
+
this.moveable.on('dragEnd', (e) => this.commitDrag(e));
|
|
2641
|
+
// Resize: live inline width/height for feedback, then commit to styleProps on end (C2 — never
|
|
2642
|
+
// a transform:scale, so no Lenis-scroll break; the panel + save + SSR all see the real size).
|
|
2643
|
+
this.moveable.on('resize', (e) => {
|
|
2644
|
+
e.target.style.width = `${Math.round(e.width)}px`;
|
|
2645
|
+
e.target.style.height = `${Math.round(e.height)}px`;
|
|
2646
|
+
e.target.style.transform = e.drag.transform; // keeps the box anchored while resizing
|
|
2647
|
+
});
|
|
2648
|
+
this.moveable.on('resizeEnd', (e) => this.commitResize(e));
|
|
2649
|
+
}
|
|
2650
|
+
destroyMoveable() {
|
|
2651
|
+
if (this.moveable) {
|
|
2652
|
+
this.moveable.destroy();
|
|
2653
|
+
this.moveable = null;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
commitDrag(e) {
|
|
2657
|
+
const el = e.target;
|
|
2658
|
+
// C1: never persist the transform — reset it; the result is a tree reorder, not a position.
|
|
2659
|
+
el.style.transform = '';
|
|
2660
|
+
if (!this.node)
|
|
2661
|
+
return;
|
|
2662
|
+
const pointer = this.lastPointer(e);
|
|
2663
|
+
if (!pointer) {
|
|
2664
|
+
this.onReorder?.();
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
const targetId = this.dropzoneUnderPoint(pointer.x, pointer.y, el);
|
|
2668
|
+
if (!targetId) {
|
|
2669
|
+
this.onReorder?.();
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
const children = this.dragEngine.getData(targetId);
|
|
2673
|
+
const cdr = this.dragEngine.getCdr(targetId);
|
|
2674
|
+
if (!children || !cdr) {
|
|
2675
|
+
this.onReorder?.();
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
const insertIndex = this.calcInsertIndex(targetId, pointer.y);
|
|
2679
|
+
const srcChildren = this.sourceId ? this.dragEngine.getData(this.sourceId) : undefined;
|
|
2680
|
+
const sourceIndex = srcChildren ? srcChildren.indexOf(this.node) : -1;
|
|
2681
|
+
const payload = { type: 'internal', data: this.node, sourceId: this.sourceId ?? null, sourceIndex };
|
|
2682
|
+
this.dragEngine.handleDrop(children, { payload, targetId, insertIndex }, targetId, cdr, () => {
|
|
2683
|
+
this.onReorder?.();
|
|
2684
|
+
// Re-point the control box at the moved element's (possibly new) host.
|
|
2685
|
+
setTimeout(() => this.moveable?.updateRect(), 0);
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
commitResize(e) {
|
|
2689
|
+
const el = e.target;
|
|
2690
|
+
el.style.transform = ''; // C1: never persist a transform on an in-flow canvas element
|
|
2691
|
+
if (!this.node) {
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
const last = e.lastEvent ?? e;
|
|
2695
|
+
const w = Math.round(last.width ?? el.getBoundingClientRect().width);
|
|
2696
|
+
const h = Math.round(last.height ?? el.getBoundingClientRect().height);
|
|
2697
|
+
// C2: geometry goes to styleProps (width/height) — never a transform:scale.
|
|
2698
|
+
this.applyStyle(this.node, { width: `${w}px`, height: `${h}px` });
|
|
2699
|
+
// Clear the transient inline size so the styleProps `.iox-node` rule drives it (single source).
|
|
2700
|
+
el.style.width = '';
|
|
2701
|
+
el.style.height = '';
|
|
2702
|
+
this.onLayoutChanged?.();
|
|
2703
|
+
setTimeout(() => this.moveable?.updateRect(), 0);
|
|
2704
|
+
}
|
|
2705
|
+
/** Write geometry changes into the node's styleTraits (the serialized source of truth) and the
|
|
2706
|
+
* central stylesheet, so the style panel, save (self-describing), and SSR engine all agree. */
|
|
2707
|
+
applyStyle(node, changes) {
|
|
2708
|
+
const styles = {};
|
|
2709
|
+
for (const group of (node.styleTraits ?? [])) {
|
|
2710
|
+
for (const trait of (group.traits ?? [])) {
|
|
2711
|
+
if (trait.name in changes)
|
|
2712
|
+
trait.default = changes[trait.name];
|
|
2713
|
+
if (trait.name !== undefined && trait.default !== undefined)
|
|
2714
|
+
styles[trait.name] = trait.default;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
const cssId = node.styleId ?? node.id;
|
|
2718
|
+
if (cssId)
|
|
2719
|
+
this.styleRegistry.upsert(cssId, styles);
|
|
2720
|
+
}
|
|
2721
|
+
/** Final pointer position from the Moveable end event (client/viewport px). */
|
|
2722
|
+
lastPointer(e) {
|
|
2723
|
+
const ie = e.inputEvent;
|
|
2724
|
+
if (ie && typeof ie.clientX === 'number')
|
|
2725
|
+
return { x: ie.clientX, y: ie.clientY };
|
|
2726
|
+
if (Array.isArray(e.clientX))
|
|
2727
|
+
return { x: e.clientX[0], y: e.clientY[0] };
|
|
2728
|
+
if (typeof e.clientX === 'number')
|
|
2729
|
+
return { x: e.clientX, y: e.clientY };
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
/** Walk up from an element to the nearest registered dropzone id (el.id is stamped by DragEngine). */
|
|
2733
|
+
resolveDropzoneId(el) {
|
|
2734
|
+
let e = el;
|
|
2735
|
+
while (e) {
|
|
2736
|
+
if (e.id && this.dragEngine.getData(e.id) !== undefined)
|
|
2737
|
+
return e.id;
|
|
2738
|
+
e = e.parentElement;
|
|
2739
|
+
}
|
|
2740
|
+
return null;
|
|
2741
|
+
}
|
|
2742
|
+
/** Topmost registered dropzone under the pointer that is NOT inside the dragged element's subtree. */
|
|
2743
|
+
dropzoneUnderPoint(x, y, draggedEl) {
|
|
2744
|
+
const stack = document.elementsFromPoint(x, y);
|
|
2745
|
+
for (const el of stack) {
|
|
2746
|
+
const id = this.resolveDropzoneId(el);
|
|
2747
|
+
if (!id)
|
|
2748
|
+
continue;
|
|
2749
|
+
const zoneEl = document.getElementById(id);
|
|
2750
|
+
if (zoneEl && draggedEl.contains(zoneEl))
|
|
2751
|
+
continue; // don't drop into own subtree
|
|
2752
|
+
return id;
|
|
2753
|
+
}
|
|
2754
|
+
return null;
|
|
2755
|
+
}
|
|
2756
|
+
/** Insertion index within a dropzone by comparing pointer Y to each child's vertical midpoint. */
|
|
2757
|
+
calcInsertIndex(targetId, y) {
|
|
2758
|
+
const zoneEl = document.getElementById(targetId);
|
|
2759
|
+
if (!zoneEl)
|
|
2760
|
+
return 0;
|
|
2761
|
+
const childWraps = Array.from(zoneEl.children).filter((c) => c.matches('.container-child, .section-child, [data-iox-draggable]'));
|
|
2762
|
+
let idx = 0;
|
|
2763
|
+
for (const w of childWraps) {
|
|
2764
|
+
const r = w.getBoundingClientRect();
|
|
2765
|
+
if (y > r.top + r.height / 2)
|
|
2766
|
+
idx++;
|
|
2767
|
+
}
|
|
2768
|
+
return idx;
|
|
2769
|
+
}
|
|
2770
|
+
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 }); }
|
|
2771
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, providedIn: 'root' }); }
|
|
2772
|
+
}
|
|
2773
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: MoveableCanvasService, decorators: [{
|
|
2774
|
+
type: Injectable,
|
|
2775
|
+
args: [{ providedIn: 'root' }]
|
|
2776
|
+
}], ctorParameters: () => [{ type: DragEngineService }, { type: StyleRegistryService }] });
|
|
2777
|
+
|
|
2778
|
+
// Virtual trait → composed CSS property helpers.
|
|
2779
|
+
//
|
|
2780
|
+
// Several style traits are "virtual" — they don't map 1:1 to a CSS property but
|
|
2781
|
+
// are components of a multi-function CSS value. composeVirtualTraits() strips
|
|
2782
|
+
// these virtual keys from the style map and replaces them with the correctly
|
|
2783
|
+
// composed CSS properties (filter, backdrop-filter, transform, transition).
|
|
2784
|
+
//
|
|
2785
|
+
// The same function is used by the render directive (initial mount + state
|
|
2786
|
+
// restore) and by the style panel's onUpdate / setStateOverride paths, so
|
|
2787
|
+
// the CSS output is always consistent regardless of how styles are applied.
|
|
2788
|
+
// ─── Filter ──────────────────────────────────────────────────────────────────
|
|
2789
|
+
const FILTER_FUNS = [
|
|
2790
|
+
['filterBlur', 'blur', '0px'],
|
|
2791
|
+
['filterBrightness', 'brightness', '1'],
|
|
2792
|
+
['filterContrast', 'contrast', '1'],
|
|
2793
|
+
['filterGrayscale', 'grayscale', '0'],
|
|
2794
|
+
['filterSaturate', 'saturate', '1'],
|
|
2795
|
+
['filterHueRotate', 'hue-rotate', '0deg'],
|
|
2796
|
+
['filterSepia', 'sepia', '0'],
|
|
2797
|
+
['filterInvert', 'invert', '0'],
|
|
2798
|
+
];
|
|
2799
|
+
// ─── Backdrop-filter ─────────────────────────────────────────────────────────
|
|
2800
|
+
const BACKDROP_FUNS = [
|
|
2801
|
+
['backdropBlur', 'blur', '0px'],
|
|
2802
|
+
['backdropBrightness', 'brightness', '1'],
|
|
2803
|
+
['backdropContrast', 'contrast', '1'],
|
|
2804
|
+
['backdropSaturate', 'saturate', '1'],
|
|
2805
|
+
];
|
|
2806
|
+
// ─── Transform ───────────────────────────────────────────────────────────────
|
|
2807
|
+
const TRANSFORM_FUNS = [
|
|
2808
|
+
['translateX', 'translateX', '0px'],
|
|
2809
|
+
['translateY', 'translateY', '0px'],
|
|
2810
|
+
['scaleX', 'scaleX', '1'],
|
|
2811
|
+
['scaleY', 'scaleY', '1'],
|
|
2812
|
+
['rotate', 'rotate', '0deg'],
|
|
2813
|
+
['skewX', 'skewX', '0deg'],
|
|
2814
|
+
['skewY', 'skewY', '0deg'],
|
|
2815
|
+
];
|
|
2816
|
+
// ─── ::marker pseudo-element ─────────────────────────────────────────────────
|
|
2817
|
+
const MARKER_FUNS = [
|
|
2818
|
+
['markerColor', 'color'],
|
|
2819
|
+
['markerFontSize', 'fontSize'],
|
|
2820
|
+
];
|
|
2821
|
+
// ─── All virtual trait names ──────────────────────────────────────────────────
|
|
2822
|
+
const VIRTUAL_TRAIT_KEYS = new Set([
|
|
2823
|
+
...FILTER_FUNS.map(([t]) => t),
|
|
2824
|
+
...BACKDROP_FUNS.map(([t]) => t),
|
|
2825
|
+
...TRANSFORM_FUNS.map(([t]) => t),
|
|
2826
|
+
'transitionDuration', 'transitionTimingFunction', 'transitionDelay',
|
|
2827
|
+
...MARKER_FUNS.map(([t]) => t),
|
|
2828
|
+
]);
|
|
2829
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
2830
|
+
function buildFns(funs, src) {
|
|
2831
|
+
return funs
|
|
2832
|
+
.filter(([t, , d]) => src[t] != null && src[t] !== '' && src[t] !== d)
|
|
2833
|
+
.map(([t, fn]) => `${fn}(${src[t]})`)
|
|
2834
|
+
.join(' ');
|
|
2835
|
+
}
|
|
2836
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
2837
|
+
/**
|
|
2838
|
+
* Strip virtual trait keys from `raw` and emit their composed CSS equivalents.
|
|
2839
|
+
*
|
|
2840
|
+
* When composing a partial state-override map (hover, active…), pass the full
|
|
2841
|
+
* base style map as `base` so non-overridden components (e.g. filterBrightness
|
|
2842
|
+
* when only filterBlur is overridden) are preserved in the composed output.
|
|
2843
|
+
* When operating on a complete style map, omit `base` — it defaults to `raw`.
|
|
2844
|
+
*/
|
|
2845
|
+
function composeVirtualTraits(raw, base = raw) {
|
|
2846
|
+
// Copy all non-virtual properties as-is
|
|
2847
|
+
const result = {};
|
|
2848
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
2849
|
+
if (!VIRTUAL_TRAIT_KEYS.has(k))
|
|
2850
|
+
result[k] = v;
|
|
2851
|
+
}
|
|
2852
|
+
// Merge base values for any virtual key absent from the partial override map
|
|
2853
|
+
const merged = (funs) => {
|
|
2854
|
+
const m = {};
|
|
2855
|
+
for (const [t] of funs)
|
|
2856
|
+
m[t] = raw[t] ?? base[t];
|
|
2857
|
+
return m;
|
|
2858
|
+
};
|
|
2859
|
+
// filter
|
|
2860
|
+
const filterStr = buildFns(FILTER_FUNS, merged(FILTER_FUNS));
|
|
2861
|
+
if (filterStr)
|
|
2862
|
+
result['filter'] = filterStr;
|
|
2863
|
+
// backdrop-filter
|
|
2864
|
+
const backdropStr = buildFns(BACKDROP_FUNS, merged(BACKDROP_FUNS));
|
|
2865
|
+
if (backdropStr)
|
|
2866
|
+
result['backdropFilter'] = backdropStr;
|
|
2867
|
+
// transform
|
|
2868
|
+
const transformStr = buildFns(TRANSFORM_FUNS, merged(TRANSFORM_FUNS));
|
|
2869
|
+
if (transformStr)
|
|
2870
|
+
result['transform'] = transformStr;
|
|
2871
|
+
// transition — only emit when duration is non-zero
|
|
2872
|
+
const dur = raw['transitionDuration'] ?? base['transitionDuration'];
|
|
2873
|
+
const ease = raw['transitionTimingFunction'] ?? base['transitionTimingFunction'];
|
|
2874
|
+
const del = raw['transitionDelay'] ?? base['transitionDelay'];
|
|
2875
|
+
if (dur && ease && dur !== '0ms' && dur !== '0s') {
|
|
2876
|
+
const delPart = del && del !== '0ms' && del !== '0s' ? ` ${del}` : '';
|
|
2877
|
+
result['transition'] = `all ${dur} ${ease}${delPart}`;
|
|
2814
2878
|
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
const
|
|
2820
|
-
if (
|
|
2821
|
-
|
|
2822
|
-
const body = decls.map(d => ` ${d};`).join('\n');
|
|
2823
|
-
return `.${className} {\n${body}\n}`;
|
|
2879
|
+
// ::marker pseudo-element — collected into __markerStyles so StyleRegistryService
|
|
2880
|
+
// can emit a separate `.iox-node-{id}::marker { … }` rule.
|
|
2881
|
+
const markerStyles = {};
|
|
2882
|
+
for (const [trait, cssProp] of MARKER_FUNS) {
|
|
2883
|
+
const val = raw[trait] ?? base[trait];
|
|
2884
|
+
if (val != null && val !== '')
|
|
2885
|
+
markerStyles[cssProp] = val;
|
|
2824
2886
|
}
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
return;
|
|
2828
|
-
this._flushPending = true;
|
|
2829
|
-
requestAnimationFrame(() => {
|
|
2830
|
-
this._flushPending = false;
|
|
2831
|
-
if (!this.styleEl)
|
|
2832
|
-
return;
|
|
2833
|
-
// __tokens__ always first so variables are available to all subsequent rules
|
|
2834
|
-
const tokenBlock = this.rules.get('__tokens__');
|
|
2835
|
-
const rest = Array.from(this.rules.entries())
|
|
2836
|
-
.filter(([k]) => k !== '__tokens__')
|
|
2837
|
-
.map(([, v]) => v);
|
|
2838
|
-
this.styleEl.textContent = [tokenBlock, ...rest].filter(Boolean).join('\n');
|
|
2839
|
-
this.changes$.next();
|
|
2840
|
-
});
|
|
2887
|
+
if (Object.keys(markerStyles).length) {
|
|
2888
|
+
result['__markerStyles'] = markerStyles;
|
|
2841
2889
|
}
|
|
2842
|
-
|
|
2843
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService }); }
|
|
2890
|
+
return result;
|
|
2844
2891
|
}
|
|
2845
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: StyleRegistryService, decorators: [{
|
|
2846
|
-
type: Injectable
|
|
2847
|
-
}] });
|
|
2848
2892
|
|
|
2849
2893
|
// Module-level counter — unique drop-list ID per Section instance across the whole app session.
|
|
2850
2894
|
let dropListCounter = 0;
|
|
@@ -2859,8 +2903,9 @@ let dropListCounter = 0;
|
|
|
2859
2903
|
const DIRECTIVE_HOST_TYPES = new Set([
|
|
2860
2904
|
'Image', 'Icon', 'Divider', 'Spacer', 'Button', 'Link', 'Heading', 'Text', 'ButtonBlock',
|
|
2861
2905
|
// Phase 2 (9c): layout components converted to directive-hosts (host IS the real tag).
|
|
2862
|
-
'Section',
|
|
2906
|
+
'Section', 'Container', 'List', 'ListItem',
|
|
2863
2907
|
]);
|
|
2908
|
+
const CONTAINER_TAGS = ['div', 'section', 'article', 'header', 'footer', 'main', 'nav', 'ul', 'ol', 'li'];
|
|
2864
2909
|
/**
|
|
2865
2910
|
* RenderDirective — applied to <ng-container> to dynamically instantiate a
|
|
2866
2911
|
* builder component with zero wrapper elements in the DOM.
|
|
@@ -2910,6 +2955,14 @@ class RenderDirective {
|
|
|
2910
2955
|
return traitVal('tag') === 'span' ? 'span' : 'p';
|
|
2911
2956
|
case 'ButtonBlock':
|
|
2912
2957
|
return traitVal('tag') === 'a' ? 'a' : 'button';
|
|
2958
|
+
case 'Container':
|
|
2959
|
+
case 'List':
|
|
2960
|
+
case 'ListItem': {
|
|
2961
|
+
const t = traitVal('htmlTag');
|
|
2962
|
+
if (CONTAINER_TAGS.includes(t))
|
|
2963
|
+
return t;
|
|
2964
|
+
return node.type === 'List' ? 'ul' : node.type === 'ListItem' ? 'li' : 'div';
|
|
2965
|
+
}
|
|
2913
2966
|
default:
|
|
2914
2967
|
return null;
|
|
2915
2968
|
}
|
|
@@ -4245,6 +4298,8 @@ class BuilderComponent {
|
|
|
4245
4298
|
// iox-ai-guidance/architecture/builder/moveable-migration.md.
|
|
4246
4299
|
if (this.moveableMode && this.previewCanvas?.nativeElement) {
|
|
4247
4300
|
this.moveableCanvas.init(this.previewCanvas.nativeElement, () => this.viewportService.getScale());
|
|
4301
|
+
// Resize commits geometry to styleProps → emit LAYOUT_CHANGED so undo/save pick it up.
|
|
4302
|
+
this.moveableCanvas.onLayoutChanged = () => this.panelEventService.emit(PanelEventTypes.LAYOUT_CHANGED, this.layout);
|
|
4248
4303
|
this.sub?.add(this.overlayService.select$.subscribe(entry => {
|
|
4249
4304
|
this.moveableCanvas.select(entry?.element ?? null, entry?.node ?? null);
|
|
4250
4305
|
}));
|
|
@@ -4836,11 +4891,11 @@ class BuilderComponent {
|
|
|
4836
4891
|
}
|
|
4837
4892
|
}
|
|
4838
4893
|
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 }); }
|
|
4839
|
-
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"] }] }); }
|
|
4894
|
+
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"] }] }); }
|
|
4840
4895
|
}
|
|
4841
4896
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
4842
4897
|
type: Component,
|
|
4843
|
-
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"] }]
|
|
4898
|
+
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"] }]
|
|
4844
4899
|
}], 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: [{
|
|
4845
4900
|
type: Input
|
|
4846
4901
|
}], dataSources: [{
|
|
@@ -4998,42 +5053,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
4998
5053
|
type: Output
|
|
4999
5054
|
}] } });
|
|
5000
5055
|
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
}
|
|
5010
|
-
ngOnInit() {
|
|
5011
|
-
this.dragEngine.registerDropzone(this.ioxDropzoneId, this.el.nativeElement, this.ioxDropzoneData, this.cdr, (evt) => this.ioxDrop.emit(evt), this.ioxDropzonePostDrop);
|
|
5012
|
-
}
|
|
5013
|
-
ngOnDestroy() {
|
|
5014
|
-
this.dragEngine.unregisterDropzone(this.ioxDropzoneId, this.el.nativeElement);
|
|
5015
|
-
}
|
|
5016
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxDropzoneDirective, deps: [{ token: i0.ElementRef }, { token: DragEngineService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
5017
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: IoxDropzoneDirective, isStandalone: false, selector: "[ioxDropzone]", inputs: { ioxDropzoneId: "ioxDropzoneId", ioxDropzoneData: "ioxDropzoneData", ioxDropzonePostDrop: "ioxDropzonePostDrop" }, outputs: { ioxDrop: "ioxDrop" }, ngImport: i0 }); }
|
|
5018
|
-
}
|
|
5019
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxDropzoneDirective, decorators: [{
|
|
5020
|
-
type: Directive,
|
|
5021
|
-
args: [{
|
|
5022
|
-
selector: '[ioxDropzone]',
|
|
5023
|
-
standalone: false,
|
|
5024
|
-
}]
|
|
5025
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: DragEngineService }, { type: i0.ChangeDetectorRef }], propDecorators: { ioxDropzoneId: [{
|
|
5026
|
-
type: Input
|
|
5027
|
-
}], ioxDropzoneData: [{
|
|
5028
|
-
type: Input
|
|
5029
|
-
}], ioxDropzonePostDrop: [{
|
|
5030
|
-
type: Input
|
|
5031
|
-
}], ioxDrop: [{
|
|
5032
|
-
type: Output
|
|
5033
|
-
}] } });
|
|
5034
|
-
|
|
5056
|
+
/**
|
|
5057
|
+
* Directive-host Container (Phase 2 of the Moveable migration = task 9c). The real tag (div/section/
|
|
5058
|
+
* ul/ol/li/…, chosen from the `htmlTag` trait by RenderDirective via `hostElement`) IS the host — no
|
|
5059
|
+
* `<app-builder-container>` wrapper, no separate `.container-root` — matching the SSR engine. The
|
|
5060
|
+
* dropzone is registered on the host itself; the `.container-child` draggable wrappers are UNCHANGED,
|
|
5061
|
+
* so interact.js (flag-off) keeps working. Also backs the List (`ul`/`ol`) and ListItem (`li`) types.
|
|
5062
|
+
* See moveable-migration.md.
|
|
5063
|
+
*/
|
|
5035
5064
|
class BuilderContainerComponent {
|
|
5036
|
-
constructor(dragEngine, cdr) {
|
|
5065
|
+
constructor(elRef, dragEngine, cdr) {
|
|
5066
|
+
this.elRef = elRef;
|
|
5037
5067
|
this.dragEngine = dragEngine;
|
|
5038
5068
|
this.cdr = cdr;
|
|
5039
5069
|
this.children = [];
|
|
@@ -5047,8 +5077,13 @@ class BuilderContainerComponent {
|
|
|
5047
5077
|
this.childMouseEnter = new EventEmitter();
|
|
5048
5078
|
this.childMouseLeave = new EventEmitter();
|
|
5049
5079
|
}
|
|
5050
|
-
ngOnInit() {
|
|
5051
|
-
|
|
5080
|
+
ngOnInit() {
|
|
5081
|
+
// Register the HOST as the dropzone (was the ioxDropzone directive on `.container-root`).
|
|
5082
|
+
this.dragEngine.registerDropzone(this.dropListId, this.elRef.nativeElement, this.children, this.cdr, (evt) => this.onDrop(evt));
|
|
5083
|
+
}
|
|
5084
|
+
ngOnDestroy() {
|
|
5085
|
+
this.dragEngine.unregisterDropzone(this.dropListId, this.elRef.nativeElement);
|
|
5086
|
+
}
|
|
5052
5087
|
get listOrientation() {
|
|
5053
5088
|
const d = this.style?.['display'];
|
|
5054
5089
|
if (d === 'flex' || d === 'inline-flex') {
|
|
@@ -5061,77 +5096,53 @@ class BuilderContainerComponent {
|
|
|
5061
5096
|
onDrop(event) {
|
|
5062
5097
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
5063
5098
|
}
|
|
5064
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderContainerComponent, deps: [{ token: DragEngineService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5065
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
5066
|
-
<
|
|
5067
|
-
<
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
</div>
|
|
5071
|
-
<div *ngFor="let child of children"
|
|
5072
|
-
[ngClass]="['container-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
5073
|
-
ioxDraggable
|
|
5074
|
-
[ioxDragData]="child"
|
|
5075
|
-
[ioxDragSourceId]="dropListId">
|
|
5076
|
-
<ng-container
|
|
5077
|
-
[ioxRender]="child"
|
|
5078
|
-
(onClick)="childClick.emit($event)"
|
|
5079
|
-
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
5080
|
-
(onMouseLeave)="childMouseLeave.emit()">
|
|
5081
|
-
</ng-container>
|
|
5082
|
-
</div>
|
|
5083
|
-
</ng-template>
|
|
5099
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderContainerComponent, deps: [{ token: i0.ElementRef }, { token: DragEngineService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5100
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: BuilderContainerComponent, isStandalone: false, selector: "div[iox-container],section[iox-container],article[iox-container],header[iox-container],footer[iox-container],main[iox-container],nav[iox-container],ul[iox-container],ol[iox-container],li[iox-container]", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", htmlTag: "htmlTag", start: "start", reversed: "reversed" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, host: { properties: { "class": "'container-root iox-node-' + nodeId + (listOrientation === 'horizontal' ? ' is-horizontal' : '')", "attr.start": "htmlTag === 'ol' && start !== 1 ? start : null", "attr.reversed": "htmlTag === 'ol' && reversed ? '' : null" } }, ngImport: i0, template: `
|
|
5101
|
+
<div *ngIf="!children.length" class="container-placeholder">
|
|
5102
|
+
<i class="ph-thin ph-square"></i>
|
|
5103
|
+
<span>Drop components here</span>
|
|
5104
|
+
</div>
|
|
5084
5105
|
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
`, isInline: true, styles: ["
|
|
5106
|
+
<div *ngFor="let child of children"
|
|
5107
|
+
[ngClass]="['container-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
5108
|
+
ioxDraggable
|
|
5109
|
+
[ioxDragData]="child"
|
|
5110
|
+
[ioxDragSourceId]="dropListId">
|
|
5111
|
+
<ng-container
|
|
5112
|
+
[ioxRender]="child"
|
|
5113
|
+
(onClick)="childClick.emit($event)"
|
|
5114
|
+
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
5115
|
+
(onMouseLeave)="childMouseLeave.emit()">
|
|
5116
|
+
</ng-container>
|
|
5117
|
+
</div>
|
|
5118
|
+
`, isInline: true, styles: [":host{position:relative;box-sizing:border-box}.container-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none;z-index:0;opacity:0;transition:opacity .15s}:host(:hover) .container-placeholder,:host(.iox-drag-over) .container-placeholder{opacity:1}.container-placeholder i{font-size:18px}.container-child{min-width:0}\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: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId"] }] }); }
|
|
5098
5119
|
}
|
|
5099
5120
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BuilderContainerComponent, decorators: [{
|
|
5100
5121
|
type: Component,
|
|
5101
|
-
args: [{ selector: '
|
|
5102
|
-
<
|
|
5103
|
-
<
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
</div>
|
|
5107
|
-
<div *ngFor="let child of children"
|
|
5108
|
-
[ngClass]="['container-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
5109
|
-
ioxDraggable
|
|
5110
|
-
[ioxDragData]="child"
|
|
5111
|
-
[ioxDragSourceId]="dropListId">
|
|
5112
|
-
<ng-container
|
|
5113
|
-
[ioxRender]="child"
|
|
5114
|
-
(onClick)="childClick.emit($event)"
|
|
5115
|
-
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
5116
|
-
(onMouseLeave)="childMouseLeave.emit()">
|
|
5117
|
-
</ng-container>
|
|
5118
|
-
</div>
|
|
5119
|
-
</ng-template>
|
|
5122
|
+
args: [{ selector: 'div[iox-container],section[iox-container],article[iox-container],header[iox-container],footer[iox-container],main[iox-container],nav[iox-container],ul[iox-container],ol[iox-container],li[iox-container]', template: `
|
|
5123
|
+
<div *ngIf="!children.length" class="container-placeholder">
|
|
5124
|
+
<i class="ph-thin ph-square"></i>
|
|
5125
|
+
<span>Drop components here</span>
|
|
5126
|
+
</div>
|
|
5120
5127
|
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
`,
|
|
5134
|
-
|
|
5128
|
+
<div *ngFor="let child of children"
|
|
5129
|
+
[ngClass]="['container-child', 'iox-outer-' + (child.styleId ?? child.id)]"
|
|
5130
|
+
ioxDraggable
|
|
5131
|
+
[ioxDragData]="child"
|
|
5132
|
+
[ioxDragSourceId]="dropListId">
|
|
5133
|
+
<ng-container
|
|
5134
|
+
[ioxRender]="child"
|
|
5135
|
+
(onClick)="childClick.emit($event)"
|
|
5136
|
+
(onMouseEnter)="childMouseEnter.emit($event)"
|
|
5137
|
+
(onMouseLeave)="childMouseLeave.emit()">
|
|
5138
|
+
</ng-container>
|
|
5139
|
+
</div>
|
|
5140
|
+
`, host: {
|
|
5141
|
+
'[class]': "'container-root iox-node-' + nodeId + (listOrientation === 'horizontal' ? ' is-horizontal' : '')",
|
|
5142
|
+
'[attr.start]': "htmlTag === 'ol' && start !== 1 ? start : null",
|
|
5143
|
+
'[attr.reversed]': "htmlTag === 'ol' && reversed ? '' : null",
|
|
5144
|
+
}, standalone: false, styles: [":host{position:relative;box-sizing:border-box}.container-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px;pointer-events:none;-webkit-user-select:none;user-select:none;z-index:0;opacity:0;transition:opacity .15s}:host(:hover) .container-placeholder,:host(.iox-drag-over) .container-placeholder{opacity:1}.container-placeholder i{font-size:18px}.container-child{min-width:0}\n"] }]
|
|
5145
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: DragEngineService }, { type: i0.ChangeDetectorRef }], propDecorators: { children: [{
|
|
5135
5146
|
type: Input
|
|
5136
5147
|
}], style: [{
|
|
5137
5148
|
type: Input
|
|
@@ -5153,6 +5164,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5153
5164
|
type: Output
|
|
5154
5165
|
}] } });
|
|
5155
5166
|
|
|
5167
|
+
class IoxDropzoneDirective {
|
|
5168
|
+
constructor(el, dragEngine, cdr) {
|
|
5169
|
+
this.el = el;
|
|
5170
|
+
this.dragEngine = dragEngine;
|
|
5171
|
+
this.cdr = cdr;
|
|
5172
|
+
this.ioxDropzoneId = '';
|
|
5173
|
+
this.ioxDropzoneData = [];
|
|
5174
|
+
this.ioxDrop = new EventEmitter();
|
|
5175
|
+
}
|
|
5176
|
+
ngOnInit() {
|
|
5177
|
+
this.dragEngine.registerDropzone(this.ioxDropzoneId, this.el.nativeElement, this.ioxDropzoneData, this.cdr, (evt) => this.ioxDrop.emit(evt), this.ioxDropzonePostDrop);
|
|
5178
|
+
}
|
|
5179
|
+
ngOnDestroy() {
|
|
5180
|
+
this.dragEngine.unregisterDropzone(this.ioxDropzoneId, this.el.nativeElement);
|
|
5181
|
+
}
|
|
5182
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxDropzoneDirective, deps: [{ token: i0.ElementRef }, { token: DragEngineService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
5183
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: IoxDropzoneDirective, isStandalone: false, selector: "[ioxDropzone]", inputs: { ioxDropzoneId: "ioxDropzoneId", ioxDropzoneData: "ioxDropzoneData", ioxDropzonePostDrop: "ioxDropzonePostDrop" }, outputs: { ioxDrop: "ioxDrop" }, ngImport: i0 }); }
|
|
5184
|
+
}
|
|
5185
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: IoxDropzoneDirective, decorators: [{
|
|
5186
|
+
type: Directive,
|
|
5187
|
+
args: [{
|
|
5188
|
+
selector: '[ioxDropzone]',
|
|
5189
|
+
standalone: false,
|
|
5190
|
+
}]
|
|
5191
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: DragEngineService }, { type: i0.ChangeDetectorRef }], propDecorators: { ioxDropzoneId: [{
|
|
5192
|
+
type: Input
|
|
5193
|
+
}], ioxDropzoneData: [{
|
|
5194
|
+
type: Input
|
|
5195
|
+
}], ioxDropzonePostDrop: [{
|
|
5196
|
+
type: Input
|
|
5197
|
+
}], ioxDrop: [{
|
|
5198
|
+
type: Output
|
|
5199
|
+
}] } });
|
|
5200
|
+
|
|
5156
5201
|
class BuilderLinkedContainerComponent {
|
|
5157
5202
|
constructor(dragEngine, cdr, elRef, isPreview) {
|
|
5158
5203
|
this.dragEngine = dragEngine;
|