@ceriousdevtech/ngx-cerious-scroll 1.0.0 → 1.0.2

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.
@@ -138,7 +138,13 @@ class CeriousScrollDirective {
138
138
  this.ceriousScrollReady = new EventEmitter();
139
139
  this.hostRef = null;
140
140
  this.viewportSub = null;
141
+ this.scheduledRenderFrame = null;
141
142
  this.viewByContainer = new Map();
143
+ // Pool of views detached because their container left the viewport. Reusing
144
+ // them on subsequent renders avoids destroying + recreating the entire row
145
+ // component tree on fast scrolls (where the engine's element pool wipes
146
+ // textContent on reused containers, so view rootNodes get orphaned).
147
+ this.freeViews = [];
142
148
  }
143
149
  ngAfterViewInit() {
144
150
  this.ensureInitialized();
@@ -148,10 +154,33 @@ class CeriousScrollDirective {
148
154
  return;
149
155
  if (changes['ceriousScrollItems'] || changes['ceriousScrollTotalElements']) {
150
156
  const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);
151
- this.hostRef.scroller.totalElements = total;
152
- this.hostRef.scroller.clearAllCaches();
153
- if (this.ceriousScrollAutoRender)
157
+ const countChanged = this.hostRef.scroller.totalElements !== total;
158
+ if (countChanged) {
159
+ // The dataset size changed: the ViewportRenderer stores its own copy of
160
+ // totalElements (set by value at construction) so patching the engine's
161
+ // public property alone leaves the renderer's internal bound stale. The
162
+ // renderer would then use the old count for its viewport-fill loop and
163
+ // bottom-boundary scan, producing phantom renders at out-of-bounds
164
+ // indices (undefined items → 0-height rows → the fill loop never
165
+ // satisfies its height condition → hundreds of renderer callbacks).
166
+ // Recreating the engine gives both the engine and the renderer a fresh,
167
+ // consistent count. ensureInitialized() schedules the first render via
168
+ // queueMicrotask, which runs after the current CD cycle completes.
169
+ this.recreate();
170
+ }
171
+ else if (this.ceriousScrollAutoRender) {
172
+ // Same count, new data reference (e.g. an immutable edit or a sort that
173
+ // happens to keep the same length). The engine reuses the DOM element it
174
+ // already rendered for each overlapping index without re-invoking the
175
+ // renderer, so update the content of every currently-visible row IN
176
+ // PLACE before calling render(). This preserves each row's embedded
177
+ // view (so a focused textbox keeps focus/caret) and does NOT discard
178
+ // cached heights. If a row's height actually changes the engine's
179
+ // ResizeObserver keeps the cache correct on its own; for a wholesale
180
+ // height change across all rows, call recalculate() instead.
181
+ this.refreshRenderedContent();
154
182
  this.render();
183
+ }
155
184
  }
156
185
  if (changes['ceriousScrollOptions'] && !changes['ceriousScrollOptions'].firstChange) {
157
186
  // Options are consumed primarily at construction time.
@@ -159,6 +188,10 @@ class CeriousScrollDirective {
159
188
  }
160
189
  }
161
190
  ngOnDestroy() {
191
+ if (this.scheduledRenderFrame != null) {
192
+ cancelAnimationFrame(this.scheduledRenderFrame);
193
+ this.scheduledRenderFrame = null;
194
+ }
162
195
  this.viewportSub?.unsubscribe();
163
196
  this.viewportSub = null;
164
197
  this.destroyAllViews();
@@ -175,16 +208,122 @@ class CeriousScrollDirective {
175
208
  const hostContainer = this.host.nativeElement;
176
209
  const height = hostContainer.clientHeight || hostContainer.offsetHeight;
177
210
  const contentContainer = this.hostRef.contentElement;
211
+ // Track whether the engine asked us to bind any row this pass. Pure scroll
212
+ // frames where the visible row set doesn't change still drive render() via
213
+ // the rAF coalescer — there's no point in walking the prune map or
214
+ // emitting the viewport range when nothing was touched.
215
+ let rendererInvocations = 0;
178
216
  const renderer = (index, elementContainer) => {
179
- // Rendering must create Angular views inside the Angular zone.
180
- this.ngZone.run(() => {
181
- this.renderTemplateIntoContainer(template, index, elementContainer);
182
- });
217
+ rendererInvocations++;
218
+ // Render each row's embedded view with LOCAL change detection
219
+ // (`view.detectChanges()` inside `renderTemplateIntoContainer`) so the
220
+ // engine can measure its height during this pass. Do NOT wrap each row in
221
+ // its own `ngZone.run` — that fires a full `ApplicationRef` tick *per row*
222
+ // (O(newRows × visibleRows) work, janky drags). The single coalesced tick
223
+ // for the whole pass is handled by the caller (`scheduleRender`).
224
+ this.renderTemplateIntoContainer(template, index, elementContainer);
183
225
  };
184
226
  const range = this.hostRef.scroller.renderViewport(height, contentContainer, renderer);
185
- this.ngZone.run(() => this.ceriousScrollMeasuredViewport.emit(range));
227
+ if (rendererInvocations === 0) {
228
+ // Viewport didn't change — skip the prune walk and the (potentially
229
+ // zone-entering) emit.
230
+ return range;
231
+ }
232
+ // Destroy views whose container the engine no longer renders. Without this,
233
+ // every container the engine recycles into its element pool leaves its
234
+ // embedded view attached to ApplicationRef forever — so the attached-view
235
+ // list (and every O(n) `detachView`) grows without bound and scrolling gets
236
+ // progressively slower. Mirrors the React/Vue wrappers, which drop rows that
237
+ // fall out of `getRenderedIndices()`.
238
+ this.pruneDetachedViews();
239
+ // Only re-enter the zone (a global tick) if someone is actually listening.
240
+ if (this.ceriousScrollMeasuredViewport.observed) {
241
+ this.ngZone.run(() => this.ceriousScrollMeasuredViewport.emit(range));
242
+ }
186
243
  return range;
187
244
  }
245
+ /** Tear down embedded views whose container is no longer part of the viewport. */
246
+ pruneDetachedViews() {
247
+ if (!this.hostRef || this.viewByContainer.size === 0)
248
+ return;
249
+ const scroller = this.hostRef.scroller;
250
+ const active = new Set();
251
+ for (const index of scroller.getRenderedIndices()) {
252
+ const el = scroller.getRenderedElement(index);
253
+ if (el)
254
+ active.add(el);
255
+ }
256
+ for (const [container, view] of this.viewByContainer) {
257
+ if (!active.has(container)) {
258
+ // Detach DOM but keep the view alive in the pool for future reuse.
259
+ // Destroying + recreating views per scroll step dominates frame time.
260
+ for (const node of view.rootNodes) {
261
+ if (node.parentNode)
262
+ node.parentNode.removeChild(node);
263
+ }
264
+ this.viewByContainer.delete(container);
265
+ this.freeViews.push(view);
266
+ }
267
+ }
268
+ }
269
+ /** Auto-render coalesced to at most once per animation frame. */
270
+ scheduleRender() {
271
+ if (this.scheduledRenderFrame != null)
272
+ return;
273
+ this.scheduledRenderFrame = requestAnimationFrame(() => {
274
+ this.scheduledRenderFrame = null;
275
+ // Run render() OUTSIDE Angular's zone. A full ApplicationRef.tick() on
276
+ // every scroll frame is the primary FPS bottleneck: even when no rows
277
+ // change (pure translation), ngZone.run() causes Angular to walk the
278
+ // entire component tree. Instead we call view.detectChanges() locally
279
+ // inside renderTemplateIntoContainer for each affected row. Zone entry is
280
+ // only needed when creating a brand-new embedded view (to zone-patch its
281
+ // event listeners) — recycled and pooled views were already created inside
282
+ // zone and their listeners remain zone-aware.
283
+ this.render();
284
+ });
285
+ }
286
+ /**
287
+ * Discard all cached row heights and re-measure the viewport.
288
+ *
289
+ * Call this only when the heights of rows you've *already rendered* may have
290
+ * changed without their indices changing — e.g. a global font/density change,
291
+ * or swapping every row to a different layout. This forces a synchronous
292
+ * re-measure (one `offsetHeight` read per visible row), so do NOT call it on
293
+ * routine edits: a single cell edit keeps its row's size, and the engine's
294
+ * ResizeObserver picks up any incidental resize on its own.
295
+ */
296
+ recalculate() {
297
+ if (!this.hostRef)
298
+ return null;
299
+ // Discard the cached heights, then re-render. The engine re-measures and
300
+ // re-caches the rendered rows during the pass and refreshes the scroll
301
+ // percentage, so an in-place height change (e.g. expand/collapse) is
302
+ // reflected in the total content height and scrollbar immediately.
303
+ this.hostRef.scroller.clearAllCaches();
304
+ return this.render();
305
+ }
306
+ /** Jump directly to an element index, then render. */
307
+ jumpToElement(index) {
308
+ if (!this.hostRef)
309
+ return null;
310
+ this.hostRef.scroller.jumpToElement(index);
311
+ return this.render();
312
+ }
313
+ /** Scroll to a percentage (0..100), then render. */
314
+ scrollToPercentage(percentage) {
315
+ if (!this.hostRef)
316
+ return null;
317
+ this.hostRef.scroller.handleScrollPercentage(percentage);
318
+ return this.render();
319
+ }
320
+ /** Reset to the top, then render. */
321
+ reset() {
322
+ if (!this.hostRef)
323
+ return null;
324
+ this.hostRef.scroller.reset();
325
+ return this.render();
326
+ }
188
327
  recreate() {
189
328
  this.viewportSub?.unsubscribe();
190
329
  this.viewportSub = null;
@@ -199,11 +338,16 @@ class CeriousScrollDirective {
199
338
  const container = this.host.nativeElement;
200
339
  const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);
201
340
  this.hostRef = this.cerious.createHost(container, total, this.ceriousScrollOptions, this.ngZone, () => {
341
+ // Coalesce scroll-driven renders to one per frame (the native scrollbar
342
+ // can fire many scroll events between paints).
202
343
  if (this.ceriousScrollAutoRender)
203
- this.render();
344
+ this.scheduleRender();
204
345
  });
205
346
  this.viewportSub = this.hostRef.viewportChanges$.subscribe((detail) => {
206
- this.ngZone.run(() => this.ceriousScrollViewportChange.emit(detail));
347
+ // Skip the global tick when nobody is bound to the output.
348
+ if (this.ceriousScrollViewportChange.observed) {
349
+ this.ngZone.run(() => this.ceriousScrollViewportChange.emit(detail));
350
+ }
207
351
  });
208
352
  this.ceriousScrollReady.emit(this.hostRef.scroller);
209
353
  if (this.ceriousScrollAutoRender) {
@@ -222,27 +366,96 @@ class CeriousScrollDirective {
222
366
  renderTemplateIntoContainer(template, index, elementContainer) {
223
367
  const previous = this.viewByContainer.get(elementContainer);
224
368
  if (previous) {
225
- this.appRef.detachView(previous);
226
- previous.destroy();
227
- this.viewByContainer.delete(elementContainer);
369
+ // Recycle: update the bound context and run local CD instead of
370
+ // destroying the embedded view and rebuilding the entire row tree.
371
+ const item = this.getItemForIndex(index);
372
+ previous.context.$implicit = item;
373
+ previous.context.item = item;
374
+ previous.context.index = index;
375
+ // The core engine wipes elementContainer.textContent when reusing it from
376
+ // its pool, orphaning the view's root nodes. Re-append them defensively.
377
+ if (previous.rootNodes.length && previous.rootNodes[0].parentNode !== elementContainer) {
378
+ for (const node of previous.rootNodes) {
379
+ elementContainer.appendChild(node);
380
+ }
381
+ }
382
+ previous.detectChanges();
383
+ return;
384
+ }
385
+ // Try to reuse a pooled view from a container that scrolled out of viewport.
386
+ const pooled = this.freeViews.pop();
387
+ if (pooled) {
388
+ const item = this.getItemForIndex(index);
389
+ pooled.context.$implicit = item;
390
+ pooled.context.item = item;
391
+ pooled.context.index = index;
392
+ elementContainer.textContent = '';
393
+ for (const node of pooled.rootNodes) {
394
+ elementContainer.appendChild(node);
395
+ }
396
+ pooled.detectChanges();
397
+ this.viewByContainer.set(elementContainer, pooled);
398
+ return;
228
399
  }
229
- // Clear prior DOM (CeriousScroll may recycle containers).
400
+ // No prior view for this container: create one.
401
+ // Enter the Angular zone so the new view's template event listeners
402
+ // ((click), (input), etc.) are zone-patched. This path runs at most
403
+ // once per visible row (after that, the view is recycled from the pool).
230
404
  elementContainer.textContent = '';
231
405
  const item = this.getItemForIndex(index);
232
- const view = template.createEmbeddedView({ $implicit: item, item, index });
233
- this.appRef.attachView(view);
234
- view.detectChanges();
406
+ const view = this.ngZone.run(() => {
407
+ const v = template.createEmbeddedView({ $implicit: item, item, index });
408
+ this.appRef.attachView(v);
409
+ v.detectChanges();
410
+ return v;
411
+ });
235
412
  for (const node of view.rootNodes) {
236
413
  elementContainer.appendChild(node);
237
414
  }
238
415
  this.viewByContainer.set(elementContainer, view);
239
416
  }
417
+ /**
418
+ * Update the bound item/index on every currently-rendered row's embedded view
419
+ * and run change detection, without recreating the views. Used when the data
420
+ * reference changes but the visible indices (and their heights) do not, so row
421
+ * state (focus, selection, open dropdowns) survives the update.
422
+ */
423
+ /**
424
+ * Re-bind each currently rendered row's context (from the current items/getter)
425
+ * and run change detection on its embedded view. Use this after mutating row
426
+ * state in place (e.g. selection flags) or column-level state read by the row
427
+ * template, when row identity and visible indices have not changed. Cheap
428
+ * relative to `render()` — does not invoke the engine's measurement pass.
429
+ */
430
+ refreshRenderedContent() {
431
+ if (!this.hostRef)
432
+ return;
433
+ const scroller = this.hostRef.scroller;
434
+ for (const index of scroller.getRenderedIndices()) {
435
+ const container = scroller.getRenderedElement(index);
436
+ if (!container)
437
+ continue;
438
+ const view = this.viewByContainer.get(container);
439
+ if (!view)
440
+ continue;
441
+ const item = this.getItemForIndex(index);
442
+ view.context.$implicit = item;
443
+ view.context.item = item;
444
+ view.context.index = index;
445
+ view.detectChanges();
446
+ }
447
+ }
240
448
  destroyAllViews() {
241
449
  for (const view of this.viewByContainer.values()) {
242
450
  this.appRef.detachView(view);
243
451
  view.destroy();
244
452
  }
245
453
  this.viewByContainer.clear();
454
+ for (const view of this.freeViews) {
455
+ this.appRef.detachView(view);
456
+ view.destroy();
457
+ }
458
+ this.freeViews.length = 0;
246
459
  }
247
460
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CeriousScrollDirective, deps: [{ token: i0.ElementRef }, { token: i0.ApplicationRef }, { token: i0.NgZone }, { token: CeriousScrollService }], target: i0.ɵɵFactoryTarget.Directive }); }
248
461
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: CeriousScrollDirective, isStandalone: true, selector: "[ceriousScroll]", inputs: { ceriousScrollTotalElements: "ceriousScrollTotalElements", ceriousScrollItems: "ceriousScrollItems", ceriousScrollGetItem: "ceriousScrollGetItem", ceriousScrollItemTemplate: "ceriousScrollItemTemplate", ceriousScrollOptions: "ceriousScrollOptions", ceriousScrollAutoRender: "ceriousScrollAutoRender" }, outputs: { ceriousScrollViewportChange: "ceriousScrollViewportChange", ceriousScrollMeasuredViewport: "ceriousScrollMeasuredViewport", ceriousScrollReady: "ceriousScrollReady" }, usesOnChanges: true, ngImport: i0 }); }
@@ -1 +1 @@
1
- {"version":3,"file":"ceriousdevtech-ngx-cerious-scroll.mjs","sources":["../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.observable.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll-item-template.directive.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.service.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.directive.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.component.ts","../../../projects/ngx-cerious-scroll/src/public-api.ts","../../../projects/ngx-cerious-scroll/src/ceriousdevtech-ngx-cerious-scroll.ts"],"sourcesContent":["import { fromEvent, map, merge, Observable, share } from 'rxjs';\n\nimport type { ScrollResult } from '@ceriousdevtech/cerious-scroll';\n\nimport {\n CeriousNativeScrollbarViewportChangeEvent,\n CeriousViewportChangeDetail,\n CeriousViewportChangeEvent,\n} from './cerious-scroll.types';\n\n/**\n * Observable wrapper around the `cerious-viewport-change` CustomEvent emitted by `@ceriousdevtech/cerious-scroll`.\n *\n * Note: this event is emitted when wheel/touch/keyboard handlers are enabled (defaults are enabled).\n */\nexport function ceriousViewportChange$(container: HTMLElement): Observable<CeriousViewportChangeDetail> {\n const fromCerious = fromEvent<CeriousViewportChangeEvent>(container, 'cerious-viewport-change').pipe(\n map((evt) => evt.detail)\n );\n\n // Native scrollbar integration in the upstream package dispatches `viewport-change`.\n // Normalize it into the same shape as `cerious-viewport-change`.\n const fromScrollbar = fromEvent<CeriousNativeScrollbarViewportChangeEvent>(container, 'viewport-change').pipe(\n map((evt) => {\n const result: ScrollResult = { element: evt.detail.element, offset: evt.detail.scrollOffset };\n return {\n percentage: evt.detail.percentage,\n currentElement: evt.detail.element,\n scrollOffset: evt.detail.scrollOffset,\n result,\n } satisfies CeriousViewportChangeDetail;\n })\n );\n\n return merge(fromCerious, fromScrollbar).pipe(share());\n}\n","import { Directive, TemplateRef } from '@angular/core';\n\nexport interface CeriousScrollItemTemplateContext<TItem = unknown> {\n /** The item for this row (also available as `$implicit`). */\n $implicit: TItem;\n /** Row index. */\n index: number;\n /** Same as `$implicit` for named access. */\n item: TItem;\n}\n\n/**\n * Marks an `ng-template` as the row template for `CeriousScrollComponent`.\n *\n * Usage:\n * ```html\n * <cerious-scroll [items]=\"items\">\n * <ng-template ceriousScrollItem let-item let-index=\"index\">\n * {{ index }} - {{ item.name }}\n * </ng-template>\n * </cerious-scroll>\n * ```\n */\n@Directive({\n selector: 'ng-template[ceriousScrollItem]',\n standalone: true,\n})\nexport class CeriousScrollItemTemplateDirective<TItem = unknown> {\n constructor(public readonly templateRef: TemplateRef<CeriousScrollItemTemplateContext<TItem>>) {}\n}\n","import { Injectable, NgZone } from '@angular/core';\n\nimport { CeriousScroll, type CeriousScrollOptions } from '@ceriousdevtech/cerious-scroll';\nimport { Observable } from 'rxjs';\n\nimport { ceriousViewportChange$ } from './cerious-scroll.observable';\nimport type { CeriousViewportChangeDetail } from './cerious-scroll.types';\n\nconst CONTENT_ATTR = 'data-cerious-scroll-content';\n\nfunction ensureContentElement(container: HTMLElement): HTMLElement {\n const existing = container.querySelector<HTMLElement>(`[${CONTENT_ATTR}]`);\n if (existing) return existing;\n\n const el = document.createElement('div');\n el.setAttribute(CONTENT_ATTR, '');\n el.style.position = 'relative';\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.overflow = 'hidden';\n container.appendChild(el);\n return el;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class CeriousScrollService {\n createHost(\n container: HTMLElement,\n totalElements: number,\n options: CeriousScrollOptions,\n ngZone: NgZone,\n onScrollHook?: () => void\n ): CeriousScrollHostRef {\n const contentElement = ensureContentElement(container);\n let scroller!: CeriousScroll;\n\n ngZone.runOutsideAngular(() => {\n const userOnScroll = options?.onScroll;\n const mergedOptions: CeriousScrollOptions = {\n ...options,\n onScroll: () => {\n userOnScroll?.();\n onScrollHook?.();\n },\n };\n\n scroller = new CeriousScroll(container, totalElements, mergedOptions);\n });\n\n const viewportChanges$ = ceriousViewportChange$(container);\n\n return {\n scroller,\n contentElement,\n viewportChanges$,\n destroy: () => {\n // Remove rendered rows content first; keep container stable.\n contentElement.textContent = '';\n scroller.detachScrollbar(container);\n scroller.dispose();\n },\n };\n }\n}\n\nexport interface CeriousScrollHostRef {\n readonly scroller: CeriousScroll;\n /** Dedicated element used for row rendering (prevents scrollbar DOM from being cleared). */\n readonly contentElement: HTMLElement;\n readonly viewportChanges$: Observable<CeriousViewportChangeDetail>;\n destroy(): void;\n}\n","import {\n AfterViewInit,\n ApplicationRef,\n Directive,\n ElementRef,\n EmbeddedViewRef,\n EventEmitter,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n Output,\n SimpleChanges,\n TemplateRef,\n} from '@angular/core';\n\nimport {\n type CeriousScrollOptions,\n type ElementRenderer,\n type MeasuredViewportRange,\n} from '@ceriousdevtech/cerious-scroll';\nimport { Subscription } from 'rxjs';\n\nimport type { CeriousViewportChangeDetail } from './cerious-scroll.types';\nimport type { CeriousScrollItemTemplateContext } from './cerious-scroll-item-template.directive';\nimport { type CeriousScrollHostRef, CeriousScrollService } from './cerious-scroll.service';\n\nfunction coerceTotalElements(explicitTotal: number | null | undefined, itemsLen: number | null | undefined): number {\n const candidate = typeof explicitTotal === 'number' ? explicitTotal : typeof itemsLen === 'number' ? itemsLen : undefined;\n if (candidate === undefined || Number.isNaN(candidate)) {\n throw new Error('CeriousScrollDirective: provide `ceriousScrollTotalElements` or `ceriousScrollItems`.');\n }\n // CeriousScroll currently requires >= 1\n return Math.max(1, candidate);\n}\n\n@Directive({\n selector: '[ceriousScroll]',\n standalone: true,\n})\nexport class CeriousScrollDirective<TItem = unknown> implements AfterViewInit, OnChanges, OnDestroy {\n /** Total number of items. If omitted, derived from `ceriousScrollItems.length`. */\n @Input() ceriousScrollTotalElements: number | null = null;\n\n /** Optional items array (enables `let-item`). */\n @Input() ceriousScrollItems: readonly TItem[] | null = null;\n\n /** Optional getter for large datasets (alternative to passing full `items`). */\n @Input() ceriousScrollGetItem: ((index: number) => TItem) | null = null;\n\n /** Template used to render each row. */\n @Input() ceriousScrollItemTemplate: TemplateRef<CeriousScrollItemTemplateContext<TItem>> | null = null;\n\n /** Options passed to `new CeriousScroll(...)`. */\n @Input() ceriousScrollOptions: CeriousScrollOptions = {};\n\n /** Automatically call render after each scroll event. Default: true */\n @Input() ceriousScrollAutoRender = true;\n\n /** Emits `cerious-viewport-change` detail. */\n @Output() ceriousScrollViewportChange = new EventEmitter<CeriousViewportChangeDetail>();\n\n /** Emits the last measured viewport after each render pass. */\n @Output() ceriousScrollMeasuredViewport = new EventEmitter<MeasuredViewportRange>();\n\n /** Emits once the underlying `CeriousScroll` instance is ready. */\n @Output() ceriousScrollReady = new EventEmitter<CeriousScrollHostRef['scroller']>();\n\n private hostRef: CeriousScrollHostRef | null = null;\n private viewportSub: Subscription | null = null;\n\n private readonly viewByContainer = new Map<HTMLElement, EmbeddedViewRef<CeriousScrollItemTemplateContext<TItem>>>();\n\n constructor(\n private readonly host: ElementRef<HTMLElement>,\n private readonly appRef: ApplicationRef,\n private readonly ngZone: NgZone,\n private readonly cerious: CeriousScrollService\n ) {}\n\n ngAfterViewInit(): void {\n this.ensureInitialized();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.hostRef) return;\n\n if (changes['ceriousScrollItems'] || changes['ceriousScrollTotalElements']) {\n const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);\n this.hostRef.scroller.totalElements = total;\n this.hostRef.scroller.clearAllCaches();\n if (this.ceriousScrollAutoRender) this.render();\n }\n\n if (changes['ceriousScrollOptions'] && !changes['ceriousScrollOptions'].firstChange) {\n // Options are consumed primarily at construction time.\n this.recreate();\n }\n }\n\n ngOnDestroy(): void {\n this.viewportSub?.unsubscribe();\n this.viewportSub = null;\n\n this.destroyAllViews();\n\n this.hostRef?.destroy();\n this.hostRef = null;\n }\n\n /** Imperatively trigger a render pass (uses `ceriousScrollItemTemplate`). */\n render(): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n const template = this.ceriousScrollItemTemplate;\n if (!template) return null;\n\n const hostContainer = this.host.nativeElement;\n const height = hostContainer.clientHeight || hostContainer.offsetHeight;\n const contentContainer = this.hostRef.contentElement;\n\n const renderer: ElementRenderer = (index, elementContainer) => {\n // Rendering must create Angular views inside the Angular zone.\n this.ngZone.run(() => {\n this.renderTemplateIntoContainer(template, index, elementContainer);\n });\n };\n\n const range = this.hostRef.scroller.renderViewport(height, contentContainer, renderer);\n this.ngZone.run(() => this.ceriousScrollMeasuredViewport.emit(range));\n return range;\n }\n\n private recreate(): void {\n this.viewportSub?.unsubscribe();\n this.viewportSub = null;\n\n this.destroyAllViews();\n\n this.hostRef?.destroy();\n this.hostRef = null;\n\n this.ensureInitialized();\n }\n\n private ensureInitialized(): void {\n if (this.hostRef) return;\n\n const container = this.host.nativeElement;\n const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);\n\n this.hostRef = this.cerious.createHost(container, total, this.ceriousScrollOptions, this.ngZone, () => {\n if (this.ceriousScrollAutoRender) this.render();\n });\n\n this.viewportSub = this.hostRef.viewportChanges$.subscribe((detail: CeriousViewportChangeDetail) => {\n this.ngZone.run(() => this.ceriousScrollViewportChange.emit(detail));\n });\n\n this.ceriousScrollReady.emit(this.hostRef.scroller);\n\n if (this.ceriousScrollAutoRender) {\n queueMicrotask(() => this.render());\n }\n }\n\n private getItemForIndex(index: number): TItem {\n const getter = this.ceriousScrollGetItem;\n if (getter) return getter(index);\n\n const items = this.ceriousScrollItems;\n if (!items) return undefined as TItem;\n\n return items[index];\n }\n\n private renderTemplateIntoContainer(\n template: TemplateRef<CeriousScrollItemTemplateContext<TItem>>,\n index: number,\n elementContainer: HTMLElement\n ): void {\n const previous = this.viewByContainer.get(elementContainer);\n if (previous) {\n this.appRef.detachView(previous);\n previous.destroy();\n this.viewByContainer.delete(elementContainer);\n }\n\n // Clear prior DOM (CeriousScroll may recycle containers).\n elementContainer.textContent = '';\n\n const item = this.getItemForIndex(index);\n const view = template.createEmbeddedView({ $implicit: item, item, index });\n this.appRef.attachView(view);\n view.detectChanges();\n\n for (const node of view.rootNodes) {\n elementContainer.appendChild(node);\n }\n\n this.viewByContainer.set(elementContainer, view);\n }\n\n private destroyAllViews(): void {\n for (const view of this.viewByContainer.values()) {\n this.appRef.detachView(view);\n view.destroy();\n }\n this.viewByContainer.clear();\n }\n}\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n TemplateRef,\n} from '@angular/core';\n\nimport { CeriousScrollDirective } from './cerious-scroll.directive';\nimport {\n CeriousScrollItemTemplateDirective,\n type CeriousScrollItemTemplateContext,\n} from './cerious-scroll-item-template.directive';\n\n@Component({\n selector: 'cerious-scroll',\n standalone: true,\n imports: [],\n hostDirectives: [\n {\n directive: CeriousScrollDirective,\n inputs: [\n 'ceriousScrollTotalElements: totalElements',\n 'ceriousScrollItems: items',\n 'ceriousScrollGetItem: getItem',\n 'ceriousScrollItemTemplate: itemTemplate',\n 'ceriousScrollOptions: options',\n 'ceriousScrollAutoRender: autoRender',\n ],\n outputs: [\n 'ceriousScrollViewportChange: viewportChange',\n 'ceriousScrollMeasuredViewport: measuredViewport',\n 'ceriousScrollReady: scrollerReady',\n ],\n },\n ],\n template: `<ng-content />`,\n styles: `\n :host {\n display: block;\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CeriousScrollComponent<TItem = unknown> implements AfterContentInit {\n /** Optional convenience template marker: <ng-template ceriousScrollItem let-item let-index=\"index\">...</ng-template> */\n @ContentChild(CeriousScrollItemTemplateDirective)\n private readonly projectedItemTemplate?: CeriousScrollItemTemplateDirective<TItem>;\n\n constructor(private readonly ceriousScroll: CeriousScrollDirective<TItem>) {}\n\n ngAfterContentInit(): void {\n if (this.ceriousScroll.ceriousScrollItemTemplate) return;\n if (!this.projectedItemTemplate) return;\n\n this.ceriousScroll.ceriousScrollItemTemplate =\n this.projectedItemTemplate.templateRef as TemplateRef<CeriousScrollItemTemplateContext<TItem>>;\n\n if (this.ceriousScroll.ceriousScrollAutoRender) {\n queueMicrotask(() => this.ceriousScroll.render());\n }\n }\n}\n","/*\n * Public API Surface of ngx-cerious-scroll\n */\n\nexport * from './lib/cerious-scroll.types';\nexport * from './lib/cerious-scroll.observable';\nexport * from './lib/cerious-scroll-item-template.directive';\nexport * from './lib/cerious-scroll.directive';\nexport * from './lib/cerious-scroll.component';\nexport * from './lib/cerious-scroll.service';\n\n// Re-export upstream types/classes for convenience\nexport * from '@ceriousdevtech/cerious-scroll';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.CeriousScrollService","i1.CeriousScrollDirective"],"mappings":"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"}
1
+ {"version":3,"file":"ceriousdevtech-ngx-cerious-scroll.mjs","sources":["../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.observable.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll-item-template.directive.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.service.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.directive.ts","../../../projects/ngx-cerious-scroll/src/lib/cerious-scroll.component.ts","../../../projects/ngx-cerious-scroll/src/public-api.ts","../../../projects/ngx-cerious-scroll/src/ceriousdevtech-ngx-cerious-scroll.ts"],"sourcesContent":["import { fromEvent, map, merge, Observable, share } from 'rxjs';\n\nimport type { ScrollResult } from '@ceriousdevtech/cerious-scroll';\n\nimport {\n CeriousNativeScrollbarViewportChangeEvent,\n CeriousViewportChangeDetail,\n CeriousViewportChangeEvent,\n} from './cerious-scroll.types';\n\n/**\n * Observable wrapper around the `cerious-viewport-change` CustomEvent emitted by `@ceriousdevtech/cerious-scroll`.\n *\n * Note: this event is emitted when wheel/touch/keyboard handlers are enabled (defaults are enabled).\n */\nexport function ceriousViewportChange$(container: HTMLElement): Observable<CeriousViewportChangeDetail> {\n const fromCerious = fromEvent<CeriousViewportChangeEvent>(container, 'cerious-viewport-change').pipe(\n map((evt) => evt.detail)\n );\n\n // Native scrollbar integration in the upstream package dispatches `viewport-change`.\n // Normalize it into the same shape as `cerious-viewport-change`.\n const fromScrollbar = fromEvent<CeriousNativeScrollbarViewportChangeEvent>(container, 'viewport-change').pipe(\n map((evt) => {\n const result: ScrollResult = { element: evt.detail.element, offset: evt.detail.scrollOffset };\n return {\n percentage: evt.detail.percentage,\n currentElement: evt.detail.element,\n scrollOffset: evt.detail.scrollOffset,\n result,\n } satisfies CeriousViewportChangeDetail;\n })\n );\n\n return merge(fromCerious, fromScrollbar).pipe(share());\n}\n","import { Directive, TemplateRef } from '@angular/core';\n\nexport interface CeriousScrollItemTemplateContext<TItem = unknown> {\n /** The item for this row (also available as `$implicit`). */\n $implicit: TItem;\n /** Row index. */\n index: number;\n /** Same as `$implicit` for named access. */\n item: TItem;\n}\n\n/**\n * Marks an `ng-template` as the row template for `CeriousScrollComponent`.\n *\n * Usage:\n * ```html\n * <cerious-scroll [items]=\"items\">\n * <ng-template ceriousScrollItem let-item let-index=\"index\">\n * {{ index }} - {{ item.name }}\n * </ng-template>\n * </cerious-scroll>\n * ```\n */\n@Directive({\n selector: 'ng-template[ceriousScrollItem]',\n standalone: true,\n})\nexport class CeriousScrollItemTemplateDirective<TItem = unknown> {\n constructor(public readonly templateRef: TemplateRef<CeriousScrollItemTemplateContext<TItem>>) {}\n}\n","import { Injectable, NgZone } from '@angular/core';\n\nimport { CeriousScroll, type CeriousScrollOptions } from '@ceriousdevtech/cerious-scroll';\nimport { Observable } from 'rxjs';\n\nimport { ceriousViewportChange$ } from './cerious-scroll.observable';\nimport type { CeriousViewportChangeDetail } from './cerious-scroll.types';\n\nconst CONTENT_ATTR = 'data-cerious-scroll-content';\n\nfunction ensureContentElement(container: HTMLElement): HTMLElement {\n const existing = container.querySelector<HTMLElement>(`[${CONTENT_ATTR}]`);\n if (existing) return existing;\n\n const el = document.createElement('div');\n el.setAttribute(CONTENT_ATTR, '');\n el.style.position = 'relative';\n el.style.width = '100%';\n el.style.height = '100%';\n el.style.overflow = 'hidden';\n container.appendChild(el);\n return el;\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class CeriousScrollService {\n createHost(\n container: HTMLElement,\n totalElements: number,\n options: CeriousScrollOptions,\n ngZone: NgZone,\n onScrollHook?: () => void\n ): CeriousScrollHostRef {\n const contentElement = ensureContentElement(container);\n let scroller!: CeriousScroll;\n\n ngZone.runOutsideAngular(() => {\n const userOnScroll = options?.onScroll;\n const mergedOptions: CeriousScrollOptions = {\n ...options,\n onScroll: () => {\n userOnScroll?.();\n onScrollHook?.();\n },\n };\n\n scroller = new CeriousScroll(container, totalElements, mergedOptions);\n });\n\n const viewportChanges$ = ceriousViewportChange$(container);\n\n return {\n scroller,\n contentElement,\n viewportChanges$,\n destroy: () => {\n // Remove rendered rows content first; keep container stable.\n contentElement.textContent = '';\n scroller.detachScrollbar(container);\n scroller.dispose();\n },\n };\n }\n}\n\nexport interface CeriousScrollHostRef {\n readonly scroller: CeriousScroll;\n /** Dedicated element used for row rendering (prevents scrollbar DOM from being cleared). */\n readonly contentElement: HTMLElement;\n readonly viewportChanges$: Observable<CeriousViewportChangeDetail>;\n destroy(): void;\n}\n","import {\n AfterViewInit,\n ApplicationRef,\n Directive,\n ElementRef,\n EmbeddedViewRef,\n EventEmitter,\n Input,\n NgZone,\n OnChanges,\n OnDestroy,\n Output,\n SimpleChanges,\n TemplateRef,\n} from '@angular/core';\n\nimport {\n type CeriousScrollOptions,\n type ElementRenderer,\n type MeasuredViewportRange,\n} from '@ceriousdevtech/cerious-scroll';\nimport { Subscription } from 'rxjs';\n\nimport type { CeriousViewportChangeDetail } from './cerious-scroll.types';\nimport type { CeriousScrollItemTemplateContext } from './cerious-scroll-item-template.directive';\nimport { type CeriousScrollHostRef, CeriousScrollService } from './cerious-scroll.service';\n\nfunction coerceTotalElements(explicitTotal: number | null | undefined, itemsLen: number | null | undefined): number {\n const candidate = typeof explicitTotal === 'number' ? explicitTotal : typeof itemsLen === 'number' ? itemsLen : undefined;\n if (candidate === undefined || Number.isNaN(candidate)) {\n throw new Error('CeriousScrollDirective: provide `ceriousScrollTotalElements` or `ceriousScrollItems`.');\n }\n // CeriousScroll currently requires >= 1\n return Math.max(1, candidate);\n}\n\n@Directive({\n selector: '[ceriousScroll]',\n standalone: true,\n})\nexport class CeriousScrollDirective<TItem = unknown> implements AfterViewInit, OnChanges, OnDestroy {\n /** Total number of items. If omitted, derived from `ceriousScrollItems.length`. */\n @Input() ceriousScrollTotalElements: number | null = null;\n\n /** Optional items array (enables `let-item`). */\n @Input() ceriousScrollItems: readonly TItem[] | null = null;\n\n /** Optional getter for large datasets (alternative to passing full `items`). */\n @Input() ceriousScrollGetItem: ((index: number) => TItem) | null = null;\n\n /** Template used to render each row. */\n @Input() ceriousScrollItemTemplate: TemplateRef<CeriousScrollItemTemplateContext<TItem>> | null = null;\n\n /** Options passed to `new CeriousScroll(...)`. */\n @Input() ceriousScrollOptions: CeriousScrollOptions = {};\n\n /** Automatically call render after each scroll event. Default: true */\n @Input() ceriousScrollAutoRender = true;\n\n /** Emits `cerious-viewport-change` detail. */\n @Output() ceriousScrollViewportChange = new EventEmitter<CeriousViewportChangeDetail>();\n\n /** Emits the last measured viewport after each render pass. */\n @Output() ceriousScrollMeasuredViewport = new EventEmitter<MeasuredViewportRange>();\n\n /** Emits once the underlying `CeriousScroll` instance is ready. */\n @Output() ceriousScrollReady = new EventEmitter<CeriousScrollHostRef['scroller']>();\n\n private hostRef: CeriousScrollHostRef | null = null;\n private viewportSub: Subscription | null = null;\n private scheduledRenderFrame: number | null = null;\n\n private readonly viewByContainer = new Map<HTMLElement, EmbeddedViewRef<CeriousScrollItemTemplateContext<TItem>>>();\n // Pool of views detached because their container left the viewport. Reusing\n // them on subsequent renders avoids destroying + recreating the entire row\n // component tree on fast scrolls (where the engine's element pool wipes\n // textContent on reused containers, so view rootNodes get orphaned).\n private readonly freeViews: EmbeddedViewRef<CeriousScrollItemTemplateContext<TItem>>[] = [];\n\n constructor(\n private readonly host: ElementRef<HTMLElement>,\n private readonly appRef: ApplicationRef,\n private readonly ngZone: NgZone,\n private readonly cerious: CeriousScrollService\n ) {}\n\n ngAfterViewInit(): void {\n this.ensureInitialized();\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (!this.hostRef) return;\n\n if (changes['ceriousScrollItems'] || changes['ceriousScrollTotalElements']) {\n const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);\n const countChanged = this.hostRef.scroller.totalElements !== total;\n\n if (countChanged) {\n // The dataset size changed: the ViewportRenderer stores its own copy of\n // totalElements (set by value at construction) so patching the engine's\n // public property alone leaves the renderer's internal bound stale. The\n // renderer would then use the old count for its viewport-fill loop and\n // bottom-boundary scan, producing phantom renders at out-of-bounds\n // indices (undefined items → 0-height rows → the fill loop never\n // satisfies its height condition → hundreds of renderer callbacks).\n // Recreating the engine gives both the engine and the renderer a fresh,\n // consistent count. ensureInitialized() schedules the first render via\n // queueMicrotask, which runs after the current CD cycle completes.\n this.recreate();\n } else if (this.ceriousScrollAutoRender) {\n // Same count, new data reference (e.g. an immutable edit or a sort that\n // happens to keep the same length). The engine reuses the DOM element it\n // already rendered for each overlapping index without re-invoking the\n // renderer, so update the content of every currently-visible row IN\n // PLACE before calling render(). This preserves each row's embedded\n // view (so a focused textbox keeps focus/caret) and does NOT discard\n // cached heights. If a row's height actually changes the engine's\n // ResizeObserver keeps the cache correct on its own; for a wholesale\n // height change across all rows, call recalculate() instead.\n this.refreshRenderedContent();\n this.render();\n }\n }\n\n if (changes['ceriousScrollOptions'] && !changes['ceriousScrollOptions'].firstChange) {\n // Options are consumed primarily at construction time.\n this.recreate();\n }\n }\n\n ngOnDestroy(): void {\n if (this.scheduledRenderFrame != null) {\n cancelAnimationFrame(this.scheduledRenderFrame);\n this.scheduledRenderFrame = null;\n }\n\n this.viewportSub?.unsubscribe();\n this.viewportSub = null;\n\n this.destroyAllViews();\n\n this.hostRef?.destroy();\n this.hostRef = null;\n }\n\n /** Imperatively trigger a render pass (uses `ceriousScrollItemTemplate`). */\n render(): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n const template = this.ceriousScrollItemTemplate;\n if (!template) return null;\n\n const hostContainer = this.host.nativeElement;\n const height = hostContainer.clientHeight || hostContainer.offsetHeight;\n const contentContainer = this.hostRef.contentElement;\n\n // Track whether the engine asked us to bind any row this pass. Pure scroll\n // frames where the visible row set doesn't change still drive render() via\n // the rAF coalescer — there's no point in walking the prune map or\n // emitting the viewport range when nothing was touched.\n let rendererInvocations = 0;\n const renderer: ElementRenderer = (index, elementContainer) => {\n rendererInvocations++;\n // Render each row's embedded view with LOCAL change detection\n // (`view.detectChanges()` inside `renderTemplateIntoContainer`) so the\n // engine can measure its height during this pass. Do NOT wrap each row in\n // its own `ngZone.run` — that fires a full `ApplicationRef` tick *per row*\n // (O(newRows × visibleRows) work, janky drags). The single coalesced tick\n // for the whole pass is handled by the caller (`scheduleRender`).\n this.renderTemplateIntoContainer(template, index, elementContainer);\n };\n\n const range = this.hostRef.scroller.renderViewport(height, contentContainer, renderer);\n\n if (rendererInvocations === 0) {\n // Viewport didn't change — skip the prune walk and the (potentially\n // zone-entering) emit.\n return range;\n }\n\n // Destroy views whose container the engine no longer renders. Without this,\n // every container the engine recycles into its element pool leaves its\n // embedded view attached to ApplicationRef forever — so the attached-view\n // list (and every O(n) `detachView`) grows without bound and scrolling gets\n // progressively slower. Mirrors the React/Vue wrappers, which drop rows that\n // fall out of `getRenderedIndices()`.\n this.pruneDetachedViews();\n\n // Only re-enter the zone (a global tick) if someone is actually listening.\n if (this.ceriousScrollMeasuredViewport.observed) {\n this.ngZone.run(() => this.ceriousScrollMeasuredViewport.emit(range));\n }\n return range;\n }\n\n /** Tear down embedded views whose container is no longer part of the viewport. */\n private pruneDetachedViews(): void {\n if (!this.hostRef || this.viewByContainer.size === 0) return;\n const scroller = this.hostRef.scroller;\n const active = new Set<HTMLElement>();\n for (const index of scroller.getRenderedIndices()) {\n const el = scroller.getRenderedElement(index);\n if (el) active.add(el);\n }\n for (const [container, view] of this.viewByContainer) {\n if (!active.has(container)) {\n // Detach DOM but keep the view alive in the pool for future reuse.\n // Destroying + recreating views per scroll step dominates frame time.\n for (const node of view.rootNodes) {\n if (node.parentNode) node.parentNode.removeChild(node);\n }\n this.viewByContainer.delete(container);\n this.freeViews.push(view);\n }\n }\n }\n\n /** Auto-render coalesced to at most once per animation frame. */\n private scheduleRender(): void {\n if (this.scheduledRenderFrame != null) return;\n this.scheduledRenderFrame = requestAnimationFrame(() => {\n this.scheduledRenderFrame = null;\n // Run render() OUTSIDE Angular's zone. A full ApplicationRef.tick() on\n // every scroll frame is the primary FPS bottleneck: even when no rows\n // change (pure translation), ngZone.run() causes Angular to walk the\n // entire component tree. Instead we call view.detectChanges() locally\n // inside renderTemplateIntoContainer for each affected row. Zone entry is\n // only needed when creating a brand-new embedded view (to zone-patch its\n // event listeners) — recycled and pooled views were already created inside\n // zone and their listeners remain zone-aware.\n this.render();\n });\n }\n\n /**\n * Discard all cached row heights and re-measure the viewport.\n *\n * Call this only when the heights of rows you've *already rendered* may have\n * changed without their indices changing — e.g. a global font/density change,\n * or swapping every row to a different layout. This forces a synchronous\n * re-measure (one `offsetHeight` read per visible row), so do NOT call it on\n * routine edits: a single cell edit keeps its row's size, and the engine's\n * ResizeObserver picks up any incidental resize on its own.\n */\n recalculate(): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n // Discard the cached heights, then re-render. The engine re-measures and\n // re-caches the rendered rows during the pass and refreshes the scroll\n // percentage, so an in-place height change (e.g. expand/collapse) is\n // reflected in the total content height and scrollbar immediately.\n this.hostRef.scroller.clearAllCaches();\n return this.render();\n }\n\n /** Jump directly to an element index, then render. */\n jumpToElement(index: number): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n this.hostRef.scroller.jumpToElement(index);\n return this.render();\n }\n\n /** Scroll to a percentage (0..100), then render. */\n scrollToPercentage(percentage: number): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n this.hostRef.scroller.handleScrollPercentage(percentage);\n return this.render();\n }\n\n /** Reset to the top, then render. */\n reset(): MeasuredViewportRange | null {\n if (!this.hostRef) return null;\n this.hostRef.scroller.reset();\n return this.render();\n }\n\n private recreate(): void {\n this.viewportSub?.unsubscribe();\n this.viewportSub = null;\n\n this.destroyAllViews();\n\n this.hostRef?.destroy();\n this.hostRef = null;\n\n this.ensureInitialized();\n }\n\n private ensureInitialized(): void {\n if (this.hostRef) return;\n\n const container = this.host.nativeElement;\n const total = coerceTotalElements(this.ceriousScrollTotalElements, this.ceriousScrollItems?.length ?? null);\n\n this.hostRef = this.cerious.createHost(container, total, this.ceriousScrollOptions, this.ngZone, () => {\n // Coalesce scroll-driven renders to one per frame (the native scrollbar\n // can fire many scroll events between paints).\n if (this.ceriousScrollAutoRender) this.scheduleRender();\n });\n\n this.viewportSub = this.hostRef.viewportChanges$.subscribe((detail: CeriousViewportChangeDetail) => {\n // Skip the global tick when nobody is bound to the output.\n if (this.ceriousScrollViewportChange.observed) {\n this.ngZone.run(() => this.ceriousScrollViewportChange.emit(detail));\n }\n });\n\n this.ceriousScrollReady.emit(this.hostRef.scroller);\n\n if (this.ceriousScrollAutoRender) {\n queueMicrotask(() => this.render());\n }\n }\n\n private getItemForIndex(index: number): TItem {\n const getter = this.ceriousScrollGetItem;\n if (getter) return getter(index);\n\n const items = this.ceriousScrollItems;\n if (!items) return undefined as TItem;\n\n return items[index];\n }\n\n private renderTemplateIntoContainer(\n template: TemplateRef<CeriousScrollItemTemplateContext<TItem>>,\n index: number,\n elementContainer: HTMLElement\n ): void {\n const previous = this.viewByContainer.get(elementContainer);\n if (previous) {\n // Recycle: update the bound context and run local CD instead of\n // destroying the embedded view and rebuilding the entire row tree.\n const item = this.getItemForIndex(index);\n previous.context.$implicit = item;\n previous.context.item = item;\n previous.context.index = index;\n // The core engine wipes elementContainer.textContent when reusing it from\n // its pool, orphaning the view's root nodes. Re-append them defensively.\n if (previous.rootNodes.length && previous.rootNodes[0].parentNode !== elementContainer) {\n for (const node of previous.rootNodes) {\n elementContainer.appendChild(node);\n }\n }\n previous.detectChanges();\n return;\n }\n\n // Try to reuse a pooled view from a container that scrolled out of viewport.\n const pooled = this.freeViews.pop();\n if (pooled) {\n const item = this.getItemForIndex(index);\n pooled.context.$implicit = item;\n pooled.context.item = item;\n pooled.context.index = index;\n elementContainer.textContent = '';\n for (const node of pooled.rootNodes) {\n elementContainer.appendChild(node);\n }\n pooled.detectChanges();\n this.viewByContainer.set(elementContainer, pooled);\n return;\n }\n\n // No prior view for this container: create one.\n // Enter the Angular zone so the new view's template event listeners\n // ((click), (input), etc.) are zone-patched. This path runs at most\n // once per visible row (after that, the view is recycled from the pool).\n elementContainer.textContent = '';\n\n const item = this.getItemForIndex(index);\n const view = this.ngZone.run(() => {\n const v = template.createEmbeddedView({ $implicit: item, item, index });\n this.appRef.attachView(v);\n v.detectChanges();\n return v;\n });\n\n for (const node of view.rootNodes) {\n elementContainer.appendChild(node);\n }\n\n this.viewByContainer.set(elementContainer, view);\n }\n\n /**\n * Update the bound item/index on every currently-rendered row's embedded view\n * and run change detection, without recreating the views. Used when the data\n * reference changes but the visible indices (and their heights) do not, so row\n * state (focus, selection, open dropdowns) survives the update.\n */\n /**\n * Re-bind each currently rendered row's context (from the current items/getter)\n * and run change detection on its embedded view. Use this after mutating row\n * state in place (e.g. selection flags) or column-level state read by the row\n * template, when row identity and visible indices have not changed. Cheap\n * relative to `render()` — does not invoke the engine's measurement pass.\n */\n refreshRenderedContent(): void {\n if (!this.hostRef) return;\n const scroller = this.hostRef.scroller;\n\n for (const index of scroller.getRenderedIndices()) {\n const container = scroller.getRenderedElement(index);\n if (!container) continue;\n\n const view = this.viewByContainer.get(container);\n if (!view) continue;\n\n const item = this.getItemForIndex(index);\n view.context.$implicit = item;\n view.context.item = item;\n view.context.index = index;\n view.detectChanges();\n }\n }\n\n private destroyAllViews(): void {\n for (const view of this.viewByContainer.values()) {\n this.appRef.detachView(view);\n view.destroy();\n }\n this.viewByContainer.clear();\n for (const view of this.freeViews) {\n this.appRef.detachView(view);\n view.destroy();\n }\n this.freeViews.length = 0;\n }\n}\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n TemplateRef,\n} from '@angular/core';\n\nimport { CeriousScrollDirective } from './cerious-scroll.directive';\nimport {\n CeriousScrollItemTemplateDirective,\n type CeriousScrollItemTemplateContext,\n} from './cerious-scroll-item-template.directive';\n\n@Component({\n selector: 'cerious-scroll',\n standalone: true,\n imports: [],\n hostDirectives: [\n {\n directive: CeriousScrollDirective,\n inputs: [\n 'ceriousScrollTotalElements: totalElements',\n 'ceriousScrollItems: items',\n 'ceriousScrollGetItem: getItem',\n 'ceriousScrollItemTemplate: itemTemplate',\n 'ceriousScrollOptions: options',\n 'ceriousScrollAutoRender: autoRender',\n ],\n outputs: [\n 'ceriousScrollViewportChange: viewportChange',\n 'ceriousScrollMeasuredViewport: measuredViewport',\n 'ceriousScrollReady: scrollerReady',\n ],\n },\n ],\n template: `<ng-content />`,\n styles: `\n :host {\n display: block;\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CeriousScrollComponent<TItem = unknown> implements AfterContentInit {\n /** Optional convenience template marker: <ng-template ceriousScrollItem let-item let-index=\"index\">...</ng-template> */\n @ContentChild(CeriousScrollItemTemplateDirective)\n private readonly projectedItemTemplate?: CeriousScrollItemTemplateDirective<TItem>;\n\n constructor(private readonly ceriousScroll: CeriousScrollDirective<TItem>) {}\n\n ngAfterContentInit(): void {\n if (this.ceriousScroll.ceriousScrollItemTemplate) return;\n if (!this.projectedItemTemplate) return;\n\n this.ceriousScroll.ceriousScrollItemTemplate =\n this.projectedItemTemplate.templateRef as TemplateRef<CeriousScrollItemTemplateContext<TItem>>;\n\n if (this.ceriousScroll.ceriousScrollAutoRender) {\n queueMicrotask(() => this.ceriousScroll.render());\n }\n }\n}\n","/*\n * Public API Surface of ngx-cerious-scroll\n */\n\nexport * from './lib/cerious-scroll.types';\nexport * from './lib/cerious-scroll.observable';\nexport * from './lib/cerious-scroll-item-template.directive';\nexport * from './lib/cerious-scroll.directive';\nexport * from './lib/cerious-scroll.component';\nexport * from './lib/cerious-scroll.service';\n\n// Re-export upstream types/classes for convenience\nexport * from '@ceriousdevtech/cerious-scroll';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.CeriousScrollService","i1.CeriousScrollDirective"],"mappings":";;;;;;AAUA;;;;AAIG;AACG,SAAU,sBAAsB,CAAC,SAAsB,EAAA;IAC3D,MAAM,WAAW,GAAG,SAAS,CAA6B,SAAS,EAAE,yBAAyB,CAAC,CAAC,IAAI,CAClG,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,MAAM,CAAC,CACzB;;;AAID,IAAA,MAAM,aAAa,GAAG,SAAS,CAA4C,SAAS,EAAE,iBAAiB,CAAC,CAAC,IAAI,CAC3G,GAAG,CAAC,CAAC,GAAG,KAAI;AACV,QAAA,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE;QAC7F,OAAO;AACL,YAAA,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU;AACjC,YAAA,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO;AAClC,YAAA,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,YAAY;YACrC,MAAM;SAC+B;IACzC,CAAC,CAAC,CACH;AAED,IAAA,OAAO,KAAK,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;AACxD;;ACxBA;;;;;;;;;;;AAWG;MAKU,kCAAkC,CAAA;AAC7C,IAAA,WAAA,CAA4B,WAAiE,EAAA;QAAjE,IAAA,CAAA,WAAW,GAAX,WAAW;IAAyD;+GADrF,kCAAkC,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,WAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAlC,kCAAkC,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gCAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;4FAAlC,kCAAkC,EAAA,UAAA,EAAA,CAAA;kBAJ9C,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,gCAAgC;AAC1C,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;AClBD,MAAM,YAAY,GAAG,6BAA6B;AAElD,SAAS,oBAAoB,CAAC,SAAsB,EAAA;IAClD,MAAM,QAAQ,GAAG,SAAS,CAAC,aAAa,CAAc,CAAA,CAAA,EAAI,YAAY,CAAA,CAAA,CAAG,CAAC;AAC1E,IAAA,IAAI,QAAQ;AAAE,QAAA,OAAO,QAAQ;IAE7B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AACxC,IAAA,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,CAAC;AACjC,IAAA,EAAE,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU;AAC9B,IAAA,EAAE,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM;AACvB,IAAA,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM;AACxB,IAAA,EAAE,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ;AAC5B,IAAA,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;AACzB,IAAA,OAAO,EAAE;AACX;MAKa,oBAAoB,CAAA;IAC/B,UAAU,CACR,SAAsB,EACtB,aAAqB,EACrB,OAA6B,EAC7B,MAAc,EACd,YAAyB,EAAA;AAEzB,QAAA,MAAM,cAAc,GAAG,oBAAoB,CAAC,SAAS,CAAC;AACtD,QAAA,IAAI,QAAwB;AAE5B,QAAA,MAAM,CAAC,iBAAiB,CAAC,MAAK;AAC5B,YAAA,MAAM,YAAY,GAAG,OAAO,EAAE,QAAQ;AACtC,YAAA,MAAM,aAAa,GAAyB;AAC1C,gBAAA,GAAG,OAAO;gBACV,QAAQ,EAAE,MAAK;oBACb,YAAY,IAAI;oBAChB,YAAY,IAAI;gBAClB,CAAC;aACF;YAED,QAAQ,GAAG,IAAI,aAAa,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC;AACvE,QAAA,CAAC,CAAC;AAEF,QAAA,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,SAAS,CAAC;QAE1D,OAAO;YACL,QAAQ;YACR,cAAc;YACd,gBAAgB;YAChB,OAAO,EAAE,MAAK;;AAEZ,gBAAA,cAAc,CAAC,WAAW,GAAG,EAAE;AAC/B,gBAAA,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC;gBACnC,QAAQ,CAAC,OAAO,EAAE;YACpB,CAAC;SACF;IACH;+GArCW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAApB,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,oBAAoB,cAFnB,MAAM,EAAA,CAAA,CAAA;;4FAEP,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAHhC,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE;AACb,iBAAA;;;ACCD,SAAS,mBAAmB,CAAC,aAAwC,EAAE,QAAmC,EAAA;IACxG,MAAM,SAAS,GAAG,OAAO,aAAa,KAAK,QAAQ,GAAG,aAAa,GAAG,OAAO,QAAQ,KAAK,QAAQ,GAAG,QAAQ,GAAG,SAAS;IACzH,IAAI,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;AACtD,QAAA,MAAM,IAAI,KAAK,CAAC,uFAAuF,CAAC;IAC1G;;IAEA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC;AAC/B;MAMa,sBAAsB,CAAA;AAuCjC,IAAA,WAAA,CACmB,IAA6B,EAC7B,MAAsB,EACtB,MAAc,EACd,OAA6B,EAAA;QAH7B,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,OAAO,GAAP,OAAO;;QAzCjB,IAAA,CAAA,0BAA0B,GAAkB,IAAI;;QAGhD,IAAA,CAAA,kBAAkB,GAA4B,IAAI;;QAGlD,IAAA,CAAA,oBAAoB,GAAsC,IAAI;;QAG9D,IAAA,CAAA,yBAAyB,GAAgE,IAAI;;QAG7F,IAAA,CAAA,oBAAoB,GAAyB,EAAE;;QAG/C,IAAA,CAAA,uBAAuB,GAAG,IAAI;;AAG7B,QAAA,IAAA,CAAA,2BAA2B,GAAG,IAAI,YAAY,EAA+B;;AAG7E,QAAA,IAAA,CAAA,6BAA6B,GAAG,IAAI,YAAY,EAAyB;;AAGzE,QAAA,IAAA,CAAA,kBAAkB,GAAG,IAAI,YAAY,EAAoC;QAE3E,IAAA,CAAA,OAAO,GAAgC,IAAI;QAC3C,IAAA,CAAA,WAAW,GAAwB,IAAI;QACvC,IAAA,CAAA,oBAAoB,GAAkB,IAAI;AAEjC,QAAA,IAAA,CAAA,eAAe,GAAG,IAAI,GAAG,EAAyE;;;;;QAKlG,IAAA,CAAA,SAAS,GAA+D,EAAE;IAOxF;IAEH,eAAe,GAAA;QACb,IAAI,CAAC,iBAAiB,EAAE;IAC1B;AAEA,IAAA,WAAW,CAAC,OAAsB,EAAA;QAChC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE;QAEnB,IAAI,OAAO,CAAC,oBAAoB,CAAC,IAAI,OAAO,CAAC,4BAA4B,CAAC,EAAE;AAC1E,YAAA,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,0BAA0B,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,IAAI,IAAI,CAAC;YAC3G,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,KAAK,KAAK;YAElE,IAAI,YAAY,EAAE;;;;;;;;;;;gBAWhB,IAAI,CAAC,QAAQ,EAAE;YACjB;AAAO,iBAAA,IAAI,IAAI,CAAC,uBAAuB,EAAE;;;;;;;;;;gBAUvC,IAAI,CAAC,sBAAsB,EAAE;gBAC7B,IAAI,CAAC,MAAM,EAAE;YACf;QACF;AAEA,QAAA,IAAI,OAAO,CAAC,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,WAAW,EAAE;;YAEnF,IAAI,CAAC,QAAQ,EAAE;QACjB;IACF;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,IAAI,CAAC,oBAAoB,IAAI,IAAI,EAAE;AACrC,YAAA,oBAAoB,CAAC,IAAI,CAAC,oBAAoB,CAAC;AAC/C,YAAA,IAAI,CAAC,oBAAoB,GAAG,IAAI;QAClC;AAEA,QAAA,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE;AAC/B,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;QAEvB,IAAI,CAAC,eAAe,EAAE;AAEtB,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE;AACvB,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;IACrB;;IAGA,MAAM,GAAA;QACJ,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,yBAAyB;AAC/C,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,IAAI;AAE1B,QAAA,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa;QAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,YAAY,IAAI,aAAa,CAAC,YAAY;AACvE,QAAA,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc;;;;;QAMpD,IAAI,mBAAmB,GAAG,CAAC;AAC3B,QAAA,MAAM,QAAQ,GAAoB,CAAC,KAAK,EAAE,gBAAgB,KAAI;AAC5D,YAAA,mBAAmB,EAAE;;;;;;;YAOrB,IAAI,CAAC,2BAA2B,CAAC,QAAQ,EAAE,KAAK,EAAE,gBAAgB,CAAC;AACrE,QAAA,CAAC;AAED,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,CAAC;AAEtF,QAAA,IAAI,mBAAmB,KAAK,CAAC,EAAE;;;AAG7B,YAAA,OAAO,KAAK;QACd;;;;;;;QAQA,IAAI,CAAC,kBAAkB,EAAE;;AAGzB,QAAA,IAAI,IAAI,CAAC,6BAA6B,CAAC,QAAQ,EAAE;AAC/C,YAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE;AACA,QAAA,OAAO,KAAK;IACd;;IAGQ,kBAAkB,GAAA;QACxB,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC;YAAE;AACtD,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ;AACtC,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe;QACrC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,kBAAkB,EAAE,EAAE;YACjD,MAAM,EAAE,GAAG,QAAQ,CAAC,kBAAkB,CAAC,KAAK,CAAC;AAC7C,YAAA,IAAI,EAAE;AAAE,gBAAA,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB;QACA,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE;YACpD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;;;AAG1B,gBAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE;oBACjC,IAAI,IAAI,CAAC,UAAU;AAAE,wBAAA,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC;gBACxD;AACA,gBAAA,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC;AACtC,gBAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAC3B;QACF;IACF;;IAGQ,cAAc,GAAA;AACpB,QAAA,IAAI,IAAI,CAAC,oBAAoB,IAAI,IAAI;YAAE;AACvC,QAAA,IAAI,CAAC,oBAAoB,GAAG,qBAAqB,CAAC,MAAK;AACrD,YAAA,IAAI,CAAC,oBAAoB,GAAG,IAAI;;;;;;;;;YAShC,IAAI,CAAC,MAAM,EAAE;AACf,QAAA,CAAC,CAAC;IACJ;AAEA;;;;;;;;;AASG;IACH,WAAW,GAAA;QACT,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;;;;;AAK9B,QAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,EAAE;AACtC,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE;IACtB;;AAGA,IAAA,aAAa,CAAC,KAAa,EAAA;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;QAC9B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AAC1C,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE;IACtB;;AAGA,IAAA,kBAAkB,CAAC,UAAkB,EAAA;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;QAC9B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,UAAU,CAAC;AACxD,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE;IACtB;;IAGA,KAAK,GAAA;QACH,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE;AAC7B,QAAA,OAAO,IAAI,CAAC,MAAM,EAAE;IACtB;IAEQ,QAAQ,GAAA;AACd,QAAA,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE;AAC/B,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI;QAEvB,IAAI,CAAC,eAAe,EAAE;AAEtB,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE;AACvB,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;QAEnB,IAAI,CAAC,iBAAiB,EAAE;IAC1B;IAEQ,iBAAiB,GAAA;QACvB,IAAI,IAAI,CAAC,OAAO;YAAE;AAElB,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa;AACzC,QAAA,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,0BAA0B,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,IAAI,IAAI,CAAC;QAE3G,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,oBAAoB,EAAE,IAAI,CAAC,MAAM,EAAE,MAAK;;;YAGpG,IAAI,IAAI,CAAC,uBAAuB;gBAAE,IAAI,CAAC,cAAc,EAAE;AACzD,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,MAAmC,KAAI;;AAEjG,YAAA,IAAI,IAAI,CAAC,2BAA2B,CAAC,QAAQ,EAAE;AAC7C,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtE;AACF,QAAA,CAAC,CAAC;QAEF,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;AAEnD,QAAA,IAAI,IAAI,CAAC,uBAAuB,EAAE;YAChC,cAAc,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACrC;IACF;AAEQ,IAAA,eAAe,CAAC,KAAa,EAAA;AACnC,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,oBAAoB;AACxC,QAAA,IAAI,MAAM;AAAE,YAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AAEhC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,kBAAkB;AACrC,QAAA,IAAI,CAAC,KAAK;AAAE,YAAA,OAAO,SAAkB;AAErC,QAAA,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB;AAEQ,IAAA,2BAA2B,CACjC,QAA8D,EAC9D,KAAa,EACb,gBAA6B,EAAA;QAE7B,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC3D,IAAI,QAAQ,EAAE;;;YAGZ,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;AACxC,YAAA,QAAQ,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI;AACjC,YAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI;AAC5B,YAAA,QAAQ,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK;;;AAG9B,YAAA,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,KAAK,gBAAgB,EAAE;AACtF,gBAAA,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,EAAE;AACrC,oBAAA,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC;gBACpC;YACF;YACA,QAAQ,CAAC,aAAa,EAAE;YACxB;QACF;;QAGA,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;QACnC,IAAI,MAAM,EAAE;YACV,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;AACxC,YAAA,MAAM,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI;AAC/B,YAAA,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI;AAC1B,YAAA,MAAM,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK;AAC5B,YAAA,gBAAgB,CAAC,WAAW,GAAG,EAAE;AACjC,YAAA,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,SAAS,EAAE;AACnC,gBAAA,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC;YACpC;YACA,MAAM,CAAC,aAAa,EAAE;YACtB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC;YAClD;QACF;;;;;AAMA,QAAA,gBAAgB,CAAC,WAAW,GAAG,EAAE;QAEjC,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAK;AAChC,YAAA,MAAM,CAAC,GAAG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACvE,YAAA,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACzB,CAAC,CAAC,aAAa,EAAE;AACjB,YAAA,OAAO,CAAC;AACV,QAAA,CAAC,CAAC;AAEF,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE;AACjC,YAAA,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC;QACpC;QAEA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC;IAClD;AAEA;;;;;AAKG;AACH;;;;;;AAMG;IACH,sBAAsB,GAAA;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE;AACnB,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ;QAEtC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,kBAAkB,EAAE,EAAE;YACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,kBAAkB,CAAC,KAAK,CAAC;AACpD,YAAA,IAAI,CAAC,SAAS;gBAAE;YAEhB,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC;AAChD,YAAA,IAAI,CAAC,IAAI;gBAAE;YAEX,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;AACxC,YAAA,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI;AAC7B,YAAA,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI;AACxB,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK;YAC1B,IAAI,CAAC,aAAa,EAAE;QACtB;IACF;IAEQ,eAAe,GAAA;QACrB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE;AAChD,YAAA,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;YAC5B,IAAI,CAAC,OAAO,EAAE;QAChB;AACA,QAAA,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE;AAC5B,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE;AACjC,YAAA,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;YAC5B,IAAI,CAAC,OAAO,EAAE;QAChB;AACA,QAAA,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;IAC3B;+GAlYW,sBAAsB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,UAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,cAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,MAAA,EAAA,EAAA,EAAA,KAAA,EAAAA,oBAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAtB,sBAAsB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,0BAAA,EAAA,4BAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,oBAAA,EAAA,sBAAA,EAAA,yBAAA,EAAA,2BAAA,EAAA,oBAAA,EAAA,sBAAA,EAAA,uBAAA,EAAA,yBAAA,EAAA,EAAA,OAAA,EAAA,EAAA,2BAAA,EAAA,6BAAA,EAAA,6BAAA,EAAA,+BAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,EAAA,aAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;4FAAtB,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBAJlC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;iKAGU,0BAA0B,EAAA,CAAA;sBAAlC;gBAGQ,kBAAkB,EAAA,CAAA;sBAA1B;gBAGQ,oBAAoB,EAAA,CAAA;sBAA5B;gBAGQ,yBAAyB,EAAA,CAAA;sBAAjC;gBAGQ,oBAAoB,EAAA,CAAA;sBAA5B;gBAGQ,uBAAuB,EAAA,CAAA;sBAA/B;gBAGS,2BAA2B,EAAA,CAAA;sBAApC;gBAGS,6BAA6B,EAAA,CAAA;sBAAtC;gBAGS,kBAAkB,EAAA,CAAA;sBAA3B;;;MCtBU,sBAAsB,CAAA;AAKjC,IAAA,WAAA,CAA6B,aAA4C,EAAA;QAA5C,IAAA,CAAA,aAAa,GAAb,aAAa;IAAkC;IAE5E,kBAAkB,GAAA;AAChB,QAAA,IAAI,IAAI,CAAC,aAAa,CAAC,yBAAyB;YAAE;QAClD,IAAI,CAAC,IAAI,CAAC,qBAAqB;YAAE;QAEjC,IAAI,CAAC,aAAa,CAAC,yBAAyB;AAC1C,YAAA,IAAI,CAAC,qBAAqB,CAAC,WAAmE;AAEhG,QAAA,IAAI,IAAI,CAAC,aAAa,CAAC,uBAAuB,EAAE;YAC9C,cAAc,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;QACnD;IACF;+GAjBW,sBAAsB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAAC,sBAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;mGAAtB,sBAAsB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,uBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAEnB,kCAAkC,EAAA,WAAA,EAAA,IAAA,EAAA,CAAA,EAAA,cAAA,EAAA,CAAA,EAAA,SAAA,EAAAA,sBAAA,EAAA,MAAA,EAAA,CAAA,4BAAA,EAAA,eAAA,EAAA,oBAAA,EAAA,OAAA,EAAA,sBAAA,EAAA,SAAA,EAAA,2BAAA,EAAA,cAAA,EAAA,sBAAA,EAAA,SAAA,EAAA,yBAAA,EAAA,YAAA,CAAA,EAAA,OAAA,EAAA,CAAA,6BAAA,EAAA,gBAAA,EAAA,+BAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,eAAA,CAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAVtC,CAAA,cAAA,CAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,wBAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAQf,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBA9BlC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,EAAA,UAAA,EACd,IAAI,EAAA,OAAA,EACP,EAAE,EAAA,cAAA,EACK;AACd,wBAAA;AACE,4BAAA,SAAS,EAAE,sBAAsB;AACjC,4BAAA,MAAM,EAAE;gCACN,2CAA2C;gCAC3C,2BAA2B;gCAC3B,+BAA+B;gCAC/B,yCAAyC;gCACzC,+BAA+B;gCAC/B,qCAAqC;AACtC,6BAAA;AACD,4BAAA,OAAO,EAAE;gCACP,6CAA6C;gCAC7C,iDAAiD;gCACjD,mCAAmC;AACpC,6BAAA;AACF,yBAAA;AACF,qBAAA,EAAA,QAAA,EACS,CAAA,cAAA,CAAgB,EAAA,eAAA,EAMT,uBAAuB,CAAC,MAAM,EAAA,MAAA,EAAA,CAAA,wBAAA,CAAA,EAAA;wFAK9B,qBAAqB,EAAA,CAAA;sBADrC,YAAY;uBAAC,kCAAkC;;;AC9ClD;;AAEG;;ACFH;;AAEG;;;;"}
@@ -29,17 +29,54 @@ export declare class CeriousScrollDirective<TItem = unknown> implements AfterVie
29
29
  ceriousScrollReady: EventEmitter<import("@ceriousdevtech/cerious-scroll").CeriousScroll>;
