@vectoriox/iox-builder 1.5.9 → 1.6.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.
@@ -271,6 +271,24 @@ class OverlayService {
271
271
  }
272
272
  return undefined;
273
273
  }
274
+ /**
275
+ * Walk up the DOM from a node's registered host element to the nearest ANCESTOR
276
+ * element that is itself a registered node. Used to resolve the immediate parent
277
+ * for relational style states (e.g. parent-hover). Returns undefined for root nodes.
278
+ */
279
+ getParentEntry(node) {
280
+ const ref = this.nodeMap.get(node);
281
+ if (!ref)
282
+ return undefined;
283
+ let el = ref.element.parentElement;
284
+ while (el) {
285
+ const match = this.findNodeByElement(el);
286
+ if (match)
287
+ return match;
288
+ el = el.parentElement;
289
+ }
290
+ return undefined;
291
+ }
274
292
  setHover(element, componentName, mode) {
275
293
  if (this.selectSubject.getValue()?.element === element)
276
294
  return;
@@ -1277,16 +1295,12 @@ class InteractionEngineService {
1277
1295
  return;
1278
1296
  const cleanups = [];
1279
1297
  for (const ix of node.interactions) {
1280
- // Pre-state: hide target elements before their entrance animation fires.
1281
- // In builder mode, pre-state is skipped for every SUPPRESSED trigger
1282
- // (pageLoad, scrollProgress, viewportEnter — see attachInteraction) so the
1283
- // element appears normally on the canvas. If we applied pre-state without
1284
- // attaching the trigger, the element would stay opacity:0 forever. The play
1285
- // button (_replay) re-applies pre-state on demand for an accurate preview.
1286
- // click/hover keep pre-state because those triggers DO fire on the canvas.
1287
- const skipPreState = inBuilder && (ix.trigger === 'pageLoad' ||
1288
- ix.trigger === 'scrollProgress' ||
1289
- ix.trigger === 'viewportEnter');
1298
+ // Pre-state: hide target elements before their entrance animation fires so
1299
+ // the user sees a real hidden→visible animation on the canvas. Every trigger
1300
+ // now runs live in the builder (see attachInteraction), so pre-state is applied
1301
+ // for all of them EXCEPT scrollProgress — that trigger is unimplemented (no-op),
1302
+ // so pre-stating it would leave the element stuck at opacity:0 forever.
1303
+ const skipPreState = inBuilder && ix.trigger === 'scrollProgress';
1290
1304
  if (!skipPreState) {
1291
1305
  for (const action of ix.actions) {
1292
1306
  if (this.ENTRANCE_TYPES.has(action.type)) {
@@ -1413,16 +1427,13 @@ class InteractionEngineService {
1413
1427
  }
1414
1428
  // ── Private ──────────────────────────────────────────────
1415
1429
  attachInteraction(node, ix, element) {
1416
- // In builder mode, scroll-based and time-based triggers don't auto-fire — use the
1417
- // play button instead. viewportEnter is skipped because the IntersectionObserver
1418
- // fires immediately (element is visible in the canvas) and its WAAPI animation +
1419
- // the overlay ResizeObserver feedback loop triggers NG0103 during synchronize().
1420
- // Click/hover remain active so users can test those interactions on the canvas.
1421
- if (this.isBuilderMode() && (ix.trigger === 'pageLoad' ||
1422
- ix.trigger === 'scrollProgress' ||
1423
- ix.trigger === 'viewportEnter')) {
1424
- return () => { };
1425
- }
1430
+ // All triggers run live in the builder canvas so the user sees the real
1431
+ // animation (pageLoad on attach, viewportEnter on scroll-into-view, click/hover).
1432
+ // The former NG0103 crash was caused by (a) Swiper's ResizeObserver created inside
1433
+ // the Angular zone — fixed in SliderContainerComponent via runOutsideAngular — and
1434
+ // (b) the overlay's Resize/MutationObservers synchronously re-dirtying the view
1435
+ // during synchronize() — fixed by coalescing overlay updates through a RAF.
1436
+ // scrollProgress is still unimplemented and returns a no-op below.
1426
1437
  switch (ix.trigger) {
1427
1438
  case 'pageLoad':
1428
1439
  return this.attachPageLoad(node, ix, element);
@@ -2005,7 +2016,17 @@ const EASING_OPTIONS = ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'];
2005
2016
  const INTERACTION_STATES = ['hover', 'active', 'focus'];
2006
2017
  /** Structural pseudo-classes — compiled to .iox-outer-{id}:state */
2007
2018
  const STRUCTURAL_STATES = ['first-child', 'last-child', 'nth-child(odd)', 'nth-child(even)'];
2008
- const SUPPORTED_STATES = [...INTERACTION_STATES, ...STRUCTURAL_STATES];
2019
+ /**
2020
+ * Relational pseudo-classes — the styled element is a descendant, the pseudo-class
2021
+ * lives on an ANCESTOR. Compiled to `.iox-node-{parentId}:{base} .iox-node-{id} { … }`,
2022
+ * so they require the ancestor's css id (`relativeToId`) at compile time.
2023
+ * `RELATIONAL_STATE_BASE` maps each relational state to the ancestor pseudo-class.
2024
+ */
2025
+ const RELATIONAL_STATES = ['parent-hover'];
2026
+ const RELATIONAL_STATE_BASE = {
2027
+ 'parent-hover': 'hover',
2028
+ };
2029
+ const SUPPORTED_STATES = [...INTERACTION_STATES, ...STRUCTURAL_STATES, ...RELATIONAL_STATES];
2009
2030
  /**
2010
2031
  * Build a full StyleTraitGroup[] schema populated with values from a flat
2011
2032
  * styleProps map. Used when editing a preset in the Style panel — the panel
@@ -2475,19 +2496,38 @@ class StyleRegistryService {
2475
2496
  *
2476
2497
  * @param nodeId The node's CSS id (used in `.iox-node-{nodeId}`)
2477
2498
  * @param styles Flat CSS property map (camelCase keys)
2478
- * @param state Optional pseudo-class state ('hover' | 'active' | 'focus').
2499
+ * @param state Optional pseudo-class state ('hover' | 'active' | 'focus' | …).
2479
2500
  * When provided, compiles to `.iox-node-{id}:{state} { … }`.
2480
2501
  * State rules are NOT partitioned into inner/outer.
2502
+ * @param relativeToId Ancestor css id, required for RELATIONAL states
2503
+ * (e.g. 'parent-hover'). Compiles to
2504
+ * `.iox-node-{relativeToId}:{base} .iox-node-{nodeId} { … }`.
2481
2505
  */
2482
- upsert(nodeId, styles, state) {
2506
+ upsert(nodeId, styles, state, relativeToId) {
2483
2507
  if (!nodeId)
2484
2508
  return;
2485
2509
  if (state) {
2510
+ const key = `${nodeId}:${state}`;
2511
+ // Relational states (parent-hover): the pseudo-class lives on an ANCESTOR
2512
+ // and cascades to this element. Requires the ancestor's css id.
2513
+ if (RELATIONAL_STATES.includes(state)) {
2514
+ const base = RELATIONAL_STATE_BASE[state];
2515
+ const css = (relativeToId && base)
2516
+ ? this.compile(`iox-node-${relativeToId}:${base} .iox-node-${nodeId}`, styles)
2517
+ : '';
2518
+ if (css) {
2519
+ this.rules.set(key, css);
2520
+ }
2521
+ else {
2522
+ this.rules.delete(key);
2523
+ }
2524
+ this.flush();
2525
+ return;
2526
+ }
2486
2527
  // Structural pseudo-classes (first-child, last-child, nth-child) target the
2487
2528
  // outer wrapper element so they participate correctly in grid/flex layout.
2488
2529
  const prefix = STRUCTURAL_STATES.includes(state) ? 'iox-outer' : 'iox-node';
2489
2530
  const css = this.compile(`${prefix}-${nodeId}:${state}`, styles);
2490
- const key = `${nodeId}:${state}`;
2491
2531
  if (css) {
2492
2532
  this.rules.set(key, css);
2493
2533
  }
@@ -2978,12 +3018,20 @@ class OverlayComponent {
2978
3018
  // Tracks which badge was last clicked — used by Cmd+C in page-ui.
2979
3019
  this.activeBadgeType = null;
2980
3020
  this.subs = [];
2981
- this.boundUpdate = () => this.updateAll();
2982
- this._scrollRaf = 0;
2983
- this.boundScrollUpdate = () => {
2984
- cancelAnimationFrame(this._scrollRaf);
2985
- this._scrollRaf = requestAnimationFrame(() => this.updateAll());
3021
+ // All overlay repositioning is funnelled through a single coalesced RAF.
3022
+ // Observer callbacks (Resize/Mutation) MUST NOT call updateAll()/markForCheck()
3023
+ // synchronously — during a live interaction animation they can fire while
3024
+ // Angular is inside ApplicationRef.synchronize(), re-dirtying the view within
3025
+ // the same loop and blowing past MAX_SYNC_RUNS → NG0103. scheduleUpdate() defers
3026
+ // the markForCheck to the next frame, outside the current synchronize() pass,
3027
+ // and collapses a burst of notifications into one update per frame.
3028
+ this._updateRaf = 0;
3029
+ this.scheduleUpdate = () => {
3030
+ cancelAnimationFrame(this._updateRaf);
3031
+ this._updateRaf = requestAnimationFrame(() => this.updateAll());
2986
3032
  };
3033
+ this.boundUpdate = () => this.scheduleUpdate();
3034
+ this.boundScrollUpdate = () => this.scheduleUpdate();
2987
3035
  this.activeScrollContainer = null;
2988
3036
  }
2989
3037
  ngOnInit() {
@@ -3003,16 +3051,16 @@ class OverlayComponent {
3003
3051
  // Re-calculate overlay positions whenever zoom / device changes.
3004
3052
  this.subs.push(this.viewportService.state$.subscribe(() => {
3005
3053
  // Delay one frame so the DOM has applied the new transform/width.
3006
- requestAnimationFrame(() => this.updateAll());
3054
+ this.scheduleUpdate();
3007
3055
  }));
3008
3056
  // Re-calculate when CSS rules change (margin/width applied via stylesheet).
3009
- this.subs.push(this.styleRegistry.changes$.subscribe(() => requestAnimationFrame(() => this.updateAll())));
3057
+ this.subs.push(this.styleRegistry.changes$.subscribe(() => this.scheduleUpdate()));
3010
3058
  // Watch the canvas container for size changes (e.g. right panel open/close
3011
3059
  // transition changes canvas width, shifting element positions).
3012
3060
  const container = this.overlayService.getContainer();
3013
3061
  if (container) {
3014
3062
  if (typeof ResizeObserver !== 'undefined') {
3015
- this.containerResizeObserver = new ResizeObserver(() => this.updateAll());
3063
+ this.containerResizeObserver = new ResizeObserver(this.scheduleUpdate);
3016
3064
  this.containerResizeObserver.observe(container);
3017
3065
  }
3018
3066
  }
@@ -3036,7 +3084,7 @@ class OverlayComponent {
3036
3084
  this.resizeObserver?.disconnect();
3037
3085
  this.containerResizeObserver?.disconnect();
3038
3086
  this.mutationObserver?.disconnect();
3039
- cancelAnimationFrame(this._scrollRaf);
3087
+ cancelAnimationFrame(this._updateRaf);
3040
3088
  window.removeEventListener('resize', this.boundUpdate);
3041
3089
  this.activeScrollContainer?.removeEventListener('scroll', this.boundScrollUpdate);
3042
3090
  this.activeScrollContainer = null;
@@ -3302,12 +3350,12 @@ class OverlayComponent {
3302
3350
  skipFirst = false;
3303
3351
  return;
3304
3352
  }
3305
- this.updateAll();
3353
+ this.scheduleUpdate();
3306
3354
  });
3307
3355
  this.resizeObserver.observe(styledEl);
3308
3356
  }
3309
3357
  // MutationObserver — catches style attribute changes (margin, padding etc.)
3310
- this.mutationObserver = new MutationObserver(() => this.updateAll());
3358
+ this.mutationObserver = new MutationObserver(this.scheduleUpdate);
3311
3359
  this.mutationObserver.observe(styledEl, {
3312
3360
  attributes: true,
3313
3361
  attributeFilter: ['style', 'class'],
@@ -7451,5 +7499,5 @@ class BuilderSpacerComponentConfig extends ComponentConfig {
7451
7499
  * Generated bundle index. Do not edit.
7452
7500
  */
7453
7501
 
7454
- export { ACTION_TYPE_OPTIONS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ComponentConfig, ComponentRegistryService, DEVICE_OPTIONS, DataSourceRegistryService, DataSourceType, DeviceMode, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS, EffectsGroupStyleConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_CONTENT_SERVICE, IOX_FONT_MANAGER, IOX_SHOP_SERVICE, InteractionEngineService, InteractionPresetRegistryService, InteractionsPanelComponent, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, 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, SymbolRegistryService, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VIRTUAL_TRAIT_KEYS, ViewportService, ZOOM_OPTIONS, buildFullStyleTraits, buildPresetStyleTraits, composeVirtualTraits, defaultPageSettings, flattenSchemaFields, generateNodeId, resolveTraitControllerType, resolveTraitOptions };
7502
+ export { ACTION_TYPE_OPTIONS, BackgroundGroupStyleConfig, BorderGroupStyleConfig, BuilderButtonBlockComponent, BuilderButtonComponentConfig, BuilderClipboardService, BuilderComponent, BuilderContainerComponent, BuilderContainerComponentConfig, BuilderDividerComponentConfig, BuilderHeadingComponentConfig, BuilderHistoryService, BuilderIconComponentConfig, BuilderImageComponentConfig, BuilderLinkComponentConfig, BuilderLinkedContainerComponent, BuilderLinkedContainerConfig, BuilderListComponentConfig, BuilderListItemComponentConfig, BuilderMode, BuilderRepeaterComponent, BuilderSpacerComponentConfig, ButtonBlockComponentConfig, CardComponentConfig, ComponentConfig, ComponentRegistryService, DEVICE_OPTIONS, DataSourceRegistryService, DataSourceType, DeviceMode, DimensionsGroupStyleConfig, DragEngineService, EASING_OPTIONS, EffectsGroupStyleConfig, GroupStyleConfig, INTERACTION_STATES, INVERSE_ACTION, IOX_CONTENT_SERVICE, IOX_FONT_MANAGER, IOX_SHOP_SERVICE, InteractionEngineService, InteractionPresetRegistryService, InteractionsPanelComponent, IoxBuilderModule, IoxDraggableDirective, IoxDropzoneDirective, LayerTreeComponent, LayoutGroupStyleConfig, ListGroupStyleConfig, NodeAction, OverlayComponent, OverlayService, PanelChildComponent, PanelComponent, PanelEventService, PanelEventTypes, PanelTypes, PositionGroupStyleConfig, PresetRegistryService, RELATIONAL_STATES, RELATIONAL_STATE_BASE, ROUTE_ANIMATION_OPTIONS, RenderDirective, RepeaterComponentConfig, SCREEN_WIDTH_OPTIONS, STRUCTURAL_STATES, SUPPORTED_STATES, SectionComponent, SectionComponentConfig, SizeGroupStyleConfig, SliderContainerComponent, SliderContainerConfig, SliderSlideComponent, SliderSlideConfig, SpacingGroupStyleConfig, StyleCategory, StyleRegistryService, TraitConfig as StyleTraitConfig, SymbolRegistryService, TRIGGER_OPTIONS, TextBlockComponentConfig, ToolbarAction, ToolbarComponent, TraitConfig, TraitInputType, TypographyGroupStyleConfig, UNITS_ALL, UNITS_DEG, UNITS_FIXED, UNITS_NO_VW, VIRTUAL_TRAIT_KEYS, ViewportService, ZOOM_OPTIONS, buildFullStyleTraits, buildPresetStyleTraits, composeVirtualTraits, defaultPageSettings, flattenSchemaFields, generateNodeId, resolveTraitControllerType, resolveTraitOptions };
7455
7503
  //# sourceMappingURL=vectoriox-iox-builder.mjs.map