lazy-render-virtual-scroll 1.1.0 → 1.2.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.
@@ -0,0 +1,1521 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ class WindowManager {
6
+ constructor(itemHeight, viewportHeight, bufferSize = 5) {
7
+ this.itemHeight = itemHeight;
8
+ this.viewportHeight = viewportHeight;
9
+ this.bufferSize = bufferSize;
10
+ }
11
+ /**
12
+ * Calculate the visible range based on scroll position
13
+ */
14
+ calculateVisibleRange(scrollTop) {
15
+ // Calculate how many items fit in the viewport
16
+ const itemsPerViewport = Math.ceil(this.viewportHeight / this.itemHeight);
17
+ // Calculate the starting index based on scroll position
18
+ const startIndex = Math.floor(scrollTop / this.itemHeight);
19
+ // Calculate the ending index with buffer
20
+ const endIndex = Math.min(startIndex + itemsPerViewport + this.bufferSize, Number.MAX_SAFE_INTEGER // Will be limited by total items later
21
+ );
22
+ return {
23
+ start: Math.max(0, startIndex - this.bufferSize),
24
+ end: endIndex
25
+ };
26
+ }
27
+ /**
28
+ * Update viewport height if it changes
29
+ */
30
+ updateViewportHeight(height) {
31
+ this.viewportHeight = height;
32
+ }
33
+ /**
34
+ * Update item height if it changes
35
+ */
36
+ updateItemHeight(height) {
37
+ this.itemHeight = height;
38
+ }
39
+ /**
40
+ * Update buffer size if it changes
41
+ */
42
+ updateBufferSize(size) {
43
+ this.bufferSize = size;
44
+ }
45
+ }
46
+
47
+ class PrefetchManager {
48
+ /**
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
55
+ */
56
+ shouldPrefetch(visibleEnd, totalLoaded) {
57
+ // Simple rule: if visible end is approaching the loaded boundary, fetch more
58
+ return visibleEnd >= totalLoaded - 5; // Default buffer
59
+ }
60
+ /**
61
+ * Update buffer size if it changes (for backward compatibility)
62
+ */
63
+ updateBufferSize(size) {
64
+ // This method exists for backward compatibility
65
+ // Intelligent prefetching is now handled in the Engine class
66
+ }
67
+ }
68
+
69
+ class RequestQueue {
70
+ constructor(maxConcurrent = 1) {
71
+ this.queue = [];
72
+ this.processing = false;
73
+ this.maxConcurrent = maxConcurrent;
74
+ }
75
+ /**
76
+ * Add a request to the queue
77
+ */
78
+ add(requestFn) {
79
+ return new Promise((resolve, reject) => {
80
+ this.queue.push(() => requestFn().then(resolve).catch(reject));
81
+ // Start processing if not already processing
82
+ if (!this.processing) {
83
+ this.processQueue();
84
+ }
85
+ });
86
+ }
87
+ /**
88
+ * Process the queue
89
+ */
90
+ async processQueue() {
91
+ if (this.queue.length === 0) {
92
+ this.processing = false;
93
+ return;
94
+ }
95
+ this.processing = true;
96
+ // Process up to maxConcurrent requests
97
+ const concurrentRequests = [];
98
+ const count = Math.min(this.maxConcurrent, this.queue.length);
99
+ for (let i = 0; i < count; i++) {
100
+ const requestFn = this.queue.shift();
101
+ if (requestFn) {
102
+ concurrentRequests.push(requestFn());
103
+ }
104
+ }
105
+ try {
106
+ await Promise.all(concurrentRequests);
107
+ }
108
+ catch (error) {
109
+ console.error('Request queue error:', error);
110
+ }
111
+ // Process remaining items
112
+ await this.processQueue();
113
+ }
114
+ /**
115
+ * Clear the queue
116
+ */
117
+ clear() {
118
+ this.queue = [];
119
+ }
120
+ /**
121
+ * Get the current queue length
122
+ */
123
+ getLength() {
124
+ return this.queue.length;
125
+ }
126
+ }
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
+
748
+ class PerformanceOptimizer {
749
+ constructor() {
750
+ this.frameBudget = 16; // Target for 60fps (16.67ms per frame)
751
+ this.lastFrameTime = 0;
752
+ this.animationFrameId = null;
753
+ this.isOptimizing = false;
754
+ // Frame rate limiter to prevent excessive updates
755
+ this.lastUpdate = 0;
756
+ this.minUpdateInterval = 16; // Minimum 16ms between updates (60fps)
757
+ // Batch updates to reduce DOM manipulations
758
+ this.updateQueue = [];
759
+ this.isProcessingQueue = false;
760
+ // Memory optimization
761
+ this.cleanupThreshold = 1000; // Clean up items beyond this threshold
762
+ this.gcInterval = null;
763
+ this.setupPerformanceMonitoring();
764
+ }
765
+ // Optimize rendering by limiting updates to frame budget
766
+ scheduleOptimizedUpdate(updateFn) {
767
+ const now = performance.now();
768
+ // Throttle updates based on frame rate
769
+ if (now - this.lastUpdate < this.minUpdateInterval) {
770
+ // Queue the update for later
771
+ this.updateQueue.push(updateFn);
772
+ if (!this.isProcessingQueue) {
773
+ this.processUpdateQueue();
774
+ }
775
+ return;
776
+ }
777
+ // Check if we have enough time in the current frame
778
+ if (this.getTimeRemaining() > 4) { // Leave 4ms buffer
779
+ updateFn();
780
+ this.lastUpdate = now;
781
+ }
782
+ else {
783
+ // Schedule for next frame
784
+ this.updateQueue.push(updateFn);
785
+ if (!this.isProcessingQueue) {
786
+ this.processUpdateQueue();
787
+ }
788
+ }
789
+ }
790
+ // Process queued updates efficiently
791
+ async processUpdateQueue() {
792
+ if (this.updateQueue.length === 0) {
793
+ this.isProcessingQueue = false;
794
+ return;
795
+ }
796
+ this.isProcessingQueue = true;
797
+ const currentTime = performance.now();
798
+ // Process as many updates as possible within frame budget
799
+ while (this.updateQueue.length > 0 && this.getTimeRemaining() > 2) {
800
+ const updateFn = this.updateQueue.shift();
801
+ if (updateFn) {
802
+ updateFn();
803
+ }
804
+ }
805
+ this.lastUpdate = currentTime;
806
+ if (this.updateQueue.length > 0) {
807
+ // Schedule remaining updates for next frame (only in browser environment)
808
+ if (typeof requestAnimationFrame !== 'undefined') {
809
+ requestAnimationFrame(() => this.processUpdateQueue());
810
+ }
811
+ else {
812
+ // In Node.js environment, use setTimeout as fallback
813
+ setTimeout(() => this.processUpdateQueue(), 0);
814
+ }
815
+ }
816
+ else {
817
+ this.isProcessingQueue = false;
818
+ }
819
+ }
820
+ // Get remaining time in current frame
821
+ getTimeRemaining() {
822
+ if (typeof performance === 'undefined' || !performance.now) {
823
+ return 16; // Fallback to 60fps
824
+ }
825
+ const currentTime = performance.now();
826
+ // Typically browsers target 10ms remaining time for smoothness
827
+ return Math.max(0, this.frameBudget - (currentTime - this.lastFrameTime));
828
+ }
829
+ // Memory optimization: cleanup off-screen items
830
+ optimizeMemory(cleanupFn, visibleRange) {
831
+ // Determine cleanup range (items far from visible range)
832
+ const cleanupStart = Math.max(0, visibleRange.end + this.cleanupThreshold);
833
+ const cleanupEnd = Math.max(0, visibleRange.start - this.cleanupThreshold);
834
+ if (cleanupStart > visibleRange.end) {
835
+ cleanupFn(visibleRange.end, cleanupStart);
836
+ }
837
+ if (cleanupEnd < visibleRange.start) {
838
+ cleanupFn(cleanupEnd, visibleRange.start);
839
+ }
840
+ }
841
+ // Enable GPU acceleration for smoother scrolling
842
+ enableGPUCssAcceleration(element) {
843
+ // Force hardware acceleration
844
+ element.style.willChange = 'transform';
845
+ element.style.transform = 'translateZ(0)';
846
+ element.style.backfaceVisibility = 'hidden';
847
+ }
848
+ // Disable GPU acceleration when not needed
849
+ disableGPUCssAcceleration(element) {
850
+ element.style.willChange = 'auto';
851
+ element.style.transform = '';
852
+ element.style.backfaceVisibility = '';
853
+ }
854
+ // Optimize for different device capabilities
855
+ getOptimizationProfile() {
856
+ // Simple profile detection based on common device characteristics
857
+ const userAgent = navigator.userAgent || navigator.vendor || window.opera;
858
+ // Detect low-end devices
859
+ if (this.isLowEndDevice(userAgent)) {
860
+ return {
861
+ frameRate: 30, // Lower target for low-end devices
862
+ batchSize: 5, // Smaller batches
863
+ bufferMultiplier: 0.5, // Smaller buffer
864
+ updateInterval: 32 // 30fps interval
865
+ };
866
+ }
867
+ // Default profile for capable devices
868
+ return {
869
+ frameRate: 60,
870
+ batchSize: 10,
871
+ bufferMultiplier: 1.0,
872
+ updateInterval: 16 // 60fps interval
873
+ };
874
+ }
875
+ isLowEndDevice(userAgent) {
876
+ // Simple heuristic for low-end devices
877
+ const lowEndPatterns = [
878
+ /Android.*Mobile/,
879
+ /iPhone.*OS [0-9]+_[0-9]+/,
880
+ /Opera Mini/,
881
+ /IEMobile/
882
+ ];
883
+ return lowEndPatterns.some(pattern => pattern.test(userAgent));
884
+ }
885
+ // Setup performance monitoring
886
+ setupPerformanceMonitoring() {
887
+ // Check if we're in a browser environment
888
+ if (typeof window === 'undefined' || typeof requestAnimationFrame === 'undefined') {
889
+ // In Node.js environment, skip browser-specific monitoring
890
+ return;
891
+ }
892
+ // Monitor frame rate
893
+ let frameCount = 0;
894
+ let lastTime = performance.now();
895
+ const monitorFrameRate = () => {
896
+ frameCount++;
897
+ const currentTime = performance.now();
898
+ if (currentTime - lastTime >= 1000) { // Every second
899
+ const fps = frameCount;
900
+ frameCount = 0;
901
+ lastTime = currentTime;
902
+ // Adjust optimization based on actual FPS
903
+ if (fps < 30) {
904
+ this.frameBudget = 32; // Target 30fps
905
+ }
906
+ else if (fps < 50) {
907
+ this.frameBudget = 20; // Target 50fps
908
+ }
909
+ else {
910
+ this.frameBudget = 16; // Target 60fps
911
+ }
912
+ }
913
+ this.animationFrameId = requestAnimationFrame(monitorFrameRate);
914
+ };
915
+ this.animationFrameId = requestAnimationFrame(monitorFrameRate);
916
+ // Setup garbage collection monitoring
917
+ this.gcInterval = window.setInterval(() => {
918
+ if ('gc' in window) {
919
+ // @ts-ignore - gc is non-standard
920
+ window.gc();
921
+ }
922
+ }, 30000); // GC every 30 seconds
923
+ }
924
+ // Get performance insights
925
+ getPerformanceInsights() {
926
+ return {
927
+ frameRate: 60, // Would be calculated from monitoring
928
+ memoryUsage: this.getMemoryUsage(),
929
+ updateFrequency: 1000 / this.minUpdateInterval,
930
+ optimizationActive: this.isOptimizing
931
+ };
932
+ }
933
+ getMemoryUsage() {
934
+ var _a;
935
+ if ('memory' in performance) {
936
+ // @ts-ignore - memory property is non-standard
937
+ return ((_a = performance.memory) === null || _a === void 0 ? void 0 : _a.usedJSHeapSize) || null;
938
+ }
939
+ return null;
940
+ }
941
+ // Cleanup resources
942
+ cleanup() {
943
+ if (this.animationFrameId && typeof cancelAnimationFrame !== 'undefined') {
944
+ cancelAnimationFrame(this.animationFrameId);
945
+ }
946
+ if (this.gcInterval && typeof clearInterval !== 'undefined') {
947
+ clearInterval(this.gcInterval);
948
+ }
949
+ this.updateQueue = [];
950
+ this.isProcessingQueue = false;
951
+ }
952
+ }
953
+
954
+ class MemoryManager {
955
+ constructor(maxCacheSize = 1000) {
956
+ this.itemCache = new Map();
957
+ this.maxCacheSize = 1000; // Maximum items to keep in cache
958
+ this.cleanupThreshold = 500; // Start cleanup when cache exceeds this
959
+ this.visibleRange = { start: 0, end: 0 };
960
+ this.totalItems = 0;
961
+ this.maxCacheSize = maxCacheSize;
962
+ }
963
+ // Set visible range to optimize cache
964
+ setVisibleRange(range) {
965
+ this.visibleRange = range;
966
+ }
967
+ // Set total number of items
968
+ setTotalItems(total) {
969
+ this.totalItems = total;
970
+ }
971
+ // Get item from cache
972
+ get(key) {
973
+ return this.itemCache.get(key) || null;
974
+ }
975
+ // Set item in cache
976
+ set(key, value) {
977
+ this.itemCache.set(key, value);
978
+ // Clean up if cache is too large
979
+ if (this.itemCache.size > this.maxCacheSize) {
980
+ this.cleanupCache();
981
+ }
982
+ }
983
+ // Check if item exists in cache
984
+ has(key) {
985
+ return this.itemCache.has(key);
986
+ }
987
+ // Remove item from cache
988
+ delete(key) {
989
+ return this.itemCache.delete(key);
990
+ }
991
+ // Clear entire cache
992
+ clear() {
993
+ this.itemCache.clear();
994
+ }
995
+ // Clean up cache based on visibility and distance from visible range
996
+ cleanupCache() {
997
+ if (this.itemCache.size <= this.cleanupThreshold) {
998
+ return; // No need to clean up
999
+ }
1000
+ const itemsToRemove = [];
1001
+ // Find items that are far from visible range
1002
+ for (const [key] of this.itemCache.entries()) {
1003
+ const distanceFromVisible = this.getDistanceFromVisible(key);
1004
+ // Remove items that are far from visible range
1005
+ if (distanceFromVisible > 100) { // Arbitrary threshold
1006
+ itemsToRemove.push(key);
1007
+ }
1008
+ }
1009
+ // If we still have too many items, remove oldest accessed items
1010
+ if (this.itemCache.size - itemsToRemove.length > this.cleanupThreshold) {
1011
+ const sortedKeys = Array.from(this.itemCache.keys())
1012
+ .sort((a, b) => a - b); // Sort by key (assuming they're indexes)
1013
+ // Remove items that are furthest from visible range
1014
+ for (const key of sortedKeys) {
1015
+ if (this.itemCache.size <= this.cleanupThreshold)
1016
+ break;
1017
+ const distance = this.getDistanceFromVisible(key);
1018
+ if (distance > 50) { // Remove items beyond 50 units from visible
1019
+ itemsToRemove.push(key);
1020
+ }
1021
+ }
1022
+ }
1023
+ // Actually remove items
1024
+ for (const key of itemsToRemove) {
1025
+ this.itemCache.delete(key);
1026
+ }
1027
+ }
1028
+ // Calculate distance from visible range
1029
+ getDistanceFromVisible(index) {
1030
+ if (index >= this.visibleRange.start && index <= this.visibleRange.end) {
1031
+ return 0; // Inside visible range
1032
+ }
1033
+ if (index < this.visibleRange.start) {
1034
+ return this.visibleRange.start - index;
1035
+ }
1036
+ return index - this.visibleRange.end;
1037
+ }
1038
+ // Get cache statistics
1039
+ getStats() {
1040
+ const visibleItems = Array.from(this.itemCache.keys())
1041
+ .filter(key => key >= this.visibleRange.start && key <= this.visibleRange.end)
1042
+ .length;
1043
+ const offScreenItems = this.itemCache.size - visibleItems;
1044
+ // Rough estimate of memory usage (in bytes)
1045
+ let memoryEstimate = 0;
1046
+ for (const [_, value] of this.itemCache.entries()) {
1047
+ memoryEstimate += this.estimateObjectSize(value);
1048
+ }
1049
+ return {
1050
+ size: this.itemCache.size,
1051
+ maxCacheSize: this.maxCacheSize,
1052
+ visibleItems,
1053
+ offScreenItems,
1054
+ memoryEstimate
1055
+ };
1056
+ }
1057
+ // Estimate object size in bytes
1058
+ estimateObjectSize(obj) {
1059
+ if (obj === null || obj === undefined)
1060
+ return 0;
1061
+ if (typeof obj === 'string')
1062
+ return obj.length * 2; // UTF-16 chars
1063
+ if (typeof obj === 'number')
1064
+ return 8; // 8 bytes for number
1065
+ if (typeof obj === 'boolean')
1066
+ return 4; // 4 bytes for boolean
1067
+ if (typeof obj === 'object') {
1068
+ let size = 0;
1069
+ for (const key in obj) {
1070
+ if (obj.hasOwnProperty(key)) {
1071
+ size += key.length * 2; // Key size
1072
+ size += this.estimateObjectSize(obj[key]); // Value size
1073
+ }
1074
+ }
1075
+ return size;
1076
+ }
1077
+ return 0; // Other types
1078
+ }
1079
+ // Prune cache to only keep essential items
1080
+ pruneEssential() {
1081
+ const essentialItems = [];
1082
+ // Keep items in visible range and nearby
1083
+ for (const [key, value] of this.itemCache.entries()) {
1084
+ if (this.isEssential(key)) {
1085
+ essentialItems.push([key, value]);
1086
+ }
1087
+ }
1088
+ // Clear cache and repopulate with essential items
1089
+ this.itemCache.clear();
1090
+ for (const [key, value] of essentialItems) {
1091
+ this.itemCache.set(key, value);
1092
+ }
1093
+ }
1094
+ // Check if item is essential (within buffer zone)
1095
+ isEssential(index) {
1096
+ const bufferZone = 20; // Keep items within 20 positions of visible range
1097
+ return index >= (this.visibleRange.start - bufferZone) &&
1098
+ index <= (this.visibleRange.end + bufferZone);
1099
+ }
1100
+ // Get cache size
1101
+ getSize() {
1102
+ return this.itemCache.size;
1103
+ }
1104
+ // Get cache keys
1105
+ getKeys() {
1106
+ return Array.from(this.itemCache.keys());
1107
+ }
1108
+ }
1109
+
1110
+ class GPUAccelerator {
1111
+ constructor() {
1112
+ this.gpuAccelerationEnabled = false;
1113
+ this.gpuElements = new WeakSet();
1114
+ this.animationFrameId = null;
1115
+ this.gpuAccelerationEnabled = this.isGPUSupported();
1116
+ }
1117
+ // Check if GPU acceleration is supported
1118
+ isGPUSupported() {
1119
+ // Check if we're in a browser environment
1120
+ if (typeof document === 'undefined') {
1121
+ return false; // Not supported in Node.js environment
1122
+ }
1123
+ // Check for 3D transform support
1124
+ const testEl = document.createElement('div');
1125
+ return testEl.style.webkitTransform !== undefined ||
1126
+ testEl.style.transform !== undefined;
1127
+ }
1128
+ // Enable GPU acceleration for an element
1129
+ enableForElement(element) {
1130
+ if (!this.gpuAccelerationEnabled)
1131
+ return;
1132
+ // Apply GPU-accelerated styles
1133
+ element.style.willChange = 'transform';
1134
+ element.style.transform = 'translateZ(0)';
1135
+ element.style.backfaceVisibility = 'hidden';
1136
+ element.style.perspective = '1000px';
1137
+ // Add to tracked elements
1138
+ this.gpuElements.add(element);
1139
+ }
1140
+ // Disable GPU acceleration for an element
1141
+ disableForElement(element) {
1142
+ if (!this.gpuAccelerationEnabled)
1143
+ return;
1144
+ // Remove GPU-accelerated styles
1145
+ element.style.willChange = 'auto';
1146
+ element.style.transform = '';
1147
+ element.style.backfaceVisibility = '';
1148
+ element.style.perspective = '';
1149
+ // Remove from tracked elements
1150
+ this.gpuElements.delete(element);
1151
+ }
1152
+ // Apply GPU acceleration to a list of elements
1153
+ enableForElements(elements) {
1154
+ elements.forEach(el => this.enableForElement(el));
1155
+ }
1156
+ // Batch update GPU acceleration
1157
+ batchUpdate(elements, enable) {
1158
+ if (!this.gpuAccelerationEnabled)
1159
+ return;
1160
+ if (enable) {
1161
+ this.enableForElements(elements);
1162
+ }
1163
+ else {
1164
+ elements.forEach(el => this.disableForElement(el));
1165
+ }
1166
+ }
1167
+ // Optimize scrolling container for GPU acceleration
1168
+ optimizeScrollContainer(container) {
1169
+ if (!this.gpuAccelerationEnabled)
1170
+ return;
1171
+ // Apply optimizations to container
1172
+ container.style.transform = 'translateZ(0)';
1173
+ container.style.willChange = 'scroll-position';
1174
+ container.style.webkitOverflowScrolling = 'touch'; // For iOS
1175
+ }
1176
+ // Optimize individual items for GPU acceleration
1177
+ optimizeItem(item) {
1178
+ if (!this.gpuAccelerationEnabled)
1179
+ return;
1180
+ // Apply lightweight GPU acceleration
1181
+ item.style.transform = 'translateZ(0)';
1182
+ item.style.willChange = 'transform';
1183
+ }
1184
+ // Get GPU acceleration status
1185
+ getStatus() {
1186
+ // Since WeakSet doesn't have a size property, we can't count directly
1187
+ // This is a limitation of WeakSet
1188
+ return {
1189
+ enabled: this.gpuAccelerationEnabled,
1190
+ supported: this.isGPUSupported(),
1191
+ elementCount: 0 // Placeholder - would need different tracking method
1192
+ };
1193
+ }
1194
+ // Optimize for different scenarios
1195
+ optimizeForScenario(scenario) {
1196
+ if (!this.gpuAccelerationEnabled)
1197
+ return;
1198
+ switch (scenario) {
1199
+ case 'scrolling':
1200
+ // Optimize for smooth scrolling
1201
+ document.body.style.willChange = 'transform';
1202
+ break;
1203
+ case 'animation':
1204
+ // Optimize for animations
1205
+ document.body.style.transform = 'translateZ(0)';
1206
+ break;
1207
+ case 'static':
1208
+ // Remove optimizations when not needed
1209
+ document.body.style.willChange = 'auto';
1210
+ document.body.style.transform = '';
1211
+ break;
1212
+ }
1213
+ }
1214
+ // Cleanup GPU acceleration resources
1215
+ cleanup() {
1216
+ if (this.animationFrameId) {
1217
+ cancelAnimationFrame(this.animationFrameId);
1218
+ }
1219
+ // Reset any applied styles (would need to track them)
1220
+ this.gpuElements = new WeakSet();
1221
+ }
1222
+ // Check if element has GPU acceleration enabled
1223
+ isAccelerated(element) {
1224
+ return this.gpuElements.has(element);
1225
+ }
1226
+ // Get optimization recommendations
1227
+ getRecommendations() {
1228
+ const recommendations = [];
1229
+ if (!this.gpuAccelerationEnabled) {
1230
+ recommendations.push('GPU acceleration not supported on this device');
1231
+ }
1232
+ else {
1233
+ recommendations.push('GPU acceleration enabled for smooth performance');
1234
+ recommendations.push('Using hardware-accelerated compositing');
1235
+ recommendations.push('Optimized for 60fps rendering');
1236
+ }
1237
+ return recommendations;
1238
+ }
1239
+ }
1240
+
1241
+ class Engine {
1242
+ constructor(config) {
1243
+ this.fetchMoreCallback = null;
1244
+ this.config = {
1245
+ ...config,
1246
+ bufferSize: config.bufferSize || 5
1247
+ };
1248
+ this.windowManager = new WindowManager(this.config.itemHeight, this.config.viewportHeight, this.config.bufferSize);
1249
+ this.prefetchManager = new PrefetchManager(this.config.bufferSize);
1250
+ this.requestQueue = new RequestQueue(1); // Single request at a time
1251
+ this.intelligentScrollDetector = new IntelligentScrollDetector();
1252
+ this.networkDetector = new NetworkSpeedDetector();
1253
+ this.networkAwarePrefetchManager = new NetworkAwarePrefetchManager(this.networkDetector);
1254
+ this.networkAwareRequestQueue = new NetworkAwareRequestQueue(this.networkDetector);
1255
+ this.adaptiveBufferCalculator = new AdaptiveBufferCalculator();
1256
+ this.performanceOptimizer = new PerformanceOptimizer();
1257
+ this.memoryManager = new MemoryManager(1000); // Cache up to 1000 items
1258
+ this.gpuAccelerator = new GPUAccelerator();
1259
+ this.totalItems = this.config.totalItems || Number.MAX_SAFE_INTEGER;
1260
+ this.state = {
1261
+ scrollTop: 0,
1262
+ visibleRange: { start: 0, end: 0 },
1263
+ loadedItems: 0,
1264
+ isLoading: false
1265
+ };
1266
+ }
1267
+ /**
1268
+ * Update scroll position and recalculate visible range with intelligent detection
1269
+ */
1270
+ async updateScrollPosition(scrollTop) {
1271
+ // Use performance optimizer to schedule updates efficiently
1272
+ this.performanceOptimizer.scheduleOptimizedUpdate(async () => {
1273
+ // Calculate velocity and other intelligent metrics
1274
+ const velocity = this.intelligentScrollDetector.calculateVelocity(scrollTop);
1275
+ this.intelligentScrollDetector.getDirection(velocity);
1276
+ // Get network quality for adaptive buffering
1277
+ const networkQuality = await this.networkDetector.assessConnectionQuality();
1278
+ // Calculate adaptive buffer considering all factors
1279
+ const adaptiveBuffer = await this.adaptiveBufferCalculator.calculateOptimalBuffer({
1280
+ scrollVelocity: velocity,
1281
+ networkQuality,
1282
+ baseBuffer: this.intelligentScrollDetector.calculateBuffer(velocity),
1283
+ visibleItems: [] // In a real implementation, this would be the actual visible items
1284
+ });
1285
+ // Update window manager with adaptive buffer
1286
+ this.windowManager.updateBufferSize(adaptiveBuffer);
1287
+ // Update memory manager with visible range
1288
+ this.memoryManager.setVisibleRange({
1289
+ start: Math.max(0, this.state.visibleRange.start - adaptiveBuffer),
1290
+ end: this.state.visibleRange.end + adaptiveBuffer
1291
+ });
1292
+ this.state.scrollTop = scrollTop;
1293
+ this.state.visibleRange = this.windowManager.calculateVisibleRange(scrollTop);
1294
+ // Check if we need to fetch more items
1295
+ if (await this.shouldFetchMore()) {
1296
+ await this.fetchMore();
1297
+ }
1298
+ });
1299
+ }
1300
+ /**
1301
+ * Get the current visible range
1302
+ */
1303
+ getVisibleRange() {
1304
+ return this.state.visibleRange;
1305
+ }
1306
+ /**
1307
+ * Check if more items should be fetched with intelligent and network-aware detection
1308
+ */
1309
+ async shouldFetchMore() {
1310
+ if (!this.fetchMoreCallback)
1311
+ return false;
1312
+ if (this.state.isLoading)
1313
+ return false;
1314
+ if (this.state.loadedItems >= this.totalItems)
1315
+ return false;
1316
+ // Get current velocity for intelligent prefetching
1317
+ const velocity = this.intelligentScrollDetector.calculateVelocity(this.state.scrollTop);
1318
+ // Get network quality for adaptive prefetching
1319
+ await this.networkDetector.assessConnectionQuality();
1320
+ // Calculate network-adjusted prefetch distance
1321
+ const prefetchDistance = await this.networkAwarePrefetchManager.calculateNetworkAdjustedPrefetch(velocity);
1322
+ // Use intelligent prefetch logic
1323
+ const visibleEnd = this.state.visibleRange.end;
1324
+ const totalLoaded = this.state.loadedItems;
1325
+ // Intelligent prefetch: if visible end is approaching the loaded boundary
1326
+ return visibleEnd >= totalLoaded - prefetchDistance;
1327
+ }
1328
+ /**
1329
+ * Fetch more items with network awareness
1330
+ */
1331
+ async fetchMore() {
1332
+ if (!this.fetchMoreCallback || this.state.isLoading)
1333
+ return;
1334
+ this.state.isLoading = true;
1335
+ try {
1336
+ // Use network-aware request queue
1337
+ const result = await this.networkAwareRequestQueue.add(this.fetchMoreCallback);
1338
+ // Assuming the result contains new items
1339
+ // In a real implementation, this would update the loaded items count
1340
+ this.state.loadedItems += Array.isArray(result) ? result.length : 1;
1341
+ }
1342
+ catch (error) {
1343
+ console.error('Error fetching more items:', error);
1344
+ }
1345
+ finally {
1346
+ this.state.isLoading = false;
1347
+ }
1348
+ }
1349
+ /**
1350
+ * Set the fetchMore callback function
1351
+ */
1352
+ setFetchMoreCallback(callback) {
1353
+ this.fetchMoreCallback = callback;
1354
+ }
1355
+ /**
1356
+ * Update total items count
1357
+ */
1358
+ updateTotalItems(count) {
1359
+ this.totalItems = count;
1360
+ }
1361
+ /**
1362
+ * Get current engine state
1363
+ */
1364
+ getState() {
1365
+ return { ...this.state };
1366
+ }
1367
+ /**
1368
+ * Update viewport dimensions
1369
+ */
1370
+ updateDimensions(viewportHeight, itemHeight) {
1371
+ this.windowManager.updateViewportHeight(viewportHeight);
1372
+ this.windowManager.updateItemHeight(itemHeight);
1373
+ // Recalculate visible range with new dimensions
1374
+ this.state.visibleRange = this.windowManager.calculateVisibleRange(this.state.scrollTop);
1375
+ }
1376
+ /**
1377
+ * Cleanup resources
1378
+ */
1379
+ cleanup() {
1380
+ this.requestQueue.clear();
1381
+ this.networkAwareRequestQueue.clear();
1382
+ this.fetchMoreCallback = null;
1383
+ this.intelligentScrollDetector.cleanup();
1384
+ this.performanceOptimizer.cleanup();
1385
+ this.memoryManager.clear();
1386
+ }
1387
+ }
1388
+
1389
+ const useLazyList = (config) => {
1390
+ const { fetchMore, ...engineConfig } = config;
1391
+ const engineRef = React.useRef(null);
1392
+ const containerRef = React.useRef(null);
1393
+ const [visibleRange, setVisibleRange] = React.useState({ start: 0, end: 0 });
1394
+ const [loadedItems, setLoadedItems] = React.useState([]);
1395
+ const [isLoading, setIsLoading] = React.useState(false);
1396
+ const [scrollAnalysis, setScrollAnalysis] = React.useState({
1397
+ velocity: 0,
1398
+ direction: 'stationary',
1399
+ buffer: 5,
1400
+ prefetchDistance: 400,
1401
+ predictedPosition: 0,
1402
+ isIdle: true
1403
+ });
1404
+ // Initialize engine
1405
+ React.useEffect(() => {
1406
+ engineRef.current = new Engine(engineConfig);
1407
+ engineRef.current.setFetchMoreCallback(fetchMore);
1408
+ return () => {
1409
+ if (engineRef.current) {
1410
+ engineRef.current.cleanup();
1411
+ }
1412
+ };
1413
+ }, []);
1414
+ // Update engine when config changes
1415
+ React.useEffect(() => {
1416
+ if (engineRef.current) {
1417
+ engineRef.current.updateDimensions(engineConfig.viewportHeight, engineConfig.itemHeight);
1418
+ }
1419
+ }, [engineConfig.viewportHeight, engineConfig.itemHeight]);
1420
+ // Handle scroll events
1421
+ const handleScroll = React.useCallback((scrollTop) => {
1422
+ if (engineRef.current) {
1423
+ engineRef.current.updateScrollPosition(scrollTop);
1424
+ // Update state based on engine
1425
+ const state = engineRef.current.getState();
1426
+ setVisibleRange(state.visibleRange);
1427
+ setIsLoading(state.isLoading);
1428
+ }
1429
+ }, []);
1430
+ // Set container reference
1431
+ const setContainerRef = (element) => {
1432
+ if (element) {
1433
+ containerRef.current = element;
1434
+ // Initialize scroll observer when container is available
1435
+ if (typeof window !== 'undefined' && element) {
1436
+ // In a real implementation, we would use ScrollObserver here
1437
+ // For now, we'll just attach a basic scroll listener
1438
+ const handleScrollEvent = () => {
1439
+ handleScroll(element.scrollTop);
1440
+ };
1441
+ element.addEventListener('scroll', handleScrollEvent, { passive: true });
1442
+ // Cleanup
1443
+ return () => {
1444
+ element.removeEventListener('scroll', handleScrollEvent);
1445
+ };
1446
+ }
1447
+ }
1448
+ };
1449
+ return {
1450
+ visibleRange,
1451
+ loadedItems,
1452
+ isLoading,
1453
+ scrollAnalysis,
1454
+ setContainerRef,
1455
+ // Helper function to trigger manual refresh
1456
+ refresh: () => {
1457
+ var _a;
1458
+ if (engineRef.current) {
1459
+ engineRef.current.updateScrollPosition(((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) || 0);
1460
+ }
1461
+ },
1462
+ // Function to get current scroll analysis
1463
+ getScrollAnalysis: () => {
1464
+ if (engineRef.current) {
1465
+ // In a real implementation, we would get the analysis from the engine
1466
+ // For now, we'll return the current state
1467
+ return scrollAnalysis;
1468
+ }
1469
+ return {
1470
+ velocity: 0,
1471
+ direction: 'stationary',
1472
+ buffer: 5,
1473
+ prefetchDistance: 400,
1474
+ predictedPosition: 0,
1475
+ isIdle: true
1476
+ };
1477
+ }
1478
+ };
1479
+ };
1480
+
1481
+ const LazyList = React.forwardRef((props, ref) => {
1482
+ const { fetchMore, renderItem, items, itemHeight, viewportHeight, bufferSize, className = '', style = {}, ...rest } = props;
1483
+ const { visibleRange, setContainerRef, isLoading } = useLazyList({
1484
+ fetchMore,
1485
+ itemHeight,
1486
+ viewportHeight,
1487
+ bufferSize,
1488
+ ...rest
1489
+ });
1490
+ // Calculate container height to simulate infinite scroll
1491
+ const containerHeight = items.length * itemHeight;
1492
+ const visibleItems = items.slice(visibleRange.start, visibleRange.end);
1493
+ // Calculate top padding to maintain scroll position
1494
+ const paddingTop = visibleRange.start * itemHeight;
1495
+ return (React.createElement("div", { ref: (el) => {
1496
+ setContainerRef(el);
1497
+ if (ref) {
1498
+ if (typeof ref === 'function') {
1499
+ ref(el);
1500
+ }
1501
+ else {
1502
+ ref.current = el;
1503
+ }
1504
+ }
1505
+ }, className: `lazy-list ${className}`, style: {
1506
+ height: `${viewportHeight}px`,
1507
+ overflowY: 'auto',
1508
+ ...style
1509
+ }, ...rest },
1510
+ React.createElement("div", { style: { height: `${paddingTop}px` } }),
1511
+ visibleItems.map((item, index) => (React.createElement("div", { key: visibleRange.start + index, style: { height: `${itemHeight}px` }, className: "lazy-item" }, renderItem(item, visibleRange.start + index)))),
1512
+ React.createElement("div", { style: {
1513
+ height: `${Math.max(0, containerHeight - (visibleRange.end * itemHeight))}px`
1514
+ } }),
1515
+ isLoading && (React.createElement("div", { className: "lazy-loading" }, "Loading more items..."))));
1516
+ });
1517
+ LazyList.displayName = 'LazyList';
1518
+
1519
+ exports.LazyList = LazyList;
1520
+ exports.useLazyList = useLazyList;
1521
+ //# sourceMappingURL=index.js.map