30
30
  private hostRef;
31
31
  private viewportSub;
32
+ private scheduledRenderFrame;
32
33
  private readonly viewByContainer;
34
+ private readonly freeViews;
33
35
  constructor(host: ElementRef<HTMLElement>, appRef: ApplicationRef, ngZone: NgZone, cerious: CeriousScrollService);
34
36
  ngAfterViewInit(): void;
35
37
  ngOnChanges(changes: SimpleChanges): void;
36
38
  ngOnDestroy(): void;
37
39
  /** Imperatively trigger a render pass (uses `ceriousScrollItemTemplate`). */
38
40
  render(): MeasuredViewportRange | null;
41
+ /** Tear down embedded views whose container is no longer part of the viewport. */
42
+ private pruneDetachedViews;
43
+ /** Auto-render coalesced to at most once per animation frame. */
44
+ private scheduleRender;
45
+ /**
46
+ * Discard all cached row heights and re-measure the viewport.
47
+ *
48
+ * Call this only when the heights of rows you've *already rendered* may have
49
+ * changed without their indices changing — e.g. a global font/density change,
50
+ * or swapping every row to a different layout. This forces a synchronous
51
+ * re-measure (one `offsetHeight` read per visible row), so do NOT call it on
52
+ * routine edits: a single cell edit keeps its row's size, and the engine's
53
+ * ResizeObserver picks up any incidental resize on its own.
54
+ */
55
+ recalculate(): MeasuredViewportRange | null;
56
+ /** Jump directly to an element index, then render. */
57
+ jumpToElement(index: number): MeasuredViewportRange | null;
58
+ /** Scroll to a percentage (0..100), then render. */
59
+ scrollToPercentage(percentage: number): MeasuredViewportRange | null;
60
+ /** Reset to the top, then render. */
61
+ reset(): MeasuredViewportRange | null;
39
62
  private recreate;
