mtrl-addons 0.1.2 → 0.2.2
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/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -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 +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- 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 +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -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 +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -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 +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -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 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
// Removed unused imports
|
|
2
|
-
import { CLASSES } from "../../constants";
|
|
3
|
-
import { addClass, removeClass } from "mtrl";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Scrollbar configuration
|
|
7
|
-
*/
|
|
8
|
-
export interface ScrollbarConfig {
|
|
9
|
-
enabled: boolean;
|
|
10
|
-
trackWidth: number;
|
|
11
|
-
thumbMinHeight: number; // Default: 10px (reduced from 20px)
|
|
12
|
-
thumbColor: string;
|
|
13
|
-
trackColor: string;
|
|
14
|
-
borderRadius: number;
|
|
15
|
-
fadeTimeout: number;
|
|
16
|
-
itemHeight: number;
|
|
17
|
-
totalItems: number;
|
|
18
|
-
totalVirtualSize?: number; // The capped virtual size used by viewport
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Scrollbar implementation that bypasses browser scroll height limitations
|
|
23
|
-
* Provides accurate scrollbar representation for datasets with millions of items
|
|
24
|
-
*/
|
|
25
|
-
export const scrollbar = (config: Partial<ScrollbarConfig> = {}): any => ({
|
|
26
|
-
name: "scrollbar",
|
|
27
|
-
version: "1.0.0",
|
|
28
|
-
dependencies: [],
|
|
29
|
-
|
|
30
|
-
install: (listManager: any, pluginConfig: any) => {
|
|
31
|
-
// Configuration with defaults
|
|
32
|
-
const scrollbarConfig: ScrollbarConfig = {
|
|
33
|
-
enabled: true,
|
|
34
|
-
trackWidth: 12,
|
|
35
|
-
thumbMinHeight: 10, // Reduced from 20 to 10 (divided by 2)
|
|
36
|
-
thumbColor: "#999999",
|
|
37
|
-
trackColor: "#f0f0f0",
|
|
38
|
-
borderRadius: 6,
|
|
39
|
-
fadeTimeout: 1000,
|
|
40
|
-
itemHeight: 84,
|
|
41
|
-
totalItems: 0,
|
|
42
|
-
...config,
|
|
43
|
-
...(pluginConfig || {}),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// State
|
|
47
|
-
let isDragging = false;
|
|
48
|
-
let dragStartY = 0;
|
|
49
|
-
let dragStartScrollRatio = 0;
|
|
50
|
-
let fadeTimeoutId: number | null = null;
|
|
51
|
-
let totalVirtualHeight = 0;
|
|
52
|
-
let viewportHeight = 0;
|
|
53
|
-
let visibleRatio = 0;
|
|
54
|
-
let scrollRatio = 0;
|
|
55
|
-
|
|
56
|
-
// Get elements
|
|
57
|
-
const getElements = () => {
|
|
58
|
-
const config = listManager.getConfig?.();
|
|
59
|
-
if (!config?.container) return null;
|
|
60
|
-
|
|
61
|
-
const containerElement =
|
|
62
|
-
typeof config.container === "string"
|
|
63
|
-
? (document.querySelector(config.container) as HTMLElement)
|
|
64
|
-
: config.container;
|
|
65
|
-
|
|
66
|
-
const viewport = containerElement?.querySelector(
|
|
67
|
-
`.mtrl-list__viewport`
|
|
68
|
-
) as HTMLElement;
|
|
69
|
-
|
|
70
|
-
// Find the actual mtrl-list element (parent of viewport)
|
|
71
|
-
const listElement = viewport?.closest(".mtrl-list") as HTMLElement;
|
|
72
|
-
|
|
73
|
-
return { containerElement, viewport, listElement };
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
let elements = getElements();
|
|
77
|
-
if (!elements?.viewport) {
|
|
78
|
-
return {};
|
|
79
|
-
}
|
|
80
|
-
if (!elements?.listElement) {
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const { containerElement, viewport, listElement } = elements;
|
|
85
|
-
|
|
86
|
-
// Create scrollbar DOM structure
|
|
87
|
-
const scrollbarTrack = document.createElement("div");
|
|
88
|
-
addClass(scrollbarTrack, CLASSES.SCROLLBAR);
|
|
89
|
-
addClass(scrollbarTrack, CLASSES.SCROLLBAR_TRACK);
|
|
90
|
-
|
|
91
|
-
const scrollbarThumb = document.createElement("div");
|
|
92
|
-
addClass(scrollbarThumb, CLASSES.SCROLLBAR_THUMB);
|
|
93
|
-
|
|
94
|
-
scrollbarTrack.appendChild(scrollbarThumb);
|
|
95
|
-
// Append scrollbar to the mtrl-list element, not the outer container
|
|
96
|
-
listElement.appendChild(scrollbarTrack);
|
|
97
|
-
|
|
98
|
-
// Hide native scrollbar using CSS classes instead of inline styles
|
|
99
|
-
addClass(viewport, CLASSES.SCROLLBAR_ENABLED);
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Calculate scrollbar dimensions and position
|
|
103
|
-
*/
|
|
104
|
-
const updateScrollbarDimensions = (): void => {
|
|
105
|
-
if (!scrollbarConfig.totalItems) return;
|
|
106
|
-
|
|
107
|
-
// Use capped virtual size if available, otherwise calculate from items
|
|
108
|
-
totalVirtualHeight =
|
|
109
|
-
scrollbarConfig.totalVirtualSize ||
|
|
110
|
-
scrollbarConfig.totalItems * scrollbarConfig.itemHeight;
|
|
111
|
-
viewportHeight = viewport.clientHeight;
|
|
112
|
-
visibleRatio = Math.min(viewportHeight / totalVirtualHeight, 1);
|
|
113
|
-
|
|
114
|
-
// Calculate thumb height (proportional to visible area)
|
|
115
|
-
const thumbHeight = Math.max(
|
|
116
|
-
scrollbarConfig.thumbMinHeight,
|
|
117
|
-
visibleRatio * (scrollbarTrack.clientHeight - 4) // 4px for padding
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
// Update thumb dimensions
|
|
121
|
-
scrollbarThumb.style.height = `${thumbHeight}px`;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Update scrollbar position based on scroll ratio
|
|
126
|
-
*/
|
|
127
|
-
const updateScrollbarPosition = (newScrollRatio: number): void => {
|
|
128
|
-
scrollRatio = Math.max(0, Math.min(1, newScrollRatio));
|
|
129
|
-
|
|
130
|
-
const trackHeight = scrollbarTrack.clientHeight;
|
|
131
|
-
const thumbHeight = scrollbarThumb.clientHeight;
|
|
132
|
-
const maxThumbTop = trackHeight - thumbHeight;
|
|
133
|
-
const thumbTop = scrollRatio * maxThumbTop;
|
|
134
|
-
|
|
135
|
-
scrollbarThumb.style.top = `${thumbTop}px`;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Calculate scroll ratio from virtual scroll position
|
|
140
|
-
*/
|
|
141
|
-
const getScrollRatioFromVirtualPosition = (
|
|
142
|
-
virtualScrollTop: number
|
|
143
|
-
): number => {
|
|
144
|
-
if (totalVirtualHeight <= viewportHeight) return 0;
|
|
145
|
-
const maxScroll = totalVirtualHeight - viewportHeight;
|
|
146
|
-
return Math.max(0, Math.min(1, virtualScrollTop / maxScroll));
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Calculate virtual scroll position from scroll ratio
|
|
151
|
-
*/
|
|
152
|
-
const getVirtualPositionFromScrollRatio = (ratio: number): number => {
|
|
153
|
-
// Use the capped virtual size if available, otherwise calculate from items
|
|
154
|
-
const currentTotalVirtualHeight =
|
|
155
|
-
scrollbarConfig.totalVirtualSize ||
|
|
156
|
-
scrollbarConfig.totalItems * scrollbarConfig.itemHeight;
|
|
157
|
-
const currentViewportHeight = viewport.clientHeight;
|
|
158
|
-
const maxScroll = Math.max(
|
|
159
|
-
0,
|
|
160
|
-
currentTotalVirtualHeight - currentViewportHeight
|
|
161
|
-
);
|
|
162
|
-
return ratio * maxScroll;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Show scrollbar with fade in
|
|
167
|
-
*/
|
|
168
|
-
const showScrollbar = (): void => {
|
|
169
|
-
if (fadeTimeoutId) {
|
|
170
|
-
clearTimeout(fadeTimeoutId);
|
|
171
|
-
fadeTimeoutId = null;
|
|
172
|
-
}
|
|
173
|
-
addClass(scrollbarTrack, CLASSES.SCROLLBAR_SCROLLING);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Hide scrollbar with fade out
|
|
178
|
-
*/
|
|
179
|
-
const hideScrollbar = (): void => {
|
|
180
|
-
if (fadeTimeoutId) clearTimeout(fadeTimeoutId);
|
|
181
|
-
fadeTimeoutId = window.setTimeout(() => {
|
|
182
|
-
removeClass(scrollbarTrack, CLASSES.SCROLLBAR_SCROLLING);
|
|
183
|
-
fadeTimeoutId = null;
|
|
184
|
-
}, scrollbarConfig.fadeTimeout);
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Handle scrollbar thumb drag
|
|
189
|
-
*/
|
|
190
|
-
const handleThumbMouseDown = (e: MouseEvent): void => {
|
|
191
|
-
e.preventDefault();
|
|
192
|
-
e.stopPropagation();
|
|
193
|
-
|
|
194
|
-
isDragging = true;
|
|
195
|
-
dragStartY = e.clientY;
|
|
196
|
-
dragStartScrollRatio = scrollRatio;
|
|
197
|
-
|
|
198
|
-
document.addEventListener("mousemove", handleThumbDrag);
|
|
199
|
-
document.addEventListener("mouseup", handleThumbMouseUp);
|
|
200
|
-
|
|
201
|
-
// Add dragging state class
|
|
202
|
-
addClass(scrollbarTrack, CLASSES.SCROLLBAR_DRAGGING);
|
|
203
|
-
addClass(scrollbarThumb, CLASSES.SCROLLBAR_THUMB_DRAGGING);
|
|
204
|
-
showScrollbar();
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Handle scrollbar thumb drag movement
|
|
209
|
-
*/
|
|
210
|
-
const handleThumbDrag = (e: MouseEvent): void => {
|
|
211
|
-
if (!isDragging) return;
|
|
212
|
-
|
|
213
|
-
const deltaY = e.clientY - dragStartY;
|
|
214
|
-
const trackHeight = scrollbarTrack.clientHeight;
|
|
215
|
-
const thumbHeight = scrollbarThumb.clientHeight;
|
|
216
|
-
const maxThumbTravel = trackHeight - thumbHeight;
|
|
217
|
-
|
|
218
|
-
if (maxThumbTravel <= 0) return;
|
|
219
|
-
|
|
220
|
-
const deltaRatio = deltaY / maxThumbTravel;
|
|
221
|
-
const newScrollRatio = Math.max(
|
|
222
|
-
0,
|
|
223
|
-
Math.min(1, dragStartScrollRatio + deltaRatio)
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
// Update position immediately (visual only)
|
|
227
|
-
updateScrollbarPosition(newScrollRatio);
|
|
228
|
-
|
|
229
|
-
// Calculate and emit the new scroll position
|
|
230
|
-
const virtualScrollTop =
|
|
231
|
-
getVirtualPositionFromScrollRatio(newScrollRatio);
|
|
232
|
-
|
|
233
|
-
listManager.emit("viewport:changed", {
|
|
234
|
-
scrollTop: virtualScrollTop,
|
|
235
|
-
scrollRatio: newScrollRatio,
|
|
236
|
-
source: "scrollbar-drag",
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Handle scrollbar thumb drag end
|
|
242
|
-
*/
|
|
243
|
-
const handleThumbMouseUp = (): void => {
|
|
244
|
-
if (!isDragging) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
isDragging = false;
|
|
249
|
-
|
|
250
|
-
document.removeEventListener("mousemove", handleThumbDrag);
|
|
251
|
-
document.removeEventListener("mouseup", handleThumbMouseUp);
|
|
252
|
-
|
|
253
|
-
// Remove dragging state classes
|
|
254
|
-
removeClass(scrollbarTrack, CLASSES.SCROLLBAR_DRAGGING);
|
|
255
|
-
removeClass(scrollbarThumb, CLASSES.SCROLLBAR_THUMB_DRAGGING);
|
|
256
|
-
hideScrollbar();
|
|
257
|
-
|
|
258
|
-
// Calculate the start index and virtual position
|
|
259
|
-
let finalStartIndex: number;
|
|
260
|
-
let finalVirtualScrollTop: number;
|
|
261
|
-
|
|
262
|
-
// Check if we're using compressed virtual space
|
|
263
|
-
const actualTotalSize =
|
|
264
|
-
scrollbarConfig.totalItems * scrollbarConfig.itemHeight;
|
|
265
|
-
const isCompressed =
|
|
266
|
-
scrollbarConfig.totalVirtualSize &&
|
|
267
|
-
actualTotalSize > scrollbarConfig.totalVirtualSize;
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
isCompressed &&
|
|
271
|
-
scrollbarConfig.totalVirtualSize &&
|
|
272
|
-
scrollbarConfig.totalItems
|
|
273
|
-
) {
|
|
274
|
-
// Compressed space: map ratio directly to item index
|
|
275
|
-
// When ratio = 1, we want to show the last viewport of items
|
|
276
|
-
const viewportItemCount = Math.floor(
|
|
277
|
-
viewportHeight / scrollbarConfig.itemHeight
|
|
278
|
-
);
|
|
279
|
-
const maxStartIndex = Math.max(
|
|
280
|
-
0,
|
|
281
|
-
scrollbarConfig.totalItems - viewportItemCount
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// Ensure we reach the last items when scrollbar is at the bottom
|
|
285
|
-
if (scrollRatio >= 0.999) {
|
|
286
|
-
// Close enough to bottom, snap to last items
|
|
287
|
-
finalStartIndex = maxStartIndex;
|
|
288
|
-
finalVirtualScrollTop =
|
|
289
|
-
scrollbarConfig.totalVirtualSize - viewportHeight;
|
|
290
|
-
} else {
|
|
291
|
-
finalStartIndex = Math.floor(scrollRatio * maxStartIndex);
|
|
292
|
-
// Map the index back to virtual space using the same ratio
|
|
293
|
-
const maxVirtualScroll = Math.max(
|
|
294
|
-
0,
|
|
295
|
-
scrollbarConfig.totalVirtualSize - viewportHeight
|
|
296
|
-
);
|
|
297
|
-
finalVirtualScrollTop = scrollRatio * maxVirtualScroll;
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
// Direct 1:1 mapping when not compressed
|
|
301
|
-
finalVirtualScrollTop = getVirtualPositionFromScrollRatio(scrollRatio);
|
|
302
|
-
finalStartIndex = Math.floor(
|
|
303
|
-
finalVirtualScrollTop / scrollbarConfig.itemHeight
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
listManager.emit("viewport:changed", {
|
|
308
|
-
scrollTop: finalVirtualScrollTop,
|
|
309
|
-
scrollRatio: scrollRatio,
|
|
310
|
-
startIndex: finalStartIndex,
|
|
311
|
-
source: "scrollbar",
|
|
312
|
-
action: "drag-end", // Indicate this is the final position
|
|
313
|
-
});
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Handle scrollbar track click
|
|
318
|
-
*/
|
|
319
|
-
const handleTrackClick = (e: MouseEvent): void => {
|
|
320
|
-
if (isDragging) return;
|
|
321
|
-
|
|
322
|
-
const rect = scrollbarTrack.getBoundingClientRect();
|
|
323
|
-
const clickY = e.clientY - rect.top;
|
|
324
|
-
const trackHeight = scrollbarTrack.clientHeight;
|
|
325
|
-
const thumbHeight = scrollbarThumb.clientHeight;
|
|
326
|
-
|
|
327
|
-
// Calculate new scroll ratio based on click position
|
|
328
|
-
const newScrollRatio = Math.max(
|
|
329
|
-
0,
|
|
330
|
-
Math.min(1, (clickY - thumbHeight / 2) / (trackHeight - thumbHeight))
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
// Update position and emit event
|
|
334
|
-
updateScrollbarPosition(newScrollRatio);
|
|
335
|
-
|
|
336
|
-
const virtualScrollTop =
|
|
337
|
-
getVirtualPositionFromScrollRatio(newScrollRatio);
|
|
338
|
-
|
|
339
|
-
listManager.emit("viewport:changed", {
|
|
340
|
-
scrollTop: virtualScrollTop,
|
|
341
|
-
scrollRatio: newScrollRatio,
|
|
342
|
-
source: "scrollbar",
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
showScrollbar();
|
|
346
|
-
hideScrollbar();
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
// Listen for virtual viewport updates
|
|
350
|
-
const unsubscribeScrollbar = listManager.subscribe((payload: any) => {
|
|
351
|
-
if (payload.event === "virtual:range:changed") {
|
|
352
|
-
if (
|
|
353
|
-
payload.data?.action === "update-scrollbar" &&
|
|
354
|
-
payload.data?.source === "virtual-viewport"
|
|
355
|
-
) {
|
|
356
|
-
const { totalItems, itemHeight, totalVirtualSize } = payload.data;
|
|
357
|
-
|
|
358
|
-
if (totalItems !== undefined) {
|
|
359
|
-
scrollbarConfig.totalItems = totalItems;
|
|
360
|
-
}
|
|
361
|
-
if (itemHeight !== undefined) {
|
|
362
|
-
scrollbarConfig.itemHeight = itemHeight;
|
|
363
|
-
}
|
|
364
|
-
if (totalVirtualSize !== undefined) {
|
|
365
|
-
scrollbarConfig.totalVirtualSize = totalVirtualSize;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
updateScrollbarDimensions();
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Listen for wheel scroll events to update scrollbar position
|
|
373
|
-
if (payload.event === "viewport:changed") {
|
|
374
|
-
if (payload.data?.source === "wheel-scroll-scrollbar-update") {
|
|
375
|
-
// Skip this update - scrollbar is already updated via updateScrollPosition
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// Attach event listeners
|
|
382
|
-
scrollbarThumb.addEventListener("mousedown", handleThumbMouseDown);
|
|
383
|
-
scrollbarTrack.addEventListener("click", handleTrackClick);
|
|
384
|
-
|
|
385
|
-
// Show/hide on hover (use listElement since scrollbar is inside it)
|
|
386
|
-
listElement.addEventListener("mouseenter", showScrollbar);
|
|
387
|
-
listElement.addEventListener("mouseleave", hideScrollbar);
|
|
388
|
-
|
|
389
|
-
// Show during scrolling
|
|
390
|
-
viewport.addEventListener("scroll", showScrollbar);
|
|
391
|
-
|
|
392
|
-
// Return plugin API
|
|
393
|
-
return {
|
|
394
|
-
/**
|
|
395
|
-
* Update total items count
|
|
396
|
-
*/
|
|
397
|
-
setTotalItems(count: number): void {
|
|
398
|
-
// 🚫 PREVENT INFINITE LOOP: Only update if count actually changed
|
|
399
|
-
|
|
400
|
-
if (scrollbarConfig.totalItems === count) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const previousCount = scrollbarConfig.totalItems;
|
|
405
|
-
scrollbarConfig.totalItems = count;
|
|
406
|
-
updateScrollbarDimensions();
|
|
407
|
-
},
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Update scroll position from external source
|
|
411
|
-
*/
|
|
412
|
-
updateScrollPosition(virtualScrollTop: number): void {
|
|
413
|
-
// 🚫 PREVENT INFINITE LOOP: Don't update scrollbar position during drag
|
|
414
|
-
if (!isDragging) {
|
|
415
|
-
const newScrollRatio =
|
|
416
|
-
getScrollRatioFromVirtualPosition(virtualScrollTop);
|
|
417
|
-
updateScrollbarPosition(newScrollRatio);
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Update item height
|
|
423
|
-
*/
|
|
424
|
-
setItemHeight(height: number): void {
|
|
425
|
-
scrollbarConfig.itemHeight = height;
|
|
426
|
-
updateScrollbarDimensions();
|
|
427
|
-
},
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Get current virtual scroll position
|
|
431
|
-
*/
|
|
432
|
-
getVirtualScrollTop(): number {
|
|
433
|
-
return getVirtualPositionFromScrollRatio(scrollRatio);
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Get current scroll ratio (0-1)
|
|
438
|
-
*/
|
|
439
|
-
getScrollRatio(): number {
|
|
440
|
-
return scrollRatio;
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Show scrollbar
|
|
445
|
-
*/
|
|
446
|
-
show(): void {
|
|
447
|
-
showScrollbar();
|
|
448
|
-
},
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Hide scrollbar
|
|
452
|
-
*/
|
|
453
|
-
hide(): void {
|
|
454
|
-
hideScrollbar();
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Update configuration
|
|
459
|
-
*/
|
|
460
|
-
updateConfig(newConfig: Partial<ScrollbarConfig>): void {
|
|
461
|
-
Object.assign(scrollbarConfig, newConfig);
|
|
462
|
-
updateScrollbarDimensions();
|
|
463
|
-
},
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Destroy scrollbar
|
|
467
|
-
*/
|
|
468
|
-
destroy(): void {
|
|
469
|
-
if (scrollbarTrack.parentNode) {
|
|
470
|
-
scrollbarTrack.parentNode.removeChild(scrollbarTrack);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Remove scrollbar classes from viewport
|
|
474
|
-
removeClass(viewport, CLASSES.SCROLLBAR_ENABLED);
|
|
475
|
-
|
|
476
|
-
// Clean up event subscriptions
|
|
477
|
-
unsubscribeScrollbar();
|
|
478
|
-
|
|
479
|
-
// Clean up event listeners
|
|
480
|
-
listElement.removeEventListener("mouseenter", showScrollbar);
|
|
481
|
-
listElement.removeEventListener("mouseleave", hideScrollbar);
|
|
482
|
-
viewport.removeEventListener("scroll", showScrollbar);
|
|
483
|
-
scrollbarThumb.removeEventListener("mousedown", handleThumbMouseDown);
|
|
484
|
-
scrollbarTrack.removeEventListener("click", handleTrackClick);
|
|
485
|
-
document.removeEventListener("mousemove", handleThumbDrag);
|
|
486
|
-
document.removeEventListener("mouseup", handleThumbMouseUp);
|
|
487
|
-
|
|
488
|
-
// Clean up timeouts
|
|
489
|
-
if (fadeTimeoutId) {
|
|
490
|
-
clearTimeout(fadeTimeoutId);
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
};
|
|
494
|
-
},
|
|
495
|
-
});
|