@vectoriox/iox-builder 2.10.1 → 2.10.3
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.
|
@@ -404,6 +404,16 @@ class SizeGroupStyleConfig extends GroupStyleConfig {
|
|
|
404
404
|
// object-fit is a style trait (applies to replaced elements, e.g. images). Lives here so
|
|
405
405
|
// it flows through the CSS engine (styleProps) instead of a per-component inline binding.
|
|
406
406
|
new TraitConfig('objectFit', 'Object Fit', TraitInputType.Select, ['cover', 'contain', 'fill', 'none', 'scale-down'], 'cover'),
|
|
407
|
+
// overflow — how content that exceeds the box is handled. `visible` is the CSS initial
|
|
408
|
+
// (dropped as a no-op by the compiler); other values emit. Applies to the node's own box
|
|
409
|
+
// (inner element), so it is NOT an OUTER_PROP.
|
|
410
|
+
new TraitConfig('overflow', 'Overflow', TraitInputType.Select, [
|
|
411
|
+
{ label: 'Visible', value: 'visible' },
|
|
412
|
+
{ label: 'Hidden', value: 'hidden' },
|
|
413
|
+
{ label: 'Scroll', value: 'scroll' },
|
|
414
|
+
{ label: 'Auto', value: 'auto' },
|
|
415
|
+
{ label: 'Clip', value: 'clip' },
|
|
416
|
+
], 'visible'),
|
|
407
417
|
]);
|
|
408
418
|
}
|
|
409
419
|
}
|
|
@@ -535,6 +545,19 @@ class EffectsGroupStyleConfig extends GroupStyleConfig {
|
|
|
535
545
|
new TraitConfig('backdropBrightness', 'Brightness', TraitInputType.Range, { min: 0, max: 3, step: 0.05, showExtremes: false, showValue: true, compact: true }, '1', true),
|
|
536
546
|
new TraitConfig('backdropContrast', 'Contrast', TraitInputType.Range, { min: 0, max: 3, step: 0.05, showExtremes: false, showValue: true, compact: true }, '1', true),
|
|
537
547
|
new TraitConfig('backdropSaturate', 'Saturate', TraitInputType.Range, { min: 0, max: 3, step: 0.05, showExtremes: false, showValue: true, compact: true }, '1', true),
|
|
548
|
+
// ── Clip (clip-path — mask the node to a shape) ───────────
|
|
549
|
+
new TraitConfig('_sep_clip', 'Clip', TraitInputType.SectionLabel),
|
|
550
|
+
new TraitConfig('clipPath', 'Shape', TraitInputType.Select, [
|
|
551
|
+
{ label: 'None', value: 'none' },
|
|
552
|
+
{ label: 'Circle', value: 'circle(50%)' },
|
|
553
|
+
{ label: 'Ellipse', value: 'ellipse(50% 40%)' },
|
|
554
|
+
{ label: 'Inset', value: 'inset(10%)' },
|
|
555
|
+
{ label: 'Rounded inset', value: 'inset(10% round 16px)' },
|
|
556
|
+
{ label: 'Triangle', value: 'polygon(50% 0%, 0% 100%, 100% 100%)' },
|
|
557
|
+
{ label: 'Diamond', value: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)' },
|
|
558
|
+
{ label: 'Hexagon', value: 'polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%)' },
|
|
559
|
+
{ label: 'Chevron', value: 'polygon(0 0, 75% 0, 100% 50%, 75% 100%, 0 100%, 25% 50%)' },
|
|
560
|
+
], 'none'),
|
|
538
561
|
// ── Transition (virtual — composed to CSS transition: all …) ──
|
|
539
562
|
new TraitConfig('_sep_transition', 'Transition', TraitInputType.SectionLabel),
|
|
540
563
|
new TraitConfig('transitionDuration', 'Duration', TraitInputType.Scrub, { min: 0, max: 3000, step: 50, units: ['ms'] }, '0ms', true),
|
|
@@ -654,6 +677,65 @@ function arrayItemSchemaFields(properties, arrayPath) {
|
|
|
654
677
|
return { fields: [], isPrimitive: true };
|
|
655
678
|
}
|
|
656
679
|
|
|
680
|
+
const freshId = () => Math.random().toString(36).substr(2, 9);
|
|
681
|
+
/**
|
|
682
|
+
* Assign fresh ids to a cloned ComponentNode subtree AND remap intra-subtree references to the
|
|
683
|
+
* new ids. Used by every author-time clone path (duplicate, paste, block/external drop) so a copied
|
|
684
|
+
* structure stays self-consistent.
|
|
685
|
+
*
|
|
686
|
+
* Absolute node-id references that break under duplication if left unmapped:
|
|
687
|
+
* - `stateTriggers[state]` — the trigger-hover target (see style-engine.md → Targeted states)
|
|
688
|
+
* - interaction `action.target` / `reverseActions.target`
|
|
689
|
+
* A reference pointing OUTSIDE the cloned subtree (id not in the map) is left untouched — the copy
|
|
690
|
+
* intentionally still points at the external node.
|
|
691
|
+
*
|
|
692
|
+
* `styleId` strategy:
|
|
693
|
+
* - `'clear'` (default) — drop styleId so the clone OWNS its own CSS rule (independent copy).
|
|
694
|
+
* - `'regenerate'` — keep a styleId but give it a fresh value (legacy builder.component path).
|
|
695
|
+
*/
|
|
696
|
+
function regenerateNodeIds(root, opts = {}) {
|
|
697
|
+
const idMap = new Map();
|
|
698
|
+
const assign = (node) => {
|
|
699
|
+
const oldId = node.id;
|
|
700
|
+
node.id = freshId();
|
|
701
|
+
if (opts.styleId === 'regenerate') {
|
|
702
|
+
if (node.styleId)
|
|
703
|
+
node.styleId = freshId();
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
node.styleId = undefined;
|
|
707
|
+
}
|
|
708
|
+
if (oldId)
|
|
709
|
+
idMap.set(oldId, node.id);
|
|
710
|
+
(node.children || []).forEach(assign);
|
|
711
|
+
};
|
|
712
|
+
assign(root);
|
|
713
|
+
remapNodeReferences(root, idMap);
|
|
714
|
+
}
|
|
715
|
+
/** Remap absolute node-id references inside a subtree to their new ids (per `idMap`). Ids not in
|
|
716
|
+
* the map (references to nodes outside the cloned subtree) are preserved. */
|
|
717
|
+
function remapNodeReferences(node, idMap) {
|
|
718
|
+
const triggers = node.stateTriggers;
|
|
719
|
+
if (triggers) {
|
|
720
|
+
for (const state of Object.keys(triggers)) {
|
|
721
|
+
const mapped = idMap.get(triggers[state]);
|
|
722
|
+
if (mapped)
|
|
723
|
+
triggers[state] = mapped;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
for (const interaction of node.interactions || []) {
|
|
727
|
+
const actions = [...(interaction.actions || []), ...(interaction.reverseActions || [])];
|
|
728
|
+
for (const action of actions) {
|
|
729
|
+
if (action?.target && action.target !== 'self') {
|
|
730
|
+
const mapped = idMap.get(action.target);
|
|
731
|
+
if (mapped)
|
|
732
|
+
action.target = mapped;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
(node.children || []).forEach(child => remapNodeReferences(child, idMap));
|
|
737
|
+
}
|
|
738
|
+
|
|
657
739
|
/**
|
|
658
740
|
* Locate a node within a forest (array of root nodes + their descendants) by
|
|
659
741
|
* reference identity. Returns the parent array holding the node and its index,
|
|
@@ -1093,14 +1175,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
1093
1175
|
|
|
1094
1176
|
function deepCloneNode(node) {
|
|
1095
1177
|
const clone = JSON.parse(JSON.stringify(node));
|
|
1096
|
-
|
|
1178
|
+
// Fresh ids + remap intra-subtree references (stateTriggers, interaction targets) so a cloned
|
|
1179
|
+
// structure stays self-consistent — e.g. trigger-hover keeps pointing at the clone's own trigger.
|
|
1180
|
+
regenerateNodeIds(clone);
|
|
1097
1181
|
return clone;
|
|
1098
1182
|
}
|
|
1099
|
-
function regenerateIds(node) {
|
|
1100
|
-
node.id = Math.random().toString(36).substr(2, 9);
|
|
1101
|
-
node.styleId = undefined;
|
|
1102
|
-
(node.children || []).forEach(regenerateIds);
|
|
1103
|
-
}
|
|
1104
1183
|
class DragEngineService {
|
|
1105
1184
|
constructor(registry, symbolRegistry) {
|
|
1106
1185
|
this.registry = registry;
|
|
@@ -3342,6 +3421,27 @@ class RenderDirective {
|
|
|
3342
3421
|
}
|
|
3343
3422
|
return undefined;
|
|
3344
3423
|
}
|
|
3424
|
+
/** Nearest ANCESTOR of the styled element that is a registered iox-node AND contains the trigger
|
|
3425
|
+
* element — i.e. the DOM equivalent of `nearestCommonAncestorId`, resolved per rendered instance.
|
|
3426
|
+
* Used to re-emit a `trigger-hover` rule after a move/duplicate. Returns undefined if the trigger
|
|
3427
|
+
* isn't in the DOM yet (caller then skips rather than clobbering the existing rule). */
|
|
3428
|
+
resolveTargetedScopeId(styledEl, triggerCssId) {
|
|
3429
|
+
let el = styledEl?.parentElement ?? null;
|
|
3430
|
+
while (el) {
|
|
3431
|
+
let cssId;
|
|
3432
|
+
for (let i = 0; i < el.classList.length; i++) {
|
|
3433
|
+
const c = el.classList[i];
|
|
3434
|
+
if (c.startsWith('iox-node-')) {
|
|
3435
|
+
cssId = c.slice('iox-node-'.length);
|
|
3436
|
+
break;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
if (cssId && el.querySelector(`.iox-node-${triggerCssId}`))
|
|
3440
|
+
return cssId;
|
|
3441
|
+
el = el.parentElement;
|
|
3442
|
+
}
|
|
3443
|
+
return undefined;
|
|
3444
|
+
}
|
|
3345
3445
|
findParentNodeElement(target) {
|
|
3346
3446
|
let el = target;
|
|
3347
3447
|
while (el) {
|
|
@@ -3423,6 +3523,21 @@ class RenderDirective {
|
|
|
3423
3523
|
continue;
|
|
3424
3524
|
this.styleRegistry.upsert(cssId, composeVirtualTraits(stateMap, initialStyle), state, relativeToId);
|
|
3425
3525
|
}
|
|
3526
|
+
else if (TARGETED_STATES.includes(state)) {
|
|
3527
|
+
// Targeted states (trigger-hover) compile to a `:has()` scoped to the nearest
|
|
3528
|
+
// common ancestor of the styled node + the chosen trigger. On a re-render (move,
|
|
3529
|
+
// duplicate, drop) we must RE-EMIT with a freshly-resolved scope — otherwise the
|
|
3530
|
+
// rule is clobbered and the hover stops working. Resolve the scope from the live
|
|
3531
|
+
// DOM (works per-instance); SKIP if the trigger isn't rendered yet, so we don't
|
|
3532
|
+
// clobber the correct rule page-ui's applyStateEnhancements already produced.
|
|
3533
|
+
const triggerCssId = node.stateTriggers?.[state];
|
|
3534
|
+
if (!triggerCssId)
|
|
3535
|
+
continue;
|
|
3536
|
+
const scopeId = this.resolveTargetedScopeId(childEl, triggerCssId);
|
|
3537
|
+
if (!scopeId)
|
|
3538
|
+
continue;
|
|
3539
|
+
this.styleRegistry.upsert(cssId, composeVirtualTraits(stateMap, initialStyle), state, scopeId, triggerCssId);
|
|
3540
|
+
}
|
|
3426
3541
|
else {
|
|
3427
3542
|
this.styleRegistry.upsert(cssId, composeVirtualTraits(stateMap, initialStyle), state);
|
|
3428
3543
|
}
|
|
@@ -5271,15 +5386,11 @@ class BuilderComponent {
|
|
|
5271
5386
|
}
|
|
5272
5387
|
deepCloneWithNewIds(node) {
|
|
5273
5388
|
const clone = JSON.parse(JSON.stringify(node));
|
|
5274
|
-
|
|
5389
|
+
// Fresh ids + remap intra-subtree references (stateTriggers, interaction targets) so a
|
|
5390
|
+
// duplicated/pasted structure stays self-consistent (trigger-hover keeps its own trigger).
|
|
5391
|
+
regenerateNodeIds(clone, { styleId: 'regenerate' });
|
|
5275
5392
|
return clone;
|
|
5276
5393
|
}
|
|
5277
|
-
assignNewIds(node) {
|
|
5278
|
-
node.id = Math.random().toString(36).slice(2, 10);
|
|
5279
|
-
if (node.styleId)
|
|
5280
|
-
node.styleId = Math.random().toString(36).slice(2, 10);
|
|
5281
|
-
node.children?.forEach(child => this.assignNewIds(child));
|
|
5282
|
-
}
|
|
5283
5394
|
_stripGlobalFlags(node) {
|
|
5284
5395
|
node.isGlobal = false;
|
|
5285
5396
|
node.globalPosition = undefined;
|