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.
- package/README.md +6 -0
- package/dist/cjs/adapters/react/useLazyList.d.ts +10 -1
- package/dist/cjs/adapters/react/useLazyList.d.ts.map +1 -1
- package/dist/cjs/core/AdaptiveBufferCalculator.d.ts +46 -0
- package/dist/cjs/core/AdaptiveBufferCalculator.d.ts.map +1 -0
- package/dist/cjs/core/ContentComplexityAnalyzer.d.ts +13 -0
- package/dist/cjs/core/ContentComplexityAnalyzer.d.ts.map +1 -0
- package/dist/cjs/core/DevicePerformanceMonitor.d.ts +22 -0
- package/dist/cjs/core/DevicePerformanceMonitor.d.ts.map +1 -0
- package/dist/cjs/core/Engine.d.ts +10 -5
- package/dist/cjs/core/Engine.d.ts.map +1 -1
- package/dist/cjs/core/IntelligentScrollDetector.d.ts +19 -0
- package/dist/cjs/core/IntelligentScrollDetector.d.ts.map +1 -0
- package/dist/cjs/core/NetworkAwarePrefetchManager.d.ts +10 -0
- package/dist/cjs/core/NetworkAwarePrefetchManager.d.ts.map +1 -0
- package/dist/cjs/core/NetworkAwareRequestQueue.d.ts +20 -0
- package/dist/cjs/core/NetworkAwareRequestQueue.d.ts.map +1 -0
- package/dist/cjs/core/NetworkSpeedDetector.d.ts +16 -0
- package/dist/cjs/core/NetworkSpeedDetector.d.ts.map +1 -0
- package/dist/cjs/core/PrefetchManager.d.ts +7 -4
- package/dist/cjs/core/PrefetchManager.d.ts.map +1 -1
- package/dist/cjs/core/WindowManager.d.ts +4 -0
- package/dist/cjs/core/WindowManager.d.ts.map +1 -1
- package/dist/cjs/core/types.d.ts +8 -0
- package/dist/cjs/core/types.d.ts.map +1 -1
- package/dist/cjs/index.d.ts +8 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +709 -16
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/adapters/react/useLazyList.d.ts +10 -1
- package/dist/esm/adapters/react/useLazyList.d.ts.map +1 -1
- package/dist/esm/core/AdaptiveBufferCalculator.d.ts +46 -0
- package/dist/esm/core/AdaptiveBufferCalculator.d.ts.map +1 -0
- package/dist/esm/core/ContentComplexityAnalyzer.d.ts +13 -0
- package/dist/esm/core/ContentComplexityAnalyzer.d.ts.map +1 -0
- package/dist/esm/core/DevicePerformanceMonitor.d.ts +22 -0
- package/dist/esm/core/DevicePerformanceMonitor.d.ts.map +1 -0
- package/dist/esm/core/Engine.d.ts +10 -5
- package/dist/esm/core/Engine.d.ts.map +1 -1
- package/dist/esm/core/IntelligentScrollDetector.d.ts +19 -0
- package/dist/esm/core/IntelligentScrollDetector.d.ts.map +1 -0
- package/dist/esm/core/NetworkAwarePrefetchManager.d.ts +10 -0
- package/dist/esm/core/NetworkAwarePrefetchManager.d.ts.map +1 -0
- package/dist/esm/core/NetworkAwareRequestQueue.d.ts +20 -0
- package/dist/esm/core/NetworkAwareRequestQueue.d.ts.map +1 -0
- package/dist/esm/core/NetworkSpeedDetector.d.ts +16 -0
- package/dist/esm/core/NetworkSpeedDetector.d.ts.map +1 -0
- package/dist/esm/core/PrefetchManager.d.ts +7 -4
- package/dist/esm/core/PrefetchManager.d.ts.map +1 -1
- package/dist/esm/core/WindowManager.d.ts +4 -0
- package/dist/esm/core/WindowManager.d.ts.map +1 -1
- package/dist/esm/core/types.d.ts +8 -0
- package/dist/esm/core/types.d.ts.map +1 -1
- package/dist/esm/index.d.ts +8 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +703 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +184 -11
- 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
|
-
*
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|