@pdanpdan/virtual-scroll 0.3.0 → 0.4.0

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.
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { default as VirtualScroll } from './components/VirtualScroll.vue';
2
2
  export * from './composables/useVirtualScroll';
3
+ export * from './types';
3
4
  export * from './utils/fenwick-tree';
4
5
  export * from './utils/scroll';
6
+ export * from './utils/virtual-scroll-logic';
package/src/types.ts ADDED
@@ -0,0 +1,535 @@
1
+ /**
2
+ * The direction of the virtual scroll.
3
+ * - 'vertical': Single-column vertical scrolling.
4
+ * - 'horizontal': Single-row horizontal scrolling.
5
+ * - 'both': Bidirectional grid-based scrolling.
6
+ */
7
+ export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
8
+
9
+ /**
10
+ * Alignment of an item within the viewport after a scroll operation.
11
+ * - 'start': Aligns item to the top or left edge.
12
+ * - 'center': Aligns item to the center of the viewport.
13
+ * - 'end': Aligns item to the bottom or right edge.
14
+ * - 'auto': Smart alignment. If visible, stays. If not, aligns to nearest edge.
15
+ */
16
+ export type ScrollAlignment = 'start' | 'center' | 'end' | 'auto';
17
+
18
+ /** Options for scroll alignment in a single axis or both axes. */
19
+ export interface ScrollAlignmentOptions {
20
+ /** Alignment on the X (horizontal) axis. */
21
+ x?: ScrollAlignment;
22
+ /** Alignment on the Y (vertical) axis. */
23
+ y?: ScrollAlignment;
24
+ }
25
+
26
+ /** Options for the `scrollToIndex` method. */
27
+ export interface ScrollToIndexOptions {
28
+ /**
29
+ * Where to align the item in the viewport.
30
+ * Can be a single value for both axes or an object for individual control.
31
+ * @default 'auto'
32
+ */
33
+ align?: ScrollAlignment | ScrollAlignmentOptions;
34
+
35
+ /**
36
+ * Scroll behavior.
37
+ * - 'auto': Instant jump.
38
+ * - 'smooth': Animated transition.
39
+ * @default 'smooth'
40
+ */
41
+ behavior?: 'auto' | 'smooth';
42
+
43
+ /**
44
+ * Internal flag for recursive correction calls.
45
+ * Users should generally not set this.
46
+ * @internal
47
+ */
48
+ isCorrection?: boolean;
49
+ }
50
+
51
+ /** Represents an item currently rendered in the virtual scroll area. */
52
+ export interface RenderedItem<T = unknown> {
53
+ /** The original data item from the provided source array. */
54
+ item: T;
55
+ /** The 0-based index of the item in the original array. */
56
+ index: number;
57
+ /** The calculated pixel offset relative to the items wrapper. */
58
+ offset: {
59
+ /** Horizontal offset (left). */
60
+ x: number;
61
+ /** Vertical offset (top). */
62
+ y: number;
63
+ };
64
+ /** The current measured or estimated size of the item. */
65
+ size: {
66
+ /** Pixel width. */
67
+ width: number;
68
+ /** Pixel height. */
69
+ height: number;
70
+ };
71
+ /** The original horizontal pixel offset before any sticky adjustments. */
72
+ originalX: number;
73
+ /** The original vertical pixel offset before any sticky adjustments. */
74
+ originalY: number;
75
+ /** Whether this item is configured to be sticky via the `stickyIndices` property. */
76
+ isSticky?: boolean;
77
+ /** Whether this item is currently in a stuck state at the viewport edge. */
78
+ isStickyActive?: boolean;
79
+ /** The relative translation applied to the item for the sticky pushing effect. */
80
+ stickyOffset: {
81
+ /** Horizontal translation. */
82
+ x: number;
83
+ /** Vertical translation. */
84
+ y: number;
85
+ };
86
+ }
87
+
88
+ /** Comprehensive state of the virtual scroll system. */
89
+ export interface ScrollDetails<T = unknown> {
90
+ /** List of items currently rendered in the DOM buffer. */
91
+ items: RenderedItem<T>[];
92
+ /** Index of the first item partially or fully visible in the viewport. */
93
+ currentIndex: number;
94
+ /** Index of the first column partially or fully visible (grid mode). */
95
+ currentColIndex: number;
96
+ /** Current relative pixel scroll position from the content start. */
97
+ scrollOffset: {
98
+ /** Horizontal position (X). */
99
+ x: number;
100
+ /** Vertical position (Y). */
101
+ y: number;
102
+ };
103
+ /** Current dimensions of the visible viewport area. */
104
+ viewportSize: {
105
+ /** Pixel width. */
106
+ width: number;
107
+ /** Pixel height. */
108
+ height: number;
109
+ };
110
+ /** Total calculated or estimated size of all items and gaps. */
111
+ totalSize: {
112
+ /** Total pixel width. */
113
+ width: number;
114
+ /** Total pixel height. */
115
+ height: number;
116
+ };
117
+ /** Whether the container is currently being scrolled by the user or an animation. */
118
+ isScrolling: boolean;
119
+ /** Whether the current scroll operation was initiated programmatically. */
120
+ isProgrammaticScroll: boolean;
121
+ /** The range of item indices currently being rendered. */
122
+ range: {
123
+ /** Inclusive start index. */
124
+ start: number;
125
+ /** Exclusive end index. */
126
+ end: number;
127
+ };
128
+ /** The range of column indices and associated paddings currently being rendered. */
129
+ columnRange: {
130
+ /** Inclusive start index. */
131
+ start: number;
132
+ /** Exclusive end index. */
133
+ end: number;
134
+ /** Pixel padding to maintain at the start of the row. */
135
+ padStart: number;
136
+ /** Pixel padding to maintain at the end of the row. */
137
+ padEnd: number;
138
+ };
139
+ }
140
+
141
+ /** Configuration properties for the `useVirtualScroll` composable. */
142
+ export interface VirtualScrollProps<T = unknown> {
143
+ /**
144
+ * Array of data items to virtualize.
145
+ */
146
+ items: T[];
147
+
148
+ /**
149
+ * Fixed size of each item (in pixels) or a function that returns the size of an item.
150
+ * Pass `0`, `null` or `undefined` for automatic dynamic size detection via `ResizeObserver`.
151
+ */
152
+ itemSize?: number | ((item: T, index: number) => number) | undefined;
153
+
154
+ /**
155
+ * Direction of the virtual scroll.
156
+ * @default 'vertical'
157
+ */
158
+ direction?: ScrollDirection | undefined;
159
+
160
+ /**
161
+ * Number of items to render before the visible viewport.
162
+ * @default 5
163
+ */
164
+ bufferBefore?: number | undefined;
165
+
166
+ /**
167
+ * Number of items to render after the visible viewport.
168
+ * @default 5
169
+ */
170
+ bufferAfter?: number | undefined;
171
+
172
+ /**
173
+ * The scrollable element or window object.
174
+ * If not provided, virtualization usually happens relative to the `hostElement`.
175
+ */
176
+ container?: HTMLElement | Window | null | undefined;
177
+
178
+ /**
179
+ * The host element that directly wraps the absolute-positioned items.
180
+ * Used for calculating relative offsets.
181
+ */
182
+ hostElement?: HTMLElement | null | undefined;
183
+
184
+ /**
185
+ * Configuration for Server-Side Rendering.
186
+ * Defines which items are rendered statically on the server.
187
+ */
188
+ ssrRange?: {
189
+ /** First row index. */
190
+ start: number;
191
+ /** Exclusive last row index. */
192
+ end: number;
193
+ /** First column index (grid mode). */
194
+ colStart?: number;
195
+ /** Exclusive last column index (grid mode). */
196
+ colEnd?: number;
197
+ } | undefined;
198
+
199
+ /**
200
+ * Number of columns for bidirectional grid scrolling.
201
+ */
202
+ columnCount?: number | undefined;
203
+
204
+ /**
205
+ * Fixed width of columns (in pixels), an array of widths, or a function returning widths.
206
+ * Pass `0`, `null` or `undefined` for dynamic column detection.
207
+ */
208
+ columnWidth?: number | number[] | ((index: number) => number) | undefined;
209
+
210
+ /**
211
+ * Pixel padding at the start of the scroll container.
212
+ */
213
+ scrollPaddingStart?: number | { x?: number; y?: number; } | undefined;
214
+
215
+ /**
216
+ * Pixel padding at the end of the scroll container.
217
+ */
218
+ scrollPaddingEnd?: number | { x?: number; y?: number; } | undefined;
219
+
220
+ /**
221
+ * Gap between items in pixels.
222
+ * Applied vertically in list/grid mode, horizontally in horizontal list mode.
223
+ */
224
+ gap?: number | undefined;
225
+
226
+ /**
227
+ * Gap between columns in pixels.
228
+ * Applied in horizontal and bidirectional grid modes.
229
+ */
230
+ columnGap?: number | undefined;
231
+
232
+ /**
233
+ * List of indices that should stick to the viewport edge.
234
+ */
235
+ stickyIndices?: number[] | undefined;
236
+
237
+ /**
238
+ * Threshold distance from the end (in pixels) to emit the 'load' event.
239
+ * @default 200
240
+ */
241
+ loadDistance?: number | undefined;
242
+
243
+ /**
244
+ * Whether data is currently loading.
245
+ */
246
+ loading?: boolean | undefined;
247
+
248
+ /**
249
+ * Whether to automatically restore and lock scroll position when items are prepended to the array.
250
+ */
251
+ restoreScrollOnPrepend?: boolean | undefined;
252
+
253
+ /**
254
+ * Initial row index to jump to on mount.
255
+ */
256
+ initialScrollIndex?: number | undefined;
257
+
258
+ /**
259
+ * Initial scroll alignment logic.
260
+ * @default 'start'
261
+ */
262
+ initialScrollAlign?: ScrollAlignment | ScrollAlignmentOptions | undefined;
263
+
264
+ /**
265
+ * Default fallback size for items before they are measured.
266
+ */
267
+ defaultItemSize?: number | undefined;
268
+
269
+ /**
270
+ * Default fallback width for columns before they are measured.
271
+ */
272
+ defaultColumnWidth?: number | undefined;
273
+
274
+ /**
275
+ * Enable debug visualization of buffers and indices.
276
+ */
277
+ debug?: boolean | undefined;
278
+ }
279
+
280
+ /** Properties passed to the 'item' scoped slot. */
281
+ export interface ItemSlotProps<T = unknown> {
282
+ /** The original data item being rendered. */
283
+ item: T;
284
+ /** The 0-based index of the item. */
285
+ index: number;
286
+ /** Information about the currently visible range of columns. */
287
+ columnRange: {
288
+ /** First rendered column. */
289
+ start: number;
290
+ /** Last rendered column (exclusive). */
291
+ end: number;
292
+ /** Pixel space before first column. */
293
+ padStart: number;
294
+ /** Pixel space after last column. */
295
+ padEnd: number;
296
+ };
297
+ /** Helper to get the current calculated width of any column index. */
298
+ getColumnWidth: (index: number) => number;
299
+ /** Whether this item index is configured as sticky. */
300
+ isSticky?: boolean | undefined;
301
+ /** Whether this item is currently in a sticky state at the edge. */
302
+ isStickyActive?: boolean | undefined;
303
+ }
304
+
305
+ /** Parameters for calculating the scroll target position. */
306
+ export interface ScrollTargetParams {
307
+ /** Row index to target. */
308
+ rowIndex: number | null | undefined;
309
+ /** Column index to target. */
310
+ colIndex: number | null | undefined;
311
+ /** Scroll options. */
312
+ options?: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions | undefined;
313
+ /** Total items count. */
314
+ itemsLength: number;
315
+ /** Total columns count. */
316
+ columnCount: number;
317
+ /** Current scroll direction. */
318
+ direction: ScrollDirection;
319
+ /** Usable viewport width (excluding padding). */
320
+ usableWidth: number;
321
+ /** Usable viewport height (excluding padding). */
322
+ usableHeight: number;
323
+ /** Current total estimated width. */
324
+ totalWidth: number;
325
+ /** Current total estimated height. */
326
+ totalHeight: number;
327
+ /** Item gap. */
328
+ gap: number;
329
+ /** Column gap. */
330
+ columnGap: number;
331
+ /** Fixed item size. */
332
+ fixedSize: number | null;
333
+ /** Fixed column width. */
334
+ fixedWidth: number | null;
335
+ /** Current relative X scroll. */
336
+ relativeScrollX: number;
337
+ /** Current relative Y scroll. */
338
+ relativeScrollY: number;
339
+ /** Resolver for item height. */
340
+ getItemSizeY: (index: number) => number;
341
+ /** Resolver for item width. */
342
+ getItemSizeX: (index: number) => number;
343
+ /** Prefix sum resolver for item height. */
344
+ getItemQueryY: (index: number) => number;
345
+ /** Prefix sum resolver for item width. */
346
+ getItemQueryX: (index: number) => number;
347
+ /** Resolver for column size. */
348
+ getColumnSize: (index: number) => number;
349
+ /** Prefix sum resolver for column width. */
350
+ getColumnQuery: (index: number) => number;
351
+ /** List of sticky indices. */
352
+ stickyIndices?: number[] | undefined;
353
+ }
354
+
355
+ /** Calculated scroll target result. */
356
+ export interface ScrollTargetResult {
357
+ /** Target relative horizontal position. */
358
+ targetX: number;
359
+ /** Target relative vertical position. */
360
+ targetY: number;
361
+ /** Resolved width of the target item. */
362
+ itemWidth: number;
363
+ /** Resolved height of the target item. */
364
+ itemHeight: number;
365
+ /** Effective alignment used for X axis. */
366
+ effectiveAlignX: ScrollAlignment;
367
+ /** Effective alignment used for Y axis. */
368
+ effectiveAlignY: ScrollAlignment;
369
+ }
370
+
371
+ /** Parameters for calculating the visible range of items. */
372
+ export interface RangeParams {
373
+ /** Scroll direction. */
374
+ direction: ScrollDirection;
375
+ /** Relative horizontal scroll position. */
376
+ relativeScrollX: number;
377
+ /** Relative vertical scroll position. */
378
+ relativeScrollY: number;
379
+ /** Usable viewport width. */
380
+ usableWidth: number;
381
+ /** Usable viewport height. */
382
+ usableHeight: number;
383
+ /** Total item count. */
384
+ itemsLength: number;
385
+ /** Buffer items before. */
386
+ bufferBefore: number;
387
+ /** Buffer items after. */
388
+ bufferAfter: number;
389
+ /** Item gap. */
390
+ gap: number;
391
+ /** Column gap. */
392
+ columnGap: number;
393
+ /** Fixed item size. */
394
+ fixedSize: number | null;
395
+ /** Binary search for row index. */
396
+ findLowerBoundY: (offset: number) => number;
397
+ /** Binary search for row index (horizontal). */
398
+ findLowerBoundX: (offset: number) => number;
399
+ /** Prefix sum for row height. */
400
+ queryY: (index: number) => number;
401
+ /** Prefix sum for row width. */
402
+ queryX: (index: number) => number;
403
+ }
404
+
405
+ /** Parameters for calculating the visible range of columns in grid mode. */
406
+ export interface ColumnRangeParams {
407
+ /** Column count. */
408
+ columnCount: number;
409
+ /** Relative horizontal scroll position. */
410
+ relativeScrollX: number;
411
+ /** Usable viewport width. */
412
+ usableWidth: number;
413
+ /** Column buffer count. */
414
+ colBuffer: number;
415
+ /** Fixed column width. */
416
+ fixedWidth: number | null;
417
+ /** Column gap. */
418
+ columnGap: number;
419
+ /** Binary search for column index. */
420
+ findLowerBound: (offset: number) => number;
421
+ /** Prefix sum for column width. */
422
+ query: (index: number) => number;
423
+ /** Resolver for total column width. */
424
+ totalColsQuery: () => number;
425
+ }
426
+
427
+ /** Parameters for calculating sticky item offsets. */
428
+ export interface StickyParams {
429
+ /** Item index. */
430
+ index: number;
431
+ /** Is sticky configured. */
432
+ isSticky: boolean;
433
+ /** Scroll direction. */
434
+ direction: ScrollDirection;
435
+ /** Relative horizontal scroll. */
436
+ relativeScrollX: number;
437
+ /** Relative vertical scroll. */
438
+ relativeScrollY: number;
439
+ /** Original X offset. */
440
+ originalX: number;
441
+ /** Original Y offset. */
442
+ originalY: number;
443
+ /** Current width. */
444
+ width: number;
445
+ /** Current height. */
446
+ height: number;
447
+ /** All sticky indices. */
448
+ stickyIndices: number[];
449
+ /** Fixed item size. */
450
+ fixedSize: number | null;
451
+ /** Fixed column width. */
452
+ fixedWidth: number | null;
453
+ /** Item gap. */
454
+ gap: number;
455
+ /** Column gap. */
456
+ columnGap: number;
457
+ /** Prefix sum resolver for rows. */
458
+ getItemQueryY: (index: number) => number;
459
+ /** Prefix sum resolver for rows (horizontal). */
460
+ getItemQueryX: (index: number) => number;
461
+ }
462
+
463
+ /** Parameters for calculating an item's position and size. */
464
+ export interface ItemPositionParams {
465
+ /** Item index. */
466
+ index: number;
467
+ /** Scroll direction. */
468
+ direction: ScrollDirection;
469
+ /** Fixed item size. */
470
+ fixedSize: number | null;
471
+ /** Item gap. */
472
+ gap: number;
473
+ /** Column gap. */
474
+ columnGap: number;
475
+ /** Usable viewport width. */
476
+ usableWidth: number;
477
+ /** Usable viewport height. */
478
+ usableHeight: number;
479
+ /** Total estimated width. */
480
+ totalWidth: number;
481
+ /** Prefix sum for row height. */
482
+ queryY: (idx: number) => number;
483
+ /** Prefix sum for row width. */
484
+ queryX: (idx: number) => number;
485
+ /** Height resolver. */
486
+ getSizeY: (idx: number) => number;
487
+ /** Width resolver. */
488
+ getSizeX: (idx: number) => number;
489
+ }
490
+
491
+ /** Parameters for calculating an item's style object. */
492
+ export interface ItemStyleParams<T = unknown> {
493
+ /** The rendered item state. */
494
+ item: RenderedItem<T>;
495
+ /** Scroll direction. */
496
+ direction: ScrollDirection;
497
+ /** Configured item size logic. */
498
+ itemSize: number | ((item: T, index: number) => number) | null | undefined;
499
+ /** Parent container tag. */
500
+ containerTag: string;
501
+ /** Padding start on X axis. */
502
+ paddingStartX: number;
503
+ /** Padding start on Y axis. */
504
+ paddingStartY: number;
505
+ /** Hydration state. */
506
+ isHydrated: boolean;
507
+ }
508
+
509
+ /** Parameters for calculating the total size of the scrollable area. */
510
+ export interface TotalSizeParams {
511
+ /** The scroll direction. */
512
+ direction: ScrollDirection;
513
+ /** The number of items in the list. */
514
+ itemsLength: number;
515
+ /** The number of columns (for grid mode). */
516
+ columnCount: number;
517
+ /** The fixed size of items, if applicable. */
518
+ fixedSize: number | null;
519
+ /** The fixed width of columns, if applicable. */
520
+ fixedWidth: number | null;
521
+ /** The gap between items. */
522
+ gap: number;
523
+ /** The gap between columns. */
524
+ columnGap: number;
525
+ /** Usable viewport width. */
526
+ usableWidth: number;
527
+ /** Usable viewport height. */
528
+ usableHeight: number;
529
+ /** Function to query the prefix sum of item heights. */
530
+ queryY: (index: number) => number;
531
+ /** Function to query the prefix sum of item widths. */
532
+ queryX: (index: number) => number;
533
+ /** Function to query the prefix sum of column widths. */
534
+ queryColumn: (index: number) => number;
535
+ }
@@ -1,20 +1,28 @@
1
1
  /**
2
2
  * Fenwick Tree (Binary Indexed Tree) implementation for efficient
3
3
  * prefix sum calculations and updates.
4
+ *
5
+ * Provides O(log n) time complexity for both point updates and prefix sum queries.
4
6
  */
