mtrl-addons 0.2.2 → 0.2.4

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 (129) hide show
  1. package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
  2. package/dist/components/vlist/config.d.ts +86 -0
  3. package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
  4. package/dist/components/vlist/features/api.d.ts +7 -0
  5. package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
  6. package/dist/components/vlist/features/selection.d.ts +6 -0
  7. package/dist/components/vlist/features/viewport.d.ts +9 -0
  8. package/dist/components/vlist/features.d.ts +31 -0
  9. package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -10
  10. package/dist/components/vlist/types.d.ts +596 -0
  11. package/dist/components/vlist/vlist.d.ts +29 -0
  12. package/dist/core/compose/features/gestures/index.d.ts +86 -0
  13. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  14. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  15. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  16. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  17. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  18. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  19. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
  20. package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
  21. package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
  22. package/dist/core/gestures/longpress.d.ts +23 -0
  23. package/dist/core/gestures/manager.d.ts +14 -0
  24. package/dist/core/gestures/pan.d.ts +12 -0
  25. package/dist/core/gestures/pinch.d.ts +14 -0
  26. package/dist/core/gestures/rotate.d.ts +14 -0
  27. package/dist/core/gestures/swipe.d.ts +20 -0
  28. package/dist/core/gestures/tap.d.ts +12 -0
  29. package/dist/core/gestures/types.d.ts +320 -0
  30. package/dist/core/gestures/utils.d.ts +57 -0
  31. package/dist/core/index.d.ts +13 -0
  32. package/dist/core/layout/config.d.ts +33 -0
  33. package/dist/core/layout/index.d.ts +51 -0
  34. package/dist/core/layout/jsx.d.ts +65 -0
  35. package/dist/core/layout/schema.d.ts +112 -0
  36. package/dist/core/layout/types.d.ts +69 -0
  37. package/dist/core/viewport/constants.d.ts +105 -0
  38. package/dist/core/viewport/features/base.d.ts +14 -0
  39. package/dist/core/viewport/features/collection.d.ts +41 -0
  40. package/dist/core/viewport/features/events.d.ts +13 -0
  41. package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
  42. package/dist/core/viewport/features/item-size.d.ts +30 -0
  43. package/dist/core/viewport/features/loading.d.ts +34 -0
  44. package/dist/core/viewport/features/momentum.d.ts +17 -0
  45. package/dist/core/viewport/features/performance.d.ts +53 -0
  46. package/dist/core/viewport/features/placeholders.d.ts +38 -0
  47. package/dist/core/viewport/features/rendering.d.ts +16 -0
  48. package/dist/core/viewport/features/scrollbar.d.ts +26 -0
  49. package/dist/core/viewport/features/scrolling.d.ts +16 -0
  50. package/dist/core/viewport/features/utils.d.ts +43 -0
  51. package/dist/core/viewport/features/virtual.d.ts +18 -0
  52. package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
  53. package/dist/core/viewport/types.d.ts +96 -0
  54. package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
  55. package/dist/core/viewport/viewport.d.ts +11 -0
  56. package/{src/index.ts → dist/index.d.ts} +0 -4
  57. package/dist/index.js +5143 -0
  58. package/dist/index.mjs +5111 -0
  59. package/dist/styles.css +254 -0
  60. package/dist/styles.css.map +1 -0
  61. package/package.json +16 -2
  62. package/.cursorrules +0 -117
  63. package/AI.md +0 -39
  64. package/CLAUDE.md +0 -882
  65. package/build.js +0 -377
  66. package/index.ts +0 -7
  67. package/scripts/analyze-orphaned-functions.ts +0 -387
  68. package/scripts/debug/vlist-selection.ts +0 -121
  69. package/src/components/vlist/config.ts +0 -323
  70. package/src/components/vlist/features/api.ts +0 -626
  71. package/src/components/vlist/features/selection.ts +0 -436
  72. package/src/components/vlist/features/viewport.ts +0 -59
  73. package/src/components/vlist/features.ts +0 -112
  74. package/src/components/vlist/types.ts +0 -723
  75. package/src/components/vlist/vlist.ts +0 -92
  76. package/src/core/compose/features/gestures/index.ts +0 -227
  77. package/src/core/compose/features/gestures/longpress.ts +0 -383
  78. package/src/core/compose/features/gestures/pan.ts +0 -424
  79. package/src/core/compose/features/gestures/pinch.ts +0 -475
  80. package/src/core/compose/features/gestures/rotate.ts +0 -485
  81. package/src/core/compose/features/gestures/swipe.ts +0 -492
  82. package/src/core/compose/features/gestures/tap.ts +0 -334
  83. package/src/core/gestures/longpress.ts +0 -68
  84. package/src/core/gestures/manager.ts +0 -418
  85. package/src/core/gestures/pan.ts +0 -48
  86. package/src/core/gestures/pinch.ts +0 -58
  87. package/src/core/gestures/rotate.ts +0 -58
  88. package/src/core/gestures/swipe.ts +0 -66
  89. package/src/core/gestures/tap.ts +0 -45
  90. package/src/core/gestures/types.ts +0 -387
  91. package/src/core/gestures/utils.ts +0 -128
  92. package/src/core/index.ts +0 -43
  93. package/src/core/layout/config.ts +0 -102
  94. package/src/core/layout/index.ts +0 -168
  95. package/src/core/layout/jsx.ts +0 -174
  96. package/src/core/layout/schema.ts +0 -1044
  97. package/src/core/layout/types.ts +0 -95
  98. package/src/core/viewport/constants.ts +0 -145
  99. package/src/core/viewport/features/base.ts +0 -73
  100. package/src/core/viewport/features/collection.ts +0 -1182
  101. package/src/core/viewport/features/events.ts +0 -130
  102. package/src/core/viewport/features/item-size.ts +0 -271
  103. package/src/core/viewport/features/loading.ts +0 -263
  104. package/src/core/viewport/features/momentum.ts +0 -269
  105. package/src/core/viewport/features/performance.ts +0 -161
  106. package/src/core/viewport/features/placeholders.ts +0 -335
  107. package/src/core/viewport/features/rendering.ts +0 -962
  108. package/src/core/viewport/features/scrollbar.ts +0 -434
  109. package/src/core/viewport/features/scrolling.ts +0 -634
  110. package/src/core/viewport/features/utils.ts +0 -94
  111. package/src/core/viewport/features/virtual.ts +0 -525
  112. package/src/core/viewport/types.ts +0 -133
  113. package/src/core/viewport/utils/speed-tracker.ts +0 -79
  114. package/src/core/viewport/viewport.ts +0 -265
  115. package/test/benchmarks/layout/advanced.test.ts +0 -656
  116. package/test/benchmarks/layout/comparison.test.ts +0 -519
  117. package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
  118. package/test/benchmarks/layout/real-components.test.ts +0 -733
  119. package/test/benchmarks/layout/simple.test.ts +0 -321
  120. package/test/benchmarks/layout/stress.test.ts +0 -990
  121. package/test/collection/basic.test.ts +0 -304
  122. package/test/components/vlist-selection.test.ts +0 -240
  123. package/test/components/vlist.test.ts +0 -63
  124. package/test/core/collection/adapter.test.ts +0 -161
  125. package/test/core/collection/collection.test.ts +0 -394
  126. package/test/core/layout/layout.test.ts +0 -201
  127. package/test/utils/dom-helpers.ts +0 -275
  128. package/test/utils/performance-helpers.ts +0 -392
  129. package/tsconfig.json +0 -20
