vlist 2.0.0 → 2.0.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/dist/index.js +1 -28
- package/dist/internals.js +1 -60
- package/package.json +1 -1
- package/dist/constants.js +0 -83
- package/dist/core/create.js +0 -740
- package/dist/core/dom.js +0 -47
- package/dist/core/hooks.js +0 -67
- package/dist/core/index.js +0 -13
- package/dist/core/pipeline.js +0 -307
- package/dist/core/pool.js +0 -42
- package/dist/core/scroll.js +0 -137
- package/dist/core/sizes.js +0 -6
- package/dist/core/state.js +0 -56
- package/dist/core/types.js +0 -7
- package/dist/core/velocity.js +0 -33
- package/dist/events/emitter.js +0 -60
- package/dist/events/index.js +0 -6
- package/dist/plugins/a11y/index.js +0 -1
- package/dist/plugins/a11y/plugin.js +0 -259
- package/dist/plugins/async/index.js +0 -12
- package/dist/plugins/async/manager.js +0 -568
- package/dist/plugins/async/placeholder.js +0 -154
- package/dist/plugins/async/plugin.js +0 -311
- package/dist/plugins/async/sparse.js +0 -540
- package/dist/plugins/autosize/index.js +0 -4
- package/dist/plugins/autosize/plugin.js +0 -185
- package/dist/plugins/grid/index.js +0 -5
- package/dist/plugins/grid/layout.js +0 -275
- package/dist/plugins/grid/plugin.js +0 -347
- package/dist/plugins/grid/renderer.js +0 -525
- package/dist/plugins/grid/types.js +0 -11
- package/dist/plugins/groups/async-bridge.js +0 -246
- package/dist/plugins/groups/index.js +0 -13
- package/dist/plugins/groups/layout.js +0 -294
- package/dist/plugins/groups/plugin.js +0 -571
- package/dist/plugins/groups/sticky.js +0 -255
- package/dist/plugins/groups/types.js +0 -12
- package/dist/plugins/masonry/index.js +0 -6
- package/dist/plugins/masonry/layout.js +0 -261
- package/dist/plugins/masonry/plugin.js +0 -381
- package/dist/plugins/masonry/renderer.js +0 -354
- package/dist/plugins/masonry/types.js +0 -9
- package/dist/plugins/page/index.js +0 -5
- package/dist/plugins/page/plugin.js +0 -166
- package/dist/plugins/scale/index.js +0 -4
- package/dist/plugins/scale/plugin.js +0 -507
- package/dist/plugins/scrollbar/controller.js +0 -574
- package/dist/plugins/scrollbar/index.js +0 -6
- package/dist/plugins/scrollbar/plugin.js +0 -93
- package/dist/plugins/scrollbar/scrollbar.js +0 -556
- package/dist/plugins/selection/index.js +0 -7
- package/dist/plugins/selection/plugin.js +0 -601
- package/dist/plugins/selection/state.js +0 -332
- package/dist/plugins/snapshots/index.js +0 -5
- package/dist/plugins/snapshots/plugin.js +0 -301
- package/dist/plugins/sortable/index.js +0 -6
- package/dist/plugins/sortable/plugin.js +0 -753
- package/dist/plugins/table/header.js +0 -501
- package/dist/plugins/table/index.js +0 -12
- package/dist/plugins/table/layout.js +0 -211
- package/dist/plugins/table/plugin.js +0 -391
- package/dist/plugins/table/renderer.js +0 -625
- package/dist/plugins/table/types.js +0 -12
- package/dist/plugins/transition/index.js +0 -5
- package/dist/plugins/transition/plugin.js +0 -405
- package/dist/rendering/aria.js +0 -23
- package/dist/rendering/index.js +0 -18
- package/dist/rendering/measured.js +0 -98
- package/dist/rendering/renderer.js +0 -586
- package/dist/rendering/scale.js +0 -267
- package/dist/rendering/scroll.js +0 -71
- package/dist/rendering/sizes.js +0 -193
- package/dist/rendering/sort.js +0 -65
- package/dist/rendering/viewport.js +0 -268
- package/dist/types.js +0 -5
- package/dist/utils/padding.js +0 -49
- package/dist/utils/stats.js +0 -124
|
@@ -1,556 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist - Custom Scrollbar
|
|
3
|
-
* Provides visual scroll indication for compressed mode where native scrollbar is hidden
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - Visual track and thumb
|
|
7
|
-
* - Thumb size proportional to visible content
|
|
8
|
-
* - Click on track to jump to position
|
|
9
|
-
* - Drag thumb to scroll
|
|
10
|
-
* - Auto-hide after idle (optional)
|
|
11
|
-
* - Show on hover with configurable hover zone (optional)
|
|
12
|
-
* - CSS variables for customization
|
|
13
|
-
* - Horizontal mode support (direction-aware axis)
|
|
14
|
-
*/
|
|
15
|
-
// =============================================================================
|
|
16
|
-
// Constants
|
|
17
|
-
// =============================================================================
|
|
18
|
-
const AUTO_HIDE = true;
|
|
19
|
-
const AUTO_HIDE_DELAY = 1000;
|
|
20
|
-
const MIN_THUMB_SIZE = 15;
|
|
21
|
-
const SHOW_ON_HOVER = true;
|
|
22
|
-
const HOVER_ZONE_REACH = 16; // px of reach beyond the visible track (added to padding for the default)
|
|
23
|
-
const SHOW_ON_VIEWPORT_ENTER = true;
|
|
24
|
-
const PADDING = 2;
|
|
25
|
-
const TRACK_CLICK_BEHAVIOR = 'scroll';
|
|
26
|
-
const PAGE_SCROLL_INITIAL_DELAY = 350; // ms before continuous scroll starts (matches keyboard repeat)
|
|
27
|
-
const PAGE_SCROLL_SPEED_PPS = 12; // pages per second during held continuous scroll
|
|
28
|
-
const resolvePadding = (raw) => {
|
|
29
|
-
// typeof !== 'object' narrows raw to number | undefined in the true branch,
|
|
30
|
-
// avoiding a narrowing gap that occurs with the equivalent (raw === undefined || typeof raw === 'number') OR form.
|
|
31
|
-
if (typeof raw !== 'object') {
|
|
32
|
-
const v = raw ?? PADDING;
|
|
33
|
-
return { top: v, right: v, bottom: v, left: v };
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
top: raw.top ?? PADDING,
|
|
37
|
-
right: raw.right ?? PADDING,
|
|
38
|
-
bottom: raw.bottom ?? PADDING,
|
|
39
|
-
left: raw.left ?? PADDING,
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
// =============================================================================
|
|
43
|
-
// Factory
|
|
44
|
-
// =============================================================================
|
|
45
|
-
/**
|
|
46
|
-
* Create a scrollbar instance
|
|
47
|
-
*
|
|
48
|
-
* @param viewport - The viewport element (used for events and CSS variables)
|
|
49
|
-
* @param onScroll - Callback when scrollbar interaction causes scroll
|
|
50
|
-
* @param config - Scrollbar configuration
|
|
51
|
-
* @param classPrefix - CSS class prefix (default: 'vlist')
|
|
52
|
-
* @param horizontal - Whether the scrollbar is horizontal (default: false)
|
|
53
|
-
* @param parent - Element to append scrollbar DOM to (default: viewport)
|
|
54
|
-
*/
|
|
55
|
-
export const createScrollbar = (viewport, onScroll, config = {}, classPrefix = "vlist", horizontal = false, parent) => {
|
|
56
|
-
const { autoHide = AUTO_HIDE, autoHideDelay = AUTO_HIDE_DELAY, minThumbSize = MIN_THUMB_SIZE, showOnHover = SHOW_ON_HOVER, showOnViewportEnter = SHOW_ON_VIEWPORT_ENTER, clickBehavior: rawClickBehavior = TRACK_CLICK_BEHAVIOR, } = config;
|
|
57
|
-
const clickBehavior = rawClickBehavior === 'page' ? 'scroll' : rawClickBehavior;
|
|
58
|
-
const attachTo = parent ?? viewport;
|
|
59
|
-
const pad = resolvePadding(config.padding);
|
|
60
|
-
// Axis-aware padding: start/end along the scroll axis, wall-side for hover zone default
|
|
61
|
-
const scrollAxisStartPad = horizontal ? pad.left : pad.top;
|
|
62
|
-
const scrollAxisEndPad = horizontal ? pad.right : pad.bottom;
|
|
63
|
-
const wallPad = horizontal ? pad.bottom : pad.right;
|
|
64
|
-
// Hover zone covers wall-gap + fixed reach beyond the track edge
|
|
65
|
-
const hoverZoneWidth = config.hoverZoneWidth ?? (wallPad + HOVER_ZONE_REACH);
|
|
66
|
-
// State
|
|
67
|
-
let totalSize = 0;
|
|
68
|
-
let containerSize = 0;
|
|
69
|
-
let thumbSize = 0;
|
|
70
|
-
let maxThumbTravel = 0;
|
|
71
|
-
let isDragging = false;
|
|
72
|
-
let isHovering = false;
|
|
73
|
-
let dragStartPos = 0;
|
|
74
|
-
let dragStartScrollPosition = 0;
|
|
75
|
-
let currentScrollPosition = 0;
|
|
76
|
-
let hideTimeout = null;
|
|
77
|
-
let visible = false;
|
|
78
|
-
let animationFrameId = null;
|
|
79
|
-
let pageClickPos = 0;
|
|
80
|
-
let pageScrollPosition = 0; // internal tracker — updated synchronously each tick
|
|
81
|
-
let repeatTimeout = null;
|
|
82
|
-
let repeatRafId = null;
|
|
83
|
-
let repeatLastTime = null;
|
|
84
|
-
// Axis helpers — select CSS property / mouse coordinate once
|
|
85
|
-
const thumbSizeProp = horizontal ? "width" : "height";
|
|
86
|
-
const translateFn = horizontal ? "translateX" : "translateY";
|
|
87
|
-
const mousePos = horizontal
|
|
88
|
-
? (e) => e.clientX
|
|
89
|
-
: (e) => e.clientY;
|
|
90
|
-
const rectStart = horizontal ? "left" : "top";
|
|
91
|
-
// DOM elements
|
|
92
|
-
const track = document.createElement("div");
|
|
93
|
-
const thumb = document.createElement("div");
|
|
94
|
-
// Always created: extends click target into the padding margin + handles hover when showOnHover
|
|
95
|
-
const hoverZone = document.createElement("div");
|
|
96
|
-
// When the scrollbar lives on root, offset its top to align with the viewport.
|
|
97
|
-
// Uses the CSS variable for padding so runtime updates via CSS take effect
|
|
98
|
-
// without rebuilding the scrollbar.
|
|
99
|
-
const startPadVar = horizontal
|
|
100
|
-
? "--vlist-custom-scrollbar-padding-left"
|
|
101
|
-
: "--vlist-custom-scrollbar-padding-top";
|
|
102
|
-
const syncTrackOffset = () => {
|
|
103
|
-
if (attachTo === viewport)
|
|
104
|
-
return;
|
|
105
|
-
const offset = viewport.offsetTop;
|
|
106
|
-
track.style[horizontal ? "left" : "top"] = offset
|
|
107
|
-
? `calc(var(${startPadVar}) + ${offset}px)`
|
|
108
|
-
: `var(${startPadVar})`;
|
|
109
|
-
hoverZone.style[horizontal ? "left" : "top"] = `${offset}px`;
|
|
110
|
-
};
|
|
111
|
-
// =============================================================================
|
|
112
|
-
// DOM Setup
|
|
113
|
-
// =============================================================================
|
|
114
|
-
const setupDOM = () => {
|
|
115
|
-
track.className = `${classPrefix}-scrollbar`;
|
|
116
|
-
thumb.className = `${classPrefix}-scrollbar__thumb`;
|
|
117
|
-
if (horizontal) {
|
|
118
|
-
track.classList.add(`${classPrefix}-scrollbar--horizontal`);
|
|
119
|
-
}
|
|
120
|
-
if (config.padding !== undefined) {
|
|
121
|
-
attachTo.style.setProperty("--vlist-custom-scrollbar-padding-top", `${pad.top}px`);
|
|
122
|
-
attachTo.style.setProperty("--vlist-custom-scrollbar-padding-right", `${pad.right}px`);
|
|
123
|
-
attachTo.style.setProperty("--vlist-custom-scrollbar-padding-bottom", `${pad.bottom}px`);
|
|
124
|
-
attachTo.style.setProperty("--vlist-custom-scrollbar-padding-left", `${pad.left}px`);
|
|
125
|
-
}
|
|
126
|
-
if (config.minThumbSize !== undefined) {
|
|
127
|
-
track.style.setProperty("--vlist-custom-scrollbar-min-thumb-size", `${minThumbSize}px`);
|
|
128
|
-
}
|
|
129
|
-
track.appendChild(thumb);
|
|
130
|
-
attachTo.appendChild(track);
|
|
131
|
-
// Edge zone — covers the padding margin + track area along the scrollbar edge.
|
|
132
|
-
// Always present so clicks in the padding margin are captured regardless of showOnHover.
|
|
133
|
-
// pointer-events:auto so events fire even when the track is hidden (opacity:0).
|
|
134
|
-
hoverZone.className = `${classPrefix}-scrollbar__hover`;
|
|
135
|
-
if (horizontal) {
|
|
136
|
-
hoverZone.classList.add(`${classPrefix}-scrollbar__hover--horizontal`);
|
|
137
|
-
hoverZone.style.height = `${hoverZoneWidth}px`;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
hoverZone.style.width = `${hoverZoneWidth}px`;
|
|
141
|
-
}
|
|
142
|
-
attachTo.appendChild(hoverZone);
|
|
143
|
-
// When attached to root instead of viewport, offset track/hover to match
|
|
144
|
-
// the viewport's position (e.g. below a sticky header).
|
|
145
|
-
if (attachTo !== viewport) {
|
|
146
|
-
syncTrackOffset();
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
// =============================================================================
|
|
150
|
-
// Hide timeout helpers
|
|
151
|
-
// =============================================================================
|
|
152
|
-
const clearHideTimeout = () => {
|
|
153
|
-
if (hideTimeout) {
|
|
154
|
-
clearTimeout(hideTimeout);
|
|
155
|
-
hideTimeout = null;
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const scheduleHide = () => {
|
|
159
|
-
if (!autoHide)
|
|
160
|
-
return;
|
|
161
|
-
clearHideTimeout();
|
|
162
|
-
hideTimeout = setTimeout(hide, autoHideDelay);
|
|
163
|
-
};
|
|
164
|
-
// =============================================================================
|
|
165
|
-
// Visibility
|
|
166
|
-
// =============================================================================
|
|
167
|
-
/**
|
|
168
|
-
* Show the scrollbar.
|
|
169
|
-
* When called from scroll events, auto-hide is scheduled (unless hovering).
|
|
170
|
-
* When called from hover events, no auto-hide is scheduled.
|
|
171
|
-
*/
|
|
172
|
-
const show = () => {
|
|
173
|
-
if (totalSize <= containerSize)
|
|
174
|
-
return;
|
|
175
|
-
clearHideTimeout();
|
|
176
|
-
if (!visible) {
|
|
177
|
-
track.classList.add(`${classPrefix}-scrollbar--visible`);
|
|
178
|
-
visible = true;
|
|
179
|
-
}
|
|
180
|
-
// Schedule auto-hide only if not hovering and not dragging
|
|
181
|
-
if (autoHide && !isDragging && !isHovering) {
|
|
182
|
-
scheduleHide();
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const hide = () => {
|
|
186
|
-
if (isDragging || isHovering)
|
|
187
|
-
return;
|
|
188
|
-
track.classList.remove(`${classPrefix}-scrollbar--visible`);
|
|
189
|
-
visible = false;
|
|
190
|
-
};
|
|
191
|
-
// =============================================================================
|
|
192
|
-
// Size & Position Calculations
|
|
193
|
-
// =============================================================================
|
|
194
|
-
const updateBounds = (newTotalSize, newContainerSize) => {
|
|
195
|
-
totalSize = newTotalSize;
|
|
196
|
-
containerSize = newContainerSize;
|
|
197
|
-
syncTrackOffset();
|
|
198
|
-
// Check if scrollbar is needed
|
|
199
|
-
if (totalSize <= containerSize) {
|
|
200
|
-
track.style.display = "none";
|
|
201
|
-
hide();
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
track.style.display = "";
|
|
205
|
-
// Effective track length shrinks by the margin on both ends (start + end along scroll axis)
|
|
206
|
-
const trackLength = Math.max(0, containerSize - scrollAxisStartPad - scrollAxisEndPad);
|
|
207
|
-
// Calculate thumb size (proportional to visible content, scaled to track)
|
|
208
|
-
const scrollRatio = containerSize / totalSize;
|
|
209
|
-
thumbSize = Math.max(minThumbSize, scrollRatio * trackLength);
|
|
210
|
-
thumb.style[thumbSizeProp] = `${thumbSize}px`;
|
|
211
|
-
// Calculate max thumb travel distance
|
|
212
|
-
maxThumbTravel = trackLength - thumbSize;
|
|
213
|
-
// Update position with current scroll
|
|
214
|
-
updatePosition(currentScrollPosition);
|
|
215
|
-
if (!autoHide) {
|
|
216
|
-
show();
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
const updatePosition = (scrollTop) => {
|
|
220
|
-
currentScrollPosition = scrollTop;
|
|
221
|
-
if (totalSize <= containerSize || maxThumbTravel <= 0)
|
|
222
|
-
return;
|
|
223
|
-
// Calculate scroll percentage
|
|
224
|
-
const maxScroll = totalSize - containerSize;
|
|
225
|
-
const scrollRatio = Math.min(1, Math.max(0, scrollTop / maxScroll));
|
|
226
|
-
// Position thumb
|
|
227
|
-
thumb.style.transform = `${translateFn}(${scrollRatio * maxThumbTravel}px)`;
|
|
228
|
-
};
|
|
229
|
-
// =============================================================================
|
|
230
|
-
// Track Click Handlers
|
|
231
|
-
// =============================================================================
|
|
232
|
-
// 'jump' — instantly center thumb at clicked position
|
|
233
|
-
const handleTrackClick = (e) => {
|
|
234
|
-
if (e.target === thumb || clickBehavior !== 'jump' || maxThumbTravel <= 0)
|
|
235
|
-
return;
|
|
236
|
-
const maxScroll = totalSize - containerSize;
|
|
237
|
-
const trackRect = track.getBoundingClientRect();
|
|
238
|
-
const clickPos = mousePos(e) - trackRect[rectStart];
|
|
239
|
-
const clampedThumbStart = Math.max(0, Math.min(clickPos - thumbSize / 2, maxThumbTravel));
|
|
240
|
-
onScroll((clampedThumbStart / maxThumbTravel) * maxScroll);
|
|
241
|
-
show();
|
|
242
|
-
};
|
|
243
|
-
const clearRepeat = () => {
|
|
244
|
-
if (repeatTimeout !== null) {
|
|
245
|
-
clearTimeout(repeatTimeout);
|
|
246
|
-
repeatTimeout = null;
|
|
247
|
-
}
|
|
248
|
-
if (repeatRafId !== null) {
|
|
249
|
-
cancelAnimationFrame(repeatRafId);
|
|
250
|
-
repeatRafId = null;
|
|
251
|
-
}
|
|
252
|
-
repeatLastTime = null;
|
|
253
|
-
};
|
|
254
|
-
// Compute direction toward pageClickPos; returns -1 (back), 1 (forward), or 0 (arrived).
|
|
255
|
-
// Caller must provide maxScroll to avoid recomputing it.
|
|
256
|
-
const pageScrollDirection = (maxScroll) => {
|
|
257
|
-
const thumbCurrentStart = maxThumbTravel > 0 ? (pageScrollPosition / maxScroll) * maxThumbTravel : 0;
|
|
258
|
-
if (pageClickPos < thumbCurrentStart)
|
|
259
|
-
return -1;
|
|
260
|
-
if (pageClickPos >= thumbCurrentStart + thumbSize)
|
|
261
|
-
return 1;
|
|
262
|
-
return 0;
|
|
263
|
-
};
|
|
264
|
-
// 'scroll' — immediate first scroll by one containerSize toward click
|
|
265
|
-
const firePageScroll = () => {
|
|
266
|
-
const maxScroll = totalSize - containerSize;
|
|
267
|
-
const dir = pageScrollDirection(maxScroll);
|
|
268
|
-
if (dir === 0) {
|
|
269
|
-
clearRepeat();
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
if (dir === -1) {
|
|
273
|
-
if (pageScrollPosition <= 0) {
|
|
274
|
-
clearRepeat();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const newPos = Math.max(0, pageScrollPosition - containerSize);
|
|
278
|
-
pageScrollPosition = newPos;
|
|
279
|
-
onScroll(newPos);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
if (pageScrollPosition >= maxScroll) {
|
|
283
|
-
clearRepeat();
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const newPos = Math.min(maxScroll, pageScrollPosition + containerSize);
|
|
287
|
-
pageScrollPosition = newPos;
|
|
288
|
-
onScroll(newPos);
|
|
289
|
-
}
|
|
290
|
-
show();
|
|
291
|
-
};
|
|
292
|
-
// Continuous RAF loop — runs after initial delay while mouse is held
|
|
293
|
-
const tickContinuousScroll = (timestamp) => {
|
|
294
|
-
// First frame: record baseline time and reschedule without scrolling
|
|
295
|
-
if (repeatLastTime === null) {
|
|
296
|
-
repeatLastTime = timestamp;
|
|
297
|
-
repeatRafId = requestAnimationFrame(tickContinuousScroll);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
const maxScroll = totalSize - containerSize;
|
|
301
|
-
const dt = timestamp - repeatLastTime;
|
|
302
|
-
repeatLastTime = timestamp;
|
|
303
|
-
const dir = pageScrollDirection(maxScroll);
|
|
304
|
-
if (dir === 0) {
|
|
305
|
-
clearRepeat();
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
// Speed in px/ms, capped so one frame never overshoots more than containerSize
|
|
309
|
-
const speed = (PAGE_SCROLL_SPEED_PPS * containerSize) / 1000;
|
|
310
|
-
const delta = Math.min(speed * dt, containerSize);
|
|
311
|
-
if (dir === -1) {
|
|
312
|
-
if (pageScrollPosition <= 0) {
|
|
313
|
-
clearRepeat();
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const newPos = Math.max(0, pageScrollPosition - delta);
|
|
317
|
-
pageScrollPosition = newPos;
|
|
318
|
-
onScroll(newPos);
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
if (pageScrollPosition >= maxScroll) {
|
|
322
|
-
clearRepeat();
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const newPos = Math.min(maxScroll, pageScrollPosition + delta);
|
|
326
|
-
pageScrollPosition = newPos;
|
|
327
|
-
onScroll(newPos);
|
|
328
|
-
}
|
|
329
|
-
// Keep scrollbar visible without creating a new hide timer every frame
|
|
330
|
-
clearHideTimeout();
|
|
331
|
-
repeatRafId = requestAnimationFrame(tickContinuousScroll);
|
|
332
|
-
};
|
|
333
|
-
const handleRepeatMouseUp = () => {
|
|
334
|
-
clearRepeat();
|
|
335
|
-
// Begin auto-hide now that the hold has ended
|
|
336
|
-
scheduleHide();
|
|
337
|
-
document.removeEventListener('mouseup', handleRepeatMouseUp);
|
|
338
|
-
};
|
|
339
|
-
// 'scroll' — immediate first scroll then smooth continuous scroll while held
|
|
340
|
-
const handleTrackMouseDown = (e) => {
|
|
341
|
-
if (e.target === thumb || clickBehavior !== 'scroll')
|
|
342
|
-
return;
|
|
343
|
-
e.preventDefault();
|
|
344
|
-
const trackRect = track.getBoundingClientRect();
|
|
345
|
-
pageClickPos = mousePos(e) - trackRect[rectStart];
|
|
346
|
-
pageScrollPosition = currentScrollPosition;
|
|
347
|
-
firePageScroll();
|
|
348
|
-
// After initial delay, begin smooth RAF-driven continuous scroll
|
|
349
|
-
repeatTimeout = setTimeout(() => {
|
|
350
|
-
repeatRafId = requestAnimationFrame(tickContinuousScroll);
|
|
351
|
-
}, PAGE_SCROLL_INITIAL_DELAY);
|
|
352
|
-
document.addEventListener('mouseup', handleRepeatMouseUp);
|
|
353
|
-
};
|
|
354
|
-
// =============================================================================
|
|
355
|
-
// Thumb Drag Handlers
|
|
356
|
-
// =============================================================================
|
|
357
|
-
const handleThumbMouseDown = (e) => {
|
|
358
|
-
e.preventDefault();
|
|
359
|
-
e.stopPropagation();
|
|
360
|
-
isDragging = true;
|
|
361
|
-
dragStartPos = mousePos(e);
|
|
362
|
-
dragStartScrollPosition = currentScrollPosition;
|
|
363
|
-
// Cancel any hide while dragging
|
|
364
|
-
clearHideTimeout();
|
|
365
|
-
track.classList.add(`${classPrefix}-scrollbar--dragging`);
|
|
366
|
-
document.addEventListener("mousemove", handleMouseMove);
|
|
367
|
-
document.addEventListener("mouseup", handleMouseUp);
|
|
368
|
-
};
|
|
369
|
-
const handleMouseMove = (e) => {
|
|
370
|
-
if (!isDragging)
|
|
371
|
-
return;
|
|
372
|
-
const delta = mousePos(e) - dragStartPos;
|
|
373
|
-
// Convert thumb movement to scroll movement
|
|
374
|
-
const scrollRatio = maxThumbTravel > 0 ? delta / maxThumbTravel : 0;
|
|
375
|
-
const maxScroll = totalSize - containerSize;
|
|
376
|
-
const deltaScroll = scrollRatio * maxScroll;
|
|
377
|
-
const newPosition = Math.max(0, Math.min(dragStartScrollPosition + deltaScroll, maxScroll));
|
|
378
|
-
// Update thumb immediately for responsive feel
|
|
379
|
-
thumb.style.transform = `${translateFn}(${(newPosition / maxScroll) * maxThumbTravel}px)`;
|
|
380
|
-
// Call synchronously — rAF throttle causes a one-frame lag because
|
|
381
|
-
// the HTML spec processes scroll events BEFORE rAF callbacks, so the
|
|
382
|
-
// pipeline would always render for the previous frame's scroll position.
|
|
383
|
-
onScroll(newPosition);
|
|
384
|
-
};
|
|
385
|
-
const handleMouseUp = () => {
|
|
386
|
-
isDragging = false;
|
|
387
|
-
track.classList.remove(`${classPrefix}-scrollbar--dragging`);
|
|
388
|
-
// Schedule auto-hide only if not hovering
|
|
389
|
-
if (autoHide && !isHovering) {
|
|
390
|
-
scheduleHide();
|
|
391
|
-
}
|
|
392
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
393
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
394
|
-
};
|
|
395
|
-
// =============================================================================
|
|
396
|
-
// Touch Handlers (thumb drag)
|
|
397
|
-
// =============================================================================
|
|
398
|
-
const touchPos = horizontal
|
|
399
|
-
? (e) => e.touches[0].clientX
|
|
400
|
-
: (e) => e.touches[0].clientY;
|
|
401
|
-
const handleThumbTouchStart = (e) => {
|
|
402
|
-
e.stopPropagation();
|
|
403
|
-
isDragging = true;
|
|
404
|
-
dragStartPos = touchPos(e);
|
|
405
|
-
dragStartScrollPosition = currentScrollPosition;
|
|
406
|
-
clearHideTimeout();
|
|
407
|
-
track.classList.add(`${classPrefix}-scrollbar--dragging`);
|
|
408
|
-
};
|
|
409
|
-
const handleThumbTouchMove = (e) => {
|
|
410
|
-
if (!isDragging)
|
|
411
|
-
return;
|
|
412
|
-
e.preventDefault();
|
|
413
|
-
const delta = touchPos(e) - dragStartPos;
|
|
414
|
-
const scrollRatio = maxThumbTravel > 0 ? delta / maxThumbTravel : 0;
|
|
415
|
-
const maxScroll = totalSize - containerSize;
|
|
416
|
-
const deltaScroll = scrollRatio * maxScroll;
|
|
417
|
-
const newPosition = Math.max(0, Math.min(dragStartScrollPosition + deltaScroll, maxScroll));
|
|
418
|
-
thumb.style.transform = `${translateFn}(${(newPosition / maxScroll) * maxThumbTravel}px)`;
|
|
419
|
-
onScroll(newPosition);
|
|
420
|
-
};
|
|
421
|
-
const handleThumbTouchEnd = () => {
|
|
422
|
-
isDragging = false;
|
|
423
|
-
track.classList.remove(`${classPrefix}-scrollbar--dragging`);
|
|
424
|
-
if (autoHide) {
|
|
425
|
-
scheduleHide();
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
const handleTrackTouchStart = (e) => {
|
|
429
|
-
if (e.target === thumb)
|
|
430
|
-
return;
|
|
431
|
-
if (maxThumbTravel <= 0)
|
|
432
|
-
return;
|
|
433
|
-
const touch = e.touches[0];
|
|
434
|
-
const trackRect = track.getBoundingClientRect();
|
|
435
|
-
const pos = (horizontal ? touch.clientX : touch.clientY) - trackRect[rectStart];
|
|
436
|
-
if (clickBehavior === 'jump') {
|
|
437
|
-
const maxScroll = totalSize - containerSize;
|
|
438
|
-
const clampedThumbStart = Math.max(0, Math.min(pos - thumbSize / 2, maxThumbTravel));
|
|
439
|
-
onScroll((clampedThumbStart / maxThumbTravel) * maxScroll);
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
pageClickPos = pos;
|
|
443
|
-
pageScrollPosition = currentScrollPosition;
|
|
444
|
-
firePageScroll();
|
|
445
|
-
}
|
|
446
|
-
show();
|
|
447
|
-
};
|
|
448
|
-
// =============================================================================
|
|
449
|
-
// Viewport Hover Handlers (show on hover)
|
|
450
|
-
// =============================================================================
|
|
451
|
-
const handleViewportEnter = () => {
|
|
452
|
-
if (showOnViewportEnter) {
|
|
453
|
-
show();
|
|
454
|
-
}
|
|
455
|
-
};
|
|
456
|
-
const handleViewportLeave = () => {
|
|
457
|
-
if (!isDragging) {
|
|
458
|
-
isHovering = false;
|
|
459
|
-
if (autoHide) {
|
|
460
|
-
scheduleHide();
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
// =============================================================================
|
|
465
|
-
// Scrollbar Hover Handlers (keep visible while hovering over scrollbar area)
|
|
466
|
-
//
|
|
467
|
-
// Both the track and the hover zone set isHovering = true.
|
|
468
|
-
// While isHovering is true, show() will NOT schedule auto-hide,
|
|
469
|
-
// and hide() will refuse to run.
|
|
470
|
-
// =============================================================================
|
|
471
|
-
const handleScrollbarAreaEnter = () => {
|
|
472
|
-
isHovering = true;
|
|
473
|
-
clearHideTimeout();
|
|
474
|
-
show();
|
|
475
|
-
};
|
|
476
|
-
const handleScrollbarAreaLeave = () => {
|
|
477
|
-
isHovering = false;
|
|
478
|
-
if (!isDragging && autoHide) {
|
|
479
|
-
scheduleHide();
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
// =============================================================================
|
|
483
|
-
// Cleanup
|
|
484
|
-
// =============================================================================
|
|
485
|
-
const destroy = () => {
|
|
486
|
-
// Clear timers
|
|
487
|
-
clearHideTimeout();
|
|
488
|
-
clearRepeat();
|
|
489
|
-
if (animationFrameId !== null) {
|
|
490
|
-
cancelAnimationFrame(animationFrameId);
|
|
491
|
-
animationFrameId = null;
|
|
492
|
-
}
|
|
493
|
-
// Remove event listeners
|
|
494
|
-
track.removeEventListener("click", handleTrackClick);
|
|
495
|
-
track.removeEventListener("mousedown", handleTrackMouseDown);
|
|
496
|
-
track.removeEventListener("mouseenter", handleScrollbarAreaEnter);
|
|
497
|
-
document.removeEventListener("mouseup", handleRepeatMouseUp);
|
|
498
|
-
track.removeEventListener("mouseleave", handleScrollbarAreaLeave);
|
|
499
|
-
track.removeEventListener("touchstart", handleTrackTouchStart);
|
|
500
|
-
thumb.removeEventListener("mousedown", handleThumbMouseDown);
|
|
501
|
-
thumb.removeEventListener("touchstart", handleThumbTouchStart);
|
|
502
|
-
thumb.removeEventListener("touchmove", handleThumbTouchMove);
|
|
503
|
-
thumb.removeEventListener("touchend", handleThumbTouchEnd);
|
|
504
|
-
viewport.removeEventListener("mouseenter", handleViewportEnter);
|
|
505
|
-
viewport.removeEventListener("mouseleave", handleViewportLeave);
|
|
506
|
-
document.removeEventListener("mousemove", handleMouseMove);
|
|
507
|
-
document.removeEventListener("mouseup", handleMouseUp);
|
|
508
|
-
hoverZone.removeEventListener("click", handleTrackClick);
|
|
509
|
-
hoverZone.removeEventListener("mousedown", handleTrackMouseDown);
|
|
510
|
-
hoverZone.removeEventListener("mouseenter", handleScrollbarAreaEnter);
|
|
511
|
-
hoverZone.removeEventListener("mouseleave", handleScrollbarAreaLeave);
|
|
512
|
-
hoverZone.remove();
|
|
513
|
-
// Remove inline CSS variable overrides
|
|
514
|
-
attachTo.style.removeProperty("--vlist-custom-scrollbar-padding-top");
|
|
515
|
-
attachTo.style.removeProperty("--vlist-custom-scrollbar-padding-right");
|
|
516
|
-
attachTo.style.removeProperty("--vlist-custom-scrollbar-padding-bottom");
|
|
517
|
-
attachTo.style.removeProperty("--vlist-custom-scrollbar-padding-left");
|
|
518
|
-
// Remove DOM elements
|
|
519
|
-
track.remove();
|
|
520
|
-
};
|
|
521
|
-
// =============================================================================
|
|
522
|
-
// Initialize
|
|
523
|
-
// =============================================================================
|
|
524
|
-
setupDOM();
|
|
525
|
-
// Attach event listeners
|
|
526
|
-
track.addEventListener("click", handleTrackClick);
|
|
527
|
-
track.addEventListener("mousedown", handleTrackMouseDown);
|
|
528
|
-
track.addEventListener("mouseenter", handleScrollbarAreaEnter);
|
|
529
|
-
track.addEventListener("mouseleave", handleScrollbarAreaLeave);
|
|
530
|
-
track.addEventListener("touchstart", handleTrackTouchStart, { passive: true });
|
|
531
|
-
thumb.addEventListener("mousedown", handleThumbMouseDown);
|
|
532
|
-
thumb.addEventListener("touchstart", handleThumbTouchStart, { passive: true });
|
|
533
|
-
thumb.addEventListener("touchmove", handleThumbTouchMove, { passive: false });
|
|
534
|
-
thumb.addEventListener("touchend", handleThumbTouchEnd, { passive: true });
|
|
535
|
-
viewport.addEventListener("mouseenter", handleViewportEnter);
|
|
536
|
-
viewport.addEventListener("mouseleave", handleViewportLeave);
|
|
537
|
-
// Always: clicks in the padding margin behave the same as track clicks
|
|
538
|
-
hoverZone.addEventListener("click", handleTrackClick);
|
|
539
|
-
hoverZone.addEventListener("mousedown", handleTrackMouseDown);
|
|
540
|
-
// Conditional: hover-to-reveal behavior
|
|
541
|
-
if (showOnHover) {
|
|
542
|
-
hoverZone.addEventListener("mouseenter", handleScrollbarAreaEnter);
|
|
543
|
-
hoverZone.addEventListener("mouseleave", handleScrollbarAreaLeave);
|
|
544
|
-
}
|
|
545
|
-
// =============================================================================
|
|
546
|
-
// Public API
|
|
547
|
-
// =============================================================================
|
|
548
|
-
return {
|
|
549
|
-
show,
|
|
550
|
-
hide,
|
|
551
|
-
updateBounds,
|
|
552
|
-
updatePosition,
|
|
553
|
-
isVisible: () => visible,
|
|
554
|
-
destroy,
|
|
555
|
-
};
|
|
556
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vlist v2 — Selection Plugin
|
|
3
|
-
*/
|
|
4
|
-
export { selection } from "./plugin";
|
|
5
|
-
export { createSelectionState, selectOne, toggleOne, selectRange, selectRangeMut, selectAllItems, moveFocus, getSelectedArray, getSelectedItems, getSelectedItemsMut,
|
|
6
|
-
// v1 immutable API (re-exported for backwards compat)
|
|
7
|
-
selectItems, deselectItems, toggleSelection, selectAll, clearSelection, isSelected, getSelectedIds, getSelectionCount, isSelectionEmpty, selectFocused, moveFocusUp, moveFocusDown, moveFocusToFirst, moveFocusToLast, moveFocusByPage, claimPlaceholderSelection, setFocusedIndex, } from "./state";
|