mtrl-addons 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
  2. package/dist/components/vlist/config.d.ts +86 -0
  3. package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
  4. package/dist/components/vlist/features/api.d.ts +7 -0
  5. package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
  6. package/dist/components/vlist/features/selection.d.ts +6 -0
  7. package/dist/components/vlist/features/viewport.d.ts +9 -0
  8. package/dist/components/vlist/features.d.ts +31 -0
  9. package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -10
  10. package/dist/components/vlist/types.d.ts +596 -0
  11. package/dist/components/vlist/vlist.d.ts +29 -0
  12. package/dist/core/compose/features/gestures/index.d.ts +86 -0
  13. package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
  14. package/dist/core/compose/features/gestures/pan.d.ts +108 -0
  15. package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
  16. package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
  17. package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
  18. package/dist/core/compose/features/gestures/tap.d.ts +79 -0
  19. package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
  20. package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
  21. package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
  22. package/dist/core/gestures/longpress.d.ts +23 -0
  23. package/dist/core/gestures/manager.d.ts +14 -0
  24. package/dist/core/gestures/pan.d.ts +12 -0
  25. package/dist/core/gestures/pinch.d.ts +14 -0
  26. package/dist/core/gestures/rotate.d.ts +14 -0
  27. package/dist/core/gestures/swipe.d.ts +20 -0
  28. package/dist/core/gestures/tap.d.ts +12 -0
  29. package/dist/core/gestures/types.d.ts +320 -0
  30. package/dist/core/gestures/utils.d.ts +57 -0
  31. package/dist/core/index.d.ts +13 -0
  32. package/dist/core/layout/config.d.ts +33 -0
  33. package/dist/core/layout/index.d.ts +51 -0
  34. package/dist/core/layout/jsx.d.ts +65 -0
  35. package/dist/core/layout/schema.d.ts +112 -0
  36. package/dist/core/layout/types.d.ts +69 -0
  37. package/dist/core/viewport/constants.d.ts +105 -0
  38. package/dist/core/viewport/features/base.d.ts +14 -0
  39. package/dist/core/viewport/features/collection.d.ts +41 -0
  40. package/dist/core/viewport/features/events.d.ts +13 -0
  41. package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
  42. package/dist/core/viewport/features/item-size.d.ts +30 -0
  43. package/dist/core/viewport/features/loading.d.ts +34 -0
  44. package/dist/core/viewport/features/momentum.d.ts +17 -0
  45. package/dist/core/viewport/features/performance.d.ts +53 -0
  46. package/dist/core/viewport/features/placeholders.d.ts +38 -0
  47. package/dist/core/viewport/features/rendering.d.ts +16 -0
  48. package/dist/core/viewport/features/scrollbar.d.ts +26 -0
  49. package/dist/core/viewport/features/scrolling.d.ts +16 -0
  50. package/dist/core/viewport/features/utils.d.ts +43 -0
  51. package/dist/core/viewport/features/virtual.d.ts +18 -0
  52. package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
  53. package/dist/core/viewport/types.d.ts +96 -0
  54. package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
  55. package/dist/core/viewport/viewport.d.ts +11 -0
  56. package/{src/index.ts → dist/index.d.ts} +0 -4
  57. package/dist/index.js +5143 -0
  58. package/dist/index.mjs +5111 -0
  59. package/dist/styles.css +254 -0
  60. package/dist/styles.css.map +1 -0
  61. package/package.json +5 -1
  62. package/.cursorrules +0 -117
  63. package/AI.md +0 -39
  64. package/CLAUDE.md +0 -882
  65. package/build.js +0 -377
  66. package/scripts/analyze-orphaned-functions.ts +0 -387
  67. package/scripts/debug/vlist-selection.ts +0 -121
  68. package/src/components/vlist/config.ts +0 -323
  69. package/src/components/vlist/features/api.ts +0 -626
  70. package/src/components/vlist/features/selection.ts +0 -436
  71. package/src/components/vlist/features/viewport.ts +0 -59
  72. package/src/components/vlist/features.ts +0 -112
  73. package/src/components/vlist/types.ts +0 -723
  74. package/src/components/vlist/vlist.ts +0 -92
  75. package/src/core/compose/features/gestures/index.ts +0 -227
  76. package/src/core/compose/features/gestures/longpress.ts +0 -383
  77. package/src/core/compose/features/gestures/pan.ts +0 -424
  78. package/src/core/compose/features/gestures/pinch.ts +0 -475
  79. package/src/core/compose/features/gestures/rotate.ts +0 -485
  80. package/src/core/compose/features/gestures/swipe.ts +0 -492
  81. package/src/core/compose/features/gestures/tap.ts +0 -334
  82. package/src/core/gestures/longpress.ts +0 -68
  83. package/src/core/gestures/manager.ts +0 -418
  84. package/src/core/gestures/pan.ts +0 -48
  85. package/src/core/gestures/pinch.ts +0 -58
  86. package/src/core/gestures/rotate.ts +0 -58
  87. package/src/core/gestures/swipe.ts +0 -66
  88. package/src/core/gestures/tap.ts +0 -45
  89. package/src/core/gestures/types.ts +0 -387
  90. package/src/core/gestures/utils.ts +0 -128
  91. package/src/core/index.ts +0 -43
  92. package/src/core/layout/config.ts +0 -102
  93. package/src/core/layout/index.ts +0 -168
  94. package/src/core/layout/jsx.ts +0 -174
  95. package/src/core/layout/schema.ts +0 -1044
  96. package/src/core/layout/types.ts +0 -95
  97. package/src/core/viewport/constants.ts +0 -145
  98. package/src/core/viewport/features/base.ts +0 -73
  99. package/src/core/viewport/features/collection.ts +0 -1182
  100. package/src/core/viewport/features/events.ts +0 -130
  101. package/src/core/viewport/features/item-size.ts +0 -271
  102. package/src/core/viewport/features/loading.ts +0 -263
  103. package/src/core/viewport/features/momentum.ts +0 -269
  104. package/src/core/viewport/features/performance.ts +0 -161
  105. package/src/core/viewport/features/placeholders.ts +0 -335
  106. package/src/core/viewport/features/rendering.ts +0 -962
  107. package/src/core/viewport/features/scrollbar.ts +0 -434
  108. package/src/core/viewport/features/scrolling.ts +0 -634
  109. package/src/core/viewport/features/utils.ts +0 -94
  110. package/src/core/viewport/features/virtual.ts +0 -525
  111. package/src/core/viewport/types.ts +0 -133
  112. package/src/core/viewport/utils/speed-tracker.ts +0 -79
  113. package/src/core/viewport/viewport.ts +0 -265
  114. package/test/benchmarks/layout/advanced.test.ts +0 -656
  115. package/test/benchmarks/layout/comparison.test.ts +0 -519
  116. package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
  117. package/test/benchmarks/layout/real-components.test.ts +0 -733
  118. package/test/benchmarks/layout/simple.test.ts +0 -321
  119. package/test/benchmarks/layout/stress.test.ts +0 -990
  120. package/test/collection/basic.test.ts +0 -304
  121. package/test/components/vlist-selection.test.ts +0 -240
  122. package/test/components/vlist.test.ts +0 -63
  123. package/test/core/collection/adapter.test.ts +0 -161
  124. package/test/core/collection/collection.test.ts +0 -394
  125. package/test/core/layout/layout.test.ts +0 -201
  126. package/test/utils/dom-helpers.ts +0 -275
  127. package/test/utils/performance-helpers.ts +0 -392
  128. package/tsconfig.json +0 -20
