selective-ui 1.2.3 → 1.2.5

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.
Files changed (59) hide show
  1. package/dist/selective-ui.css.map +1 -1
  2. package/dist/selective-ui.esm.js +5462 -1043
  3. package/dist/selective-ui.esm.js.map +1 -1
  4. package/dist/selective-ui.esm.min.js +2 -2
  5. package/dist/selective-ui.esm.min.js.br +0 -0
  6. package/dist/selective-ui.min.js +2 -2
  7. package/dist/selective-ui.min.js.br +0 -0
  8. package/dist/selective-ui.umd.js +5463 -1044
  9. package/dist/selective-ui.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/ts/adapter/mixed-adapter.ts +312 -65
  12. package/src/ts/components/accessorybox.ts +248 -28
  13. package/src/ts/components/directive.ts +91 -11
  14. package/src/ts/components/option-handle.ts +191 -28
  15. package/src/ts/components/placeholder.ts +111 -16
  16. package/src/ts/components/popup/empty-state.ts +162 -0
  17. package/src/ts/components/popup/loading-state.ts +160 -0
  18. package/src/ts/components/{popup.ts → popup/popup.ts} +167 -71
  19. package/src/ts/components/searchbox.ts +225 -20
  20. package/src/ts/components/selectbox.ts +498 -120
  21. package/src/ts/core/base/adapter.ts +200 -53
  22. package/src/ts/core/base/fenwick.ts +147 -0
  23. package/src/ts/core/base/lifecycle.ts +258 -0
  24. package/src/ts/core/base/model.ts +120 -31
  25. package/src/ts/core/base/recyclerview.ts +55 -18
  26. package/src/ts/core/base/view.ts +87 -19
  27. package/src/ts/core/base/virtual-recyclerview.ts +475 -202
  28. package/src/ts/core/model-manager.ts +166 -85
  29. package/src/ts/core/search-controller.ts +236 -38
  30. package/src/ts/global.ts +6 -6
  31. package/src/ts/index.ts +6 -6
  32. package/src/ts/models/group-model.ts +159 -32
  33. package/src/ts/models/option-model.ts +213 -54
  34. package/src/ts/services/dataset-observer.ts +72 -10
  35. package/src/ts/services/ea-observer.ts +92 -15
  36. package/src/ts/services/effector.ts +181 -32
  37. package/src/ts/services/refresher.ts +30 -6
  38. package/src/ts/services/resize-observer.ts +132 -15
  39. package/src/ts/services/select-observer.ts +115 -50
  40. package/src/ts/types/components/searchbox.type.ts +1 -1
  41. package/src/ts/types/core/base/adapter.type.ts +2 -1
  42. package/src/ts/types/core/base/lifecycle.type.ts +62 -0
  43. package/src/ts/types/core/base/model.type.ts +3 -1
  44. package/src/ts/types/core/base/recyclerview.type.ts +2 -8
  45. package/src/ts/types/core/base/view.type.ts +36 -24
  46. package/src/ts/types/utils/ievents.type.ts +6 -1
  47. package/src/ts/utils/callback-scheduler.ts +112 -34
  48. package/src/ts/utils/ievents.ts +91 -29
  49. package/src/ts/utils/istorage.ts +1 -1
  50. package/src/ts/utils/selective.ts +474 -88
  51. package/src/ts/views/group-view.ts +170 -21
  52. package/src/ts/views/option-view.ts +349 -68
  53. package/src/ts/components/empty-state.ts +0 -68
  54. package/src/ts/components/loading-state.ts +0 -66
  55. /package/src/css/components/{empty-state.css → popup/empty-state.css} +0 -0
  56. /package/src/css/components/{loading-state.css → popup/loading-state.css} +0 -0
  57. /package/src/css/components/{popup.css → popup/popup.css} +0 -0
  58. /package/src/css/{components/optgroup.css → views/group-view.css} +0 -0
  59. /package/src/css/{components/option.css → views/option-view.css} +0 -0
@@ -3,81 +3,72 @@ import { RecyclerView } from "./recyclerview";
3
3
  import { AdapterContract } from "src/ts/types/core/base/adapter.type";
4
4
  import { Libs } from "src/ts/utils/libs";
5
5
  import { VirtualOptions, VirtualRecyclerViewTags } from "src/ts/types/core/base/virtual-recyclerview.type";
6
+ import { Lifecycle } from "./lifecycle";
7
+ import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
8
+ import { Fenwick } from "./fenwick";
6
9
 