5
7
  export class FenwickTree {
6
8
  private tree: Float64Array;
7
9
  private values: Float64Array;
8
10
 
11
+ /**
12
+ * Creates a new Fenwick Tree with the specified size.
13
+ *
14
+ * @param size - The number of elements in the tree.
15
+ */
9
16
  constructor(size: number) {
10
17
  this.tree = new Float64Array(size + 1);
11
18
  this.values = new Float64Array(size);
12
19
  }
13
20
 
14
21
  /**
15
- * Update the value at a specific index and propagate changes.
16
- * @param index 0-based index
17
- * @param delta The change in value (new value - old value)
22
+ * Update the value at a specific index and propagate changes throughout the tree.
23
+ *
24
+ * @param index - The 0-based index to update.
25
+ * @param delta - The change in value (new value - old value).
18
26
  */
19
27
  update(index: number, delta: number): void {
20
28
  if (index < 0 || index >= this.values.length) {
@@ -31,8 +39,9 @@ export class FenwickTree {
31
39
 
32
40
  /**
33
41
  * Get the prefix sum up to a specific index (exclusive).
34
- * @param index 0-based index. query(n) returns sum of values from 0 to n-1.
35
- * @returns Sum of values in range [0, index)
42
+ *
43
+ * @param index - 0-based index. `query(n)` returns sum of values from index 0 to n-1.
44
+ * @returns Sum of values in range [0, index).
36
45
  */
37
46
  query(index: number): number {
38
47
  let sum = 0;
@@ -44,8 +53,11 @@ export class FenwickTree {
44
53
  }
45
54
 
46
55
  /**
47
- * Set the individual value at an index without updating the tree.
48
- * Call rebuild() after multiple sets to update the tree efficiently.
56
+ * Set the individual value at an index without updating the prefix sum tree.
57
+ * Call `rebuild()` after multiple sets to update the tree efficiently in O(n).
58
+ *
59
+ * @param index - The 0-based index.
60
+ * @param value - The new value.
49
61
  */
50
62
  set(index: number, value: number): void {
51
63
  if (index < 0 || index >= this.values.length) {
@@ -62,14 +74,19 @@ export class FenwickTree {
62
74
  }
63
75
 
64
76
  /**
65
- * Get the individual value at an index.
77
+ * Get the individual value at a specific index.
78
+ *
79
+ * @param index - The 0-based index.
80
+ * @returns The value at the specified index.
66
81
  */
67
82
  get(index: number): number {
68
83
  return this.values[ index ] || 0;
69
84
  }
70
85
 
71
86
  /**
72
- * Get the underlying values array.
87
+ * Get the underlying values array as a read-only Float64Array.
88
+ *
89
+ * @returns The read-only values array.
73
90
  */
74
91
  getValues(): Readonly<Float64Array> {
75
92
  return this.values;
@@ -77,9 +94,10 @@ export class FenwickTree {
77
94
 
78
95
  /**
79
96
  * Find the largest index such that the prefix sum is less than or equal to the given value.
80
- * Useful for finding which item is at a specific scroll offset.
81
- * @param value The prefix sum value to search for
82
- * @returns The 0-based index
97
+ * Highly efficient search used to find which item is at a specific scroll offset.
98
+ *
99
+ * @param value - The prefix sum value to search for.
100
+ * @returns The 0-based index.
83
101
  */
84
102
  findLowerBound(value: number): number {
85
103
  let index = 0;
@@ -101,8 +119,8 @@ export class FenwickTree {
101
119
  }
102
120
 
103
121
  /**
104
- * Rebuild the entire tree from the current values array in O(N).
105
- * Useful after bulk updates to the values array.
122
+ * Rebuild the entire prefix sum tree from the current values array.
123
+ * Time complexity: O(n).
106
124
  */
107
125
  rebuild(): void {
108
126
  this.tree.fill(0);
@@ -118,8 +136,9 @@ export class FenwickTree {
118
136
  }
119
137
 
120
138
  /**
121
- * Resize the tree while preserving existing values.
122
- * @param size New size of the tree
139
+ * Resize the tree while preserving existing values and rebuilding the prefix sums.
140
+ *
141
+ * @param size - The new size of the tree.
123
142
  */
124
143
  resize(size: number): void {
125
144
  if (size === this.values.length) {
@@ -135,8 +154,9 @@ export class FenwickTree {
135
154
 
136
155
  /**
137
156
  * Shift values by a given offset and rebuild the tree.
138
- * Useful when items are prepended to the list.
139
- * @param offset Number of positions to shift (positive for prepending)
157
+ * Useful when items are prepended to the list to maintain existing measurements.
158
+ *
159
+ * @param offset - Number of positions to shift. Positive for prepending (shifts right).
140
160
  */
141
161
  shift(offset: number): void {
142
162
  if (offset === 0) {