vlist 2.0.0 → 2.0.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.
Files changed (91) hide show
  1. package/README.github.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/core/dom.d.ts +1 -1
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/pipeline.d.ts +2 -2
  6. package/dist/core/scroll.d.ts +1 -1
  7. package/dist/core/types.d.ts +7 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -28
  10. package/dist/internals.js +1 -60
  11. package/dist/plugins/scrollbar/controller.d.ts +3 -3
  12. package/dist/plugins/scrollbar/scrollbar.d.ts +2 -2
  13. package/dist/rendering/renderer.d.ts +2 -2
  14. package/dist/rendering/viewport.d.ts +1 -1
  15. package/dist/size.json +1 -1
  16. package/dist/types.d.ts +1 -1
  17. package/package.json +1 -1
  18. package/dist/constants.js +0 -83
  19. package/dist/core/create.js +0 -740
  20. package/dist/core/dom.js +0 -47
  21. package/dist/core/hooks.js +0 -67
  22. package/dist/core/index.js +0 -13
  23. package/dist/core/pipeline.js +0 -307
  24. package/dist/core/pool.js +0 -42
  25. package/dist/core/scroll.js +0 -137
  26. package/dist/core/sizes.js +0 -6
  27. package/dist/core/state.js +0 -56
  28. package/dist/core/types.js +0 -7
  29. package/dist/core/velocity.js +0 -33
  30. package/dist/events/emitter.js +0 -60
  31. package/dist/events/index.js +0 -6
  32. package/dist/plugins/a11y/index.js +0 -1
  33. package/dist/plugins/a11y/plugin.js +0 -259
  34. package/dist/plugins/async/index.js +0 -12
  35. package/dist/plugins/async/manager.js +0 -568
  36. package/dist/plugins/async/placeholder.js +0 -154
  37. package/dist/plugins/async/plugin.js +0 -311
  38. package/dist/plugins/async/sparse.js +0 -540
  39. package/dist/plugins/autosize/index.js +0 -4
  40. package/dist/plugins/autosize/plugin.js +0 -185
  41. package/dist/plugins/grid/index.js +0 -5
  42. package/dist/plugins/grid/layout.js +0 -275
  43. package/dist/plugins/grid/plugin.js +0 -347
  44. package/dist/plugins/grid/renderer.js +0 -525
  45. package/dist/plugins/grid/types.js +0 -11
  46. package/dist/plugins/groups/async-bridge.js +0 -246
  47. package/dist/plugins/groups/index.js +0 -13
  48. package/dist/plugins/groups/layout.js +0 -294
  49. package/dist/plugins/groups/plugin.js +0 -571
  50. package/dist/plugins/groups/sticky.js +0 -255
  51. package/dist/plugins/groups/types.js +0 -12
  52. package/dist/plugins/masonry/index.js +0 -6
  53. package/dist/plugins/masonry/layout.js +0 -261
  54. package/dist/plugins/masonry/plugin.js +0 -381
  55. package/dist/plugins/masonry/renderer.js +0 -354
  56. package/dist/plugins/masonry/types.js +0 -9
  57. package/dist/plugins/page/index.js +0 -5
  58. package/dist/plugins/page/plugin.js +0 -166
  59. package/dist/plugins/scale/index.js +0 -4
  60. package/dist/plugins/scale/plugin.js +0 -507
  61. package/dist/plugins/scrollbar/controller.js +0 -574
  62. package/dist/plugins/scrollbar/index.js +0 -6
  63. package/dist/plugins/scrollbar/plugin.js +0 -93
  64. package/dist/plugins/scrollbar/scrollbar.js +0 -556
  65. package/dist/plugins/selection/index.js +0 -7
  66. package/dist/plugins/selection/plugin.js +0 -601
  67. package/dist/plugins/selection/state.js +0 -332
  68. package/dist/plugins/snapshots/index.js +0 -5
  69. package/dist/plugins/snapshots/plugin.js +0 -301
  70. package/dist/plugins/sortable/index.js +0 -6
  71. package/dist/plugins/sortable/plugin.js +0 -753
  72. package/dist/plugins/table/header.js +0 -501
  73. package/dist/plugins/table/index.js +0 -12
  74. package/dist/plugins/table/layout.js +0 -211
  75. package/dist/plugins/table/plugin.js +0 -391
  76. package/dist/plugins/table/renderer.js +0 -625
  77. package/dist/plugins/table/types.js +0 -12
  78. package/dist/plugins/transition/index.js +0 -5
  79. package/dist/plugins/transition/plugin.js +0 -405
  80. package/dist/rendering/aria.js +0 -23
  81. package/dist/rendering/index.js +0 -18
  82. package/dist/rendering/measured.js +0 -98
  83. package/dist/rendering/renderer.js +0 -586
  84. package/dist/rendering/scale.js +0 -267
  85. package/dist/rendering/scroll.js +0 -71
  86. package/dist/rendering/sizes.js +0 -193
  87. package/dist/rendering/sort.js +0 -65
  88. package/dist/rendering/viewport.js +0 -268
  89. package/dist/types.js +0 -5
  90. package/dist/utils/padding.js +0 -49
  91. 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";