@vectoriox/iox-builder 7.0.2 → 8.1.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 +287 -105
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +185 -65
- package/package.json +2 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject, NgZone, EventEmitter, createComponent, Output, Input,
|
|
2
|
+
import { Injectable, InjectionToken, Optional, Inject, inject, NgZone, EventEmitter, createComponent, Output, Input, Directive, ChangeDetectionStrategy, Component, ViewChild, effect, ContentChildren, NO_ERRORS_SCHEMA, NgModule } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
5
5
|
import * as i16 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
|
-
import * as
|
|
7
|
+
import * as i5 from '@angular/cdk/drag-drop';
|
|
8
8
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
9
9
|
import { AccordionModule } from 'primeng/accordion';
|
|
10
10
|
import * as i17 from 'primeng/button';
|
|
@@ -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, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, Breakpoint, BREAKPOINTS, countOverrides, setOverride, defaultElementStyleFor, applyElementStyle, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, statePseudoClass, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, breakpointDef, breakpointForWidth, FormRenderState, safeSetInput, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, resolveInputs, setInputOverride, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, resolveBoundValue, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule, PALETTE_PRESETS, elementStyleById, PART_CLASS_NAMES } from '@vectoriox/iox-ui';
|
|
19
|
+
import { VALIDATION_STATES as VALIDATION_STATES$1, VALIDATION_STATE_CLASS as VALIDATION_STATE_CLASS$1, DEFAULT_WIDTH_BY_TYPE, COLOR_SCHEME_TRAIT, COLOR_SCHEMES, ColorScheme, Breakpoint, BREAKPOINTS, countOverrides, setOverride, NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, applyElementStyle, stripSuppressedProps, DeclarationContext, buildPartSelector, planResponsiveRules, resolveStyleProps, buildTargetedStateSelector, statePseudoClass, compileTokenCssBlocks, catalogFontId, fontsForTokens, GOOGLE_FONTS_FILE_ORIGIN, googleFontHref, compileDeclarations, wrapInBreakpointQuery, BreakpointQueryKind, breakpointDef, breakpointForWidth, FormRenderState, safeSetInput, ContextStore, ComponentInstanceRegistryService, isActionEnabled, isContextAction, contextActionEffect, resolveContextValue, declaredInputNames, emptyImagePlaceholder, syncNodeDesignClasses, composeVirtualTraits, withDefaultHoverTransition, stateNamesIn, partClassName, resolveInputs, setInputOverride, collectContextSlotKeys, withContextSlots, compilePageStyle, nodeUsesContext, resolveBoundValue, IS_PREVIEW, planDataSource, dataSourcePlanToRequest, normalizeCollectionResult, resolveRepeaterItems, getByPath, computeRelative, IoxPageModule, PALETTE_PRESETS, elementStyleById, 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';
|
|
@@ -784,6 +784,24 @@ function reissueIdentityTraits(node) {
|
|
|
784
784
|
trait.default = `${prefix}-${freshId()}`;
|
|
785
785
|
}
|
|
786
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* A NEW node is born with its identity: an empty identity trait (a palette Form's `formId`) is issued,
|
|
789
|
+
* one already set (a preset's `contact-…`) is kept. A Form inserted from the palette used to keep
|
|
790
|
+
* `formId: ''` — its submissions posted to `forms//submit` and nothing was ever stored.
|
|
791
|
+
*/
|
|
792
|
+
function issueIdentityTraits(root) {
|
|
793
|
+
for (const trait of (root.traits ?? [])) {
|
|
794
|
+
if (!trait?.name || !REGENERATED_TRAITS.has(trait.name))
|
|
795
|
+
continue;
|
|
796
|
+
if (trait.default === undefined || trait.default === '')
|
|
797
|
+
trait.default = `${IDENTITY_PREFIX[trait.name] ?? trait.name}-${freshId()}`;
|
|
798
|
+
}
|
|
799
|
+
for (const child of root.children ?? [])
|
|
800
|
+
if (child)
|
|
801
|
+
issueIdentityTraits(child);
|
|
802
|
+
}
|
|
803
|
+
/** The readable prefix a freshly issued identity carries (`form-a1b2c3`). */
|
|
804
|
+
const IDENTITY_PREFIX = { formId: 'form' };
|
|
787
805
|
function regenerateNodeIds(root, opts = {}) {
|
|
788
806
|
const idMap = new Map();
|
|
789
807
|
const assign = (node) => {
|
|
@@ -1601,6 +1619,8 @@ const BUILDER_CHROME_EN = Object.freeze({
|
|
|
1601
1619
|
'builder.chrome.save': 'Save',
|
|
1602
1620
|
'builder.chrome.preview': 'Preview',
|
|
1603
1621
|
'builder.chrome.addImage': 'Add image',
|
|
1622
|
+
'builder.chrome.refuseNestedForm': 'A form can’t go inside another form. Place it outside the form.',
|
|
1623
|
+
'builder.chrome.refuseStateBlock': 'Success and error messages belong to their form. They can’t be placed on their own.',
|
|
1604
1624
|
});
|
|
1605
1625
|
const CANVAS_EMPTY_IMAGE_LABEL_KEY = 'builder.chrome.addImage';
|
|
1606
1626
|
const CANVAS_EMPTY_IMAGE_LABEL_EN = BUILDER_CHROME_EN[CANVAS_EMPTY_IMAGE_LABEL_KEY];
|
|
@@ -1651,6 +1671,7 @@ class ComponentRegistryService {
|
|
|
1651
1671
|
if (!config)
|
|
1652
1672
|
return null;
|
|
1653
1673
|
wearDefaultStyles(config);
|
|
1674
|
+
issueIdentityTraits(config);
|
|
1654
1675
|
return config;
|
|
1655
1676
|
}
|
|
1656
1677
|
/**
|
|
@@ -1664,16 +1685,33 @@ class ComponentRegistryService {
|
|
|
1664
1685
|
}
|
|
1665
1686
|
/** The unstyled base style map of a NEW node of the type — `switchElementStyle`'s baseline. */
|
|
1666
1687
|
baselineStyleProps(type) {
|
|
1688
|
+
const out = this.adoptionBaseline(type);
|
|
1689
|
+
for (const [name, value] of Object.entries(NEW_NODE_STYLE_DEFAULTS))
|
|
1690
|
+
if (name in out)
|
|
1691
|
+
out[name] = value;
|
|
1692
|
+
return out;
|
|
1693
|
+
}
|
|
1694
|
+
/** What an untouched SAVED node of the type holds — its constructor defaults, nothing on top.
|
|
1695
|
+
* Adopt (DS7) rewrites an owned trait only while it still holds this. */
|
|
1696
|
+
adoptionBaseline(type) {
|
|
1667
1697
|
const out = {};
|
|
1668
1698
|
for (const group of this.createBaseConfig(type)?.styleTraits ?? []) {
|
|
1669
1699
|
for (const trait of group.traits ?? [])
|
|
1670
1700
|
out[trait.name] = trait.default;
|
|
1671
1701
|
}
|
|
1672
|
-
for (const [name, value] of Object.entries(NEW_NODE_STYLE_DEFAULTS))
|
|
1673
|
-
if (name in out)
|
|
1674
|
-
out[name] = value;
|
|
1675
1702
|
return out;
|
|
1676
1703
|
}
|
|
1704
|
+
/** The context `adoptElementStyles` (design-core) reads baselines from. */
|
|
1705
|
+
adoptionContext() {
|
|
1706
|
+
const cache = new Map();
|
|
1707
|
+
return {
|
|
1708
|
+
baseline: type => {
|
|
1709
|
+
if (!cache.has(type))
|
|
1710
|
+
cache.set(type, this.adoptionBaseline(type));
|
|
1711
|
+
return cache.get(type);
|
|
1712
|
+
},
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1677
1715
|
register(type, component, config) {
|
|
1678
1716
|
this.components[type] = { type, component, config };
|
|
1679
1717
|
}
|
|
@@ -1686,13 +1724,6 @@ class ComponentRegistryService {
|
|
|
1686
1724
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: ComponentRegistryService, decorators: [{
|
|
1687
1725
|
type: Injectable
|
|
1688
1726
|
}] });
|
|
1689
|
-
/**
|
|
1690
|
-
* What a new node holds before its element style is written. `color: inherit` because a new node
|
|
1691
|
-
* belongs to the section it is dropped into: running text inherits (contract `body`/`lead`), so a
|
|
1692
|
-
* literal on any ancestor — a Form, a slide, a field group — would override the section's text.
|
|
1693
|
-
* A style that owns a colour (headings, buttons, notices) writes its own over this.
|
|
1694
|
-
*/
|
|
1695
|
-
const NEW_NODE_STYLE_DEFAULTS = { color: 'inherit' };
|
|
1696
1727
|
/** A new node AND the default children its config builds (a palette Form's fields, submit and
|
|
1697
1728
|
* notices) are all new elements: each wears its type's default style. */
|
|
1698
1729
|
function wearDefaultStyles(node) {
|
|
@@ -1911,6 +1942,93 @@ function resolveDragTarget(el, selectedElement, isDragHandle) {
|
|
|
1911
1942
|
return (isDragHandle || isDescendantOfSelected) ? selectedElement : el;
|
|
1912
1943
|
}
|
|
1913
1944
|
|
|
1945
|
+
/**
|
|
1946
|
+
* What may NOT be placed where — the ONE rule every insert path asks (drag, click-to-insert, quick-add,
|
|
1947
|
+
* the Form presets, paste, duplicate). Everything not refused here is accepted: any container-capable
|
|
1948
|
+
* node takes any child, a Form included.
|
|
1949
|
+
*/
|
|
1950
|
+
var InsertRefusal;
|
|
1951
|
+
(function (InsertRefusal) {
|
|
1952
|
+
/** A form inside a form — fields belong to their NEAREST form (forms plan D1), and HTML forbids it. */
|
|
1953
|
+
InsertRefusal["NestedForm"] = "nestedForm";
|
|
1954
|
+
/** A Success / Error block is part of its form's state machine; it is never placed on its own. */
|
|
1955
|
+
InsertRefusal["StateBlockAlone"] = "stateBlockAlone";
|
|
1956
|
+
})(InsertRefusal || (InsertRefusal = {}));
|
|
1957
|
+
const FORM_STATE_BLOCK_TYPES = new Set(['FormSuccess', 'FormError']);
|
|
1958
|
+
/** The builder-chrome label that says each refusal on screen (English in `BUILDER_CHROME_EN`). */
|
|
1959
|
+
const INSERT_REFUSAL_LABEL_KEYS = Object.freeze({
|
|
1960
|
+
[InsertRefusal.NestedForm]: 'builder.chrome.refuseNestedForm',
|
|
1961
|
+
[InsertRefusal.StateBlockAlone]: 'builder.chrome.refuseStateBlock',
|
|
1962
|
+
});
|
|
1963
|
+
/** The refusal in the host's language (the library's English when no host resolver is provided). */
|
|
1964
|
+
function insertRefusalLabel(reason, labels = defaultBuilderLabels) {
|
|
1965
|
+
const key = INSERT_REFUSAL_LABEL_KEYS[reason];
|
|
1966
|
+
return labels(key, BUILDER_CHROME_EN[key]);
|
|
1967
|
+
}
|
|
1968
|
+
function containsType(node, type) {
|
|
1969
|
+
if (node.type === type)
|
|
1970
|
+
return true;
|
|
1971
|
+
return (node.children ?? []).some(child => containsType(child, type));
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Why `node` may not be inserted under `ancestors` (outermost first, the new parent last; empty = the page),
|
|
1975
|
+
* or null when it may. `node` is the whole subtree being placed — a Container holding a Form is refused
|
|
1976
|
+
* inside a Form just as a bare Form is. Reordering a node inside its CURRENT parent is not an insert and
|
|
1977
|
+
* never asks.
|
|
1978
|
+
*/
|
|
1979
|
+
function insertRefusal(node, ancestors) {
|
|
1980
|
+
if (!node)
|
|
1981
|
+
return null;
|
|
1982
|
+
if (node.type && FORM_STATE_BLOCK_TYPES.has(node.type))
|
|
1983
|
+
return InsertRefusal.StateBlockAlone;
|
|
1984
|
+
if (ancestors.some(a => a?.type === FORM_TYPE) && containsType(node, FORM_TYPE))
|
|
1985
|
+
return InsertRefusal.NestedForm;
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* ROOT ESCAPE — near the top or bottom edge of a ROOT-level container the drop goes to the PAGE, between
|
|
1991
|
+
* root containers, instead of inside the container. This file is the decision; DragEngineService only
|
|
1992
|
+
* measures and applies it.
|
|
1993
|
+
*
|
|
1994
|
+
* ⚠️ The bug this replaced (2026-10-19, "a Form cannot be inserted into a Section / Container / Linked
|
|
1995
|
+
* Container"): escape switched on when the pointer was in the edge band and the PREVIOUS target was the
|
|
1996
|
+
* same container — which is also true on the second sample after ENTERING through the band — and it only
|
|
1997
|
+
* switched off once the pointer LEFT the container. So approaching a root container from above or below
|
|
1998
|
+
* locked every drop inside it to the page, even at its centre; an empty (short) container was almost
|
|
1999
|
+
* always entered that way. Now escape needs a move OUTWARD — from the container's inner region to its
|
|
2000
|
+
* edge — and it ends as soon as the pointer is back in the inner region.
|
|
2001
|
+
*/
|
|
2002
|
+
/** The edge band on a tall container, in screen px. */
|
|
2003
|
+
const ROOT_ESCAPE_PX = 14;
|
|
2004
|
+
var RootZone;
|
|
2005
|
+
(function (RootZone) {
|
|
2006
|
+
RootZone["Outside"] = "outside";
|
|
2007
|
+
RootZone["Edge"] = "edge";
|
|
2008
|
+
RootZone["Inner"] = "inner";
|
|
2009
|
+
})(RootZone || (RootZone = {}));
|
|
2010
|
+
/** The band shrinks on a short container (a quarter of its height), so its middle stays a drop target. */
|
|
2011
|
+
function rootEscapeBand(height) {
|
|
2012
|
+
return Math.min(ROOT_ESCAPE_PX, Math.max(0, height) / 4);
|
|
2013
|
+
}
|
|
2014
|
+
/** Where the pointer's y sits relative to a root container's vertical extent. */
|
|
2015
|
+
function rootZone(py, top, bottom) {
|
|
2016
|
+
if (py < top || py > bottom)
|
|
2017
|
+
return RootZone.Outside;
|
|
2018
|
+
const band = rootEscapeBand(bottom - top);
|
|
2019
|
+
return py <= top + band || py >= bottom - band ? RootZone.Edge : RootZone.Inner;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Whether this sample escapes `rootId` to the page: the pointer is on its edge band AND it either was
|
|
2023
|
+
* already escaping it or has just moved out to the edge from its inner region. Entering from outside
|
|
2024
|
+
* through the band never escapes.
|
|
2025
|
+
*/
|
|
2026
|
+
function escapesRoot(rootId, zone, memory) {
|
|
2027
|
+
if (zone !== RootZone.Edge)
|
|
2028
|
+
return false;
|
|
2029
|
+
return memory.escapingFor === rootId || memory.innerOf === rootId;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1914
2032
|
/**
|
|
1915
2033
|
* CSS container name of the builder canvas frame (and of the global-elements overlay). Breakpoint
|
|
1916
2034
|
* rules are emitted as `@container iox-canvas (max-width: …)`: the frame's layout width IS the
|
|
@@ -2452,6 +2570,11 @@ const MOTION_EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
|
2452
2570
|
// Lower = more lag/weight, higher = snappier. 0.35 catches up to the pointer
|
|
2453
2571
|
// within ~5-6 frames (~80-100ms) — reads as "responsive but weighted".
|
|
2454
2572
|
const GHOST_FOLLOW_SMOOTHING = 0.35;
|
|
2573
|
+
const TARGET_CHIP_BG = '#6366f1';
|
|
2574
|
+
// The admin chrome's danger fill (toolbar.component.scss's unpublish hover reads the same token).
|
|
2575
|
+
const REFUSED_CHIP_BG = 'var(--iox-danger-text, #8E211A)';
|
|
2576
|
+
/** On the drop-target overlay while the target refuses the dragged payload (styled by the host). */
|
|
2577
|
+
const DROP_REFUSED_CLASS = 'iox-drop-target-overlay--refused';
|
|
2455
2578
|
function deepCloneNode(node) {
|
|
2456
2579
|
const clone = JSON.parse(JSON.stringify(node));
|
|
2457
2580
|
// Fresh ids + remap intra-subtree references (stateTriggers, interaction targets) so a cloned
|
|
@@ -2460,10 +2583,12 @@ function deepCloneNode(node) {
|
|
|
2460
2583
|
return clone;
|
|
2461
2584
|
}
|
|
2462
2585
|
class DragEngineService {
|
|
2463
|
-
constructor(registry, symbolRegistry, overlayService) {
|
|
2586
|
+
constructor(registry, symbolRegistry, overlayService, labels) {
|
|
2464
2587
|
this.registry = registry;
|
|
2465
2588
|
this.symbolRegistry = symbolRegistry;
|
|
2466
2589
|
this.overlayService = overlayService;
|
|
2590
|
+
/** Every insert the builder refused, for the host to say on screen (a toast). */
|
|
2591
|
+
this.insertRefused$ = new Subject();
|
|
2467
2592
|
// ── Drop-zone registry ────────────────────────────────────
|
|
2468
2593
|
this.ids = [];
|
|
2469
2594
|
this.elMap = new Map();
|
|
@@ -2514,9 +2639,13 @@ class DragEngineService {
|
|
|
2514
2639
|
this._scale = 1;
|
|
2515
2640
|
// ── Dropzone rect cache — snapshotted at drag start and on scroll ─────────
|
|
2516
2641
|
this._dropzoneRectCache = new Map();
|
|
2517
|
-
// ── Root-escape
|
|
2518
|
-
this.ROOT_ESCAPE_PX = 14;
|
|
2642
|
+
// ── Root-escape (decision: utils/root-escape.util.ts) ─────
|
|
2519
2643
|
this._rootEscapeActiveFor = null;
|
|
2644
|
+
/** The root container whose inner region held the previous sample. */
|
|
2645
|
+
this._innerRootId = null;
|
|
2646
|
+
// ── Drop refusal (utils/insert-rules.util.ts) ─────────────
|
|
2647
|
+
/** Why the current target would refuse the dragged payload; null = it accepts. */
|
|
2648
|
+
this._targetRefusal = null;
|
|
2520
2649
|
// ─────────────────────────────────────────────────────────
|
|
2521
2650
|
// Draggable registration
|
|
2522
2651
|
// ─────────────────────────────────────────────────────────
|
|
@@ -2534,10 +2663,47 @@ class DragEngineService {
|
|
|
2534
2663
|
this._overlay.style.top = this._ghostY + 'px';
|
|
2535
2664
|
this._ghostRaf = requestAnimationFrame(this._ghostFollowStep);
|
|
2536
2665
|
};
|
|
2666
|
+
this.labels = labels ?? defaultBuilderLabels;
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* The ONE acceptance check for every insert path: null when `data` (a panel payload or a node) may go
|
|
2670
|
+
* under `ancestors`; otherwise the refusal, already announced on `insertRefused$`.
|
|
2671
|
+
*/
|
|
2672
|
+
refuseInsert(data, ancestors) {
|
|
2673
|
+
const reason = insertRefusal(this.payloadNode(data), ancestors);
|
|
2674
|
+
if (reason)
|
|
2675
|
+
this.insertRefused$.next({ reason, message: insertRefusalLabel(reason, this.labels) });
|
|
2676
|
+
return reason;
|
|
2677
|
+
}
|
|
2678
|
+
/** The node a payload WOULD insert, without building it: a type string, a block/symbol, or a node. */
|
|
2679
|
+
payloadNode(data) {
|
|
2680
|
+
if (typeof data === 'string')
|
|
2681
|
+
return { type: data };
|
|
2682
|
+
if (!data || typeof data !== 'object')
|
|
2683
|
+
return null;
|
|
2684
|
+
const blk = data;
|
|
2685
|
+
const symbolNode = blk.symbolId ? this.symbolRegistry.getSymbol(blk.symbolId)?.node : null;
|
|
2686
|
+
return (symbolNode ?? blk.node ?? blk);
|
|
2687
|
+
}
|
|
2688
|
+
/** The nodes a dropzone sits in, outermost first, its own node last — read from the DOM, which is
|
|
2689
|
+
* where a dropzone id lives. Empty for the page itself. */
|
|
2690
|
+
dropzoneAncestors(id) {
|
|
2691
|
+
const out = [];
|
|
2692
|
+
for (let el = this._getEl(id); el && el.id !== 'canvas-preview'; el = el.parentElement) {
|
|
2693
|
+
const match = this.overlayService.findNodeByElement(el);
|
|
2694
|
+
if (match)
|
|
2695
|
+
out.unshift(match.node);
|
|
2696
|
+
}
|
|
2697
|
+
return out;
|
|
2537
2698
|
}
|
|
2538
2699
|
// ── Shared drop handler — used by all layout components ───────────────────
|
|
2539
2700
|
handleDrop(children, event, dropListId, cdr, afterDrop) {
|
|
2540
2701
|
const { payload, insertIndex } = event;
|
|
2702
|
+
const changesParent = payload.type === 'external' || payload.sourceId !== dropListId;
|
|
2703
|
+
if (changesParent && this.refuseInsert(payload.data, this.dropzoneAncestors(dropListId))) {
|
|
2704
|
+
afterDrop?.();
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2541
2707
|
if (payload.type === 'external') {
|
|
2542
2708
|
const node = this.materialize(payload.data);
|
|
2543
2709
|
if (node) {
|
|
@@ -2611,7 +2777,9 @@ class DragEngineService {
|
|
|
2611
2777
|
* `dropComplete$` exactly like a drop, so the builder selects the new node and records one undo
|
|
2612
2778
|
* step through the same path. Returns the inserted node, or null if the payload was unusable.
|
|
2613
2779
|
*/
|
|
2614
|
-
insertPayload(children, index, data) {
|
|
2780
|
+
insertPayload(children, index, data, ancestors = []) {
|
|
2781
|
+
if (this.refuseInsert(data, ancestors))
|
|
2782
|
+
return null;
|
|
2615
2783
|
const node = this.materialize(data);
|
|
2616
2784
|
if (!node)
|
|
2617
2785
|
return null;
|
|
@@ -2657,7 +2825,7 @@ class DragEngineService {
|
|
|
2657
2825
|
registerDropzone(id, el, data, cdr, dropCb, postDrop) {
|
|
2658
2826
|
if (!id)
|
|
2659
2827
|
return;
|
|
2660
|
-
// Stamp id on element so DOM-walk helpers (
|
|
2828
|
+
// Stamp id on element so DOM-walk helpers (_rootContainerOf, dropzoneAncestors) find it.
|
|
2661
2829
|
el.id = id;
|
|
2662
2830
|
if (!this.ids.includes(id)) {
|
|
2663
2831
|
this.ids = [...this.ids, id];
|
|
@@ -2763,6 +2931,8 @@ class DragEngineService {
|
|
|
2763
2931
|
this._sourceEl = el;
|
|
2764
2932
|
this._isDragging = true;
|
|
2765
2933
|
this._rootEscapeActiveFor = null;
|
|
2934
|
+
this._innerRootId = null;
|
|
2935
|
+
this._targetRefusal = null;
|
|
2766
2936
|
// Suppress native text selection for the duration of the drag — without this,
|
|
2767
2937
|
// dragging the pointer across any text (canvas or sidebar) selects/highlights it.
|
|
2768
2938
|
document.body.classList.add('iox-dragging');
|
|
@@ -2877,6 +3047,8 @@ class DragEngineService {
|
|
|
2877
3047
|
this._removeTargetOverlay();
|
|
2878
3048
|
document.querySelectorAll('.iox-drag-over').forEach(el => el.classList.remove('iox-drag-over'));
|
|
2879
3049
|
this._rootEscapeActiveFor = null;
|
|
3050
|
+
this._innerRootId = null;
|
|
3051
|
+
this._targetRefusal = null;
|
|
2880
3052
|
this._isDragging = false;
|
|
2881
3053
|
this._payload = null;
|
|
2882
3054
|
this._sourceEl = null;
|
|
@@ -2964,55 +3136,14 @@ class DragEngineService {
|
|
|
2964
3136
|
}
|
|
2965
3137
|
}
|
|
2966
3138
|
}
|
|
2967
|
-
|
|
2968
|
-
// Purpose: when the cursor is near the top/bottom edge of a root-level
|
|
2969
|
-
// section, treat the target as canvas-preview so the user can insert
|
|
2970
|
-
// between root sections.
|
|
2971
|
-
//
|
|
2972
|
-
// IMPORTANT: only activate if the cursor was ALREADY inside this section
|
|
2973
|
-
// (i.e. deepTarget is the section itself or a nested child of it).
|
|
2974
|
-
// Without this guard, crossing from Section A into the top 14px of
|
|
2975
|
-
// Section C immediately locks the target to canvas-preview and prevents
|
|
2976
|
-
// the user from ever reaching Section C's nested containers.
|
|
2977
|
-
if (this._rootEscapeActiveFor) {
|
|
2978
|
-
const escEl = this._getEl(this._rootEscapeActiveFor);
|
|
2979
|
-
if (escEl) {
|
|
2980
|
-
const r = this._dropzoneRectCache.get(this._rootEscapeActiveFor) ?? escEl.getBoundingClientRect();
|
|
2981
|
-
const inside = px >= r.left && px <= r.right && py >= r.top && py <= r.bottom;
|
|
2982
|
-
if (inside) {
|
|
2983
|
-
bestId = 'canvas-preview';
|
|
2984
|
-
}
|
|
2985
|
-
else {
|
|
2986
|
-
this._rootEscapeActiveFor = null;
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
else {
|
|
2990
|
-
this._rootEscapeActiveFor = null;
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
if (!this._rootEscapeActiveFor &&
|
|
2994
|
-
bestId && bestId !== 'canvas-preview' && this._isRootContainer(bestId)) {
|
|
2995
|
-
const el = this._getEl(bestId);
|
|
2996
|
-
if (el) {
|
|
2997
|
-
const rect = this._dropzoneRectCache.get(bestId) ?? el.getBoundingClientRect();
|
|
2998
|
-
const nearEdge = py <= rect.top + this.ROOT_ESCAPE_PX ||
|
|
2999
|
-
py >= rect.bottom - this.ROOT_ESCAPE_PX;
|
|
3000
|
-
// Only escape if we were already targeting this section or
|
|
3001
|
-
// something nested inside it — not when crossing into it fresh.
|
|
3002
|
-
const wasInsideThisSection = this._deepTargetId === bestId ||
|
|
3003
|
-
this._isDescendantDropzone(this._deepTargetId, bestId);
|
|
3004
|
-
if (nearEdge && wasInsideThisSection) {
|
|
3005
|
-
this._rootEscapeActiveFor = bestId;
|
|
3006
|
-
bestId = 'canvas-preview';
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3139
|
+
bestId = this._applyRootEscape(bestId, py);
|
|
3010
3140
|
if (bestId !== this._deepTargetId) {
|
|
3011
3141
|
if (this._deepTargetId) {
|
|
3012
3142
|
const old = this._getEl(this._deepTargetId);
|
|
3013
3143
|
old?.classList.remove('iox-drag-over');
|
|
3014
3144
|
}
|
|
3015
3145
|
this._deepTargetId = bestId;
|
|
3146
|
+
this._targetRefusal = this._refusalAt(bestId);
|
|
3016
3147
|
if (bestId) {
|
|
3017
3148
|
this._getEl(bestId)?.classList.add('iox-drag-over');
|
|
3018
3149
|
}
|
|
@@ -3024,6 +3155,50 @@ class DragEngineService {
|
|
|
3024
3155
|
this._indicatorContainerId = null;
|
|
3025
3156
|
}
|
|
3026
3157
|
}
|
|
3158
|
+
/** Near a ROOT container's edge, after moving there from its inner region, the drop goes to the page. */
|
|
3159
|
+
_applyRootEscape(bestId, py) {
|
|
3160
|
+
const rootId = bestId && bestId !== 'canvas-preview' ? this._rootContainerOf(bestId) : null;
|
|
3161
|
+
const rootEl = rootId ? this._getEl(rootId) : null;
|
|
3162
|
+
const rect = rootId && rootEl ? (this._dropzoneRectCache.get(rootId) ?? rootEl.getBoundingClientRect()) : null;
|
|
3163
|
+
const zone = rect ? rootZone(py, rect.top, rect.bottom) : RootZone.Outside;
|
|
3164
|
+
const escape = !!rootId && rootId === bestId &&
|
|
3165
|
+
escapesRoot(rootId, zone, { escapingFor: this._rootEscapeActiveFor, innerOf: this._innerRootId });
|
|
3166
|
+
this._rootEscapeActiveFor = escape ? rootId : null;
|
|
3167
|
+
this._innerRootId = zone === RootZone.Inner ? rootId : null;
|
|
3168
|
+
return escape ? 'canvas-preview' : bestId;
|
|
3169
|
+
}
|
|
3170
|
+
/** The ROOT-level dropzone (a direct child zone of the page) that holds dropzone `id`, or null when it
|
|
3171
|
+
* is not on the page canvas (the global-elements layer). */
|
|
3172
|
+
_rootContainerOf(id) {
|
|
3173
|
+
let root = null;
|
|
3174
|
+
for (let el = this._getEl(id); el; el = el.parentElement) {
|
|
3175
|
+
if (el.id === 'canvas-preview')
|
|
3176
|
+
return root;
|
|
3177
|
+
if (el.id && this.elMap.has(el.id))
|
|
3178
|
+
root = el.id;
|
|
3179
|
+
}
|
|
3180
|
+
return null;
|
|
3181
|
+
}
|
|
3182
|
+
/** Why the dragged payload may not land in `targetId` (null = it may). Reordering inside the source
|
|
3183
|
+
* container is not an insert. */
|
|
3184
|
+
_refusalAt(targetId) {
|
|
3185
|
+
if (!targetId || !this._payload)
|
|
3186
|
+
return null;
|
|
3187
|
+
if (this._payload.type === 'internal' && this._payload.sourceId === targetId)
|
|
3188
|
+
return null;
|
|
3189
|
+
const ancestors = targetId === 'canvas-preview' ? [] : this.dropzoneAncestors(targetId);
|
|
3190
|
+
return insertRefusal(this.payloadNode(this._payload.data), ancestors);
|
|
3191
|
+
}
|
|
3192
|
+
/** The node a dropzone element belongs to — the element itself or the nearest registered ancestor
|
|
3193
|
+
* (a LinkedContainer, a Dialog, a Repeater or a slide registers an INNER element as its zone). */
|
|
3194
|
+
_ownerNode(el) {
|
|
3195
|
+
for (let cur = el; cur && cur.id !== 'canvas-preview'; cur = cur.parentElement) {
|
|
3196
|
+
const match = this.overlayService.findNodeByElement(cur);
|
|
3197
|
+
if (match)
|
|
3198
|
+
return match.node;
|
|
3199
|
+
}
|
|
3200
|
+
return undefined;
|
|
3201
|
+
}
|
|
3027
3202
|
// ─────────────────────────────────────────────────────────
|
|
3028
3203
|
// Target overlay — fixed to <body>, sized from getBoundingClientRect
|
|
3029
3204
|
// so it is always above all canvas content regardless of stacking
|
|
@@ -3057,7 +3232,7 @@ class DragEngineService {
|
|
|
3057
3232
|
this._targetChip.style.cssText = [
|
|
3058
3233
|
'position:absolute', 'left:0', 'top:-18px', 'max-width:100%', 'overflow:hidden',
|
|
3059
3234
|
'white-space:nowrap', 'text-overflow:ellipsis', 'padding:1px 6px',
|
|
3060
|
-
'border-radius:4px 4px 0 0',
|
|
3235
|
+
'border-radius:4px 4px 0 0', `background:${TARGET_CHIP_BG}`, 'color:#fff',
|
|
3061
3236
|
'font:500 10px/1.5 system-ui,sans-serif', 'pointer-events:none',
|
|
3062
3237
|
].join(';');
|
|
3063
3238
|
this._targetOverlay.appendChild(this._targetChip);
|
|
@@ -3085,40 +3260,19 @@ class DragEngineService {
|
|
|
3085
3260
|
_updateTargetChip(targetId, el) {
|
|
3086
3261
|
if (!this._targetChip)
|
|
3087
3262
|
return;
|
|
3088
|
-
const
|
|
3263
|
+
const refused = this._targetRefusal;
|
|
3264
|
+
this._targetOverlay?.classList.toggle(DROP_REFUSED_CLASS, !!refused);
|
|
3265
|
+
this._targetChip.style.background = refused ? REFUSED_CHIP_BG : TARGET_CHIP_BG;
|
|
3266
|
+
if (refused) {
|
|
3267
|
+
this._targetChip.textContent = insertRefusalLabel(refused, this.labels);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
const name = this._ownerNode(el);
|
|
3089
3271
|
const childCount = this.getData(targetId)?.length ?? 0;
|
|
3090
3272
|
const index = this._indicatorContainerId === targetId && this._indicatorIndex >= 0
|
|
3091
3273
|
? this._indicatorIndex : childCount;
|
|
3092
3274
|
this._targetChip.textContent = dropTargetLabel(name?.label || name?.type, index, childCount);
|
|
3093
3275
|
}
|
|
3094
|
-
_isRootContainer(id) {
|
|
3095
|
-
const el = this._getEl(id);
|
|
3096
|
-
if (!el)
|
|
3097
|
-
return false;
|
|
3098
|
-
let parent = el.parentElement;
|
|
3099
|
-
while (parent) {
|
|
3100
|
-
if (parent.id === 'canvas-preview')
|
|
3101
|
-
return true;
|
|
3102
|
-
if (parent.id && this.ids.includes(parent.id))
|
|
3103
|
-
return false;
|
|
3104
|
-
parent = parent.parentElement;
|
|
3105
|
-
}
|
|
3106
|
-
return false;
|
|
3107
|
-
}
|
|
3108
|
-
/**
|
|
3109
|
-
* Returns true if the dropzone with `childId` is a DOM descendant of the
|
|
3110
|
-
* dropzone with `parentId`. Used to decide whether the cursor was already
|
|
3111
|
-
* "inside" a root section before triggering root-escape.
|
|
3112
|
-
*/
|
|
3113
|
-
_isDescendantDropzone(childId, parentId) {
|
|
3114
|
-
if (!childId)
|
|
3115
|
-
return false;
|
|
3116
|
-
const childEl = this._getEl(childId);
|
|
3117
|
-
const parentEl = this._getEl(parentId);
|
|
3118
|
-
if (!childEl || !parentEl)
|
|
3119
|
-
return false;
|
|
3120
|
-
return parentEl.contains(childEl);
|
|
3121
|
-
}
|
|
3122
3276
|
// ─────────────────────────────────────────────────────────
|
|
3123
3277
|
// Insertion indicator (animates open so siblings shift smoothly)
|
|
3124
3278
|
// ─────────────────────────────────────────────────────────
|
|
@@ -3271,12 +3425,17 @@ class DragEngineService {
|
|
|
3271
3425
|
}
|
|
3272
3426
|
return children.length;
|
|
3273
3427
|
}
|
|
3274
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService, deps: [{ token: ComponentRegistryService }, { token: SymbolRegistryService }, { token: OverlayService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3428
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService, deps: [{ token: ComponentRegistryService }, { token: SymbolRegistryService }, { token: OverlayService }, { token: IOX_BUILDER_LABELS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3275
3429
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService }); }
|
|
3276
3430
|
}
|
|
3277
3431
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService, decorators: [{
|
|
3278
3432
|
type: Injectable
|
|
3279
|
-
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: SymbolRegistryService }, { type: OverlayService }
|
|
3433
|
+
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: SymbolRegistryService }, { type: OverlayService }, { type: undefined, decorators: [{
|
|
3434
|
+
type: Optional
|
|
3435
|
+
}, {
|
|
3436
|
+
type: Inject,
|
|
3437
|
+
args: [IOX_BUILDER_LABELS]
|
|
3438
|
+
}] }] });
|
|
3280
3439
|
|
|
3281
3440
|
/**
|
|
3282
3441
|
* Page-scoped registry that holds the current page's data sources.
|
|
@@ -5982,9 +6141,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5982
6141
|
}] } });
|
|
5983
6142
|
|
|
5984
6143
|
class LayerTreeComponent {
|
|
5985
|
-
constructor(registry, overlayService) {
|
|
6144
|
+
constructor(registry, overlayService, dragEngine) {
|
|
5986
6145
|
this.registry = registry;
|
|
5987
6146
|
this.overlayService = overlayService;
|
|
6147
|
+
this.dragEngine = dragEngine;
|
|
5988
6148
|
this.layout = [];
|
|
5989
6149
|
this.globalElements = [];
|
|
5990
6150
|
this.nodeSelect = new EventEmitter();
|
|
@@ -6092,6 +6252,13 @@ class LayerTreeComponent {
|
|
|
6092
6252
|
const targetTreeNode = this.flatTree[event.currentIndex];
|
|
6093
6253
|
if (!targetTreeNode)
|
|
6094
6254
|
return;
|
|
6255
|
+
// Landing beside the target means landing in the target's parent — the same acceptance rule
|
|
6256
|
+
// every canvas insert asks (a form never goes inside a form).
|
|
6257
|
+
if (targetTreeNode.parentArray !== draggedTreeNode.parentArray) {
|
|
6258
|
+
const ancestors = (nodeAncestorPathInEither(this.layout, this.globalElements, targetTreeNode.node) ?? []).slice(0, -1);
|
|
6259
|
+
if (this.dragEngine.refuseInsert(draggedTreeNode.node, ancestors))
|
|
6260
|
+
return;
|
|
6261
|
+
}
|
|
6095
6262
|
// Remove from source parent
|
|
6096
6263
|
const srcArray = draggedTreeNode.parentArray;
|
|
6097
6264
|
const srcIdx = draggedTreeNode.indexInParent;
|
|
@@ -6155,13 +6322,13 @@ class LayerTreeComponent {
|
|
|
6155
6322
|
}
|
|
6156
6323
|
}
|
|
6157
6324
|
}
|
|
6158
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6159
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> Page\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> Global\n <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i4.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i4.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
|
|
6325
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, deps: [{ token: ComponentRegistryService }, { token: OverlayService }, { token: DragEngineService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6326
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: LayerTreeComponent, isStandalone: false, selector: "app-layer-tree", inputs: { layout: "layout", globalElements: "globalElements" }, outputs: { nodeSelect: "nodeSelect", nodeAction: "nodeAction", nodeMove: "nodeMove" }, ngImport: i0, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> Page\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> Global\n <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\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: i5.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] }); }
|
|
6160
6327
|
}
|
|
6161
6328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, decorators: [{
|
|
6162
6329
|
type: Component,
|
|
6163
6330
|
args: [{ selector: 'app-layer-tree', standalone: false, template: "<div class=\"layer-tree\">\n <div class=\"tree-header\">\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'page'\" (click)=\"activeSection = 'page'\">\n <i class=\"ph-thin ph-stack\"></i> Page\n </button>\n <button class=\"section-tab\" [class.section-tab--active]=\"activeSection === 'global'\" (click)=\"activeSection = 'global'\">\n <i class=\"ph-thin ph-globe\"></i> Global\n <span *ngIf=\"flatGlobalTree.length\" class=\"section-tab-badge\">{{ flatGlobalTree.length }}</span>\n </button>\n </div>\n\n <!-- \u2500\u2500 Page elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'page'\"\n class=\"tree-body\"\n cdkDropList\n [cdkDropListData]=\"flatTree\"\n (cdkDropListDropped)=\"onTreeDrop($event)\">\n <div *ngIf=\"!flatTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-tree-structure\"></i>\n <span>No layers yet</span>\n </div>\n\n <div *ngFor=\"let tn of flatTree\"\n class=\"tree-row\"\n cdkDrag\n [cdkDragData]=\"tn\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <i class=\"ph-thin ph-dots-six-vertical drag-handle\" cdkDragHandle></i>\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <ng-container *ngIf=\"editingNodeId === tn.node.id; else staticLabel\">\n <input class=\"node-rename-input\"\n [value]=\"editingValue\"\n (input)=\"editingValue = $any($event.target).value\"\n (keydown.enter)=\"saveRename(tn)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"saveRename(tn)\"\n (click)=\"$event.stopPropagation()\"\n autofocus>\n </ng-container>\n <ng-template #staticLabel>\n <span class=\"node-label\" [class.node-label--named]=\"tn.node.label\">{{ tn.label }}</span>\n </ng-template>\n\n <span class=\"node-actions\">\n <button class=\"action-btn\" title=\"Rename\" (click)=\"startRename(tn, $event)\">\n <i class=\"ph-thin ph-pencil-simple\"></i>\n </button>\n <button class=\"action-btn\" title=\"Duplicate\" (click)=\"onDuplicateNode(tn, $event)\">\n <i class=\"ph-thin ph-copy\"></i>\n </button>\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n\n <!-- \u2500\u2500 Global elements tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div *ngIf=\"activeSection === 'global'\" class=\"tree-body\">\n <div *ngIf=\"!flatGlobalTree.length\" class=\"tree-empty\">\n <i class=\"ph-thin ph-globe\"></i>\n <span>No global elements</span>\n </div>\n\n <div *ngFor=\"let tn of flatGlobalTree\"\n class=\"tree-row tree-row--global\"\n [class.is-selected]=\"tn.node.id === selectedNodeId\"\n [class.is-hovered]=\"tn.node.id === hoveredNodeId\"\n [style.padding-inline-start.px]=\"12 + tn.depth * 16\"\n (click)=\"onNodeClick(tn)\"\n (mouseenter)=\"onNodeMouseEnter(tn)\"\n (mouseleave)=\"onNodeMouseLeave()\">\n\n <button *ngIf=\"tn.hasChildren\" class=\"expand-btn\" (click)=\"toggleExpand(tn, $event)\">\n <i class=\"ph-thin\" [ngClass]=\"tn.expanded ? 'ph-caret-down' : 'ph-caret-right'\"></i>\n </button>\n <span *ngIf=\"!tn.hasChildren\" class=\"expand-spacer\"></span>\n\n <i [class]=\"tn.icon\" class=\"node-icon\"></i>\n <span class=\"node-label\">{{ tn.label }}</span>\n <span class=\"node-position-badge\">{{ tn.node.globalPosition }}</span>\n\n <span class=\"node-actions\">\n <button class=\"action-btn action-btn--delete\" title=\"Delete\" (click)=\"onDeleteNode(tn, $event)\">\n <i class=\"ph-thin ph-trash\"></i>\n </button>\n </span>\n </div>\n </div>\n</div>\n", styles: [".layer-tree{display:flex;flex-direction:column;height:100%;overflow:hidden}.layer-tree .tree-header{display:flex;gap:2px;padding:6px 8px;border-bottom:1px solid var(--p-surface-200);flex-shrink:0}.layer-tree .section-tab{display:inline-flex;align-items:center;gap:4px;flex:1;justify-content:center;padding:4px 8px;font-size:11px;font-weight:500;border:1px solid var(--p-surface-200);border-radius:4px;background:transparent;color:var(--p-text-muted-color);cursor:pointer;transition:background .12s,color .12s,border-color .12s;white-space:nowrap}.layer-tree .section-tab i{font-size:11px}.layer-tree .section-tab:hover{background:var(--p-surface-100);color:var(--p-text-color)}.layer-tree .section-tab--active{background:#cb909026;border-color:#cb9090;color:#cb9090}.layer-tree .section-tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-size:10px;font-weight:600;background:#cb9090;color:#fff;border-radius:8px}.layer-tree .tree-body{flex:1 1 0;overflow-y:auto;overflow-x:hidden;padding-block:4px}.layer-tree .tree-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2rem 1rem;color:var(--p-text-muted-color);font-size:12px}.layer-tree .tree-empty i{font-size:22px}.layer-tree .tree-row{display:flex;align-items:center;gap:6px;height:28px;padding-inline-end:8px;cursor:pointer;font-size:12px;color:var(--p-text-color);border-inline-start:2px solid transparent;transition:background .12s,border-color .12s;-webkit-user-select:none;user-select:none}.layer-tree .tree-row:hover,.layer-tree .tree-row.is-hovered{background:var(--p-surface-100)}.layer-tree .tree-row.is-selected{background:#cb90901a;border-inline-start-color:#cb9090;font-weight:500}.layer-tree .drag-handle{font-size:10px;color:var(--p-text-muted-color);opacity:0;cursor:grab;flex-shrink:0;transition:opacity .12s}.layer-tree .tree-row:hover .drag-handle{opacity:.5}.layer-tree .drag-handle:hover{opacity:1!important}.layer-tree .cdk-drag-preview{box-sizing:border-box;display:flex;align-items:center;gap:6px;height:28px;padding:0 8px 0 12px;border-radius:6px;background:var(--p-surface-0);border:1px solid #cb9090;box-shadow:0 4px 12px #0000001f;font-size:12px}.layer-tree .cdk-drag-placeholder{height:2px;background:#cb9090;border-radius:1px;opacity:.6}.layer-tree .cdk-drag-placeholder>*{visibility:hidden}.layer-tree .cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.layer-tree .expand-btn{display:flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;flex-shrink:0;border-radius:2px}.layer-tree .expand-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .expand-btn i{font-size:10px}.layer-tree .expand-spacer{width:16px;flex-shrink:0}.layer-tree .node-icon{font-size:14px;color:var(--p-text-muted-color);flex-shrink:0}.layer-tree .node-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.layer-tree .node-label.node-label--named{color:var(--p-primary-color, #cb9090);font-weight:500}.layer-tree .node-rename-input{flex:1;min-width:0;border:1px solid var(--p-primary-color, #cb9090);border-radius:4px;background:var(--p-surface-0, #fff);color:var(--p-text-color);font-family:inherit;font-size:.72rem;padding:1px 4px;outline:none}.layer-tree .node-actions{display:flex;gap:2px;margin-inline-start:auto;opacity:0;transition:opacity .12s;flex-shrink:0}.layer-tree .tree-row:hover .node-actions,.layer-tree .tree-row.is-selected .node-actions{opacity:1}.layer-tree .tree-section-header{display:flex;align-items:center;gap:6px;padding:6px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--p-text-muted-color);border-top:1px solid var(--p-surface-200);margin-top:4px}.layer-tree .tree-section-header i{font-size:12px}.layer-tree .tree-body--global{flex:0 0 auto}.layer-tree .tree-row--global{border-inline-start-color:transparent}.layer-tree .tree-row--global.is-selected{background:#638fcb1a;border-inline-start-color:#638fcb}.layer-tree .node-position-badge{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#638fcb;background:#638fcb1f;padding:1px 5px;border-radius:3px;flex-shrink:0}.layer-tree .action-btn{display:flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;background:transparent;color:var(--p-text-muted-color);cursor:pointer;border-radius:4px;font-size:12px;transition:background .12s,color .12s}.layer-tree .action-btn:hover{background:var(--p-surface-200);color:var(--p-text-color)}.layer-tree .action-btn--delete:hover{background:#dc26261a;color:#dc2626}\n"] }]
|
|
6164
|
-
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }], propDecorators: { layout: [{
|
|
6331
|
+
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: DragEngineService }], propDecorators: { layout: [{
|
|
6165
6332
|
type: Input
|
|
6166
6333
|
}], globalElements: [{
|
|
6167
6334
|
type: Input
|
|
@@ -7002,6 +7169,9 @@ class BuilderComponent {
|
|
|
7002
7169
|
}
|
|
7003
7170
|
onDrop(event) {
|
|
7004
7171
|
const { payload, insertIndex } = event;
|
|
7172
|
+
const changesParent = payload.type === 'external' || payload.sourceId !== 'canvas-preview';
|
|
7173
|
+
if (changesParent && this.dragEngine.refuseInsert(payload.data, []))
|
|
7174
|
+
return;
|
|
7005
7175
|
if (payload.type === 'external') {
|
|
7006
7176
|
if (typeof payload.data === 'string') {
|
|
7007
7177
|
const config = this.registry.createConfig(payload.data);
|
|
@@ -7292,7 +7462,7 @@ class BuilderComponent {
|
|
|
7292
7462
|
}
|
|
7293
7463
|
case ToolbarAction.Duplicate: {
|
|
7294
7464
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
7295
|
-
if (result) {
|
|
7465
|
+
if (result && !this.dragEngine.refuseInsert(node, this.siblingAncestors(node))) {
|
|
7296
7466
|
const clone = this.deepCloneWithNewIds(node);
|
|
7297
7467
|
result.parent.splice(result.index + 1, 0, clone);
|
|
7298
7468
|
this.cdr.detectChanges();
|
|
@@ -7452,6 +7622,8 @@ class BuilderComponent {
|
|
|
7452
7622
|
const pastedNode = this.clipboard.getNode();
|
|
7453
7623
|
if (!pastedNode)
|
|
7454
7624
|
break;
|
|
7625
|
+
if (this.dragEngine.refuseInsert(pastedNode, this.siblingAncestors(node)))
|
|
7626
|
+
break;
|
|
7455
7627
|
const clone = this.deepCloneWithNewIds(pastedNode);
|
|
7456
7628
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
7457
7629
|
if (result) {
|
|
@@ -7534,6 +7706,10 @@ class BuilderComponent {
|
|
|
7534
7706
|
this.cdr.markForCheck();
|
|
7535
7707
|
}, 1200);
|
|
7536
7708
|
}
|
|
7709
|
+
/** The nodes a sibling of `node` sits under — `node`'s ancestors, outermost first (empty at a root). */
|
|
7710
|
+
siblingAncestors(node) {
|
|
7711
|
+
return (nodeAncestorPathInEither(this.layout, this.globalElements, node) ?? [node]).slice(0, -1);
|
|
7712
|
+
}
|
|
7537
7713
|
findNodeInTree(target, children) {
|
|
7538
7714
|
for (let i = 0; i < children.length; i++) {
|
|
7539
7715
|
if (children[i] === target) {
|
|
@@ -7627,7 +7803,8 @@ class BuilderComponent {
|
|
|
7627
7803
|
if (inGlobals && !target.parent) {
|
|
7628
7804
|
target = resolveInsertTarget(this.layout, null, n => this.dragEngine.isDropContainer(n.children));
|
|
7629
7805
|
}
|
|
7630
|
-
|
|
7806
|
+
const ancestors = target.parent ? (nodeAncestorPathInEither(this.layout, this.globalElements, target.parent) ?? [target.parent]) : [];
|
|
7807
|
+
if (!this.dragEngine.insertPayload(target.children, target.index, data, ancestors))
|
|
7631
7808
|
return;
|
|
7632
7809
|
this.cdr.detectChanges();
|
|
7633
7810
|
}
|
|
@@ -9896,10 +10073,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
9896
10073
|
}] } });
|
|
9897
10074
|
|
|
9898
10075
|
const MAX_HISTORY = 50;
|
|
10076
|
+
const clone = (value) => (value === undefined ? value : JSON.parse(JSON.stringify(value)));
|
|
9899
10077
|
/**
|
|
9900
10078
|
* BuilderHistoryService — snapshot-based undo/redo for the page builder.
|
|
9901
10079
|
*
|
|
9902
|
-
* Snapshots are JSON deep-clones of the full layout array
|
|
10080
|
+
* Snapshots are JSON deep-clones of the full layout array; a step that also changed state outside
|
|
10081
|
+
* the layout records that change, so one undo reverts both.
|
|
9903
10082
|
* Session-only — not persisted to the backend.
|
|
9904
10083
|
* Scoped to PageUiComponent.providers[] — one history stack per builder instance.
|
|
9905
10084
|
*/
|
|
@@ -9910,8 +10089,8 @@ class BuilderHistoryService {
|
|
|
9910
10089
|
}
|
|
9911
10090
|
get canUndo() { return this._past.length > 1; }
|
|
9912
10091
|
get canRedo() { return this._future.length > 0; }
|
|
9913
|
-
push(layout) {
|
|
9914
|
-
this._past.push({ layout:
|
|
10092
|
+
push(layout, change) {
|
|
10093
|
+
this._past.push({ layout: clone(layout), timestamp: Date.now(), ...(change ? { change: clone(change) } : {}) });
|
|
9915
10094
|
if (this._past.length > MAX_HISTORY) {
|
|
9916
10095
|
this._past.shift();
|
|
9917
10096
|
}
|
|
@@ -9922,19 +10101,22 @@ class BuilderHistoryService {
|
|
|
9922
10101
|
return null;
|
|
9923
10102
|
const current = this._past.pop();
|
|
9924
10103
|
this._future.unshift(current);
|
|
9925
|
-
return
|
|
10104
|
+
return this.step(this._past[this._past.length - 1].layout, current.change?.before, !!current.change);
|
|
9926
10105
|
}
|
|
9927
10106
|
redo() {
|
|
9928
10107
|
if (!this.canRedo)
|
|
9929
10108
|
return null;
|
|
9930
10109
|
const next = this._future.shift();
|
|
9931
10110
|
this._past.push(next);
|
|
9932
|
-
return
|
|
10111
|
+
return this.step(next.layout, next.change?.after, !!next.change);
|
|
9933
10112
|
}
|
|
9934
10113
|
clear() {
|
|
9935
10114
|
this._past = [];
|
|
9936
10115
|
this._future = [];
|
|
9937
10116
|
}
|
|
10117
|
+
step(layout, restore, changed) {
|
|
10118
|
+
return changed ? { layout: clone(layout), restore: clone(restore) } : { layout: clone(layout) };
|
|
10119
|
+
}
|
|
9938
10120
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderHistoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
9939
10121
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: BuilderHistoryService }); }
|
|
9940
10122
|
}
|
|
@@ -11661,5 +11843,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
11661
11843
|
* Generated bundle index. Do not edit.
|
|
11662
11844
|
*/
|
|
11663
11845
|
|
|
11664
|
-
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, 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, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, 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_CANVAS, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, 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, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, isHiddenAt, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, textNode, visibilityProps, wrapNode };
|
|
11846
|
+
export { ACTION_TYPE_OPTIONS, AuditCode, AuditPanelComponent, AuditSeverity, BODY_CRUMB_LABEL, BREAKPOINT_PRESENTATION, BUILDER_CANVAS_CONTAINER, BUILDER_CHROME_EN, BUILDER_FONT_LINK_ATTR, 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, CANVAS_EMPTY_IMAGE_LABEL_EN, CANVAS_EMPTY_IMAGE_LABEL_KEY, CANVAS_ROOT_RESET_CSS, CardComponentConfig, ChildLayoutGroupStyleConfig, ComponentConfig, ComponentRegistryService, DEFAULT_ESSENTIAL_TRAITS, DRAG_HANDLE_ATTR, DROP_REFUSED_CLASS, DataSourceRegistryService, DataSourceType, DialogComponentConfig, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS$1 as EASING_OPTIONS, EDITOR_CHROME_ATTRIBUTE, ESSENTIAL_TRAITS_BY_TYPE, EffectsGroupStyleConfig, FIELD_KIND_PRESETS, FIELD_PARTS, FORM_PRESETS, FORM_STATE_BLOCK_TYPES, FORM_TYPE, FieldComponentConfig, FieldGroupComponentConfig, FormComponentConfig, FormErrorComponentConfig, FormStateService, FormSuccessComponentConfig, GalleryConfig, GroupStyleConfig, INSERT_REFUSAL_LABEL_KEYS, INTERACTION_STATES, INVERSE_ACTION, IOX_BUILDER_CANVAS, IOX_BUILDER_LABELS, IOX_DATA_SOURCE_ENDPOINTS, InsertRefusal, 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, ROOT_ESCAPE_PX, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, RootZone, 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, buildElementStyleSpecimen, buildFieldKindPreset, buildFlavorSpecimen, buildFormPreset, buildFullStyleTraits, buildLayoutPreset, buildPalettePreset, buildPresetStyleTraits, canFluidise, canvasEmptyImageLabel, collapseColumnsProps, columnCountOf, defaultBuilderLabels, defaultPageSettings, dropTargetLabel, escapesRoot, essentialTraitsFor, findNodeLocation, findParentOf, flattenSchemaFields, fluidiseProps, generateNodeId, insertRefusal, insertRefusalLabel, isHiddenAt, isSpecimen, markSpecimen, matchesControlSearch, nearestFormNode, nodeAncestorPath, nodeAncestorPathInEither, reorderProps, resolveInsertTarget, resolveTraitControllerType, resolveTraitOptions, rootEscapeBand, rootZone, setStyleTrait, setTrait, stackChildrenProps, summarise, syncEmptyImagePlaceholder, syncNodeDesign, textNode, visibilityProps, wrapNode };
|
|
11665
11847
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|