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.
- package/{src/components/index.ts → dist/components/index.d.ts} +0 -2
- package/dist/components/vlist/config.d.ts +86 -0
- package/{src/components/vlist/constants.ts → dist/components/vlist/constants.d.ts} +10 -11
- package/dist/components/vlist/features/api.d.ts +7 -0
- package/{src/components/vlist/features/index.ts → dist/components/vlist/features/index.d.ts} +0 -2
- package/dist/components/vlist/features/selection.d.ts +6 -0
- package/dist/components/vlist/features/viewport.d.ts +9 -0
- package/dist/components/vlist/features.d.ts +31 -0
- package/{src/components/vlist/index.ts → dist/components/vlist/index.d.ts} +1 -10
- package/dist/components/vlist/types.d.ts +596 -0
- package/dist/components/vlist/vlist.d.ts +29 -0
- package/dist/core/compose/features/gestures/index.d.ts +86 -0
- package/dist/core/compose/features/gestures/longpress.d.ts +85 -0
- package/dist/core/compose/features/gestures/pan.d.ts +108 -0
- package/dist/core/compose/features/gestures/pinch.d.ts +111 -0
- package/dist/core/compose/features/gestures/rotate.d.ts +111 -0
- package/dist/core/compose/features/gestures/swipe.d.ts +149 -0
- package/dist/core/compose/features/gestures/tap.d.ts +79 -0
- package/{src/core/compose/features/index.ts → dist/core/compose/features/index.d.ts} +1 -2
- package/{src/core/compose/index.ts → dist/core/compose/index.d.ts} +2 -11
- package/{src/core/gestures/index.ts → dist/core/gestures/index.d.ts} +1 -20
- package/dist/core/gestures/longpress.d.ts +23 -0
- package/dist/core/gestures/manager.d.ts +14 -0
- package/dist/core/gestures/pan.d.ts +12 -0
- package/dist/core/gestures/pinch.d.ts +14 -0
- package/dist/core/gestures/rotate.d.ts +14 -0
- package/dist/core/gestures/swipe.d.ts +20 -0
- package/dist/core/gestures/tap.d.ts +12 -0
- package/dist/core/gestures/types.d.ts +320 -0
- package/dist/core/gestures/utils.d.ts +57 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/layout/config.d.ts +33 -0
- package/dist/core/layout/index.d.ts +51 -0
- package/dist/core/layout/jsx.d.ts +65 -0
- package/dist/core/layout/schema.d.ts +112 -0
- package/dist/core/layout/types.d.ts +69 -0
- package/dist/core/viewport/constants.d.ts +105 -0
- package/dist/core/viewport/features/base.d.ts +14 -0
- package/dist/core/viewport/features/collection.d.ts +41 -0
- package/dist/core/viewport/features/events.d.ts +13 -0
- package/{src/core/viewport/features/index.ts → dist/core/viewport/features/index.d.ts} +0 -7
- package/dist/core/viewport/features/item-size.d.ts +30 -0
- package/dist/core/viewport/features/loading.d.ts +34 -0
- package/dist/core/viewport/features/momentum.d.ts +17 -0
- package/dist/core/viewport/features/performance.d.ts +53 -0
- package/dist/core/viewport/features/placeholders.d.ts +38 -0
- package/dist/core/viewport/features/rendering.d.ts +16 -0
- package/dist/core/viewport/features/scrollbar.d.ts +26 -0
- package/dist/core/viewport/features/scrolling.d.ts +16 -0
- package/dist/core/viewport/features/utils.d.ts +43 -0
- package/dist/core/viewport/features/virtual.d.ts +18 -0
- package/{src/core/viewport/index.ts → dist/core/viewport/index.d.ts} +1 -17
- package/dist/core/viewport/types.d.ts +96 -0
- package/dist/core/viewport/utils/speed-tracker.d.ts +22 -0
- package/dist/core/viewport/viewport.d.ts +11 -0
- package/{src/index.ts → dist/index.d.ts} +0 -4
- package/dist/index.js +5143 -0
- package/dist/index.mjs +5111 -0
- package/dist/styles.css +254 -0
- package/dist/styles.css.map +1 -0
- package/package.json +5 -1
- package/.cursorrules +0 -117
- package/AI.md +0 -39
- package/CLAUDE.md +0 -882
- package/build.js +0 -377
- package/scripts/analyze-orphaned-functions.ts +0 -387
- package/scripts/debug/vlist-selection.ts +0 -121
- package/src/components/vlist/config.ts +0 -323
- package/src/components/vlist/features/api.ts +0 -626
- package/src/components/vlist/features/selection.ts +0 -436
- package/src/components/vlist/features/viewport.ts +0 -59
- package/src/components/vlist/features.ts +0 -112
- package/src/components/vlist/types.ts +0 -723
- package/src/components/vlist/vlist.ts +0 -92
- package/src/core/compose/features/gestures/index.ts +0 -227
- package/src/core/compose/features/gestures/longpress.ts +0 -383
- package/src/core/compose/features/gestures/pan.ts +0 -424
- package/src/core/compose/features/gestures/pinch.ts +0 -475
- package/src/core/compose/features/gestures/rotate.ts +0 -485
- package/src/core/compose/features/gestures/swipe.ts +0 -492
- package/src/core/compose/features/gestures/tap.ts +0 -334
- package/src/core/gestures/longpress.ts +0 -68
- package/src/core/gestures/manager.ts +0 -418
- package/src/core/gestures/pan.ts +0 -48
- package/src/core/gestures/pinch.ts +0 -58
- package/src/core/gestures/rotate.ts +0 -58
- package/src/core/gestures/swipe.ts +0 -66
- package/src/core/gestures/tap.ts +0 -45
- package/src/core/gestures/types.ts +0 -387
- package/src/core/gestures/utils.ts +0 -128
- package/src/core/index.ts +0 -43
- package/src/core/layout/config.ts +0 -102
- package/src/core/layout/index.ts +0 -168
- package/src/core/layout/jsx.ts +0 -174
- package/src/core/layout/schema.ts +0 -1044
- package/src/core/layout/types.ts +0 -95
- package/src/core/viewport/constants.ts +0 -145
- package/src/core/viewport/features/base.ts +0 -73
- package/src/core/viewport/features/collection.ts +0 -1182
- package/src/core/viewport/features/events.ts +0 -130
- package/src/core/viewport/features/item-size.ts +0 -271
- package/src/core/viewport/features/loading.ts +0 -263
- package/src/core/viewport/features/momentum.ts +0 -269
- package/src/core/viewport/features/performance.ts +0 -161
- package/src/core/viewport/features/placeholders.ts +0 -335
- package/src/core/viewport/features/rendering.ts +0 -962
- package/src/core/viewport/features/scrollbar.ts +0 -434
- package/src/core/viewport/features/scrolling.ts +0 -634
- package/src/core/viewport/features/utils.ts +0 -94
- package/src/core/viewport/features/virtual.ts +0 -525
- package/src/core/viewport/types.ts +0 -133
- package/src/core/viewport/utils/speed-tracker.ts +0 -79
- package/src/core/viewport/viewport.ts +0 -265
- package/test/benchmarks/layout/advanced.test.ts +0 -656
- package/test/benchmarks/layout/comparison.test.ts +0 -519
- package/test/benchmarks/layout/performance-comparison.test.ts +0 -274
- package/test/benchmarks/layout/real-components.test.ts +0 -733
- package/test/benchmarks/layout/simple.test.ts +0 -321
- package/test/benchmarks/layout/stress.test.ts +0 -990
- package/test/collection/basic.test.ts +0 -304
- package/test/components/vlist-selection.test.ts +0 -240
- package/test/components/vlist.test.ts +0 -63
- package/test/core/collection/adapter.test.ts +0 -161
- package/test/core/collection/collection.test.ts +0 -394
- package/test/core/layout/layout.test.ts +0 -201
- package/test/utils/dom-helpers.ts +0 -275
- package/test/utils/performance-helpers.ts +0 -392
- 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
|
-
};
|