lazy-render-virtual-scroll 1.0.5 → 1.0.7

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/README.md +6 -0
  2. package/dist/cjs/adapters/react/useLazyList.d.ts +10 -1
  3. package/dist/cjs/adapters/react/useLazyList.d.ts.map +1 -1
  4. package/dist/cjs/core/AdaptiveBufferCalculator.d.ts +46 -0
  5. package/dist/cjs/core/AdaptiveBufferCalculator.d.ts.map +1 -0
  6. package/dist/cjs/core/ContentComplexityAnalyzer.d.ts +13 -0
  7. package/dist/cjs/core/ContentComplexityAnalyzer.d.ts.map +1 -0
  8. package/dist/cjs/core/DevicePerformanceMonitor.d.ts +22 -0
  9. package/dist/cjs/core/DevicePerformanceMonitor.d.ts.map +1 -0
  10. package/dist/cjs/core/Engine.d.ts +10 -5
  11. package/dist/cjs/core/Engine.d.ts.map +1 -1
  12. package/dist/cjs/core/IntelligentScrollDetector.d.ts +19 -0
  13. package/dist/cjs/core/IntelligentScrollDetector.d.ts.map +1 -0
  14. package/dist/cjs/core/NetworkAwarePrefetchManager.d.ts +10 -0
  15. package/dist/cjs/core/NetworkAwarePrefetchManager.d.ts.map +1 -0
  16. package/dist/cjs/core/NetworkAwareRequestQueue.d.ts +20 -0
  17. package/dist/cjs/core/NetworkAwareRequestQueue.d.ts.map +1 -0
  18. package/dist/cjs/core/NetworkSpeedDetector.d.ts +16 -0
  19. package/dist/cjs/core/NetworkSpeedDetector.d.ts.map +1 -0
  20. package/dist/cjs/core/PrefetchManager.d.ts +7 -4
  21. package/dist/cjs/core/PrefetchManager.d.ts.map +1 -1
  22. package/dist/cjs/core/WindowManager.d.ts +4 -0
  23. package/dist/cjs/core/WindowManager.d.ts.map +1 -1
  24. package/dist/cjs/core/types.d.ts +8 -0
  25. package/dist/cjs/core/types.d.ts.map +1 -1
  26. package/dist/cjs/index.d.ts +8 -1
  27. package/dist/cjs/index.d.ts.map +1 -1
  28. package/dist/cjs/index.js +709 -16
  29. package/dist/cjs/index.js.map +1 -1
  30. package/dist/esm/adapters/react/useLazyList.d.ts +10 -1
  31. package/dist/esm/adapters/react/useLazyList.d.ts.map +1 -1
  32. package/dist/esm/core/AdaptiveBufferCalculator.d.ts +46 -0
  33. package/dist/esm/core/AdaptiveBufferCalculator.d.ts.map +1 -0
  34. package/dist/esm/core/ContentComplexityAnalyzer.d.ts +13 -0
  35. package/dist/esm/core/ContentComplexityAnalyzer.d.ts.map +1 -0
  36. package/dist/esm/core/DevicePerformanceMonitor.d.ts +22 -0
  37. package/dist/esm/core/DevicePerformanceMonitor.d.ts.map +1 -0
  38. package/dist/esm/core/Engine.d.ts +10 -5
  39. package/dist/esm/core/Engine.d.ts.map +1 -1
  40. package/dist/esm/core/IntelligentScrollDetector.d.ts +19 -0
  41. package/dist/esm/core/IntelligentScrollDetector.d.ts.map +1 -0
  42. package/dist/esm/core/NetworkAwarePrefetchManager.d.ts +10 -0
  43. package/dist/esm/core/NetworkAwarePrefetchManager.d.ts.map +1 -0
  44. package/dist/esm/core/NetworkAwareRequestQueue.d.ts +20 -0
  45. package/dist/esm/core/NetworkAwareRequestQueue.d.ts.map +1 -0
  46. package/dist/esm/core/NetworkSpeedDetector.d.ts +16 -0
  47. package/dist/esm/core/NetworkSpeedDetector.d.ts.map +1 -0
  48. package/dist/esm/core/PrefetchManager.d.ts +7 -4
  49. package/dist/esm/core/PrefetchManager.d.ts.map +1 -1
  50. package/dist/esm/core/WindowManager.d.ts +4 -0
  51. package/dist/esm/core/WindowManager.d.ts.map +1 -1
  52. package/dist/esm/core/types.d.ts +8 -0
  53. package/dist/esm/core/types.d.ts.map +1 -1
  54. package/dist/esm/index.d.ts +8 -1
  55. package/dist/esm/index.d.ts.map +1 -1
  56. package/dist/esm/index.js +703 -17
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/index.d.ts +184 -11
  59. package/package.json +3 -1
package/dist/cjs/index.js CHANGED
@@ -36,24 +36,33 @@ class WindowManager {
36
36
  updateItemHeight(height) {
37
37
  this.itemHeight = height;
38
38
  }
39
+ /**
40
+ * Update buffer size if it changes
41
+ */
42
+ updateBufferSize(size) {
43
+ this.bufferSize = size;
44
+ }
39
45
  }
