mtrl-addons 0.1.1 → 0.2.1
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/build.js +139 -108
- package/package.json +13 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +322 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +444 -0
- package/src/components/vlist/features/viewport.ts +65 -0
- package/src/components/vlist/index.ts +16 -0
- package/src/components/{list → vlist}/types.ts +104 -26
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +73 -35
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +140 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +882 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +27 -30
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +260 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +568 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +618 -0
- package/src/core/viewport/features/utils.ts +88 -0
- package/src/core/viewport/features/virtual.ts +384 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +246 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +331 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -14
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,654 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Viewport Feature - Complete Virtual Scrolling Enhancer
|
|
3
|
-
* Handles orientation, virtual scrolling, custom scrollbar, and item rendering
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
ListManagerComponent,
|
|
8
|
-
ItemRange,
|
|
9
|
-
ViewportInfo,
|
|
10
|
-
} from "../../types";
|
|
11
|
-
import { LIST_MANAGER_CONSTANTS } from "../../constants";
|
|
12
|
-
// Removed calculateViewportInfoUtil import - using virtualManager directly
|
|
13
|
-
import { getDefaultTemplate } from "./template";
|
|
14
|
-
import { createItemSizeManager, type ItemSizeManager } from "./item-size";
|
|
15
|
-
import { createScrollingManager, type ScrollingManager } from "./scrolling";
|
|
16
|
-
import { createVirtualManager, type VirtualManager } from "./virtual";
|
|
17
|
-
import { createRenderingManager, type RenderingManager } from "./rendering";
|
|
18
|
-
import { scrollbar as createScrollbar } from "./scrollbar";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Configuration for viewport enhancer
|
|
22
|
-
*/
|
|
23
|
-
export interface ViewportConfig {
|
|
24
|
-
orientation?: "vertical" | "horizontal";
|
|
25
|
-
estimatedItemSize?: number;
|
|
26
|
-
overscan?: number;
|
|
27
|
-
enableScrollbar?: boolean;
|
|
28
|
-
loadDataForRange?: (
|
|
29
|
-
range: { start: number; end: number },
|
|
30
|
-
priority?: "high" | "normal" | "low"
|
|
31
|
-
) => void;
|
|
32
|
-
measureItems?: boolean; // Add measureItems flag
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Component interface after viewport enhancement
|
|
37
|
-
*/
|
|
38
|
-
export interface ViewportComponent {
|
|
39
|
-
viewport: {
|
|
40
|
-
// Virtual scrolling
|
|
41
|
-
getScrollPosition(): number;
|
|
42
|
-
getContainerSize(): number;
|
|
43
|
-
getTotalVirtualSize(): number;
|
|
44
|
-
getVisibleRange(): ItemRange;
|
|
45
|
-
getViewportInfo(): ViewportInfo;
|
|
46
|
-
|
|
47
|
-
// Navigation - Visual positioning only
|
|
48
|
-
scrollToPosition(position: number): void;
|
|
49
|
-
scrollToIndex(index: number, alignment?: "start" | "center" | "end"): void;
|
|
50
|
-
|
|
51
|
-
// Item sizing
|
|
52
|
-
measureItemSize(element: HTMLElement, index: number): number;
|
|
53
|
-
hasMeasuredSize(index: number): boolean;
|
|
54
|
-
getMeasuredSize(index: number): number;
|
|
55
|
-
getEstimatedItemSize(): number;
|
|
56
|
-
|
|
57
|
-
// Rendering
|
|
58
|
-
renderItems(): void;
|
|
59
|
-
updateItemPositions(): void;
|
|
60
|
-
getRenderedElements(): Map<number, HTMLElement>;
|
|
61
|
-
|
|
62
|
-
// State
|
|
63
|
-
getOrientation(): "vertical" | "horizontal";
|
|
64
|
-
isInitialized(): boolean;
|
|
65
|
-
|
|
66
|
-
// Manual updates
|
|
67
|
-
updateContainerPosition(): void;
|
|
68
|
-
updateScrollbar(): void;
|
|
69
|
-
updateViewport(): void;
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Adds viewport functionality to a List Manager component
|
|
75
|
-
*
|
|
76
|
-
* @param config - Viewport configuration
|
|
77
|
-
* @returns Function that enhances a component with viewport capabilities
|
|
78
|
-
*/
|
|
79
|
-
export const withViewport =
|
|
80
|
-
(config: ViewportConfig = {}) =>
|
|
81
|
-
<T extends ListManagerComponent>(component: T): T & ViewportComponent => {
|
|
82
|
-
// Configuration with defaults
|
|
83
|
-
const orientation = config.orientation || "vertical";
|
|
84
|
-
const estimatedItemSize = 84;
|
|
85
|
-
// const estimatedItemSize =
|
|
86
|
-
// config.estimatedItemSize ||
|
|
87
|
-
// LIST_MANAGER_CONSTANTS.VIRTUAL_SCROLL.DEFAULT_ITEM_SIZE;
|
|
88
|
-
const overscan =
|
|
89
|
-
config.overscan || LIST_MANAGER_CONSTANTS.VIRTUAL_SCROLL.OVERSCAN_BUFFER;
|
|
90
|
-
const enableScrollbar = config.enableScrollbar !== false;
|
|
91
|
-
const measureItems = config.measureItems === true; // Default to false
|
|
92
|
-
|
|
93
|
-
// Items container for virtual positioning
|
|
94
|
-
let itemsContainer: HTMLElement | null = null;
|
|
95
|
-
|
|
96
|
-
// Viewport element reference
|
|
97
|
-
let viewportElement: HTMLElement | null = null;
|
|
98
|
-
|
|
99
|
-
// State
|
|
100
|
-
let isViewportInitialized = false;
|
|
101
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
102
|
-
|
|
103
|
-
// Store the correct total items value from collection layer
|
|
104
|
-
let actualTotalItems = component.totalItems;
|
|
105
|
-
|
|
106
|
-
// Scrollbar plugin reference
|
|
107
|
-
let scrollbarPlugin: any = null;
|
|
108
|
-
|
|
109
|
-
// Item size management
|
|
110
|
-
const itemSizeManager = createItemSizeManager({
|
|
111
|
-
initialEstimate: estimatedItemSize,
|
|
112
|
-
orientation,
|
|
113
|
-
onSizeUpdated: (newTotalSize) => {
|
|
114
|
-
// DON'T update virtual size from measured items - this breaks height capping!
|
|
115
|
-
// The virtual size should only be managed by virtualManager.updateTotalVirtualSize()
|
|
116
|
-
// Note: Ignoring onSizeUpdated to prevent virtual size reset from measured items
|
|
117
|
-
scrollingManager.updateScrollbar();
|
|
118
|
-
// Emit current state, not the reset state
|
|
119
|
-
component.emit?.("dimensions:changed", {
|
|
120
|
-
containerSize: virtualManager.getState().containerSize,
|
|
121
|
-
totalVirtualSize: virtualManager.getState().totalVirtualSize,
|
|
122
|
-
estimatedItemSize: itemSizeManager.getEstimatedItemSize(),
|
|
123
|
-
});
|
|
124
|
-
},
|
|
125
|
-
onEstimatedSizeChanged: (newEstimate) => {
|
|
126
|
-
component.emit?.("estimated-size:changed", {
|
|
127
|
-
previousEstimate: estimatedItemSize,
|
|
128
|
-
newEstimate,
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Virtual scrolling manager
|
|
134
|
-
const virtualManager = createVirtualManager(
|
|
135
|
-
component,
|
|
136
|
-
itemSizeManager,
|
|
137
|
-
{
|
|
138
|
-
orientation,
|
|
139
|
-
overscan,
|
|
140
|
-
onDimensionsChanged: (data) => {
|
|
141
|
-
scrollingManager.updateState({
|
|
142
|
-
totalVirtualSize: data.totalVirtualSize,
|
|
143
|
-
containerSize: data.containerSize,
|
|
144
|
-
});
|
|
145
|
-
scrollingManager.updateScrollbar();
|
|
146
|
-
component.emit?.("dimensions:changed", data);
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
() => actualTotalItems
|
|
150
|
-
); // Pass the callback to get actual total items
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check for missing data in a specific range and trigger loading if needed
|
|
154
|
-
*/
|
|
155
|
-
const checkForMissingData = (targetRange: {
|
|
156
|
-
start: number;
|
|
157
|
-
end: number;
|
|
158
|
-
}): void => {
|
|
159
|
-
// Try immediate detection first
|
|
160
|
-
const collection = (component as any).collection;
|
|
161
|
-
const hasCollection = !!collection;
|
|
162
|
-
const hasLoadMissingRanges =
|
|
163
|
-
hasCollection && typeof collection.loadMissingRanges === "function";
|
|
164
|
-
|
|
165
|
-
if (hasLoadMissingRanges) {
|
|
166
|
-
// Improved missing data detection - check for null/undefined items in range
|
|
167
|
-
let missingCount = 0;
|
|
168
|
-
const missingIndices: number[] = [];
|
|
169
|
-
|
|
170
|
-
for (let i = targetRange.start; i <= targetRange.end; i++) {
|
|
171
|
-
// Skip if index is beyond actual total items
|
|
172
|
-
if (i >= actualTotalItems) {
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Check if item is missing (null, undefined, or array doesn't extend to this index)
|
|
177
|
-
const itemExists =
|
|
178
|
-
i < component.items.length &&
|
|
179
|
-
component.items[i] !== null &&
|
|
180
|
-
component.items[i] !== undefined;
|
|
181
|
-
|
|
182
|
-
if (!itemExists) {
|
|
183
|
-
missingCount++;
|
|
184
|
-
missingIndices.push(i);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (missingCount > 0) {
|
|
189
|
-
// Always use loading manager via loadDataForRange callback
|
|
190
|
-
if (config.loadDataForRange) {
|
|
191
|
-
config.loadDataForRange(targetRange, "high");
|
|
192
|
-
} else {
|
|
193
|
-
// Fallback to direct collection call if no loading manager
|
|
194
|
-
collection.loadMissingRanges(targetRange).catch((error: any) => {
|
|
195
|
-
console.error("❌ Failed to load missing ranges:", error);
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Proactively load data for a specific range (called by scrolling manager)
|
|
204
|
-
*/
|
|
205
|
-
const loadDataForRange = (range: { start: number; end: number }): void => {
|
|
206
|
-
// Use the callback from config if provided (proactive approach)
|
|
207
|
-
if (config.loadDataForRange) {
|
|
208
|
-
config.loadDataForRange(range);
|
|
209
|
-
} else {
|
|
210
|
-
// Fallback to reactive approach (force re-render)
|
|
211
|
-
|
|
212
|
-
// Force a re-render
|
|
213
|
-
setTimeout(() => {
|
|
214
|
-
renderItems();
|
|
215
|
-
}, 0);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Forward declare renderItems for scrollingManager
|
|
220
|
-
let renderItems: () => void;
|
|
221
|
-
|
|
222
|
-
// Scrolling manager
|
|
223
|
-
const scrollingManager = createScrollingManager(
|
|
224
|
-
component,
|
|
225
|
-
itemSizeManager,
|
|
226
|
-
{
|
|
227
|
-
orientation,
|
|
228
|
-
enableScrollbar,
|
|
229
|
-
onScrollPositionChanged: (data) => {
|
|
230
|
-
component.emit?.("scroll:position:changed", data);
|
|
231
|
-
},
|
|
232
|
-
onVirtualRangeChanged: (range) => {
|
|
233
|
-
component.emit?.("virtual:range:changed", range);
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
() =>
|
|
237
|
-
virtualManager.calculateVisibleRange(
|
|
238
|
-
scrollingManager.getScrollPosition()
|
|
239
|
-
),
|
|
240
|
-
() => {
|
|
241
|
-
if (renderingManager) {
|
|
242
|
-
renderingManager.renderItems();
|
|
243
|
-
}
|
|
244
|
-
}, // Wrap in arrow function to avoid hoisting issues
|
|
245
|
-
() => actualTotalItems, // Pass the callback to get actual total items
|
|
246
|
-
loadDataForRange, // Pass the proactive data loading function
|
|
247
|
-
undefined, // Removed height cap info - using index-based scrolling
|
|
248
|
-
(index: number) => virtualManager.calculateVirtualPositionForIndex(index) // Pass position calculator
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
// Rendering manager
|
|
252
|
-
const renderingManager = createRenderingManager(
|
|
253
|
-
component,
|
|
254
|
-
itemSizeManager,
|
|
255
|
-
virtualManager,
|
|
256
|
-
scrollingManager,
|
|
257
|
-
{
|
|
258
|
-
orientation,
|
|
259
|
-
overscan,
|
|
260
|
-
loadDataForRange,
|
|
261
|
-
measureItems, // Pass the flag
|
|
262
|
-
},
|
|
263
|
-
() => actualTotalItems
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Initialize viewport
|
|
268
|
-
*/
|
|
269
|
-
const initialize = (): void => {
|
|
270
|
-
if (isViewportInitialized) return;
|
|
271
|
-
|
|
272
|
-
setupContainer();
|
|
273
|
-
if (enableScrollbar) {
|
|
274
|
-
scrollingManager.setupScrollbar();
|
|
275
|
-
}
|
|
276
|
-
scrollingManager.setupWheelEvents();
|
|
277
|
-
setupCollectionEventListeners();
|
|
278
|
-
setupResizeObserver();
|
|
279
|
-
measureContainer();
|
|
280
|
-
|
|
281
|
-
if (itemsContainer) {
|
|
282
|
-
// Set items container styles: relative, no transform, flex for orientation
|
|
283
|
-
itemsContainer.style.position = "relative";
|
|
284
|
-
// Remove flex layout as it conflicts with absolute positioning of items
|
|
285
|
-
itemsContainer.style.overflow = "hidden"; // Prevent overflow issues
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
isViewportInitialized = true;
|
|
289
|
-
component.emit?.("viewport:initialized", {
|
|
290
|
-
orientation,
|
|
291
|
-
containerSize: virtualManager.getState().containerSize,
|
|
292
|
-
});
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Setup collection event listeners for data updates
|
|
297
|
-
*/
|
|
298
|
-
const setupCollectionEventListeners = (): void => {
|
|
299
|
-
// Listen for collection events to trigger rendering
|
|
300
|
-
if (component.on) {
|
|
301
|
-
component.on("items:set", (data: any) => {
|
|
302
|
-
// Use the total from the event data or get the current component total
|
|
303
|
-
const currentTotal = data?.total || component.totalItems;
|
|
304
|
-
virtualManager.updateTotalVirtualSize(currentTotal);
|
|
305
|
-
// Force recalculation
|
|
306
|
-
if (renderingManager) {
|
|
307
|
-
renderingManager.renderItems();
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
component.on("range:loaded", (data: any) => {
|
|
312
|
-
// Use actualTotalItems which maintains the correct total (1M) instead of
|
|
313
|
-
// component.totalItems which may be temporarily reset during data loading
|
|
314
|
-
virtualManager.updateTotalVirtualSize(actualTotalItems);
|
|
315
|
-
// Always render when data loads - the render function will handle what to show
|
|
316
|
-
if (renderingManager) {
|
|
317
|
-
renderingManager.renderItems();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
component.on("total:changed", (data: any) => {
|
|
322
|
-
// Use the total from the event data instead of component.totalItems
|
|
323
|
-
const newTotal = data?.total || component.totalItems;
|
|
324
|
-
actualTotalItems = newTotal; // Store the correct value
|
|
325
|
-
|
|
326
|
-
virtualManager.updateTotalVirtualSize(newTotal);
|
|
327
|
-
|
|
328
|
-
// Update scrollbar plugin
|
|
329
|
-
if (scrollbarPlugin && scrollbarPlugin.setTotalItems) {
|
|
330
|
-
scrollbarPlugin.setTotalItems(newTotal);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Force recalculation
|
|
334
|
-
if (renderingManager) {
|
|
335
|
-
renderingManager.renderItems();
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
component.on("placeholders:replaced", () => {
|
|
340
|
-
if (renderingManager) {
|
|
341
|
-
renderingManager.renderItems();
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
component.on("estimated-size:changed", (data: any) => {
|
|
346
|
-
// Recalculate virtual size with updated estimated size
|
|
347
|
-
virtualManager.updateTotalVirtualSize(actualTotalItems);
|
|
348
|
-
scrollingManager.updateScrollbar();
|
|
349
|
-
|
|
350
|
-
// Update scrollbar plugin
|
|
351
|
-
if (scrollbarPlugin && scrollbarPlugin.setItemHeight) {
|
|
352
|
-
scrollbarPlugin.setItemHeight(
|
|
353
|
-
itemSizeManager.getEstimatedItemSize()
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
if (scrollbarPlugin && scrollbarPlugin.setTotalItems) {
|
|
357
|
-
scrollbarPlugin.setTotalItems(actualTotalItems);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Destroy viewport
|
|
365
|
-
*/
|
|
366
|
-
const destroy = (): void => {
|
|
367
|
-
if (!isViewportInitialized) return;
|
|
368
|
-
|
|
369
|
-
scrollingManager.removeWheelEvents();
|
|
370
|
-
scrollingManager.destroyScrollbar();
|
|
371
|
-
|
|
372
|
-
// Destroy scrollbar plugin
|
|
373
|
-
if (scrollbarPlugin && scrollbarPlugin.destroy) {
|
|
374
|
-
scrollbarPlugin.destroy();
|
|
375
|
-
scrollbarPlugin = null;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
resizeObserver?.disconnect();
|
|
379
|
-
|
|
380
|
-
// Clean up rendered elements
|
|
381
|
-
renderingManager.clear();
|
|
382
|
-
|
|
383
|
-
isViewportInitialized = false;
|
|
384
|
-
component.emit?.("viewport:destroyed", {});
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Render items in the visible range
|
|
389
|
-
*/
|
|
390
|
-
renderItems = (): void => {
|
|
391
|
-
if (!isViewportInitialized) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
renderingManager.renderItems();
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Measure actual item size and update cache
|
|
399
|
-
* (Kept for compatibility - delegates to itemSizeManager)
|
|
400
|
-
*/
|
|
401
|
-
const measureItemSize = (element: HTMLElement, index: number): number => {
|
|
402
|
-
return itemSizeManager.measureItem(element, index, orientation);
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Get current viewport information
|
|
407
|
-
*/
|
|
408
|
-
const getViewportInfo = (): ViewportInfo => {
|
|
409
|
-
const scrollPosition = scrollingManager.getScrollPosition();
|
|
410
|
-
const state = virtualManager.getState();
|
|
411
|
-
const visibleRange = virtualManager.calculateVisibleRange(scrollPosition);
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
containerSize: state.containerSize,
|
|
415
|
-
totalVirtualSize: state.totalVirtualSize,
|
|
416
|
-
visibleRange,
|
|
417
|
-
virtualScrollPosition: scrollPosition,
|
|
418
|
-
};
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Update viewport manually
|
|
423
|
-
*/
|
|
424
|
-
const updateViewport = (): void => {
|
|
425
|
-
measureContainer();
|
|
426
|
-
virtualManager.updateTotalVirtualSize(component.totalItems);
|
|
427
|
-
scrollingManager.updateContainerPosition();
|
|
428
|
-
scrollingManager.updateScrollbar();
|
|
429
|
-
renderItems();
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// Private helper functions
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Setup scrollbar plugin integration
|
|
436
|
-
*/
|
|
437
|
-
const setupScrollbarPlugin = (): void => {
|
|
438
|
-
// Create simple adapter interface for scrollbar plugin
|
|
439
|
-
const listManagerAdapter = {
|
|
440
|
-
getConfig: () => ({
|
|
441
|
-
container: component.element,
|
|
442
|
-
}),
|
|
443
|
-
subscribe: (callback: (payload: any) => void) => {
|
|
444
|
-
// Simple event subscription using component events
|
|
445
|
-
component.on?.("scroll:position:changed", (data: any) => {
|
|
446
|
-
callback({
|
|
447
|
-
event: "viewport:changed",
|
|
448
|
-
data: {
|
|
449
|
-
...data,
|
|
450
|
-
source: "wheel-scroll-scrollbar-update",
|
|
451
|
-
scrollRatio:
|
|
452
|
-
data.position &&
|
|
453
|
-
virtualManager.getState().totalVirtualSize >
|
|
454
|
-
virtualManager.getState().containerSize
|
|
455
|
-
? data.position /
|
|
456
|
-
(virtualManager.getState().totalVirtualSize -
|
|
457
|
-
virtualManager.getState().containerSize)
|
|
458
|
-
: 0,
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
component.on?.("dimensions:changed", (data: any) => {
|
|
464
|
-
callback({
|
|
465
|
-
event: "virtual:range:changed",
|
|
466
|
-
data: {
|
|
467
|
-
...data,
|
|
468
|
-
action: "update-scrollbar",
|
|
469
|
-
source: "virtual-viewport",
|
|
470
|
-
totalItems: actualTotalItems,
|
|
471
|
-
itemHeight: itemSizeManager.getEstimatedItemSize(),
|
|
472
|
-
totalVirtualSize:
|
|
473
|
-
data.totalVirtualSize || virtualManager.getTotalVirtualSize(),
|
|
474
|
-
},
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
return () => {}; // Unsubscribe function
|
|
479
|
-
},
|
|
480
|
-
emit: (event: string, data: any) => {
|
|
481
|
-
if (
|
|
482
|
-
event === "viewport:changed" &&
|
|
483
|
-
(data.source === "scrollbar" || data.source === "scrollbar-drag")
|
|
484
|
-
) {
|
|
485
|
-
// Handle scrollbar events - scroll to position
|
|
486
|
-
const targetPosition = data.scrollTop || 0;
|
|
487
|
-
scrollingManager.scrollToPosition(targetPosition, data.source);
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
// Initialize scrollbar plugin
|
|
493
|
-
const scrollbarPluginInstance = createScrollbar({
|
|
494
|
-
enabled: true,
|
|
495
|
-
itemHeight: itemSizeManager.getEstimatedItemSize(),
|
|
496
|
-
totalItems: actualTotalItems,
|
|
497
|
-
totalVirtualSize: virtualManager.getTotalVirtualSize(),
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
scrollbarPlugin = scrollbarPluginInstance.install(listManagerAdapter, {});
|
|
501
|
-
|
|
502
|
-
// Connect scrollbar to scrolling manager
|
|
503
|
-
(scrollingManager as any).setScrollbarPlugin(scrollbarPlugin);
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Setup container structure with transform-based virtual scrolling
|
|
508
|
-
*/
|
|
509
|
-
const setupContainer = (): void => {
|
|
510
|
-
component.element.style.position = "relative";
|
|
511
|
-
component.element.style.overflow = "hidden";
|
|
512
|
-
|
|
513
|
-
// Create viewport container (using class expected by scrollbar plugin)
|
|
514
|
-
const viewport = document.createElement("div");
|
|
515
|
-
viewport.className = `mtrl-list__viewport ${component.getClass(
|
|
516
|
-
"list-manager"
|
|
517
|
-
)}-viewport`;
|
|
518
|
-
viewport.style.cssText = `
|
|
519
|
-
position: absolute;
|
|
520
|
-
top: 0;
|
|
521
|
-
left: 0;
|
|
522
|
-
width: 100%;
|
|
523
|
-
height: 100%;
|
|
524
|
-
overflow: hidden;
|
|
525
|
-
`;
|
|
526
|
-
|
|
527
|
-
// Store viewport reference
|
|
528
|
-
viewportElement = viewport;
|
|
529
|
-
|
|
530
|
-
// Create items container for natural item flow
|
|
531
|
-
itemsContainer = document.createElement("div");
|
|
532
|
-
itemsContainer.className = `${component.getClass(
|
|
533
|
-
"list-manager"
|
|
534
|
-
)}-viewport-items`;
|
|
535
|
-
itemsContainer.style.cssText = `
|
|
536
|
-
position: relative;
|
|
537
|
-
width: 100%;
|
|
538
|
-
will-change: contents;
|
|
539
|
-
`;
|
|
540
|
-
|
|
541
|
-
viewport.appendChild(itemsContainer);
|
|
542
|
-
component.element.appendChild(viewport);
|
|
543
|
-
|
|
544
|
-
// Set items container reference in scrolling manager and rendering manager
|
|
545
|
-
(scrollingManager as any).setItemsContainer(itemsContainer);
|
|
546
|
-
renderingManager.setItemsContainer(itemsContainer);
|
|
547
|
-
|
|
548
|
-
// Initialize scrollbar plugin if enabled
|
|
549
|
-
if (enableScrollbar) {
|
|
550
|
-
setupScrollbarPlugin();
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Setup resize observer
|
|
556
|
-
*/
|
|
557
|
-
const setupResizeObserver = (): void => {
|
|
558
|
-
resizeObserver = new ResizeObserver(() => {
|
|
559
|
-
measureContainer();
|
|
560
|
-
updateViewport();
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// Observe the viewport element if available, otherwise the component element
|
|
564
|
-
const observeElement = viewportElement || component.element;
|
|
565
|
-
resizeObserver.observe(observeElement);
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Measure container dimensions
|
|
570
|
-
*/
|
|
571
|
-
const measureContainer = (): void => {
|
|
572
|
-
// Measure the actual viewport element, not the component element
|
|
573
|
-
const measureElement = viewportElement || component.element;
|
|
574
|
-
const newSize =
|
|
575
|
-
orientation === "vertical"
|
|
576
|
-
? measureElement.offsetHeight
|
|
577
|
-
: measureElement.offsetWidth;
|
|
578
|
-
|
|
579
|
-
const currentSize = virtualManager.getState().containerSize;
|
|
580
|
-
if (newSize !== currentSize) {
|
|
581
|
-
virtualManager.updateState({ containerSize: newSize });
|
|
582
|
-
scrollingManager.updateState({ containerSize: newSize });
|
|
583
|
-
scrollingManager.updateContainerPosition();
|
|
584
|
-
scrollingManager.updateScrollbar();
|
|
585
|
-
|
|
586
|
-
component.emit?.("viewport:changed", getViewportInfo());
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
// Initialize viewport when component initializes
|
|
591
|
-
const originalInitialize = component.initialize;
|
|
592
|
-
component.initialize = () => {
|
|
593
|
-
originalInitialize.call(component);
|
|
594
|
-
initialize();
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
// Destroy viewport when component destroys
|
|
598
|
-
const originalDestroy = component.destroy;
|
|
599
|
-
component.destroy = () => {
|
|
600
|
-
destroy();
|
|
601
|
-
originalDestroy.call(component);
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
// Viewport API
|
|
605
|
-
const viewport = {
|
|
606
|
-
// Virtual scrolling
|
|
607
|
-
getScrollPosition: () => scrollingManager.getScrollPosition(),
|
|
608
|
-
getContainerSize: () => virtualManager.getState().containerSize,
|
|
609
|
-
getTotalVirtualSize: () => virtualManager.getState().totalVirtualSize,
|
|
610
|
-
getVisibleRange: () =>
|
|
611
|
-
virtualManager.calculateVisibleRange(
|
|
612
|
-
scrollingManager.getScrollPosition()
|
|
613
|
-
),
|
|
614
|
-
getViewportInfo,
|
|
615
|
-
|
|
616
|
-
// Navigation - Visual positioning only
|
|
617
|
-
scrollToPosition: scrollingManager.scrollToPosition,
|
|
618
|
-
scrollToIndex: scrollingManager.scrollToIndex,
|
|
619
|
-
|
|
620
|
-
// Item sizing
|
|
621
|
-
measureItemSize,
|
|
622
|
-
hasMeasuredSize: (index: number) =>
|
|
623
|
-
itemSizeManager.hasMeasuredSize(index),
|
|
624
|
-
getMeasuredSize: (index: number) =>
|
|
625
|
-
itemSizeManager.getMeasuredSize(index),
|
|
626
|
-
getEstimatedItemSize: () => itemSizeManager.getEstimatedItemSize(),
|
|
627
|
-
|
|
628
|
-
// Rendering
|
|
629
|
-
renderItems,
|
|
630
|
-
updateItemPositions: renderingManager.updateItemPositions,
|
|
631
|
-
getRenderedElements: renderingManager.getRenderedElements,
|
|
632
|
-
|
|
633
|
-
// State
|
|
634
|
-
getOrientation: () => orientation,
|
|
635
|
-
isInitialized: () => isViewportInitialized,
|
|
636
|
-
|
|
637
|
-
// Manual updates
|
|
638
|
-
updateContainerPosition: scrollingManager.updateContainerPosition,
|
|
639
|
-
updateScrollbar: scrollingManager.updateScrollbar,
|
|
640
|
-
updateViewport,
|
|
641
|
-
|
|
642
|
-
// Proactive data loading
|
|
643
|
-
loadDataForRange,
|
|
644
|
-
|
|
645
|
-
// Lifecycle
|
|
646
|
-
initialize,
|
|
647
|
-
destroy,
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
return {
|
|
651
|
-
...component,
|
|
652
|
-
viewport,
|
|
653
|
-
};
|
|
654
|
-
};
|