vlist 3.0.0-next.1 → 3.0.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.github.md +111 -65
  2. package/README.md +70 -54
  3. package/dist/config.d.ts +105 -14
  4. package/dist/config.js +1 -1
  5. package/dist/core/create.d.ts +22 -5
  6. package/dist/core/dom.d.ts +1 -1
  7. package/dist/core/hooks.d.ts +1 -1
  8. package/dist/core/index.d.ts +1 -1
  9. package/dist/core/pipeline.d.ts +1 -1
  10. package/dist/core/pool.d.ts +1 -1
  11. package/dist/core/runway.d.ts +6 -2
  12. package/dist/core/scroll.d.ts +1 -1
  13. package/dist/core/sizes.d.ts +1 -1
  14. package/dist/core/state.d.ts +13 -1
  15. package/dist/core/types.d.ts +134 -62
  16. package/dist/core/velocity.d.ts +1 -1
  17. package/dist/index.d.ts +17 -2
  18. package/dist/index.js +1 -1
  19. package/dist/internals.d.ts +0 -1
  20. package/dist/internals.js +1 -1
  21. package/dist/native.d.ts +6 -10
  22. package/dist/native.js +1 -1
  23. package/dist/plugins/a11y/index.d.ts +1 -0
  24. package/dist/plugins/a11y/plugin.d.ts +1 -1
  25. package/dist/plugins/autosize/index.d.ts +2 -1
  26. package/dist/plugins/autosize/plugin.d.ts +13 -2
  27. package/dist/plugins/carousel/engine.d.ts +1 -1
  28. package/dist/plugins/carousel/index.d.ts +1 -0
  29. package/dist/plugins/carousel/plugin.d.ts +30 -2
  30. package/dist/plugins/carousel/presets.d.ts +1 -1
  31. package/dist/plugins/data/index.d.ts +1 -0
  32. package/dist/plugins/data/plugin.d.ts +15 -2
  33. package/dist/plugins/grid/index.d.ts +2 -1
  34. package/dist/plugins/grid/plugin.d.ts +14 -2
  35. package/dist/plugins/groups/index.d.ts +1 -0
  36. package/dist/plugins/groups/plugin.d.ts +11 -3
  37. package/dist/plugins/groups/types.d.ts +6 -1
  38. package/dist/plugins/masonry/index.d.ts +2 -1
  39. package/dist/plugins/masonry/plugin.d.ts +10 -2
  40. package/dist/plugins/masonry/renderer.d.ts +5 -1
  41. package/dist/plugins/page/plugin.d.ts +1 -1
  42. package/dist/plugins/scrollbar/index.d.ts +2 -1
  43. package/dist/plugins/scrollbar/plugin.d.ts +7 -2
  44. package/dist/plugins/search/index.d.ts +1 -0
  45. package/dist/plugins/search/plugin.d.ts +26 -1
  46. package/dist/plugins/selection/index.d.ts +2 -1
  47. package/dist/plugins/selection/plugin.d.ts +23 -2
  48. package/dist/plugins/snapshots/index.d.ts +1 -0
  49. package/dist/plugins/snapshots/plugin.d.ts +9 -2
  50. package/dist/plugins/sortable/index.d.ts +1 -0
  51. package/dist/plugins/sortable/plugin.d.ts +11 -3
  52. package/dist/plugins/table/index.d.ts +1 -0
  53. package/dist/plugins/table/plugin.d.ts +19 -3
  54. package/dist/plugins/transition/plugin.d.ts +1 -1
  55. package/dist/plugins/tree/index.d.ts +1 -0
  56. package/dist/plugins/tree/layout.d.ts +1 -1
  57. package/dist/plugins/tree/plugin.d.ts +37 -3
  58. package/dist/plugins/tree/types.d.ts +1 -1
  59. package/dist/rendering/sizes.d.ts +7 -1
  60. package/dist/size.json +1 -1
  61. package/dist/synthetic.d.ts +3 -3
  62. package/dist/synthetic.js +1 -1
  63. package/dist/types.d.ts +17 -21
  64. package/dist/utils/scroll-padding.d.ts +36 -0
  65. package/dist/utils/stats.d.ts +9 -1
  66. package/dist/vlist.css +1 -1
  67. package/package.json +6 -2
  68. package/dist/plugins/grid/renderer.d.ts +0 -68
