mtrl-addons 0.2.1 → 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 -9
- 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 +6 -1
- package/src/styles/components/_vlist.scss +234 -213
- package/.cursorrules +0 -117
- package/AI.md +0 -241
- package/build.js +0 -201
- 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 -322
- package/src/components/vlist/features/selection.ts +0 -444
- package/src/components/vlist/features/viewport.ts +0 -65
- package/src/components/vlist/features.ts +0 -112
- package/src/components/vlist/types.ts +0 -591
- 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 -1001
- package/src/core/layout/types.ts +0 -95
- package/src/core/viewport/constants.ts +0 -140
- package/src/core/viewport/features/base.ts +0 -73
- package/src/core/viewport/features/collection.ts +0 -882
- 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 -260
- 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 -568
- package/src/core/viewport/features/scrollbar.ts +0 -434
- package/src/core/viewport/features/scrolling.ts +0 -618
- package/src/core/viewport/features/utils.ts +0 -88
- package/src/core/viewport/features/virtual.ts +0 -384
- 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 -246
- 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,384 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Virtual Feature - Core virtual scrolling calculations
|
|
3
|
-
* Handles visible range calculation and total virtual size management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ViewportContext, ViewportComponent } from "../types";
|
|
7
|
-
import { VIEWPORT_CONSTANTS } from "../constants";
|
|
8
|
-
import { wrapInitialize, getViewportState } from "./utils";
|
|
9
|
-
|
|
10
|
-
export interface VirtualConfig {
|
|
11
|
-
itemSize?: number;
|
|
12
|
-
overscan?: number;
|
|
13
|
-
orientation?: "vertical" | "horizontal";
|
|
14
|
-
autoDetectItemSize?: boolean;
|
|
15
|
-
debug?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Virtual scrolling feature for viewport
|
|
20
|
-
* Handles visible range calculations with compression for large datasets
|
|
21
|
-
*/
|
|
22
|
-
export const withVirtual = (config: VirtualConfig = {}) => {
|
|
23
|
-
return <T extends ViewportContext & ViewportComponent>(component: T): T => {
|
|
24
|
-
const {
|
|
25
|
-
itemSize,
|
|
26
|
-
overscan = VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.OVERSCAN_BUFFER,
|
|
27
|
-
orientation = "vertical",
|
|
28
|
-
autoDetectItemSize = itemSize === undefined
|
|
29
|
-
? VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.AUTO_DETECT_ITEM_SIZE
|
|
30
|
-
: false,
|
|
31
|
-
debug = false,
|
|
32
|
-
} = config;
|
|
33
|
-
|
|
34
|
-
// Use provided itemSize or default, but mark if we should auto-detect
|
|
35
|
-
const initialItemSize =
|
|
36
|
-
itemSize || VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.DEFAULT_ITEM_SIZE;
|
|
37
|
-
|
|
38
|
-
const MAX_VIRTUAL_SIZE = VIEWPORT_CONSTANTS.VIRTUAL_SCROLL.MAX_VIRTUAL_SIZE;
|
|
39
|
-
let viewportState: any;
|
|
40
|
-
let hasCalculatedItemSize = false;
|
|
41
|
-
|
|
42
|
-
// Initialize using shared wrapper
|
|
43
|
-
wrapInitialize(component, () => {
|
|
44
|
-
viewportState = getViewportState(component);
|
|
45
|
-
if (!viewportState) return;
|
|
46
|
-
|
|
47
|
-
Object.assign(viewportState, {
|
|
48
|
-
itemSize: initialItemSize,
|
|
49
|
-
overscan,
|
|
50
|
-
containerSize:
|
|
51
|
-
viewportState.viewportElement?.[
|
|
52
|
-
orientation === "horizontal" ? "offsetWidth" : "offsetHeight"
|
|
53
|
-
] || 600,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
updateTotalVirtualSize(viewportState.totalItems);
|
|
57
|
-
updateVisibleRange(viewportState.scrollPosition || 0);
|
|
58
|
-
|
|
59
|
-
// Ensure container size is measured after DOM is ready
|
|
60
|
-
requestAnimationFrame(() => {
|
|
61
|
-
updateContainerSize();
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Helper functions
|
|
66
|
-
const getCompressionRatio = (): number => {
|
|
67
|
-
if (!viewportState?.virtualTotalSize) return 1;
|
|
68
|
-
const actualSize = viewportState.totalItems * viewportState.itemSize;
|
|
69
|
-
return actualSize <= MAX_VIRTUAL_SIZE ? 1 : MAX_VIRTUAL_SIZE / actualSize;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const log = (message: string, data?: any) => {
|
|
73
|
-
if (debug) console.log(`[Virtual] ${message}`, data);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Calculate visible range
|
|
77
|
-
const calculateVisibleRange = (
|
|
78
|
-
scrollPosition: number
|
|
79
|
-
): { start: number; end: number } => {
|
|
80
|
-
if (!viewportState) {
|
|
81
|
-
console.warn("[Virtual] No viewport state, returning empty range");
|
|
82
|
-
return { start: 0, end: 0 };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const { containerSize, totalItems } = viewportState;
|
|
86
|
-
|
|
87
|
-
// Early returns for invalid states
|
|
88
|
-
if (
|
|
89
|
-
!containerSize ||
|
|
90
|
-
containerSize <= 0 ||
|
|
91
|
-
!totalItems ||
|
|
92
|
-
totalItems <= 0
|
|
93
|
-
) {
|
|
94
|
-
log(`Invalid state: container=${containerSize}, items=${totalItems}`);
|
|
95
|
-
return { start: 0, end: 0 };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const virtualSize =
|
|
99
|
-
viewportState.virtualTotalSize || totalItems * viewportState.itemSize;
|
|
100
|
-
const visibleCount = Math.ceil(containerSize / viewportState.itemSize);
|
|
101
|
-
const compressionRatio = getCompressionRatio();
|
|
102
|
-
|
|
103
|
-
let start: number, end: number;
|
|
104
|
-
|
|
105
|
-
if (compressionRatio < 1) {
|
|
106
|
-
// Compressed space calculation
|
|
107
|
-
const scrollRatio = scrollPosition / virtualSize;
|
|
108
|
-
const exactIndex = scrollRatio * totalItems;
|
|
109
|
-
start = Math.floor(exactIndex);
|
|
110
|
-
end = Math.ceil(exactIndex) + visibleCount;
|
|
111
|
-
|
|
112
|
-
// Near-bottom handling
|
|
113
|
-
const maxScroll = virtualSize - containerSize;
|
|
114
|
-
const distanceFromBottom = maxScroll - scrollPosition;
|
|
115
|
-
|
|
116
|
-
if (distanceFromBottom <= containerSize && distanceFromBottom >= -1) {
|
|
117
|
-
const itemsAtBottom = Math.floor(
|
|
118
|
-
containerSize / viewportState.itemSize
|
|
119
|
-
);
|
|
120
|
-
const firstVisibleAtBottom = Math.max(0, totalItems - itemsAtBottom);
|
|
121
|
-
const interpolation = Math.max(
|
|
122
|
-
0,
|
|
123
|
-
Math.min(1, 1 - distanceFromBottom / containerSize)
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
start = Math.floor(
|
|
127
|
-
start + (firstVisibleAtBottom - start) * interpolation
|
|
128
|
-
);
|
|
129
|
-
end =
|
|
130
|
-
distanceFromBottom <= 1
|
|
131
|
-
? totalItems - 1
|
|
132
|
-
: Math.min(totalItems - 1, start + visibleCount + overscan);
|
|
133
|
-
|
|
134
|
-
log("Near bottom calculation:", {
|
|
135
|
-
distanceFromBottom,
|
|
136
|
-
interpolation,
|
|
137
|
-
start,
|
|
138
|
-
end,
|
|
139
|
-
scrollPosition,
|
|
140
|
-
totalItems,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Apply overscan
|
|
145
|
-
start = Math.max(0, start - overscan);
|
|
146
|
-
end = Math.min(totalItems - 1, end + overscan);
|
|
147
|
-
} else {
|
|
148
|
-
// Direct calculation
|
|
149
|
-
start = Math.max(
|
|
150
|
-
0,
|
|
151
|
-
Math.floor(scrollPosition / viewportState.itemSize) - overscan
|
|
152
|
-
);
|
|
153
|
-
end = Math.min(totalItems - 1, start + visibleCount + overscan * 2);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Validate output
|
|
157
|
-
if (isNaN(start) || isNaN(end)) {
|
|
158
|
-
console.error("[Virtual] NaN in range calculation:", {
|
|
159
|
-
scrollPosition,
|
|
160
|
-
containerSize,
|
|
161
|
-
totalItems,
|
|
162
|
-
itemSize: viewportState.itemSize,
|
|
163
|
-
compressionRatio,
|
|
164
|
-
});
|
|
165
|
-
return { start: 0, end: 0 };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// log(`Range: ${start}-${end} (scroll: ${scrollPosition})`);
|
|
169
|
-
|
|
170
|
-
// Strategic log for last range
|
|
171
|
-
// if (end >= totalItems - 10) {
|
|
172
|
-
// console.log(
|
|
173
|
-
// `[Virtual] Near end range: ${start}-${end}, totalItems=${totalItems}, lastItemPos=${
|
|
174
|
-
// end * viewportState.itemSize
|
|
175
|
-
// }px, virtualSize=${viewportState.virtualTotalSize}px`
|
|
176
|
-
// );
|
|
177
|
-
// }
|
|
178
|
-
|
|
179
|
-
return { start, end };
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
// Update functions
|
|
183
|
-
const updateVisibleRange = (scrollPosition: number) => {
|
|
184
|
-
if (!viewportState) return;
|
|
185
|
-
viewportState.visibleRange = calculateVisibleRange(scrollPosition);
|
|
186
|
-
component.emit?.("viewport:range-changed", {
|
|
187
|
-
range: viewportState.visibleRange,
|
|
188
|
-
scrollPosition,
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Update total virtual size
|
|
193
|
-
const updateTotalVirtualSize = (totalItems: number) => {
|
|
194
|
-
if (!viewportState) return;
|
|
195
|
-
|
|
196
|
-
const oldSize = viewportState.virtualTotalSize;
|
|
197
|
-
viewportState.totalItems = totalItems;
|
|
198
|
-
const actualSize = totalItems * viewportState.itemSize;
|
|
199
|
-
|
|
200
|
-
// Get padding from the items container if available
|
|
201
|
-
let totalPadding = 0;
|
|
202
|
-
if (viewportState.itemsContainer) {
|
|
203
|
-
const computedStyle = window.getComputedStyle(
|
|
204
|
-
viewportState.itemsContainer
|
|
205
|
-
);
|
|
206
|
-
const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
|
|
207
|
-
const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
|
|
208
|
-
totalPadding = paddingTop + paddingBottom;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Include padding in the virtual size
|
|
212
|
-
viewportState.virtualTotalSize = Math.min(
|
|
213
|
-
actualSize + totalPadding,
|
|
214
|
-
MAX_VIRTUAL_SIZE
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// Strategic log for debugging gap issue
|
|
218
|
-
// console.log(
|
|
219
|
-
// `[Virtual] Total size update: items=${totalItems}, itemSize=${viewportState.itemSize}px, padding=${totalPadding}px, virtualSize=${viewportState.virtualTotalSize}px (was ${oldSize}px)`
|
|
220
|
-
// );
|
|
221
|
-
|
|
222
|
-
component.emit?.("viewport:virtual-size-changed", {
|
|
223
|
-
totalVirtualSize: viewportState.virtualTotalSize,
|
|
224
|
-
totalItems: totalItems,
|
|
225
|
-
compressionRatio: getCompressionRatio(),
|
|
226
|
-
});
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Update container size
|
|
230
|
-
const updateContainerSize = () => {
|
|
231
|
-
if (!viewportState || !viewportState.viewportElement) return;
|
|
232
|
-
|
|
233
|
-
const size =
|
|
234
|
-
viewportState.viewportElement[
|
|
235
|
-
viewportState.orientation === "horizontal"
|
|
236
|
-
? "offsetWidth"
|
|
237
|
-
: "offsetHeight"
|
|
238
|
-
];
|
|
239
|
-
|
|
240
|
-
// Log the actual measurement
|
|
241
|
-
// console.log(
|
|
242
|
-
// `[Virtual] Container size measured: ${size}px from viewport element`
|
|
243
|
-
// );
|
|
244
|
-
|
|
245
|
-
if (size !== viewportState.containerSize) {
|
|
246
|
-
viewportState.containerSize = size;
|
|
247
|
-
updateVisibleRange(viewportState.scrollPosition || 0);
|
|
248
|
-
updateTotalVirtualSize(viewportState.totalItems); // Also update virtual size
|
|
249
|
-
component.emit?.("viewport:container-size-changed", {
|
|
250
|
-
containerSize: size,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// Override viewport methods
|
|
256
|
-
component.viewport.getVisibleRange = () =>
|
|
257
|
-
viewportState?.visibleRange ||
|
|
258
|
-
calculateVisibleRange(viewportState?.scrollPosition || 0);
|
|
259
|
-
|
|
260
|
-
// Event listeners
|
|
261
|
-
component.on?.("viewport:scroll", (data: any) =>
|
|
262
|
-
updateVisibleRange(data.position)
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
component.on?.("viewport:items-changed", (data: any) => {
|
|
266
|
-
if (data.totalItems !== undefined) {
|
|
267
|
-
updateTotalVirtualSize(data.totalItems);
|
|
268
|
-
updateVisibleRange(viewportState?.scrollPosition || 0);
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
// Listen for total items changes (important for cursor pagination)
|
|
273
|
-
component.on?.("viewport:total-items-changed", (data: any) => {
|
|
274
|
-
if (
|
|
275
|
-
data.total !== undefined &&
|
|
276
|
-
data.total !== viewportState?.totalItems
|
|
277
|
-
) {
|
|
278
|
-
log(
|
|
279
|
-
`Total items changed from ${viewportState?.totalItems} to ${data.total}`
|
|
280
|
-
);
|
|
281
|
-
updateTotalVirtualSize(data.total);
|
|
282
|
-
updateVisibleRange(viewportState?.scrollPosition || 0);
|
|
283
|
-
|
|
284
|
-
// Trigger a render to update the view
|
|
285
|
-
component.viewport?.renderItems?.();
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// Listen for container size changes to recalculate virtual size
|
|
290
|
-
component.on?.("viewport:container-size-changed", (data: any) => {
|
|
291
|
-
if (viewportState && data.containerSize !== viewportState.containerSize) {
|
|
292
|
-
viewportState.containerSize = data.containerSize;
|
|
293
|
-
// Recalculate virtual size with new container size
|
|
294
|
-
updateTotalVirtualSize(viewportState.totalItems);
|
|
295
|
-
updateVisibleRange(viewportState.scrollPosition || 0);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
component.on?.("collection:range-loaded", (data: any) => {
|
|
300
|
-
if (
|
|
301
|
-
data.total !== undefined &&
|
|
302
|
-
data.total !== viewportState?.totalItems
|
|
303
|
-
) {
|
|
304
|
-
updateTotalVirtualSize(data.total);
|
|
305
|
-
}
|
|
306
|
-
updateVisibleRange(viewportState?.scrollPosition || 0);
|
|
307
|
-
component.viewport?.renderItems?.();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Listen for first items rendered to calculate item size (if auto-detection is enabled)
|
|
311
|
-
if (autoDetectItemSize) {
|
|
312
|
-
component.on?.("viewport:items-rendered", (data: any) => {
|
|
313
|
-
if (
|
|
314
|
-
!hasCalculatedItemSize &&
|
|
315
|
-
data.elements?.length > 0 &&
|
|
316
|
-
viewportState
|
|
317
|
-
) {
|
|
318
|
-
// Calculate average item size from first rendered batch
|
|
319
|
-
const sizes: number[] = [];
|
|
320
|
-
const sizeProperty =
|
|
321
|
-
orientation === "horizontal" ? "offsetWidth" : "offsetHeight";
|
|
322
|
-
|
|
323
|
-
data.elements.forEach((element: HTMLElement) => {
|
|
324
|
-
const size = element[sizeProperty];
|
|
325
|
-
if (size > 0) {
|
|
326
|
-
sizes.push(size);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
if (sizes.length > 0) {
|
|
331
|
-
const avgSize = Math.round(
|
|
332
|
-
sizes.reduce((sum, size) => sum + size, 0) / sizes.length
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
// console.log(
|
|
336
|
-
// `[Virtual] Auto-detected item size: ${avgSize}px (was ${viewportState.itemSize}px), based on ${sizes.length} items`
|
|
337
|
-
// );
|
|
338
|
-
viewportState.itemSize = avgSize;
|
|
339
|
-
|
|
340
|
-
// Recalculate everything with new size
|
|
341
|
-
updateTotalVirtualSize(viewportState.totalItems);
|
|
342
|
-
updateVisibleRange(viewportState.scrollPosition || 0);
|
|
343
|
-
|
|
344
|
-
// Re-render to adjust positions
|
|
345
|
-
component.viewport?.renderItems?.();
|
|
346
|
-
|
|
347
|
-
// Emit event for size change
|
|
348
|
-
component.emit?.("viewport:item-size-detected", {
|
|
349
|
-
previousSize: initialItemSize,
|
|
350
|
-
detectedSize: avgSize,
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
hasCalculatedItemSize = true;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Expose virtual API
|
|
360
|
-
const compressionRatio = getCompressionRatio();
|
|
361
|
-
(component as any).virtual = {
|
|
362
|
-
calculateVisibleRange,
|
|
363
|
-
updateTotalVirtualSize,
|
|
364
|
-
getTotalVirtualSize: () => viewportState?.virtualTotalSize || 0,
|
|
365
|
-
getContainerSize: () => viewportState?.containerSize || 0,
|
|
366
|
-
updateContainerSize: (size: number) => {
|
|
367
|
-
if (viewportState) {
|
|
368
|
-
viewportState.containerSize = size;
|
|
369
|
-
updateVisibleRange(viewportState.scrollPosition || 0);
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
getItemSize: () => viewportState?.itemSize || initialItemSize,
|
|
373
|
-
calculateIndexFromPosition: (position: number) =>
|
|
374
|
-
Math.floor(
|
|
375
|
-
position /
|
|
376
|
-
((viewportState?.itemSize || initialItemSize) * compressionRatio)
|
|
377
|
-
),
|
|
378
|
-
calculatePositionForIndex: (index: number) =>
|
|
379
|
-
index * (viewportState?.itemSize || initialItemSize) * compressionRatio,
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
return component;
|
|
383
|
-
};
|
|
384
|
-
};
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// src/core/viewport/types.ts
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Core type definitions for Viewport
|
|
5
|
-
* Virtual scrolling and rendering types
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { CollectionComponent } from "./features/collection";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Core Range and Item Types
|
|
12
|
-
*/
|
|
13
|
-
export interface ItemRange {
|
|
14
|
-
start: number;
|
|
15
|
-
end: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface ViewportInfo {
|
|
19
|
-
containerSize: number;
|
|
20
|
-
totalVirtualSize: number;
|
|
21
|
-
visibleRange: ItemRange;
|
|
22
|
-
virtualScrollPosition: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Viewport context interface - provides context for viewport features
|
|
27
|
-
*/
|
|
28
|
-
export interface ViewportContext {
|
|
29
|
-
element: HTMLElement;
|
|
30
|
-
items: any[];
|
|
31
|
-
totalItems: number;
|
|
32
|
-
template?: (item: any, index: number) => string | HTMLElement;
|
|
33
|
-
emit?: (event: string, data?: any) => void;
|
|
34
|
-
on?: (event: string, handler: Function) => () => void;
|
|
35
|
-
once?: (event: string, handler: Function) => () => void;
|
|
36
|
-
off?: (event: string, handler: Function) => void;
|
|
37
|
-
getClass?: (name: string) => string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Viewport configuration - Feature-oriented structure
|
|
42
|
-
*/
|
|
43
|
-
export interface ViewportConfig {
|
|
44
|
-
// Basic configuration
|
|
45
|
-
element?: HTMLElement;
|
|
46
|
-
className?: string;
|
|
47
|
-
debug?: boolean;
|
|
48
|
-
|
|
49
|
-
// Template for rendering items
|
|
50
|
-
template?: (
|
|
51
|
-
item: any,
|
|
52
|
-
index: number
|
|
53
|
-
) => string | HTMLElement | any[] | Record<string, any>;
|
|
54
|
-
|
|
55
|
-
// Collection/data source configuration
|
|
56
|
-
collection?: {
|
|
57
|
-
adapter?: any; // Data adapter
|
|
58
|
-
transform?: (item: any) => any; // Transform items before rendering
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Pagination configuration
|
|
62
|
-
pagination?: {
|
|
63
|
-
strategy?: "page" | "offset" | "cursor";
|
|
64
|
-
limit?: number; // Items per page/range
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Virtual scrolling configuration
|
|
68
|
-
virtual?: {
|
|
69
|
-
itemSize?: number;
|
|
70
|
-
overscan?: number; // Extra items to render outside viewport
|
|
71
|
-
autoDetectItemSize?: boolean; // Auto-detect item size from first render
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Scrolling configuration
|
|
75
|
-
scrolling?: {
|
|
76
|
-
orientation?: "vertical" | "horizontal";
|
|
77
|
-
animation?: boolean; // Smooth scrolling
|
|
78
|
-
sensitivity?: number; // Scroll sensitivity
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// Scrollbar configuration
|
|
82
|
-
scrollbar?: {
|
|
83
|
-
enabled?: boolean;
|
|
84
|
-
autoHide?: boolean;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Performance configuration
|
|
88
|
-
performance?: {
|
|
89
|
-
maxConcurrentRequests?: number;
|
|
90
|
-
cancelLoadThreshold?: number; // Velocity threshold for cancelling loads
|
|
91
|
-
enableRequestQueue?: boolean;
|
|
92
|
-
recycleElements?: boolean;
|
|
93
|
-
bufferSize?: number;
|
|
94
|
-
renderDebounce?: number;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Placeholder configuration
|
|
98
|
-
placeholders?: {
|
|
99
|
-
enabled?: boolean;
|
|
100
|
-
template?: (index: number) => string | HTMLElement;
|
|
101
|
-
maskCharacter?: string;
|
|
102
|
-
analyzeFirstLoad?: boolean;
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Enhanced component after viewport is applied
|
|
108
|
-
*/
|
|
109
|
-
export interface ViewportComponent extends ViewportContext {
|
|
110
|
-
viewport: {
|
|
111
|
-
// Core API
|
|
112
|
-
initialize(): void;
|
|
113
|
-
destroy(): void;
|
|
114
|
-
updateViewport(): void;
|
|
115
|
-
|
|
116
|
-
// Scrolling
|
|
117
|
-
scrollToIndex(index: number, alignment?: "start" | "center" | "end"): void;
|
|
118
|
-
scrollToPosition(position: number): void;
|
|
119
|
-
getScrollPosition(): number;
|
|
120
|
-
scrollBy(delta: number): void;
|
|
121
|
-
getVelocity(): number;
|
|
122
|
-
|
|
123
|
-
// Collection feature (optional)
|
|
124
|
-
collection?: CollectionComponent["collection"];
|
|
125
|
-
|
|
126
|
-
// Info
|
|
127
|
-
getVisibleRange(): ItemRange;
|
|
128
|
-
getViewportInfo(): ViewportInfo;
|
|
129
|
-
|
|
130
|
-
// Rendering
|
|
131
|
-
renderItems(): void;
|
|
132
|
-
};
|
|
133
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// src/core/viewport/utils/speed-tracker.ts
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Speed tracking utility for viewport
|
|
5
|
-
* Measures velocity and determines loading strategies
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { VIEWPORT_CONSTANTS } from "../constants";
|
|
9
|
-
|
|
10
|
-
export interface SpeedTracker {
|
|
11
|
-
velocity: number;
|
|
12
|
-
direction: "forward" | "backward";
|
|
13
|
-
isAccelerating: boolean;
|
|
14
|
-
lastMeasurement: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Create a new speed tracker instance
|
|
19
|
-
*/
|
|
20
|
-
export function createSpeedTracker(): SpeedTracker {
|
|
21
|
-
return {
|
|
22
|
-
velocity: 0,
|
|
23
|
-
direction: "forward",
|
|
24
|
-
isAccelerating: false,
|
|
25
|
-
lastMeasurement: Date.now(),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Update speed tracker with new scroll position
|
|
31
|
-
*/
|
|
32
|
-
export function updateSpeedTracker(
|
|
33
|
-
tracker: SpeedTracker,
|
|
34
|
-
newPosition: number,
|
|
35
|
-
previousPosition: number
|
|
36
|
-
): SpeedTracker {
|
|
37
|
-
const now = Date.now();
|
|
38
|
-
const deltaTime = now - tracker.lastMeasurement;
|
|
39
|
-
const deltaPosition = newPosition - previousPosition;
|
|
40
|
-
|
|
41
|
-
// Calculate velocity (px/ms)
|
|
42
|
-
const currentVelocity =
|
|
43
|
-
deltaTime > 0 ? Math.abs(deltaPosition) / deltaTime : 0;
|
|
44
|
-
|
|
45
|
-
// Determine direction
|
|
46
|
-
const direction = deltaPosition >= 0 ? "forward" : "backward";
|
|
47
|
-
|
|
48
|
-
// Check if accelerating
|
|
49
|
-
const isAccelerating = currentVelocity > tracker.velocity;
|
|
50
|
-
|
|
51
|
-
// Apply smoothing for more stable velocity tracking
|
|
52
|
-
const smoothingFactor = 0.85;
|
|
53
|
-
const smoothedVelocity =
|
|
54
|
-
tracker.velocity * smoothingFactor +
|
|
55
|
-
currentVelocity * (1 - smoothingFactor);
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
velocity: smoothedVelocity,
|
|
59
|
-
direction,
|
|
60
|
-
isAccelerating,
|
|
61
|
-
lastMeasurement: now,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Determine if scroll speed is fast (should defer loading)
|
|
67
|
-
*/
|
|
68
|
-
export function isFastScrolling(tracker: SpeedTracker): boolean {
|
|
69
|
-
return tracker.velocity > VIEWPORT_CONSTANTS.LOADING.CANCEL_THRESHOLD;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get loading strategy based on current speed
|
|
74
|
-
*/
|
|
75
|
-
export function getLoadingStrategy(
|
|
76
|
-
tracker: SpeedTracker
|
|
77
|
-
): "defer" | "immediate" {
|
|
78
|
-
return isFastScrolling(tracker) ? "defer" : "immediate";
|
|
79
|
-
}
|