40
63
  private ensureInitialized;
41
64
  private getItemForIndex;
42
65
  private renderTemplateIntoContainer;
66
+ /**
67
+ * Update the bound item/index on every currently-rendered row's embedded view
68
+ * and run change detection, without recreating the views. Used when the data
69
+ * reference changes but the visible indices (and their heights) do not, so row
70
+ * state (focus, selection, open dropdowns) survives the update.
71
+ */
72
+ /**
73
+ * Re-bind each currently rendered row's context (from the current items/getter)
74
+ * and run change detection on its embedded view. Use this after mutating row
75
+ * state in place (e.g. selection flags) or column-level state read by the row
76
+ * template, when row identity and visible indices have not changed. Cheap
77
+ * relative to `render()` — does not invoke the engine's measurement pass.
78
+ */
79
+ refreshRenderedContent(): void;
43
80
  private destroyAllViews;
44
81
  static ɵfac: i0.ɵɵFactoryDeclaration<CeriousScrollDirective<any>, never>;
45
82
  static ɵdir: i0.ɵɵDirectiveDeclaration<CeriousScrollDirective<any>, "[ceriousScroll]", never, { "ceriousScrollTotalElements": { "alias": "ceriousScrollTotalElements"; "required": false; }; "ceriousScrollItems": { "alias": "ceriousScrollItems"; "required": false; }; "ceriousScrollGetItem": { "alias": "ceriousScrollGetItem"; "required": false; }; "ceriousScrollItemTemplate": { "alias": "ceriousScrollItemTemplate"; "required": false; }; "ceriousScrollOptions": { "alias": "ceriousScrollOptions"; "required": false; }; "ceriousScrollAutoRender": { "alias": "ceriousScrollAutoRender"; "required": false; }; }, { "ceriousScrollViewportChange": "ceriousScrollViewportChange"; "ceriousScrollMeasuredViewport": "ceriousScrollMeasuredViewport"; "ceriousScrollReady": "ceriousScrollReady"; }, never, never, true, never>;
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@ceriousdevtech/ngx-cerious-scroll",
3
- "version": "1.0.0",
4
- "license": "(MIT OR LicenseRef-CeriousScroll-Commercial)",
3
+ "version": "1.0.2",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/ceriousdevtech/ngx-cerious-scroll"
8
+ },
5
9
  "peerDependencies": {
6
- "@angular/common": "^17.3.0",
7
- "@angular/core": "^17.3.0",
8
- "@ceriousdevtech/cerious-scroll": "^1.0.0"
10
+ "@angular/common": ">=16.0.0",
11
+ "@angular/core": ">=16.0.0",
12
+ "@ceriousdevtech/cerious-scroll": "^1.0.1"
9
13
  },