7
10
  /**
8
- * Fenwick tree (Binary Indexed Tree) for efficient prefix sum queries.
9
- * Supports O(log n) update and query operations for cumulative item heights.
10
- * Uses 1-based indexing internally for BIT operations.
11
- */
12
- class Fenwick {
13
- private bit: number[] = [];
14
- private n = 0;
15
-
16
- constructor(n = 0) { this.reset(n); }
17
-
18
- /** Resets tree to new size, clearing all values. */
19
- reset(n: number) {
20
- this.n = n;
21
- this.bit = new Array(n + 1).fill(0);
22
- }
23
-
24
- /** Adds delta to element at 1-based index i. */
25
- add(i: number, delta: number) {
26
- for (let x = i; x <= this.n; x += x & -x) this.bit[x] += delta;
27
- }
28
-
29
- /** Returns prefix sum for range [1..i]. */
30
- sum(i: number): number {
31
- let s = 0;
32
- for (let x = i; x > 0; x -= x & -x) s += this.bit[x];
33
- return s;
34
- }
35
-
36
- /** Returns sum in range [l..r] (1-based, inclusive). */
37
- rangeSum(l: number, r: number): number {
38
- return r < l ? 0 : this.sum(r) - this.sum(l - 1);
39
- }
40
-
41
- /** Builds tree from 0-based array in O(n log n). */
42
- buildFrom(arr: number[]) {
43
- this.reset(arr.length);
44
- arr.forEach((val, i) => this.add(i + 1, val));
45
- }
46
-
47
- /**
48
- * Binary search to find largest index where prefix sum <= target.
49
- * Returns count of items that fit within target height.
50
- */
51
- lowerBoundPrefix(target: number): number {
52
- let idx = 0, bitMask = 1;
53
- while (bitMask << 1 <= this.n) bitMask <<= 1;
54
-
55
- let cur = 0;
56
- for (let step = bitMask; step !== 0; step >>= 1) {
57
- const next = idx + step;
58
- if (next <= this.n && cur + this.bit[next] <= target) {
59
- idx = next;
60
- cur += this.bit[next];
61
- }
62
- }
63
- return idx;
64
- }
65
- }
66
-
67
- /**
68
- * Virtual RecyclerView with efficient windowing and dynamic height support.
69
- *
70
- * Only renders items visible in viewport plus overscan buffer, using padding
71
- * elements to simulate scroll height. Supports variable item heights with
72
- * adaptive estimation and maintains scroll position during height changes.
73
- *
74
- * @template TItem - Model type for list items
75
- * @template TAdapter - Adapter managing item views
11
+ * Virtualized RecyclerView with windowing and dynamic-height support.
12
+ *
13
+ * This recycler only keeps the **visible window** mounted in the DOM, plus an overscan buffer,
14
+ * while simulating the full scroll height using top/bottom padding elements.
15
+ *
16
+ * ### Responsibility
17
+ * - Maintain a viewport window `[start..end]` over adapter items and mount/unmount DOM accordingly.
18
+ * - Support **variable row heights** using measured outer heights (including vertical margins).
19
+ * - Provide stable scrolling under height changes via an **anchor correction** strategy.
20
+ * - Integrate with item visibility (filtering): invisible items are treated as height `0` and are not mounted.
21
+ *
22
+ * ### Virtualization strategy
23
+ * - **Prefix sums** over heights are maintained in a {@link Fenwick} tree:
24
+ * - `offsetTopOf(i)` prefix sum for heights before item `i`
25
+ * - `findFirstVisibleIndex(scrollTop)` → lower-bound over prefix sums (then forward-scan to visible)
26
+ * - **Overscan** is expressed in item multiples and converted to pixels using the current estimate:
27
+ * `overscanPx = overscan * estimate`.
28
+ * - **Adaptive estimate** can be enabled to use the running average of measured items as the estimate.
29
+ *
30
+ * ### Dynamic heights (measurement)
31
+ * - When enabled, visible items are measured using `getBoundingClientRect()` + computed margins.
32
+ * - A {@link ResizeObserver} observes the host container and schedules re-measurement on the next animation frame.
33
+ * - Height updates are applied incrementally to the Fenwick tree in **O(log n)** per item.
34
+ *
35
+ * ### Anchor correction (scroll stability)
36
+ * - An "anchor index" (first visible item) is derived from the current scroll position.
37
+ * - After re-render and potential height changes, scrollTop is adjusted so the anchor remains visually stable,
38
+ * preventing "jumping" during measurement-driven reflows.
39
+ *
40
+ * ### Lifecycle / idempotency
41
+ * - Mounted scaffold elements are created when an adapter is set via {@link setAdapter}.
42
+ * - `refresh()` is safe to call repeatedly; it rebuilds internal structures and schedules a window update.
43
+ * - `destroy()` is idempotent once in {@link LifecycleState.DESTROYED} and removes scaffold DOM nodes.
44
+ *
45
+ * ### DOM side effects
46
+ * - Mutates DOM under `viewElement` by creating three nodes:
47
+ * - `PadTop`, `ItemsHost`, `PadBottom`
48
+ * - Mounts/unmounts item nodes inside `ItemsHost`
49
+ * - Attaches/removes a scroll listener on the resolved scroll container
50
+ * - Uses `scrollIntoView`/scrollTop assignments when asked to bring an item into view
51
+ *
52
+ * @template TItem - Model type for list items.
53
+ * @template TAdapter - Adapter providing view holders and binding logic.
54
+ *
55
+ * @extends {RecyclerView<TItem, TAdapter>}
56
+ * @see {@link VirtualOptions}
57
+ * @see {@link RecyclerView}
76
58
  */
77
59
  export class VirtualRecyclerView<
78
60
  TItem extends ModelContract<any, any>,
79
61
  TAdapter extends AdapterContract<TItem>
