vlist 3.0.0 → 3.1.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.github.md CHANGED
@@ -2,7 +2,7 @@
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** — 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).
5
+ **v3.1.0-next.2** (prerelease on the npm `next` tag) — `scroll.mode` on the one `vlist` entry: the default, `"auto"`, scrolls natively and hands a list past the browser's size limit to synthetic input, which draws its own scrollbar; carousels honour `"synthetic"`; `vlist/synthetic` is deprecated. 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)
@@ -11,7 +11,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
11
11
 
12
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
- - **10.0 KB gzipped** — composable plugins with perfect tree-shaking
14
+ - **10.3 KB gzipped** — 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
@@ -46,20 +46,19 @@ npm install vlist@2 # the 2.x line, still maintained
46
46
  npm install vlist vlist-vue # or vlist-svelte / vlist-solidjs / vlist-react
47
47
  ```
48
48
 
49
- Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
49
+ Adapters forward `scroll` unchanged, so `scroll.mode` works through them too (adapters `3.1.0-next.2` and later):
50
50
 
51
51
  ```ts
52
52
  import { useVList } from "vlist-react";
53
- import { createVList } from "vlist/synthetic";
54
53
 
55
54
  useVList({
56
- factory: createVList,
57
55
  items,
58
56
  item: { height: 48, template: item => String(item.id) },
57
+ scroll: { mode: "synthetic" }, // "auto" by default
59
58
  });
60
59
  ```
61
60
 
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.
61
+ The mode is structural configuration: changing it requires recreating the list.
63
62
 
64
63
  ## Quick Start
65
64
 
@@ -108,47 +107,54 @@ const list = createVList({
108
107
 
109
108
  ## Scroll input
110
109
 
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.
110
+ `scroll.mode` chooses who owns scroll input, on the one `vlist` entry. Plugins come from `vlist` in every mode.
112
111
 
113
- | Entry | Input model | Use |
112
+ | `scroll.mode` | Input | Use |
114
113
  |---|---|---|
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 |
114
+ | `"auto"` (default) | Native; past the browser's element size limit (16,000,000 px of content) the list hands itself to synthetic input in place, and takes native input back below 12,000,000 px | Every list: nothing to choose, however large it grows |
115
+ | `"native"` | Always native | Browser scrolling only; past the limit the last rows are out of reach, and `content:size:overflow` says so |
116
+ | `"synthetic"` | vlist owns wheel, touch and keys from the start | Application-owned touch motion |
118
117
 
119
- ### Synthetic input
120
-
121
- Select synthetic input by importing its factory; there is no `scroll.mode` option. Plugins still come from `vlist`.
118
+ Native input keeps browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. The synthetic driver is a separate file, `synthetic-driver.js`, downloaded the first time a list needs it; until it arrives the list scrolls natively. Each handoff emits `scroll:mode`.
122
119
 
123
120
  ```typescript
124
- import { createVList } from 'vlist/synthetic'
125
- import { scrollbar } from 'vlist'
121
+ import { createVList } from 'vlist'
126
122
  import 'vlist/styles'
127
123
 
128
124
  const list = createVList({
129
125
  container: '#my-list',
130
126
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
131
127
  item: { height: 48, template: item => `<div>${item.name}</div>` },
132
- }, [scrollbar()])
128
+ scroll: { mode: 'synthetic' },
129
+ })
133
130
  ```
134
131
 
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.
132
+ | Entry | Use |
133
+ |---|---|
134
+ | `vlist` | Every list; input chosen by `scroll.mode` |
135
+ | `vlist/synthetic` | Deprecated: `scroll: { mode: "synthetic" }`. Kept through 3.x; it bundles the driver instead of loading it |
136
+ | `vlist/native` | Deprecated alias of `vlist` |
137
+
138
+ ### Synthetic input
139
+
140
+ A synthetic list draws its own scrollbar, since the browser draws none for content the size of its viewport: the `scrollbar()` plugin's, loaded with the driver. `scroll.scrollbar` options configure it, `"none"` skips it, and a list with `scrollbar()` keeps that one. `scroll.scrollbar: "native"` asks for the browser's scrollbar, so `mode: "synthetic"` rejects it.
141
+
142
+ `page()` scrolls the document natively in every mode. 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 viewport scrolling (`"auto"` or `"synthetic"`) for larger lists.
136
143
 
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.
144
+ `carousel()` runs on its own wrap runway with `"auto"` and `"native"`, and on the synthetic handler with `"synthetic"`: folds preserve touch motion and smooth snapping across whole laps. The first carousel on a page starts on its runway and switches in place once the driver loads and the carousel is idle. `page()` and carousel remain incompatible.
138
145
 
139
146
  Synthetic input limitations:
140
147
 
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.
148
+ - Horizontal RTL lists throw at creation, in every mode. Vertical lists and tables support `dir="rtl"`, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
149
+ - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use `"native"` when boundary gestures must scroll the parent page.
144
150
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
145
151
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
146
152
 
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).
153
+ 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) and [RFC-015](https://vlist.io/docs/rfcs/RFC-015-Overflow-Handoff).
148
154
 
149
155
  ### Sortable gestures
150
156
 
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.
157
+ `sortable()` works in every scroll mode. 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
158
 
153
159
  ```typescript