40
46
 
41
47
  class PrefetchManager {
42
- constructor(bufferSize = 5) {
43
- this.bufferSize = bufferSize;
44
- }
45
48
  /**
46
- * Determine if more items should be fetched based on visible range and loaded items
49
+ * This class is kept for backward compatibility
50
+ * Intelligent prefetching is now handled in the Engine class
51
+ */
52
+ constructor() { }
53
+ /**
54
+ * Legacy method - not used in intelligent mode
47
55
  */
48
56
  shouldPrefetch(visibleEnd, totalLoaded) {
49
57
  // Simple rule: if visible end is approaching the loaded boundary, fetch more
50
- return visibleEnd >= totalLoaded - this.bufferSize;
58
+ return visibleEnd >= totalLoaded - 5; // Default buffer
51
59
  }
52
60
  /**
53
- * Update buffer size if it changes
61
+ * Update buffer size if it changes (for backward compatibility)
54
62
  */
55
63
  updateBufferSize(size) {
56
- this.bufferSize = size;
64
+ // This method exists for backward compatibility
65
+ // Intelligent prefetching is now handled in the Engine class
57
66
  }
58
67
  }
59
68
 
@@ -116,6 +125,626 @@ class RequestQueue {
116
125
  }
117
126
  }
118
127
 
