@vectoriox/iox-builder 1.5.8 → 1.5.10

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.
@@ -1277,10 +1277,12 @@ class InteractionEngineService {
1277
1277
  return;
1278
1278
  const cleanups = [];
1279
1279
  for (const ix of node.interactions) {
1280
- // Pre-state: hide target elements before their entrance animation fires.
1281
- // In builder mode: skip only suppressed triggers (pageLoad, scrollProgress).
1282
- // viewportEnter IS active in builder — elements must be hidden before trigger.
1283
- const skipPreState = inBuilder && (ix.trigger === 'pageLoad' || ix.trigger === 'scrollProgress');
1280
+ // Pre-state: hide target elements before their entrance animation fires so
1281
+ // the user sees a real hidden→visible animation on the canvas. Every trigger
1282
+ // now runs live in the builder (see attachInteraction), so pre-state is applied
1283
+ // for all of them EXCEPT scrollProgress — that trigger is unimplemented (no-op),
1284
+ // so pre-stating it would leave the element stuck at opacity:0 forever.
1285
+ const skipPreState = inBuilder && ix.trigger === 'scrollProgress';
1284
1286
  if (!skipPreState) {
1285
1287
  for (const action of ix.actions) {
1286
1288
  if (this.ENTRANCE_TYPES.has(action.type)) {
@@ -1407,16 +1409,13 @@ class InteractionEngineService {
1407
1409
  }
1408
1410
  // ── Private ──────────────────────────────────────────────
1409
1411
  attachInteraction(node, ix, element) {
1410
- // In builder mode, scroll-based and time-based triggers don't auto-fire — use the
1411
- // play button instead. viewportEnter is skipped because the IntersectionObserver
1412
- // fires immediately (element is visible in the canvas) and its WAAPI animation +
1413
- // the overlay ResizeObserver feedback loop triggers NG0103 during synchronize().
1414
- // Click/hover remain active so users can test those interactions on the canvas.
1415
- if (this.isBuilderMode() && (ix.trigger === 'pageLoad' ||
1416
- ix.trigger === 'scrollProgress' ||
1417
- ix.trigger === 'viewportEnter')) {
1418
- return () => { };
1419
- }
1412
+ // All triggers run live in the builder canvas so the user sees the real
1413
+ // animation (pageLoad on attach, viewportEnter on scroll-into-view, click/hover).
1414
+ // The former NG0103 crash was caused by (a) Swiper's ResizeObserver created inside
1415
+ // the Angular zone — fixed in SliderContainerComponent via runOutsideAngular — and
1416
+ // (b) the overlay's Resize/MutationObservers synchronously re-dirtying the view
1417
+ // during synchronize() — fixed by coalescing overlay updates through a RAF.
1418
+ // scrollProgress is still unimplemented and returns a no-op below.
1420
1419
  switch (ix.trigger) {
1421
1420
  case 'pageLoad':
1422
1421
  return this.attachPageLoad(node, ix, element);
@@ -2972,12 +2971,20 @@ class OverlayComponent {
2972
2971
  // Tracks which badge was last clicked — used by Cmd+C in page-ui.
2973
2972
  this.activeBadgeType = null;
2974
2973
  this.subs = [];
2975
- this.boundUpdate = () => this.updateAll();
2976
- this._scrollRaf = 0;
2977
- this.boundScrollUpdate = () => {
2978
- cancelAnimationFrame(this._scrollRaf);
2979
- this._scrollRaf = requestAnimationFrame(() => this.updateAll());
2974
+ // All overlay repositioning is funnelled through a single coalesced RAF.
2975
+ // Observer callbacks (Resize/Mutation) MUST NOT call updateAll()/markForCheck()
2976
+ // synchronously — during a live interaction animation they can fire while
2977
+ // Angular is inside ApplicationRef.synchronize(), re-dirtying the view within
2978
+ // the same loop and blowing past MAX_SYNC_RUNS → NG0103. scheduleUpdate() defers
2979
+ // the markForCheck to the next frame, outside the current synchronize() pass,
2980
+ // and collapses a burst of notifications into one update per frame.
2981
+ this._updateRaf = 0;
2982
+ this.scheduleUpdate = () => {
2983
+ cancelAnimationFrame(this._updateRaf);
2984
+ this._updateRaf = requestAnimationFrame(() => this.updateAll());
2980
2985
  };
2986
+ this.boundUpdate = () => this.scheduleUpdate();
2987
+ this.boundScrollUpdate = () => this.scheduleUpdate();
2981
2988
  this.activeScrollContainer = null;
2982
2989
  }
2983
2990
  ngOnInit() {
@@ -2997,16 +3004,16 @@ class OverlayComponent {
2997
3004
  // Re-calculate overlay positions whenever zoom / device changes.
2998
3005
  this.subs.push(this.viewportService.state$.subscribe(() => {
2999
3006
  // Delay one frame so the DOM has applied the new transform/width.
3000
- requestAnimationFrame(() => this.updateAll());
3007
+ this.scheduleUpdate();
3001
3008
  }));
3002
3009
  // Re-calculate when CSS rules change (margin/width applied via stylesheet).
3003
- this.subs.push(this.styleRegistry.changes$.subscribe(() => requestAnimationFrame(() => this.updateAll())));
3010
+ this.subs.push(this.styleRegistry.changes$.subscribe(() => this.scheduleUpdate()));
3004
3011
  // Watch the canvas container for size changes (e.g. right panel open/close
3005
3012
  // transition changes canvas width, shifting element positions).
3006
3013
  const container = this.overlayService.getContainer();
3007
3014
  if (container) {
3008
3015
  if (typeof ResizeObserver !== 'undefined') {
3009
- this.containerResizeObserver = new ResizeObserver(() => this.updateAll());
3016
+ this.containerResizeObserver = new ResizeObserver(this.scheduleUpdate);
3010
3017
  this.containerResizeObserver.observe(container);
3011
3018
  }
3012
3019
  }
@@ -3030,7 +3037,7 @@ class OverlayComponent {
3030
3037
  this.resizeObserver?.disconnect();
3031
3038
  this.containerResizeObserver?.disconnect();
3032
3039
  this.mutationObserver?.disconnect();
3033
- cancelAnimationFrame(this._scrollRaf);
3040
+ cancelAnimationFrame(this._updateRaf);
3034
3041
  window.removeEventListener('resize', this.boundUpdate);
3035
3042
  this.activeScrollContainer?.removeEventListener('scroll', this.boundScrollUpdate);
3036
3043
  this.activeScrollContainer = null;
@@ -3296,12 +3303,12 @@ class OverlayComponent {
3296
3303
  skipFirst = false;
3297
3304
  return;
3298
3305
  }
3299
- this.updateAll();
3306
+ this.scheduleUpdate();
3300
3307
  });
3301
3308
  this.resizeObserver.observe(styledEl);
3302
3309
  }
3303
3310
  // MutationObserver — catches style attribute changes (margin, padding etc.)
3304
- this.mutationObserver = new MutationObserver(() => this.updateAll());
3311
+ this.mutationObserver = new MutationObserver(this.scheduleUpdate);
3305
3312
  this.mutationObserver.observe(styledEl, {
3306
3313
  attributes: true,
3307
3314
  attributeFilter: ['style', 'class'],