154
160
  sortable({ touchDelay: 350, dragThreshold: 5 })
@@ -161,46 +167,46 @@ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts
161
167
 
162
168
  ## Migrating to 3.0
163
169
 
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`.
170
+ 3.0 scrolls natively by default, as 2.x did. Since 3.1, `scroll.mode` chooses the input on the one `vlist` entry, and its default, `"auto"`, hands huge lists to synthetic input by itself. `vlist/synthetic` and `vlist/native` are deprecated aliases; `vlist/native` its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
165
171
 
166
172
  | 2.x option or API | 3.0 replacement |
167
173
  |---|---|
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()`. |
174
+ | `scroll.mode` | Since 3.1: `"auto"` (default), `"native"` or `"synthetic"`. The 2.x `"bounded"` is refused. |
175
+ | `scroll.runway` | Remove it. Huge lists need nothing: the default `scroll.mode`, `"auto"`, hands them to synthetic input. |
176
+ | `scroll.scrollbar: "native"` or `"none"` | Retained. `"native"` needs native input; a synthetic list draws its own scrollbar, which `"none"` skips. |
171
177
  | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `ctx.scroll.setSource` to supply an external position source and commit callback. |
172
178
  | 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. |
179
+ | `scale()` and `ScalePluginConfig` | Remove them; the default `scroll.mode` covers the full logical range. Config no longer installs a scale stub. |
174
180
 
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`.
181
+ `scroll.runway` and the bounded mode 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 supply their handler factory as the second argument of `ctx.scroll.setWrap` (named `setBoundedWrap` in 3.0.0, still accepted).
176
182
 
177
183
  ## Plugins
178
184
 
179
185
  | Entry / export | Minified | Gzipped |
180
186
  |---|---:|---:|
181
- | **Base (`vlist`)** | 27.0 KB | 10.0 KB |
182
- | `vlist/synthetic` | 33.9 KB | 12.6 KB |
183
- | `vlist/native` (alias) | 27.0 KB | 10.0 KB |
184
- | `a11y()` | 30.8 KB | 11.4 KB |
185
- | `selection()` | 36.5 KB | 12.9 KB |
186
- | `data()` | 40.6 KB | 14.8 KB |
187
- | `scrollbar()` | 35.1 KB | 12.8 KB |
188
- | `sortable()` | 38.9 KB | 13.6 KB |
189
- | `groups()` | 43.0 KB | 15.4 KB |
190
- | `page()` | 29.5 KB | 10.9 KB |
191
- | `snapshots()` | 30.3 KB | 11.1 KB |
192
- | `transition()` | 33.7 KB | 12.0 KB |
193
- | `autosize()` | 30.2 KB | 11.1 KB |
194
- | `grid()` | 34.4 KB | 12.5 KB |
195
- | `table()` | 45.4 KB | 15.9 KB |
196
- | `masonry()` | 39.0 KB | 14.4 KB |
197
- | `tree()` | 43.1 KB | 15.3 KB |
198
- | `search()` | 36.5 KB | 13.3 KB |
199
- | `carousel()` | 41.7 KB | 15.2 KB |
200
- | `vlist/synthetic` + `carousel()` | 47.8 KB | 17.4 KB |
201
- | `vlist/synthetic` + `sortable()` | 45.8 KB | 16.2 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 **10,201 bytes gzipped**, within the 10.0 KB budget (10,240 bytes). `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 **12,893 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
187
+ | **Base (`vlist`)** | 27.3 KB | 10.3 KB |
188
+ | `vlist/synthetic` (deprecated) | 34.2 KB | 12.9 KB |
189
+ | `vlist/native` (alias) | 27.3 KB | 10.3 KB |
190
+ | `a11y()` | 31.2 KB | 11.7 KB |
191
+ | `selection()` | 36.9 KB | 13.2 KB |
192
+ | `data()` | 41.0 KB | 15.1 KB |
193
+ | `scrollbar()` | 35.5 KB | 13.2 KB |
194
+ | `sortable()` | 39.2 KB | 13.9 KB |
195
+ | `groups()` | 43.4 KB | 15.7 KB |
196
+ | `page()` | 29.9 KB | 11.2 KB |
197
+ | `snapshots()` | 30.6 KB | 11.4 KB |
198
+ | `transition()` | 34.1 KB | 12.3 KB |
199
+ | `autosize()` | 30.6 KB | 11.5 KB |
200
+ | `grid()` | 34.8 KB | 12.9 KB |
201
+ | `table()` | 45.8 KB | 16.2 KB |
202
+ | `masonry()` | 39.4 KB | 14.7 KB |
203
+ | `tree()` | 43.6 KB | 15.7 KB |
204
+ | `search()` | 36.9 KB | 13.6 KB |
205
+ | `carousel()` | 43.1 KB | 15.7 KB |
206
+ | `vlist/synthetic` + `carousel()` | 49.2 KB | 18.0 KB |
207
+ | `vlist/synthetic` + `sortable()` | 46.1 KB | 16.5 KB |
208
+
209
+ 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 **10,541 bytes gzipped**, within the 10.3 KB budget (10,547 bytes). `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 **13,183 bytes** before plugins; with `scroll.mode`, a `vlist` list downloads the driver and its scrollbar as `synthetic-driver.js` (6.2 KB gzipped) only when it goes synthetic. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
204
210
 
205
211
  ## Examples
206
212
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
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** — 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).
5
+ **v3.1.0-next.2** (prerelease on the npm `next` tag) — `scroll.mode` on the one `vlist` entry: the default, `"auto"`, scrolls natively and hands a list past the browser's size limit to synthetic input, which draws its own scrollbar; carousels honour `"synthetic"`; `vlist/synthetic` is deprecated. 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)
@@ -11,7 +11,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
11
11
 
12
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
- - **10.0 KB gzipped** — composable plugins with perfect tree-shaking
14
+ - **10.3 KB gzipped** — 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,47 +55,54 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
55
55
 
56
56
  ## Scroll input
57
57
 
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.
58
+ `scroll.mode` chooses who owns scroll input, on the one `vlist` entry. Plugins come from `vlist` in every mode.
59
59
 
60
- | Entry | Input model | Use |
60
+ | `scroll.mode` | Input | Use |
61
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 |
62
+ | `"auto"` (default) | Native; past the browser's element size limit (16,000,000 px of content) the list hands itself to synthetic input in place, and takes native input back below 12,000,000 px | Every list: nothing to choose, however large it grows |
63
+ | `"native"` | Always native | Browser scrolling only; past the limit the last rows are out of reach, and `content:size:overflow` says so |
64
+ | `"synthetic"` | vlist owns wheel, touch and keys from the start | Application-owned touch motion |
65
65
 
66
- ### Synthetic input
67
-
68
- Select synthetic input by importing its factory; there is no `scroll.mode` option. Plugins still come from `vlist`.
66
+ Native input keeps browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. The synthetic driver is a separate file, `synthetic-driver.js`, downloaded the first time a list needs it; until it arrives the list scrolls natively. Each handoff emits `scroll:mode`.
69
67
 
70
68
  ```typescript