128
+ class IntelligentScrollDetector {
129
+ constructor() {
130
+ this.lastScrollTop = 0;
131
+ this.lastTime = 0;
132
+ this.velocityHistory = [];
133
+ this.HISTORY_SIZE = 5;
134
+ this.scrollTimeout = null;
135
+ this.isIdle = true;
136
+ this.lastTime = performance.now();
137
+ }
138
+ // Calculate velocity from scroll event
139
+ calculateVelocity(scrollTop) {
140
+ const now = performance.now();
141
+ const deltaY = scrollTop - this.lastScrollTop;
142
+ const deltaTime = now - this.lastTime;
143
+ // Calculate velocity (pixels per millisecond)
144
+ const velocity = deltaTime > 0 ? deltaY / deltaTime : 0;
145
+ // Store in history for smoothing
146
+ this.velocityHistory.push(velocity);
147
+ if (this.velocityHistory.length > this.HISTORY_SIZE) {
148
+ this.velocityHistory.shift();
149
+ }
150
+ // Update for next calculation
151
+ this.lastScrollTop = scrollTop;
152
+ this.lastTime = now;
153
+ // Update idle state
154
+ this.isIdle = false;
155
+ this.resetIdleTimer();
156
+ // Return smoothed velocity (average of recent values)
157
+ return this.getAverageVelocity();
158
+ }
159
+ // Get smoothed velocity from history
160
+ getAverageVelocity() {
161
+ if (this.velocityHistory.length === 0)
162
+ return 0;
163
+ const sum = this.velocityHistory.reduce((acc, vel) => acc + vel, 0);
164
+ return sum / this.velocityHistory.length;
165
+ }
166
+ // Determine scroll direction from velocity
167
+ getDirection(velocity) {
168
+ if (Math.abs(velocity) < 0.1)
169
+ return 'stationary';
170
+ return velocity > 0 ? 'down' : 'up';
171
+ }
172
+ // Calculate buffer size based on scroll velocity
173
+ calculateBuffer(velocity) {
174
+ const absVelocity = Math.abs(velocity);
175
+ if (absVelocity > 1.5) {
176
+ return 20; // Large buffer for fast scrolling
177
+ }
178
+ else if (absVelocity > 1.0) {
179
+ return 10; // Medium buffer
180
+ }
181
+ else if (absVelocity > 0.3) {
182
+ return 7; // Small buffer for medium scrolling
183
+ }
184
+ else {
185
+ return 5; // Minimal buffer when nearly stationary
186
+ }
187
+ }
188
+ // Calculate prefetch distance based on velocity
189
+ calculatePrefetchDistance(velocity) {
190
+ const absVelocity = Math.abs(velocity);
191
+ if (absVelocity > 2.0)
192
+ return 1200; // Far ahead for fast scrolling
193
+ if (absVelocity > 1.0)
194
+ return 800; // Medium distance
195
+ if (absVelocity > 0.3)
196
+ return 400; // Close distance for slow scroll
197
+ return 200; // Minimal prefetch when nearly stationary
198
+ }
199
+ // Predict where user will be in X milliseconds
200
+ predictPosition(currentPosition, velocity, msAhead = 500) {
201
+ return currentPosition + (velocity * msAhead);
202
+ }
203
+ // Check if user is currently idle
204
+ getIsIdle() {
205
+ return this.isIdle;
206
+ }
207
+ // Reset idle timer
208
+ resetIdleTimer() {
209
+ if (this.scrollTimeout) {
210
+ clearTimeout(this.scrollTimeout);
211
+ }
212
+ this.scrollTimeout = window.setTimeout(() => {
213
+ this.isIdle = true;
214
+ }, 150); // 150ms after last scroll = idle
215
+ }
216
+ // Clean up resources
217
+ cleanup() {
218
+ if (this.scrollTimeout) {
219
+ clearTimeout(this.scrollTimeout);
220
+ }
221
+ }
222
+ }
223
+
224
+ class NetworkSpeedDetector {
225
+ constructor() {
226
+ this.bandwidthHistory = [];
227
+ this.latencyHistory = [];
228
+ this.HISTORY_SIZE = 5;
229
+ }
230
+ // Estimate available bandwidth
231
+ async estimateBandwidth() {
232
+ const startTime = performance.now();
233
+ const testData = new Array(10000).fill('test_data').join('');
234
+ try {
235
+ // Send test request to measure bandwidth
236
+ const response = await fetch('/api/network-test', {
237
+ method: 'POST',
238
+ body: testData
239
+ });
240
+ const endTime = performance.now();
241
+ const duration = (endTime - startTime) / 1000; // seconds
242
+ const dataSize = testData.length; // bytes
243
+ const bandwidth = dataSize / duration; // bytes per second
244
+ this.bandwidthHistory.push(bandwidth);
245
+ if (this.bandwidthHistory.length > this.HISTORY_SIZE) {
246
+ this.bandwidthHistory.shift();
247
+ }
248
+ return this.getAverageBandwidth();
249
+ }
250
+ catch (error) {
251
+ // If network test fails, return a conservative estimate
252
+ return 100000; // 100 KB/s as fallback
253
+ }
254
+ }
255
+ // Measure network latency
256
+ async measureLatency() {
257
+ try {
258
+ const startTime = performance.now();
259
+ await fetch('/api/ping');
260
+ const endTime = performance.now();
261
+ const latency = endTime - startTime;
262
+ this.latencyHistory.push(latency);
263
+ if (this.latencyHistory.length > this.HISTORY_SIZE) {
264
+ this.latencyHistory.shift();
265
+ }
266
+ return this.getAverageLatency();
267
+ }
268
+ catch (error) {
269
+ // If ping fails, return a high latency as fallback
270
+ return 1000; // 1 second as fallback
271
+ }
272
+ }
273
+ // Assess overall connection quality
274
+ async assessConnectionQuality() {
275
+ try {
276
+ const [bandwidth, latency] = await Promise.all([
277
+ this.estimateBandwidth(),
278
+ this.measureLatency()
279
+ ]);
280
+ if (latency > 1000)
281
+ return 'poor'; // High latency
282
+ if (bandwidth < 100000)
283
+ return 'poor'; // Low bandwidth (< 100 KB/s)
284
+ if (latency > 500 || bandwidth < 500000)
285
+ return 'good'; // Moderate
286
+ return 'excellent'; // Fast and responsive
287
+ }
288
+ catch (_a) {
289
+ return 'offline';
290
+ }
291
+ }
292
+ getAverageBandwidth() {
293
+ if (this.bandwidthHistory.length === 0)
294
+ return 0;
295
+ const sum = this.bandwidthHistory.reduce((a, b) => a + b, 0);
296
+ return sum / this.bandwidthHistory.length;
297
+ }
298
+ getAverageLatency() {
299
+ if (this.latencyHistory.length === 0)
300
+ return 0;
301
+ const sum = this.latencyHistory.reduce((a, b) => a + b, 0);
302
+ return sum / this.latencyHistory.length;
303
+ }
304
+ // Get current network statistics
305
+ getNetworkStats() {
306
+ return {
307
+ bandwidth: this.getAverageBandwidth(),
308
+ latency: this.getAverageLatency(),
309
+ history: [...this.bandwidthHistory]
310
+ };
311
+ }
312
+ }
313
+
314
+ class NetworkAwarePrefetchManager {
315
+ constructor(networkDetector) {
316
+ this.basePrefetchDistance = 400; // Base prefetch distance in pixels
317
+ this.networkDetector = networkDetector;
318
+ }
319
+ // Calculate prefetch distance based on network conditions
320
+ async calculateNetworkAdjustedPrefetch(velocity) {
321
+ const connectionQuality = await this.networkDetector.assessConnectionQuality();
322
+ // Base prefetch distance from scroll velocity
323
+ let baseDistance = this.basePrefetchDistance;
324
+ if (Math.abs(velocity) > 2.0)
325
+ baseDistance = 1200;
326
+ else if (Math.abs(velocity) > 1.0)
327
+ baseDistance = 800;
328
+ else if (Math.abs(velocity) > 0.3)
329
+ baseDistance = 400;
330
+ else
331
+ baseDistance = 200;
332
+ // Adjust based on network quality
333
+ switch (connectionQuality) {
334
+ case 'excellent':
335
+ return Math.round(baseDistance * 1.5); // Extra prefetch on fast networks
336
+ case 'good':
337
+ return Math.round(baseDistance * 1.2); // Slightly more prefetch
338
+ case 'poor':
339
+ return Math.round(baseDistance * 0.7); // Less prefetch on slow networks
340
+ case 'offline':
341
+ return Math.round(baseDistance * 0.3); // Minimal prefetch when offline
342
+ default:
343
+ return baseDistance;
344
+ }
345
+ }
346
+ // Calculate batch size based on network conditions
347
+ async calculateNetworkAdjustedBatchSize(velocity) {
348
+ const connectionQuality = await this.networkDetector.assessConnectionQuality();
349
+ // Base batch size from scroll velocity
350
+ let baseBatchSize = 10; // Default batch size
351
+ if (Math.abs(velocity) > 2.0)
352
+ baseBatchSize = 20; // Fast scroll needs more
353
+ else if (Math.abs(velocity) > 1.0)
354
+ baseBatchSize = 15;
355
+ else if (Math.abs(velocity) > 0.3)
356
+ baseBatchSize = 10;
357
+ else
358
+ baseBatchSize = 5; // Slow scroll needs less
359
+ // Adjust based on network quality
360
+ switch (connectionQuality) {
361
+ case 'excellent':
362
+ return Math.min(baseBatchSize * 2, 50); // Large batches on fast networks
363
+ case 'good':
364
+ return Math.min(baseBatchSize * 1.5, 30); // Medium batches
365
+ case 'poor':
366
+ return Math.max(Math.round(baseBatchSize * 0.5), 5); // Small batches on slow networks
367
+ case 'offline':
368
+ return Math.max(Math.round(baseBatchSize * 0.3), 3); // Minimal batches when offline
369
+ default:
370
+ return baseBatchSize;
371
+ }
372
+ }
373
+ // Determine if prefetch should be delayed based on network conditions
374
+ async shouldDelayPrefetch() {
375
+ const connectionQuality = await this.networkDetector.assessConnectionQuality();
376
+ return connectionQuality === 'poor';
377
+ }
378
+ }
379
+
380
+ class NetworkAwareRequestQueue {
381
+ constructor(networkDetector) {
382
+ this.queue = [];
383
+ this.processing = false;
384
+ this.maxConcurrent = 1;
385
+ this.offlineQueue = [];
386
+ this.networkDetector = networkDetector;
387
+ }
388
+ // Add request with network-aware concurrency
389
+ async add(requestFn) {
390
+ // Adjust concurrency based on network conditions
391
+ const connectionQuality = await this.networkDetector.assessConnectionQuality();
392
+ switch (connectionQuality) {
393
+ case 'excellent':
394
+ this.maxConcurrent = 3; // Allow more concurrent requests
395
+ break;
396
+ case 'good':
397
+ this.maxConcurrent = 2; // Moderate concurrency
398
+ break;
399
+ case 'poor':
400
+ this.maxConcurrent = 1; // Sequential requests on slow networks
401
+ break;
402
+ case 'offline':
403
+ // Queue for later when online
404
+ return this.handleOfflineRequest(requestFn);
405
+ default:
406
+ this.maxConcurrent = 1;
407
+ }
408
+ return new Promise((resolve, reject) => {
409
+ this.queue.push(() => requestFn().then(resolve).catch(reject));
410
+ if (!this.processing) {
411
+ this.processQueue();
412
+ }
413
+ });
414
+ }
415
+ // Process queue with network-aware concurrency
416
+ async processQueue() {
417
+ if (this.queue.length === 0) {
418
+ this.processing = false;
419
+ return;
420
+ }
421
+ this.processing = true;
422
+ // Process up to maxConcurrent requests
423
+ const concurrentRequests = [];
424
+ const count = Math.min(this.maxConcurrent, this.queue.length);
425
+ for (let i = 0; i < count; i++) {
426
+ const requestFn = this.queue.shift();
427
+ if (requestFn) {
428
+ concurrentRequests.push(requestFn());
429
+ }
430
+ }
431
+ try {
432
+ await Promise.all(concurrentRequests);
433
+ }
434
+ catch (error) {
435
+ console.error('Network-aware request queue error:', error);
436
+ }
437
+ // Process remaining items
438
+ await this.processQueue();
439
+ }
440
+ // Handle requests when offline
441
+ async handleOfflineRequest(requestFn) {
442
+ // Store request for later execution
443
+ return new Promise((resolve, reject) => {
444
+ // Add to offline queue
445
+ this.offlineQueue.push(() => requestFn().then(resolve).catch(reject));
446
+ // Check for network restoration periodically
447
+ const checkOnline = () => {
448
+ if (navigator.onLine) {
449
+ // Process offline queue
450
+ this.processOfflineQueue();
451
+ resolve(null); // Resolve with null since we can't return the actual result
452
+ }
453
+ else {
454
+ setTimeout(checkOnline, 5000); // Check again in 5 seconds
455
+ }
456
+ };
457
+ checkOnline();
458
+ });
459
+ }
460
+ // Process offline queue when back online
461
+ async processOfflineQueue() {
462
+ const offlineRequests = [...this.offlineQueue];
463
+ this.offlineQueue = [];
464
+ for (const requestFn of offlineRequests) {
465
+ try {
466
+ await requestFn();
467
+ }
468
+ catch (error) {
469
+ console.error('Offline request failed:', error);
470
+ // Add back to offline queue for retry
471
+ this.offlineQueue.push(requestFn);
472
+ }
473
+ }
474
+ }
475
+ // Get current queue status
476
+ getQueueStatus() {
477
+ return {
478
+ pending: this.queue.length,
479
+ offline: this.offlineQueue.length,
480
+ maxConcurrent: this.maxConcurrent
481
+ };
482
+ }
483
+ // Clear all queues
484
+ clear() {
485
+ this.queue = [];
486
+ this.offlineQueue = [];
487
+ this.processing = false;
488
+ }
489
+ }
490
+
491
+ class DevicePerformanceMonitor {
492
+ constructor() {
493
+ this.frameRateHistory = [];
494
+ this.memoryUsageHistory = [];
495
+ this.gcMonitoring = false;
496
+ this.HISTORY_SIZE = 10;
497
+ this.setupPerformanceMonitoring();
498
+ }
499
+ // Monitor frame rate
500
+ async getFrameRate() {
501
+ return new Promise(resolve => {
502
+ const start = performance.now();
503
+ let frames = 0;
504
+ const measure = () => {
505
+ frames++;
506
+ if (frames >= 60) { // Measure over 60 frames
507
+ const elapsed = performance.now() - start;
508
+ const fps = Math.round((frames / elapsed) * 1000);
509
+ this.frameRateHistory.push(fps);
510
+ if (this.frameRateHistory.length > this.HISTORY_SIZE) {
511
+ this.frameRateHistory.shift();
512
+ }
513
+ resolve(fps);
514
+ }
515
+ else {
516
+ requestAnimationFrame(measure);
517
+ }
518
+ };
519
+ requestAnimationFrame(measure);
520
+ });
521
+ }
522
+ // Get average frame rate
523
+ getAverageFrameRate() {
524
+ if (this.frameRateHistory.length === 0)
525
+ return 60;
526
+ const sum = this.frameRateHistory.reduce((a, b) => a + b, 0);
527
+ return sum / this.frameRateHistory.length;
528
+ }
529
+ // Monitor memory usage (where available)
530
+ getMemoryInfo() {
531
+ if ('memory' in performance) {
532
+ // @ts-ignore - memory property is non-standard
533
+ const mem = performance.memory;
534
+ if (mem) {
535
+ return {
536
+ used: mem.usedJSHeapSize,
537
+ total: mem.jsHeapSizeLimit
538
+ };
539
+ }
540
+ }
541
+ return null;
542
+ }
543
+ // Assess overall device performance
544
+ async assessPerformance() {
545
+ const frameRate = await this.getFrameRate();
546
+ const memoryInfo = this.getMemoryInfo();
547
+ // Normalize frame rate (60fps = excellent, 30fps = poor)
548
+ const frameRateScore = Math.min(frameRate / 60, 1);
549
+ // If we have memory info, factor it in
550
+ if (memoryInfo) {
551
+ const memoryScore = 1 - (memoryInfo.used / memoryInfo.total);
552
+ return (frameRateScore * 0.7) + (memoryScore * 0.3);
553
+ }
554
+ return frameRateScore;
555
+ }
556
+ setupPerformanceMonitoring() {
557
+ // Set up performance monitoring intervals
558
+ setInterval(() => {
559
+ this.getFrameRate(); // Update frame rate history
560
+ }, 5000); // Every 5 seconds
561
+ }
562
+ // Get performance insights
563
+ getPerformanceInsights() {
564
+ const frameRate = this.getAverageFrameRate();
565
+ const memoryInfo = this.getMemoryInfo();
566
+ // Calculate performance score based on frame rate
567
+ const performanceScore = Math.min(frameRate / 60, 1);
568
+ return {
569
+ frameRate,
570
+ performanceScore,
571
+ memoryUsed: (memoryInfo === null || memoryInfo === void 0 ? void 0 : memoryInfo.used) || null,
572
+ memoryTotal: (memoryInfo === null || memoryInfo === void 0 ? void 0 : memoryInfo.total) || null
573
+ };
574
+ }
575
+ }
576
+
577
+ class ContentComplexityAnalyzer {
578
+ // Analyze content complexity based on various factors
579
+ analyzeContentComplexity(items) {
580
+ if (items.length === 0)
581
+ return 0.1; // Minimal complexity for empty
582
+ let totalComplexity = 0;
583
+ for (const item of items) {
584
+ // Analyze different aspects of complexity
585
+ const textComplexity = this.analyzeTextComplexity(item);
586
+ const mediaComplexity = this.analyzeMediaComplexity(item);
587
+ const componentComplexity = this.analyzeComponentComplexity(item);
588
+ totalComplexity += (textComplexity + mediaComplexity + componentComplexity) / 3;
589
+ }
590
+ // Return average complexity normalized to 0-1 scale
591
+ return Math.min(totalComplexity / items.length, 1);
592
+ }
593
+ analyzeTextComplexity(item) {
594
+ let complexity = 0;
595
+ // Length of text content
596
+ if (typeof item.text === 'string') {
597
+ complexity += Math.min(item.text.length / 1000, 0.5); // Max 0.5 for text
598
+ }
599
+ // Number of text elements
600
+ if (Array.isArray(item.textElements)) {
601
+ complexity += Math.min(item.textElements.length / 10, 0.3); // Max 0.3 for elements
602
+ }
603
+ // Formatting complexity
604
+ if (item.hasRichText)
605
+ complexity += 0.2;
606
+ return Math.min(complexity, 1);
607
+ }
608
+ analyzeMediaComplexity(item) {
609
+ let complexity = 0;
610
+ // Number of media elements
611
+ if (Array.isArray(item.media)) {
612
+ complexity += Math.min(item.media.length * 0.2, 0.5);
613
+ }
614
+ // Media types (images, videos are more complex than icons)
615
+ if (item.hasVideo)
616
+ complexity += 0.3;
617
+ if (item.hasImage)
618
+ complexity += 0.15;
619
+ if (item.hasSVG)
620
+ complexity += 0.1;
621
+ return Math.min(complexity, 1);
622
+ }
623
+ analyzeComponentComplexity(item) {
624
+ let complexity = 0;
625
+ // Number of nested components
626
+ if (typeof item.componentDepth === 'number') {
627
+ complexity += Math.min(item.componentDepth * 0.1, 0.4);
628
+ }
629
+ // Interactivity
630
+ if (item.interactive)
631
+ complexity += 0.2;
632
+ if (item.hasAnimations)
633
+ complexity += 0.2;
634
+ if (item.hasState)
635
+ complexity += 0.1;
636
+ return Math.min(complexity, 1);
637
+ }
638
+ // Get complexity insights
639
+ getComplexityInsights(items) {
640
+ if (items.length === 0) {
641
+ return {
642
+ averageComplexity: 0.1,
643
+ textComplexity: 0,
644
+ mediaComplexity: 0,
645
+ componentComplexity: 0
646
+ };
647
+ }
648
+ let totalText = 0, totalMedia = 0, totalComponent = 0;
649
+ for (const item of items) {
650
+ totalText += this.analyzeTextComplexity(item);
651
+ totalMedia += this.analyzeMediaComplexity(item);
652
+ totalComponent += this.analyzeComponentComplexity(item);
653
+ }
654
+ return {
655
+ averageComplexity: this.analyzeContentComplexity(items),
656
+ textComplexity: totalText / items.length,
657
+ mediaComplexity: totalMedia / items.length,
658
+ componentComplexity: totalComponent / items.length
659
+ };
660
+ }
661
+ }
662
+
663
+ class AdaptiveBufferCalculator {
664
+ constructor() {
665
+ this.scrollFactor = 0.3; // Weight for scroll velocity
666
+ this.networkFactor = 0.3; // Weight for network quality
667
+ this.performanceFactor = 0.2; // Weight for device performance
668
+ this.contentFactor = 0.2; // Weight for content complexity
669
+ this.performanceMonitor = new DevicePerformanceMonitor();
670
+ this.contentAnalyzer = new ContentComplexityAnalyzer();
671
+ }
672
+ // Calculate optimal buffer size based on multiple factors
673
+ async calculateOptimalBuffer(params) {
674
+ // Calculate scroll-based buffer
675
+ const scrollBuffer = this.calculateScrollBuffer(params.scrollVelocity, params.baseBuffer);
676
+ // Calculate network-based adjustment
677
+ const networkAdjustment = this.calculateNetworkAdjustment(params.networkQuality);
678
+ // Calculate performance-based adjustment
679
+ const performanceScore = await this.performanceMonitor.assessPerformance();
680
+ const performanceAdjustment = this.calculatePerformanceAdjustment(performanceScore);
681
+ // Calculate content-based adjustment
682
+ const contentComplexity = this.contentAnalyzer.analyzeContentComplexity(params.visibleItems);
683
+ const contentAdjustment = this.calculateContentAdjustment(contentComplexity);
684
+ // Combine all factors
685
+ const weightedBuffer = (scrollBuffer * this.scrollFactor +
686
+ (params.baseBuffer * networkAdjustment) * this.networkFactor +
687
+ (params.baseBuffer * performanceAdjustment) * this.performanceFactor +
688
+ (params.baseBuffer * contentAdjustment) * this.contentFactor);
689
+ // Apply reasonable bounds
690
+ return Math.max(3, Math.min(50, Math.round(weightedBuffer)));
691
+ }
692
+ calculateScrollBuffer(velocity, baseBuffer) {
693
+ const absVelocity = Math.abs(velocity);
694
+ if (absVelocity > 2.0)
695
+ return baseBuffer * 4; // Very fast scroll
696
+ if (absVelocity > 1.0)
697
+ return baseBuffer * 2.5; // Fast scroll
698
+ if (absVelocity > 0.3)
699
+ return baseBuffer * 1.5; // Medium scroll
700
+ return baseBuffer * 0.8; // Slow scroll
701
+ }
702
+ calculateNetworkAdjustment(quality) {
703
+ switch (quality) {
704
+ case 'excellent': return 1.5; // More buffer on fast networks
705
+ case 'good': return 1.2; // Slightly more
706
+ case 'poor': return 0.7; // Less buffer on slow networks
707
+ case 'offline': return 0.5; // Minimal buffer when offline
708
+ default: return 1.0;
709
+ }
710
+ }
711
+ calculatePerformanceAdjustment(performance) {
712
+ // performance is 0-1 scale (0 = poor, 1 = excellent)
713
+ return 0.5 + (performance * 0.8); // Range from 0.5 to 1.3
714
+ }
715
+ calculateContentAdjustment(complexity) {
716
+ // complexity is 0-1 scale (0 = simple, 1 = complex)
717
+ return 1.5 - (complexity * 0.8); // Range from 0.7 to 1.5
718
+ }
719
+ // Get adaptive insights
720
+ async getAdaptiveInsights(params) {
721
+ const buffer = await this.calculateOptimalBuffer(params);
722
+ const perfInsights = this.performanceMonitor.getPerformanceInsights();
723
+ const complexityInsights = this.contentAnalyzer.getComplexityInsights(params.visibleItems);
724
+ return {
725
+ currentBuffer: buffer,
726
+ performance: {
727
+ frameRate: perfInsights.frameRate,
728
+ score: perfInsights.performanceScore
729
+ },
730
+ network: {
731
+ quality: params.networkQuality,
732
+ adjustment: this.calculateNetworkAdjustment(params.networkQuality)
733
+ },
734
+ complexity: {
735
+ score: this.contentAnalyzer.analyzeContentComplexity(params.visibleItems),
736
+ breakdown: complexityInsights
737
+ },
738
+ factors: {
739
+ scroll: this.calculateScrollBuffer(params.scrollVelocity, params.baseBuffer),
740
+ network: this.calculateNetworkAdjustment(params.networkQuality),
741
+ performance: this.calculatePerformanceAdjustment(perfInsights.performanceScore),
742
+ content: this.calculateContentAdjustment(complexityInsights.averageComplexity)
743
+ }
744
+ };
745
+ }
746
+ }
747
+
119
748
  class Engine {
120
749
  constructor(config) {
121
750
  this.fetchMoreCallback = null;
@@ -126,6 +755,11 @@ class Engine {
126
755
  this.windowManager = new WindowManager(this.config.itemHeight, this.config.viewportHeight, this.config.bufferSize);
127
756
  this.prefetchManager = new PrefetchManager(this.config.bufferSize);
128
757
  this.requestQueue = new RequestQueue(1); // Single request at a time
758
+ this.intelligentScrollDetector = new IntelligentScrollDetector();
759
+ this.networkDetector = new NetworkSpeedDetector();
760
+ this.networkAwarePrefetchManager = new NetworkAwarePrefetchManager(this.networkDetector);
761
+ this.networkAwareRequestQueue = new NetworkAwareRequestQueue(this.networkDetector);
762
+ this.adaptiveBufferCalculator = new AdaptiveBufferCalculator();
129
763
  this.totalItems = this.config.totalItems || Number.MAX_SAFE_INTEGER;
130
764
  this.state = {
131
765
  scrollTop: 0,
@@ -135,14 +769,28 @@ class Engine {
135
769
  };
136
770
  }
137
771
  /**
138
- * Update scroll position and recalculate visible range
772
+ * Update scroll position and recalculate visible range with intelligent detection
139
773
  */
140
- updateScrollPosition(scrollTop) {
774
+ async updateScrollPosition(scrollTop) {
775
+ // Calculate velocity and other intelligent metrics
776
+ const velocity = this.intelligentScrollDetector.calculateVelocity(scrollTop);
777
+ this.intelligentScrollDetector.getDirection(velocity);
778
+ // Get network quality for adaptive buffering
779
+ const networkQuality = await this.networkDetector.assessConnectionQuality();
780
+ // Calculate adaptive buffer considering all factors
781
+ const adaptiveBuffer = await this.adaptiveBufferCalculator.calculateOptimalBuffer({
782
+ scrollVelocity: velocity,
783
+ networkQuality,
784
+ baseBuffer: this.intelligentScrollDetector.calculateBuffer(velocity),
785
+ visibleItems: [] // In a real implementation, this would be the actual visible items
786
+ });
787
+ // Update window manager with adaptive buffer
788
+ this.windowManager.updateBufferSize(adaptiveBuffer);
141
789
  this.state.scrollTop = scrollTop;
142
790
  this.state.visibleRange = this.windowManager.calculateVisibleRange(scrollTop);
143
791
  // Check if we need to fetch more items
144
- if (this.shouldFetchMore()) {
145
- this.fetchMore();
792
+ if (await this.shouldFetchMore()) {
793
+ await this.fetchMore();
146
794
  }
147
795
  }
148
796
  /**
@@ -152,26 +800,37 @@ class Engine {
152
800
  return this.state.visibleRange;
153
801
  }
154
802
  /**
155
- * Check if more items should be fetched
803
+ * Check if more items should be fetched with intelligent and network-aware detection
156
804
  */
157
- shouldFetchMore() {
805
+ async shouldFetchMore() {
158
806
  if (!this.fetchMoreCallback)
159
807
  return false;
160
808
  if (this.state.isLoading)
161
809
  return false;
162
810
  if (this.state.loadedItems >= this.totalItems)
163
811
  return false;
164
- return this.prefetchManager.shouldPrefetch(this.state.visibleRange.end, this.state.loadedItems);
812
+ // Get current velocity for intelligent prefetching
813
+ const velocity = this.intelligentScrollDetector.calculateVelocity(this.state.scrollTop);
814
+ // Get network quality for adaptive prefetching
815
+ await this.networkDetector.assessConnectionQuality();
816
+ // Calculate network-adjusted prefetch distance
817
+ const prefetchDistance = await this.networkAwarePrefetchManager.calculateNetworkAdjustedPrefetch(velocity);
818
+ // Use intelligent prefetch logic
819
+ const visibleEnd = this.state.visibleRange.end;
820
+ const totalLoaded = this.state.loadedItems;
821
+ // Intelligent prefetch: if visible end is approaching the loaded boundary
822
+ return visibleEnd >= totalLoaded - prefetchDistance;
165
823
  }
166
824
  /**
167
- * Fetch more items
825
+ * Fetch more items with network awareness
168
826
  */
169
827
  async fetchMore() {
170
828
  if (!this.fetchMoreCallback || this.state.isLoading)
171
829
  return;
172
830
  this.state.isLoading = true;
173
831
  try {
174
- const result = await this.requestQueue.add(this.fetchMoreCallback);
832
+ // Use network-aware request queue
833
+ const result = await this.networkAwareRequestQueue.add(this.fetchMoreCallback);
175
834
  // Assuming the result contains new items
176
835
  // In a real implementation, this would update the loaded items count
177
836
  this.state.loadedItems += Array.isArray(result) ? result.length : 1;
@@ -215,7 +874,9 @@ class Engine {
215
874
  */
216
875
  cleanup() {
217
876
  this.requestQueue.clear();
877
+ this.networkAwareRequestQueue.clear();
218
878
  this.fetchMoreCallback = null;
879
+ this.intelligentScrollDetector.cleanup();
219
880
  }
220
881
  }
221
882
 
@@ -294,6 +955,14 @@ const useLazyList = (config) => {
294
955
  const [visibleRange, setVisibleRange] = React.useState({ start: 0, end: 0 });
295
956
  const [loadedItems, setLoadedItems] = React.useState([]);
296
957
  const [isLoading, setIsLoading] = React.useState(false);
958
+ const [scrollAnalysis, setScrollAnalysis] = React.useState({
959
+ velocity: 0,
960
+ direction: 'stationary',
961
+ buffer: 5,
962
+ prefetchDistance: 400,
963
+ predictedPosition: 0,
964
+ isIdle: true
965
+ });
297
966
  // Initialize engine
298
967
  React.useEffect(() => {
299
968
  engineRef.current = new Engine(engineConfig);
@@ -343,6 +1012,7 @@ const useLazyList = (config) => {
343
1012
  visibleRange,
344
1013
  loadedItems,
345
1014
  isLoading,
1015
+ scrollAnalysis,
346
1016
  setContainerRef,
347
1017
  // Helper function to trigger manual refresh
348
1018
  refresh: () => {
@@ -350,6 +1020,22 @@ const useLazyList = (config) => {
350
1020
  if (engineRef.current) {
351
1021
  engineRef.current.updateScrollPosition(((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) || 0);
352
1022
  }
1023
+ },
1024
+ // Function to get current scroll analysis
1025
+ getScrollAnalysis: () => {
1026
+ if (engineRef.current) {
1027
+ // In a real implementation, we would get the analysis from the engine
1028
+ // For now, we'll return the current state
1029
+ return scrollAnalysis;
1030
+ }
1031
+ return {
1032
+ velocity: 0,
1033
+ direction: 'stationary',
1034
+ buffer: 5,
1035
+ prefetchDistance: 400,
1036
+ predictedPosition: 0,
1037
+ isIdle: true
1038
+ };
353
1039
  }
354
1040
  };
355
1041
  };
@@ -423,8 +1109,15 @@ function throttle(func, limit) {
423
1109
  };
424
1110
  }
425
1111
 
1112
+ exports.AdaptiveBufferCalculator = AdaptiveBufferCalculator;
1113
+ exports.ContentComplexityAnalyzer = ContentComplexityAnalyzer;
1114
+ exports.DevicePerformanceMonitor = DevicePerformanceMonitor;
426
1115
  exports.Engine = Engine;
1116
+ exports.IntelligentScrollDetector = IntelligentScrollDetector;
427
1117
  exports.LazyList = LazyList;
1118
+ exports.NetworkAwarePrefetchManager = NetworkAwarePrefetchManager;
1119
+ exports.NetworkAwareRequestQueue = NetworkAwareRequestQueue;
1120
+ exports.NetworkSpeedDetector = NetworkSpeedDetector;
428
1121
  exports.PrefetchManager = PrefetchManager;
429
1122
  exports.RequestQueue = RequestQueue;
430
1123
  exports.ScrollObserver = ScrollObserver;