80
62
  > extends RecyclerView<TItem, TAdapter> {
63
+ /**
64
+ * Virtualization settings (materialized to `Required<VirtualOptions>`).
65
+ *
66
+ * - `scrollEl` : External scroll container (if omitted, inferred from DOM)
67
+ * - `estimateItemHeight` : Initial/fallback item height in pixels
68
+ * - `overscan` : Extra viewport height (in item multiples) rendered above/below
69
+ * - `dynamicHeights` : Enable measuring items with ResizeObserver
70
+ * - `adaptiveEstimate` : Use average of measured items as the running estimate
71
+ */
81
72
  private opts: Required<VirtualOptions> = {
82
73
  scrollEl: undefined as HTMLElement,
83
74
  estimateItemHeight: 36,
@@ -86,22 +77,42 @@ export class VirtualRecyclerView<
86
77
  adaptiveEstimate: true,
87
78
  };
88
79
 
80
+ /** Top padding element (simulates offscreen items above). */
89
81
  private PadTop!: HTMLDivElement;
82
+ /** Host container where visible item elements are mounted. */
90
83
  private ItemsHost!: HTMLDivElement;
84
+ /** Bottom padding element (simulates offscreen items below). */
91
85
  private PadBottom!: HTMLDivElement;
86
+ /** Scroll container used for viewport calculations. */
92
87
  private scrollEl!: HTMLElement;
93
88
 
89
+ /** Cache of measured heights per item index (undefined when not measured). */
94
90
  private heightCache: Array<number | undefined> = [];
91
+ /**
92
+ * Fenwick tree storing current height values (in pixels).
93
+ * Invisible items are encoded as height 0.
94
+ */
95
95
  private fenwick = new Fenwick(0);
96
+ /**
97
+ * Map of currently mounted DOM elements keyed by item index.
98
+ * Used to avoid re-creating nodes and to manage ordering within the host.
99
+ */
96
100
  private created = new Map<number, HTMLElement>();
97
101
 
102
+ /** Whether an initial height probe has been performed. */
98
103
  private firstMeasured = false;
104
+ /** Current window bounds (inclusive) in item index space. */
99
105
  private start = 0;
106
+ /** Current window end (inclusive). -1 means not initialized. */
100
107
  private end = -1;
108
+ /** Observer used to detect resize events that may change item heights. */
101
109
  private resizeObs?: ResizeObserver;
102
110
 
111
+ /** Pending animation frame ids for window and measurement. */
103
112
  private rafId: number | null = null;
104
113
  private measureRaf: number | null = null;
114
+
115
+ /** Re-entrancy/suspension flags used to prevent feedback loops. */
105
116
  private updating = false;
106
117
  private suppressResize = false;
107
118
  private lastRenderCount = 0;
@@ -109,30 +120,64 @@ export class VirtualRecyclerView<
109
120
  private boundOnScroll?: () => void;
110
121
  private resumeResizeAfter = false;
111
122
 
123
+ /** Small cache for sticky header height (≈16ms TTL) to limit layout reads. */
112
124
  private stickyCacheTick = 0;
113
125
  private stickyCacheVal = 0;
114
126
 
127
+ /** Stats for adaptive estimator (sum of measured heights / count of measured items). */
115
128
  private measuredSum = 0;
116
129
  private measuredCount = 0;
117
130
 
131
+ /** Epsilon threshold for height-change significance (px). */
118
132
  private static readonly EPS = 0.5;
133
+ /** Attribute stored on each mounted element indicating its item index. */
119
134
  private static readonly ATTR_INDEX = "data-vindex";
120
135
 
121
- /** Creates virtual recycler view with optional root element. */
136
+ /**
137
+ * Creates a virtual recycler view.
138
+ *
139
+ * Note: The virtualization scaffold is built when an adapter is set via {@link setAdapter}.
140
+ *
141
+ * @param {HTMLDivElement | null} [viewElement=null] - Optional root container for the recycler view.
142
+ */
122
143
  constructor(viewElement: HTMLDivElement | null = null) {
123
144
  super(viewElement);
124
145
  }
125
146
 
126
- /** Updates virtualization settings (overscan, heights, etc). */
127
- public configure(opts: Partial<VirtualOptions>) {
147
+ /**
148
+ * Updates virtualization settings (overscan, estimates, dynamic heights, etc.).
149
+ *
150
+ * This only updates internal configuration; consumers should call {@link refresh}
151
+ * to apply changes immediately if needed.
152
+ *
153
+ * @param {Partial<VirtualOptions>} opts - Partial configuration merged into current options.
154
+ * @returns {void}
155
+ */
156
+ public configure(opts: Partial<VirtualOptions>): void {
128
157
  this.opts = { ...this.opts, ...opts } as Required<VirtualOptions>;
129
158
  }
130
159
 
131
- /**
132
- * Binds adapter and initializes virtualization scaffold.
133
- * Removes previous adapter if exists, sets up scroll listeners and DOM structure.
160
+ /**
161
+ * Binds an adapter and initializes the virtualization scaffold.
162
+ *
163
+ * ### Flow
164
+ * 1) Dispose previous listeners/observers if an adapter was already attached
165
+ * 2) Call `super.setAdapter(adapter)` to wire base recycler state
166
+ * 3) Build the scaffold elements (PadTop, ItemsHost, PadBottom)
167
+ * 4) Resolve `scrollEl` (configured `opts.scrollEl` → nearest popup → parentElement)
168
+ * 5) Attach scroll listener, perform initial refresh, attach resize observer
169
+ * 6) Subscribe to adapter visibility updates (if supported) to hard-refresh windowing state
170
+ *
171
+ * DOM side effects:
172
+ * - Clears `viewElement` children and replaces with scaffold nodes.
173
+ * - Attaches a `scroll` listener to `scrollEl` (`passive: true`).
174
+ *
175
+ * @param {TAdapter} adapter - Adapter that provides items and view binding.
176
+ * @returns {void}
177
+ * @throws {Error} If no scroll container can be resolved.
178
+ * @override
134
179
  */
135
- public override setAdapter(adapter: TAdapter) {
180
+ public override setAdapter(adapter: TAdapter): void {
136
181
  if (this.adapter) this.dispose();
137
182
 
138
183
  super.setAdapter(adapter);
@@ -143,17 +188,17 @@ export class VirtualRecyclerView<
143
188
  this.viewElement.replaceChildren();
144
189
 
145
190
  const nodeMounted = Libs.mountNode({
146
- PadTop: { tag: { node: "div", classList: "selective-ui-virtual-pad-top" } },
147
- ItemsHost: { tag: { node: "div", classList: "selective-ui-virtual-items" } },
148
- PadBottom: { tag: { node: "div", classList: "selective-ui-virtual-pad-bottom" } },
191
+ PadTop: { tag: { node: "div", classList: "selective-ui-virtual-pad-top" } },
192
+ ItemsHost:{ tag: { node: "div", classList: "selective-ui-virtual-items" } },
193
+ PadBottom:{ tag: { node: "div", classList: "selective-ui-virtual-pad-bottom" } },
149
194
  }, this.viewElement) as VirtualRecyclerViewTags;
150
195
 
151
196
  this.PadTop = nodeMounted.PadTop;
152
197
  this.ItemsHost = nodeMounted.ItemsHost;
153
198
  this.PadBottom = nodeMounted.PadBottom;
154
199
 
155
- this.scrollEl = this.opts.scrollEl
156
- ?? (this.viewElement.closest(".selective-ui-popup") as HTMLElement)
200
+ this.scrollEl = this.opts.scrollEl
201
+ ?? (this.viewElement.closest(".selective-ui-popup") as HTMLElement)
157
202
  ?? (this.viewElement.parentElement as HTMLElement);
158
203
 
159
204
  if (!this.scrollEl) throw new Error("VirtualRecyclerView: scrollEl not found");
@@ -166,14 +211,20 @@ export class VirtualRecyclerView<
166
211
  (adapter as any)?.onVisibilityChanged?.(() => this.refreshItem());
167
212
  }
168
213
 
169
- /**
170
- * Pauses scroll/resize processing to prevent updates during batch operations.
171
- * Cancels pending frames and disconnects observers.
214
+ /**
215
+ * Suspends scroll/resize processing to prevent window updates during batch operations.
216
+ *
217
+ * Behavior:
218
+ * - Cancels any scheduled animation frames.
219
+ * - Detaches the scroll listener (if attached).
220
+ * - Disconnects ResizeObserver and remembers to restore it on {@link resume}.
221
+ *
222
+ * @returns {void}
172
223
  */
173
- public suspend() {
224
+ public suspend(): void {
174
225
  this.suspended = true;
175
226
  this.cancelFrames();
176
-
227
+
177
228
  if (this.scrollEl && this.boundOnScroll) {
178
229
  this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
179
230
  }
@@ -184,11 +235,17 @@ export class VirtualRecyclerView<
184
235
  }
185
236
  }
186
237
 
187
- /**
188
- * Resumes scroll/resize processing after suspension.
189
- * Re-attaches listeners and schedules window update.
238
+ /**
239
+ * Resumes processing after {@link suspend}.
240
+ *
241
+ * Behavior:
242
+ * - Re-attaches the scroll listener (if available).
243
+ * - Restores ResizeObserver when it was previously disconnected.
244
+ * - Schedules a window recalculation on the next animation frame.
245
+ *
246
+ * @returns {void}
190
247
  */
191
- public resume() {
248
+ public resume(): void {
192
249
  this.suspended = false;
193
250
 
194
251
  if (this.scrollEl && this.boundOnScroll) {
@@ -204,10 +261,19 @@ export class VirtualRecyclerView<
204
261
  }
205
262
 
206
263
  /**
207
- * Rebuilds internal state and schedules render update.
208
- * Probes initial item height on first run, rebuilds Fenwick tree.
209
- *
210
- * @param isUpdate - True if called from data update, false on initial setup
264
+ * Rebuilds internal virtualization state and schedules a render update.
265
+ *
266
+ * Behavior:
267
+ * - When `isUpdate === false`, triggers a hard refresh via {@link refreshItem} (reset + rebuild).
268
+ * - Updates caches to match the adapter item count.
269
+ * - Probes initial item height on first run to seed a better estimate.
270
+ * - Rebuilds Fenwick prefix sums and schedules window computation.
271
+ *
272
+ * No-op if adapter or `viewElement` is missing.
273
+ *
274
+ * @param {boolean} isUpdate - `true` when called due to incremental data update; `false` for initial setup/full replace.
275
+ * @returns {void}
276
+ * @override
211
277
  */
212
278
  public override refresh(isUpdate: boolean): void {
213
279
  if (!this.adapter || !this.viewElement) return;
@@ -218,6 +284,7 @@ export class VirtualRecyclerView<
218
284
 
219
285
  if (count === 0) {
220
286
  this.resetState();
287
+ this.update();
221
288
  return;
222
289
  }
223
290
 
@@ -230,22 +297,37 @@ export class VirtualRecyclerView<
230
297
 
231
298
  this.rebuildFenwick(count);
232
299
  this.scheduleUpdateWindow();
300
+ this.update();
233
301
  }
234
302
 
235
- /**
236
- * Ensures item at index is rendered and optionally scrolls into view.
237
- * Useful for programmatic navigation to specific items.
303
+ /**
304
+ * Ensures the item at `index` is mounted, and optionally scrolls it into view.
305
+ *
306
+ * This is primarily used by navigation/highlight flows where the target may not be rendered
307
+ * due to virtualization.
308
+ *
309
+ * @param {number} index - Item index to ensure visible/mounted.
310
+ * @param {{ scrollIntoView?: boolean }} [opt] - Optional behavior controls.
311
+ * @returns {void}
238
312
  */
239
- public ensureRendered(index: number, opt?: { scrollIntoView?: boolean }) {
313
+ public ensureRendered(index: number, opt?: { scrollIntoView?: boolean }): void {
240
314
  this.mountRange(index, index);
241
315
  if (opt?.scrollIntoView) this.scrollToIndex(index);
242
316
  }
243
317
 
244
- /**
245
- * Scrolls container to make item at index visible.
246
- * Calculates target scroll position accounting for container offset.
318
+ /**
319
+ * Scrolls the scroll container to align the item at `index` into view.
320
+ *
321
+ * Calculation notes:
322
+ * - Computes target top using prefix sums (`offsetTopOf`) and container offset relative to scrollEl.
323
+ * - Clamps scrollTop to the scrollable range to avoid overshoot.
324
+ *
325
+ * No-op when itemCount is 0.
326
+ *
327
+ * @param {number} index - Item index to bring into view.
328
+ * @returns {void}
247
329
  */
248
- public scrollToIndex(index: number) {
330
+ public scrollToIndex(index: number): void {
249
331
  const count = this.adapter?.itemCount?.() ?? 0;
250
332
  if (count <= 0) return;
251
333
 
@@ -253,32 +335,77 @@ export class VirtualRecyclerView<
253
335
  const containerTop = this.containerTopInScroll();
254
336
  const target = containerTop + topInContainer;
255
337
  const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
256
-
338
+
257
339
  this.scrollEl.scrollTop = Math.min(Math.max(0, target), maxScroll);
258
340
  }
259
341
 
260
- /**
261
- * Cleans up all resources: listeners, observers, DOM elements.
262
- * Call before removing component to prevent memory leaks.
342
+ /**
343
+ * Disposes runtime resources without destroying the instance.
344
+ *
345
+ * Intended for adapter swaps or teardown sequencing:
346
+ * - cancels pending frames,
347
+ * - removes scroll listeners,
348
+ * - disconnects ResizeObserver,
349
+ * - removes mounted item elements and clears internal maps.
350
+ *
351
+ * @returns {void}
263
352
  */
264
- public dispose() {
353
+ public dispose(): void {
265
354
  this.cancelFrames();
266
-
355
+
267
356
  if (this.scrollEl && this.boundOnScroll) {
268
357
  this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
269
358
  }
270
-
359
+
271
360
  this.resizeObs?.disconnect();
272
361
  this.created.forEach(el => el.remove());
273
362
  this.created.clear();
274
363
  }
275
364
 
276
365
  /**
277
- * Hard reset after visibility changes (e.g., search/filter cleared).
278
- * Rebuilds all height structures and remounts visible window.
279
- * Essential for fixing padding calculations after bulk visibility changes.
366
+ * Destroys the virtual recycler view and releases all resources.
367
+ *
368
+ * Behavior:
369
+ * - Idempotent: returns early if already in {@link LifecycleState.DESTROYED}.
370
+ * - Resets internal caches and disposes listeners/observers.
371
+ * - Removes scaffold DOM nodes (PadTop, ItemsHost, PadBottom).
372
+ * - Completes lifecycle teardown via {@link Lifecycle.destroy}.
373
+ *
374
+ * @returns {void}
375
+ * @override
376
+ */
377
+ public override destroy(): void {
378
+ if (this.is(LifecycleState.DESTROYED)) {
379
+ return;
380
+ }
381
+
382
+ this.resetState();
383
+ this.dispose();
384
+
385
+ this.PadTop.remove();
386
+ this.ItemsHost.remove();
387
+ this.PadBottom.remove();
388
+
389
+ this.PadTop = null as HTMLDivElement;
390
+ this.ItemsHost = null as HTMLDivElement;
391
+ this.PadBottom = null as HTMLDivElement;
392
+
393
+ super.destroy();
394
+ }
395
+
396
+ /**
397
+ * Hard reset used after large visibility changes (e.g., search/filter cleared).
398
+ *
399
+ * This recalculates padding and height structures by:
400
+ * - suspending processing,
401
+ * - resetting state and removing invisible elements,
402
+ * - recomputing estimator stats from cache,
403
+ * - rebuilding Fenwick prefix sums,
404
+ * - resetting window bounds and resuming updates.
405
+ *
406
+ * @returns {void}
280
407
  */
281
- public refreshItem() {
408
+ public refreshItem(): void {
282
409
  if (!this.adapter) return;
283
410
  const count = this.adapter.itemCount();
284
411
  if (count <= 0) return;
@@ -293,8 +420,8 @@ export class VirtualRecyclerView<
293
420
  this.resume();
294
421
  }
295
422
 
296
- /** Cancels all pending animation frames. */
297
- private cancelFrames() {
423
+ /** Cancels any pending animation frames for window calculation and measurement. */
424
+ private cancelFrames(): void {
298
425
  if (this.rafId != null) {
299
426
  cancelAnimationFrame(this.rafId);
300
427
  this.rafId = null;
@@ -305,8 +432,16 @@ export class VirtualRecyclerView<
305
432
  }
306
433
  }
307
434
 
308
- /** Resets all internal state: DOM, caches, measurements. */
309
- private resetState() {
435
+ /**
436
+ * Resets internal state: mounted elements, caches, Fenwick sums, padding, and estimator stats.
437
+ *
438
+ * DOM side effects:
439
+ * - Removes all currently mounted item elements tracked in {@link created}.
440
+ * - Resets pad heights to `0px`.
441
+ *
442
+ * @returns {void}
443
+ */
444
+ private resetState(): void {
310
445
  this.created.forEach(el => el.remove());
311
446
  this.created.clear();
312
447
  this.heightCache = [];
@@ -318,11 +453,17 @@ export class VirtualRecyclerView<
318
453
  this.measuredCount = 0;
319
454
  }
320
455
 
321
- /**
322
- * Measures first item to set initial height estimate.
323
- * Removes probe element if dynamic heights disabled.
456
+ /**
457
+ * Measures the first item to seed a better initial height estimate.
458
+ *
459
+ * Strategy:
460
+ * - Temporarily mounts index 0, measures its outer height, and updates `estimateItemHeight`.
461
+ * - If `dynamicHeights` is disabled, the probe element is removed and the model/view init flags
462
+ * are reverted for that item to avoid treating the probe as a real render.
463
+ *
464
+ * @returns {void}
324
465
  */
325
- private probeInitialHeight() {
466
+ private probeInitialHeight(): void {
326
467
  const probe = 0;
327
468
  this.mountIndexOnce(probe);
328
469
 
@@ -343,18 +484,26 @@ export class VirtualRecyclerView<
343
484
  }
344
485
  }
345
486
 
346
- /**
347
- * Checks if item is visible (not filtered/hidden).
348
- * Defaults to visible if property undefined.
487
+ /**
488
+ * Whether the item at `index` is visible (i.e., not filtered/hidden).
489
+ *
490
+ * Visibility convention:
491
+ * - If `item.visible` is undefined, the item is treated as visible.
492
+ *
493
+ * @param {number} index - 0-based item index.
494
+ * @returns {boolean} True if visible; otherwise false.
349
495
  */
350
496
  private isIndexVisible(index: number): boolean {
351
497
  const item = this.adapter?.items?.[index];
352
- return item?.visible ?? true;
498
+ return (item as any)?.visible ?? true;
353
499
  }
354
500
 
355
- /**
356
- * Finds next visible item index starting from given index.
357
- * Returns -1 if no visible items found.
501
+ /**
502
+ * Finds the next visible item index starting from `index`.
503
+ *
504
+ * @param {number} index - Start index (0-based).
505
+ * @param {number} count - Total item count.
506
+ * @returns {number} Next visible index, or -1 if none exist.
358
507
  */
359
508
  private nextVisibleFrom(index: number, count: number): number {
360
509
  for (let i = Math.max(0, index); i < count; i++) {
@@ -363,11 +512,15 @@ export class VirtualRecyclerView<
363
512
  return -1;
364
513
  }
365
514
 
366
- /**
367
- * Recalculates total measured height and count from cache.
368
- * Only counts visible items for adaptive estimation.
515
+ /**
516
+ * Recomputes running estimator stats from the current height cache.
517
+ *
518
+ * Only counts **visible** items; invisible items do not contribute to adaptive estimation.
519
+ *
520
+ * @param {number} count - Total item count.
521
+ * @returns {void}
369
522
  */
370
- private recomputeMeasuredStats(count: number) {
523
+ private recomputeMeasuredStats(count: number): void {
371
524
  this.measuredSum = 0;
372
525
  this.measuredCount = 0;
373
526
  for (let i = 0; i < count; i++) {
@@ -380,16 +533,25 @@ export class VirtualRecyclerView<
380
533
  }
381
534
  }
382
535
 
383
- /** Returns view container's top offset relative to scroll container. */
536
+ /**
537
+ * Returns the view container's top offset relative to the scroll container.
538
+ *
539
+ * This is used to convert absolute scrollTop to a scrollTop relative to the recycler's own container.
540
+ *
541
+ * @returns {number} Top offset in pixels (non-negative).
542
+ */
384
543
  private containerTopInScroll(): number {
385
544
  const a = this.viewElement!.getBoundingClientRect();
386
545
  const b = this.scrollEl.getBoundingClientRect();
387
546
  return Math.max(0, a.top - b.top + this.scrollEl.scrollTop);
388
547
  }
389
548
 
390
- /**
391
- * Returns sticky header height with 16ms cache to avoid DOM thrashing.
392
- * Used to adjust viewport calculations.
549
+ /**
550
+ * Returns sticky header height with a short cache window (~16ms) to avoid layout thrashing.
551
+ *
552
+ * Used to adjust effective viewport height (so windowing math accounts for a visible sticky header).
553
+ *
554
+ * @returns {number} Sticky header height in pixels.
393
555
  */
394
556
  private stickyTopHeight(): number {
395
557
  const now = performance.now();
@@ -401,8 +563,16 @@ export class VirtualRecyclerView<
401
563
  return this.stickyCacheVal;
402
564
  }
403
565
 
404
- /** Schedules window update on next frame if not already scheduled. */
405
- private scheduleUpdateWindow() {
566
+ /**
567
+ * Schedules a window update on the next animation frame.
568
+ *
569
+ * No-op if:
570
+ * - a frame is already scheduled, or
571
+ * - the recycler is currently suspended.
572
+ *
573
+ * @returns {void}
574
+ */
575
+ private scheduleUpdateWindow(): void {
406
576
  if (this.rafId != null || this.suspended) return;
407
577
  this.rafId = requestAnimationFrame(() => {
408
578
  this.rafId = null;
@@ -410,9 +580,11 @@ export class VirtualRecyclerView<
410
580
  });
411
581
  }
412
582
 
413
- /**
414
- * Measures element's total height including margins.
415
- * Used for accurate item height tracking.
583
+ /**
584
+ * Measures an element's "outer height" including vertical margins.
585
+ *
586
+ * @param {HTMLElement} el - Element to measure.
587
+ * @returns {number} Total outer height in pixels (minimum 1).
416
588
  */
417
589
  private measureOuterHeight(el: HTMLElement): number {
418
590
  const rect = el.getBoundingClientRect();
@@ -422,9 +594,14 @@ export class VirtualRecyclerView<
422
594
  return Math.max(1, rect.height + mt + mb);
423
595
  }
424
596
 
425
- /**
426
- * Returns height estimate for unmeasured items.
427
- * Uses adaptive average if enabled, otherwise fixed estimate.
597
+ /**
598
+ * Returns the current height estimate for unmeasured items.
599
+ *
600
+ * - When adaptive estimation is enabled and at least one item was measured,
601
+ * returns the running average.
602
+ * - Otherwise returns the configured fixed estimate.
603
+ *
604
+ * @returns {number} Estimated item height in pixels (minimum 1).
428
605
  */
429
606
  private getEstimate(): number {
430
607
  if (this.opts.adaptiveEstimate && this.measuredCount > 0) {
@@ -433,27 +610,39 @@ export class VirtualRecyclerView<
433
610
  return this.opts.estimateItemHeight;
434
611
  }
435
612
 
436
- /**
437
- * Rebuilds Fenwick tree with current heights and estimates.
438
- * Invisible items get 0 height, others use cached or estimated height.
613
+ /**
614
+ * Rebuilds Fenwick prefix sums from current cache/estimate and visibility.
615
+ *
616
+ * Encoding:
617
+ * - Invisible items contribute `0` height.
618
+ * - Visible items contribute either cached measured height, or the current estimate.
619
+ *
620
+ * @param {number} count - Total number of items.
621
+ * @returns {void}
439
622
  */
440
- private rebuildFenwick(count: number) {
623
+ private rebuildFenwick(count: number): void {
441
624
  const est = this.getEstimate();
442
- const arr = Array.from({ length: count }, (_, i) =>
625
+ const arr = Array.from({ length: count }, (_, i) =>
443
626
  this.isIndexVisible(i) ? (this.heightCache[i] ?? est) : 0
444
627
  );
445
628
  this.fenwick.buildFrom(arr);
446
629
  }
447
630
 
448
631
  /**
449
- * Updates cached height at index and applies delta to Fenwick tree.
450
- * Updates running average for adaptive estimation.
451
- *
452
- * @returns True if height changed beyond epsilon threshold
632
+ * Updates cached height at `index` and applies delta to the Fenwick tree.
633
+ *
634
+ * Behavior:
635
+ * - Ignores invisible items (no-op).
636
+ * - Applies an epsilon threshold to avoid jitter from sub-pixel / minor changes.
637
+ * - Updates adaptive estimator stats and Fenwick sums in **O(log n)**.
638
+ *
639
+ * @param {number} index - 0-based item index to update.
640
+ * @param {number} newH - Newly measured outer height (px).
641
+ * @returns {boolean} True if the height changed beyond the epsilon threshold.
453
642
  */
454
643
  private updateHeightAt(index: number, newH: number): boolean {
455
644
  if (!this.isIndexVisible(index)) return false;
456
-
645
+
457
646
  const est = this.getEstimate();
458
647
  const oldH = this.heightCache[index] ?? est;
459
648
 
@@ -473,8 +662,15 @@ export class VirtualRecyclerView<
473
662
  }
474
663
 
475
664
  /**
476
- * Finds first visible item at or after scroll offset.
477
- * Uses Fenwick binary search then adjusts for visibility.
665
+ * Finds the first visible item at or after a scroll-relative offset.
666
+ *
667
+ * Strategy:
668
+ * - Use Fenwick lower-bound to approximate a candidate index by cumulative height,
669
+ * - Then advance to the next visible item.
670
+ *
671
+ * @param {number} stRel - ScrollTop relative to the view container (px).
672
+ * @param {number} count - Total item count.
673
+ * @returns {number} A visible index (best-effort); falls back to clamped candidate when needed.
478
674
  */
479
675
  private findFirstVisibleIndex(stRel: number, count: number): number {
480
676
  const k = this.fenwick.lowerBoundPrefix(Math.max(0, stRel));
@@ -484,10 +680,18 @@ export class VirtualRecyclerView<
484
680
  }
485
681
 
486
682
  /**
487
- * Inserts element into DOM maintaining index order.
488
- * Tries adjacent siblings first, then scans for insertion point.
683
+ * Inserts an element into {@link ItemsHost} maintaining increasing index order.
684
+ *
685
+ * Heuristics:
686
+ * - Prefer inserting after the previous index element if present.
687
+ * - Else insert before the next index element if present.
688
+ * - Else scan children to find the first element with a larger `data-vindex`.
689
+ *
690
+ * @param {number} index - Item index.
691
+ * @param {HTMLElement} el - Element to insert.
692
+ * @returns {void}
489
693
  */
490
- private insertIntoHostByIndex(index: number, el: HTMLElement) {
694
+ private insertIntoHostByIndex(index: number, el: HTMLElement): void {
491
695
  el.setAttribute(VirtualRecyclerView.ATTR_INDEX, String(index));
492
696
 
493
697
  const prev = this.created.get(index - 1);
@@ -514,10 +718,15 @@ export class VirtualRecyclerView<
514
718
  }
515
719
 
516
720
  /**
517
- * Ensures element is in correct DOM position for its index.
518
- * Reinserts if siblings indicate wrong position.
721
+ * Ensures the element is in the correct DOM position for its index.
722
+ *
723
+ * Reinserts the element when adjacent siblings indicate an out-of-order position.
724
+ *
725
+ * @param {number} index - Item index.
726
+ * @param {HTMLElement} el - Element to validate/reinsert.
727
+ * @returns {void}
519
728
  */
520
- private ensureDomOrder(index: number, el: HTMLElement) {
729
+ private ensureDomOrder(index: number, el: HTMLElement): void {
521
730
  if (el.parentElement !== this.ItemsHost) {
522
731
  this.insertIntoHostByIndex(index, el);
523
732
  return;
@@ -528,7 +737,7 @@ export class VirtualRecyclerView<
528
737
  const prev = el.previousElementSibling as HTMLElement | null;
529
738
  const next = el.nextElementSibling as HTMLElement | null;
530
739
 
531
- const needsReorder =
740
+ const needsReorder =
532
741
  (prev && Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) > index) ||
533
742
  (next && Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) < index);
534
743
 
@@ -539,15 +748,23 @@ export class VirtualRecyclerView<
539
748
  }
540
749
 
541
750
  /**
542
- * Attaches ResizeObserver to measure items when they resize.
543
- * Singleton pattern - only creates once per instance.
751
+ * Attaches a {@link ResizeObserver} used for dynamic-height measurement.
752
+ *
753
+ * Singleton behavior:
754
+ * - Only creates/attaches the observer once per instance.
755
+ *
756
+ * Scheduling:
757
+ * - Observer callback schedules measurement on the next animation frame to batch DOM reads.
758
+ * - No-op when suppressed or suspended.
759
+ *
760
+ * @returns {void}
544
761
  */
545
- private attachResizeObserverOnce() {
762
+ private attachResizeObserverOnce(): void {
546
763
  if (this.resizeObs) return;
547
764
 
548
765
  this.resizeObs = new ResizeObserver(() => {
549
766
  if (this.suppressResize || this.suspended || !this.adapter || this.measureRaf != null) return;
550
-
767
+
551
768
  this.measureRaf = requestAnimationFrame(() => {
552
769
  this.measureRaf = null;
553
770
  this.measureVisibleAndUpdate();
@@ -558,10 +775,15 @@ export class VirtualRecyclerView<
558
775
  }
559
776
 
560
777
  /**
561
- * Measures all currently rendered items and updates height cache.
562
- * Triggers window update if any heights changed.
778
+ * Measures all currently rendered items and updates the height cache.
779
+ *
780
+ * If any height changed:
781
+ * - Rebuilds Fenwick sums when adaptive estimation is enabled.
782
+ * - Schedules a window recalculation.
783
+ *
784
+ * @returns {void}
563
785
  */
564
- private measureVisibleAndUpdate() {
786
+ private measureVisibleAndUpdate(): void {
565
787
  if (!this.adapter) return;
566
788
  const count = this.adapter.itemCount();
567
789
  if (count <= 0) return;
@@ -572,7 +794,7 @@ export class VirtualRecyclerView<
572
794
  if (!this.isIndexVisible(i)) continue;
573
795
 
574
796
  const item = this.adapter.items[i];
575
- const el = item?.view?.getView?.() as HTMLElement | undefined;
797
+ const el = (item as any)?.view?.getView?.() as HTMLElement | undefined;
576
798
  if (!el) continue;
577
799
 
578
800
  const newH = this.measureOuterHeight(el);
@@ -585,23 +807,33 @@ export class VirtualRecyclerView<
585
807
  }
586
808
  }
587
809
 
588
- /** Scroll event handler - schedules render update. */
589
- private onScroll() {
810
+ /**
811
+ * Scroll event handler. Schedules a window update on the next frame.
812
+ *
813
+ * @returns {void}
814
+ */
815
+ private onScroll(): void {
590
816
  this.scheduleUpdateWindow();
591
817
  }
592
818
 
593
819
  /**
594
- * Core rendering logic - calculates and updates visible window.
595
- *
596
- * 1. Calculates viewport bounds accounting for scroll and sticky headers
597
- * 2. Uses anchor item to prevent scroll jumping during height changes
598
- * 3. Determines start/end indices with overscan buffer
599
- * 4. Mounts/unmounts items as needed
600
- * 5. Measures visible items if dynamic heights enabled
601
- * 6. Updates padding elements to maintain total scroll height
602
- * 7. Adjusts scroll position to maintain anchor item position
820
+ * Core window update routine: computes the visible range and reconciles mounted DOM.
821
+ *
822
+ * High-level steps:
823
+ * 1) Compute scroll-relative viewport bounds (accounting for sticky header height).
824
+ * 2) Capture an anchor item and its visual delta relative to scrollTop.
825
+ * 3) Compute new start/end with overscan.
826
+ * 4) Mount missing items and unmount items outside the window.
827
+ * 5) Measure visible items (optional) and update pad heights.
828
+ * 6) Apply anchor correction to keep scroll position stable after height changes.
829
+ *
830
+ * Guarding:
831
+ * - Prevents re-entrancy via `updating`.
832
+ * - No-op while `suspended`.
833
+ *
834
+ * @returns {void}
603
835
  */
604
- private updateWindowInternal() {
836
+ private updateWindowInternal(): void {
605
837
  if (this.updating || this.suspended) return;
606
838
  this.updating = true;
607
839
 
@@ -611,6 +843,7 @@ export class VirtualRecyclerView<
611
843
  const count = this.adapter.itemCount();
612
844
  if (count <= 0) return;
613
845
 
846
+ // Handle item count changes (e.g., add/remove)
614
847
  if (this.lastRenderCount !== count) {
615
848
  this.lastRenderCount = count;
616
849
  this.heightCache.length = count;
@@ -665,6 +898,7 @@ export class VirtualRecyclerView<
665
898
  this.suppressResize = false;
666
899
  }
667
900
 
901
+ // Keep anchor item stable to prevent scroll jump
668
902
  const anchorTopNew = this.offsetTopOf(anchorIndex);
669
903
  const targetScroll = this.containerTopInScroll() + anchorTopNew - anchorDelta;
670
904
  const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
@@ -681,16 +915,24 @@ export class VirtualRecyclerView<
681
915
  }
682
916
  }
683
917
 
684
- /** Mounts all items in inclusive range [start..end]. */
685
- private mountRange(start: number, end: number) {
918
+ /** Mounts all items in the inclusive range `[start..end]`. */
919
+ private mountRange(start: number, end: number): void {
686
920
  for (let i = start; i <= end; i++) this.mountIndexOnce(i);
687
921
  }
688
922
 
689
923
  /**
690
- * Mounts single item, reusing existing element if available.
691
- * Creates view holder on first mount, rebinds on subsequent renders.
924
+ * Mounts/rebinds a single item at `index`.
925
+ *
926
+ * Behavior:
927
+ * - If the item is invisible, ensures it is removed/untracked (no-op otherwise).
928
+ * - Reuses an existing DOM element when present and the model already has a view.
929
+ * - Creates a new view holder on first mount (`item.isInit === false`) and binds via `adapter.onViewHolder`.
930
+ * - Ensures DOM order within {@link ItemsHost} and updates the {@link created} map.
931
+ *
932
+ * @param {number} index - Item index to mount/rebind.
933
+ * @returns {void}
692
934
  */
693
- private mountIndexOnce(index: number) {
935
+ private mountIndexOnce(index: number): void {
694
936
  if (!this.isIndexVisible(index)) {
695
937
  const existing = this.created.get(index);
696
938
  if (existing?.parentElement === this.ItemsHost) existing.remove();
@@ -703,34 +945,40 @@ export class VirtualRecyclerView<
703
945
  const existing = this.created.get(index);
704
946
 
705
947
  if (existing) {
706
- if (!item?.view) {
948
+ if (!(item as any)?.view) {
707
949
  existing.remove();
708
950
  this.created.delete(index);
709
951
  } else {
710
952
  this.ensureDomOrder(index, existing);
711
- this.adapter.onViewHolder(item, item.view, index);
953
+ this.adapter.onViewHolder(item, (item as any).view, index);
712
954
  }
713
955
  return;
714
956
  }
715
957
 
716
- if (!item.isInit) {
958
+ if (!(item as any).isInit) {
717
959
  const viewer = this.adapter!.viewHolder(this.ItemsHost, item);
718
- item.view = viewer;
960
+ (item as any).view = viewer;
719
961
  this.adapter!.onViewHolder(item, viewer, index);
720
- item.isInit = true;
721
- } else if (item.view) {
722
- this.adapter!.onViewHolder(item, item.view, index);
962
+ (item as any).isInit = true;
963
+ } else if ((item as any).view) {
964
+ this.adapter!.onViewHolder(item, (item as any).view, index);
723
965
  }
724
966
 
725
- const el = item.view?.getView?.() as HTMLElement | undefined;
967
+ const el = (item as any).view?.getView?.() as HTMLElement | undefined;
726
968
  if (el) {
727
969
  this.ensureDomOrder(index, el);
728
970
  this.created.set(index, el);
729
971
  }
730
972
  }
731
973
 
732
- /** Removes all mounted items outside [start..end] range. */
733
- private unmountOutside(start: number, end: number) {
974
+ /**
975
+ * Unmounts all mounted items outside the inclusive range `[start..end]`.
976
+ *
977
+ * @param {number} start - Window start (inclusive).
978
+ * @param {number} end - Window end (inclusive).
979
+ * @returns {void}
980
+ */
981
+ private unmountOutside(start: number, end: number): void {
734
982
  this.created.forEach((el, idx) => {
735
983
  if (idx < start || idx > end) {
736
984
  if (el.parentElement === this.ItemsHost) el.remove();
@@ -739,8 +987,12 @@ export class VirtualRecyclerView<
739
987
  });
740
988
  }
741
989
 
742
- /** Removes all items marked as invisible from DOM. */
743
- private cleanupInvisibleItems() {
990
+ /**
991
+ * Removes all currently mounted items that are now marked invisible.
992
+ *
993
+ * @returns {void}
994
+ */
995
+ private cleanupInvisibleItems(): void {
744
996
  this.created.forEach((el, idx) => {
745
997
  if (!this.isIndexVisible(idx)) {
746
998
  if (el.parentElement === this.ItemsHost) el.remove();
@@ -749,17 +1001,38 @@ export class VirtualRecyclerView<
749
1001
  });
750
1002
  }
751
1003
 
752
- /** Returns cumulative height from start to top of item at index. */
1004
+ /**
1005
+ * Returns cumulative height from the start of the list to the **top** of item at `index`.
1006
+ *
1007
+ * Indexing note:
1008
+ * - Uses Fenwick prefix sum with a 1-based contract.
1009
+ * - Passing a 0-based `index` to `sum(index)` yields the sum of heights for items `[0..index-1]`,
1010
+ * which corresponds to the CSS `offsetTop` for item `index` in a stacked list.
1011
+ *
1012
+ * @param {number} index - Item index (0-based).
1013
+ * @returns {number} Offset from the top of the list to the top of the item (px).
1014
+ */
753
1015
  private offsetTopOf(index: number): number {
754
1016
  return this.fenwick.sum(index);
755
1017
  }
756
1018
 
757
- /** Returns total height of items in range [start..end]. */
1019
+ /**
1020
+ * Returns the total height of items in the inclusive range `[start..end]`.
1021
+ *
1022
+ * @param {number} start - Start index (0-based).
1023
+ * @param {number} end - End index (0-based).
1024
+ * @returns {number} Total height in pixels.
1025
+ */
758
1026
  private windowHeight(start: number, end: number): number {
759
1027
  return this.fenwick.rangeSum(start + 1, end + 1);
760
1028
  }
761
1029
 
762
- /** Returns total scrollable height for all items. */
1030
+ /**
1031
+ * Returns total scrollable height for all items.
1032
+ *
1033
+ * @param {number} count - Total item count.
1034
+ * @returns {number} Total height in pixels.
1035
+ */
763
1036
  private totalHeight(count: number): number {
764
1037
  return this.fenwick.sum(count);
765
1038
  }