10
14
  "dependencies": {
11
15
  "tslib": "^2.3.0"
@@ -1,71 +0,0 @@
1
- ngx-cerious-scroll Commercial License
2
-
3
- Copyright © 2024–2026 Cerious DevTech LLC.
4
- All rights reserved.
5
-
6
- This Commercial License governs use of ngx-cerious-scroll only when you have
7
- entered into a separately executed commercial agreement with Cerious DevTech LLC.
8
-
9
- If you do not have such an agreement, you may use this software under the
10
- MIT license (see LICENSE).
11
-
12
- A commercial agreement is commonly used when ngx-cerious-scroll is:
13
-
14
- - Redistributed as part of a commercial product
15
- - Bundled with a proprietary SDK, framework, or component library
16
- - Integrated into a low-code / no-code platform
17
- - Used in OEM, white-label, or downstream resale offerings
18
- - Included in internal frameworks or platforms sold or licensed to third parties
19
-
20
- This license grants the Licensee a non-exclusive, non-transferable,
21
- non-sublicensable right to use, modify, and distribute ngx-cerious-scroll
22
- solely under the terms of that commercial agreement.
23
-
24
- ### Restrictions
25
-
26
- Except as explicitly permitted in a written commercial agreement, the Licensee
27
- may not:
28
-
29
- - Redistribute ngx-cerious-scroll as a standalone product
30
- - Offer ngx-cerious-scroll as part of a competing scrolling, virtualization,
31
- or UI infrastructure library
32
- - Remove or obscure copyright, trademark, or patent notices
33
- - Grant sublicenses or derivative commercial licenses
34
-
35
- ### Ownership
36
-
37
- ngx-cerious-scroll and all associated intellectual property rights, including
38
- but not limited to copyrights, patents, and trademarks, remain the exclusive
39
- property of Cerious DevTech LLC.
40
-
41
- No rights are granted except those expressly stated in this license.
42
-
43
- ### Patent Rights
44
-
45
- ngx-cerious-scroll includes technology covered by one or more pending patent
46
- applications owned by Cerious DevTech LLC.
47
-
48
- This license grants only those patent rights explicitly provided in a
49
- commercial agreement. No implied patent licenses are granted.
50
-
51
- ### Termination
52
-
53
- Any violation of this license immediately terminates the rights granted herein
54
- without notice.
55
-
56
- Upon termination, the Licensee must cease all use and distribution of
57
- ngx-cerious-scroll.
58
-
59
- ### Warranty Disclaimer
60
-
61
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63
- FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
64
-
65
- ### Limitation of Liability
66
-
67
- IN NO EVENT SHALL CERIOUS DEVTECH LLC BE LIABLE FOR ANY CLAIM, DAMAGES, OR
68
- OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
69
-
70
- For commercial licensing inquiries, contact:
71
- info@ceriousdevtech.com