@vectoriox/iox-builder 4.38.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/vectoriox-iox-builder.mjs +780 -792
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +22 -19
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ import { SelectModule } from 'primeng/select';
|
|
|
16
16
|
import { TooltipModule } from 'primeng/tooltip';
|
|
17
17
|
import { DialogModule } from 'primeng/dialog';
|
|
18
18
|
import * as i1 from '@vectoriox/iox-ui';
|
|
19
|
-
import { VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, setOverride, defaultElementStyleFor, applyElementStyle,
|
|
19
|
+
import { VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, Breakpoint, BREAKPOINTS, countOverrides, setOverride, defaultElementStyleFor, applyElementStyle, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, statePseudoClass, compileTokenCssBlocks, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, breakpointDef, breakpointForWidth, FormRenderState, safeSetInput, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, composeVirtualTraits, stateNamesIn, partClassName, syncElementStyleMarker, resolveInputs, setInputOverride, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, resolveBoundValue, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule, PALETTE_PRESETS, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
|
|
20
20
|
export { ComponentInstanceRegistryService, VIRTUAL_TRAIT_KEYS, composeVirtualTraits } from '@vectoriox/iox-ui';
|
|
21
21
|
import Lenis from 'lenis';
|
|
22
22
|
import { Subject, BehaviorSubject, of, forkJoin } from 'rxjs';
|
|
@@ -1797,78 +1797,463 @@ function resolveDragTarget(el, selectedElement, isDragHandle) {
|
|
|
1797
1797
|
}
|
|
1798
1798
|
|
|
1799
1799
|
/**
|
|
1800
|
-
*
|
|
1800
|
+
* CSS container name of the builder canvas frame (and of the global-elements overlay). Breakpoint
|
|
1801
|
+
* rules are emitted as `@container iox-canvas (max-width: …)`: the frame's layout width IS the
|
|
1802
|
+
* simulated viewport and is unaffected by the zoom `transform: scale`, whereas a `@media` query
|
|
1803
|
+
* would read the editor window. Set on the frame in builder.component.scss.
|
|
1804
|
+
*/
|
|
1805
|
+
const BUILDER_CANVAS_CONTAINER = 'iox-canvas';
|
|
1806
|
+
/**
|
|
1807
|
+
* The class that carries the ORG's design tokens on the canvas. Tokens used to be declared on
|
|
1808
|
+
* `:root`, where they collided with the admin's own `--iox-*` chrome tokens (`accent`, `surface-2`,
|
|
1809
|
+
* `scrim`, `focus-ring`… — DS2 seeds all 161 Crisp rows, so the clash became universal and the
|
|
1810
|
+
* admin turned the studio's accent colour). Every surface that renders site content through this
|
|
1811
|
+
* registry — the canvas frame, the global-elements overlay, preview, specimens — wears this class.
|
|
1812
|
+
*/
|
|
1813
|
+
const BUILDER_TOKEN_SCOPE_CLASS = 'iox-token-scope';
|
|
1814
|
+
/**
|
|
1815
|
+
* StyleRegistryService — manages a single <style> tag for the builder canvas.
|
|
1801
1816
|
*
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1817
|
+
* Each builder component gets two CSS classes:
|
|
1818
|
+
* .iox-node-{id} — applied to the inner element; handles layout/visual props
|
|
1819
|
+
* (display, flex, background, border, padding, etc.)
|
|
1820
|
+
* .iox-outer-{id} — applied to the host element by RenderDirective; handles
|
|
1821
|
+
* properties that participate in the PARENT flow (margin,
|
|
1822
|
+
* align-self, flex-grow/shrink/basis, order).
|
|
1823
|
+
*
|
|
1824
|
+
* This split is necessary because margin on an inner wrapper only adds internal
|
|
1825
|
+
* space — it does not push sibling elements. Applying margin to the host element
|
|
1826
|
+
* (which IS the flex/block child in the parent container) makes it work correctly.
|
|
1827
|
+
*
|
|
1828
|
+
* State styles (hover/active/focus) are stored under key `${nodeId}:${state}`
|
|
1829
|
+
* and compiled to `.iox-node-{id}:hover { … }` selectors.
|
|
1830
|
+
*
|
|
1831
|
+
* Design tokens are compiled by the SHARED token compiler (`compileTokenCssBlocks`, design-core)
|
|
1832
|
+
* scoped to `.iox-token-scope`: its base half (variables, scheme classes, reduced-motion and
|
|
1833
|
+
* reduced-transparency blocks) is prepended to all other rules and its dark half appended LAST —
|
|
1834
|
+
* the same two halves, in the same places, as the SSR engine.
|
|
1835
|
+
*
|
|
1836
|
+
* Scoped to PageUiComponent.providers[] — one stylesheet per builder instance.
|
|
1804
1837
|
*/
|
|
1805
|
-
class
|
|
1838
|
+
class StyleRegistryService {
|
|
1806
1839
|
constructor() {
|
|
1807
|
-
this.
|
|
1808
|
-
|
|
1809
|
-
|
|
1840
|
+
this.changes$ = new Subject();
|
|
1841
|
+
this.rules = new Map();
|
|
1842
|
+
/** Breakpoint rules, kept apart so they flush AFTER every base rule — equal specificity, so
|
|
1843
|
+
* source order decides (plan trap 1). Keyed per breakpoint like `rules` (`id`, `id:state`). */
|
|
1844
|
+
this.responsiveRules = new Map();
|
|
1845
|
+
this.responsiveInputs = new Map();
|
|
1846
|
+
/** Which breakpoint rule keys each node owns — so re-emitting one node's rules is O(its rules),
|
|
1847
|
+
* not a scan of every breakpoint rule on the page (this runs on every node render). */
|
|
1848
|
+
this.responsiveKeys = new Map();
|
|
1849
|
+
this.stateScopes = new Map();
|
|
1850
|
+
/** Node type per css id — the compiler strips the props a type's renderer owns (a Dialog's
|
|
1851
|
+
* positioning, an Icon's line-height) from EVERY rule of that node, whoever upserts it. */
|
|
1852
|
+
this.nodeTypes = new Map();
|
|
1853
|
+
this.tokenCss = { base: '', dark: '' };
|
|
1854
|
+
this.styleEl = null;
|
|
1855
|
+
this._flushPending = false;
|
|
1810
1856
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1857
|
+
/**
|
|
1858
|
+
* Properties that must live on the OUTER wrapper (.iox-outer-{id}) because they
|
|
1859
|
+
* affect how the element participates in its PARENT layout (flex/block flow).
|
|
1860
|
+
* Everything else goes to the INNER element (.iox-node-{id}).
|
|
1861
|
+
*
|
|
1862
|
+
* Exception — out-of-flow positioning (position:fixed/absolute):
|
|
1863
|
+
* When position is fixed or absolute, width is redirected to the inner element
|
|
1864
|
+
* (the positioned one) and a default width:100% is added if none is set, so the
|
|
1865
|
+
* element stays visible. top/right/bottom/left/zIndex are always inner because
|
|
1866
|
+
* they are co-ordinates of the positioned element, not the flow wrapper.
|
|
1867
|
+
* position itself goes to inner with `!important` to beat component-scoped CSS.
|
|
1868
|
+
*/
|
|
1869
|
+
static { this.OUTER_PROPS = new Set([
|
|
1870
|
+
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
|
|
1871
|
+
'alignSelf', 'flexGrow', 'flexShrink', 'flexBasis', 'order',
|
|
1872
|
+
// Grid-child placement — like the flex-child props above, these position the node
|
|
1873
|
+
// within the PARENT's grid, so they must land on the outer host, not the inner div.
|
|
1874
|
+
'gridColumn', 'gridRow', 'justifySelf',
|
|
1875
|
+
'width',
|
|
1876
|
+
]); }
|
|
1877
|
+
/**
|
|
1878
|
+
* Properties that need !important on the inner element to beat Angular's scoped-CSS
|
|
1879
|
+
* specificity boost. CANVAS-ONLY (H2 audit, 2026-10-11): the shared compiler never forces
|
|
1880
|
+
* importance and the SSR engine never emits it — this is the builder's own fight with its
|
|
1881
|
+
* scoped component styles. The A4 dialog centring clash this once caused is resolved
|
|
1882
|
+
* structurally (the grid stage PLACES a static surface), so the set stays at exactly one
|
|
1883
|
+
* documented member; adding to it needs the same scrutiny.
|
|
1884
|
+
*/
|
|
1885
|
+
static { this.IMPORTANT_PROPS = new Set(['position']); }
|
|
1886
|
+
init() {
|
|
1887
|
+
this.styleEl = document.createElement('style');
|
|
1888
|
+
this.styleEl.id = 'iox-runtime-styles';
|
|
1889
|
+
document.head.appendChild(this.styleEl);
|
|
1816
1890
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1891
|
+
/**
|
|
1892
|
+
* Write or update the base styles for a node, or a pseudo-class state override.
|
|
1893
|
+
*
|
|
1894
|
+
* @param nodeId The node's CSS id (used in `.iox-node-{nodeId}`)
|
|
1895
|
+
* @param styles Flat CSS property map (camelCase keys)
|
|
1896
|
+
* @param state Optional pseudo-class state ('hover' | 'active' | 'focus' | …).
|
|
1897
|
+
* When provided, compiles to `.iox-node-{id}:{state} { … }`.
|
|
1898
|
+
* State rules are NOT partitioned into inner/outer.
|
|
1899
|
+
* @param relativeToId For RELATIONAL states (e.g. 'parent-hover') this is the ancestor
|
|
1900
|
+
* css id; compiles to `.iox-node-{relativeToId}:{base} .iox-node-{nodeId} { … }`.
|
|
1901
|
+
* For TARGETED states (e.g. 'trigger-hover') this is the SCOPE css id
|
|
1902
|
+
* (nearest common ancestor of the styled node + trigger).
|
|
1903
|
+
* @param triggerId For TARGETED states only: the css id of the trigger node whose
|
|
1904
|
+
* pseudo-class drives the state. Compiles a `:has()`-scoped selector.
|
|
1905
|
+
*/
|
|
1906
|
+
/** Record a node's type (RenderDirective, before its first upsert) and recompile nothing —
|
|
1907
|
+
* every later rule for the node is stripped of what the type's renderer owns. */
|
|
1908
|
+
setNodeType(nodeId, type) {
|
|
1909
|
+
if (nodeId && type)
|
|
1910
|
+
this.nodeTypes.set(nodeId, type);
|
|
1819
1911
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1912
|
+
ownProps(nodeId, styles) {
|
|
1913
|
+
const type = this.nodeTypes.get(nodeId);
|
|
1914
|
+
return type ? stripSuppressedProps(type, styles) : styles;
|
|
1822
1915
|
}
|
|
1823
|
-
upsert(
|
|
1824
|
-
|
|
1916
|
+
upsert(nodeId, styles, state, relativeToId, triggerId) {
|
|
1917
|
+
if (!nodeId)
|
|
1918
|
+
return;
|
|
1919
|
+
styles = this.ownProps(nodeId, styles);
|
|
1920
|
+
if (state) {
|
|
1921
|
+
const key = `${nodeId}:${state}`;
|
|
1922
|
+
// Remember the scope a relational / targeted rule was given, so the same state at a
|
|
1923
|
+
// breakpoint is scoped identically without re-resolving it (see upsertResponsive).
|
|
1924
|
+
if (relativeToId || triggerId)
|
|
1925
|
+
this.stateScopes.set(key, { relativeToId, triggerId });
|
|
1926
|
+
const selector = this.stateSelector(nodeId, state, relativeToId, triggerId);
|
|
1927
|
+
const css = selector ? this.compile(selector, styles) : '';
|
|
1928
|
+
if (css)
|
|
1929
|
+
this.rules.set(key, css);
|
|
1930
|
+
else
|
|
1931
|
+
this.rules.delete(key);
|
|
1932
|
+
if (this.responsiveInputs.has(nodeId))
|
|
1933
|
+
this.emitResponsive(nodeId);
|
|
1934
|
+
this.flush();
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
const { main, marker } = this.nodeRuleCss(nodeId, styles, this.isOutOfFlow(styles), DeclarationContext.Base);
|
|
1938
|
+
if (marker)
|
|
1939
|
+
this.rules.set(`${nodeId}::marker`, marker);
|
|
1940
|
+
else
|
|
1941
|
+
this.rules.delete(`${nodeId}::marker`);
|
|
1942
|
+
if (main)
|
|
1943
|
+
this.rules.set(nodeId, main);
|
|
1944
|
+
else
|
|
1945
|
+
this.rules.delete(nodeId);
|
|
1946
|
+
this.flush();
|
|
1825
1947
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1948
|
+
/**
|
|
1949
|
+
* Write or clear the styles for one PART of a compound component (board 7 — "the failure
|
|
1950
|
+
* design"), optionally further scoped to a validation state ('wrong'/'accepted'). Compiles
|
|
1951
|
+
* through the exact same `compile()` every other rule uses — `buildPartSelector` (shared,
|
|
1952
|
+
* `@vectoriox/iox-ui`) just builds a different selector string. Unlike interaction/relational/
|
|
1953
|
+
* targeted states, a validation state compounds a CLASS onto the node (`.iox-invalid`), not a
|
|
1954
|
+
* `:pseudo-class`, since `IoxFieldComponent` sets that class itself from `forceInvalid`/
|
|
1955
|
+
* `showValid` — there is no real "wrong" pseudo-class to hook.
|
|
1956
|
+
*/
|
|
1957
|
+
upsertPart(nodeId, partClassName, styles, state) {
|
|
1958
|
+
if (!nodeId || !partClassName)
|
|
1959
|
+
return;
|
|
1960
|
+
const key = `${nodeId}:part:${partClassName}${state ? ':' + state : ''}`;
|
|
1961
|
+
const validationClass = state ? VALIDATION_STATE_CLASS[state] : undefined;
|
|
1962
|
+
const selector = buildPartSelector(nodeId, partClassName, validationClass);
|
|
1963
|
+
const css = Object.keys(styles).length ? this.compile(selector, styles) : '';
|
|
1964
|
+
if (css)
|
|
1965
|
+
this.rules.set(key, css);
|
|
1966
|
+
else
|
|
1967
|
+
this.rules.delete(key);
|
|
1968
|
+
this.flush();
|
|
1828
1969
|
}
|
|
1829
|
-
/**
|
|
1830
|
-
|
|
1831
|
-
|
|
1970
|
+
/**
|
|
1971
|
+
* Emit (or clear) a node's breakpoint rules. `planResponsiveRules` — shared with the SSR engine —
|
|
1972
|
+
* decides what each breakpoint emits; this only turns it into selectors, through the SAME
|
|
1973
|
+
* inner/outer split and state selectors as the base rules, compiled in the Override context
|
|
1974
|
+
* (a CSS initial value undoes the wider rule there, so it must not be dropped).
|
|
1975
|
+
*
|
|
1976
|
+
* @param styleProps The node's full BASE style map (raw — virtual traits not yet composed).
|
|
1977
|
+
*/
|
|
1978
|
+
upsertResponsive(nodeId, styleProps, stateStyles, responsive) {
|
|
1979
|
+
if (!nodeId)
|
|
1980
|
+
return;
|
|
1981
|
+
const hasOverrides = !!responsive && Object.keys(responsive).length > 0;
|
|
1982
|
+
// The common case — a node with no overrides that never had any — costs nothing.
|
|
1983
|
+
if (!hasOverrides && !this.responsiveInputs.has(nodeId))
|
|
1984
|
+
return;
|
|
1985
|
+
if (hasOverrides)
|
|
1986
|
+
this.responsiveInputs.set(nodeId, { styleProps, stateStyles, responsive: responsive });
|
|
1987
|
+
else
|
|
1988
|
+
this.responsiveInputs.delete(nodeId);
|
|
1989
|
+
this.emitResponsive(nodeId);
|
|
1990
|
+
this.flush();
|
|
1832
1991
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1992
|
+
emitResponsive(nodeId) {
|
|
1993
|
+
this.clearResponsive(nodeId);
|
|
1994
|
+
const input = this.responsiveInputs.get(nodeId);
|
|
1995
|
+
if (!input)
|
|
1996
|
+
return;
|
|
1997
|
+
for (const layer of planResponsiveRules(input.styleProps, input.stateStyles, input.responsive)) {
|
|
1998
|
+
const bp = layer.breakpoint;
|
|
1999
|
+
const layerProps = this.ownProps(nodeId, layer.styleProps);
|
|
2000
|
+
// Where `width` lands depends on the position in force AT this breakpoint.
|
|
2001
|
+
const outOfFlow = this.isOutOfFlow(resolveStyleProps(input.styleProps, input.responsive, bp));
|
|
2002
|
+
const { main, marker } = this.nodeRuleCss(nodeId, layerProps, outOfFlow, DeclarationContext.Override);
|
|
2003
|
+
if (main)
|
|
2004
|
+
this.setResponsiveRule(nodeId, bp, nodeId, main);
|
|
2005
|
+
if (marker)
|
|
2006
|
+
this.setResponsiveRule(nodeId, bp, `${nodeId}::marker`, marker);
|
|
2007
|
+
for (const [state, map] of Object.entries(layer.stateStyles)) {
|
|
2008
|
+
const scope = this.stateScopes.get(`${nodeId}:${state}`);
|
|
2009
|
+
const selector = this.stateSelector(nodeId, state, scope?.relativeToId, scope?.triggerId);
|
|
2010
|
+
// A relational/targeted state whose scope isn't known yet is skipped; it is re-emitted
|
|
2011
|
+
// when the base state rule arrives with its scope (see upsert).
|
|
2012
|
+
const css = selector ? this.compile(selector, this.ownProps(nodeId, map), DeclarationContext.Override) : '';
|
|
2013
|
+
if (css)
|
|
2014
|
+
this.setResponsiveRule(nodeId, bp, `${nodeId}:${state}`, css);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
1836
2017
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2018
|
+
setResponsiveRule(nodeId, bp, key, css) {
|
|
2019
|
+
let bucket = this.responsiveRules.get(bp);
|
|
2020
|
+
if (!bucket)
|
|
2021
|
+
this.responsiveRules.set(bp, (bucket = new Map()));
|
|
2022
|
+
bucket.set(key, css);
|
|
2023
|
+
let owned = this.responsiveKeys.get(nodeId);
|
|
2024
|
+
if (!owned)
|
|
2025
|
+
this.responsiveKeys.set(nodeId, (owned = []));
|
|
2026
|
+
owned.push([bp, key]);
|
|
1840
2027
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
//
|
|
1856
|
-
|
|
1857
|
-
//
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
2028
|
+
clearResponsive(nodeId) {
|
|
2029
|
+
for (const [bp, key] of this.responsiveKeys.get(nodeId) ?? [])
|
|
2030
|
+
this.responsiveRules.get(bp)?.delete(key);
|
|
2031
|
+
this.responsiveKeys.delete(nodeId);
|
|
2032
|
+
}
|
|
2033
|
+
isOutOfFlow(styles) {
|
|
2034
|
+
const pos = String(styles['position'] ?? '');
|
|
2035
|
+
return pos === 'fixed' || pos === 'absolute';
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* The selector BODY (no leading dot) for a state rule, or null when a relational / targeted
|
|
2039
|
+
* state has no scope yet (emitting it unscoped would be wrong — a page-global `:has()`).
|
|
2040
|
+
*/
|
|
2041
|
+
stateSelector(nodeId, state, relativeToId, triggerId) {
|
|
2042
|
+
// Targeted states (trigger-hover): the styled node restyles when an ARBITRARY chosen
|
|
2043
|
+
// element is hovered. The selector is `:has()`-scoped to `relativeToId` (the nearest
|
|
2044
|
+
// common ancestor of styled + trigger) so it stays repetition-safe with one shared rule.
|
|
2045
|
+
if (TARGETED_STATES.includes(state)) {
|
|
2046
|
+
const base = TARGETED_STATE_BASE[state];
|
|
2047
|
+
return relativeToId && triggerId && base
|
|
2048
|
+
? buildTargetedStateSelector(relativeToId, triggerId, nodeId, base) : null;
|
|
2049
|
+
}
|
|
2050
|
+
// Relational states (parent-hover): the pseudo-class lives on an ANCESTOR
|
|
2051
|
+
// and cascades to this element. Requires the ancestor's css id.
|
|
2052
|
+
if (RELATIONAL_STATES.includes(state)) {
|
|
2053
|
+
const base = RELATIONAL_STATE_BASE[state];
|
|
2054
|
+
return relativeToId && base ? `iox-node-${relativeToId}:${base} .iox-node-${nodeId}` : null;
|
|
2055
|
+
}
|
|
2056
|
+
// Validation states (wrong/accepted, board 7) are not real CSS pseudo-classes — `:wrong`
|
|
2057
|
+
// doesn't exist. Compound a CLASS instead, matching the host class IoxFieldComponent
|
|
2058
|
+
// already sets from forceInvalid/showValid. Reuses buildPartSelector with no part, since
|
|
2059
|
+
// "whole node, when wrong" is exactly that function's no-part case.
|
|
2060
|
+
const validationClass = VALIDATION_STATE_CLASS[state];
|
|
2061
|
+
if (validationClass)
|
|
2062
|
+
return buildPartSelector(nodeId, undefined, validationClass);
|
|
2063
|
+
// Structural pseudo-classes (first-child, last-child, nth-child) target the
|
|
2064
|
+
// outer wrapper element so they participate correctly in grid/flex layout.
|
|
2065
|
+
const prefix = STRUCTURAL_STATES.includes(state) ? 'iox-outer' : 'iox-node';
|
|
2066
|
+
// `focus` compiles to :focus-visible (DS2) — shared with the engine via statePseudoClass.
|
|
2067
|
+
return `${prefix}-${nodeId}:${statePseudoClass(state)}`;
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* The inner + outer rule (joined) and the `::marker` rule for one composed style map.
|
|
2071
|
+
* `isOutOfFlow` is passed in rather than read from `styles` because a breakpoint layer is
|
|
2072
|
+
* sparse — the position in force may come from a wider layer.
|
|
2073
|
+
*/
|
|
2074
|
+
nodeRuleCss(nodeId, styles, isOutOfFlow, context) {
|
|
2075
|
+
// ::marker pseudo-element — emitted as a separate rule, not part of inner/outer split.
|
|
2076
|
+
const markerStyles = styles['__markerStyles'];
|
|
2077
|
+
const marker = markerStyles && Object.keys(markerStyles).length
|
|
2078
|
+
? this.compile(`iox-node-${nodeId}::marker`, markerStyles, context) : '';
|
|
2079
|
+
const inner = {};
|
|
2080
|
+
const outer = {};
|
|
2081
|
+
for (const [k, v] of Object.entries(styles)) {
|
|
2082
|
+
if (k === '__markerStyles')
|
|
2083
|
+
continue;
|
|
2084
|
+
const wantsOuter = StyleRegistryService.OUTER_PROPS.has(k);
|
|
2085
|
+
// width goes to inner for fixed/absolute (the positioned element owns its own width)
|
|
2086
|
+
if (wantsOuter && !(isOutOfFlow && k === 'width')) {
|
|
2087
|
+
outer[k] = v;
|
|
2088
|
+
}
|
|
2089
|
+
else {
|
|
2090
|
+
inner[k] = v;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
// Ensure fixed/absolute elements stay visible when no explicit width is set. A base-rule
|
|
2094
|
+
// render default only — a sparse breakpoint layer must not invent a width.
|
|
2095
|
+
if (context === DeclarationContext.Base && isOutOfFlow && inner['width'] === undefined) {
|
|
2096
|
+
inner['width'] = '100%';
|
|
2097
|
+
}
|
|
2098
|
+
const innerCss = this.compile(`iox-node-${nodeId}`, inner, context);
|
|
2099
|
+
const outerCss = this.compile(`iox-outer-${nodeId}`, outer, context);
|
|
2100
|
+
return { main: [innerCss, outerCss].filter(Boolean).join('\n'), marker };
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* The org's design tokens on the canvas — through the SHARED compiler (`compileTokenCssBlocks`,
|
|
2104
|
+
* design-core), scoped to `.iox-token-scope` (BUILDER_TOKEN_SCOPE_CLASS — never `:root`, which
|
|
2105
|
+
* the admin's chrome tokens own). So the canvas gets exactly what the live site gets: the
|
|
2106
|
+
* variables, the scheme classes, the reduced-motion / reduced-transparency blocks and the dark
|
|
2107
|
+
* block, LAST. It used to be a hand-written copy that emitted variables and dark only.
|
|
2108
|
+
* Pass the full row set every time (an empty list clears).
|
|
2109
|
+
*/
|
|
2110
|
+
upsertTokens(tokens) {
|
|
2111
|
+
this.tokenCss = compileTokenCssBlocks(tokens, { scope: `.${BUILDER_TOKEN_SCOPE_CLASS}` });
|
|
2112
|
+
this.flush();
|
|
2113
|
+
}
|
|
2114
|
+
remove(nodeId) {
|
|
2115
|
+
if (!nodeId)
|
|
2116
|
+
return;
|
|
2117
|
+
// Remove base rule and all state rules for this node
|
|
2118
|
+
this.rules.delete(nodeId);
|
|
2119
|
+
for (const key of this.rules.keys()) {
|
|
2120
|
+
if (key.startsWith(`${nodeId}:`)) {
|
|
2121
|
+
this.rules.delete(key);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
for (const key of this.stateScopes.keys()) {
|
|
2125
|
+
if (key.startsWith(`${nodeId}:`))
|
|
2126
|
+
this.stateScopes.delete(key);
|
|
2127
|
+
}
|
|
2128
|
+
this.responsiveInputs.delete(nodeId);
|
|
2129
|
+
this.clearResponsive(nodeId);
|
|
2130
|
+
this.nodeTypes.delete(nodeId);
|
|
2131
|
+
this.flush();
|
|
2132
|
+
}
|
|
2133
|
+
destroy() {
|
|
2134
|
+
this.rules.clear();
|
|
2135
|
+
this.responsiveRules.clear();
|
|
2136
|
+
this.responsiveInputs.clear();
|
|
2137
|
+
this.responsiveKeys.clear();
|
|
2138
|
+
this.stateScopes.clear();
|
|
2139
|
+
this.nodeTypes.clear();
|
|
2140
|
+
this.tokenCss = { base: '', dark: '' };
|
|
2141
|
+
if (this.styleEl) {
|
|
2142
|
+
this.styleEl.remove();
|
|
2143
|
+
this.styleEl = null;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
compile(className, styles, context = DeclarationContext.Base) {
|
|
2147
|
+
// Declaration compilation (kebab-case, viewport-unit rewrite, !important) lives in
|
|
2148
|
+
// the shared style-compiler so the SSR engine produces byte-identical output.
|
|
2149
|
+
// The inner/outer partition + selector emission stay here (builder-only concern).
|
|
2150
|
+
const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS, context);
|
|
2151
|
+
if (!decls.length)
|
|
2152
|
+
return '';
|
|
2153
|
+
const body = decls.map(d => ` ${d};`).join('\n');
|
|
2154
|
+
return `.${className} {\n${body}\n}`;
|
|
2155
|
+
}
|
|
2156
|
+
flush() {
|
|
2157
|
+
if (!this.styleEl || this._flushPending)
|
|
2158
|
+
return;
|
|
2159
|
+
this._flushPending = true;
|
|
2160
|
+
requestAnimationFrame(() => {
|
|
2161
|
+
this._flushPending = false;
|
|
2162
|
+
if (!this.styleEl)
|
|
2163
|
+
return;
|
|
2164
|
+
// The token base first, so variables are available to all subsequent rules.
|
|
2165
|
+
const rest = Array.from(this.rules.values());
|
|
2166
|
+
// Breakpoint rules LAST, widest → narrowest, each as a canvas container query.
|
|
2167
|
+
const breakpoints = BREAKPOINTS
|
|
2168
|
+
.map(({ id }) => this.responsiveRules.get(id))
|
|
2169
|
+
.map((bucket, i) => bucket?.size
|
|
2170
|
+
? wrapInBreakpointQuery(BREAKPOINTS[i].id, Array.from(bucket.values()).join('\n'), BreakpointQueryKind.Container, BUILDER_CANVAS_CONTAINER)
|
|
2171
|
+
: '');
|
|
2172
|
+
// …and the token dark half LAST (the appended-last contract the engine also keeps).
|
|
2173
|
+
this.styleEl.textContent = [this.tokenCss.base, ...rest, ...breakpoints, this.tokenCss.dark].filter(Boolean).join('\n');
|
|
2174
|
+
this.changes$.next();
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: StyleRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2178
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: StyleRegistryService }); }
|
|
2179
|
+
}
|
|
2180
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: StyleRegistryService, decorators: [{
|
|
2181
|
+
type: Injectable
|
|
2182
|
+
}] });
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* SymbolRegistryService — in-memory store for org-level symbols.
|
|
2186
|
+
*
|
|
2187
|
+
* Loaded once per page by PageUiComponent alongside the page layout.
|
|
2188
|
+
* Scoped to PageUiComponent.providers[] via IoxBuilderModule — one registry per builder instance.
|
|
2189
|
+
*/
|
|
2190
|
+
class SymbolRegistryService {
|
|
2191
|
+
constructor() {
|
|
2192
|
+
this.symbols = new Map();
|
|
2193
|
+
/** symbolIds that have a live reusable block in the DB — used to guard isDetachedSymbol. */
|
|
2194
|
+
this.blockSymbolIds = new Set();
|
|
2195
|
+
}
|
|
2196
|
+
setSymbols(symbols) {
|
|
2197
|
+
this.symbols.clear();
|
|
2198
|
+
for (const s of symbols) {
|
|
2199
|
+
this.symbols.set(s.id, s);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
getSymbol(id) {
|
|
2203
|
+
return this.symbols.get(id);
|
|
2204
|
+
}
|
|
2205
|
+
getAll() {
|
|
2206
|
+
return Array.from(this.symbols.values());
|
|
2207
|
+
}
|
|
2208
|
+
upsert(symbol) {
|
|
2209
|
+
this.symbols.set(symbol.id, symbol);
|
|
2210
|
+
}
|
|
2211
|
+
remove(id) {
|
|
2212
|
+
this.symbols.delete(id);
|
|
2213
|
+
}
|
|
2214
|
+
/** Set the symbolIds that have a live reusable block. Called on page load. */
|
|
2215
|
+
setBlockSymbolIds(ids) {
|
|
2216
|
+
this.blockSymbolIds = new Set(ids);
|
|
2217
|
+
}
|
|
2218
|
+
/** Returns true only when the symbol has a live backing reusable block. */
|
|
2219
|
+
isBlockSymbol(id) {
|
|
2220
|
+
return this.blockSymbolIds.has(id);
|
|
2221
|
+
}
|
|
2222
|
+
/** Remove a symbolId from the block-backed set (called when a block is deleted). */
|
|
2223
|
+
removeBlockSymbolId(id) {
|
|
2224
|
+
this.blockSymbolIds.delete(id);
|
|
2225
|
+
}
|
|
2226
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SymbolRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2227
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SymbolRegistryService }); }
|
|
2228
|
+
}
|
|
2229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SymbolRegistryService, decorators: [{
|
|
2230
|
+
type: Injectable
|
|
2231
|
+
}] });
|
|
2232
|
+
|
|
2233
|
+
/** Marks the overlay's dedicated drag-handle chrome (see overlay.component.html) — it carries no
|
|
2234
|
+
* node of its own, so a drag started on it always redirects to whatever is currently selected. */
|
|
2235
|
+
const DRAG_HANDLE_ATTR = 'data-iox-drag-handle';
|
|
2236
|
+
// Ease-out-quint — fast start, precise stop, no overshoot. Used for FLIP sibling
|
|
2237
|
+
// reflow and the insertion indicator open/close. Deliberately NOT a spring/back
|
|
2238
|
+
// curve (e.g. cubic-bezier(0.34,1.56,0.64,1)): multiple siblings animating with
|
|
2239
|
+
// overshoot at once reads as jitter, not polish — reserve overshoot for single-
|
|
2240
|
+
// element motion (ghost drop-settle, a later phase).
|
|
2241
|
+
const MOTION_EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
2242
|
+
// Per-frame exponential smoothing factor for the drag ghost's cursor-follow.
|
|
2243
|
+
// Was: ghost position set 1:1 to the pointer every dragmove (rigid/teleporty).
|
|
2244
|
+
// Lower = more lag/weight, higher = snappier. 0.35 catches up to the pointer
|
|
2245
|
+
// within ~5-6 frames (~80-100ms) — reads as "responsive but weighted".
|
|
2246
|
+
const GHOST_FOLLOW_SMOOTHING = 0.35;
|
|
2247
|
+
function deepCloneNode(node) {
|
|
2248
|
+
const clone = JSON.parse(JSON.stringify(node));
|
|
2249
|
+
// Fresh ids + remap intra-subtree references (stateTriggers, interaction targets) so a cloned
|
|
2250
|
+
// structure stays self-consistent — e.g. trigger-hover keeps pointing at the clone's own trigger.
|
|
2251
|
+
regenerateNodeIds(clone);
|
|
2252
|
+
return clone;
|
|
2253
|
+
}
|
|
2254
|
+
class DragEngineService {
|
|
2255
|
+
constructor(registry, symbolRegistry, overlayService) {
|
|
2256
|
+
this.registry = registry;
|
|
1872
2257
|
this.symbolRegistry = symbolRegistry;
|
|
1873
2258
|
this.overlayService = overlayService;
|
|
1874
2259
|
// ── Drop-zone registry ────────────────────────────────────
|
|
@@ -2231,6 +2616,10 @@ class DragEngineService {
|
|
|
2231
2616
|
'margin:0',
|
|
2232
2617
|
].join(';');
|
|
2233
2618
|
clone.classList.add('iox-drag-moving');
|
|
2619
|
+
// The org's tokens are declared on the token scope (never :root), and <body> is outside
|
|
2620
|
+
// it — so a ghost without the scope class resolved every var(--iox-…) to nothing and an
|
|
2621
|
+
// element-styled button lost its fill and padding mid-drag (DS2 verification).
|
|
2622
|
+
clone.classList.add(BUILDER_TOKEN_SCOPE_CLASS);
|
|
2234
2623
|
document.body.appendChild(clone);
|
|
2235
2624
|
this._overlay = clone;
|
|
2236
2625
|
this._startGhostFollow(rect.left, rect.top);
|
|
@@ -3348,723 +3737,339 @@ class InteractionEngineService {
|
|
|
3348
3737
|
}
|
|
3349
3738
|
// Context actions write STATE, not animation. `selfEl` is deliberate: $item is read by
|
|
3350
3739
|
// walking up from the element actually hovered/clicked, which for a repeater PREVIEW ROW
|
|
3351
|
-
// is the clone — so each row publishes its own item, exactly as on the live site.
|
|
3352
|
-
if (isContextAction(action)) {
|
|
3353
|
-
this.applyContextAction(action, selfEl ?? null, 'forward');
|
|
3354
|
-
continue;
|
|
3355
|
-
}
|
|
3356
|
-
// selfEl lets 'self'-targeted actions resolve for preview CLONES, which aren't
|
|
3357
|
-
// overlay-registered (getNodeRef would return undefined) — see attachTo. (issue #2)
|
|
3358
|
-
const target = this.resolveTarget(node, action, selfEl);
|
|
3359
|
-
if (!target)
|
|
3360
|
-
continue;
|
|
3361
|
-
this.executeAction(target, action);
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
/**
|
|
3365
|
-
* Execute a context action against the shared store. The forward/reverse semantics come from
|
|
3366
|
-
* `contextActionEffect` in @vectoriox/iox-ui — the same function the SSR engine calls, so the
|
|
3367
|
-
* builder canvas and the live site behave identically.
|
|
3368
|
-
*/
|
|
3369
|
-
applyContextAction(action, el, pass) {
|
|
3370
|
-
const effect = contextActionEffect(action, pass);
|
|
3371
|
-
if (!effect)
|
|
3372
|
-
return;
|
|
3373
|
-
if (effect.op === 'clear') {
|
|
3374
|
-
this.contextStore.clear(effect.slot);
|
|
3375
|
-
return;
|
|
3376
|
-
}
|
|
3377
|
-
const value = resolveContextValue(effect.value, {
|
|
3378
|
-
item: this.rowItemFor(el),
|
|
3379
|
-
resolvedData: this.contextData,
|
|
3380
|
-
});
|
|
3381
|
-
this.contextStore.set(effect.slot, value);
|
|
3382
|
-
}
|
|
3383
|
-
/** The repeater item this element belongs to — the nearest marked row ancestor. */
|
|
3384
|
-
rowItemFor(el) {
|
|
3385
|
-
const row = el?.closest?.('[data-iox-row-idx]');
|
|
3386
|
-
if (!row)
|
|
3387
|
-
return undefined;
|
|
3388
|
-
const source = row.getAttribute('data-iox-row-src') ?? '';
|
|
3389
|
-
const index = Number(row.getAttribute('data-iox-row-idx'));
|
|
3390
|
-
const items = this.contextData?.[source];
|
|
3391
|
-
return Array.isArray(items) && Number.isInteger(index) ? items[index] : undefined;
|
|
3392
|
-
}
|
|
3393
|
-
/** Resolved data for the canvas, kept current by the repeater as it fetches. */
|
|
3394
|
-
setContextData(data) {
|
|
3395
|
-
// MERGE, not replace — a page can hold several repeaters, each publishing its own source.
|
|
3396
|
-
this.contextData = { ...this.contextData, ...(data ?? {}) };
|
|
3397
|
-
}
|
|
3398
|
-
executeBehaviourAction(node, action, selfEl) {
|
|
3399
|
-
switch (action.type) {
|
|
3400
|
-
case 'copyToClipboard': {
|
|
3401
|
-
const text = String(action.params?.['text'] ?? '');
|
|
3402
|
-
if (text)
|
|
3403
|
-
navigator.clipboard?.writeText(text).catch(() => { });
|
|
3404
|
-
return;
|
|
3405
|
-
}
|
|
3406
|
-
case 'scrollTo': {
|
|
3407
|
-
const target = this.resolveTarget(node, action, selfEl);
|
|
3408
|
-
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
3409
|
-
return;
|
|
3410
|
-
}
|
|
3411
|
-
case 'navigate':
|
|
3412
|
-
case 'submitForm': {
|
|
3413
|
-
// Simulated: the host (the admin app) listens and shows "would …" as a toast.
|
|
3414
|
-
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
3415
|
-
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
3416
|
-
return;
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
}
|
|
3420
|
-
executeConsentAction(action) {
|
|
3421
|
-
if (action.type === 'openConsentPreferences') {
|
|
3422
|
-
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
3423
|
-
dialog?.open?.();
|
|
3424
|
-
return;
|
|
3425
|
-
}
|
|
3426
|
-
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
3427
|
-
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
3428
|
-
}
|
|
3429
|
-
executeDialogAction(action) {
|
|
3430
|
-
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
3431
|
-
if (!dialog)
|
|
3432
|
-
return;
|
|
3433
|
-
switch (action.type) {
|
|
3434
|
-
case 'openDialog':
|
|
3435
|
-
dialog.open?.();
|
|
3436
|
-
break;
|
|
3437
|
-
case 'closeDialog':
|
|
3438
|
-
dialog.close?.();
|
|
3439
|
-
break;
|
|
3440
|
-
case 'toggleDialog':
|
|
3441
|
-
dialog.toggle?.();
|
|
3442
|
-
break;
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
executeSwiperAction(action) {
|
|
3446
|
-
// Target is the nodeId of the SliderContainer to control.
|
|
3447
|
-
const swiper = this.instanceRegistry.get(action.target);
|
|
3448
|
-
if (!swiper)
|
|
3449
|
-
return;
|
|
3450
|
-
switch (action.type) {
|
|
3451
|
-
case 'swiperNext':
|
|
3452
|
-
swiper.slideNext();
|
|
3453
|
-
break;
|
|
3454
|
-
case 'swiperPrev':
|
|
3455
|
-
swiper.slidePrev();
|
|
3456
|
-
break;
|
|
3457
|
-
case 'swiperGoToSlide':
|
|
3458
|
-
swiper.slideTo(action.params?.['index'] ?? 0);
|
|
3459
|
-
break;
|
|
3460
|
-
case 'swiperAutoplayStart':
|
|
3461
|
-
swiper.autoplay?.start();
|
|
3462
|
-
break;
|
|
3463
|
-
case 'swiperAutoplayStop':
|
|
3464
|
-
swiper.autoplay?.stop();
|
|
3465
|
-
break;
|
|
3466
|
-
case 'swiperAutoplayPause':
|
|
3467
|
-
swiper.autoplay?.pause();
|
|
3468
|
-
break;
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
resolveTarget(ownerNode, action, selfEl) {
|
|
3472
|
-
if (action.target === 'self' || !action.target) {
|
|
3473
|
-
return selfEl ?? this.overlayService.getNodeRef(ownerNode)?.element ?? null;
|
|
3474
|
-
}
|
|
3475
|
-
// Find by node ID
|
|
3476
|
-
for (const [n, ref] of this.overlayService.getAllNodeEntries()) {
|
|
3477
|
-
if (n.id === action.target)
|
|
3478
|
-
return ref.element;
|
|
3479
|
-
}
|
|
3480
|
-
return null;
|
|
3481
|
-
}
|
|
3482
|
-
executeAction(element, action) {
|
|
3483
|
-
const { keyframes } = this.buildAnimation(action);
|
|
3484
|
-
if (!keyframes.length)
|
|
3485
|
-
return;
|
|
3486
|
-
// 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
|
|
3487
|
-
const anim = element.animate(keyframes, {
|
|
3488
|
-
duration: action.duration,
|
|
3489
|
-
delay: action.delay,
|
|
3490
|
-
easing: action.easing,
|
|
3491
|
-
fill: 'both',
|
|
3492
|
-
});
|
|
3493
|
-
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
3494
|
-
anim.finished.then(() => {
|
|
3495
|
-
element.style.removeProperty('pointer-events');
|
|
3496
|
-
element.removeAttribute('data-iox-hidden');
|
|
3497
|
-
this.preStatedElements.delete(element);
|
|
3498
|
-
// Builder: clear inline pre-state so cancelling animations later
|
|
3499
|
-
// doesn't leave the element invisible after a play preview.
|
|
3500
|
-
if (this.isBuilderMode())
|
|
3501
|
-
this.clearInlineAnimationStyles(element);
|
|
3502
|
-
}).catch(() => { });
|
|
3503
|
-
}
|
|
3504
|
-
else if (this.EXIT_TYPES.has(action.type)) {
|
|
3505
|
-
// Once the exit animation finishes the element is invisible —
|
|
3506
|
-
// disable pointer events so it doesn't silently block clicks underneath it.
|
|
3507
|
-
anim.finished.then(() => {
|
|
3508
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3509
|
-
}).catch(() => { });
|
|
3510
|
-
}
|
|
3511
|
-
}
|
|
3512
|
-
/**
|
|
3513
|
-
* Freeze an element at the animation's starting frame immediately, before any
|
|
3514
|
-
* trigger fires. Called for ALL entrance-type actions regardless of trigger so
|
|
3515
|
-
* that a click-triggered fadeIn on a menu overlay also starts it hidden.
|
|
3516
|
-
*
|
|
3517
|
-
* In builder mode only opacity:0 is applied — transforms on canvas elements
|
|
3518
|
-
* inside iox-page-component's scale() transform break Lenis scroll. The transform
|
|
3519
|
-
* jump at animation t=0 is invisible because the element is already opacity:0.
|
|
3520
|
-
* In production the full keyframe is applied + pointer-events:none.
|
|
3521
|
-
*/
|
|
3522
|
-
applyPreState(element, action) {
|
|
3523
|
-
const { keyframes } = this.buildAnimation(action);
|
|
3524
|
-
if (!keyframes.length)
|
|
3525
|
-
return;
|
|
3526
|
-
const first = keyframes[0];
|
|
3527
|
-
if (this.isBuilderMode()) {
|
|
3528
|
-
// No transforms — inline transforms on children inside iox-page-component's
|
|
3529
|
-
// scale() break Lenis scroll. pointer-events:none lets wheel events and
|
|
3530
|
-
// hover fall through to visible elements underneath.
|
|
3531
|
-
element.style.setProperty('opacity', '0');
|
|
3532
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3533
|
-
}
|
|
3534
|
-
else {
|
|
3535
|
-
for (const [prop, val] of Object.entries(first)) {
|
|
3536
|
-
element.style[prop] = String(val);
|
|
3537
|
-
}
|
|
3538
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3539
|
-
}
|
|
3540
|
-
element.setAttribute('data-iox-hidden', '');
|
|
3541
|
-
this.preStatedElements.add(element);
|
|
3542
|
-
}
|
|
3543
|
-
clearInlineAnimationStyles(element) {
|
|
3544
|
-
element.style.removeProperty('opacity');
|
|
3545
|
-
element.style.removeProperty('transform');
|
|
3546
|
-
element.style.removeProperty('visibility');
|
|
3547
|
-
element.style.removeProperty('pointer-events');
|
|
3548
|
-
element.removeAttribute('data-iox-hidden');
|
|
3549
|
-
}
|
|
3550
|
-
reverseAction(element, action) {
|
|
3551
|
-
const { keyframes } = this.buildAnimation(action);
|
|
3552
|
-
if (!keyframes.length)
|
|
3553
|
-
return;
|
|
3554
|
-
// Reverse: play the keyframes backwards
|
|
3555
|
-
element.animate([...keyframes].reverse(), {
|
|
3556
|
-
duration: action.duration,
|
|
3557
|
-
easing: action.easing,
|
|
3558
|
-
fill: 'forwards',
|
|
3559
|
-
});
|
|
3560
|
-
}
|
|
3561
|
-
buildAnimation(action) {
|
|
3562
|
-
const dist = action.params?.['distance'] ?? 20;
|
|
3563
|
-
let keyframes = [];
|
|
3564
|
-
switch (action.type) {
|
|
3565
|
-
case 'fadeIn':
|
|
3566
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3567
|
-
break;
|
|
3568
|
-
case 'fadeOut':
|
|
3569
|
-
keyframes = [{ opacity: 1 }, { opacity: 0 }];
|
|
3570
|
-
break;
|
|
3571
|
-
case 'moveUp':
|
|
3572
|
-
keyframes = [{ transform: `translateY(${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3573
|
-
break;
|
|
3574
|
-
case 'moveDown':
|
|
3575
|
-
keyframes = [{ transform: `translateY(-${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3576
|
-
break;
|
|
3577
|
-
case 'moveLeft':
|
|
3578
|
-
keyframes = [{ transform: `translateX(${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3579
|
-
break;
|
|
3580
|
-
case 'moveRight':
|
|
3581
|
-
keyframes = [{ transform: `translateX(-${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3582
|
-
break;
|
|
3583
|
-
case 'scaleIn':
|
|
3584
|
-
keyframes = [{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }];
|
|
3585
|
-
break;
|
|
3586
|
-
case 'scaleOut':
|
|
3587
|
-
keyframes = [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }];
|
|
3588
|
-
break;
|
|
3589
|
-
case 'rotate':
|
|
3590
|
-
const deg = action.params?.['degrees'] ?? 360;
|
|
3591
|
-
keyframes = [{ transform: `rotate(0deg)` }, { transform: `rotate(${deg}deg)` }];
|
|
3592
|
-
break;
|
|
3593
|
-
case 'show':
|
|
3594
|
-
keyframes = [{ opacity: 0, visibility: 'hidden' }, { opacity: 1, visibility: 'visible' }];
|
|
3595
|
-
break;
|
|
3596
|
-
case 'hide':
|
|
3597
|
-
keyframes = [{ opacity: 1, visibility: 'visible' }, { opacity: 0, visibility: 'hidden' }];
|
|
3598
|
-
break;
|
|
3599
|
-
case 'toggleVisibility':
|
|
3600
|
-
// Toggle is stateful — handled separately via element style
|
|
3601
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3602
|
-
break;
|
|
3603
|
-
}
|
|
3604
|
-
return {
|
|
3605
|
-
keyframes,
|
|
3606
|
-
options: {
|
|
3607
|
-
duration: action.duration,
|
|
3608
|
-
delay: action.delay,
|
|
3609
|
-
easing: action.easing,
|
|
3610
|
-
fill: 'forwards',
|
|
3611
|
-
},
|
|
3612
|
-
};
|
|
3613
|
-
}
|
|
3614
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3615
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService }); }
|
|
3616
|
-
}
|
|
3617
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, decorators: [{
|
|
3618
|
-
type: Injectable
|
|
3619
|
-
}], ctorParameters: () => [{ type: OverlayService }] });
|
|
3620
|
-
|
|
3621
|
-
/**
|
|
3622
|
-
* InteractionPresetRegistryService — in-memory store for org-level interaction presets.
|
|
3623
|
-
*
|
|
3624
|
-
* Loaded once per page by PageUiComponent alongside the page layout.
|
|
3625
|
-
* Scoped to PageUiComponent.providers[] via IoxBuilderModule — one registry per builder instance.
|
|
3626
|
-
*/
|
|
3627
|
-
class InteractionPresetRegistryService {
|
|
3628
|
-
constructor() {
|
|
3629
|
-
this.presets = new Map();
|
|
3630
|
-
}
|
|
3631
|
-
setPresets(presets) {
|
|
3632
|
-
this.presets.clear();
|
|
3633
|
-
for (const p of presets) {
|
|
3634
|
-
this.presets.set(p.id, p);
|
|
3635
|
-
}
|
|
3636
|
-
}
|
|
3637
|
-
getPreset(id) {
|
|
3638
|
-
return this.presets.get(id);
|
|
3639
|
-
}
|
|
3640
|
-
getAll() {
|
|
3641
|
-
return Array.from(this.presets.values());
|
|
3642
|
-
}
|
|
3643
|
-
upsert(preset) {
|
|
3644
|
-
this.presets.set(preset.id, preset);
|
|
3645
|
-
}
|
|
3646
|
-
remove(id) {
|
|
3647
|
-
this.presets.delete(id);
|
|
3648
|
-
}
|
|
3649
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3650
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService }); }
|
|
3651
|
-
}
|
|
3652
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, decorators: [{
|
|
3653
|
-
type: Injectable
|
|
3654
|
-
}] });
|
|
3655
|
-
|
|
3656
|
-
/**
|
|
3657
|
-
* BuilderClipboardService — in-memory typed clipboard for the page builder.
|
|
3658
|
-
*
|
|
3659
|
-
* Stores element copies, style preset references, and interaction preset references.
|
|
3660
|
-
* Lives in memory only — no navigator.clipboard usage.
|
|
3661
|
-
* Scoped to PageUiComponent.providers[] — one clipboard per builder instance.
|
|
3662
|
-
*/
|
|
3663
|
-
class BuilderClipboardService {
|
|
3664
|
-
constructor() {
|
|
3665
|
-
this._clip = {};
|
|
3666
|
-
}
|
|
3667
|
-
copyNode(node) {
|
|
3668
|
-
this._clip = { node: JSON.parse(JSON.stringify(node)) };
|
|
3669
|
-
}
|
|
3670
|
-
copyPreset(presetId) {
|
|
3671
|
-
this._clip = { ...this._clip, presetId };
|
|
3672
|
-
}
|
|
3673
|
-
copyInteractionPreset(interactionPresetId) {
|
|
3674
|
-
this._clip = { ...this._clip, interactionPresetId };
|
|
3675
|
-
}
|
|
3676
|
-
getNode() { return this._clip.node; }
|
|
3677
|
-
getPresetId() { return this._clip.presetId; }
|
|
3678
|
-
getInteractionPresetId() { return this._clip.interactionPresetId; }
|
|
3679
|
-
clear() { this._clip = {}; }
|
|
3680
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3681
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService }); }
|
|
3682
|
-
}
|
|
3683
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, decorators: [{
|
|
3684
|
-
type: Injectable
|
|
3685
|
-
}] });
|
|
3686
|
-
|
|
3687
|
-
/**
|
|
3688
|
-
* CSS container name of the builder canvas frame (and of the global-elements overlay). Breakpoint
|
|
3689
|
-
* rules are emitted as `@container iox-canvas (max-width: …)`: the frame's layout width IS the
|
|
3690
|
-
* simulated viewport and is unaffected by the zoom `transform: scale`, whereas a `@media` query
|
|
3691
|
-
* would read the editor window. Set on the frame in builder.component.scss.
|
|
3692
|
-
*/
|
|
3693
|
-
const BUILDER_CANVAS_CONTAINER = 'iox-canvas';
|
|
3694
|
-
/**
|
|
3695
|
-
* The class that carries the ORG's design tokens on the canvas. Tokens used to be declared on
|
|
3696
|
-
* `:root`, where they collided with the admin's own `--iox-*` chrome tokens (`accent`, `surface-2`,
|
|
3697
|
-
* `scrim`, `focus-ring`… — DS2 seeds all 161 Crisp rows, so the clash became universal and the
|
|
3698
|
-
* admin turned the studio's accent colour). Every surface that renders site content through this
|
|
3699
|
-
* registry — the canvas frame, the global-elements overlay, preview, specimens — wears this class.
|
|
3700
|
-
*/
|
|
3701
|
-
const BUILDER_TOKEN_SCOPE_CLASS = 'iox-token-scope';
|
|
3702
|
-
/**
|
|
3703
|
-
* StyleRegistryService — manages a single <style> tag for the builder canvas.
|
|
3704
|
-
*
|
|
3705
|
-
* Each builder component gets two CSS classes:
|
|
3706
|
-
* .iox-node-{id} — applied to the inner element; handles layout/visual props
|
|
3707
|
-
* (display, flex, background, border, padding, etc.)
|
|
3708
|
-
* .iox-outer-{id} — applied to the host element by RenderDirective; handles
|
|
3709
|
-
* properties that participate in the PARENT flow (margin,
|
|
3710
|
-
* align-self, flex-grow/shrink/basis, order).
|
|
3711
|
-
*
|
|
3712
|
-
* This split is necessary because margin on an inner wrapper only adds internal
|
|
3713
|
-
* space — it does not push sibling elements. Applying margin to the host element
|
|
3714
|
-
* (which IS the flex/block child in the parent container) makes it work correctly.
|
|
3715
|
-
*
|
|
3716
|
-
* State styles (hover/active/focus) are stored under key `${nodeId}:${state}`
|
|
3717
|
-
* and compiled to `.iox-node-{id}:hover { … }` selectors.
|
|
3718
|
-
*
|
|
3719
|
-
* Design token variables are stored under the reserved key `__tokens__` and
|
|
3720
|
-
* compiled to a `.iox-token-scope { … }` block prepended to all other rules.
|
|
3721
|
-
*
|
|
3722
|
-
* Scoped to PageUiComponent.providers[] — one stylesheet per builder instance.
|
|
3723
|
-
*/
|
|
3724
|
-
class StyleRegistryService {
|
|
3725
|
-
constructor() {
|
|
3726
|
-
this.changes$ = new Subject();
|
|
3727
|
-
this.rules = new Map();
|
|
3728
|
-
/** Breakpoint rules, kept apart so they flush AFTER every base rule — equal specificity, so
|
|
3729
|
-
* source order decides (plan trap 1). Keyed per breakpoint like `rules` (`id`, `id:state`). */
|
|
3730
|
-
this.responsiveRules = new Map();
|
|
3731
|
-
this.responsiveInputs = new Map();
|
|
3732
|
-
/** Which breakpoint rule keys each node owns — so re-emitting one node's rules is O(its rules),
|
|
3733
|
-
* not a scan of every breakpoint rule on the page (this runs on every node render). */
|
|
3734
|
-
this.responsiveKeys = new Map();
|
|
3735
|
-
this.stateScopes = new Map();
|
|
3736
|
-
this.styleEl = null;
|
|
3737
|
-
this._flushPending = false;
|
|
3738
|
-
}
|
|
3739
|
-
/**
|
|
3740
|
-
* Properties that must live on the OUTER wrapper (.iox-outer-{id}) because they
|
|
3741
|
-
* affect how the element participates in its PARENT layout (flex/block flow).
|
|
3742
|
-
* Everything else goes to the INNER element (.iox-node-{id}).
|
|
3743
|
-
*
|
|
3744
|
-
* Exception — out-of-flow positioning (position:fixed/absolute):
|
|
3745
|
-
* When position is fixed or absolute, width is redirected to the inner element
|
|
3746
|
-
* (the positioned one) and a default width:100% is added if none is set, so the
|
|
3747
|
-
* element stays visible. top/right/bottom/left/zIndex are always inner because
|
|
3748
|
-
* they are co-ordinates of the positioned element, not the flow wrapper.
|
|
3749
|
-
* position itself goes to inner with `!important` to beat component-scoped CSS.
|
|
3750
|
-
*/
|
|
3751
|
-
static { this.OUTER_PROPS = new Set([
|
|
3752
|
-
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
|
|
3753
|
-
'alignSelf', 'flexGrow', 'flexShrink', 'flexBasis', 'order',
|
|
3754
|
-
// Grid-child placement — like the flex-child props above, these position the node
|
|
3755
|
-
// within the PARENT's grid, so they must land on the outer host, not the inner div.
|
|
3756
|
-
'gridColumn', 'gridRow', 'justifySelf',
|
|
3757
|
-
'width',
|
|
3758
|
-
]); }
|
|
3759
|
-
/**
|
|
3760
|
-
* Properties that need !important on the inner element to beat Angular's scoped-CSS
|
|
3761
|
-
* specificity boost. CANVAS-ONLY (H2 audit, 2026-10-11): the shared compiler never forces
|
|
3762
|
-
* importance and the SSR engine never emits it — this is the builder's own fight with its
|
|
3763
|
-
* scoped component styles. The A4 dialog centring clash this once caused is resolved
|
|
3764
|
-
* structurally (the grid stage PLACES a static surface), so the set stays at exactly one
|
|
3765
|
-
* documented member; adding to it needs the same scrutiny.
|
|
3766
|
-
*/
|
|
3767
|
-
static { this.IMPORTANT_PROPS = new Set(['position']); }
|
|
3768
|
-
init() {
|
|
3769
|
-
this.styleEl = document.createElement('style');
|
|
3770
|
-
this.styleEl.id = 'iox-runtime-styles';
|
|
3771
|
-
document.head.appendChild(this.styleEl);
|
|
3772
|
-
}
|
|
3773
|
-
/**
|
|
3774
|
-
* Write or update the base styles for a node, or a pseudo-class state override.
|
|
3775
|
-
*
|
|
3776
|
-
* @param nodeId The node's CSS id (used in `.iox-node-{nodeId}`)
|
|
3777
|
-
* @param styles Flat CSS property map (camelCase keys)
|
|
3778
|
-
* @param state Optional pseudo-class state ('hover' | 'active' | 'focus' | …).
|
|
3779
|
-
* When provided, compiles to `.iox-node-{id}:{state} { … }`.
|
|
3780
|
-
* State rules are NOT partitioned into inner/outer.
|
|
3781
|
-
* @param relativeToId For RELATIONAL states (e.g. 'parent-hover') this is the ancestor
|
|
3782
|
-
* css id; compiles to `.iox-node-{relativeToId}:{base} .iox-node-{nodeId} { … }`.
|
|
3783
|
-
* For TARGETED states (e.g. 'trigger-hover') this is the SCOPE css id
|
|
3784
|
-
* (nearest common ancestor of the styled node + trigger).
|
|
3785
|
-
* @param triggerId For TARGETED states only: the css id of the trigger node whose
|
|
3786
|
-
* pseudo-class drives the state. Compiles a `:has()`-scoped selector.
|
|
3787
|
-
*/
|
|
3788
|
-
upsert(nodeId, styles, state, relativeToId, triggerId) {
|
|
3789
|
-
if (!nodeId)
|
|
3790
|
-
return;
|
|
3791
|
-
if (state) {
|
|
3792
|
-
const key = `${nodeId}:${state}`;
|
|
3793
|
-
// Remember the scope a relational / targeted rule was given, so the same state at a
|
|
3794
|
-
// breakpoint is scoped identically without re-resolving it (see upsertResponsive).
|
|
3795
|
-
if (relativeToId || triggerId)
|
|
3796
|
-
this.stateScopes.set(key, { relativeToId, triggerId });
|
|
3797
|
-
const selector = this.stateSelector(nodeId, state, relativeToId, triggerId);
|
|
3798
|
-
const css = selector ? this.compile(selector, styles) : '';
|
|
3799
|
-
if (css)
|
|
3800
|
-
this.rules.set(key, css);
|
|
3801
|
-
else
|
|
3802
|
-
this.rules.delete(key);
|
|
3803
|
-
if (this.responsiveInputs.has(nodeId))
|
|
3804
|
-
this.emitResponsive(nodeId);
|
|
3805
|
-
this.flush();
|
|
3806
|
-
return;
|
|
3740
|
+
// is the clone — so each row publishes its own item, exactly as on the live site.
|
|
3741
|
+
if (isContextAction(action)) {
|
|
3742
|
+
this.applyContextAction(action, selfEl ?? null, 'forward');
|
|
3743
|
+
continue;
|
|
3744
|
+
}
|
|
3745
|
+
// selfEl lets 'self'-targeted actions resolve for preview CLONES, which aren't
|
|
3746
|
+
// overlay-registered (getNodeRef would return undefined) — see attachTo. (issue #2)
|
|
3747
|
+
const target = this.resolveTarget(node, action, selfEl);
|
|
3748
|
+
if (!target)
|
|
3749
|
+
continue;
|
|
3750
|
+
this.executeAction(target, action);
|
|
3807
3751
|
}
|
|
3808
|
-
const { main, marker } = this.nodeRuleCss(nodeId, styles, this.isOutOfFlow(styles), DeclarationContext.Base);
|
|
3809
|
-
if (marker)
|
|
3810
|
-
this.rules.set(`${nodeId}::marker`, marker);
|
|
3811
|
-
else
|
|
3812
|
-
this.rules.delete(`${nodeId}::marker`);
|
|
3813
|
-
if (main)
|
|
3814
|
-
this.rules.set(nodeId, main);
|
|
3815
|
-
else
|
|
3816
|
-
this.rules.delete(nodeId);
|
|
3817
|
-
this.flush();
|
|
3818
|
-
}
|
|
3819
|
-
/**
|
|
3820
|
-
* Write or clear the styles for one PART of a compound component (board 7 — "the failure
|
|
3821
|
-
* design"), optionally further scoped to a validation state ('wrong'/'accepted'). Compiles
|
|
3822
|
-
* through the exact same `compile()` every other rule uses — `buildPartSelector` (shared,
|
|
3823
|
-
* `@vectoriox/iox-ui`) just builds a different selector string. Unlike interaction/relational/
|
|
3824
|
-
* targeted states, a validation state compounds a CLASS onto the node (`.iox-invalid`), not a
|
|
3825
|
-
* `:pseudo-class`, since `IoxFieldComponent` sets that class itself from `forceInvalid`/
|
|
3826
|
-
* `showValid` — there is no real "wrong" pseudo-class to hook.
|
|
3827
|
-
*/
|
|
3828
|
-
upsertPart(nodeId, partClassName, styles, state) {
|
|
3829
|
-
if (!nodeId || !partClassName)
|
|
3830
|
-
return;
|
|
3831
|
-
const key = `${nodeId}:part:${partClassName}${state ? ':' + state : ''}`;
|
|
3832
|
-
const validationClass = state ? VALIDATION_STATE_CLASS[state] : undefined;
|
|
3833
|
-
const selector = buildPartSelector(nodeId, partClassName, validationClass);
|
|
3834
|
-
const css = Object.keys(styles).length ? this.compile(selector, styles) : '';
|
|
3835
|
-
if (css)
|
|
3836
|
-
this.rules.set(key, css);
|
|
3837
|
-
else
|
|
3838
|
-
this.rules.delete(key);
|
|
3839
|
-
this.flush();
|
|
3840
3752
|
}
|
|
3841
3753
|
/**
|
|
3842
|
-
*
|
|
3843
|
-
*
|
|
3844
|
-
*
|
|
3845
|
-
* (a CSS initial value undoes the wider rule there, so it must not be dropped).
|
|
3846
|
-
*
|
|
3847
|
-
* @param styleProps The node's full BASE style map (raw — virtual traits not yet composed).
|
|
3754
|
+
* Execute a context action against the shared store. The forward/reverse semantics come from
|
|
3755
|
+
* `contextActionEffect` in @vectoriox/iox-ui — the same function the SSR engine calls, so the
|
|
3756
|
+
* builder canvas and the live site behave identically.
|
|
3848
3757
|
*/
|
|
3849
|
-
|
|
3850
|
-
|
|
3758
|
+
applyContextAction(action, el, pass) {
|
|
3759
|
+
const effect = contextActionEffect(action, pass);
|
|
3760
|
+
if (!effect)
|
|
3851
3761
|
return;
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
if (!hasOverrides && !this.responsiveInputs.has(nodeId))
|
|
3762
|
+
if (effect.op === 'clear') {
|
|
3763
|
+
this.contextStore.clear(effect.slot);
|
|
3855
3764
|
return;
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
this.
|
|
3860
|
-
|
|
3861
|
-
this.
|
|
3765
|
+
}
|
|
3766
|
+
const value = resolveContextValue(effect.value, {
|
|
3767
|
+
item: this.rowItemFor(el),
|
|
3768
|
+
resolvedData: this.contextData,
|
|
3769
|
+
});
|
|
3770
|
+
this.contextStore.set(effect.slot, value);
|
|
3862
3771
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
const
|
|
3866
|
-
if (!
|
|
3867
|
-
return;
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3772
|
+
/** The repeater item this element belongs to — the nearest marked row ancestor. */
|
|
3773
|
+
rowItemFor(el) {
|
|
3774
|
+
const row = el?.closest?.('[data-iox-row-idx]');
|
|
3775
|
+
if (!row)
|
|
3776
|
+
return undefined;
|
|
3777
|
+
const source = row.getAttribute('data-iox-row-src') ?? '';
|
|
3778
|
+
const index = Number(row.getAttribute('data-iox-row-idx'));
|
|
3779
|
+
const items = this.contextData?.[source];
|
|
3780
|
+
return Array.isArray(items) && Number.isInteger(index) ? items[index] : undefined;
|
|
3781
|
+
}
|
|
3782
|
+
/** Resolved data for the canvas, kept current by the repeater as it fetches. */
|
|
3783
|
+
setContextData(data) {
|
|
3784
|
+
// MERGE, not replace — a page can hold several repeaters, each publishing its own source.
|
|
3785
|
+
this.contextData = { ...this.contextData, ...(data ?? {}) };
|
|
3786
|
+
}
|
|
3787
|
+
executeBehaviourAction(node, action, selfEl) {
|
|
3788
|
+
switch (action.type) {
|
|
3789
|
+
case 'copyToClipboard': {
|
|
3790
|
+
const text = String(action.params?.['text'] ?? '');
|
|
3791
|
+
if (text)
|
|
3792
|
+
navigator.clipboard?.writeText(text).catch(() => { });
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
case 'scrollTo': {
|
|
3796
|
+
const target = this.resolveTarget(node, action, selfEl);
|
|
3797
|
+
target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
case 'navigate':
|
|
3801
|
+
case 'submitForm': {
|
|
3802
|
+
// Simulated: the host (the admin app) listens and shows "would …" as a toast.
|
|
3803
|
+
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
3804
|
+
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
3805
|
+
return;
|
|
3885
3806
|
}
|
|
3886
3807
|
}
|
|
3887
3808
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
owned.push([bp, key]);
|
|
3809
|
+
executeConsentAction(action) {
|
|
3810
|
+
if (action.type === 'openConsentPreferences') {
|
|
3811
|
+
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
3812
|
+
dialog?.open?.();
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
const detail = { type: action.type, params: action.params ?? {}, target: action.target };
|
|
3816
|
+
window.dispatchEvent(new CustomEvent('iox-action-simulated', { detail }));
|
|
3897
3817
|
}
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3818
|
+
executeDialogAction(action) {
|
|
3819
|
+
const dialog = action.target && action.target !== 'self' ? this.instanceRegistry.get(action.target) : null;
|
|
3820
|
+
if (!dialog)
|
|
3821
|
+
return;
|
|
3822
|
+
switch (action.type) {
|
|
3823
|
+
case 'openDialog':
|
|
3824
|
+
dialog.open?.();
|
|
3825
|
+
break;
|
|
3826
|
+
case 'closeDialog':
|
|
3827
|
+
dialog.close?.();
|
|
3828
|
+
break;
|
|
3829
|
+
case 'toggleDialog':
|
|
3830
|
+
dialog.toggle?.();
|
|
3831
|
+
break;
|
|
3832
|
+
}
|
|
3902
3833
|
}
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3834
|
+
executeSwiperAction(action) {
|
|
3835
|
+
// Target is the nodeId of the SliderContainer to control.
|
|
3836
|
+
const swiper = this.instanceRegistry.get(action.target);
|
|
3837
|
+
if (!swiper)
|
|
3838
|
+
return;
|
|
3839
|
+
switch (action.type) {
|
|
3840
|
+
case 'swiperNext':
|
|
3841
|
+
swiper.slideNext();
|
|
3842
|
+
break;
|
|
3843
|
+
case 'swiperPrev':
|
|
3844
|
+
swiper.slidePrev();
|
|
3845
|
+
break;
|
|
3846
|
+
case 'swiperGoToSlide':
|
|
3847
|
+
swiper.slideTo(action.params?.['index'] ?? 0);
|
|
3848
|
+
break;
|
|
3849
|
+
case 'swiperAutoplayStart':
|
|
3850
|
+
swiper.autoplay?.start();
|
|
3851
|
+
break;
|
|
3852
|
+
case 'swiperAutoplayStop':
|
|
3853
|
+
swiper.autoplay?.stop();
|
|
3854
|
+
break;
|
|
3855
|
+
case 'swiperAutoplayPause':
|
|
3856
|
+
swiper.autoplay?.pause();
|
|
3857
|
+
break;
|
|
3858
|
+
}
|
|
3906
3859
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
*/
|
|
3911
|
-
stateSelector(nodeId, state, relativeToId, triggerId) {
|
|
3912
|
-
// Targeted states (trigger-hover): the styled node restyles when an ARBITRARY chosen
|
|
3913
|
-
// element is hovered. The selector is `:has()`-scoped to `relativeToId` (the nearest
|
|
3914
|
-
// common ancestor of styled + trigger) so it stays repetition-safe with one shared rule.
|
|
3915
|
-
if (TARGETED_STATES.includes(state)) {
|
|
3916
|
-
const base = TARGETED_STATE_BASE[state];
|
|
3917
|
-
return relativeToId && triggerId && base
|
|
3918
|
-
? buildTargetedStateSelector(relativeToId, triggerId, nodeId, base) : null;
|
|
3860
|
+
resolveTarget(ownerNode, action, selfEl) {
|
|
3861
|
+
if (action.target === 'self' || !action.target) {
|
|
3862
|
+
return selfEl ?? this.overlayService.getNodeRef(ownerNode)?.element ?? null;
|
|
3919
3863
|
}
|
|
3920
|
-
//
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
return relativeToId && base ? `iox-node-${relativeToId}:${base} .iox-node-${nodeId}` : null;
|
|
3864
|
+
// Find by node ID
|
|
3865
|
+
for (const [n, ref] of this.overlayService.getAllNodeEntries()) {
|
|
3866
|
+
if (n.id === action.target)
|
|
3867
|
+
return ref.element;
|
|
3925
3868
|
}
|
|
3926
|
-
|
|
3927
|
-
// doesn't exist. Compound a CLASS instead, matching the host class IoxFieldComponent
|
|
3928
|
-
// already sets from forceInvalid/showValid. Reuses buildPartSelector with no part, since
|
|
3929
|
-
// "whole node, when wrong" is exactly that function's no-part case.
|
|
3930
|
-
const validationClass = VALIDATION_STATE_CLASS[state];
|
|
3931
|
-
if (validationClass)
|
|
3932
|
-
return buildPartSelector(nodeId, undefined, validationClass);
|
|
3933
|
-
// Structural pseudo-classes (first-child, last-child, nth-child) target the
|
|
3934
|
-
// outer wrapper element so they participate correctly in grid/flex layout.
|
|
3935
|
-
const prefix = STRUCTURAL_STATES.includes(state) ? 'iox-outer' : 'iox-node';
|
|
3936
|
-
// `focus` compiles to :focus-visible (DS2) — shared with the engine via statePseudoClass.
|
|
3937
|
-
return `${prefix}-${nodeId}:${statePseudoClass(state)}`;
|
|
3869
|
+
return null;
|
|
3938
3870
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
inner[k] = v;
|
|
3961
|
-
}
|
|
3871
|
+
executeAction(element, action) {
|
|
3872
|
+
const { keyframes } = this.buildAnimation(action);
|
|
3873
|
+
if (!keyframes.length)
|
|
3874
|
+
return;
|
|
3875
|
+
// 'both' = backwards (holds first keyframe during delay) + forwards (holds last keyframe after end).
|
|
3876
|
+
const anim = element.animate(keyframes, {
|
|
3877
|
+
duration: action.duration,
|
|
3878
|
+
delay: action.delay,
|
|
3879
|
+
easing: action.easing,
|
|
3880
|
+
fill: 'both',
|
|
3881
|
+
});
|
|
3882
|
+
if (this.ENTRANCE_TYPES.has(action.type)) {
|
|
3883
|
+
anim.finished.then(() => {
|
|
3884
|
+
element.style.removeProperty('pointer-events');
|
|
3885
|
+
element.removeAttribute('data-iox-hidden');
|
|
3886
|
+
this.preStatedElements.delete(element);
|
|
3887
|
+
// Builder: clear inline pre-state so cancelling animations later
|
|
3888
|
+
// doesn't leave the element invisible after a play preview.
|
|
3889
|
+
if (this.isBuilderMode())
|
|
3890
|
+
this.clearInlineAnimationStyles(element);
|
|
3891
|
+
}).catch(() => { });
|
|
3962
3892
|
}
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3893
|
+
else if (this.EXIT_TYPES.has(action.type)) {
|
|
3894
|
+
// Once the exit animation finishes the element is invisible —
|
|
3895
|
+
// disable pointer events so it doesn't silently block clicks underneath it.
|
|
3896
|
+
anim.finished.then(() => {
|
|
3897
|
+
element.style.setProperty('pointer-events', 'none');
|
|
3898
|
+
}).catch(() => { });
|
|
3967
3899
|
}
|
|
3968
|
-
const innerCss = this.compile(`iox-node-${nodeId}`, inner, context);
|
|
3969
|
-
const outerCss = this.compile(`iox-outer-${nodeId}`, outer, context);
|
|
3970
|
-
return { main: [innerCss, outerCss].filter(Boolean).join('\n'), marker };
|
|
3971
3900
|
}
|
|
3972
3901
|
/**
|
|
3973
|
-
*
|
|
3974
|
-
*
|
|
3975
|
-
*
|
|
3976
|
-
*
|
|
3977
|
-
*
|
|
3902
|
+
* Freeze an element at the animation's starting frame immediately, before any
|
|
3903
|
+
* trigger fires. Called for ALL entrance-type actions regardless of trigger so
|
|
3904
|
+
* that a click-triggered fadeIn on a menu overlay also starts it hidden.
|
|
3905
|
+
*
|
|
3906
|
+
* In builder mode only opacity:0 is applied — transforms on canvas elements
|
|
3907
|
+
* inside iox-page-component's scale() transform break Lenis scroll. The transform
|
|
3908
|
+
* jump at animation t=0 is invisible because the element is already opacity:0.
|
|
3909
|
+
* In production the full keyframe is applied + pointer-events:none.
|
|
3978
3910
|
*/
|
|
3979
|
-
|
|
3980
|
-
const
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
// LAST rule — one ordering convention for every @media block (responsive A3 lesson).
|
|
3991
|
-
const darkEntries = Object.entries(darkTokens ?? {})
|
|
3992
|
-
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
3993
|
-
.map(([k, v]) => ` --${k}: ${v};`);
|
|
3994
|
-
if (darkEntries.length) {
|
|
3995
|
-
this.rules.set('__tokens_dark__', `@media (prefers-color-scheme: dark) {\n .${BUILDER_TOKEN_SCOPE_CLASS} {\n${darkEntries.join('\n')}\n }\n}`);
|
|
3911
|
+
applyPreState(element, action) {
|
|
3912
|
+
const { keyframes } = this.buildAnimation(action);
|
|
3913
|
+
if (!keyframes.length)
|
|
3914
|
+
return;
|
|
3915
|
+
const first = keyframes[0];
|
|
3916
|
+
if (this.isBuilderMode()) {
|
|
3917
|
+
// No transforms — inline transforms on children inside iox-page-component's
|
|
3918
|
+
// scale() break Lenis scroll. pointer-events:none lets wheel events and
|
|
3919
|
+
// hover fall through to visible elements underneath.
|
|
3920
|
+
element.style.setProperty('opacity', '0');
|
|
3921
|
+
element.style.setProperty('pointer-events', 'none');
|
|
3996
3922
|
}
|
|
3997
3923
|
else {
|
|
3998
|
-
|
|
3924
|
+
for (const [prop, val] of Object.entries(first)) {
|
|
3925
|
+
element.style[prop] = String(val);
|
|
3926
|
+
}
|
|
3927
|
+
element.style.setProperty('pointer-events', 'none');
|
|
3999
3928
|
}
|
|
4000
|
-
|
|
3929
|
+
element.setAttribute('data-iox-hidden', '');
|
|
3930
|
+
this.preStatedElements.add(element);
|
|
4001
3931
|
}
|
|
4002
|
-
|
|
4003
|
-
|
|
3932
|
+
clearInlineAnimationStyles(element) {
|
|
3933
|
+
element.style.removeProperty('opacity');
|
|
3934
|
+
element.style.removeProperty('transform');
|
|
3935
|
+
element.style.removeProperty('visibility');
|
|
3936
|
+
element.style.removeProperty('pointer-events');
|
|
3937
|
+
element.removeAttribute('data-iox-hidden');
|
|
3938
|
+
}
|
|
3939
|
+
reverseAction(element, action) {
|
|
3940
|
+
const { keyframes } = this.buildAnimation(action);
|
|
3941
|
+
if (!keyframes.length)
|
|
4004
3942
|
return;
|
|
4005
|
-
//
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
3943
|
+
// Reverse: play the keyframes backwards
|
|
3944
|
+
element.animate([...keyframes].reverse(), {
|
|
3945
|
+
duration: action.duration,
|
|
3946
|
+
easing: action.easing,
|
|
3947
|
+
fill: 'forwards',
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
buildAnimation(action) {
|
|
3951
|
+
const dist = action.params?.['distance'] ?? 20;
|
|
3952
|
+
let keyframes = [];
|
|
3953
|
+
switch (action.type) {
|
|
3954
|
+
case 'fadeIn':
|
|
3955
|
+
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3956
|
+
break;
|
|
3957
|
+
case 'fadeOut':
|
|
3958
|
+
keyframes = [{ opacity: 1 }, { opacity: 0 }];
|
|
3959
|
+
break;
|
|
3960
|
+
case 'moveUp':
|
|
3961
|
+
keyframes = [{ transform: `translateY(${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3962
|
+
break;
|
|
3963
|
+
case 'moveDown':
|
|
3964
|
+
keyframes = [{ transform: `translateY(-${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3965
|
+
break;
|
|
3966
|
+
case 'moveLeft':
|
|
3967
|
+
keyframes = [{ transform: `translateX(${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3968
|
+
break;
|
|
3969
|
+
case 'moveRight':
|
|
3970
|
+
keyframes = [{ transform: `translateX(-${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3971
|
+
break;
|
|
3972
|
+
case 'scaleIn':
|
|
3973
|
+
keyframes = [{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }];
|
|
3974
|
+
break;
|
|
3975
|
+
case 'scaleOut':
|
|
3976
|
+
keyframes = [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }];
|
|
3977
|
+
break;
|
|
3978
|
+
case 'rotate':
|
|
3979
|
+
const deg = action.params?.['degrees'] ?? 360;
|
|
3980
|
+
keyframes = [{ transform: `rotate(0deg)` }, { transform: `rotate(${deg}deg)` }];
|
|
3981
|
+
break;
|
|
3982
|
+
case 'show':
|
|
3983
|
+
keyframes = [{ opacity: 0, visibility: 'hidden' }, { opacity: 1, visibility: 'visible' }];
|
|
3984
|
+
break;
|
|
3985
|
+
case 'hide':
|
|
3986
|
+
keyframes = [{ opacity: 1, visibility: 'visible' }, { opacity: 0, visibility: 'hidden' }];
|
|
3987
|
+
break;
|
|
3988
|
+
case 'toggleVisibility':
|
|
3989
|
+
// Toggle is stateful — handled separately via element style
|
|
3990
|
+
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3991
|
+
break;
|
|
4015
3992
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
3993
|
+
return {
|
|
3994
|
+
keyframes,
|
|
3995
|
+
options: {
|
|
3996
|
+
duration: action.duration,
|
|
3997
|
+
delay: action.delay,
|
|
3998
|
+
easing: action.easing,
|
|
3999
|
+
fill: 'forwards',
|
|
4000
|
+
},
|
|
4001
|
+
};
|
|
4019
4002
|
}
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4003
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4004
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService }); }
|
|
4005
|
+
}
|
|
4006
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, decorators: [{
|
|
4007
|
+
type: Injectable
|
|
4008
|
+
}], ctorParameters: () => [{ type: OverlayService }] });
|
|
4009
|
+
|
|
4010
|
+
/**
|
|
4011
|
+
* InteractionPresetRegistryService — in-memory store for org-level interaction presets.
|
|
4012
|
+
*
|
|
4013
|
+
* Loaded once per page by PageUiComponent alongside the page layout.
|
|
4014
|
+
* Scoped to PageUiComponent.providers[] via IoxBuilderModule — one registry per builder instance.
|
|
4015
|
+
*/
|
|
4016
|
+
class InteractionPresetRegistryService {
|
|
4017
|
+
constructor() {
|
|
4018
|
+
this.presets = new Map();
|
|
4019
|
+
}
|
|
4020
|
+
setPresets(presets) {
|
|
4021
|
+
this.presets.clear();
|
|
4022
|
+
for (const p of presets) {
|
|
4023
|
+
this.presets.set(p.id, p);
|
|
4029
4024
|
}
|
|
4030
4025
|
}
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
// the shared style-compiler so the SSR engine produces byte-identical output.
|
|
4034
|
-
// The inner/outer partition + selector emission stay here (builder-only concern).
|
|
4035
|
-
const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS, context);
|
|
4036
|
-
if (!decls.length)
|
|
4037
|
-
return '';
|
|
4038
|
-
const body = decls.map(d => ` ${d};`).join('\n');
|
|
4039
|
-
return `.${className} {\n${body}\n}`;
|
|
4026
|
+
getPreset(id) {
|
|
4027
|
+
return this.presets.get(id);
|
|
4040
4028
|
}
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
return;
|
|
4044
|
-
this._flushPending = true;
|
|
4045
|
-
requestAnimationFrame(() => {
|
|
4046
|
-
this._flushPending = false;
|
|
4047
|
-
if (!this.styleEl)
|
|
4048
|
-
return;
|
|
4049
|
-
// __tokens__ always first so variables are available to all subsequent rules
|
|
4050
|
-
const tokenBlock = this.rules.get('__tokens__');
|
|
4051
|
-
const rest = Array.from(this.rules.entries())
|
|
4052
|
-
.filter(([k]) => k !== '__tokens__')
|
|
4053
|
-
.map(([, v]) => v);
|
|
4054
|
-
// Breakpoint rules LAST, widest → narrowest, each as a canvas container query.
|
|
4055
|
-
const breakpoints = BREAKPOINTS
|
|
4056
|
-
.map(({ id }) => this.responsiveRules.get(id))
|
|
4057
|
-
.map((bucket, i) => bucket?.size
|
|
4058
|
-
? wrapInBreakpointQuery(BREAKPOINTS[i].id, Array.from(bucket.values()).join('\n'), BreakpointQueryKind.Container, BUILDER_CANVAS_CONTAINER)
|
|
4059
|
-
: '');
|
|
4060
|
-
this.styleEl.textContent = [tokenBlock, ...rest, ...breakpoints].filter(Boolean).join('\n');
|
|
4061
|
-
this.changes$.next();
|
|
4062
|
-
});
|
|
4029
|
+
getAll() {
|
|
4030
|
+
return Array.from(this.presets.values());
|
|
4063
4031
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4032
|
+
upsert(preset) {
|
|
4033
|
+
this.presets.set(preset.id, preset);
|
|
4034
|
+
}
|
|
4035
|
+
remove(id) {
|
|
4036
|
+
this.presets.delete(id);
|
|
4037
|
+
}
|
|
4038
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4039
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService }); }
|
|
4066
4040
|
}
|
|
4067
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type:
|
|
4041
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, decorators: [{
|
|
4042
|
+
type: Injectable
|
|
4043
|
+
}] });
|
|
4044
|
+
|
|
4045
|
+
/**
|
|
4046
|
+
* BuilderClipboardService — in-memory typed clipboard for the page builder.
|
|
4047
|
+
*
|
|
4048
|
+
* Stores element copies, style preset references, and interaction preset references.
|
|
4049
|
+
* Lives in memory only — no navigator.clipboard usage.
|
|
4050
|
+
* Scoped to PageUiComponent.providers[] — one clipboard per builder instance.
|
|
4051
|
+
*/
|
|
4052
|
+
class BuilderClipboardService {
|
|
4053
|
+
constructor() {
|
|
4054
|
+
this._clip = {};
|
|
4055
|
+
}
|
|
4056
|
+
copyNode(node) {
|
|
4057
|
+
this._clip = { node: JSON.parse(JSON.stringify(node)) };
|
|
4058
|
+
}
|
|
4059
|
+
copyPreset(presetId) {
|
|
4060
|
+
this._clip = { ...this._clip, presetId };
|
|
4061
|
+
}
|
|
4062
|
+
copyInteractionPreset(interactionPresetId) {
|
|
4063
|
+
this._clip = { ...this._clip, interactionPresetId };
|
|
4064
|
+
}
|
|
4065
|
+
getNode() { return this._clip.node; }
|
|
4066
|
+
getPresetId() { return this._clip.presetId; }
|
|
4067
|
+
getInteractionPresetId() { return this._clip.interactionPresetId; }
|
|
4068
|
+
clear() { this._clip = {}; }
|
|
4069
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4070
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService }); }
|
|
4071
|
+
}
|
|
4072
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, decorators: [{
|
|
4068
4073
|
type: Injectable
|
|
4069
4074
|
}] });
|
|
4070
4075
|
|
|
@@ -4496,6 +4501,11 @@ const DIRECTIVE_HOST_TYPES = new Set([
|
|
|
4496
4501
|
'Image', 'Icon', 'Divider', 'Spacer', 'Button', 'Link', 'Heading', 'Text', 'ButtonBlock',
|
|
4497
4502
|
// Phase 2 (9c): layout components converted to directive-hosts (host IS the real tag).
|
|
4498
4503
|
'Section', 'Container', 'List', 'ListItem',
|
|
4504
|
+
// Forms: Field/SubmitButton are the engine's own directive-host twins, and Form/FieldGroup/
|
|
4505
|
+
// FormSuccess/FormError render through the Container twin (a directive-host). The forced
|
|
4506
|
+
// inline `display:block` out-ranked the node rule (a SubmitButton wearing a button style) —
|
|
4507
|
+
// the engine never had it (DS2.1).
|
|
4508
|
+
'Form', 'Field', 'FieldGroup', 'SubmitButton', 'FormSuccess', 'FormError',
|
|
4499
4509
|
]);
|
|
4500
4510
|
const CONTAINER_TAGS = ['div', 'section', 'article', 'header', 'footer', 'main', 'nav', 'ul', 'ol', 'li'];
|
|
4501
4511
|
/**
|
|
@@ -4700,8 +4710,10 @@ class RenderDirective {
|
|
|
4700
4710
|
}
|
|
4701
4711
|
// Use styleId when present (clone nodes share the template node's CSS rule).
|
|
4702
4712
|
const cssId = node.styleId ?? node.id;
|
|
4703
|
-
// Register CSS rules in the central stylesheet (inner + outer split).
|
|
4713
|
+
// Register CSS rules in the central stylesheet (inner + outer split). The type first: the
|
|
4714
|
+
// registry strips the props a type's renderer owns (a Dialog's positioning) on every rule.
|
|
4704
4715
|
if (cssId) {
|
|
4716
|
+
this.styleRegistry.setNodeType(cssId, node.type);
|
|
4705
4717
|
const composedBase = composeVirtualTraits(initialStyle);
|
|
4706
4718
|
this.styleRegistry.upsert(cssId, composedBase);
|
|
4707
4719
|
// Restore per-state overrides saved from a previous session.
|
|
@@ -4801,6 +4813,10 @@ class RenderDirective {
|
|
|
4801
4813
|
// For layout components the host element is <app-section>; for leaf components
|
|
4802
4814
|
// it is e.g. <app-card>. Either way this is the correct inspectable element.
|
|
4803
4815
|
const nativeEl = componentRef.location?.nativeElement ?? null;
|
|
4816
|
+
// The element-style marker (design-system §3b-bis) — on the HOST, as both engine paths do:
|
|
4817
|
+
// component CSS that carries the system look is scoped to it, so a node saved before DS2
|
|
4818
|
+
// keeps its exact pre-DS2 look. Clones carry it too (they render the same look).
|
|
4819
|
+
syncElementStyleMarker(nativeEl, node.elementStyle);
|
|
4804
4820
|
// Custom element host defaults to display:inline. Set it to block so it participates
|
|
4805
4821
|
// properly in layout and CDK can capture its bounding rect. SKIP for directive-hosts: the
|
|
4806
4822
|
// host is a real element with its own display, and an inline style here out-ranks both
|
|
@@ -7826,7 +7842,6 @@ class BuilderLinkedContainerComponent {
|
|
|
7826
7842
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderLinkedContainerComponent, isStandalone: false, selector: "app-builder-linked-container", inputs: { children: "children", style: "style", nodeId: "nodeId", dropListId: "dropListId", linkType: "linkType", url: "url", pageRoute: "pageRoute", target: "target" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
|
|
7827
7843
|
<a [href]="resolvedHref"
|
|
7828
7844
|
[target]="target"
|
|
7829
|
-
[ngStyle]="style"
|
|
7830
7845
|
[ngClass]="['iox-node-' + nodeId, listOrientation === 'horizontal' ? 'is-horizontal' : '']"
|
|
7831
7846
|
class="linked-container-root"
|
|
7832
7847
|
ioxDropzone
|
|
@@ -7849,14 +7864,13 @@ class BuilderLinkedContainerComponent {
|
|
|
7849
7864
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
7850
7865
|
</ng-container>
|
|
7851
7866
|
</a>
|
|
7852
|
-
`, isInline: true, styles: [".linked-container-root{display:block;position:relative;box-sizing:border-box;text-decoration:none;color:inherit;cursor:pointer}.linked-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;opacity:0;transition:opacity .15s}.linked-container-root:hover .linked-container-placeholder,.linked-container-root.iox-drag-over .linked-container-placeholder{opacity:1}.linked-container-placeholder i{font-size:18px}:host ::ng-deep .linked-container-root>[class*=iox-outer-]{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:
|
|
7867
|
+
`, isInline: true, styles: [":where(.linked-container-root){display:block;position:relative;box-sizing:border-box;text-decoration:none;color:inherit;cursor:pointer}.linked-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;opacity:0;transition:opacity .15s}.linked-container-root:hover .linked-container-placeholder,.linked-container-root.iox-drag-over .linked-container-placeholder{opacity:1}.linked-container-placeholder i{font-size:18px}:host ::ng-deep .linked-container-root>[class*=iox-outer-]{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", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
7853
7868
|
}
|
|
7854
7869
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderLinkedContainerComponent, decorators: [{
|
|
7855
7870
|
type: Component,
|
|
7856
7871
|
args: [{ selector: 'app-builder-linked-container', template: `
|
|
7857
7872
|
<a [href]="resolvedHref"
|
|
7858
7873
|
[target]="target"
|
|
7859
|
-
[ngStyle]="style"
|
|
7860
7874
|
[ngClass]="['iox-node-' + nodeId, listOrientation === 'horizontal' ? 'is-horizontal' : '']"
|
|
7861
7875
|
class="linked-container-root"
|
|
7862
7876
|
ioxDropzone
|
|
@@ -7879,7 +7893,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
7879
7893
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
7880
7894
|
</ng-container>
|
|
7881
7895
|
</a>
|
|
7882
|
-
`, standalone: false, styles: [".linked-container-root{display:block;position:relative;box-sizing:border-box;text-decoration:none;color:inherit;cursor:pointer}.linked-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;opacity:0;transition:opacity .15s}.linked-container-root:hover .linked-container-placeholder,.linked-container-root.iox-drag-over .linked-container-placeholder{opacity:1}.linked-container-placeholder i{font-size:18px}:host ::ng-deep .linked-container-root>[class*=iox-outer-]{min-width:0}\n"] }]
|
|
7896
|
+
`, standalone: false, styles: [":where(.linked-container-root){display:block;position:relative;box-sizing:border-box;text-decoration:none;color:inherit;cursor:pointer}.linked-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;opacity:0;transition:opacity .15s}.linked-container-root:hover .linked-container-placeholder,.linked-container-root.iox-drag-over .linked-container-placeholder{opacity:1}.linked-container-placeholder i{font-size:18px}:host ::ng-deep .linked-container-root>[class*=iox-outer-]{min-width:0}\n"] }]
|
|
7883
7897
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: undefined, decorators: [{
|
|
7884
7898
|
type: Optional
|
|
7885
7899
|
}, {
|
|
@@ -8548,7 +8562,6 @@ class BuilderDialogComponent {
|
|
|
8548
8562
|
this.overlayService = overlayService;
|
|
8549
8563
|
this.cdr = cdr;
|
|
8550
8564
|
this.children = [];
|
|
8551
|
-
this.style = {};
|
|
8552
8565
|
this.nodeId = '';
|
|
8553
8566
|
this.dropListId = '';
|
|
8554
8567
|
this.closeOnBackdrop = true;
|
|
@@ -8619,11 +8632,10 @@ class BuilderDialogComponent {
|
|
|
8619
8632
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
8620
8633
|
}
|
|
8621
8634
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, deps: [{ token: DragEngineService }, { token: i1.ComponentInstanceRegistryService }, { token: OverlayService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8622
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderDialogComponent, isStandalone: false, selector: "app-builder-dialog", inputs: { children: "children",
|
|
8635
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: BuilderDialogComponent, isStandalone: false, selector: "app-builder-dialog", inputs: { children: "children", nodeId: "nodeId", dropListId: "dropListId", closeOnBackdrop: "closeOnBackdrop", showCloseButton: "showCloseButton", placement: "placement" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
|
|
8623
8636
|
<div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
|
|
8624
8637
|
(click)="onStageClick($event)">
|
|
8625
|
-
<div [
|
|
8626
|
-
[ngClass]="'iox-node-' + nodeId"
|
|
8638
|
+
<div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
|
|
8627
8639
|
class="dialog-surface"
|
|
8628
8640
|
ioxDropzone
|
|
8629
8641
|
[ioxDropzoneId]="dropListId"
|
|
@@ -8652,15 +8664,14 @@ class BuilderDialogComponent {
|
|
|
8652
8664
|
</ng-container>
|
|
8653
8665
|
</div>
|
|
8654
8666
|
</div>
|
|
8655
|
-
`, isInline: true, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:24px;box-sizing:border-box;background:var(--iox-scrim
|
|
8667
|
+
`, isInline: true, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\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", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
8656
8668
|
}
|
|
8657
8669
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, decorators: [{
|
|
8658
8670
|
type: Component,
|
|
8659
8671
|
args: [{ selector: 'app-builder-dialog', template: `
|
|
8660
8672
|
<div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
|
|
8661
8673
|
(click)="onStageClick($event)">
|
|
8662
|
-
<div [
|
|
8663
|
-
[ngClass]="'iox-node-' + nodeId"
|
|
8674
|
+
<div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
|
|
8664
8675
|
class="dialog-surface"
|
|
8665
8676
|
ioxDropzone
|
|
8666
8677
|
[ioxDropzoneId]="dropListId"
|
|
@@ -8689,11 +8700,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
8689
8700
|
</ng-container>
|
|
8690
8701
|
</div>
|
|
8691
8702
|
</div>
|
|
8692
|
-
`, standalone: false, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:24px;box-sizing:border-box;background:var(--iox-scrim
|
|
8703
|
+
`, standalone: false, styles: [".dialog-stage{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:24px;box-sizing:border-box;background:#0f172a73}:host(.iox-es) .dialog-stage{background:var(--iox-scrim)}.dialog-stage[data-placement=top]{align-items:start}.dialog-stage[data-placement=bottom]{align-items:end}.dialog-stage[data-placement=start]{justify-items:start}.dialog-stage[data-placement=end]{justify-items:end}:where(.dialog-surface){box-sizing:border-box;max-width:100%;max-height:100%;overflow:auto;contain:layout}.dialog-close{position:absolute;inset-inline-end:10px;top:10px;width:26px;height:26px;border:none;border-radius:999px;background:#0f172a0f;color:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}:host(.iox-es) .dialog-close{border-radius:var(--iox-radius-pill);background:var(--iox-surface-2);color:var(--iox-text)}.dialog-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;min-height:90px;border:1.5px dashed rgba(100,116,139,.35);border-radius:4px;color:#64748b80;font-size:12px}.dialog-placeholder i{font-size:18px}\n"] }]
|
|
8693
8704
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }], propDecorators: { children: [{
|
|
8694
8705
|
type: Input
|
|
8695
|
-
}], style: [{
|
|
8696
|
-
type: Input
|
|
8697
8706
|
}], nodeId: [{
|
|
8698
8707
|
type: Input
|
|
8699
8708
|
}], dropListId: [{
|
|
@@ -9512,24 +9521,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9512
9521
|
}] } });
|
|
9513
9522
|
|
|
9514
9523
|
class SliderSlideComponent {
|
|
9515
|
-
set style(v) {
|
|
9516
|
-
if (v === this._style)
|
|
9517
|
-
return;
|
|
9518
|
-
this._style = v;
|
|
9519
|
-
// Strip width constraints; rebuild cached object so ngStyle only updates DOM
|
|
9520
|
-
// when the input actually changed — prevents spurious MutationObserver triggers.
|
|
9521
|
-
const { width, minWidth, maxWidth, ...rest } = v || {};
|
|
9522
|
-
this._slideEditStyle = rest;
|
|
9523
|
-
}
|
|
9524
|
-
get style() { return this._style; }
|
|
9525
|
-
get slideEditStyle() { return this._slideEditStyle; }
|
|
9526
9524
|
constructor(dragEngine, cdr, isPreview) {
|
|
9527
9525
|
this.dragEngine = dragEngine;
|
|
9528
9526
|
this.cdr = cdr;
|
|
9529
9527
|
this.isPreview = isPreview;
|
|
9530
9528
|
this.children = [];
|
|
9531
|
-
this._style = {};
|
|
9532
|
-
this._slideEditStyle = {};
|
|
9533
9529
|
this.nodeId = '';
|
|
9534
9530
|
this.dropListId = '';
|
|
9535
9531
|
this.childClick = new EventEmitter();
|
|
@@ -9543,18 +9539,15 @@ class SliderSlideComponent {
|
|
|
9543
9539
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
9544
9540
|
}
|
|
9545
9541
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SliderSlideComponent, deps: [{ token: DragEngineService }, { token: i0.ChangeDetectorRef }, { token: IS_PREVIEW, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
9546
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: SliderSlideComponent, isStandalone: false, selector: "app-slider-slide", inputs: { children: "children",
|
|
9542
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: SliderSlideComponent, isStandalone: false, selector: "app-slider-slide", inputs: { children: "children", nodeId: "nodeId", dropListId: "dropListId" }, outputs: { childClick: "childClick", childMouseEnter: "childMouseEnter", childMouseLeave: "childMouseLeave" }, ngImport: i0, template: `
|
|
9547
9543
|
<!-- The .swiper-slide wrapper (with the iox-outer-* class) is provided by SliderContainer —
|
|
9548
9544
|
Swiper needs it as a direct child of .swiper-wrapper — and the SLIDE'S size lands there
|
|
9549
|
-
via StyleRegistryService. This is only the styled INNER block
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
behave identically — the earlier preview branch used the un-stripped style input and
|
|
9554
|
-
shrank the content inside its own slide. -->
|
|
9545
|
+
via StyleRegistryService (width is an outer prop). This is only the styled INNER block:
|
|
9546
|
+
its look (aspect-ratio, background, radius, the media style's hover scale) comes from
|
|
9547
|
+
the node's .iox-node-{id} class rule ONLY, like every other component — an inline
|
|
9548
|
+
base map beat the rule's :hover (DS2 verification). -->
|
|
9555
9549
|
<div class="slide-edit-block"
|
|
9556
9550
|
[ngClass]="['iox-node-' + nodeId]"
|
|
9557
|
-
[ngStyle]="slideEditStyle"
|
|
9558
9551
|
ioxDropzone
|
|
9559
9552
|
[ioxDropzoneId]="dropListId"
|
|
9560
9553
|
[ioxDropzoneData]="children"
|
|
@@ -9578,22 +9571,19 @@ class SliderSlideComponent {
|
|
|
9578
9571
|
</ng-container>
|
|
9579
9572
|
</div>
|
|
9580
9573
|
</div>
|
|
9581
|
-
`, isInline: true, styles: [".slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-300)}.slide-
|
|
9574
|
+
`, isInline: true, styles: [":where(.slide-edit-block){position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;width:100%;background-color:var(--p-surface-300)}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-placeholder i{font-size:22px}\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", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "directive", type: IoxDraggableDirective, selector: "[ioxDraggable]", inputs: ["ioxDragData", "ioxDragSourceId", "ioxDragDisabled"] }, { kind: "directive", type: IoxDropzoneDirective, selector: "[ioxDropzone]", inputs: ["ioxDropzoneId", "ioxDropzoneData", "ioxDropzonePostDrop"], outputs: ["ioxDrop"] }] }); }
|
|
9582
9575
|
}
|
|
9583
9576
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SliderSlideComponent, decorators: [{
|
|
9584
9577
|
type: Component,
|
|
9585
9578
|
args: [{ selector: 'app-slider-slide', template: `
|
|
9586
9579
|
<!-- The .swiper-slide wrapper (with the iox-outer-* class) is provided by SliderContainer —
|
|
9587
9580
|
Swiper needs it as a direct child of .swiper-wrapper — and the SLIDE'S size lands there
|
|
9588
|
-
via StyleRegistryService. This is only the styled INNER block
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
behave identically — the earlier preview branch used the un-stripped style input and
|
|
9593
|
-
shrank the content inside its own slide. -->
|
|
9581
|
+
via StyleRegistryService (width is an outer prop). This is only the styled INNER block:
|
|
9582
|
+
its look (aspect-ratio, background, radius, the media style's hover scale) comes from
|
|
9583
|
+
the node's .iox-node-{id} class rule ONLY, like every other component — an inline
|
|
9584
|
+
base map beat the rule's :hover (DS2 verification). -->
|
|
9594
9585
|
<div class="slide-edit-block"
|
|
9595
9586
|
[ngClass]="['iox-node-' + nodeId]"
|
|
9596
|
-
[ngStyle]="slideEditStyle"
|
|
9597
9587
|
ioxDropzone
|
|
9598
9588
|
[ioxDropzoneId]="dropListId"
|
|
9599
9589
|
[ioxDropzoneData]="children"
|
|
@@ -9617,7 +9607,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9617
9607
|
</ng-container>
|
|
9618
9608
|
</div>
|
|
9619
9609
|
</div>
|
|
9620
|
-
`, standalone: false, styles: [".slide-edit-block{position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:var(--p-surface-300)}.slide-
|
|
9610
|
+
`, standalone: false, styles: [":where(.slide-edit-block){position:relative;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;width:100%;background-color:var(--p-surface-300)}.slide-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;opacity:0;transition:opacity .15s}:host:hover .slide-placeholder,:host.iox-drag-over .slide-placeholder{opacity:1}.slide-placeholder i{font-size:22px}\n"] }]
|
|
9621
9611
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
9622
9612
|
type: Optional
|
|
9623
9613
|
}, {
|
|
@@ -9625,8 +9615,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9625
9615
|
args: [IS_PREVIEW]
|
|
9626
9616
|
}] }], propDecorators: { children: [{
|
|
9627
9617
|
type: Input
|
|
9628
|
-
}], style: [{
|
|
9629
|
-
type: Input
|
|
9630
9618
|
}], nodeId: [{
|
|
9631
9619
|
type: Input
|
|
9632
9620
|
}], dropListId: [{
|