@vectoriox/iox-builder 1.5.9 → 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,16 +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, 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');
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';
1290
1286
  if (!skipPreState) {
1291
1287
  for (const action of ix.actions) {
1292
1288
  if (this.ENTRANCE_TYPES.has(action.type)) {
@@ -1413,16 +1409,13 @@ class InteractionEngineService {
1413
1409
  }
1414
1410
  // ── Private ──────────────────────────────────────────────
1415
1411
  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
- }
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.
1426
1419
  switch (ix.trigger) {
1427
1420
  case 'pageLoad':
1428
1421
  return this.attachPageLoad(node, ix, element);
@@ -2978,12 +2971,20 @@ class OverlayComponent {
2978
2971
  // Tracks which badge was last clicked — used by Cmd+C in page-ui.
2979
2972
  this.activeBadgeType = null;
2980
2973
  this.subs = [];
2981
- this.boundUpdate = () => this.updateAll();
2982
- this._scrollRaf = 0;
2983
- this.boundScrollUpdate = () => {
2984
- cancelAnimationFrame(this._scrollRaf);
2985
- 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());
2986
2985
  };
2986
+ this.boundUpdate = () => this.scheduleUpdate();
2987
+ this.boundScrollUpdate = () => this.scheduleUpdate();
2987
2988
  this.activeScrollContainer = null;
2988
2989
  }
2989
2990
  ngOnInit() {
@@ -3003,16 +3004,16 @@ class OverlayComponent {
3003
3004
  // Re-calculate overlay positions whenever zoom / device changes.
3004
3005
  this.subs.push(this.viewportService.state$.subscribe(() => {
3005
3006
  // Delay one frame so the DOM has applied the new transform/width.
3006
- requestAnimationFrame(() => this.updateAll());
3007
+ this.scheduleUpdate();
3007
3008
  }));
3008
3009
  // Re-calculate when CSS rules change (margin/width applied via stylesheet).
3009
- this.subs.push(this.styleRegistry.changes$.subscribe(() => requestAnimationFrame(() => this.updateAll())));
3010
+ this.subs.push(this.styleRegistry.changes$.subscribe(() => this.scheduleUpdate()));
3010
3011
  // Watch the canvas container for size changes (e.g. right panel open/close
3011
3012
  // transition changes canvas width, shifting element positions).
3012
3013
  const container = this.overlayService.getContainer();
3013
3014
  if (container) {
3014
3015
  if (typeof ResizeObserver !== 'undefined') {
3015
- this.containerResizeObserver = new ResizeObserver(() => this.updateAll());
3016
+ this.containerResizeObserver = new ResizeObserver(this.scheduleUpdate);
3016
3017
  this.containerResizeObserver.observe(container);
3017
3018
  }
3018
3019
  }
@@ -3036,7 +3037,7 @@ class OverlayComponent {
3036
3037
  this.resizeObserver?.disconnect();
3037
3038
  this.containerResizeObserver?.disconnect();
3038
3039
  this.mutationObserver?.disconnect();
3039
- cancelAnimationFrame(this._scrollRaf);
3040
+ cancelAnimationFrame(this._updateRaf);
3040
3041
  window.removeEventListener('resize', this.boundUpdate);
3041
3042
  this.activeScrollContainer?.removeEventListener('scroll', this.boundScrollUpdate);
3042
3043
  this.activeScrollContainer = null;
@@ -3302,12 +3303,12 @@ class OverlayComponent {
3302
3303
  skipFirst = false;
3303
3304
  return;
3304
3305
  }
3305
- this.updateAll();
3306
+ this.scheduleUpdate();
3306
3307
  });
3307
3308
  this.resizeObserver.observe(styledEl);
3308
3309
  }
3309
3310
  // MutationObserver — catches style attribute changes (margin, padding etc.)
3310
- this.mutationObserver = new MutationObserver(() => this.updateAll());
3311
+ this.mutationObserver = new MutationObserver(this.scheduleUpdate);
3311
3312
  this.mutationObserver.observe(styledEl, {
3312
3313
  attributes: true,
3313
3314
  attributeFilter: ['style', 'class'],