@vectoriox/iox-builder 8.0.0 → 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 +256 -89
- package/fesm2022/vectoriox-iox-builder.mjs.map +1 -1
- package/index.d.ts +157 -60
- package/package.json +1 -1
|
@@ -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';
|
|
@@ -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
|
/**
|
|
@@ -1921,6 +1942,93 @@ function resolveDragTarget(el, selectedElement, isDragHandle) {
|
|
|
1921
1942
|
return (isDragHandle || isDescendantOfSelected) ? selectedElement : el;
|
|
1922
1943
|
}
|
|
1923
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
|
+
|
|
1924
2032
|
/**
|
|
1925
2033
|
* CSS container name of the builder canvas frame (and of the global-elements overlay). Breakpoint
|
|
1926
2034
|
* rules are emitted as `@container iox-canvas (max-width: …)`: the frame's layout width IS the
|
|
@@ -2462,6 +2570,11 @@ const MOTION_EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
|
2462
2570
|
// Lower = more lag/weight, higher = snappier. 0.35 catches up to the pointer
|
|
2463
2571
|
// within ~5-6 frames (~80-100ms) — reads as "responsive but weighted".
|
|
2464
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';
|
|
2465
2578
|
function deepCloneNode(node) {
|
|
2466
2579
|
const clone = JSON.parse(JSON.stringify(node));
|
|
2467
2580
|
// Fresh ids + remap intra-subtree references (stateTriggers, interaction targets) so a cloned
|
|
@@ -2470,10 +2583,12 @@ function deepCloneNode(node) {
|
|
|
2470
2583
|
return clone;
|
|
2471
2584
|
}
|
|
2472
2585
|
class DragEngineService {
|
|
2473
|
-
constructor(registry, symbolRegistry, overlayService) {
|
|
2586
|
+
constructor(registry, symbolRegistry, overlayService, labels) {
|
|
2474
2587
|
this.registry = registry;
|
|
2475
2588
|
this.symbolRegistry = symbolRegistry;
|
|
2476
2589
|
this.overlayService = overlayService;
|
|
2590
|
+
/** Every insert the builder refused, for the host to say on screen (a toast). */
|
|
2591
|
+
this.insertRefused$ = new Subject();
|
|
2477
2592
|
// ── Drop-zone registry ────────────────────────────────────
|
|
2478
2593
|
this.ids = [];
|
|
2479
2594
|
this.elMap = new Map();
|
|
@@ -2524,9 +2639,13 @@ class DragEngineService {
|
|
|
2524
2639
|
this._scale = 1;
|
|
2525
2640
|
// ── Dropzone rect cache — snapshotted at drag start and on scroll ─────────
|
|
2526
2641
|
this._dropzoneRectCache = new Map();
|
|
2527
|
-
// ── Root-escape
|
|
2528
|
-
this.ROOT_ESCAPE_PX = 14;
|
|
2642
|
+
// ── Root-escape (decision: utils/root-escape.util.ts) ─────
|
|
2529
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;
|
|
2530
2649
|
// ─────────────────────────────────────────────────────────
|
|
2531
2650
|
// Draggable registration
|
|
2532
2651
|
// ─────────────────────────────────────────────────────────
|
|
@@ -2544,10 +2663,47 @@ class DragEngineService {
|
|
|
2544
2663
|
this._overlay.style.top = this._ghostY + 'px';
|
|
2545
2664
|
this._ghostRaf = requestAnimationFrame(this._ghostFollowStep);
|
|
2546
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;
|
|
2547
2698
|
}
|
|
2548
2699
|
// ── Shared drop handler — used by all layout components ───────────────────
|
|
2549
2700
|
handleDrop(children, event, dropListId, cdr, afterDrop) {
|
|
2550
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
|
+
}
|
|
2551
2707
|
if (payload.type === 'external') {
|
|
2552
2708
|
const node = this.materialize(payload.data);
|
|
2553
2709
|
if (node) {
|
|
@@ -2621,7 +2777,9 @@ class DragEngineService {
|
|
|
2621
2777
|
* `dropComplete$` exactly like a drop, so the builder selects the new node and records one undo
|
|
2622
2778
|
* step through the same path. Returns the inserted node, or null if the payload was unusable.
|
|
2623
2779
|
*/
|
|
2624
|
-
insertPayload(children, index, data) {
|
|
2780
|
+
insertPayload(children, index, data, ancestors = []) {
|
|
2781
|
+
if (this.refuseInsert(data, ancestors))
|
|
2782
|
+
return null;
|
|
2625
2783
|
const node = this.materialize(data);
|
|
2626
2784
|
if (!node)
|
|
2627
2785
|
return null;
|
|
@@ -2667,7 +2825,7 @@ class DragEngineService {
|
|
|
2667
2825
|
registerDropzone(id, el, data, cdr, dropCb, postDrop) {
|
|
2668
2826
|
if (!id)
|
|
2669
2827
|
return;
|
|
2670
|
-
// Stamp id on element so DOM-walk helpers (
|
|
2828
|
+
// Stamp id on element so DOM-walk helpers (_rootContainerOf, dropzoneAncestors) find it.
|
|
2671
2829
|
el.id = id;
|
|
2672
2830
|
if (!this.ids.includes(id)) {
|
|
2673
2831
|
this.ids = [...this.ids, id];
|
|
@@ -2773,6 +2931,8 @@ class DragEngineService {
|
|
|
2773
2931
|
this._sourceEl = el;
|
|
2774
2932
|
this._isDragging = true;
|
|
2775
2933
|
this._rootEscapeActiveFor = null;
|
|
2934
|
+
this._innerRootId = null;
|
|
2935
|
+
this._targetRefusal = null;
|
|
2776
2936
|
// Suppress native text selection for the duration of the drag — without this,
|
|
2777
2937
|
// dragging the pointer across any text (canvas or sidebar) selects/highlights it.
|
|
2778
2938
|
document.body.classList.add('iox-dragging');
|
|
@@ -2887,6 +3047,8 @@ class DragEngineService {
|
|
|
2887
3047
|
this._removeTargetOverlay();
|
|
2888
3048
|
document.querySelectorAll('.iox-drag-over').forEach(el => el.classList.remove('iox-drag-over'));
|
|
2889
3049
|
this._rootEscapeActiveFor = null;
|
|
3050
|
+
this._innerRootId = null;
|
|
3051
|
+
this._targetRefusal = null;
|
|
2890
3052
|
this._isDragging = false;
|
|
2891
3053
|
this._payload = null;
|
|
2892
3054
|
this._sourceEl = null;
|
|
@@ -2974,55 +3136,14 @@ class DragEngineService {
|
|
|
2974
3136
|
}
|
|
2975
3137
|
}
|
|
2976
3138
|
}
|
|
2977
|
-
|
|
2978
|
-
// Purpose: when the cursor is near the top/bottom edge of a root-level
|
|
2979
|
-
// section, treat the target as canvas-preview so the user can insert
|
|
2980
|
-
// between root sections.
|
|
2981
|
-
//
|
|
2982
|
-
// IMPORTANT: only activate if the cursor was ALREADY inside this section
|
|
2983
|
-
// (i.e. deepTarget is the section itself or a nested child of it).
|
|
2984
|
-
// Without this guard, crossing from Section A into the top 14px of
|
|
2985
|
-
// Section C immediately locks the target to canvas-preview and prevents
|
|
2986
|
-
// the user from ever reaching Section C's nested containers.
|
|
2987
|
-
if (this._rootEscapeActiveFor) {
|
|
2988
|
-
const escEl = this._getEl(this._rootEscapeActiveFor);
|
|
2989
|
-
if (escEl) {
|
|
2990
|
-
const r = this._dropzoneRectCache.get(this._rootEscapeActiveFor) ?? escEl.getBoundingClientRect();
|
|
2991
|
-
const inside = px >= r.left && px <= r.right && py >= r.top && py <= r.bottom;
|
|
2992
|
-
if (inside) {
|
|
2993
|
-
bestId = 'canvas-preview';
|
|
2994
|
-
}
|
|
2995
|
-
else {
|
|
2996
|
-
this._rootEscapeActiveFor = null;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
else {
|
|
3000
|
-
this._rootEscapeActiveFor = null;
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
if (!this._rootEscapeActiveFor &&
|
|
3004
|
-
bestId && bestId !== 'canvas-preview' && this._isRootContainer(bestId)) {
|
|
3005
|
-
const el = this._getEl(bestId);
|
|
3006
|
-
if (el) {
|
|
3007
|
-
const rect = this._dropzoneRectCache.get(bestId) ?? el.getBoundingClientRect();
|
|
3008
|
-
const nearEdge = py <= rect.top + this.ROOT_ESCAPE_PX ||
|
|
3009
|
-
py >= rect.bottom - this.ROOT_ESCAPE_PX;
|
|
3010
|
-
// Only escape if we were already targeting this section or
|
|
3011
|
-
// something nested inside it — not when crossing into it fresh.
|
|
3012
|
-
const wasInsideThisSection = this._deepTargetId === bestId ||
|
|
3013
|
-
this._isDescendantDropzone(this._deepTargetId, bestId);
|
|
3014
|
-
if (nearEdge && wasInsideThisSection) {
|
|
3015
|
-
this._rootEscapeActiveFor = bestId;
|
|
3016
|
-
bestId = 'canvas-preview';
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3139
|
+
bestId = this._applyRootEscape(bestId, py);
|
|
3020
3140
|
if (bestId !== this._deepTargetId) {
|
|
3021
3141
|
if (this._deepTargetId) {
|
|
3022
3142
|
const old = this._getEl(this._deepTargetId);
|
|
3023
3143
|
old?.classList.remove('iox-drag-over');
|
|
3024
3144
|
}
|
|
3025
3145
|
this._deepTargetId = bestId;
|
|
3146
|
+
this._targetRefusal = this._refusalAt(bestId);
|
|
3026
3147
|
if (bestId) {
|
|
3027
3148
|
this._getEl(bestId)?.classList.add('iox-drag-over');
|
|
3028
3149
|
}
|
|
@@ -3034,6 +3155,50 @@ class DragEngineService {
|
|
|
3034
3155
|
this._indicatorContainerId = null;
|
|
3035
3156
|
}
|
|
3036
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
|
+
}
|
|
3037
3202
|
// ─────────────────────────────────────────────────────────
|
|
3038
3203
|
// Target overlay — fixed to <body>, sized from getBoundingClientRect
|
|
3039
3204
|
// so it is always above all canvas content regardless of stacking
|
|
@@ -3067,7 +3232,7 @@ class DragEngineService {
|
|
|
3067
3232
|
this._targetChip.style.cssText = [
|
|
3068
3233
|
'position:absolute', 'left:0', 'top:-18px', 'max-width:100%', 'overflow:hidden',
|
|
3069
3234
|
'white-space:nowrap', 'text-overflow:ellipsis', 'padding:1px 6px',
|
|
3070
|
-
'border-radius:4px 4px 0 0',
|
|
3235
|
+
'border-radius:4px 4px 0 0', `background:${TARGET_CHIP_BG}`, 'color:#fff',
|
|
3071
3236
|
'font:500 10px/1.5 system-ui,sans-serif', 'pointer-events:none',
|
|
3072
3237
|
].join(';');
|
|
3073
3238
|
this._targetOverlay.appendChild(this._targetChip);
|
|
@@ -3095,40 +3260,19 @@ class DragEngineService {
|
|
|
3095
3260
|
_updateTargetChip(targetId, el) {
|
|
3096
3261
|
if (!this._targetChip)
|
|
3097
3262
|
return;
|
|
3098
|
-
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);
|
|
3099
3271
|
const childCount = this.getData(targetId)?.length ?? 0;
|
|
3100
3272
|
const index = this._indicatorContainerId === targetId && this._indicatorIndex >= 0
|
|
3101
3273
|
? this._indicatorIndex : childCount;
|
|
3102
3274
|
this._targetChip.textContent = dropTargetLabel(name?.label || name?.type, index, childCount);
|
|
3103
3275
|
}
|
|
3104
|
-
_isRootContainer(id) {
|
|
3105
|
-
const el = this._getEl(id);
|
|
3106
|
-
if (!el)
|
|
3107
|
-
return false;
|
|
3108
|
-
let parent = el.parentElement;
|
|
3109
|
-
while (parent) {
|
|
3110
|
-
if (parent.id === 'canvas-preview')
|
|
3111
|
-
return true;
|
|
3112
|
-
if (parent.id && this.ids.includes(parent.id))
|
|
3113
|
-
return false;
|
|
3114
|
-
parent = parent.parentElement;
|
|
3115
|
-
}
|
|
3116
|
-
return false;
|
|
3117
|
-
}
|
|
3118
|
-
/**
|
|
3119
|
-
* Returns true if the dropzone with `childId` is a DOM descendant of the
|
|
3120
|
-
* dropzone with `parentId`. Used to decide whether the cursor was already
|
|
3121
|
-
* "inside" a root section before triggering root-escape.
|
|
3122
|
-
*/
|
|
3123
|
-
_isDescendantDropzone(childId, parentId) {
|
|
3124
|
-
if (!childId)
|
|
3125
|
-
return false;
|
|
3126
|
-
const childEl = this._getEl(childId);
|
|
3127
|
-
const parentEl = this._getEl(parentId);
|
|
3128
|
-
if (!childEl || !parentEl)
|
|
3129
|
-
return false;
|
|
3130
|
-
return parentEl.contains(childEl);
|
|
3131
|
-
}
|
|
3132
3276
|
// ─────────────────────────────────────────────────────────
|
|
3133
3277
|
// Insertion indicator (animates open so siblings shift smoothly)
|
|
3134
3278
|
// ─────────────────────────────────────────────────────────
|
|
@@ -3281,12 +3425,17 @@ class DragEngineService {
|
|
|
3281
3425
|
}
|
|
3282
3426
|
return children.length;
|
|
3283
3427
|
}
|
|
3284
|
-
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 }); }
|
|
3285
3429
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService }); }
|
|
3286
3430
|
}
|
|
3287
3431
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: DragEngineService, decorators: [{
|
|
3288
3432
|
type: Injectable
|
|
3289
|
-
}], 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
|
+
}] }] });
|
|
3290
3439
|
|
|
3291
3440
|
/**
|
|
3292
3441
|
* Page-scoped registry that holds the current page's data sources.
|
|
@@ -5992,9 +6141,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
|
|
|
5992
6141
|
}] } });
|
|
5993
6142
|
|
|
5994
6143
|
class LayerTreeComponent {
|
|
5995
|
-
constructor(registry, overlayService) {
|
|
6144
|
+
constructor(registry, overlayService, dragEngine) {
|
|
5996
6145
|
this.registry = registry;
|
|
5997
6146
|
this.overlayService = overlayService;
|
|
6147
|
+
this.dragEngine = dragEngine;
|
|
5998
6148
|
this.layout = [];
|
|
5999
6149
|
this.globalElements = [];
|
|
6000
6150
|
this.nodeSelect = new EventEmitter();
|
|
@@ -6102,6 +6252,13 @@ class LayerTreeComponent {
|
|
|
6102
6252
|
const targetTreeNode = this.flatTree[event.currentIndex];
|
|
6103
6253
|
if (!targetTreeNode)
|
|
6104
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
|
+
}
|
|
6105
6262
|
// Remove from source parent
|
|
6106
6263
|
const srcArray = draggedTreeNode.parentArray;
|
|
6107
6264
|
const srcIdx = draggedTreeNode.indexInParent;
|
|
@@ -6165,13 +6322,13 @@ class LayerTreeComponent {
|
|
|
6165
6322
|
}
|
|
6166
6323
|
}
|
|
6167
6324
|
}
|
|
6168
|
-
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 }); }
|
|
6169
|
-
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"] }] }); }
|
|
6170
6327
|
}
|
|
6171
6328
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: LayerTreeComponent, decorators: [{
|
|
6172
6329
|
type: Component,
|
|
6173
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"] }]
|
|
6174
|
-
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }], propDecorators: { layout: [{
|
|
6331
|
+
}], ctorParameters: () => [{ type: ComponentRegistryService }, { type: OverlayService }, { type: DragEngineService }], propDecorators: { layout: [{
|
|
6175
6332
|
type: Input
|
|
6176
6333
|
}], globalElements: [{
|
|
6177
6334
|
type: Input
|
|
@@ -7012,6 +7169,9 @@ class BuilderComponent {
|
|
|
7012
7169
|
}
|
|
7013
7170
|
onDrop(event) {
|
|
7014
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;
|
|
7015
7175
|
if (payload.type === 'external') {
|
|
7016
7176
|
if (typeof payload.data === 'string') {
|
|
7017
7177
|
const config = this.registry.createConfig(payload.data);
|
|
@@ -7302,7 +7462,7 @@ class BuilderComponent {
|
|
|
7302
7462
|
}
|
|
7303
7463
|
case ToolbarAction.Duplicate: {
|
|
7304
7464
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
7305
|
-
if (result) {
|
|
7465
|
+
if (result && !this.dragEngine.refuseInsert(node, this.siblingAncestors(node))) {
|
|
7306
7466
|
const clone = this.deepCloneWithNewIds(node);
|
|
7307
7467
|
result.parent.splice(result.index + 1, 0, clone);
|
|
7308
7468
|
this.cdr.detectChanges();
|
|
@@ -7462,6 +7622,8 @@ class BuilderComponent {
|
|
|
7462
7622
|
const pastedNode = this.clipboard.getNode();
|
|
7463
7623
|
if (!pastedNode)
|
|
7464
7624
|
break;
|
|
7625
|
+
if (this.dragEngine.refuseInsert(pastedNode, this.siblingAncestors(node)))
|
|
7626
|
+
break;
|
|
7465
7627
|
const clone = this.deepCloneWithNewIds(pastedNode);
|
|
7466
7628
|
const result = this.findNodeInTree(node, this.layout) ?? this.findNodeInTree(node, this.globalElements);
|
|
7467
7629
|
if (result) {
|
|
@@ -7544,6 +7706,10 @@ class BuilderComponent {
|
|
|
7544
7706
|
this.cdr.markForCheck();
|
|
7545
7707
|
}, 1200);
|
|
7546
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
|
+
}
|
|
7547
7713
|
findNodeInTree(target, children) {
|
|
7548
7714
|
for (let i = 0; i < children.length; i++) {
|
|
7549
7715
|
if (children[i] === target) {
|
|
@@ -7637,7 +7803,8 @@ class BuilderComponent {
|
|
|
7637
7803
|
if (inGlobals && !target.parent) {
|
|
7638
7804
|
target = resolveInsertTarget(this.layout, null, n => this.dragEngine.isDropContainer(n.children));
|
|
7639
7805
|
}
|
|
7640
|
-
|
|
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))
|
|
7641
7808
|
return;
|
|
7642
7809
|
this.cdr.detectChanges();
|
|
7643
7810
|
}
|
|
@@ -11676,5 +11843,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
|
|
|
11676
11843
|
* Generated bundle index. Do not edit.
|
|
11677
11844
|
*/
|
|
11678
11845
|
|
|
11679
|
-
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 };
|
|
11680
11847
|
//# sourceMappingURL=vectoriox-iox-builder.mjs.map
|