71
- import { createVList } from 'vlist/synthetic'
72
- import { scrollbar } from 'vlist'
69
+ import { createVList } from 'vlist'
73
70
  import 'vlist/styles'
74
71
 
75
72
  const list = createVList({
76
73
  container: '#my-list',
77
74
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
78
75
  item: { height: 48, template: item => `<div>${item.name}</div>` },
79
- }, [scrollbar()])
76
+ scroll: { mode: 'synthetic' },
77
+ })
80
78
  ```
81
79
 
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.
80
+ | Entry | Use |
81
+ |---|---|
82
+ | `vlist` | Every list; input chosen by `scroll.mode` |
83
+ | `vlist/synthetic` | Deprecated: `scroll: { mode: "synthetic" }`. Kept through 3.x; it bundles the driver instead of loading it |
84
+ | `vlist/native` | Deprecated alias of `vlist` |
85
+
86
+ ### Synthetic input
87
+
88
+ A synthetic list draws its own scrollbar, since the browser draws none for content the size of its viewport: the `scrollbar()` plugin's, loaded with the driver. `scroll.scrollbar` options configure it, `"none"` skips it, and a list with `scrollbar()` keeps that one. `scroll.scrollbar: "native"` asks for the browser's scrollbar, so `mode: "synthetic"` rejects it.
89
+
90
+ `page()` scrolls the document natively in every mode. 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 viewport scrolling (`"auto"` or `"synthetic"`) for larger lists.
83
91
 
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.
92
+ `carousel()` runs on its own wrap runway with `"auto"` and `"native"`, and on the synthetic handler with `"synthetic"`: folds preserve touch motion and smooth snapping across whole laps. The first carousel on a page starts on its runway and switches in place once the driver loads and the carousel is idle. `page()` and carousel remain incompatible.
85
93
 
86
94
  Synthetic input limitations:
87
95
 
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.
96
+ - Horizontal RTL lists throw at creation, in every mode. Vertical lists and tables support `dir="rtl"`, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
97
+ - Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use `"native"` when boundary gestures must scroll the parent page.
91
98
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
92
99
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
93
100
 
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).
101
+ 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) and [RFC-015](https://vlist.io/docs/rfcs/RFC-015-Overflow-Handoff).
95
102
 
96
103
  ### Sortable gestures
97
104
 
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.
105
+ `sortable()` works in every scroll mode. 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
106
 
100
107
  ```typescript
