@mozaic-ds/angular 2.1.2 → 2.1.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.
@@ -4950,7 +4950,11 @@ const VIEWPORT_HEIGHT_PX = 300;
4950
4950
  class MozComboboxComponent {
4951
4951
  _doc = inject(DOCUMENT);
4952
4952
  _overlay = inject(Overlay);
4953
- _cdr = inject(ChangeDetectorRef);
4953
+ _appRef = inject(ApplicationRef);
4954
+ /** rAF handle for the scroll-driven change-detection loop (see _kickViewportTick). */
4955
+ _viewportTickRaf = null;
4956
+ /** Timestamp of the last viewport scroll event, used to stop the tick loop when idle. */
4957
+ _lastViewportScrollTs = 0;
4954
4958
  /**
4955
4959
  * No-op scroll strategy: the dropdown embeds a CDK virtual-scroll viewport,
4956
4960
  * which is a global `CdkScrollable`. With the default `reposition` strategy,
@@ -5251,17 +5255,39 @@ class MozComboboxComponent {
5251
5255
  }
5252
5256
  }
5253
5257
  });
5254
- // Zoneless support: the CDK virtual-scroll viewport recomputes its rendered
5255
- // range on scroll events fired OUTSIDE the Angular zone. Without Zone.js,
5256
- // that never schedules change detection, so the rendered window stays frozen
5257
- // (the list appears to "bounce" and never reaches the bottom). Forcing a CD
5258
- // pass on each scroll keeps the virtual viewport in sync.
5258
+ // Zoneless support for the virtual-scroll viewport.
5259
+ //
5260
+ // The viewport lives inside the CDK connected-overlay portal, so it CANNOT
5261
+ // be reached through `viewChild()` (the portal renders in a separate view
5262
+ // container). On Angular/CDK 22 the viewport applies its content transform
5263
+ // from an `effect()` that only runs during an ApplicationRef tick; in
5264
+ // zoneless mode no tick is scheduled while the user scrolls the list, so the
5265
+ // rendered window freezes and the panel appears to "bounce" without ever
5266
+ // reaching the bottom. (CDK 21 used a different path and was unaffected,
5267
+ // which is why Storybook on 21 looked fine while the 22 consumer bounced.)
5268
+ //
5269
+ // Fix: while the dropdown is open, listen for scroll events on the viewport
5270
+ // in the capture phase (scroll does not bubble) and drive change detection
5271
+ // for a few frames after each scroll, restoring the per-frame CD that
5272
+ // Zone.js would otherwise provide.
5259
5273
  effect((onCleanup) => {
5260
- const viewport = this.viewportEl();
5261
- if (!viewport)
5274
+ if (!this.isOpen())
5262
5275
  return;
5263
- const sub = viewport.elementScrolled().subscribe(() => this._cdr.markForCheck());
5264
- onCleanup(() => sub.unsubscribe());
5276
+ const viewportId = `${this.comboboxId}-viewport`;
5277
+ const onScroll = (event) => {
5278
+ const target = event.target;
5279
+ if (target?.id === viewportId) {
5280
+ this._kickViewportTick();
5281
+ }
5282
+ };
5283
+ this._doc.addEventListener('scroll', onScroll, true);
5284
+ onCleanup(() => {
5285
+ this._doc.removeEventListener('scroll', onScroll, true);
5286
+ if (this._viewportTickRaf !== null) {
5287
+ cancelAnimationFrame(this._viewportTickRaf);
5288
+ this._viewportTickRaf = null;
5289
+ }
5290
+ });
5265
5291
  });
5266
5292
  // Inject global overlay styles — CDK teleports content outside the
5267
5293
  // component host so scoped CSS never reaches .cdk-overlay-pane.
@@ -5572,6 +5598,35 @@ class MozComboboxComponent {
5572
5598
  });
5573
5599
  }
5574
5600
  }
5601
+ /**
5602
+ * Drive change detection for a short window after a viewport scroll so the
5603
+ * CDK virtual-scroll viewport (inside the overlay portal) applies its content
5604
+ * transform in zoneless mode. Runs at most one rAF loop at a time; each new
5605
+ * scroll event extends the window, and the loop stops ~200ms after the last
5606
+ * scroll. `tick()` is guarded because it throws if called during an ongoing
5607
+ * change-detection pass (it never is here — rAF callbacks run outside CD).
5608
+ */
5609
+ _kickViewportTick() {
5610
+ this._lastViewportScrollTs = Date.now();
5611
+ if (this._viewportTickRaf !== null) {
5612
+ return;
5613
+ }
5614
+ const loop = () => {
5615
+ try {
5616
+ this._appRef.tick();
5617
+ }
5618
+ catch {
5619
+ // tick() called during an active CD pass — safe to skip this frame.
5620
+ }
5621
+ if (Date.now() - this._lastViewportScrollTs < 200) {
5622
+ this._viewportTickRaf = requestAnimationFrame(loop);
5623
+ }
5624
+ else {
5625
+ this._viewportTickRaf = null;
5626
+ }
5627
+ };
5628
+ this._viewportTickRaf = requestAnimationFrame(loop);
5629
+ }
5575
5630
  /** Coerce the current value into an array (safe for .some/.filter/.every). */
5576
5631
  _currentValueAsArray() {
5577
5632
  const raw = this.value();