package/README.github.md CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v3.0.0-next.1** (prerelease on the npm `next` tag) — Synthetic input by default, native input through `vlist/native`, and removal of the deprecated scroll configuration and plugin hooks. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
5
+ **v3.0.0-next.3** (prerelease on the npm `next` tag) — Native scrolling by default, opt-in `vlist/synthetic`, and removal of deprecated scroll configuration and plugin hooks. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
8
  [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
9
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
10
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](https://github.com/floor/vlist/blob/main/LICENSE)
11
11
 
12
- - **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
12
+ - **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
13
13
  - **Zero dependencies** — framework-agnostic core with tiny adapters for Vue, Svelte, Solid, React
14
- - **11.4 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
14
+ - **9.5 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
15
15
  - **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
16
16
  - **Tree, grid, masonry, carousel, table, groups, data, selection, search, sortable, transition** — all opt-in
17
17
  - **Axis-neutral** — vertical and horizontal scrolling through a single code path, all plugins work in both orientations
@@ -22,7 +22,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
22
22
 
23
23
  | | vlist | TanStack Virtual | react-virtuoso | virtua | vue-virtual-scroller |
24
24
  |---|---|---|---|---|---|
25
- | **A11y built-in** | WAI-ARIA + 2D keyboard | None (DIY) | Partial | Minimal | None |
25
+ | **A11y** | WAI-ARIA + 2D keyboard, one plugin | None (DIY) | Partial | Minimal | None |
26
26
  | **Grid + Masonry + Table** | All | Grid only | Grid + Table | Grid only | None |
27
27
  | **Vue** | 0.6 KB adapter | Yes | — | Yes | 11.8 KB |
28
28
  | **Svelte** | 0.5 KB adapter | Yes | — | Yes | — |
@@ -46,11 +46,11 @@ npm install vlist@next # 3.0 prerelease; latest stays on 2.8
46
46
  npm install vlist vlist-vue # or vlist-svelte / vlist-solidjs / vlist-react
47
47
  ```
48
48
 
49
- Adapters use synthetic input by default. With an adapter that forwards `factory`, select native input explicitly:
49
+ Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
50
50
 
51
51
  ```ts
52
52
  import { useVList } from "vlist-react";
53
- import { createVList } from "vlist/native";
53
+ import { createVList } from "vlist/synthetic";
54
54
 
55
55
  useVList({
56
56
  factory: createVList,
@@ -59,7 +59,7 @@ useVList({
59
59
  });
60
60
  ```
61
61
 
62
- The same factory option is available to the other adapters. `vlist/config` defaults to synthetic input. The factory is structural configuration: changing it requires recreating the list.
62
+ The same factory option is available to the other adapters. `vlist/config` defaults to native scrolling. The factory is structural configuration: changing it requires recreating the list.
63
63
 
64
64
  ## Quick Start
65
65
 
@@ -108,10 +108,21 @@ const list = createVList({
108
108
 
109
109
  ## Scroll input
110
110
 
111
- Synthetic scrolling is the default in 3.0. Import `createVList` and plugins from `vlist`; there is no `scroll.mode` option. The deprecated `vlist/synthetic` entry remains an alias of the same factory.
111
+ Native scrolling is the default: import `createVList` and plugins from `vlist`. This preserves browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. Carousel and sortable work with this factory; horizontal RTL lists are rejected by both entries. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
112
+
113
+ | Entry | Input model | Use |
114
+ |---|---|---|
115
+ | `vlist` | Native (default) | Browser scrolling and all supported plugins |
116
+ | `vlist/synthetic` | Synthetic (opt-in) | Huge lists and application-owned touch motion |
117
+ | `vlist/native` | Alias of `vlist` (deprecated) | Compatibility with `3.0.0-next.1`; use `vlist` for new code |
118
+
119
+ ### Synthetic input
120
+
121
+ Select synthetic input by importing its factory; there is no `scroll.mode` option. Plugins still come from `vlist`.
112
122
 
113
123
  ```typescript
114
- import { createVList, scrollbar } from 'vlist'
124
+ import { createVList } from 'vlist/synthetic'
125
+ import { scrollbar } from 'vlist'
115
126
  import 'vlist/styles'
116
127
 
117
128
  const list = createVList({
@@ -121,69 +132,75 @@ const list = createVList({
121
132
  }, [scrollbar()])
122
133
  ```
123
134
 
124
- For native scrolling, import the factory from `vlist/native` and plugins from `vlist`. Native scrolling is required for `carousel()`, `sortable()`, and horizontal RTL lists; configuring these with the default entry throws. The native entry preserves carousel wrapping through a private runway implementation. Bounded scrolling is no longer a public mode.
135
+ `page()` uses native document scrolling through an external source with either entry. Its content must fit the 16,777,216 px document element limit; creation throws above that limit when the size is known, and later growth warns once. A deferred custom renderer whose size is first committed during rendering also warns once. Use `vlist/synthetic` viewport scrolling for larger lists. Default native viewport lists warn once when content exceeds their browser-size safety limit, pointing to that entry.
125
136
 
126
- ```typescript
127
- import { createVList } from 'vlist/native'
128
- import { carousel } from 'vlist'
137
+ `carousel()` works with either entry. Synthetic carousel folds preserve touch motion and smooth snapping across whole laps. `page()` and carousel remain incompatible. The shared carousel plugin includes its native wrap handler even when used with synthetic input; the combined size is reported below.
129
138
 
130
- const list = createVList({
131
- container: '#slides',
132
- orientation: 'horizontal',
133
- items: slides,
134
- item: { width: 320, template: renderSlide },
135
- }, [carousel()])
136
- ```
139
+ Synthetic input limitations:
137
140
 
138
- `page()` uses native document scrolling through an external source with either entry. Its content must fit the 16,777,216 px document element limit; creation throws above that limit when the size is known, and later growth warns once. A deferred custom renderer whose size is first committed during rendering also warns once. Use default viewport scrolling for larger lists. Native viewport lists warn once when content exceeds their browser-size safety limit.
139
-
140
- Known limitations:
141
-
142
- - Horizontal RTL lists require `vlist/native`. Vertical lists and tables support `dir="rtl"` on the container, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
143
- - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use `vlist/native` when boundary gestures must scroll the parent page.
144
- - The default entry has no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar; native visibility options belong to `vlist/native`.
141
+ - Horizontal RTL lists throw at creation — in this entry and in `vlist`. Vertical lists and tables support `dir="rtl"` in both, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
142
+ - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use the native default when boundary gestures must scroll the parent page.
143
+ - There is no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar. The synthetic entry rejects the `"native"` and `"none"` scrollbar strings.
145
144
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
146
145
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
147
146
 
148
- Measurement corrections from autosize preserve ongoing motion. Existing plugin conflicts still apply. See the [scroll input contract](https://vlist.io/docs/rfcs/RFC-014-Scroll-Input-Model).
147
+ Measurement corrections from autosize preserve ongoing synthetic motion. Existing plugin conflicts still apply. See the [scroll input contract](https://vlist.io/docs/rfcs/RFC-014-Scroll-Input-Model).
148
+
149
+ ### Sortable gestures
150
+
151
+ `sortable()` works with either input entry. Mouse and trackpad dragging still starts after `dragThreshold` (5 px by default). Touch and pen without a handle use `touchDelay`, a 350 ms long press; moving at least `dragThreshold` pixels in any direction before it completes yields to scrolling. This delay separates a deliberate hold from a quick flick. With `handle`, dragging starts at the threshold without waiting; touches elsewhere scroll.
152
+
153
+ ```typescript
154
+ sortable({ touchDelay: 350, dragThreshold: 5 })
155
+ sortable({ handle: '.drag-handle' })
156
+ ```
157
+
158
+ A touch during scrolling catches the motion and does not arm a hold for that contact. Lift and press again once the list is at rest. A second finger or `pointercancel` cancels the pending press or active drag. Edge auto-scroll uses the list's logical position and stops on drop.
159
+
160
+ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts on the pressed item. A claimed touch/pen drag adds `.vlist-sort-ghost--touch` to the ghost as a visual cue; override that class in CSS to customize it. Keyboard reordering remains Space to grab/drop, arrows to move and Escape to cancel.
149
161
 
150
162
  ## Migrating to 3.0
151
163
 
152
- | Removed public API | Replacement |
164
+ `3.0.0-next.1` used synthetic input by default. `3.0.0-next.2` restores the 2.x default. Import `vlist/synthetic` explicitly to retain synthetic behavior across these prereleases. `vlist/native` remains a deprecated compatibility alias; its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
165
+
166
+ | 2.x option or API | 3.0 replacement |
153
167
  |---|---|
154
- | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides synthetic input; import `vlist/native` for native scrolling. |
155
- | `scroll.runway` (both entries) | Remove it. Default synthetic scrolling supports huge lists without a native runway. |
156
- | Core `scroll.scrollbar: "native"` or `"none"` | Use `scrollbar()` with `vlist`, or select `vlist/native` to retain either string. Native types are exported as `NativeScrollConfig` and `NativeCreateVListConfig`. |
157
- | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
158
- | `scale()` and `ScalePluginConfig` | Remove it; the default entry supports the full logical range. `vlist/config` no longer installs a scale stub. |
168
+ | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides native scrolling; import `vlist/synthetic` for synthetic input. |
169
+ | `scroll.runway` (both entries) | Remove it. For huge lists use `vlist/synthetic`, which needs no native runway. |
170
+ | `scroll.scrollbar: "native"` or `"none"` | Retained in default `ScrollConfig`. With synthetic input, omit the string and optionally install `scrollbar()`. |
171
+ | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `ctx.scroll.setSource` to supply an external position source and commit callback. |
172
+ | Flat `PluginContext` members (`registerMethod`, `forceRender`, `sizeCache`, `scrollTo`, …) | Grouped by capability: `ctx.hooks.method`, `ctx.render.force`, `ctx.sizes.cache`, `ctx.scroll.to`, `ctx.items.all`. `pool`, `config`, `emitter`, `template` and `getState()` stay at the top level. |
173
+ | `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
159
174
 
160
- Removed scroll options throw a migration error before creating DOM. `vlist/config` retains its scrollbar omission/options convenience and its top-level `scrollbar: "none"` option; native visibility strings require an injected native factory. `baseOffset` remains private engine state for input providers; plugins continue to use `ctx.scroll.getRenderOrigin()`. No other public plugin hooks or adapter methods are removed.
175
+ Removed mode/runway options throw a migration error before creating DOM. `vlist/config` wires only what the config asks for: `selection`, the custom scrollbar, `snapshots` and `a11y` are each opt-in, so an adapter list matches a core list built from the same options. It keeps the scrollbar options convenience and its top-level `scrollbar: "none"`; pass `scrollbar: true` for the overlay scrollbar earlier versions added to every list. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.scroll.setBoundedWrap`.
161
176
 
162
177
  ## Plugins
163
178
 
164
179
  | Entry / export | Minified | Gzipped |
165
180
  |---|---:|---:|
166
- | **Base (`vlist`)** | 31.1 KB | 11.4 KB |
167
- | `vlist/synthetic` (alias) | 31.1 KB | 11.4 KB |
168
- | `vlist/native` | 28.1 KB | 10.2 KB |
169
- | `a11y()` | 34.4 KB | 12.6 KB |
170
- | `selection()` | 40.5 KB | 14.2 KB |
171
- | `data()` | 44.8 KB | 16.2 KB |
172
- | `scrollbar()` | 39.3 KB | 14.2 KB |
173
- | `sortable()` | 40.6 KB | 14.3 KB |
174
- | `groups()` | 47.1 KB | 16.7 KB |
175
- | `page()` | 33.6 KB | 12.3 KB |
176
- | `snapshots()` | 34.4 KB | 12.5 KB |
177
- | `transition()` | 37.8 KB | 13.3 KB |
178
- | `autosize()` | 34.2 KB | 12.4 KB |
179
- | `grid()` | 38.2 KB | 13.8 KB |
180
- | `table()` | 49.5 KB | 17.2 KB |
181
- | `masonry()` | 42.5 KB | 15.5 KB |
182
- | `tree()` | 46.4 KB | 16.4 KB |
183
- | `search()` | 40.2 KB | 14.5 KB |
184
- | `carousel()` | 41.0 KB | 14.9 KB |
185
-
186
- Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows measure the base factory plus that export for comparison across revisions; `carousel()` and `sortable()` must be used with the native factory at runtime. The base is **11,670 bytes gzipped** in this 3.0 work-in-progress build. The 9.9 KB target is not yet met; size optimization is deferred.
181
+ | **Base (`vlist`)** | 25.7 KB | 9.5 KB |
182
+ | `vlist/synthetic` | 31.7 KB | 11.7 KB |
183
+ | `vlist/native` (alias) | 25.7 KB | 9.5 KB |
184
+ | `a11y()` | 29.0 KB | 10.7 KB |
185
+ | `selection()` | 35.1 KB | 12.3 KB |
186
+ | `data()` | 39.4 KB | 14.3 KB |
187
+ | `scrollbar()` | 33.9 KB | 12.4 KB |
188
+ | `sortable()` | 37.5 KB | 13.0 KB |
189
+ | `groups()` | 41.5 KB | 14.7 KB |
190
+ | `page()` | 28.2 KB | 10.4 KB |
191
+ | `snapshots()` | 29.0 KB | 10.6 KB |
192
+ | `transition()` | 32.4 KB | 11.5 KB |
193
+ | `autosize()` | 28.7 KB | 10.5 KB |
194
+ | `grid()` | 32.5 KB | 11.9 KB |
195
+ | `table()` | 44.1 KB | 15.4 KB |
196
+ | `masonry()` | 36.9 KB | 13.6 KB |
197
+ | `tree()` | 41.0 KB | 14.5 KB |
198
+ | `search()` | 34.7 KB | 12.6 KB |
199
+ | `carousel()` | 38.5 KB | 13.9 KB |
200
+ | `vlist/synthetic` + `carousel()` | 44.4 KB | 16.2 KB |
201
+ | `vlist/synthetic` + `sortable()` | 43.5 KB | 15.3 KB |
202
+
203
+ Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin. The base is **9,914 bytes gzipped**, below the 9.9 KB target. `bun run size` fails if a scenario fails to build, an unused plugin leaks into a bundle, or any published size exceeds its gzip budget. Synthetic input is **11,960 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
187
204
 
188
205
  ## Examples
189
206
 
@@ -308,6 +325,13 @@ const list = createVList({
308
325
  ])
309
326
  ```
310
327
 
328
+ Search filters client-side over the items the list holds, so it cannot be
329
+ combined with `data()` — creating a list with both throws. With an adapter, the
330
+ rows in memory are only the loaded window; query the remote dataset through the
331
+ adapter instead. It cannot be combined with `tree()` either: filtering a tree
332
+ means keeping the ancestors of each match, which belongs to the plugin that
333
+ owns the layout, and `tree()` offers no such hook.
334
+
311
335
  ## Custom scrollbar (3.0 preview)
312
336
 
313
337
  On `next`, `scrollbar()` remains a plugin. macOS and Android default to thin,
@@ -368,7 +392,9 @@ colors take priority while forced colors are active.
368
392
 
369
393
  ## Accessibility
370
394
 
371
- Every vlist is accessible by default following the [WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
395
+ Accessibility is a plugin, not a default. Add `a11y()`, or a `selection()` mode
396
+ other than `"none"` which brings the same behaviour, and the list follows the
397
+ [WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
372
398
 
373
399
  - **Arrow keys** move focus between items with a visible focus ring
374
400
  - **2D navigation** in grids and masonry — Up/Down by row, Left/Right by cell
@@ -377,7 +403,9 @@ Every vlist is accessible by default following the [WAI-ARIA listbox pattern](ht
377
403
  - **Screen-reader DOM ordering** — items reordered on scroll idle for correct reading order
378
404
  - **Focus recovery** — maintains focus when items are removed
379
405
 
380
- Set `interactive: false` for display-only lists (log viewers, activity feeds) where items contain their own interactive elements.
406
+ A list created without either stays display-only: `role="list"`, no item roles
407
+ and out of the tab order. That is the right shape for log viewers and activity
408
+ feeds, and for items carrying their own interactive elements.
381
409
 
382
410
  ## API
383
411
 
@@ -391,7 +419,7 @@ const list = createVList(config, [plugin1(), plugin2()])
391
419
  |--------|-------------|
392
420
  | `list.setItems(items)` | Replace all items |
393
421
  | `list.appendItems(items)` | Add to end (auto-scrolls in reverse mode) |
394
- | `list.prependItems(items)` | Add to start (preserves scroll position) |
422
+ | `list.prependItems(items)` | Add to start (in reverse mode, the visible rows hold their position) |
395
423
  | `list.updateItem(id, partial)` | Update a single item by ID |
396
424
  | `list.insertItem(item, index?)` | Insert at index (animated with `transition`) |
397
425
  | `list.removeItem(id)` | Remove by ID (animated with `transition`) |
@@ -435,6 +463,10 @@ list.on('sort:end', ({ fromIndex, toIndex }) => {})
435
463
  list.on('sort:cancel', ({ originalItems }) => {})
436
464
  ```
437
465
 
466
+ The `index` on `item:click`, `item:dblclick` and `item:contextmenu` is the data
467
+ index — the one `getItemAt`, `scrollToIndex` and `removeItem` take — not the
468
+ layout index, which counts group headers and carousel laps.
469
+
438
470
  ### Properties
439
471
 
440
472
  | Property | Description |
@@ -482,8 +514,7 @@ Full configuration reference → **[vlist.io](https://vlist.io)**
482
514
  | `ariaLabel` | — | Accessible label for the listbox |
483
515
  | `orientation` | `'vertical'` | `'vertical'` or `'horizontal'` scroll direction |
484
516
  | `padding` | `0` | Content inset — number, `[v, h]`, or `[top, right, bottom, left]` |
485
- | `interactive` | `true` | Enable built-in keyboard navigation |
486
- | `reverse` | `false` | Reverse mode for chat UIs |
517
+ | `reverse` | `false` | The list reads bottom-up (chat UIs): a view sitting at the end stays there as `appendItems` adds to it, `prependItems` holds the visible rows in place as older history loads above them, arrow keys invert, and `transition()` animates from the bottom. Item order is yours to supply; the layout itself is not reversed. `masonry()` and `table()` reject it |
487
518
 
488
519
  ## Styling
489
520
 
@@ -513,23 +544,38 @@ Live benchmarks against 9 competitors → **[vlist.io/benchmarks](https://vlist.
513
544
 
514
545
  ## TypeScript
515
546
 
516
- Fully typed. Generic over your item type:
547
+ Fully typed and generic over your item type. An item only needs an `id`, so ordinary
548
+ interfaces work:
517
549
 
518
550
  ```typescript
519
- import { createVList, grid, type VList } from 'vlist'
551
+ import { createVList, grid, selection, type CreateVListConfig } from 'vlist'
520
552
 
521
553
  interface Photo { id: number; url: string; title: string }
522
554
 
523
- const list: VList<Photo> = createVList<Photo>({
555
+ const config: CreateVListConfig<Photo> = {
524
556
  container: '#gallery',
525
557
  items: photos,
526
558
  item: {
527
559
  height: 200,
528
560
  template: (photo) => `<img src="${photo.url}" />`,
529
561
  },
530
- }, [grid({ columns: 4 })])
562
+ }
563
+
564
+ const list = createVList(config, [grid({ columns: 4 }), selection<Photo>({ mode: 'multiple' })])
565
+
566
+ list.select(photos[0].id) // typed: selection is in the array
567
+ const chosen: Photo[] = list.getSelectedItems()
568
+ // list.expand(1) // compile error: no tree plugin here
531
569
  ```
532
570
 
571
+ `createVList` infers the methods each plugin adds, so a list exposes exactly what its
572
+ plugins provide, and a mistyped call does not compile.
573
+
574
+ TypeScript does not allow a partial type argument list. Writing
575
+ `createVList<Photo>(config, plugins)` supplies the item type and leaves the plugin list on
576
+ its default, so plugin methods are not inferred. Type the config as above, or let the item
577
+ type come from your data.
578
+
533
579
  ## Migrating from v1
534
580
 
535
581
  v2 is a ground-up rewrite — simpler API, 55% smaller base bundle, zero-allocation scroll path. [Full announcement →](https://vlist.io/blog/v2)
package/README.md CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v3.0.0-next.1** (prerelease on the npm `next` tag) — Synthetic input by default, native input through `vlist/native`, and removal of the deprecated scroll configuration and plugin hooks. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
5
+ **v3.0.0-next.3** (prerelease on the npm `next` tag) — Native scrolling by default, opt-in `vlist/synthetic`, and removal of deprecated scroll configuration and plugin hooks. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
8
  [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
9
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
10
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](https://github.com/floor/vlist/blob/main/LICENSE)
11
11
 
12
- - **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
12
+ - **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
13
13
  - **Zero dependencies** — framework-agnostic core, tiny adapters for Vue, Svelte, Solid, React
14
- - **11.4 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
14
+ - **9.4 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
15
15
  - **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
16
16
  - **Axis-neutral** — vertical and horizontal scrolling through a single code path, all plugins work in both orientations
17
17
 
@@ -55,10 +55,21 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
55
55
 
56
56
  ## Scroll input
57
57
 
58
- Synthetic scrolling is the default in 3.0. Import `createVList` and plugins from `vlist`; there is no `scroll.mode` option. The deprecated `vlist/synthetic` entry remains an alias of the same factory.
58
+ Native scrolling is the default: import `createVList` and plugins from `vlist`. This preserves browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. Carousel and sortable work with this factory; horizontal RTL lists are rejected by both entries. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
59
+
60
+ | Entry | Input model | Use |
61
+ |---|---|---|
62
+ | `vlist` | Native (default) | Browser scrolling and all supported plugins |
63
+ | `vlist/synthetic` | Synthetic (opt-in) | Huge lists and application-owned touch motion |
64
+ | `vlist/native` | Alias of `vlist` (deprecated) | Compatibility with `3.0.0-next.1`; use `vlist` for new code |
65
+
66
+ ### Synthetic input
67
+
68
+ Select synthetic input by importing its factory; there is no `scroll.mode` option. Plugins still come from `vlist`.
59
69
 
60
70
  ```typescript
61
- import { createVList, scrollbar } from 'vlist'
71
+ import { createVList } from 'vlist/synthetic'
72
+ import { scrollbar } from 'vlist'
62
73
  import 'vlist/styles'
63
74
 
64
75
  const list = createVList({
@@ -68,69 +79,74 @@ const list = createVList({
68
79
  }, [scrollbar()])
69
80
  ```
70
81
 
71
- For native scrolling, import the factory from `vlist/native` and plugins from `vlist`. Native scrolling is required for `carousel()`, `sortable()`, and horizontal RTL lists; configuring these with the default entry throws. The native entry preserves carousel wrapping through a private runway implementation. Bounded scrolling is no longer a public mode.
72
-
73
- ```typescript
74
- import { createVList } from 'vlist/native'
75
- import { carousel } from 'vlist'
76
-
77
- const list = createVList({
78
- container: '#slides',
79
- orientation: 'horizontal',
80
- items: slides,
81
- item: { width: 320, template: renderSlide },
82
- }, [carousel()])
83
- ```
82
+ `page()` uses native document scrolling through an external source with either entry. Its content must fit the 16,777,216 px document element limit; creation throws above that limit when the size is known, and later growth warns once. A deferred custom renderer whose size is first committed during rendering also warns once. Use `vlist/synthetic` viewport scrolling for larger lists. Default native viewport lists warn once when content exceeds their browser-size safety limit, pointing to that entry.
84
83
 
85
- `page()` uses native document scrolling through an external source with either entry. Its content must fit the 16,777,216 px document element limit; creation throws above that limit when the size is known, and later growth warns once. A deferred custom renderer whose size is first committed during rendering also warns once. Use default viewport scrolling for larger lists. Native viewport lists warn once when content exceeds their browser-size safety limit.
84
+ `carousel()` works with either entry. Synthetic carousel folds preserve touch motion and smooth snapping across whole laps. `page()` and carousel remain incompatible. The shared carousel plugin includes its native wrap handler even when used with synthetic input; the combined size is reported below.
86
85
 
87
- Known limitations:
86
+ Synthetic input limitations:
88
87
 
89
- - Horizontal RTL lists require `vlist/native`. Vertical lists and tables support `dir="rtl"` on the container, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
90
- - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use `vlist/native` when boundary gestures must scroll the parent page.
91
- - The default entry has no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar; native visibility options belong to `vlist/native`.
88
+ - Horizontal RTL lists throw at creation — in this entry and in `vlist`. Vertical lists and tables support `dir="rtl"` in both, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
89
+ - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use the native default when boundary gestures must scroll the parent page.
90
+ - There is no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar. The synthetic entry rejects the `"native"` and `"none"` scrollbar strings.
92
91
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
93
92
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
94
93
 
95
- Measurement corrections from autosize preserve ongoing motion. Existing plugin conflicts still apply. See the [scroll input contract](https://vlist.io/docs/rfcs/RFC-014-Scroll-Input-Model).
94
+ Measurement corrections from autosize preserve ongoing synthetic motion. Existing plugin conflicts still apply. See the [scroll input contract](https://vlist.io/docs/rfcs/RFC-014-Scroll-Input-Model).
95
+
96
+ ### Sortable gestures
97
+
98
+ `sortable()` works with either input entry. Mouse and trackpad dragging still starts after `dragThreshold` (5 px by default). Touch and pen without a handle use `touchDelay`, a 350 ms long press; moving at least `dragThreshold` pixels in any direction before it completes yields to scrolling. This delay separates a deliberate hold from a quick flick. With `handle`, dragging starts at the threshold without waiting; touches elsewhere scroll.
99
+
100
+ ```typescript
101
+ sortable({ touchDelay: 350, dragThreshold: 5 })
102
+ sortable({ handle: '.drag-handle' })
103
+ ```
104
+
105
+ A touch during scrolling catches the motion and does not arm a hold for that contact. Lift and press again once the list is at rest. A second finger or `pointercancel` cancels the pending press or active drag. Edge auto-scroll uses the list's logical position and stops on drop.
106
+
107
+ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts on the pressed item. A claimed touch/pen drag adds `.vlist-sort-ghost--touch` to the ghost as a visual cue; override that class in CSS to customize it. Keyboard reordering remains Space to grab/drop, arrows to move and Escape to cancel.
96
108
 
97
109
  ## Migrating to 3.0
98
110
 
99
- | Removed public API | Replacement |
111
+ `3.0.0-next.1` used synthetic input by default. `3.0.0-next.2` restores the 2.x default. Import `vlist/synthetic` explicitly to retain synthetic behavior across these prereleases. `vlist/native` remains a deprecated compatibility alias; its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
112
+
113
+ | 2.x option or API | 3.0 replacement |
100
114
  |---|---|
101
- | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides synthetic input; import `vlist/native` for native scrolling. |
102
- | `scroll.runway` (both entries) | Remove it. Default synthetic scrolling supports huge lists without a native runway. |
103
- | Core `scroll.scrollbar: "native"` or `"none"` | Use `scrollbar()` with `vlist`, or select `vlist/native` to retain either string. Native types are exported as `NativeScrollConfig` and `NativeCreateVListConfig`. |
115
+ | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides native scrolling; import `vlist/synthetic` for synthetic input. |
116
+ | `scroll.runway` (both entries) | Remove it. For huge lists use `vlist/synthetic`, which needs no native runway. |
117
+ | `scroll.scrollbar: "native"` or `"none"` | Retained in default `ScrollConfig`. With synthetic input, omit the string and optionally install `scrollbar()`. |
104
118
  | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
105
- | `scale()` and `ScalePluginConfig` | Remove it; the default entry supports the full logical range. `vlist/config` no longer installs a scale stub. |
119
+ | `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
106
120
 
107
- Removed scroll options throw a migration error before creating DOM. `vlist/config` retains its scrollbar omission/options convenience and its top-level `scrollbar: "none"` option; native visibility strings require an injected native factory. `baseOffset` remains private engine state for input providers; plugins continue to use `ctx.scroll.getRenderOrigin()`. No other public plugin hooks or adapter methods are removed.
121
+ Removed mode/runway options throw a migration error before creating DOM. `vlist/config` wires only what the config asks for: `selection`, the custom scrollbar, `snapshots` and `a11y` are each opt-in, so an adapter list matches a core list built from the same options. It keeps the scrollbar options convenience and its top-level `scrollbar: "none"`; pass `scrollbar: true` for the overlay scrollbar earlier versions added to every list. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.scroll.setBoundedWrap`.
108
122
 
109
123
  ## Plugins
110
124
 
111
125
  | Entry / export | Minified | Gzipped |
112
126
  |---|---:|---:|
113
- | **Base (`vlist`)** | 31.1 KB | 11.4 KB |
114
- | `vlist/synthetic` (alias) | 31.1 KB | 11.4 KB |
115
- | `vlist/native` | 28.1 KB | 10.2 KB |
116
- | `a11y()` | 34.4 KB | 12.6 KB |
117
- | `selection()` | 40.5 KB | 14.2 KB |
118
- | `data()` | 44.8 KB | 16.2 KB |
119
- | `scrollbar()` | 39.3 KB | 14.2 KB |
120
- | `sortable()` | 40.6 KB | 14.3 KB |
121
- | `groups()` | 47.1 KB | 16.7 KB |
122
- | `page()` | 33.6 KB | 12.3 KB |
123
- | `snapshots()` | 34.4 KB | 12.5 KB |
124
- | `transition()` | 37.8 KB | 13.3 KB |
125
- | `autosize()` | 34.2 KB | 12.4 KB |
126
- | `grid()` | 38.2 KB | 13.8 KB |
127
- | `table()` | 49.5 KB | 17.2 KB |
128
- | `masonry()` | 42.5 KB | 15.5 KB |
129
- | `tree()` | 46.4 KB | 16.4 KB |
130
- | `search()` | 40.2 KB | 14.5 KB |
131
- | `carousel()` | 41.0 KB | 14.9 KB |
132
-
133
- Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows measure the base factory plus that export for comparison across revisions; `carousel()` and `sortable()` must be used with the native factory at runtime. The base is **11,670 bytes gzipped** in this 3.0 work-in-progress build. The 9.9 KB target is not yet met; size optimization is deferred.
127
+ | **Base (`vlist`)** | 25.7 KB | 9.4 KB |
128
+ | `vlist/synthetic` | 31.7 KB | 11.7 KB |
129
+ | `vlist/native` (alias) | 25.7 KB | 9.5 KB |
130
+ | `a11y()` | 29.0 KB | 10.7 KB |
131
+ | `selection()` | 35.1 KB | 12.3 KB |
132
+ | `data()` | 39.4 KB | 14.3 KB |
133
+ | `scrollbar()` | 33.9 KB | 12.3 KB |
134
+ | `sortable()` | 37.4 KB | 12.9 KB |
135
+ | `groups()` | 41.4 KB | 14.7 KB |
136
+ | `page()` | 28.2 KB | 10.3 KB |
137
+ | `snapshots()` | 29.0 KB | 10.6 KB |
138
+ | `transition()` | 32.4 KB | 11.4 KB |
139
+ | `autosize()` | 28.8 KB | 10.5 KB |
140
+ | `grid()` | 32.6 KB | 11.8 KB |
141
+ | `table()` | 44.2 KB | 15.3 KB |
142
+ | `masonry()` | 37.0 KB | 13.5 KB |
143
+ | `tree()` | 41.0 KB | 14.5 KB |
144
+ | `search()` | 34.7 KB | 12.6 KB |
145
+ | `carousel()` | 38.5 KB | 13.9 KB |
146
+ | `vlist/synthetic` + `carousel()` | 44.4 KB | 16.1 KB |
147
+ | `vlist/synthetic` + `sortable()` | 43.4 KB | 15.2 KB |
148
+
149
+ Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin. The base is **9,914 bytes gzipped**, below the 9.9 KB target. `bun run size` fails if a scenario fails to build, an unused plugin leaks into a bundle, or any published size exceeds its gzip budget. Synthetic input is **11,960 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
134
150
 
135
151
  ## Framework Adapters
136
152
 
@@ -141,11 +157,11 @@ Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plu
141
157
  | SolidJS | [`vlist-solidjs`](https://github.com/floor/vlist-solidjs) | 0.5 KB |
142
158
  | React | [`vlist-react`](https://github.com/floor/vlist-react) | 0.6 KB |
143
159
 
144
- Adapters use synthetic input by default. With an adapter that forwards `factory`, select native input explicitly:
160
+ Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
145
161
 
146
162
  ```ts
147
163
  import { useVList } from "vlist-react";
148
- import { createVList } from "vlist/native";
164
+ import { createVList } from "vlist/synthetic";
149
165
 
150
166
  useVList({
151
167
  factory: createVList,
@@ -154,7 +170,7 @@ useVList({
154
170
  });
155
171
  ```
156
172
 
157
- The same factory option is available to the other adapters. `vlist/config` defaults to synthetic input. The factory is structural configuration: changing it requires recreating the list.
173
+ The same factory option is available to the other adapters. `vlist/config` defaults to native scrolling. The factory is structural configuration: changing it requires recreating the list.
158
174
 
159
175
  ## Docs & Examples
160
176