@vectoriox/iox-builder 4.37.3 → 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 +728 -663
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +80 -20
- 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,
|
|
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';
|
|
@@ -245,26 +245,6 @@ class ComponentConfig {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Layer 2 of the flavor cascade (flavors plan §4), applied by the registry factory to every
|
|
250
|
-
* BRAND-NEW element: overwrite matching styleTrait defaults with the pack's token references
|
|
251
|
-
* (`var(--iox-<token>, <packValue>)`). Runs AFTER the subclass constructor, so a pack beats the
|
|
252
|
-
* constructor's literals; the author's own edits (layer 4) beat everything as always. Self-
|
|
253
|
-
* describing serialisation then writes the var into styleProps on save — which is why the
|
|
254
|
-
* engine's `flavorDefaultDeclarations` only has to cover LEGACY nodes.
|
|
255
|
-
*/
|
|
256
|
-
function applyFlavorDefaults(config, pack) {
|
|
257
|
-
const defaults = resolveFlavorDefaults(config.type, pack);
|
|
258
|
-
const names = Object.keys(defaults);
|
|
259
|
-
if (!names.length)
|
|
260
|
-
return;
|
|
261
|
-
for (const group of config.styleTraits) {
|
|
262
|
-
for (const trait of group.traits) {
|
|
263
|
-
if (defaults[trait.name] !== undefined)
|
|
264
|
-
trait.default = defaults[trait.name];
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
248
|
class TraitConfig {
|
|
269
249
|
constructor(name, label, type, options, defaultValue, inline, showWhen, responsive) {
|
|
270
250
|
this.name = name;
|
|
@@ -553,6 +533,14 @@ class TypographyGroupStyleConfig extends GroupStyleConfig {
|
|
|
553
533
|
// completely (icons/glyph-only elements), which `0.5` used to block.
|
|
554
534
|
new TraitConfig('lineHeight', 'Line Height', TraitInputType.Scrub, { min: 0, max: 5, step: 0.05, units: [''] }, '1.5'),
|
|
555
535
|
new TraitConfig('letterSpacing', 'Spacing', TraitInputType.Scrub, { min: -5, max: 50, step: 0.5, units: UNITS_FIXED }, '0px'),
|
|
536
|
+
// DS2: element styles write `var(--iox-button-case)` / `var(--iox-case-label)` here.
|
|
537
|
+
// `none` is the CSS initial and a compiler no-op (T4), so it never leaks onto nodes.
|
|
538
|
+
new TraitConfig('textTransform', 'Case', TraitInputType.Select, [
|
|
539
|
+
{ label: 'None', value: 'none' },
|
|
540
|
+
{ label: 'UPPERCASE', value: 'uppercase' },
|
|
541
|
+
{ label: 'lowercase', value: 'lowercase' },
|
|
542
|
+
{ label: 'Capitalize', value: 'capitalize' },
|
|
543
|
+
], 'none'),
|
|
556
544
|
new TraitConfig('textAlign', '', TraitInputType.SelectButton, [
|
|
557
545
|
{ value: 'left', icon: 'ph-thin ph-text-align-left' },
|
|
558
546
|
{ value: 'center', icon: 'ph-thin ph-text-align-center' },
|
|
@@ -606,6 +594,9 @@ class EffectsGroupStyleConfig extends GroupStyleConfig {
|
|
|
606
594
|
new TraitConfig('filterInvert', 'Invert', TraitInputType.Range, { min: 0, max: 1, step: 0.05, showExtremes: false, showValue: true, compact: true }, '0'),
|
|
607
595
|
// ── Backdrop filter (virtual — composed to CSS backdrop-filter: …)
|
|
608
596
|
new TraitConfig('_sep_backdrop', 'Backdrop', TraitInputType.SectionLabel),
|
|
597
|
+
// DS2 whole-value backdrop (`var(--iox-card-backdrop)`, `blur(12px) saturate(1.4)`).
|
|
598
|
+
// When set it WINS over the composed controls below; `none` = unset and a no-op (T3).
|
|
599
|
+
new TraitConfig('backdropFilter', 'Filter', TraitInputType.Text, undefined, 'none'),
|
|
609
600
|
new TraitConfig('backdropBlur', 'Blur', TraitInputType.Scrub, { min: 0, max: 50, step: 1, units: ['px'] }, '0px', true),
|
|
610
601
|
new TraitConfig('backdropBrightness', 'Brightness', TraitInputType.Range, { min: 0, max: 3, step: 0.05, showExtremes: false, showValue: true, compact: true }, '1', true),
|
|
611
602
|
new TraitConfig('backdropContrast', 'Contrast', TraitInputType.Range, { min: 0, max: 3, step: 0.05, showExtremes: false, showValue: true, compact: true }, '1', true),
|
|
@@ -1558,16 +1549,39 @@ class ComponentRegistryService {
|
|
|
1558
1549
|
hasComponent(type) {
|
|
1559
1550
|
return !!this.components[type];
|
|
1560
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* A NEW element, born wearing its type's default element style (design-system plan §3e):
|
|
1554
|
+
* the style's exact trait map goes into `styleTraits`, its hover/active/focus into
|
|
1555
|
+
* `stateStyles`, its id into `elementStyle`. Flavor-independent — the look arrives through the
|
|
1556
|
+
* org's tokens, so no pack is consulted here.
|
|
1557
|
+
*/
|
|
1561
1558
|
createConfig(type) {
|
|
1562
|
-
const
|
|
1563
|
-
if (!
|
|
1559
|
+
const config = this.createBaseConfig(type);
|
|
1560
|
+
if (!config)
|
|
1564
1561
|
return null;
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
applyFlavorDefaults(config, DEFAULT_FLAVOR);
|
|
1562
|
+
const id = defaultElementStyleFor(type, traitValues(config));
|
|
1563
|
+
if (id)
|
|
1564
|
+
applyElementStyle(config, id);
|
|
1569
1565
|
return config;
|
|
1570
1566
|
}
|
|
1567
|
+
/**
|
|
1568
|
+
* The type's UNSTYLED config — constructor defaults only. For rebuilding a SAVED node (its own
|
|
1569
|
+
* stored values decide its look; a pre-DS2 page must not silently adopt element styles) and as
|
|
1570
|
+
* the baseline a switched-off style's traits fall back to.
|
|
1571
|
+
*/
|
|
1572
|
+
createBaseConfig(type) {
|
|
1573
|
+
const configType = this.components[type]?.config;
|
|
1574
|
+
return configType ? new configType() : null;
|
|
1575
|
+
}
|
|
1576
|
+
/** The unstyled base style map of a type — `switchElementStyle`'s baseline. */
|
|
1577
|
+
baselineStyleProps(type) {
|
|
1578
|
+
const out = {};
|
|
1579
|
+
for (const group of this.createBaseConfig(type)?.styleTraits ?? []) {
|
|
1580
|
+
for (const trait of group.traits ?? [])
|
|
1581
|
+
out[trait.name] = trait.default;
|
|
1582
|
+
}
|
|
1583
|
+
return out;
|
|
1584
|
+
}
|
|
1571
1585
|
register(type, component, config) {
|
|
1572
1586
|
this.components[type] = { type, component, config };
|
|
1573
1587
|
}
|
|
@@ -1580,6 +1594,12 @@ class ComponentRegistryService {
|
|
|
1580
1594
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ComponentRegistryService, decorators: [{
|
|
1581
1595
|
type: Injectable
|
|
1582
1596
|
}] });
|
|
1597
|
+
function traitValues(config) {
|
|
1598
|
+
const out = {};
|
|
1599
|
+
for (const trait of config.traits ?? [])
|
|
1600
|
+
out[trait.name] = trait.default;
|
|
1601
|
+
return out;
|
|
1602
|
+
}
|
|
1583
1603
|
|
|
1584
1604
|
class OverlayService {
|
|
1585
1605
|
constructor() {
|
|
@@ -1777,60 +1797,445 @@ function resolveDragTarget(el, selectedElement, isDragHandle) {
|
|
|
1777
1797
|
}
|
|
1778
1798
|
|
|
1779
1799
|
/**
|
|
1780
|
-
*
|
|
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.
|
|
1781
1816
|
*
|
|
1782
|
-
*
|
|
1783
|
-
*
|
|
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.
|
|
1784
1837
|
*/
|
|
1785
|
-
class
|
|
1838
|
+
class StyleRegistryService {
|
|
1786
1839
|
constructor() {
|
|
1787
|
-
this.
|
|
1788
|
-
|
|
1789
|
-
|
|
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;
|
|
1790
1856
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
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);
|
|
1796
1890
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
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);
|
|
1799
1911
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1912
|
+
ownProps(nodeId, styles) {
|
|
1913
|
+
const type = this.nodeTypes.get(nodeId);
|
|
1914
|
+
return type ? stripSuppressedProps(type, styles) : styles;
|
|
1802
1915
|
}
|
|
1803
|
-
upsert(
|
|
1804
|
-
|
|
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();
|
|
1805
1947
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
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();
|
|
1808
1969
|
}
|
|
1809
|
-
/**
|
|
1810
|
-
|
|
1811
|
-
|
|
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();
|
|
1812
1991
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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
|
+
}
|
|
1816
2017
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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]);
|
|
1820
2027
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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
|
|
1834
2239
|
// overshoot at once reads as jitter, not polish — reserve overshoot for single-
|
|
1835
2240
|
// element motion (ghost drop-settle, a later phase).
|
|
1836
2241
|
const MOTION_EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
@@ -2211,6 +2616,10 @@ class DragEngineService {
|
|
|
2211
2616
|
'margin:0',
|
|
2212
2617
|
].join(';');
|
|
2213
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);
|
|
2214
2623
|
document.body.appendChild(clone);
|
|
2215
2624
|
this._overlay = clone;
|
|
2216
2625
|
this._startGhostFollow(rect.left, rect.top);
|
|
@@ -3475,566 +3884,192 @@ class InteractionEngineService {
|
|
|
3475
3884
|
element.style.removeProperty('pointer-events');
|
|
3476
3885
|
element.removeAttribute('data-iox-hidden');
|
|
3477
3886
|
this.preStatedElements.delete(element);
|
|
3478
|
-
// Builder: clear inline pre-state so cancelling animations later
|
|
3479
|
-
// doesn't leave the element invisible after a play preview.
|
|
3480
|
-
if (this.isBuilderMode())
|
|
3481
|
-
this.clearInlineAnimationStyles(element);
|
|
3482
|
-
}).catch(() => { });
|
|
3483
|
-
}
|
|
3484
|
-
else if (this.EXIT_TYPES.has(action.type)) {
|
|
3485
|
-
// Once the exit animation finishes the element is invisible —
|
|
3486
|
-
// disable pointer events so it doesn't silently block clicks underneath it.
|
|
3487
|
-
anim.finished.then(() => {
|
|
3488
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3489
|
-
}).catch(() => { });
|
|
3490
|
-
}
|
|
3491
|
-
}
|
|
3492
|
-
/**
|
|
3493
|
-
* Freeze an element at the animation's starting frame immediately, before any
|
|
3494
|
-
* trigger fires. Called for ALL entrance-type actions regardless of trigger so
|
|
3495
|
-
* that a click-triggered fadeIn on a menu overlay also starts it hidden.
|
|
3496
|
-
*
|
|
3497
|
-
* In builder mode only opacity:0 is applied — transforms on canvas elements
|
|
3498
|
-
* inside iox-page-component's scale() transform break Lenis scroll. The transform
|
|
3499
|
-
* jump at animation t=0 is invisible because the element is already opacity:0.
|
|
3500
|
-
* In production the full keyframe is applied + pointer-events:none.
|
|
3501
|
-
*/
|
|
3502
|
-
applyPreState(element, action) {
|
|
3503
|
-
const { keyframes } = this.buildAnimation(action);
|
|
3504
|
-
if (!keyframes.length)
|
|
3505
|
-
return;
|
|
3506
|
-
const first = keyframes[0];
|
|
3507
|
-
if (this.isBuilderMode()) {
|
|
3508
|
-
// No transforms — inline transforms on children inside iox-page-component's
|
|
3509
|
-
// scale() break Lenis scroll. pointer-events:none lets wheel events and
|
|
3510
|
-
// hover fall through to visible elements underneath.
|
|
3511
|
-
element.style.setProperty('opacity', '0');
|
|
3512
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3513
|
-
}
|
|
3514
|
-
else {
|
|
3515
|
-
for (const [prop, val] of Object.entries(first)) {
|
|
3516
|
-
element.style[prop] = String(val);
|
|
3517
|
-
}
|
|
3518
|
-
element.style.setProperty('pointer-events', 'none');
|
|
3519
|
-
}
|
|
3520
|
-
element.setAttribute('data-iox-hidden', '');
|
|
3521
|
-
this.preStatedElements.add(element);
|
|
3522
|
-
}
|
|
3523
|
-
clearInlineAnimationStyles(element) {
|
|
3524
|
-
element.style.removeProperty('opacity');
|
|
3525
|
-
element.style.removeProperty('transform');
|
|
3526
|
-
element.style.removeProperty('visibility');
|
|
3527
|
-
element.style.removeProperty('pointer-events');
|
|
3528
|
-
element.removeAttribute('data-iox-hidden');
|
|
3529
|
-
}
|
|
3530
|
-
reverseAction(element, action) {
|
|
3531
|
-
const { keyframes } = this.buildAnimation(action);
|
|
3532
|
-
if (!keyframes.length)
|
|
3533
|
-
return;
|
|
3534
|
-
// Reverse: play the keyframes backwards
|
|
3535
|
-
element.animate([...keyframes].reverse(), {
|
|
3536
|
-
duration: action.duration,
|
|
3537
|
-
easing: action.easing,
|
|
3538
|
-
fill: 'forwards',
|
|
3539
|
-
});
|
|
3540
|
-
}
|
|
3541
|
-
buildAnimation(action) {
|
|
3542
|
-
const dist = action.params?.['distance'] ?? 20;
|
|
3543
|
-
let keyframes = [];
|
|
3544
|
-
switch (action.type) {
|
|
3545
|
-
case 'fadeIn':
|
|
3546
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3547
|
-
break;
|
|
3548
|
-
case 'fadeOut':
|
|
3549
|
-
keyframes = [{ opacity: 1 }, { opacity: 0 }];
|
|
3550
|
-
break;
|
|
3551
|
-
case 'moveUp':
|
|
3552
|
-
keyframes = [{ transform: `translateY(${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3553
|
-
break;
|
|
3554
|
-
case 'moveDown':
|
|
3555
|
-
keyframes = [{ transform: `translateY(-${dist}px)`, opacity: 0 }, { transform: 'translateY(0)', opacity: 1 }];
|
|
3556
|
-
break;
|
|
3557
|
-
case 'moveLeft':
|
|
3558
|
-
keyframes = [{ transform: `translateX(${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3559
|
-
break;
|
|
3560
|
-
case 'moveRight':
|
|
3561
|
-
keyframes = [{ transform: `translateX(-${dist}px)`, opacity: 0 }, { transform: 'translateX(0)', opacity: 1 }];
|
|
3562
|
-
break;
|
|
3563
|
-
case 'scaleIn':
|
|
3564
|
-
keyframes = [{ transform: 'scale(0.8)', opacity: 0 }, { transform: 'scale(1)', opacity: 1 }];
|
|
3565
|
-
break;
|
|
3566
|
-
case 'scaleOut':
|
|
3567
|
-
keyframes = [{ transform: 'scale(1)', opacity: 1 }, { transform: 'scale(0.8)', opacity: 0 }];
|
|
3568
|
-
break;
|
|
3569
|
-
case 'rotate':
|
|
3570
|
-
const deg = action.params?.['degrees'] ?? 360;
|
|
3571
|
-
keyframes = [{ transform: `rotate(0deg)` }, { transform: `rotate(${deg}deg)` }];
|
|
3572
|
-
break;
|
|
3573
|
-
case 'show':
|
|
3574
|
-
keyframes = [{ opacity: 0, visibility: 'hidden' }, { opacity: 1, visibility: 'visible' }];
|
|
3575
|
-
break;
|
|
3576
|
-
case 'hide':
|
|
3577
|
-
keyframes = [{ opacity: 1, visibility: 'visible' }, { opacity: 0, visibility: 'hidden' }];
|
|
3578
|
-
break;
|
|
3579
|
-
case 'toggleVisibility':
|
|
3580
|
-
// Toggle is stateful — handled separately via element style
|
|
3581
|
-
keyframes = [{ opacity: 0 }, { opacity: 1 }];
|
|
3582
|
-
break;
|
|
3583
|
-
}
|
|
3584
|
-
return {
|
|
3585
|
-
keyframes,
|
|
3586
|
-
options: {
|
|
3587
|
-
duration: action.duration,
|
|
3588
|
-
delay: action.delay,
|
|
3589
|
-
easing: action.easing,
|
|
3590
|
-
fill: 'forwards',
|
|
3591
|
-
},
|
|
3592
|
-
};
|
|
3593
|
-
}
|
|
3594
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, deps: [{ token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3595
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService }); }
|
|
3596
|
-
}
|
|
3597
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionEngineService, decorators: [{
|
|
3598
|
-
type: Injectable
|
|
3599
|
-
}], ctorParameters: () => [{ type: OverlayService }] });
|
|
3600
|
-
|
|
3601
|
-
/**
|
|
3602
|
-
* InteractionPresetRegistryService — in-memory store for org-level interaction presets.
|
|
3603
|
-
*
|
|
3604
|
-
* Loaded once per page by PageUiComponent alongside the page layout.
|
|
3605
|
-
* Scoped to PageUiComponent.providers[] via IoxBuilderModule — one registry per builder instance.
|
|
3606
|
-
*/
|
|
3607
|
-
class InteractionPresetRegistryService {
|
|
3608
|
-
constructor() {
|
|
3609
|
-
this.presets = new Map();
|
|
3610
|
-
}
|
|
3611
|
-
setPresets(presets) {
|
|
3612
|
-
this.presets.clear();
|
|
3613
|
-
for (const p of presets) {
|
|
3614
|
-
this.presets.set(p.id, p);
|
|
3615
|
-
}
|
|
3616
|
-
}
|
|
3617
|
-
getPreset(id) {
|
|
3618
|
-
return this.presets.get(id);
|
|
3619
|
-
}
|
|
3620
|
-
getAll() {
|
|
3621
|
-
return Array.from(this.presets.values());
|
|
3622
|
-
}
|
|
3623
|
-
upsert(preset) {
|
|
3624
|
-
this.presets.set(preset.id, preset);
|
|
3625
|
-
}
|
|
3626
|
-
remove(id) {
|
|
3627
|
-
this.presets.delete(id);
|
|
3628
|
-
}
|
|
3629
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3630
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService }); }
|
|
3631
|
-
}
|
|
3632
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: InteractionPresetRegistryService, decorators: [{
|
|
3633
|
-
type: Injectable
|
|
3634
|
-
}] });
|
|
3635
|
-
|
|
3636
|
-
/**
|
|
3637
|
-
* BuilderClipboardService — in-memory typed clipboard for the page builder.
|
|
3638
|
-
*
|
|
3639
|
-
* Stores element copies, style preset references, and interaction preset references.
|
|
3640
|
-
* Lives in memory only — no navigator.clipboard usage.
|
|
3641
|
-
* Scoped to PageUiComponent.providers[] — one clipboard per builder instance.
|
|
3642
|
-
*/
|
|
3643
|
-
class BuilderClipboardService {
|
|
3644
|
-
constructor() {
|
|
3645
|
-
this._clip = {};
|
|
3646
|
-
}
|
|
3647
|
-
copyNode(node) {
|
|
3648
|
-
this._clip = { node: JSON.parse(JSON.stringify(node)) };
|
|
3649
|
-
}
|
|
3650
|
-
copyPreset(presetId) {
|
|
3651
|
-
this._clip = { ...this._clip, presetId };
|
|
3652
|
-
}
|
|
3653
|
-
copyInteractionPreset(interactionPresetId) {
|
|
3654
|
-
this._clip = { ...this._clip, interactionPresetId };
|
|
3655
|
-
}
|
|
3656
|
-
getNode() { return this._clip.node; }
|
|
3657
|
-
getPresetId() { return this._clip.presetId; }
|
|
3658
|
-
getInteractionPresetId() { return this._clip.interactionPresetId; }
|
|
3659
|
-
clear() { this._clip = {}; }
|
|
3660
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3661
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService }); }
|
|
3662
|
-
}
|
|
3663
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderClipboardService, decorators: [{
|
|
3664
|
-
type: Injectable
|
|
3665
|
-
}] });
|
|
3666
|
-
|
|
3667
|
-
/**
|
|
3668
|
-
* CSS container name of the builder canvas frame (and of the global-elements overlay). Breakpoint
|
|
3669
|
-
* rules are emitted as `@container iox-canvas (max-width: …)`: the frame's layout width IS the
|
|
3670
|
-
* simulated viewport and is unaffected by the zoom `transform: scale`, whereas a `@media` query
|
|
3671
|
-
* would read the editor window. Set on the frame in builder.component.scss.
|
|
3672
|
-
*/
|
|
3673
|
-
const BUILDER_CANVAS_CONTAINER = 'iox-canvas';
|
|
3674
|
-
/**
|
|
3675
|
-
* StyleRegistryService — manages a single <style> tag for the builder canvas.
|
|
3676
|
-
*
|
|
3677
|
-
* Each builder component gets two CSS classes:
|
|
3678
|
-
* .iox-node-{id} — applied to the inner element; handles layout/visual props
|
|
3679
|
-
* (display, flex, background, border, padding, etc.)
|
|
3680
|
-
* .iox-outer-{id} — applied to the host element by RenderDirective; handles
|
|
3681
|
-
* properties that participate in the PARENT flow (margin,
|
|
3682
|
-
* align-self, flex-grow/shrink/basis, order).
|
|
3683
|
-
*
|
|
3684
|
-
* This split is necessary because margin on an inner wrapper only adds internal
|
|
3685
|
-
* space — it does not push sibling elements. Applying margin to the host element
|
|
3686
|
-
* (which IS the flex/block child in the parent container) makes it work correctly.
|
|
3687
|
-
*
|
|
3688
|
-
* State styles (hover/active/focus) are stored under key `${nodeId}:${state}`
|
|
3689
|
-
* and compiled to `.iox-node-{id}:hover { … }` selectors.
|
|
3690
|
-
*
|
|
3691
|
-
* Design token variables are stored under the reserved key `__tokens__` and
|
|
3692
|
-
* compiled to a `:root { … }` block prepended to all other rules.
|
|
3693
|
-
*
|
|
3694
|
-
* Scoped to PageUiComponent.providers[] — one stylesheet per builder instance.
|
|
3695
|
-
*/
|
|
3696
|
-
class StyleRegistryService {
|
|
3697
|
-
constructor() {
|
|
3698
|
-
this.changes$ = new Subject();
|
|
3699
|
-
this.rules = new Map();
|
|
3700
|
-
/** Breakpoint rules, kept apart so they flush AFTER every base rule — equal specificity, so
|
|
3701
|
-
* source order decides (plan trap 1). Keyed per breakpoint like `rules` (`id`, `id:state`). */
|
|
3702
|
-
this.responsiveRules = new Map();
|
|
3703
|
-
this.responsiveInputs = new Map();
|
|
3704
|
-
/** Which breakpoint rule keys each node owns — so re-emitting one node's rules is O(its rules),
|
|
3705
|
-
* not a scan of every breakpoint rule on the page (this runs on every node render). */
|
|
3706
|
-
this.responsiveKeys = new Map();
|
|
3707
|
-
this.stateScopes = new Map();
|
|
3708
|
-
this.styleEl = null;
|
|
3709
|
-
this._flushPending = false;
|
|
3710
|
-
}
|
|
3711
|
-
/**
|
|
3712
|
-
* Properties that must live on the OUTER wrapper (.iox-outer-{id}) because they
|
|
3713
|
-
* affect how the element participates in its PARENT layout (flex/block flow).
|
|
3714
|
-
* Everything else goes to the INNER element (.iox-node-{id}).
|
|
3715
|
-
*
|
|
3716
|
-
* Exception — out-of-flow positioning (position:fixed/absolute):
|
|
3717
|
-
* When position is fixed or absolute, width is redirected to the inner element
|
|
3718
|
-
* (the positioned one) and a default width:100% is added if none is set, so the
|
|
3719
|
-
* element stays visible. top/right/bottom/left/zIndex are always inner because
|
|
3720
|
-
* they are co-ordinates of the positioned element, not the flow wrapper.
|
|
3721
|
-
* position itself goes to inner with `!important` to beat component-scoped CSS.
|
|
3722
|
-
*/
|
|
3723
|
-
static { this.OUTER_PROPS = new Set([
|
|
3724
|
-
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
|
|
3725
|
-
'alignSelf', 'flexGrow', 'flexShrink', 'flexBasis', 'order',
|
|
3726
|
-
// Grid-child placement — like the flex-child props above, these position the node
|
|
3727
|
-
// within the PARENT's grid, so they must land on the outer host, not the inner div.
|
|
3728
|
-
'gridColumn', 'gridRow', 'justifySelf',
|
|
3729
|
-
'width',
|
|
3730
|
-
]); }
|
|
3731
|
-
/**
|
|
3732
|
-
* Properties that need !important on the inner element to beat Angular's scoped-CSS
|
|
3733
|
-
* specificity boost. CANVAS-ONLY (H2 audit, 2026-10-11): the shared compiler never forces
|
|
3734
|
-
* importance and the SSR engine never emits it — this is the builder's own fight with its
|
|
3735
|
-
* scoped component styles. The A4 dialog centring clash this once caused is resolved
|
|
3736
|
-
* structurally (the grid stage PLACES a static surface), so the set stays at exactly one
|
|
3737
|
-
* documented member; adding to it needs the same scrutiny.
|
|
3738
|
-
*/
|
|
3739
|
-
static { this.IMPORTANT_PROPS = new Set(['position']); }
|
|
3740
|
-
init() {
|
|
3741
|
-
this.styleEl = document.createElement('style');
|
|
3742
|
-
this.styleEl.id = 'iox-runtime-styles';
|
|
3743
|
-
document.head.appendChild(this.styleEl);
|
|
3744
|
-
}
|
|
3745
|
-
/**
|
|
3746
|
-
* Write or update the base styles for a node, or a pseudo-class state override.
|
|
3747
|
-
*
|
|
3748
|
-
* @param nodeId The node's CSS id (used in `.iox-node-{nodeId}`)
|
|
3749
|
-
* @param styles Flat CSS property map (camelCase keys)
|
|
3750
|
-
* @param state Optional pseudo-class state ('hover' | 'active' | 'focus' | …).
|
|
3751
|
-
* When provided, compiles to `.iox-node-{id}:{state} { … }`.
|
|
3752
|
-
* State rules are NOT partitioned into inner/outer.
|
|
3753
|
-
* @param relativeToId For RELATIONAL states (e.g. 'parent-hover') this is the ancestor
|
|
3754
|
-
* css id; compiles to `.iox-node-{relativeToId}:{base} .iox-node-{nodeId} { … }`.
|
|
3755
|
-
* For TARGETED states (e.g. 'trigger-hover') this is the SCOPE css id
|
|
3756
|
-
* (nearest common ancestor of the styled node + trigger).
|
|
3757
|
-
* @param triggerId For TARGETED states only: the css id of the trigger node whose
|
|
3758
|
-
* pseudo-class drives the state. Compiles a `:has()`-scoped selector.
|
|
3759
|
-
*/
|
|
3760
|
-
upsert(nodeId, styles, state, relativeToId, triggerId) {
|
|
3761
|
-
if (!nodeId)
|
|
3762
|
-
return;
|
|
3763
|
-
if (state) {
|
|
3764
|
-
const key = `${nodeId}:${state}`;
|
|
3765
|
-
// Remember the scope a relational / targeted rule was given, so the same state at a
|
|
3766
|
-
// breakpoint is scoped identically without re-resolving it (see upsertResponsive).
|
|
3767
|
-
if (relativeToId || triggerId)
|
|
3768
|
-
this.stateScopes.set(key, { relativeToId, triggerId });
|
|
3769
|
-
const selector = this.stateSelector(nodeId, state, relativeToId, triggerId);
|
|
3770
|
-
const css = selector ? this.compile(selector, styles) : '';
|
|
3771
|
-
if (css)
|
|
3772
|
-
this.rules.set(key, css);
|
|
3773
|
-
else
|
|
3774
|
-
this.rules.delete(key);
|
|
3775
|
-
if (this.responsiveInputs.has(nodeId))
|
|
3776
|
-
this.emitResponsive(nodeId);
|
|
3777
|
-
this.flush();
|
|
3778
|
-
return;
|
|
3779
|
-
}
|
|
3780
|
-
const { main, marker } = this.nodeRuleCss(nodeId, styles, this.isOutOfFlow(styles), DeclarationContext.Base);
|
|
3781
|
-
if (marker)
|
|
3782
|
-
this.rules.set(`${nodeId}::marker`, marker);
|
|
3783
|
-
else
|
|
3784
|
-
this.rules.delete(`${nodeId}::marker`);
|
|
3785
|
-
if (main)
|
|
3786
|
-
this.rules.set(nodeId, main);
|
|
3787
|
-
else
|
|
3788
|
-
this.rules.delete(nodeId);
|
|
3789
|
-
this.flush();
|
|
3790
|
-
}
|
|
3791
|
-
/**
|
|
3792
|
-
* Write or clear the styles for one PART of a compound component (board 7 — "the failure
|
|
3793
|
-
* design"), optionally further scoped to a validation state ('wrong'/'accepted'). Compiles
|
|
3794
|
-
* through the exact same `compile()` every other rule uses — `buildPartSelector` (shared,
|
|
3795
|
-
* `@vectoriox/iox-ui`) just builds a different selector string. Unlike interaction/relational/
|
|
3796
|
-
* targeted states, a validation state compounds a CLASS onto the node (`.iox-invalid`), not a
|
|
3797
|
-
* `:pseudo-class`, since `IoxFieldComponent` sets that class itself from `forceInvalid`/
|
|
3798
|
-
* `showValid` — there is no real "wrong" pseudo-class to hook.
|
|
3799
|
-
*/
|
|
3800
|
-
upsertPart(nodeId, partClassName, styles, state) {
|
|
3801
|
-
if (!nodeId || !partClassName)
|
|
3802
|
-
return;
|
|
3803
|
-
const key = `${nodeId}:part:${partClassName}${state ? ':' + state : ''}`;
|
|
3804
|
-
const validationClass = state ? VALIDATION_STATE_CLASS[state] : undefined;
|
|
3805
|
-
const selector = buildPartSelector(nodeId, partClassName, validationClass);
|
|
3806
|
-
const css = Object.keys(styles).length ? this.compile(selector, styles) : '';
|
|
3807
|
-
if (css)
|
|
3808
|
-
this.rules.set(key, css);
|
|
3809
|
-
else
|
|
3810
|
-
this.rules.delete(key);
|
|
3811
|
-
this.flush();
|
|
3812
|
-
}
|
|
3813
|
-
/**
|
|
3814
|
-
* Emit (or clear) a node's breakpoint rules. `planResponsiveRules` — shared with the SSR engine —
|
|
3815
|
-
* decides what each breakpoint emits; this only turns it into selectors, through the SAME
|
|
3816
|
-
* inner/outer split and state selectors as the base rules, compiled in the Override context
|
|
3817
|
-
* (a CSS initial value undoes the wider rule there, so it must not be dropped).
|
|
3818
|
-
*
|
|
3819
|
-
* @param styleProps The node's full BASE style map (raw — virtual traits not yet composed).
|
|
3820
|
-
*/
|
|
3821
|
-
upsertResponsive(nodeId, styleProps, stateStyles, responsive) {
|
|
3822
|
-
if (!nodeId)
|
|
3823
|
-
return;
|
|
3824
|
-
const hasOverrides = !!responsive && Object.keys(responsive).length > 0;
|
|
3825
|
-
// The common case — a node with no overrides that never had any — costs nothing.
|
|
3826
|
-
if (!hasOverrides && !this.responsiveInputs.has(nodeId))
|
|
3827
|
-
return;
|
|
3828
|
-
if (hasOverrides)
|
|
3829
|
-
this.responsiveInputs.set(nodeId, { styleProps, stateStyles, responsive: responsive });
|
|
3830
|
-
else
|
|
3831
|
-
this.responsiveInputs.delete(nodeId);
|
|
3832
|
-
this.emitResponsive(nodeId);
|
|
3833
|
-
this.flush();
|
|
3834
|
-
}
|
|
3835
|
-
emitResponsive(nodeId) {
|
|
3836
|
-
this.clearResponsive(nodeId);
|
|
3837
|
-
const input = this.responsiveInputs.get(nodeId);
|
|
3838
|
-
if (!input)
|
|
3839
|
-
return;
|
|
3840
|
-
for (const layer of planResponsiveRules(input.styleProps, input.stateStyles, input.responsive)) {
|
|
3841
|
-
const bp = layer.breakpoint;
|
|
3842
|
-
// Where `width` lands depends on the position in force AT this breakpoint.
|
|
3843
|
-
const outOfFlow = this.isOutOfFlow(resolveStyleProps(input.styleProps, input.responsive, bp));
|
|
3844
|
-
const { main, marker } = this.nodeRuleCss(nodeId, layer.styleProps, outOfFlow, DeclarationContext.Override);
|
|
3845
|
-
if (main)
|
|
3846
|
-
this.setResponsiveRule(nodeId, bp, nodeId, main);
|
|
3847
|
-
if (marker)
|
|
3848
|
-
this.setResponsiveRule(nodeId, bp, `${nodeId}::marker`, marker);
|
|
3849
|
-
for (const [state, map] of Object.entries(layer.stateStyles)) {
|
|
3850
|
-
const scope = this.stateScopes.get(`${nodeId}:${state}`);
|
|
3851
|
-
const selector = this.stateSelector(nodeId, state, scope?.relativeToId, scope?.triggerId);
|
|
3852
|
-
// A relational/targeted state whose scope isn't known yet is skipped; it is re-emitted
|
|
3853
|
-
// when the base state rule arrives with its scope (see upsert).
|
|
3854
|
-
const css = selector ? this.compile(selector, map, DeclarationContext.Override) : '';
|
|
3855
|
-
if (css)
|
|
3856
|
-
this.setResponsiveRule(nodeId, bp, `${nodeId}:${state}`, css);
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
}
|
|
3860
|
-
setResponsiveRule(nodeId, bp, key, css) {
|
|
3861
|
-
let bucket = this.responsiveRules.get(bp);
|
|
3862
|
-
if (!bucket)
|
|
3863
|
-
this.responsiveRules.set(bp, (bucket = new Map()));
|
|
3864
|
-
bucket.set(key, css);
|
|
3865
|
-
let owned = this.responsiveKeys.get(nodeId);
|
|
3866
|
-
if (!owned)
|
|
3867
|
-
this.responsiveKeys.set(nodeId, (owned = []));
|
|
3868
|
-
owned.push([bp, key]);
|
|
3869
|
-
}
|
|
3870
|
-
clearResponsive(nodeId) {
|
|
3871
|
-
for (const [bp, key] of this.responsiveKeys.get(nodeId) ?? [])
|
|
3872
|
-
this.responsiveRules.get(bp)?.delete(key);
|
|
3873
|
-
this.responsiveKeys.delete(nodeId);
|
|
3874
|
-
}
|
|
3875
|
-
isOutOfFlow(styles) {
|
|
3876
|
-
const pos = String(styles['position'] ?? '');
|
|
3877
|
-
return pos === 'fixed' || pos === 'absolute';
|
|
3878
|
-
}
|
|
3879
|
-
/**
|
|
3880
|
-
* The selector BODY (no leading dot) for a state rule, or null when a relational / targeted
|
|
3881
|
-
* state has no scope yet (emitting it unscoped would be wrong — a page-global `:has()`).
|
|
3882
|
-
*/
|
|
3883
|
-
stateSelector(nodeId, state, relativeToId, triggerId) {
|
|
3884
|
-
// Targeted states (trigger-hover): the styled node restyles when an ARBITRARY chosen
|
|
3885
|
-
// element is hovered. The selector is `:has()`-scoped to `relativeToId` (the nearest
|
|
3886
|
-
// common ancestor of styled + trigger) so it stays repetition-safe with one shared rule.
|
|
3887
|
-
if (TARGETED_STATES.includes(state)) {
|
|
3888
|
-
const base = TARGETED_STATE_BASE[state];
|
|
3889
|
-
return relativeToId && triggerId && base
|
|
3890
|
-
? buildTargetedStateSelector(relativeToId, triggerId, nodeId, base) : null;
|
|
3891
|
-
}
|
|
3892
|
-
// Relational states (parent-hover): the pseudo-class lives on an ANCESTOR
|
|
3893
|
-
// and cascades to this element. Requires the ancestor's css id.
|
|
3894
|
-
if (RELATIONAL_STATES.includes(state)) {
|
|
3895
|
-
const base = RELATIONAL_STATE_BASE[state];
|
|
3896
|
-
return relativeToId && base ? `iox-node-${relativeToId}:${base} .iox-node-${nodeId}` : null;
|
|
3897
|
-
}
|
|
3898
|
-
// Validation states (wrong/accepted, board 7) are not real CSS pseudo-classes — `:wrong`
|
|
3899
|
-
// doesn't exist. Compound a CLASS instead, matching the host class IoxFieldComponent
|
|
3900
|
-
// already sets from forceInvalid/showValid. Reuses buildPartSelector with no part, since
|
|
3901
|
-
// "whole node, when wrong" is exactly that function's no-part case.
|
|
3902
|
-
const validationClass = VALIDATION_STATE_CLASS[state];
|
|
3903
|
-
if (validationClass)
|
|
3904
|
-
return buildPartSelector(nodeId, undefined, validationClass);
|
|
3905
|
-
// Structural pseudo-classes (first-child, last-child, nth-child) target the
|
|
3906
|
-
// outer wrapper element so they participate correctly in grid/flex layout.
|
|
3907
|
-
const prefix = STRUCTURAL_STATES.includes(state) ? 'iox-outer' : 'iox-node';
|
|
3908
|
-
return `${prefix}-${nodeId}:${state}`;
|
|
3909
|
-
}
|
|
3910
|
-
/**
|
|
3911
|
-
* The inner + outer rule (joined) and the `::marker` rule for one composed style map.
|
|
3912
|
-
* `isOutOfFlow` is passed in rather than read from `styles` because a breakpoint layer is
|
|
3913
|
-
* sparse — the position in force may come from a wider layer.
|
|
3914
|
-
*/
|
|
3915
|
-
nodeRuleCss(nodeId, styles, isOutOfFlow, context) {
|
|
3916
|
-
// ::marker pseudo-element — emitted as a separate rule, not part of inner/outer split.
|
|
3917
|
-
const markerStyles = styles['__markerStyles'];
|
|
3918
|
-
const marker = markerStyles && Object.keys(markerStyles).length
|
|
3919
|
-
? this.compile(`iox-node-${nodeId}::marker`, markerStyles, context) : '';
|
|
3920
|
-
const inner = {};
|
|
3921
|
-
const outer = {};
|
|
3922
|
-
for (const [k, v] of Object.entries(styles)) {
|
|
3923
|
-
if (k === '__markerStyles')
|
|
3924
|
-
continue;
|
|
3925
|
-
const wantsOuter = StyleRegistryService.OUTER_PROPS.has(k);
|
|
3926
|
-
// width goes to inner for fixed/absolute (the positioned element owns its own width)
|
|
3927
|
-
if (wantsOuter && !(isOutOfFlow && k === 'width')) {
|
|
3928
|
-
outer[k] = v;
|
|
3929
|
-
}
|
|
3930
|
-
else {
|
|
3931
|
-
inner[k] = v;
|
|
3932
|
-
}
|
|
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(() => { });
|
|
3933
3892
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
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(() => { });
|
|
3938
3899
|
}
|
|
3939
|
-
const innerCss = this.compile(`iox-node-${nodeId}`, inner, context);
|
|
3940
|
-
const outerCss = this.compile(`iox-outer-${nodeId}`, outer, context);
|
|
3941
|
-
return { main: [innerCss, outerCss].filter(Boolean).join('\n'), marker };
|
|
3942
3900
|
}
|
|
3943
3901
|
/**
|
|
3944
|
-
*
|
|
3945
|
-
*
|
|
3946
|
-
*
|
|
3947
|
-
*
|
|
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.
|
|
3948
3910
|
*/
|
|
3949
|
-
|
|
3950
|
-
const
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
// LAST rule — one ordering convention for every @media block (responsive A3 lesson).
|
|
3961
|
-
const darkEntries = Object.entries(darkTokens ?? {})
|
|
3962
|
-
.filter(([, v]) => v !== undefined && v !== null && v !== '')
|
|
3963
|
-
.map(([k, v]) => ` --${k}: ${v};`);
|
|
3964
|
-
if (darkEntries.length) {
|
|
3965
|
-
this.rules.set('__tokens_dark__', `@media (prefers-color-scheme: dark) {\n :root {\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');
|
|
3966
3922
|
}
|
|
3967
3923
|
else {
|
|
3968
|
-
|
|
3924
|
+
for (const [prop, val] of Object.entries(first)) {
|
|
3925
|
+
element.style[prop] = String(val);
|
|
3926
|
+
}
|
|
3927
|
+
element.style.setProperty('pointer-events', 'none');
|
|
3969
3928
|
}
|
|
3970
|
-
|
|
3929
|
+
element.setAttribute('data-iox-hidden', '');
|
|
3930
|
+
this.preStatedElements.add(element);
|
|
3971
3931
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
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)
|
|
3974
3942
|
return;
|
|
3975
|
-
//
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
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;
|
|
3985
3992
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3993
|
+
return {
|
|
3994
|
+
keyframes,
|
|
3995
|
+
options: {
|
|
3996
|
+
duration: action.duration,
|
|
3997
|
+
delay: action.delay,
|
|
3998
|
+
easing: action.easing,
|
|
3999
|
+
fill: 'forwards',
|
|
4000
|
+
},
|
|
4001
|
+
};
|
|
3989
4002
|
}
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
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);
|
|
3999
4024
|
}
|
|
4000
4025
|
}
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
// the shared style-compiler so the SSR engine produces byte-identical output.
|
|
4004
|
-
// The inner/outer partition + selector emission stay here (builder-only concern).
|
|
4005
|
-
const decls = compileDeclarations(styles, StyleRegistryService.IMPORTANT_PROPS, context);
|
|
4006
|
-
if (!decls.length)
|
|
4007
|
-
return '';
|
|
4008
|
-
const body = decls.map(d => ` ${d};`).join('\n');
|
|
4009
|
-
return `.${className} {\n${body}\n}`;
|
|
4026
|
+
getPreset(id) {
|
|
4027
|
+
return this.presets.get(id);
|
|
4010
4028
|
}
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
return;
|
|
4014
|
-
this._flushPending = true;
|
|
4015
|
-
requestAnimationFrame(() => {
|
|
4016
|
-
this._flushPending = false;
|
|
4017
|
-
if (!this.styleEl)
|
|
4018
|
-
return;
|
|
4019
|
-
// __tokens__ always first so variables are available to all subsequent rules
|
|
4020
|
-
const tokenBlock = this.rules.get('__tokens__');
|
|
4021
|
-
const rest = Array.from(this.rules.entries())
|
|
4022
|
-
.filter(([k]) => k !== '__tokens__')
|
|
4023
|
-
.map(([, v]) => v);
|
|
4024
|
-
// Breakpoint rules LAST, widest → narrowest, each as a canvas container query.
|
|
4025
|
-
const breakpoints = BREAKPOINTS
|
|
4026
|
-
.map(({ id }) => this.responsiveRules.get(id))
|
|
4027
|
-
.map((bucket, i) => bucket?.size
|
|
4028
|
-
? wrapInBreakpointQuery(BREAKPOINTS[i].id, Array.from(bucket.values()).join('\n'), BreakpointQueryKind.Container, BUILDER_CANVAS_CONTAINER)
|
|
4029
|
-
: '');
|
|
4030
|
-
this.styleEl.textContent = [tokenBlock, ...rest, ...breakpoints].filter(Boolean).join('\n');
|
|
4031
|
-
this.changes$.next();
|
|
4032
|
-
});
|
|
4029
|
+
getAll() {
|
|
4030
|
+
return Array.from(this.presets.values());
|
|
4033
4031
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
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 }); }
|
|
4036
4040
|
}
|
|
4037
|
-
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: [{
|
|
4038
4073
|
type: Injectable
|
|
4039
4074
|
}] });
|
|
4040
4075
|
|
|
@@ -4466,6 +4501,11 @@ const DIRECTIVE_HOST_TYPES = new Set([
|
|
|
4466
4501
|
'Image', 'Icon', 'Divider', 'Spacer', 'Button', 'Link', 'Heading', 'Text', 'ButtonBlock',
|
|
4467
4502
|
// Phase 2 (9c): layout components converted to directive-hosts (host IS the real tag).
|
|
4468
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',
|
|
4469
4509
|
]);
|
|
4470
4510
|
const CONTAINER_TAGS = ['div', 'section', 'article', 'header', 'footer', 'main', 'nav', 'ul', 'ol', 'li'];
|
|
4471
4511
|
/**
|
|
@@ -4670,8 +4710,10 @@ class RenderDirective {
|
|
|
4670
4710
|
}
|
|
4671
4711
|
// Use styleId when present (clone nodes share the template node's CSS rule).
|
|
4672
4712
|
const cssId = node.styleId ?? node.id;
|
|
4673
|
-
// 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.
|
|
4674
4715
|
if (cssId) {
|
|
4716
|
+
this.styleRegistry.setNodeType(cssId, node.type);
|
|
4675
4717
|
const composedBase = composeVirtualTraits(initialStyle);
|
|
4676
4718
|
this.styleRegistry.upsert(cssId, composedBase);
|
|
4677
4719
|
// Restore per-state overrides saved from a previous session.
|
|
@@ -4771,6 +4813,10 @@ class RenderDirective {
|
|
|
4771
4813
|
// For layout components the host element is <app-section>; for leaf components
|
|
4772
4814
|
// it is e.g. <app-card>. Either way this is the correct inspectable element.
|
|
4773
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);
|
|
4774
4820
|
// Custom element host defaults to display:inline. Set it to block so it participates
|
|
4775
4821
|
// properly in layout and CDK can capture its bounding rect. SKIP for directive-hosts: the
|
|
4776
4822
|
// host is a real element with its own display, and an inline style here out-ranks both
|
|
@@ -7429,11 +7475,11 @@ class BuilderComponent {
|
|
|
7429
7475
|
}
|
|
7430
7476
|
}
|
|
7431
7477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: PanelEventService }, { token: DragEngineService }, { token: DataSourceRegistryService }, { token: ViewportService }, { token: FormStateService }, { token: InteractionEngineService }, { token: InteractionPresetRegistryService }, { token: BuilderClipboardService }, { token: SymbolRegistryService }, { token: i0.ChangeDetectorRef }, { token: i0.ApplicationRef }, { token: MoveableCanvasService }, { token: SelectoCanvasService }, { token: StyleRegistryService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7432
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i16.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i16.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i16.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i17.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
7478
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.31", type: BuilderComponent, isStandalone: false, selector: "app-builder", inputs: { layout: "layout", dataSources: "dataSources", orgId: "orgId", routeParams: "routeParams", pageName: "pageName", isSaving: "isSaving", pageStatus: "pageStatus", pageSettings: "pageSettings", globalElements: "globalElements", canUndo: "canUndo", canRedo: "canRedo", moveableMode: "moveableMode" }, outputs: { save: "save", undo: "undo", redo: "redo", back: "back", preview: "preview", publishToggle: "publishToggle", pageSettingsChange: "pageSettingsChange", applyAnimationToAll: "applyAnimationToAll", saveBlock: "saveBlock", globalElementsChange: "globalElementsChange" }, viewQueries: [{ propertyName: "previewCanvas", first: true, predicate: ["previewCanvas"], descendants: true }, { propertyName: "previewScrollRef", first: true, predicate: ["previewScroll"], descendants: true }, { propertyName: "lenisContentRef", first: true, predicate: ["lenisContent"], descendants: true }, { propertyName: "globalOverlayRef", first: true, predicate: ["globalOverlay"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i16.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i16.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i16.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i17.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i1.IoxPageComponent, selector: "iox-page-component", inputs: ["class"], outputs: ["scrollEvent"] }, { kind: "directive", type: RenderDirective, selector: "[ioxRender]", inputs: ["ioxRender", "ioxDragSourceId"], outputs: ["onClick", "onMouseEnter", "onMouseLeave"] }, { kind: "component", type: OverlayComponent, selector: "app-overlay", outputs: ["toolbarAction"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["activeMode", "activeBreakpoint", "breakpointCounts", "auditCount", "auditOpen", "activeZoom", "activeScreenWidth", "canUndo", "canRedo", "isSaving", "pageStatus"], outputs: ["modeChange", "breakpointChange", "auditToggle", "zoomChange", "screenWidthChange", "undo", "redo", "save", "preview", "publishToggle"] }, { kind: "component", type: LayerTreeComponent, selector: "app-layer-tree", inputs: ["layout", "globalElements"], outputs: ["nodeSelect", "nodeAction", "nodeMove"] }, { kind: "component", type: BreadcrumbBarComponent, selector: "app-breadcrumb-bar", inputs: ["path"], outputs: ["nodeSelect", "rootSelect", "nodePeek"] }, { kind: "component", type: AuditPanelComponent, selector: "app-audit-panel", inputs: ["findings", "breakpointLabel", "running"], outputs: ["select", "fix", "fixAll", "rerun", "closePanel"] }] }); }
|
|
7433
7479
|
}
|
|
7434
7480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderComponent, decorators: [{
|
|
7435
7481
|
type: Component,
|
|
7436
|
-
args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <iox-page-component\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
7482
|
+
args: [{ selector: 'app-builder', standalone: false, template: "<div class=\"builder\">\n\n <div class=\"builder-main\" [class.panel-open]=\"isPanelOpen\">\n <!-- The switcher is the panel's OWN tab bar. It used to live in the header, at the\n maximum possible distance from the thing it controls; attached, the column and the\n panel are one card with one shadow. See board D2. -->\n <aside class=\"sidebar\">\n <div class=\"sidebar-switcher\">\n <button (click)=\"activeSidebar = 'pages'\" [class.active]=\"activeSidebar === 'pages'\" title=\"Pages\" aria-label=\"Pages\">\n <i class=\"ph-thin ph-browsers\"></i>\n </button>\n <button (click)=\"activeSidebar = 'components'\" [class.active]=\"activeSidebar === 'components'\" title=\"Components\" aria-label=\"Components\">\n <i class=\"ph-thin ph-cube\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tree'\" [class.active]=\"activeSidebar === 'tree'\" title=\"Layers\" aria-label=\"Layers\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n </button>\n <button (click)=\"activeSidebar = 'tokens'\" [class.active]=\"activeSidebar === 'tokens'\" title=\"Design Tokens\" aria-label=\"Design Tokens\">\n <i class=\"ph-thin ph-circles-three\"></i>\n </button>\n </div>\n <div class=\"sidebar-body\">\n <div *ngIf=\"activeSidebar === 'pages'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderPages]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'components'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderComponents]\"></ng-content>\n </div>\n <div *ngIf=\"activeSidebar === 'tokens'\" class=\"sidebar-slot\">\n <ng-content select=\"[builderTokens]\"></ng-content>\n </div>\n <app-layer-tree *ngIf=\"activeSidebar === 'tree'\"\n [layout]=\"layout\"\n [globalElements]=\"globalElements\"\n (nodeSelect)=\"onTreeNodeSelect($event)\"\n (nodeAction)=\"onTreeNodeAction($event)\"\n (nodeMove)=\"onTreeNodeMove()\"></app-layer-tree>\n </div>\n </aside>\n\n <!-- \u26A0\uFE0F The strips are SIBLINGS of .preview, not children of it. Board D2 floated them\n over the canvas and that is what they did \u2014 over the page being edited, which is the\n one thing in the builder that must stay clear. They keep their pill shape and their\n centre alignment; they just take their own row.\n\n Siblings, specifically, rather than a wrapper INSIDE .preview: `previewCanvas` is the\n overlay container and the coordinate origin for the selection box, the drag clone,\n Lenis and the zoom maths (nine call sites). Nesting anything between it and the canvas\n would shift every one of them by the strip's height. -->\n <div class=\"canvas-column\">\n <!-- \u2460 THE ACTION STRIP \u2014 floating, top-centre (board D2). It was a solid\n full-width header bar costing 56px of height on every screen; the canvas now\n starts at the top of the viewport and the strip hovers over it.\n stopPropagation for the same reason the breadcrumb does: .preview's (click)\n is \"you clicked empty canvas, drop the selection\". -->\n <div class=\"toolbar-strip\" (click)=\"$event.stopPropagation()\">\n <app-toolbar\n [activeMode]=\"activeMode\"\n [activeBreakpoint]=\"activeBreakpoint\"\n [breakpointCounts]=\"breakpointCounts\"\n [auditCount]=\"auditFindings.length\"\n [auditOpen]=\"auditOpen\"\n [activeZoom]=\"activeZoom\"\n [activeScreenWidth]=\"activeScreenWidth\"\n [isSaving]=\"isSaving\"\n [pageStatus]=\"pageStatus\"\n [canUndo]=\"canUndo\"\n [canRedo]=\"canRedo\"\n (modeChange)=\"handleToolbarModeChange($event)\"\n (breakpointChange)=\"handleToolbarBreakpointChange($event)\"\n (auditToggle)=\"toggleAudit()\"\n (zoomChange)=\"handleToolbarZoomChange($event)\"\n (screenWidthChange)=\"handleToolbarScreenWidthChange($event)\"\n (save)=\"save.emit()\"\n (preview)=\"preview.emit()\"\n (publishToggle)=\"publishToggle.emit()\"\n (undo)=\"undo.emit()\"\n (redo)=\"redo.emit()\">\n <ng-content select=\"[builderToolbarActions]\"></ng-content>\n </app-toolbar>\n </div>\n\n <main #previewCanvas class=\"preview\"\n [class.grab-mode]=\"activeMode === builderModes.Pan\"\n [class.grabbing]=\"isPanning\"\n (click)=\"handleCanvasClick()\"\n (mousedown)=\"onBoardMouseDown($event)\"\n (mousemove)=\"onBoardMouseMove($event)\"\n (mouseup)=\"onBoardMouseUp()\"\n (mouseleave)=\"onBoardMouseUp()\">\n <app-overlay (toolbarAction)=\"onToolbarAction($event)\"></app-overlay>\n\n <!-- Responsive audit (A5) \u2014 floats over the canvas, right gutter. -->\n @if (auditOpen) {\n <div class=\"audit-dock\" (click)=\"$event.stopPropagation()\">\n <app-audit-panel\n [findings]=\"auditFindings\"\n [breakpointLabel]=\"auditBreakpointLabel\"\n [running]=\"auditRunning\"\n (select)=\"selectAuditNode($event)\"\n (fix)=\"applyAuditFix($event)\"\n (fixAll)=\"applyAllAuditFixes()\"\n (rerun)=\"runAudit()\"\n (closePanel)=\"toggleAudit()\"></app-audit-panel>\n </div>\n }\n\n <!-- Custom Lenis scrollbar \u2014 lives in .preview (not .preview-scroll) so it\n stays in the right gutter and never overlaps the canvas at large widths.\n No translateY counteract needed since it isn't inside the Lenis wrapper. -->\n <div class=\"canvas-scrollbar\"\n [class.visible]=\"isScrollbarVisible\">\n <div class=\"canvas-scrollbar-thumb\"\n [style.height.px]=\"scrollThumbHeight\"\n [style.top.px]=\"scrollThumbTop\">\n </div>\n </div>\n\n <!-- Global elements overlay \u2014 outside Lenis so position:fixed children\n stay pinned to the canvas viewport rather than scrolling with content.\n Shares viewportTransform so elements scale correctly at any zoom level.\n Height matches viewportHeight so position:fixed + inset:0 fills the\n simulated viewport correctly (CSS calc(100%) would give the wrong\n containing-block height and break full-screen fixed overlays). -->\n <div *ngIf=\"globalElements.length\"\n #globalOverlay\n class=\"global-elements-overlay iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n <ng-container *ngFor=\"let item of globalElementsBefore\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n <ng-container *ngFor=\"let item of globalElementsAfter\">\n <ng-container [ioxRender]=\"item\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </ng-container>\n </div>\n\n <div class=\"preview-scroll\"\n #previewScroll\n [style.width.px]=\"viewportScaledWidth\"\n [class.pan-mode]=\"activeMode === builderModes.Pan\">\n\n <!-- Lenis content proxy: height = realContentHeight \u00D7 scale.\n Lenis scrolls against this value so scroll range is always in\n visual px \u2014 correct at any zoom level. -->\n <div class=\"preview-lenis-content\" #lenisContent\n [style.height.px]=\"scaledContentHeight\">\n\n <!-- iox-token-scope: the ORG's design tokens are declared on this class, not :root\n (StyleRegistryService.upsertTokens), so they never repaint the admin chrome. -->\n <iox-page-component class=\"iox-token-scope\"\n [style.width.px]=\"viewportWidth\"\n [style.height.px]=\"canvasFrameHeight || viewportHeight\"\n [style.transform]=\"viewportTransform\">\n\n <!-- ngStyle carries the page's own background/typography (pageSettings.style),\n compiled by the SAME function the engine applies to <body>. Inline wins over\n the #canvas-preview white default, which stays as the unset fallback. -->\n <div class=\"canvas-viewport\"\n id=\"canvas-preview\"\n [ngStyle]=\"pageStyleCss\"\n [style.min-height.px]=\"canvasMinHeight\">\n\n <!-- Empty page (B4): a starting point beats a blank canvas. Each starter is\n a responsive layout preset \u2014 ordinary nodes, editable afterwards. -->\n @if (isPageEmpty) {\n <div class=\"empty-canvas\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"empty-title\">Start with a layout</h2>\n <p class=\"empty-sub\">Each one already adapts to tablet and phone. You can edit or take it apart afterwards.</p>\n <div class=\"empty-grid\">\n @for (starter of starterLayouts; track starter.id) {\n <button type=\"button\" class=\"empty-card\" (click)=\"startWithLayout(starter)\">\n <i [class]=\"starter.icon\" aria-hidden=\"true\"></i>\n <span class=\"empty-card-name\">{{ starter.name }}</span>\n <span class=\"empty-card-desc\">{{ starter.description }}</span>\n </button>\n }\n </div>\n <button type=\"button\" class=\"empty-link\" (click)=\"browseComponents()\">Or start from a single element</button>\n </div>\n }\n\n <!-- WRAPPER COLLAPSE (task 9c): top-level nodes render DIRECTLY into the\n canvas viewport \u2014 no .preview-node wrapper; iox-outer + drag move to the\n host (RenderDirective, via ioxDragSourceId). -->\n <ng-container *ngFor=\"let item of layout\"\n [ioxRender]=\"item\"\n [ioxDragSourceId]=\"'canvas-preview'\"\n (onClick)=\"handleClick($event)\"\n (onMouseEnter)=\"handleMouseEnter($event)\"\n (onMouseLeave)=\"handleMouseLeave()\">\n </ng-container>\n </div>\n </iox-page-component>\n </div>\n </div>\n </main>\n <!-- \u2462 THE ELEMENT PATH \u2014 a thin floating strip, BOTTOM-CENTRE (board D2). It is\n pinned to the PREVIEW VIEWPORT, not to the canvas frame: the frame pans and\n scales, so a breadcrumb tracking its edge would drift off-screen and shrink with\n zoom. It used to sit in the top gutter, which is where the eye is least often \u2014\n and where the floating action strip now lives.\n\n \u26A0\uFE0F stopPropagation is REQUIRED, not defensive. This element lives inside .preview,\n whose (click) is handleCanvasClick() \u2014 \"you clicked empty canvas, drop the\n selection\". Without it, picking an ancestor crumb selects that node and then\n immediately bubbles into the canvas handler, which clears it: the selection and the\n layer-tree highlight both fall back to Body. Same reason .audit-dock stops its own. -->\n <app-breadcrumb-bar\n (click)=\"$event.stopPropagation()\"\n [path]=\"selectedPath\"\n (nodeSelect)=\"onCrumbSelect($event)\"\n (rootSelect)=\"onCrumbRootSelect()\"\n (nodePeek)=\"onCrumbPeek($event)\">\n </app-breadcrumb-bar>\n\n </div>\n\n <!-- Same rule as the sidebar: the switcher belongs to the panel it drives, as a\n tab ROW across its top. -->\n <div class=\"panel-dock\">\n <div class=\"panel-switcher\">\n <button (click)=\"selectPanel(panelTypes.BINDINGS)\" [class.active]=\"activePanel === panelTypes.BINDINGS\" title=\"Bindings\">\n <i class=\"ph-thin ph-plugs-connected\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.STYLES)\" [class.active]=\"activePanel === panelTypes.STYLES\" title=\"Styles\">\n <i class=\"ph-thin ph-paint-brush\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.PAGE)\" [class.active]=\"activePanel === panelTypes.PAGE\" title=\"Page settings\" aria-label=\"Page settings\">\n <i class=\"ph-thin ph-gear-six\"></i>\n </button>\n <button (click)=\"selectPanel(panelTypes.INTERACTIONS)\" [class.active]=\"activePanel === panelTypes.INTERACTIONS\" title=\"Interactions\">\n <i class=\"ph-thin ph-lightning\"></i>\n </button>\n </div>\n <ng-content select=\"[builderPanel]\"></ng-content>\n </div>\n </div>\n\n <!-- Save as Reusable Block dialog -->\n @if (showSaveBlockDialog) {\n <div class=\"save-block-backdrop\" (click)=\"cancelSaveBlock()\"></div>\n <div class=\"save-block-dialog\" (click)=\"$event.stopPropagation()\">\n <h4 class=\"save-block-title\">Save as Reusable Block</h4>\n <input class=\"save-block-input\"\n type=\"text\"\n placeholder=\"Block name...\"\n [(ngModel)]=\"saveBlockName\"\n (keydown.enter)=\"confirmSaveBlock()\"\n (keydown.escape)=\"cancelSaveBlock()\"\n autofocus>\n <div class=\"save-block-actions\">\n <p-button size=\"small\" severity=\"secondary\" label=\"Cancel\" (click)=\"cancelSaveBlock()\"></p-button>\n <p-button size=\"small\" label=\"Save\" [disabled]=\"!saveBlockName.trim()\" (click)=\"confirmSaveBlock()\"></p-button>\n </div>\n </div>\n }\n</div>", styles: ["@charset \"UTF-8\";:host{display:flex;flex:1;min-height:0;overflow:hidden}.builder{display:flex;flex-direction:column;direction:ltr;width:100%;height:100%;min-height:0;border-radius:0;overflow:hidden;position:relative}.builder .toolbar-strip{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:6px 8px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 40px);overflow-x:auto;scrollbar-width:none}.builder .toolbar-strip::-webkit-scrollbar{display:none}.builder .sidebar-switcher,.builder .panel-switcher{flex:none;display:flex;flex-direction:row;align-items:center;gap:.375rem;padding:7px 10px;background:var(--p-surface-0);border-block-end:1px solid var(--iox-neutral-100, #EDEFF3)}.builder .sidebar-switcher button,.builder .panel-switcher button{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--iox-surface-3, #F6F7F9);color:var(--p-text-muted-color);cursor:pointer;font-size:1rem;transition:background .15s ease-out,color .15s ease-out}.builder .sidebar-switcher button i,.builder .panel-switcher button i{pointer-events:none}.builder .sidebar-switcher button:hover,.builder .panel-switcher button:hover{background:var(--iox-rose-100, #F8EAEA);color:var(--p-text-color)}.builder .sidebar-switcher button.active,.builder .panel-switcher button.active{background:var(--iox-grad-brand, linear-gradient(135deg, #9B4458 0%, #8A3A72 50%, #63347F 100%));color:#fff}.builder .panel-dock{display:grid;grid-template-rows:auto minmax(0,1fr);flex:none;min-height:0;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden}.builder .builder-main{display:flex;flex:1 1 0;min-height:0;position:relative;overflow:hidden;gap:12px;padding:14px;background:var(--iox-surface-2, #EDEFF3)}.builder .sidebar{display:flex;flex-direction:column;inline-size:260px;min-inline-size:260px;background:var(--p-surface-0);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:var(--iox-radius-card, 20px);box-shadow:var(--iox-elev-1, 0 0 12px rgba(16, 27, 66, .05));overflow:hidden;flex-shrink:0;min-block-size:0}.builder .sidebar .sidebar-body{flex:1;min-inline-size:0;overflow-y:auto;overflow-x:hidden}.builder .canvas-column{flex:1 1 0;min-width:0;min-height:0;display:flex;flex-direction:column;gap:10px}.builder .preview{flex:1 1 0;min-width:0;min-height:0;position:relative;overflow:hidden;background:transparent}.builder .preview.grab-mode{cursor:grab}.builder .preview.grab-mode iox-page-component{pointer-events:none}.builder .preview.grabbing{cursor:grabbing}.builder .canvas-scrollbar{position:absolute;right:4px;top:20px;bottom:20px;width:6px;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none}.builder .canvas-scrollbar.visible{opacity:1}.builder .canvas-scrollbar-thumb{position:absolute;left:0;width:100%;background:#00000059;border-radius:3px;transition:top .05s linear}.builder .preview-scroll{position:absolute;inset:20px auto;inset-inline-start:50%;transform:translate(-50%);max-width:calc(100% - 40px);height:calc(100% - 40px);overflow:hidden}.builder .preview-lenis-content{position:relative;width:100%;overflow:hidden;contain:paint}.builder iox-page-component{position:absolute!important;top:0;bottom:auto!important;right:auto!important;left:50%;transform-origin:top center;overflow:visible!important;container:iox-canvas/inline-size;box-shadow:0 1px 4px #00000014,0 4px 16px #0000000a;transition:width .25s ease,transform .25s ease}.builder iox-page-component ::ng-deep .iox-page-wrapper{height:auto!important;overflow:visible!important}.builder #canvas-preview{background:#fff}.builder .empty-canvas{max-inline-size:560px;margin:0 auto;padding:48px 24px;text-align:center;color:#4b5563}.builder .empty-title{margin:0;font-size:18px;font-weight:600;color:#111827}.builder .empty-sub{margin:6px 0 20px;font-size:12px;line-height:1.5;color:#6b7280}.builder .empty-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.builder .empty-card{display:flex;flex-direction:column;align-items:center;gap:4px;padding:14px 10px;border:1px solid #e5e7eb;border-radius:10px;background:#fff;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease}.builder .empty-card:hover{border-color:#cb9090;box-shadow:0 2px 10px #0000000f}.builder .empty-card:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder .empty-card i{font-size:20px;color:#a05c5c}.builder .empty-card-name{font-size:11.5px;font-weight:600;color:#111827}.builder .empty-card-desc{font-size:9.5px;line-height:1.35;color:#6b7280}.builder .empty-link{margin-block-start:16px;padding:0;border:none;background:none;font-size:11px;color:#a05c5c;cursor:pointer}.builder .empty-link:hover{text-decoration:underline}.builder .empty-link:focus-visible{outline:2px solid #cb9090;outline-offset:2px}.builder app-breadcrumb-bar{flex:none;align-self:center;max-inline-size:100%;display:flex;align-items:center;padding:5px 13px;background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid var(--iox-border-1, #DDE1E8);border-radius:999px;box-shadow:0 6px 20px #101b421a;max-inline-size:calc(100% - 112px);pointer-events:none}.builder .audit-dock{position:absolute;inset-block-start:20px;inset-inline-end:16px;z-index:20}.builder .global-elements-overlay{position:absolute;top:20px;left:50%;transform-origin:top center;pointer-events:none;z-index:9;overflow:hidden;container:iox-canvas/inline-size}.builder .global-elements-overlay ::ng-deep>*{pointer-events:auto}.builder .preview-node.is-selected{cursor:grab}.builder .empty-state{min-height:240px;border:1px dashed var(--p-surface-200);border-radius:10px;display:flex;align-items:center;justify-content:center;color:var(--p-text-muted-color);text-align:center;padding:1rem}.save-block-backdrop{position:fixed;inset:0;z-index:1000;background:#00000040}.save-block-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1001;background:#fff;border-radius:8px;box-shadow:0 8px 32px #0000002e;padding:24px;width:320px}.save-block-dialog .save-block-title{margin:0 0 16px;font-size:14px;font-weight:600;color:var(--p-text-color, #333)}.save-block-dialog .save-block-input{width:100%;padding:8px 10px;border:1px solid var(--p-surface-300, #d1d5db);border-radius:6px;font-size:13px;outline:none;box-sizing:border-box;margin-bottom:16px}.save-block-dialog .save-block-input:focus{border-color:#cb9090;box-shadow:0 0 0 2px #cb909033}.save-block-dialog .save-block-actions{display:flex;justify-content:flex-end;gap:8px}::ng-deep .moveable-control-box.iox-moveable-silent{z-index:1000!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-line,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation,::ng-deep .moveable-control-box.iox-moveable-silent .moveable-rotation-line{display:none!important}::ng-deep .moveable-control-box.iox-moveable-silent .moveable-control{display:block!important;width:10px!important;height:10px!important;margin-top:-5px!important;margin-inline-start:-5px!important;background:#fff!important;border:1.5px solid #cb9090!important;border-radius:2px!important;box-shadow:0 1px 3px #0003!important}\n"] }]
|
|
7437
7483
|
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: PanelEventService }, { type: DragEngineService }, { type: DataSourceRegistryService }, { type: ViewportService }, { type: FormStateService }, { type: InteractionEngineService }, { type: InteractionPresetRegistryService }, { type: BuilderClipboardService }, { type: SymbolRegistryService }, { type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: MoveableCanvasService }, { type: SelectoCanvasService }, { type: StyleRegistryService }], propDecorators: { layout: [{
|
|
7438
7484
|
type: Input
|
|
7439
7485
|
}], dataSources: [{
|
|
@@ -7796,7 +7842,6 @@ class BuilderLinkedContainerComponent {
|
|
|
7796
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: `
|
|
7797
7843
|
<a [href]="resolvedHref"
|
|
7798
7844
|
[target]="target"
|
|
7799
|
-
[ngStyle]="style"
|
|
7800
7845
|
[ngClass]="['iox-node-' + nodeId, listOrientation === 'horizontal' ? 'is-horizontal' : '']"
|
|
7801
7846
|
class="linked-container-root"
|
|
7802
7847
|
ioxDropzone
|
|
@@ -7819,14 +7864,13 @@ class BuilderLinkedContainerComponent {
|
|
|
7819
7864
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
7820
7865
|
</ng-container>
|
|
7821
7866
|
</a>
|
|
7822
|
-
`, 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"] }] }); }
|
|
7823
7868
|
}
|
|
7824
7869
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderLinkedContainerComponent, decorators: [{
|
|
7825
7870
|
type: Component,
|
|
7826
7871
|
args: [{ selector: 'app-builder-linked-container', template: `
|
|
7827
7872
|
<a [href]="resolvedHref"
|
|
7828
7873
|
[target]="target"
|
|
7829
|
-
[ngStyle]="style"
|
|
7830
7874
|
[ngClass]="['iox-node-' + nodeId, listOrientation === 'horizontal' ? 'is-horizontal' : '']"
|
|
7831
7875
|
class="linked-container-root"
|
|
7832
7876
|
ioxDropzone
|
|
@@ -7849,7 +7893,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
7849
7893
|
(onMouseLeave)="childMouseLeave.emit()">
|
|
7850
7894
|
</ng-container>
|
|
7851
7895
|
</a>
|
|
7852
|
-
`, 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"] }]
|
|
7853
7897
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: undefined, decorators: [{
|
|
7854
7898
|
type: Optional
|
|
7855
7899
|
}, {
|
|
@@ -8518,7 +8562,6 @@ class BuilderDialogComponent {
|
|
|
8518
8562
|
this.overlayService = overlayService;
|
|
8519
8563
|
this.cdr = cdr;
|
|
8520
8564
|
this.children = [];
|
|
8521
|
-
this.style = {};
|
|
8522
8565
|
this.nodeId = '';
|
|
8523
8566
|
this.dropListId = '';
|
|
8524
8567
|
this.closeOnBackdrop = true;
|
|
@@ -8589,11 +8632,10 @@ class BuilderDialogComponent {
|
|
|
8589
8632
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
8590
8633
|
}
|
|
8591
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 }); }
|
|
8592
|
-
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: `
|
|
8593
8636
|
<div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
|
|
8594
8637
|
(click)="onStageClick($event)">
|
|
8595
|
-
<div [
|
|
8596
|
-
[ngClass]="'iox-node-' + nodeId"
|
|
8638
|
+
<div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
|
|
8597
8639
|
class="dialog-surface"
|
|
8598
8640
|
ioxDropzone
|
|
8599
8641
|
[ioxDropzoneId]="dropListId"
|
|
@@ -8622,15 +8664,14 @@ class BuilderDialogComponent {
|
|
|
8622
8664
|
</ng-container>
|
|
8623
8665
|
</div>
|
|
8624
8666
|
</div>
|
|
8625
|
-
`, 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}.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}.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}.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:
|
|
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"] }] }); }
|
|
8626
8668
|
}
|
|
8627
8669
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderDialogComponent, decorators: [{
|
|
8628
8670
|
type: Component,
|
|
8629
8671
|
args: [{ selector: 'app-builder-dialog', template: `
|
|
8630
8672
|
<div *ngIf="designOpen" class="dialog-stage" [attr.data-placement]="placementValue"
|
|
8631
8673
|
(click)="onStageClick($event)">
|
|
8632
|
-
<div [
|
|
8633
|
-
[ngClass]="'iox-node-' + nodeId"
|
|
8674
|
+
<div [ngClass]="['iox-node-' + nodeId, 'iox-outer-' + nodeId]"
|
|
8634
8675
|
class="dialog-surface"
|
|
8635
8676
|
ioxDropzone
|
|
8636
8677
|
[ioxDropzoneId]="dropListId"
|
|
@@ -8659,11 +8700,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
8659
8700
|
</ng-container>
|
|
8660
8701
|
</div>
|
|
8661
8702
|
</div>
|
|
8662
|
-
`, 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}.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}.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}.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"] }]
|
|
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"] }]
|
|
8663
8704
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i1.ComponentInstanceRegistryService }, { type: OverlayService }, { type: i0.ChangeDetectorRef }], propDecorators: { children: [{
|
|
8664
8705
|
type: Input
|
|
8665
|
-
}], style: [{
|
|
8666
|
-
type: Input
|
|
8667
8706
|
}], nodeId: [{
|
|
8668
8707
|
type: Input
|
|
8669
8708
|
}], dropListId: [{
|
|
@@ -9482,24 +9521,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9482
9521
|
}] } });
|
|
9483
9522
|
|
|
9484
9523
|
class SliderSlideComponent {
|
|
9485
|
-
set style(v) {
|
|
9486
|
-
if (v === this._style)
|
|
9487
|
-
return;
|
|
9488
|
-
this._style = v;
|
|
9489
|
-
// Strip width constraints; rebuild cached object so ngStyle only updates DOM
|
|
9490
|
-
// when the input actually changed — prevents spurious MutationObserver triggers.
|
|
9491
|
-
const { width, minWidth, maxWidth, ...rest } = v || {};
|
|
9492
|
-
this._slideEditStyle = rest;
|
|
9493
|
-
}
|
|
9494
|
-
get style() { return this._style; }
|
|
9495
|
-
get slideEditStyle() { return this._slideEditStyle; }
|
|
9496
9524
|
constructor(dragEngine, cdr, isPreview) {
|
|
9497
9525
|
this.dragEngine = dragEngine;
|
|
9498
9526
|
this.cdr = cdr;
|
|
9499
9527
|
this.isPreview = isPreview;
|
|
9500
9528
|
this.children = [];
|
|
9501
|
-
this._style = {};
|
|
9502
|
-
this._slideEditStyle = {};
|
|
9503
9529
|
this.nodeId = '';
|
|
9504
9530
|
this.dropListId = '';
|
|
9505
9531
|
this.childClick = new EventEmitter();
|
|
@@ -9513,18 +9539,15 @@ class SliderSlideComponent {
|
|
|
9513
9539
|
this.dragEngine.handleDrop(this.children, event, this.dropListId, this.cdr);
|
|
9514
9540
|
}
|
|
9515
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 }); }
|
|
9516
|
-
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: `
|
|
9517
9543
|
<!-- The .swiper-slide wrapper (with the iox-outer-* class) is provided by SliderContainer —
|
|
9518
9544
|
Swiper needs it as a direct child of .swiper-wrapper — and the SLIDE'S size lands there
|
|
9519
|
-
via StyleRegistryService. This is only the styled INNER block
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
behave identically — the earlier preview branch used the un-stripped style input and
|
|
9524
|
-
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). -->
|
|
9525
9549
|
<div class="slide-edit-block"
|
|
9526
9550
|
[ngClass]="['iox-node-' + nodeId]"
|
|
9527
|
-
[ngStyle]="slideEditStyle"
|
|
9528
9551
|
ioxDropzone
|
|
9529
9552
|
[ioxDropzoneId]="dropListId"
|
|
9530
9553
|
[ioxDropzoneData]="children"
|
|
@@ -9548,22 +9571,19 @@ class SliderSlideComponent {
|
|
|
9548
9571
|
</ng-container>
|
|
9549
9572
|
</div>
|
|
9550
9573
|
</div>
|
|
9551
|
-
`, 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"] }] }); }
|
|
9552
9575
|
}
|
|
9553
9576
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: SliderSlideComponent, decorators: [{
|
|
9554
9577
|
type: Component,
|
|
9555
9578
|
args: [{ selector: 'app-slider-slide', template: `
|
|
9556
9579
|
<!-- The .swiper-slide wrapper (with the iox-outer-* class) is provided by SliderContainer —
|
|
9557
9580
|
Swiper needs it as a direct child of .swiper-wrapper — and the SLIDE'S size lands there
|
|
9558
|
-
via StyleRegistryService. This is only the styled INNER block
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
behave identically — the earlier preview branch used the un-stripped style input and
|
|
9563
|
-
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). -->
|
|
9564
9585
|
<div class="slide-edit-block"
|
|
9565
9586
|
[ngClass]="['iox-node-' + nodeId]"
|
|
9566
|
-
[ngStyle]="slideEditStyle"
|
|
9567
9587
|
ioxDropzone
|
|
9568
9588
|
[ioxDropzoneId]="dropListId"
|
|
9569
9589
|
[ioxDropzoneData]="children"
|
|
@@ -9587,7 +9607,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9587
9607
|
</ng-container>
|
|
9588
9608
|
</div>
|
|
9589
9609
|
</div>
|
|
9590
|
-
`, 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"] }]
|
|
9591
9611
|
}], ctorParameters: () => [{ type: DragEngineService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
9592
9612
|
type: Optional
|
|
9593
9613
|
}, {
|
|
@@ -9595,8 +9615,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9595
9615
|
args: [IS_PREVIEW]
|
|
9596
9616
|
}] }], propDecorators: { children: [{
|
|
9597
9617
|
type: Input
|
|
9598
|
-
}], style: [{
|
|
9599
|
-
type: Input
|
|
9600
9618
|
}], nodeId: [{
|
|
9601
9619
|
type: Input
|
|
9602
9620
|
}], dropListId: [{
|
|
@@ -10131,6 +10149,42 @@ function setTrait(node, name, value) {
|
|
|
10131
10149
|
trait.default = value;
|
|
10132
10150
|
}
|
|
10133
10151
|
|
|
10152
|
+
const PRESENTATION = {
|
|
10153
|
+
card: { icon: 'ph-thin ph-cards', description: 'A Container with the Card look: surface, border, radius and a hover lift.' },
|
|
10154
|
+
panel: { icon: 'ph-thin ph-square-half', description: 'A quiet tinted block for grouping content.' },
|
|
10155
|
+
nav: { icon: 'ph-thin ph-app-window', description: 'A <nav> row: brand at one end, links at the other.' },
|
|
10156
|
+
};
|
|
10157
|
+
const PALETTE_PRESET_DEFS = Object.freeze(PALETTE_PRESETS.map(preset => ({
|
|
10158
|
+
id: preset.elementStyle,
|
|
10159
|
+
name: preset.label,
|
|
10160
|
+
icon: PRESENTATION[preset.elementStyle]?.icon ?? 'ph-thin ph-square',
|
|
10161
|
+
description: PRESENTATION[preset.elementStyle]?.description ?? '',
|
|
10162
|
+
preset,
|
|
10163
|
+
})));
|
|
10164
|
+
function setDefault(list, name, value) {
|
|
10165
|
+
const hit = list?.find(t => t.name === name);
|
|
10166
|
+
if (hit)
|
|
10167
|
+
hit.default = value;
|
|
10168
|
+
}
|
|
10169
|
+
/**
|
|
10170
|
+
* A fresh preset node: the type's NEW-element config (already wearing its own default style), moved
|
|
10171
|
+
* onto the preset's element style, then the preset's traits and layout styleProps on top.
|
|
10172
|
+
*/
|
|
10173
|
+
function buildPalettePreset(def, createConfig) {
|
|
10174
|
+
const node = createConfig(def.preset.type);
|
|
10175
|
+
if (!node)
|
|
10176
|
+
return null;
|
|
10177
|
+
applyElementStyle(node, def.preset.elementStyle);
|
|
10178
|
+
for (const [name, value] of Object.entries(def.preset.traits ?? {}))
|
|
10179
|
+
setDefault(node.traits, name, value);
|
|
10180
|
+
for (const [name, value] of Object.entries(def.preset.styleProps)) {
|
|
10181
|
+
for (const group of node.styleTraits ?? [])
|
|
10182
|
+
setDefault(group.traits, name, value);
|
|
10183
|
+
}
|
|
10184
|
+
node.label = def.name;
|
|
10185
|
+
return node;
|
|
10186
|
+
}
|
|
10187
|
+
|
|
10134
10188
|
function text$1(content, styles, createConfig) {
|
|
10135
10189
|
const node = textNode(content, createConfig);
|
|
10136
10190
|
if (node)
|
|
@@ -10490,12 +10544,18 @@ class BuilderButtonComponentConfig extends ComponentConfig {
|
|
|
10490
10544
|
new TraitConfig('variant', 'Variant', TraitInputType.Select, ['primary', 'outlined', 'text'], 'primary'),
|
|
10491
10545
|
];
|
|
10492
10546
|
this.styleTraits = buildFullStyleTraits();
|
|
10547
|
+
// Hidden from the palette since DS2 (design-system plan T5): its `:host.variant-*` CSS
|
|
10548
|
+
// out-ranks the node rule, so an author's colour silently lost. ButtonBlock is "Button".
|
|
10549
|
+
// Stays registered and renderable for the saved pages that hold one.
|
|
10550
|
+
this.hidden = true;
|
|
10493
10551
|
}
|
|
10494
10552
|
}
|
|
10495
10553
|
|
|
10496
10554
|
class ButtonBlockComponentConfig extends ComponentConfig {
|
|
10497
10555
|
constructor() {
|
|
10498
10556
|
super('ButtonBlock', 'app-builder-button-block', 'ph-thin ph-cursor-click', 'Basic');
|
|
10557
|
+
// The palette's one button since DS2 (the legacy Button is hidden).
|
|
10558
|
+
this.label = 'Button';
|
|
10499
10559
|
this.children = [];
|
|
10500
10560
|
this.inputs = {
|
|
10501
10561
|
tag: 'select',
|
|
@@ -10618,13 +10678,16 @@ class GalleryConfig extends ComponentConfig {
|
|
|
10618
10678
|
new TraitConfig('fadeDuration', 'Fade duration (ms)', TraitInputType.Number, undefined, 1000),
|
|
10619
10679
|
new TraitConfig('autoStart', 'Auto start', TraitInputType.Checkbox, undefined, true),
|
|
10620
10680
|
];
|
|
10621
|
-
// Styling: size + spacing + border + background (letterbox). No typography (not
|
|
10681
|
+
// Styling: size + spacing + border + background (letterbox) + effects. No typography (not
|
|
10682
|
+
// text-bearing). Effects since DS2: the Image element style a Gallery may wear writes a
|
|
10683
|
+
// transition and a hover scale, and a trait with no slot is silently dropped.
|
|
10622
10684
|
this.styleTraits = [
|
|
10623
10685
|
new PositionGroupStyleConfig(),
|
|
10624
10686
|
new SizeGroupStyleConfig(),
|
|
10625
10687
|
new SpacingGroupStyleConfig(),
|
|
10626
10688
|
new BorderGroupStyleConfig(),
|
|
10627
10689
|
new BackgroundGroupStyleConfig(),
|
|
10690
|
+
new EffectsGroupStyleConfig(),
|
|
10628
10691
|
];
|
|
10629
10692
|
// Size traits stay at their group default (auto) so a gallery starts unconstrained. The one
|
|
10630
10693
|
// exception is `minHeight: 200px` — the image layers are absolutely positioned (no intrinsic
|
|
@@ -11147,6 +11210,8 @@ class SliderSlideConfig extends ComponentConfig {
|
|
|
11147
11210
|
new BorderGroupStyleConfig(),
|
|
11148
11211
|
new BackgroundGroupStyleConfig(),
|
|
11149
11212
|
new TypographyGroupStyleConfig(),
|
|
11213
|
+
// DS2: the default `media` element style writes a transition and a hover scale.
|
|
11214
|
+
new EffectsGroupStyleConfig(),
|
|
11150
11215
|
];
|
|
11151
11216
|
this.applyStyleDefaults({
|
|
11152
11217
|
width: '33%',
|
|
@@ -11297,5 +11362,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
11297
11362
|
* Generated bundle index. Do not edit.
|
|
11298
11363
|
*/
|
|
11299
11364
|
|
|
11300
|
-
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_PRESETS, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, buildConsentPreset, buildFieldKindPreset, buildFlavorSampler, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPresetStyleTraits, canFluidise, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, isHiddenAt, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, setStyleTrait, setTrait, stackChildrenProps, summarise, textNode, visibilityProps, wrapNode };
|
|
11365
|
+
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_TOKEN_SCOPE_CLASS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BreadcrumbBarComponent, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDataSourceService, BuilderDialogComponent, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_PRESETS, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GalleryConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, IOX_FONT_MANAGER, InteractionEngineService, InteractionPresetRegistryService, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LAYOUT_PRESETS, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PALETTE_PRESET_DEFS, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SubmitButtonComponentConfig, SymbolRegistryService, TARGETED_STATES, TARGETED_STATE_BASE, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VALIDATION_STATES, VALIDATION_STATE_CLASS, ViewportService, ZOOM_OPTIONS, arrayItemSchemaFields, arraySchemaFields, auditNodes, buildConsentPreset, buildFieldKindPreset, buildFlavorSampler, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, canFluidise, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, isHiddenAt, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, setStyleTrait, setTrait, stackChildrenProps, summarise, textNode, visibilityProps, wrapNode };
|
|
11301
11366
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|