@@ -1,634 +0,0 @@
1
- /**
2
- * Scrolling Feature - Virtual scrolling with integrated velocity tracking
3
- * Handles wheel events, touch/mouse events, scroll position management, velocity measurement, and momentum scrolling
4
- */
5
-
6
- import type { ViewportContext, ViewportComponent } from "../types";
7
- import { VIEWPORT_CONSTANTS } from "../constants";
8
- import { wrapInitialize, getViewportState, clamp } from "./utils";
9
-
10
- export interface ScrollingConfig {
11
- orientation?: "vertical" | "horizontal";
12
- sensitivity?: number;
13
- smoothing?: boolean;
14
- idleTimeout?: number;
15
- }
16
-
17
- // Speed tracker interface
18
- interface SpeedTracker {
19
- velocity: number;
20
- lastPosition: number;
21
- lastTime: number;
22
- direction: "forward" | "backward";
23
- samples: Array<{ position: number; time: number }>;
24
- }
25
-
26
- /**
27
- * Creates a new speed tracker
28
- */
29
- const createSpeedTracker = (): SpeedTracker => ({
30
- velocity: 0,
31
- lastPosition: 0,
32
- lastTime: Date.now(),
33
- direction: "forward",
34
- samples: [],
35
- });
36
-
37
- /**
38
- * Updates speed tracker with new position
39
- */
40
- const updateSpeedTracker = (
41
- tracker: SpeedTracker,
42
- newPosition: number,
43
- previousPosition: number,
44
- ): SpeedTracker => {
45
- const now = Date.now();
46
- const timeDelta = now - tracker.lastTime;
47
-
48
- if (timeDelta === 0) return tracker;
49
-
50
- const positionDelta = newPosition - previousPosition;
51
- const instantVelocity = Math.abs(positionDelta) / timeDelta;
52
-
53
- // Add new sample
54
- const samples = [...tracker.samples, { position: newPosition, time: now }];
55
-
56
- // Keep only recent samples (last 100ms)
57
- const recentSamples = samples.filter((s) => now - s.time < 100);
58
-
59
- // Calculate average velocity from recent samples
60
- let avgVelocity = instantVelocity;
61
- if (recentSamples.length > 1) {
62
- const oldestSample = recentSamples[0];
63
- const totalDistance = Math.abs(newPosition - oldestSample.position);
64
- const totalTime = now - oldestSample.time;
65
- avgVelocity = totalTime > 0 ? totalDistance / totalTime : instantVelocity;
66
- }
67
-
68
- return {
69
- velocity: avgVelocity,
70
- lastPosition: newPosition,
71
- lastTime: now,
72
- direction: positionDelta >= 0 ? "forward" : "backward",
73
- samples: recentSamples,
74
- };
75
- };
76
-
77
- /**
78
- * Scrolling feature for viewport
79
- * Handles wheel events, velocity tracking, and idle detection
80
- */
81
- export const withScrolling = (config: ScrollingConfig = {}) => {
82
- return <T extends ViewportContext & ViewportComponent>(component: T): T => {
83
- const {
84
- orientation = "vertical",
85
- sensitivity = VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.SCROLL_SENSITIVITY,
86
- smoothing = false,
87
- idleTimeout = 100, // Default idle timeout in ms
88
- } = config;
89
-
90
- // State
91
- let scrollPosition = 0;
92
- let totalVirtualSize = 0;
93
- let containerSize = 0;
94
- let isScrolling = false;
95
- let lastScrollTime = 0;
96
- let speedTracker = createSpeedTracker();
97
- let idleTimeoutId: number | null = null;
98
- let idleCheckFrame: number | null = null;
99
- let lastIdleCheckPosition = 0;
100
- let hasEmittedIdle = false; // Track if we've already emitted idle
101
-
102
- // console.log(`[Scrolling] Initial state - position: ${scrollPosition}`);
103
-
104
- // Get viewport state
105
- let viewportState: any;
106
-
107
- // Use shared initialization wrapper
108
- wrapInitialize(component, () => {
109
- viewportState = getViewportState(component);
110
-
111
- // Initialize state values
112
- if (viewportState) {
113
- totalVirtualSize = viewportState.virtualTotalSize || 0;
114
- containerSize = viewportState.containerSize || 0;
115
- // Sync scrollPosition from viewportState (important for initialScrollIndex)
116
- if (viewportState.scrollPosition > 0) {
117
- scrollPosition = viewportState.scrollPosition;
118
- }
119
- }
120
-
121
- // Listen for virtual size changes
122
- component.on?.("viewport:virtual-size-changed", (data: any) => {
123
- // console.log("[Scrolling] Virtual size changed:", data);
124
- updateScrollBounds(data.totalVirtualSize, containerSize);
125
- });
126
-
127
- // Listen for scroll position changes from other features (e.g., virtual.ts after item size detection)
128
- component.on?.("viewport:scroll-position-sync", (data: any) => {
129
- if (data.position !== undefined && data.position !== scrollPosition) {
130
- scrollPosition = data.position;
131
- // Also update local tracking vars
132
- totalVirtualSize =
133
- viewportState?.virtualTotalSize || totalVirtualSize;
134
- containerSize = viewportState?.containerSize || containerSize;
135
- }
136
- });
137
-
138
- // Listen for container size changes
139
- component.on?.("viewport:container-size-changed", (data: any) => {
140
- if (data.containerSize) {
141
- containerSize = data.containerSize;
142
- updateScrollBounds(totalVirtualSize, containerSize);
143
- }
144
- });
145
-
146
- // Attach wheel event listener to viewport element
147
- const viewportElement =
148
- viewportState?.viewportElement || (component as any).viewportElement;
149
- if (viewportElement) {
150
- // console.log(`[Scrolling] Attaching wheel event to viewport element`);
151
- viewportElement.addEventListener("wheel", handleWheel, {
152
- passive: false,
153
- });
154
-
155
- // Store reference for cleanup
156
- (component as any)._scrollingViewportElement = viewportElement;
157
- } else {
158
- console.warn(`[Scrolling] No viewport element found for wheel events`);
159
- }
160
- });
161
-
162
- // Use clamp from utils
163
-
164
- // Start idle detection
165
- const startIdleDetection = () => {
166
- // console.log("[Scrolling] Starting idle detection");
167
-
168
- // Stop any existing idle detection first
169
- if (idleCheckFrame !== null) {
170
- cancelAnimationFrame(idleCheckFrame);
171
- idleCheckFrame = null;
172
- }
173
-
174
- hasEmittedIdle = false; // Reset idle emission flag
175
- const checkIdle = () => {
176
- if (scrollPosition === lastIdleCheckPosition) {
177
- // Position hasn't changed - we're idle
178
- if (!hasEmittedIdle && (speedTracker.velocity > 0 || isScrolling)) {
179
- // console.log(
180
- // "[Scrolling] Idle detected - position stable, setting velocity to zero"
181
- // );
182
- hasEmittedIdle = true; // Mark that we've emitted idle
183
- setVelocityToZero();
184
- }
185
- } else {
186
- // Position changed, reset the flag
187
- hasEmittedIdle = false;
188
- }
189
- lastIdleCheckPosition = scrollPosition;
190
- idleCheckFrame = requestAnimationFrame(checkIdle);
191
- };
192
- idleCheckFrame = requestAnimationFrame(checkIdle);
193
- };
194
-
195
- // Stop idle detection
196
- const stopIdleDetection = () => {
197
- if (idleCheckFrame) {
198
- // console.log("[Scrolling] Stopping idle detection");
199
- cancelAnimationFrame(idleCheckFrame);
200
- idleCheckFrame = null;
201
- }
202
- };
203
-
204
- // Set velocity to zero and emit idle event
205
- const setVelocityToZero = () => {
206
- // console.log("[Scrolling] Setting velocity to zero and emitting idle");
207
-
208
- // Stop idle detection since we're now idle
209
- stopIdleDetection();
210
-
211
- speedTracker = createSpeedTracker();
212
- isScrolling = false; // Reset scrolling state
213
-
214
- // Emit velocity change
215
- component.emit?.("viewport:velocity-changed", {
216
- velocity: 0,
217
- direction: speedTracker.direction,
218
- });
219
-
220
- // Emit idle state
221
- component.emit?.("viewport:idle", {
222
- position: scrollPosition,
223
- lastScrollTime,
224
- });
225
- };
226
- // Update container position
227
- const updateContainerPosition = () => {
228
- if (!viewportState || !viewportState.itemsContainer) return;
229
-
230
- // Items container doesn't move - items inside it are positioned
231
- // This is handled by the rendering feature
232
- };
233
-
234
- // Handle wheel event
235
- const handleWheel = (event: WheelEvent) => {
236
- event.preventDefault();
237
-
238
- const delta = orientation === "vertical" ? event.deltaY : event.deltaX;
239
- const scrollDelta = delta * sensitivity;
240
-
241
- const previousPosition = scrollPosition;
242
- const maxScroll = Math.max(0, totalVirtualSize - containerSize);
243
-
244
- let newPosition = scrollPosition + scrollDelta;
245
-
246
- // Apply smoothing if enabled
247
- if (smoothing) {
248
- const smoothingFactor = 0.3;
249
- newPosition = scrollPosition + scrollDelta * smoothingFactor;
250
- }
251
-
252
- newPosition = clamp(newPosition, 0, maxScroll);
253
-
254
- if (newPosition !== scrollPosition) {
255
- scrollPosition = newPosition;
256
- const now = Date.now();
257
-
258
- // Update scroll state
259
- if (!isScrolling) {
260
- isScrolling = true;
261
- // Stop any existing idle detection before starting new one
262
- stopIdleDetection();
263
- startIdleDetection();
264
- }
265
- lastScrollTime = now;
266
-
267
- // Update speed tracker
268
- speedTracker = updateSpeedTracker(
269
- speedTracker,
270
- scrollPosition,
271
- previousPosition,
272
- );
273
-
274
- // Update viewport state
275
- if (viewportState) {
276
- viewportState.scrollPosition = scrollPosition;
277
- viewportState.velocity = speedTracker.velocity;
278
- viewportState.scrollDirection = speedTracker.direction;
279
- }
280
-
281
- // Emit events
282
- component.emit?.("viewport:scroll", {
283
- position: scrollPosition,
284
- direction: speedTracker.direction,
285
- previousPosition,
286
- });
287
-
288
- component.emit?.("viewport:velocity-changed", {
289
- velocity: speedTracker.velocity,
290
- direction: speedTracker.direction,
291
- });
292
-
293
- // Trigger render
294
- component.viewport.renderItems();
295
- }
296
- };
297
-
298
- // Scroll to position
299
- const scrollToPosition = (position: number, source?: string) => {
300
- const maxScroll = Math.max(0, totalVirtualSize - containerSize);
301
- const clampedPosition = clamp(position, 0, maxScroll);
302
-
303
- // console.log(
304
- // `[Scrolling] scrollToPosition: pos=${position} -> ${clampedPosition}, source=${source}, currentPos=${scrollPosition}, velocity=${speedTracker.velocity.toFixed(
305
- // 3
306
- // )}`
307
- // );
308
-
309
- if (clampedPosition !== scrollPosition) {
310
- const previousPosition = scrollPosition;
311
- scrollPosition = clampedPosition;
312
-
313
- // Update speed tracker to calculate velocity
314
- speedTracker = updateSpeedTracker(
315
- speedTracker,
316
- scrollPosition,
317
- previousPosition,
318
- );
319
-
320
- // Update viewport state
321
- if (viewportState) {
322
- viewportState.scrollPosition = scrollPosition;
323
- viewportState.velocity = speedTracker.velocity;
324
- viewportState.scrollDirection = speedTracker.direction;
325
- }
326
-
327
- const direction =
328
- clampedPosition > previousPosition ? "forward" : "backward";
329
-
330
- component.emit?.("viewport:scroll", {
331
- position: scrollPosition,
332
- direction,
333
- previousPosition,
334
- source,
335
- });
336
-
337
- // Emit velocity change event so collection can track it
338
- component.emit?.("viewport:velocity-changed", {
339
- velocity: speedTracker.velocity,
340
- direction: speedTracker.direction,
341
- });
342
-
343
- // Update scroll state and idle detection
344
- if (!isScrolling) {
345
- isScrolling = true;
346
- startIdleDetection();
347
- }
348
- lastScrollTime = Date.now();
349
-
350
- // Trigger render
351
- component.viewport.renderItems();
352
- } else {
353
- // console.log(`[Scrolling] Position unchanged: ${scrollPosition}`);
354
- // console.log(
355
- // `[Scrolling] Position unchanged: ${scrollPosition}, not resetting idle timeout`
356
- // );
357
- }
358
- };
359
-
360
- // Scroll to index
361
- const scrollToIndex = (
362
- index: number,
363
- alignment: "start" | "center" | "end" = "start",
364
- ) => {
365
- // console.log(
366
- // `[Scrolling] scrollToIndex called: index=${index}, alignment=${alignment}`
367
- // );
368
- if (!viewportState) {
369
- //console.log(`[Scrolling] scrollToIndex aborted: no viewport state`);
370
- return;
371
- }
372
-
373
- const itemSize = viewportState.itemSize || 50;
374
- const totalItems = viewportState.totalItems || 0;
375
- const actualTotalSize = totalItems * itemSize;
376
- const MAX_VIRTUAL_SIZE =
377
- VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.MAX_VIRTUAL_SIZE;
378
- const isCompressed = actualTotalSize > MAX_VIRTUAL_SIZE;
379
-
380
- let targetPosition: number;
381
-
382
- if (isCompressed) {
383
- // In compressed space, map index to virtual position
384
- const ratio = index / totalItems;
385
- targetPosition = ratio * Math.min(actualTotalSize, MAX_VIRTUAL_SIZE);
386
- } else {
387
- // Direct calculation when not compressed
388
- targetPosition = index * itemSize;
389
- }
390
-
391
- // Adjust position based on alignment
392
- switch (alignment) {
393
- case "center":
394
- targetPosition -= containerSize / 2 - itemSize / 2;
395
- break;
396
- case "end":
397
- targetPosition -= containerSize - itemSize;
398
- break;
399
- }
400
-
401
- // console.log(
402
- // `[Scrolling] Target position: ${targetPosition}, isCompressed: ${isCompressed}`
403
- // );
404
-
405
- // console.log(
406
- // `[Scrolling] ScrollToIndex: index=${index}, position=${targetPosition}, alignment=${alignment}`
407
- // );
408
-
409
- scrollToPosition(targetPosition, "scrollToIndex");
410
- };
411
-
412
- // Scroll to a specific page
413
- const scrollToPage = (
414
- page: number,
415
- limit: number = 20,
416
- alignment: "start" | "center" | "end" = "start",
417
- ) => {
418
- // Validate alignment parameter
419
- if (
420
- typeof alignment !== "string" ||
421
- !["start", "center", "end"].includes(alignment)
422
- ) {
423
- console.warn(
424
- `[Scrolling] Invalid alignment "${alignment}", using "start"`,
425
- );
426
- alignment = "start";
427
- }
428
-
429
- // Check if we're in cursor mode
430
- const viewportConfig = (component as any).config;
431
- const isCursorMode = viewportConfig?.pagination?.strategy === "cursor";
432
-
433
- if (isCursorMode) {
434
- // In cursor mode, check if we can scroll to this page
435
- const collection = (component.viewport as any).collection;
436
- if (collection) {
437
- const highestLoadedPage = Math.floor(
438
- collection.getLoadedRanges().size,
439
- );
440
-
441
- if (page > highestLoadedPage + 1) {
442
- // Limit how many pages we'll load at once to prevent excessive API calls
443
- const maxPagesToLoad = 10; // Reasonable limit
444
- const targetPage = Math.min(
445
- page,
446
- highestLoadedPage + maxPagesToLoad,
447
- );
448
-
449
- console.warn(
450
- `[Scrolling] Cannot jump directly to page ${page} in cursor mode. ` +
451
- `Pages must be loaded sequentially. Current highest page: ${highestLoadedPage}. ` +
452
- `Will load up to page ${targetPage}`,
453
- );
454
-
455
- // Trigger sequential loading to the target page (limited)
456
- const targetOffset = (targetPage - 1) * limit;
457
- const currentOffset = highestLoadedPage * limit;
458
-
459
- // Load pages sequentially up to the limited target
460
- // console.log(
461
- // `[Scrolling] Initiating sequential load from page ${
462
- // highestLoadedPage + 1
463
- // } to ${targetPage}`,
464
- // );
465
-
466
- // Scroll to the last loaded position first
467
- const lastLoadedIndex = highestLoadedPage * limit;
468
- scrollToIndex(lastLoadedIndex, alignment);
469
-
470
- // The collection feature will handle sequential loading
471
- return;
472
- }
473
- }
474
- }
475
-
476
- // Convert page to index (page 1 = index 0)
477
- const index = (page - 1) * limit;
478
-
479
- // console.log(
480
- // `[Scrolling] ScrollToPage: page=${page}, limit=${limit}, targetIndex=${index}, alignment=${alignment}`
481
- // );
482
-
483
- // Just scroll to the index - let the normal rendering flow handle data loading and placeholders
484
- scrollToIndex(index, alignment);
485
- };
486
-
487
- // Update scroll bounds
488
- const updateScrollBounds = (
489
- newTotalSize: number,
490
- newContainerSize: number,
491
- ) => {
492
- totalVirtualSize = newTotalSize;
493
- containerSize = newContainerSize;
494
-
495
- if (viewportState) {
496
- viewportState.virtualTotalSize = newTotalSize;
497
- viewportState.containerSize = newContainerSize;
498
- }
499
-
500
- // Don't clamp scroll position until we have real data loaded
501
- // This prevents resetting initialScrollIndex position before data arrives
502
- // Check totalItems instead of totalVirtualSize since virtualSize can be non-zero from padding
503
- const totalItems = viewportState?.totalItems || 0;
504
- if (totalItems <= 0) {
505
- return;
506
- }
507
-
508
- // Clamp current position to new bounds
509
- const maxScroll = Math.max(0, totalVirtualSize - containerSize);
510
- if (scrollPosition > maxScroll) {
511
- scrollToPosition(maxScroll);
512
- }
513
- };
514
-
515
- // Extend viewport API
516
- const originalScrollToIndex = component.viewport.scrollToIndex;
517
- component.viewport.scrollToIndex = (
518
- index: number,
519
- alignment?: "start" | "center" | "end",
520
- ) => {
521
- scrollToIndex(index, alignment);
522
- originalScrollToIndex?.(index, alignment);
523
- };
524
-
525
- // Add scrollToPage to viewport API (new method, no original to preserve)
526
- (component.viewport as any).scrollToPage = (
527
- page: number,
528
- limit?: number,
529
- alignment?: "start" | "center" | "end",
530
- ) => {
531
- scrollToPage(page, limit, alignment);
532
- };
533
-
534
- const originalScrollToPosition = component.viewport.scrollToPosition;
535
- component.viewport.scrollToPosition = (position: number) => {
536
- scrollToPosition(position, "api");
537
- originalScrollToPosition?.(position);
538
- };
539
-
540
- const originalGetScrollPosition = component.viewport.getScrollPosition;
541
- component.viewport.getScrollPosition = () => {
542
- return scrollPosition;
543
- };
544
-
545
- // Add wheel event listener on initialization
546
- // This block is removed as per the edit hint.
547
-
548
- // Clean up on destroy
549
- if ("destroy" in component && typeof component.destroy === "function") {
550
- const originalDestroy = component.destroy;
551
- component.destroy = () => {
552
- // Remove event listeners
553
- const viewportElement = (component as any)._scrollingViewportElement;
554
- if (viewportElement) {
555
- viewportElement.removeEventListener("wheel", handleWheel);
556
- }
557
-
558
- // Clear timeouts
559
- if (idleTimeoutId) {
560
- clearTimeout(idleTimeoutId);
561
- }
562
-
563
- stopIdleDetection();
564
- originalDestroy?.();
565
- };
566
- }
567
-
568
- // Add scrollBy method
569
- const scrollBy = (delta: number) => {
570
- const previousPosition = scrollPosition;
571
- const maxScroll = Math.max(0, totalVirtualSize - containerSize);
572
- const newPosition = clamp(scrollPosition + delta, 0, maxScroll);
573
-
574
- if (newPosition !== previousPosition) {
575
- scrollPosition = newPosition;
576
-
577
- // Update speed tracker
578
- speedTracker = updateSpeedTracker(
579
- speedTracker,
580
- scrollPosition,
581
- previousPosition,
582
- );
583
-
584
- // Update viewport state
585
- if (viewportState) {
586
- viewportState.scrollPosition = scrollPosition;
587
- viewportState.velocity = speedTracker.velocity;
588
- }
589
-
590
- // Emit scroll event
591
- component.emit?.("viewport:scroll", {
592
- position: scrollPosition,
593
- velocity: speedTracker.velocity,
594
- direction: speedTracker.direction,
595
- });
596
-
597
- // Trigger render
598
- component.viewport.renderItems?.();
599
-
600
- // Start idle detection if not scrolling
601
- if (!isScrolling) {
602
- isScrolling = true;
603
- startIdleDetection();
604
- }
605
- lastScrollTime = Date.now();
606
- }
607
- };
608
-
609
- // Expose scrolling state for other features
610
- (component.viewport as any).scrollingState = {
611
- setVelocityToZero,
612
- };
613
-
614
- // Add scrollBy to viewport API
615
- component.viewport.scrollBy = scrollBy;
616
- component.viewport.getVelocity = () => speedTracker.velocity;
617
-
618
- // Expose scrolling API
619
- (component as any).scrolling = {
620
- handleWheel,
621
- scrollToPosition,
622
- scrollToIndex,
623
- scrollToPage,
624
- scrollBy,
625
- getScrollPosition: () => scrollPosition,
626
- updateScrollBounds,
627
- getVelocity: () => speedTracker.velocity,
628
- getDirection: () => speedTracker.direction,
629
- isScrolling: () => isScrolling,
630
- };
631
-
632
- return component;
633
- };
634
- };