@@ -1,130 +0,0 @@
1
- // src/core/viewport/features/events.ts
2
-
3
- /**
4
- * Events Feature - Centralized event system for viewport
5
- * Provides event emission and subscription for inter-feature communication
6
- */
7
-
8
- import type { ViewportContext, ViewportComponent } from "../types";
9
-
10
- export interface EventsConfig {
11
- debug?: boolean;
12
- }
13
-
14
- /**
15
- * Events feature for viewport
16
- * Centralizes all event handling for viewport features
17
- */
18
- export const withEvents = (config: EventsConfig = {}) => {
19
- return <T extends ViewportContext & ViewportComponent>(component: T): T => {
20
- const { debug = false } = config;
21
-
22
- // Event listeners map
23
- const listeners = new Map<string, Set<Function>>();
24
-
25
- // Emit an event
26
- const emit = (event: string, data?: any) => {
27
- // if (debug) {
28
- // console.log(`[Events] Emit: ${event}`, data);
29
- // }
30
-
31
- const eventListeners = listeners.get(event);
32
- if (eventListeners) {
33
- eventListeners.forEach((listener) => {
34
- try {
35
- listener(data);
36
- } catch (error) {
37
- console.error(`[Events] Error in listener for ${event}:`, error);
38
- }
39
- });
40
- }
41
- };
42
-
43
- // Subscribe to an event
44
- const on = (event: string, handler: Function): (() => void) => {
45
- if (!listeners.has(event)) {
46
- listeners.set(event, new Set());
47
- }
48
-
49
- listeners.get(event)!.add(handler);
50
-
51
- // if (debug) {
52
- // console.log(`[Events] Subscribed to: ${event}`);
53
- // }
54
-
55
- // Return unsubscribe function
56
- return () => {
57
- const eventListeners = listeners.get(event);
58
- if (eventListeners) {
59
- eventListeners.delete(handler);
60
- if (eventListeners.size === 0) {
61
- listeners.delete(event);
62
- }
63
- }
64
- };
65
- };
66
-
67
- // Subscribe to an event once
68
- const once = (event: string, handler: Function): (() => void) => {
69
- const wrappedHandler = (data: any) => {
70
- handler(data);
71
- off(event, wrappedHandler);
72
- };
73
- return on(event, wrappedHandler);
74
- };
75
-
76
- // Unsubscribe from an event
77
- const off = (event: string, handler: Function) => {
78
- const eventListeners = listeners.get(event);
79
- if (eventListeners) {
80
- eventListeners.delete(handler);
81
- if (eventListeners.size === 0) {
82
- listeners.delete(event);
83
- }
84
- }
85
- };
86
-
87
- // Clear all listeners for an event
88
- const clear = (event?: string) => {
89
- if (event) {
90
- listeners.delete(event);
91
- } else {
92
- listeners.clear();
93
- }
94
- };
95
-
96
- // Add event methods to component
97
- component.emit = emit;
98
- component.on = on;
99
- component.once = once;
100
- component.off = off;
101
-
102
- // Add events API to viewport
103
- (component.viewport as any).events = {
104
- emit,
105
- on,
106
- once,
107
- off,
108
- clear,
109
- getListenerCount: (event?: string) => {
110
- if (event) {
111
- return listeners.get(event)?.size || 0;
112
- }
113
- let total = 0;
114
- listeners.forEach((set) => (total += set.size));
115
- return total;
116
- },
117
- };
118
-
119
- // Clean up on destroy
120
- if ("destroy" in component && typeof component.destroy === "function") {
121
- const originalDestroy = component.destroy;
122
- component.destroy = () => {
123
- clear();
124
- originalDestroy?.();
125
- };
126
- }
127
-
128
- return component;
129
- };
130
- };
@@ -1,271 +0,0 @@
1
- /**
2
- * Item Size Management
3
- * Handles item measurement, caching, and size estimation for virtual scrolling
4
- * Works with both vertical (height) and horizontal (width) orientations
5
- */
6
-
7
- export interface ItemSizeManager {
8
- // Measurement
9
- measureItem(
10
- element: HTMLElement,
11
- index: number,
12
- orientation?: "vertical" | "horizontal",
13
- ): number;
14
-
15
- // Cache management
16
- hasMeasuredSize(index: number): boolean;
17
- getMeasuredSize(index: number): number;
18
- getMeasuredSizes(): Map<number, number>;
19
- cacheItemSize(index: number, size: number): void;
20
- clearCache(): void;
21
-
22
- // Size estimation
23
- getItemSize(): number;
24
- updateItemSize(): void;
25
-
26
- // Additional utilities
27
- calculateTotalSize(totalItems?: number): number;
28
- getStats(): any;
29
-
30
- // Callbacks
31
- onSizeUpdated?: (totalSize: number) => void;
32
- onItemSizeChanged?: (newEstimate: number) => void;
33
- }
34
-
35
- export interface ItemSizeConfig {
36
- initialEstimate?: number;
37
- orientation?: "vertical" | "horizontal";
38
- cacheSize?: number;
39
- onSizeUpdated?: (totalSize: number) => void;
40
- onItemSizeChanged?: (newEstimate: number) => void;
41
- }
42
-
43
- /**
44
- * Creates an item size manager for measuring and caching item dimensions
45
- */
46
- export const createItemSizeManager = (
47
- config: ItemSizeConfig = {},
48
- ): ItemSizeManager => {
49
- const {
50
- initialEstimate = 60,
51
- orientation = "vertical",
52
- cacheSize = 1000,
53
- onSizeUpdated,
54
- onItemSizeChanged,
55
- } = config;
56
-
57
- // Size cache - stores actual measured sizes
58
- const measuredSizes = new Map<number, number>();
59
- let currentItemSize = initialEstimate;
60
-
61
- // Batching state for performance optimization
62
- let batchUpdateTimeout: number | null = null;
63
- let pendingMeasurements = 0;
64
- let batchStartTime = 0;
65
-
66
- /**
67
- * Cache a specific item size with cache size management
68
- */
69
- const cacheItemSize = (index: number, size: number): void => {
70
- if (measuredSizes.size >= cacheSize) {
71
- // Remove oldest entries (10% of cache size)
72
- const entries = Array.from(measuredSizes.entries());
73
- entries.slice(0, Math.floor(cacheSize * 0.1)).forEach(([key]) => {
74
- measuredSizes.delete(key);
75
- });
76
- }
77
- measuredSizes.set(index, size);
78
- };
79
-
80
- /**
81
- * Trigger batched updates after measurements are complete
82
- */
83
- const triggerBatchedUpdates = (): void => {
84
- // Update estimated size based on all measurements
85
- updateItemSize();
86
-
87
- // Notify about total size update
88
- if (onSizeUpdated) {
89
- const totalSize = calculateTotalSize();
90
- onSizeUpdated(totalSize);
91
- }
92
-
93
- // Reset batch state
94
- pendingMeasurements = 0;
95
- batchUpdateTimeout = null;
96
- };
97
-
98
- /**
99
- * Schedule batched updates (debounced)
100
- */
101
- const scheduleBatchedUpdates = (): void => {
102
- if (batchUpdateTimeout) {
103
- clearTimeout(batchUpdateTimeout);
104
- }
105
-
106
- // Short timeout to batch rapid measurements
107
- batchUpdateTimeout = window.setTimeout(() => {
108
- const batchDuration = Date.now() - batchStartTime;
109
- triggerBatchedUpdates();
110
- }, 16); // ~1 frame delay to batch measurements
111
- };
112
-
113
- /**
114
- * Measure actual item size and update cache (with batching)
115
- */
116
- const measureItem = (
117
- element: HTMLElement,
118
- index: number,
119
- measureOrientation?: "vertical" | "horizontal",
120
- ): number => {
121
- if (!element || index < 0) {
122
- return currentItemSize;
123
- }
124
-
125
- const actualOrientation = measureOrientation || orientation;
126
- const size =
127
- actualOrientation === "vertical"
128
- ? element.offsetHeight
129
- : element.offsetWidth;
130
-
131
- if (size > 0) {
132
- const previousSize = measuredSizes.get(index);
133
- cacheItemSize(index, size);
134
-
135
- // Track batch state
136
- if (pendingMeasurements === 0) {
137
- batchStartTime = Date.now();
138
- }
139
- pendingMeasurements++;
140
-
141
- // Schedule batched updates instead of immediate callbacks
142
- scheduleBatchedUpdates();
143
-
144
- return size;
145
- }
146
-
147
- return currentItemSize;
148
- };
149
-
150
- /**
151
- * Update estimated item size based on measured sizes (with change threshold)
152
- */
153
- const updateItemSize = (): void => {
154
- if (measuredSizes.size === 0) return;
155
-
156
- const sizes = Array.from(measuredSizes.values());
157
- const average = sizes.reduce((sum, size) => sum + size, 0) / sizes.length;
158
- const newEstimate = Math.max(1, Math.round(average));
159
-
160
- // Only update if the change is significant (>2px or >5% change)
161
- const changeThreshold = Math.max(2, Math.round(currentItemSize * 0.05));
162
- const absoluteChange = Math.abs(newEstimate - currentItemSize);
163
-
164
- if (absoluteChange >= changeThreshold) {
165
- const previousEstimate = currentItemSize;
166
- currentItemSize = newEstimate;
167
-
168
- if (onItemSizeChanged) {
169
- onItemSizeChanged(newEstimate);
170
- }
171
- } else if (absoluteChange > 0) {
172
- // Silent update for small changes
173
- currentItemSize = newEstimate;
174
- }
175
- };
176
-
177
- /**
178
- * Calculate total size for a given number of items
179
- */
180
- const calculateTotalSize = (totalItems?: number): number => {
181
- if (!totalItems) {
182
- // Calculate based on measured items only
183
- return Array.from(measuredSizes.values()).reduce(
184
- (sum, size) => sum + size,
185
- 0,
186
- );
187
- }
188
-
189
- let totalSize = 0;
190
- for (let i = 0; i < totalItems; i++) {
191
- totalSize += measuredSizes.get(i) || currentItemSize;
192
- }
193
- return totalSize;
194
- };
195
-
196
- /**
197
- * Get size for a specific item (measured or estimated)
198
- */
199
- const getMeasuredSize = (index: number): number => {
200
- return measuredSizes.get(index) || currentItemSize;
201
- };
202
-
203
- /**
204
- * Check if item has been measured
205
- */
206
- const hasMeasuredSize = (index: number): boolean => {
207
- return measuredSizes.has(index);
208
- };
209
-
210
- /**
211
- * Clear all cached measurements
212
- */
213
- const clearCache = (): void => {
214
- const cacheSize = measuredSizes.size;
215
- measuredSizes.clear();
216
- };
217
-
218
- /**
219
- * Get current estimated item size
220
- */
221
- const getItemSize = (): number => {
222
- return currentItemSize;
223
- };
224
-
225
- /**
226
- * Get all measured sizes (for compatibility)
227
- */
228
- const getMeasuredSizes = (): Map<number, number> => {
229
- return new Map(measuredSizes);
230
- };
231
-
232
- /**
233
- * Get cache statistics
234
- */
235
- const getStats = () => {
236
- return {
237
- cachedItems: measuredSizes.size,
238
- estimatedSize: currentItemSize,
239
- cacheSize: cacheSize,
240
- minSize:
241
- measuredSizes.size > 0
242
- ? Math.min(...measuredSizes.values())
243
- : currentItemSize,
244
- maxSize:
245
- measuredSizes.size > 0
246
- ? Math.max(...measuredSizes.values())
247
- : currentItemSize,
248
- orientation: orientation,
249
- };
250
- };
251
-
252
- return {
253
- // Core API
254
- measureItem,
255
- hasMeasuredSize,
256
- getMeasuredSize,
257
- getMeasuredSizes,
258
- clearCache,
259
- getItemSize,
260
- updateItemSize,
261
- cacheItemSize,
262
-
263
- // Additional utilities
264
- calculateTotalSize,
265
- getStats,
266
-
267
- // Callbacks
268
- onSizeUpdated,
269
- onItemSizeChanged,
270
- };
271
- };
@@ -1,263 +0,0 @@
1
- /**
2
- * Loading - Velocity-based intelligent data loading for viewport
3
- *
4
- * This viewport module manages data loading based on scroll velocity.
5
- * When scrolling fast, it cancels loads to prevent server overload.
6
- */
7
-
8
- import type { ItemRange } from "../types";
9
- type ListManagerComponent = any;
10
- import { VIEWPORT_CONSTANTS } from "../constants";
11
-
12
- export interface LoadingConfig {
13
- cancelLoadThreshold?: number; // Velocity (px/ms) above which all loads are cancelled
14
- maxConcurrentRequests?: number;
15
- enableRequestQueue?: boolean;
16
- }
17
-
18
- export interface LoadingManager {
19
- requestLoad(range: ItemRange, priority: "high" | "normal" | "low"): void;
20
- updateVelocity(velocity: number, direction: "forward" | "backward"): void;
21
- cancelPendingLoads(): void;
22
- getStats(): LoadingStats;
23
- isRangeLoading(range: ItemRange): boolean;
24
- }
25
-
26
- interface LoadingStats {
27
- pendingRequests: number;
28
- completedRequests: number;
29
- failedRequests: number;
30
- cancelledRequests: number;
31
- currentVelocity: number;
32
- canLoad: boolean;
33
- queuedRequests: number;
34
- }
35
-
36
- interface QueuedRequest {
37
- range: ItemRange;
38
- priority: "high" | "normal" | "low";
39
- timestamp: number;
40
- }
41
-
42
- /**
43
- * Creates a loading manager that handles data loading based on scroll velocity
44
- */
45
- export const createLoadingManager = (
46
- component: ListManagerComponent,
47
- config: LoadingConfig = {}
48
- ): LoadingManager => {
49
- const {
50
- cancelLoadThreshold = VIEWPORT_CONSTANTS.LOADING.CANCEL_THRESHOLD,
51
- maxConcurrentRequests = VIEWPORT_CONSTANTS.REQUEST_QUEUE
52
- .MAX_ACTIVE_REQUESTS,
53
- enableRequestQueue = VIEWPORT_CONSTANTS.REQUEST_QUEUE.ENABLED,
54
- } = config;
55
-
56
- // State
57
- let currentVelocity = 0;
58
- let scrollDirection: "forward" | "backward" = "forward";
59
- let activeRequests = 0;
60
-
61
- // Request queue
62
- let requestQueue: QueuedRequest[] = [];
63
-
64
- // Track active requests to prevent duplicates
65
- const activeRanges = new Set<string>();
66
-
67
- // Stats
68
- let completedRequests = 0;
69
- let failedRequests = 0;
70
- let cancelledRequests = 0;
71
-
72
- /**
73
- * Get range key for deduplication
74
- */
75
- const getRangeKey = (range: ItemRange): string => {
76
- return `${range.start}-${range.end}`;
77
- };
78
-
79
- /**
80
- * Check if we should load data at current velocity
81
- */
82
- const canLoad = (): boolean => {
83
- return currentVelocity <= cancelLoadThreshold;
84
- };
85
-
86
- /**
87
- * Update current velocity
88
- */
89
- const updateVelocity = (
90
- velocity: number,
91
- direction: "forward" | "backward"
92
- ): void => {
93
- const previousVelocity = currentVelocity;
94
- currentVelocity = Math.abs(velocity);
95
- scrollDirection = direction;
96
-
97
- // When velocity drops below threshold (including reaching zero), process queued requests
98
- if (
99
- previousVelocity > cancelLoadThreshold &&
100
- currentVelocity <= cancelLoadThreshold
101
- ) {
102
- processQueue();
103
- }
104
- };
105
-
106
- /**
107
- * Process the request queue
108
- */
109
- const processQueue = (): void => {
110
- if (!enableRequestQueue) {
111
- return;
112
- }
113
-
114
- let processed = 0;
115
- while (requestQueue.length > 0 && activeRequests < maxConcurrentRequests) {
116
- const request = requestQueue.shift();
117
- if (request) {
118
- executeLoad(request.range, request.priority);
119
- processed++;
120
- }
121
- }
122
- };
123
-
124
- /**
125
- * Request to load a range with priority
126
- */
127
- const requestLoad = (
128
- range: ItemRange,
129
- priority: "high" | "normal" | "low"
130
- ): void => {
131
- const rangeKey = getRangeKey(range);
132
-
133
- // Check if already loading this range
134
- if (activeRanges.has(rangeKey)) {
135
- return;
136
- }
137
-
138
- // Check velocity for all requests
139
- if (!canLoad()) {
140
- // console.log(
141
- // `🚫 [LOADING] Request cancelled - velocity ${currentVelocity.toFixed(
142
- // 2
143
- // )} px/ms exceeds threshold ${cancelLoadThreshold} px/ms`
144
- // );
145
- cancelledRequests++;
146
- return;
147
- }
148
-
149
- // For higher velocities or zero velocity, execute immediately
150
- // If velocity is low, execute immediately
151
- if (activeRequests < maxConcurrentRequests) {
152
- executeLoad(range, priority);
153
- } else if (enableRequestQueue) {
154
- // Add to queue if we're at capacity
155
- requestQueue.push({
156
- range,
157
- priority,
158
- timestamp: Date.now(),
159
- });
160
-
161
- // Enforce max queue size
162
- if (
163
- requestQueue.length > VIEWPORT_CONSTANTS.REQUEST_QUEUE.MAX_QUEUE_SIZE
164
- ) {
165
- const removed = requestQueue.splice(
166
- 0,
167
- requestQueue.length - VIEWPORT_CONSTANTS.REQUEST_QUEUE.MAX_QUEUE_SIZE
168
- );
169
- cancelledRequests += removed.length;
170
- }
171
- } else {
172
- // Queue is disabled and we're at capacity - drop the request
173
-
174
- cancelledRequests++;
175
- }
176
- };
177
-
178
- /**
179
- * Execute load immediately
180
- */
181
- const executeLoad = (
182
- range: ItemRange,
183
- priority: "high" | "normal" | "low"
184
- ): void => {
185
- const rangeKey = getRangeKey(range);
186
-
187
- // Double-check for duplicates
188
- if (activeRanges.has(rangeKey)) {
189
- return;
190
- }
191
-
192
- activeRequests++;
193
- activeRanges.add(rangeKey);
194
-
195
- // Request data from collection
196
- const collection = (component as any).collection;
197
- if (collection && typeof collection.loadMissingRanges === "function") {
198
- collection
199
- .loadMissingRanges(range, "loading:loadRange")
200
- .then(() => {
201
- activeRequests--;
202
- activeRanges.delete(rangeKey);
203
- completedRequests++;
204
- // Process more requests from queue
205
- processQueue();
206
- })
207
- .catch((error: any) => {
208
- activeRequests--;
209
- activeRanges.delete(rangeKey);
210
- failedRequests++;
211
- // Process more requests from queue even on failure
212
- processQueue();
213
- });
214
- } else {
215
- activeRequests--;
216
- activeRanges.delete(rangeKey);
217
- }
218
- };
219
-
220
- /**
221
- * Cancel all pending loads
222
- */
223
- const cancelPendingLoads = (): void => {
224
- const count = requestQueue.length;
225
- if (count > 0) {
226
- cancelledRequests += count;
227
- requestQueue = [];
228
- }
229
- };
230
-
231
- /**
232
- * Check if a range is already being loaded
233
- */
234
- const isRangeLoading = (range: ItemRange): boolean => {
235
- const rangeKey = getRangeKey(range);
236
- return activeRanges.has(rangeKey);
237
- };
238
-
239
- /**
240
- * Get loading statistics
241
- */
242
- const getStats = (): LoadingStats => {
243
- const stats = {
244
- pendingRequests: activeRequests,
245
- completedRequests,
246
- failedRequests,
247
- cancelledRequests,
248
- currentVelocity,
249
- canLoad: canLoad(),
250
- queuedRequests: requestQueue.length,
251
- };
252
-
253
- return stats;
254
- };
255
-
256
- return {
257
- requestLoad,
258
- updateVelocity,
259
- cancelPendingLoads,
260
- getStats,
261
- isRangeLoading,
262
- };
263
- };