101
108
  sortable({ touchDelay: 350, dragThreshold: 5 })
@@ -108,45 +115,45 @@ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts
108
115
 
109
116
  ## Migrating to 3.0
110
117
 
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`.
118
+ 3.0 scrolls natively by default, as 2.x did. Since 3.1, `scroll.mode` chooses the input on the one `vlist` entry, and its default, `"auto"`, hands huge lists to synthetic input by itself. `vlist/synthetic` and `vlist/native` are deprecated aliases; `vlist/native` its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
112
119
 
113
120
  | 2.x option or API | 3.0 replacement |
114
121
  |---|---|
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()`. |
122
+ | `scroll.mode` | Since 3.1: `"auto"` (default), `"native"` or `"synthetic"`. The 2.x `"bounded"` is refused. |
123
+ | `scroll.runway` | Remove it. Huge lists need nothing: the default `scroll.mode`, `"auto"`, hands them to synthetic input. |
124
+ | `scroll.scrollbar: "native"` or `"none"` | Retained. `"native"` needs native input; a synthetic list draws its own scrollbar, which `"none"` skips. |
118
125
  | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
119
- | `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
126
+ | `scale()` and `ScalePluginConfig` | Remove them; the default `scroll.mode` covers the full logical range. Config no longer installs a scale stub. |
120
127
 
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`.
128
+ `scroll.runway` and the bounded mode 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 supply their handler factory as the second argument of `ctx.scroll.setWrap` (named `setBoundedWrap` in 3.0.0, still accepted).
122
129
 
123
130
  ## Plugins
124
131
 
125
132
  | Entry / export | Minified | Gzipped |
126
133
  |---|---:|---:|
127
- | **Base (`vlist`)** | 27.0 KB | 10.0 KB |
128
- | `vlist/synthetic` | 33.9 KB | 12.6 KB |
129
- | `vlist/native` (alias) | 27.0 KB | 10.0 KB |
130
- | `a11y()` | 30.8 KB | 11.4 KB |
131
- | `selection()` | 36.5 KB | 12.9 KB |
132
- | `data()` | 40.6 KB | 14.8 KB |
133
- | `scrollbar()` | 35.1 KB | 12.8 KB |
134
- | `sortable()` | 38.9 KB | 13.6 KB |
135
- | `groups()` | 43.0 KB | 15.4 KB |
136
- | `page()` | 29.5 KB | 10.9 KB |
137
- | `snapshots()` | 30.3 KB | 11.1 KB |
138
- | `transition()` | 33.7 KB | 12.0 KB |
139
- | `autosize()` | 30.2 KB | 11.1 KB |
140
- | `grid()` | 34.4 KB | 12.5 KB |
141
- | `table()` | 45.4 KB | 15.9 KB |
142
- | `masonry()` | 39.0 KB | 14.4 KB |
143
- | `tree()` | 43.1 KB | 15.3 KB |
144
- | `search()` | 36.5 KB | 13.3 KB |
145
- | `carousel()` | 41.7 KB | 15.2 KB |
146
- | `vlist/synthetic` + `carousel()` | 47.8 KB | 17.4 KB |
147
- | `vlist/synthetic` + `sortable()` | 45.8 KB | 16.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 **10,201 bytes gzipped**, within the 10.0 KB budget (10,240 bytes). `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 **12,893 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
134
+ | **Base (`vlist`)** | 27.3 KB | 10.3 KB |
135
+ | `vlist/synthetic` (deprecated) | 34.2 KB | 12.9 KB |
136
+ | `vlist/native` (alias) | 27.3 KB | 10.3 KB |
137
+ | `a11y()` | 31.2 KB | 11.7 KB |
138
+ | `selection()` | 36.9 KB | 13.2 KB |
139
+ | `data()` | 41.0 KB | 15.1 KB |
140
+ | `scrollbar()` | 35.5 KB | 13.2 KB |
141
+ | `sortable()` | 39.2 KB | 13.9 KB |
142
+ | `groups()` | 43.4 KB | 15.7 KB |
143
+ | `page()` | 29.9 KB | 11.2 KB |
144
+ | `snapshots()` | 30.6 KB | 11.4 KB |
145
+ | `transition()` | 34.1 KB | 12.3 KB |
146
+ | `autosize()` | 30.6 KB | 11.5 KB |
147
+ | `grid()` | 34.8 KB | 12.9 KB |
148
+ | `table()` | 45.8 KB | 16.2 KB |
149
+ | `masonry()` | 39.4 KB | 14.7 KB |
150
+ | `tree()` | 43.6 KB | 15.7 KB |
151
+ | `search()` | 36.9 KB | 13.6 KB |
152
+ | `carousel()` | 43.1 KB | 15.7 KB |
153
+ | `vlist/synthetic` + `carousel()` | 49.2 KB | 18.0 KB |
154
+ | `vlist/synthetic` + `sortable()` | 46.1 KB | 16.5 KB |
155
+
156
+ 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 **10,541 bytes gzipped**, within the 10.3 KB budget (10,547 bytes). `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 **13,183 bytes** before plugins; with `scroll.mode`, a `vlist` list downloads the driver and its scrollbar as `synthetic-driver.js` (6.2 KB gzipped) only when it goes synthetic. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
150
157
 
151
158
  ## Framework Adapters
152
159
 
@@ -157,20 +164,19 @@ Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plu
157
164
  | SolidJS | [`vlist-solidjs`](https://github.com/floor/vlist-solidjs) | 0.5 KB |
158
165
  | React | [`vlist-react`](https://github.com/floor/vlist-react) | 0.6 KB |
159
166
 
160
- Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
167
+ Adapters forward `scroll` unchanged, so `scroll.mode` works through them too (adapters `3.1.0-next.2` and later):
161
168
 
162
169
  ```ts
163
170
  import { useVList } from "vlist-react";
164
- import { createVList } from "vlist/synthetic";
165
171
 
166
172
  useVList({
167
- factory: createVList,
168
173
  items,
169
174
  item: { height: 48, template: item => String(item.id) },
175
+ scroll: { mode: "synthetic" }, // "auto" by default
170
176
  });
171
177
  ```
172
178
 
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.
179
+ The mode is structural configuration: changing it requires recreating the list.
174
180
 
175
181
  ## Docs & Examples
176
182
 
package/dist/config.d.ts CHANGED
@@ -41,7 +41,7 @@ export type VListFactory<T extends VListItem = VListItem> = typeof createVList<T
41
41
  * translated into plugins by {@link resolvePlugins}.
42
42
  */
43
43
  export interface VListConfig<T extends VListItem = VListItem> extends Omit<CreateVListConfig<T>, "container" | "scroll"> {
44
- /** Scroll options; select synthetic input with a factory from vlist/synthetic. */
44
+ /** Scroll options; `scroll.mode` chooses native, synthetic or auto input. */
45
45
  scroll?: ScrollConfig;
46
46
  /** List factory; defaults to core createVList. Fixed for this instance. */
47
47
  factory?: VListFactory<T>;