vlist 2.8.1 → 3.0.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
@@ -1,8 +1,8 @@
1
1
  # vlist
2
2
 
3
- The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 9.9 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v2.8.1** — [Changelog](./CHANGELOG.md) · deprecation notices corrected for the revised 3.0 shape (native stays the default, synthetic input stays opt-in, the scrollbar strings are not deprecated); carousel slots follow container resizes; prerelease-aware publish workflow.
5
+ **v3.0.0-next.2** (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)
@@ -11,7 +11,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
11
11
 
12
12
  - **Accessible** — 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
- - **9.9 KB gzipped** — composable plugins with perfect tree-shaking
14
+ - **9.3 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
@@ -42,10 +42,11 @@ The virtual list library for every framework. Ultra efficient, batteries-include
42
42
 
43
43
  ```bash
44
44
  npm install vlist # vanilla JS
45
+ npm install vlist@next # 3.0 prerelease; latest stays on 2.8
45
46
  npm install vlist vlist-vue # or vlist-svelte / vlist-solidjs / vlist-react
46
47
  ```
47
48
 
48
- With vlist 2.8 and an adapter that forwards the `factory` option, opt into synthetic input explicitly:
49
+ Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
49
50
 
50
51
  ```ts
51
52
  import { useVList } from "vlist-react";
@@ -53,13 +54,12 @@ import { createVList } from "vlist/synthetic";
53
54
 
54
55
  useVList({
55
56
  factory: createVList,
56
- scroll: { mode: "synthetic" },
57
57
  items,
58
58
  item: { height: 48, template: item => String(item.id) },
59
59
  });
60
60
  ```
61
61
 
62
- The same factory option is available to the other adapters. `vlist/config` keeps the synthetic driver out of its default bundle; importing the factory opts in. 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
 
@@ -106,11 +106,19 @@ const list = createVList({
106
106
  ])
107
107
  ```
108
108
 
109
- ## Synthetic scroll input
109
+ ## Scroll input
110
110
 
111
- The opt-in `vlist/synthetic` entry adds `scroll.mode: 'synthetic'` alongside native and bounded modes. Native remains the default. Import the factory from this entry and plugins from `vlist`:
112
- Bounded mode note: on touch devices a long native fling can outrun the 2x runway and stall at its edge (measured at 145-226% of a 16x runway on an iPhone SE and a Pixel 8a). Prefer synthetic mode for touch-heavy lists; bounded remains the right choice for wheel and keyboard driven lists.
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, sortable and horizontal RTL lists work with this factory. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
113
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`.
114
122
 
115
123
  ```typescript
116
124
  import { createVList } from 'vlist/synthetic'
@@ -121,56 +129,60 @@ const list = createVList({
121
129
  container: '#my-list',
122
130
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
123
131
  item: { height: 48, template: item => `<div>${item.name}</div>` },
124
- scroll: { mode: 'synthetic' },
125
132
  }, [scrollbar()])
126
133
  ```
127
134
 
128
- Supported plugins are **table, groups, snapshots, scrollbar, autosize, transition, selection and a11y**. Existing plugin conflicts still apply; this list does not imply that all eight can be combined. `page()`, `carousel()` and `sortable()` throw when configured with synthetic mode. Carousel uses wrap scrolling, which this release does not support with synthetic input.
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.
129
136
 
130
- Known limitations:
137
+ Synthetic input limitations:
131
138
 
132
- - RTL horizontal lists throw in synthetic mode in this release; use native mode. Vertical lists on RTL pages are supported. RTL support for the synthetic driver is planned as a non-breaking addition.
133
- - Same-axis touch stops at either boundary with no parent handoff, including gestures that start inside an edge-pinned list. Use native mode when touch gestures must scroll the parent page at a boundary.
134
- - The native main-axis scrollbar is absent. Provide a custom scrollbar, such as `scrollbar()` above. Its accessibility release gate remains open; synthetic mode is not a completed scrollbar-accessibility sign-off.
139
+ - `carousel()`, `sortable()` and horizontal RTL lists throw with instructions to use `vlist`. Vertical lists and tables support `dir="rtl"`, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
140
+ - 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.
141
+ - There is no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar. The synthetic entry rejects the `"native"` and `"none"` scrollbar strings.
135
142
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
136
143
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
137
144
 
138
- Measurement corrections from autosize preserve ongoing motion. Synthetic input adds **2.6 KB gzipped** over the base entry (**12.5 KB** total before plugins); ordinary `vlist` imports exclude this driver. See [RFC-014](https://github.com/floor/vlist/discussions/127).
145
+ 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).
139
146
 
140
- ## Deprecated in 2.8, removed in 3.0
147
+ ## Migrating to 3.0
141
148
 
142
- These notices prepare the 3.0 migration; 2.x behavior and defaults stay unchanged. Native scrolling stays the default in 3.0, and synthetic input stays the opt-in `vlist/synthetic` entry. Bounded mode remains supported in 2.x, including carousel and sortable; it emits no deprecation warning.
149
+ `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`.
143
150
 
144
- | Option or API | In 3.0 | Since |
145
- |---|---|---|
146
- | `scroll.mode` | Removed. `vlist` is native by default; for lists beyond the browser's element size limit, import `createVList` from `vlist/synthetic` instead of using bounded mode. | 2.8 |
147
- | `scroll.runway` | Removed with bounded mode. | 2.8 |
148
- | `PluginContext.setScrollFns`, `disableDefaultScroll` | Removed; use `setScrollSource`. | 2.8 |
149
- | `scale()` | Removed; use `vlist/synthetic` for huge lists. | 2.4 |
151
+ | 2.x option or API | 3.0 replacement |
152
+ |---|---|
153
+ | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides native scrolling; import `vlist/synthetic` for synthetic input. |
154
+ | `scroll.runway` (both entries) | Remove it. For huge lists use `vlist/synthetic`, which needs no native runway. |
155
+ | `scroll.scrollbar: "native"` or `"none"` | Retained in default `ScrollConfig`. With synthetic input, omit the string and optionally install `scrollbar()`. |
156
+ | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
157
+ | `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
150
158
 
151
- `scroll.scrollbar: "native"` and `"none"` are not deprecated: they stay available on the native default in 3.0. Only explicit `scale()` calls warn, once per process, and 3.0's `vlist/config` no longer installs the scale compatibility stub. Release 2.8.0 described native scrolling and the scrollbar strings as moving to a `vlist/native` entry in 3.0; that plan was revised. See the [v2 to v3 migration guide](https://vlist.io/docs/migration-v3).
159
+ Removed mode/runway options throw a migration error before creating DOM. `vlist/config` retains scrollbar omission/options convenience and its top-level `scrollbar: "none"` option. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.setBoundedWrap`.
152
160
 
153
161
  ## Plugins
154
162
 
155
- | Plugin | Size | Description |
156
- |--------|------|-------------|
157
- | **Base** | 9.9 KB | Virtualization, ARIA, keyboard nav, gap, padding, bounded scroll (1M+ items) |
158
- | `vlist/synthetic` entry | +2.6 KB | Opt-in synthetic scroll input (12.5 KB total before plugins) |
159
- | `data()` | +4.8 KB | Lazy loading with velocity-aware fetching |
160
- | `selection()` | +2.8 KB | Single/multiple selection with 2D keyboard nav |
161
- | `search()` | +3.2 KB | Search bar: filter/navigate modes, match highlighting |
162
- | `groups()` | +5.3 KB | Sticky/inline headers with grid + masonry + table + data integration |
163
- | `autosize()` | +1.0 KB | Auto-measure items via ResizeObserver |
164
- | `scrollbar()` | +2.0 KB | Custom scrollbar UI |
165
- | `grid()` | +2.5 KB | 2D grid layout |
166
- | `masonry()` | +4.1 KB | Pinterest-style masonry with lane-aware keyboard nav |
167
- | `carousel()` | +3.6 KB | Paged horizontal carousel with snap and keyboard nav |
168
- | `table()` | +5.8 KB | Data table with columns, resize, sort |
169
- | `tree()` | +5.0 KB | Collapsible tree with async loading and indent guides |
170
- | `page()` | +0.8 KB | Window-level scrolling |
171
- | `sortable()` | +3.0 KB | Drag-and-drop reordering with auto-scroll |
172
- | `snapshots()` | +1.2 KB | Scroll position save/restore |
173
- | `transition()` | +2.0 KB | FLIP-based enter/exit animations for insert & remove |
163
+ | Entry / export | Minified | Gzipped |
164
+ |---|---:|---:|
165
+ | **Base (`vlist`)** | 25.4 KB | 9.3 KB |
166
+ | `vlist/synthetic` | 31.1 KB | 11.4 KB |
167
+ | `vlist/native` (alias) | 25.4 KB | 9.3 KB |
168
+ | `a11y()` | 28.7 KB | 10.5 KB |
169
+ | `selection()` | 34.8 KB | 12.1 KB |
170
+ | `data()` | 39.0 KB | 14.1 KB |
171
+ | `scrollbar()` | 33.6 KB | 12.2 KB |
172
+ | `sortable()` | 34.9 KB | 12.2 KB |
173
+ | `groups()` | 41.3 KB | 14.6 KB |
174
+ | `page()` | 27.9 KB | 10.2 KB |
175
+ | `snapshots()` | 28.7 KB | 10.4 KB |
176
+ | `transition()` | 32.1 KB | 11.3 KB |
177
+ | `autosize()` | 28.5 KB | 10.3 KB |
178
+ | `grid()` | 32.5 KB | 11.8 KB |
179
+ | `table()` | 43.8 KB | 15.2 KB |
180
+ | `masonry()` | 36.8 KB | 13.4 KB |
181
+ | `tree()` | 40.6 KB | 14.3 KB |
182
+ | `search()` | 34.5 KB | 12.4 KB |
183
+ | `carousel()` | 38.0 KB | 13.7 KB |
184
+
185
+ 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,523 bytes gzipped**, below the 9.9 KB target; synthetic input is **11,687 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
174
186
 
175
187
  ## Examples
176
188
 
@@ -295,6 +307,64 @@ const list = createVList({
295
307
  ])
296
308
  ```
297
309
 
310
+ ## Custom scrollbar (3.0 preview)
311
+
312
+ On `next`, `scrollbar()` remains a plugin. macOS and Android default to thin,
313
+ rounded, auto-hiding overlays; Windows defaults to a wider, square, always-visible
314
+ bar. Set `gutter: true` to reserve space. The same behavior works horizontally.
315
+
316
+ ```typescript
317
+ scrollbar({
318
+ platform: 'windows', // optional: macos | windows | android
319
+ width: 'thin', // optional: pixels (number) | auto | thin | none
320
+ radius: 4, // optional: thumb radius in pixels
321
+ thumbColor: '#666', // optional explicit colors
322
+ trackColor: '#eee',
323
+ gutter: true,
324
+ })
325
+ ```
326
+
327
+ Without explicit overrides, the plugin reads the container's standard
328
+ `scrollbar-width` and `scrollbar-color`. `auto` uses the platform width; `thin`
329
+ uses 6 px; `none` disables the track, hover target and gutter. Color order is
330
+ thumb then track. Numeric `width` and `radius` override the corresponding
331
+ `--vlist-custom-scrollbar-width` and `--vlist-custom-scrollbar-radius` variables
332
+ on the container; those variables override platform defaults. Without a custom
333
+ width variable, the width keywords retain their standard meaning (`none` always
334
+ disables the bar). Explicit colors override author standard colors. `autoHide`, `autoHideDelay` and `minThumbSize` remain available.
335
+ After changing author CSS, call `list.refreshScrollbar()` (or `refresh()` on a
336
+ standalone `Scrollbar` instance). Refresh rereads CSS; platform selection remains
337
+ fixed for that instance. Setting `enabled: false` disables the plugin's bar.
338
+
339
+ The focusable track exposes its controlled viewport, orientation, logical range,
340
+ and “Row N of M” value. Arrows move one row along the active axis, PageUp/PageDown
341
+ move a viewport, and Home/End reach the bounds. Focus keeps the bar visible.
342
+ Forced-color themes use system colors for the track, thumb and focus indicator.
343
+ The thumb has a minimum size even for millions of rows; drag positions continue
344
+ to use the full logical range. The real screen-reader acceptance pass is still
345
+ pending; this is not a completed accessibility sign-off.
346
+
347
+ ### Migrating WebKit scrollbar selectors
348
+
349
+ **The `::-webkit-scrollbar*` pseudo-elements are not mirrored.** They style
350
+ browser-owned scrollbars, not this plugin's DOM. Migrate each rule as follows
351
+ (the classes shown use the default `vlist` prefix):
352
+
353
+ | Existing selector | Plugin replacement |
354
+ | --- | --- |
355
+ | `::-webkit-scrollbar` | `.vlist-scrollbar`; `--vlist-custom-scrollbar-width` for thickness |
356
+ | `::-webkit-scrollbar-track` | `.vlist-scrollbar`; `--vlist-custom-scrollbar-track-color` |
357
+ | `::-webkit-scrollbar-thumb` | `.vlist-scrollbar__thumb`; `--vlist-custom-scrollbar-thumb-color`, `--vlist-custom-scrollbar-radius`, `--vlist-custom-scrollbar-min-thumb-size` |
358
+ | `::-webkit-scrollbar-thumb:hover` | `.vlist-scrollbar__thumb:hover`; `--vlist-custom-scrollbar-thumb-hover-color` |
359
+ | `::-webkit-scrollbar-corner` | No separate corner element. Reserved gutter space uses the `.vlist` background (`--vlist-bg`). A dedicated corner rule has no direct equivalent. |
360
+ | `::-webkit-scrollbar-button` | No arrow-button elements or direct styling equivalent. Use the scrollbar's row keys or track paging; custom buttons must be separate controls. |
361
+
362
+ For width and base thumb/track colors, prefer standard `scrollbar-width` and
363
+ `scrollbar-color` on the container, or plugin config. The plugin maps these onto
364
+ its custom properties at setup/refresh. Use the plugin classes and remaining
365
+ variables for radius, minimum thumb size and hover styling. High-contrast system
366
+ colors take priority while forced colors are active.
367
+
298
368
  ## Accessibility
299
369
 
300
370
  Every vlist is accessible by default following the [WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # vlist
2
2
 
3
- The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 9.9 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v2.8.1** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md) · deprecation notices corrected for the revised 3.0 shape (native stays the default, synthetic input stays opt-in, the scrollbar strings are not deprecated); carousel slots follow container resizes; prerelease-aware publish workflow.
5
+ **v3.0.0-next.2** (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)
@@ -11,7 +11,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
11
11
 
12
12
  - **Accessible** — 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
- - **9.9 KB gzipped** — composable plugins with perfect tree-shaking
14
+ - **9.3 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
 
@@ -19,6 +19,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
19
19
 
20
20
  ```bash
21
21
  npm install vlist
22
+ npm install vlist@next # 3.0 prerelease; latest stays on 2.8
22
23
  ```
23
24
 
24
25
  ## Quick Start
@@ -52,11 +53,19 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
52
53
  ])
53
54
  ```
54
55
 
55
- ## Synthetic scroll input
56
+ ## Scroll input
56
57
 
57
- The opt-in `vlist/synthetic` entry adds `scroll.mode: 'synthetic'` alongside native and bounded modes. Native remains the default. Import the factory from this entry and plugins from `vlist`:
58
- Bounded mode note: on touch devices a long native fling can outrun the 2x runway and stall at its edge (measured at 145-226% of a 16x runway on an iPhone SE and a Pixel 8a). Prefer synthetic mode for touch-heavy lists; bounded remains the right choice for wheel and keyboard driven lists.
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, sortable and horizontal RTL lists work with this factory. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
59
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`.
60
69
 
61
70
  ```typescript
62
71
  import { createVList } from 'vlist/synthetic'
@@ -67,56 +76,60 @@ const list = createVList({
67
76
  container: '#my-list',
68
77
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
69
78
  item: { height: 48, template: item => `<div>${item.name}</div>` },
70
- scroll: { mode: 'synthetic' },
71
79
  }, [scrollbar()])
72
80
  ```
73
81
 
74
- Supported plugins are **table, groups, snapshots, scrollbar, autosize, transition, selection and a11y**. Existing plugin conflicts still apply; this list does not imply that all eight can be combined. `page()`, `carousel()` and `sortable()` throw when configured with synthetic mode. Carousel uses wrap scrolling, which this release does not support with synthetic input.
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.
75
83
 
76
- Known limitations:
84
+ Synthetic input limitations:
77
85
 
78
- - RTL horizontal lists throw in synthetic mode in this release; use native mode. Vertical lists on RTL pages are supported. RTL support for the synthetic driver is planned as a non-breaking addition.
79
- - Same-axis touch stops at either boundary with no parent handoff, including gestures that start inside an edge-pinned list. Use native mode when touch gestures must scroll the parent page at a boundary.
80
- - The native main-axis scrollbar is absent. Provide a custom scrollbar, such as `scrollbar()` above. Its accessibility release gate remains open; synthetic mode is not a completed scrollbar-accessibility sign-off.
86
+ - `carousel()`, `sortable()` and horizontal RTL lists throw with instructions to use `vlist`. Vertical lists and tables support `dir="rtl"`, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
87
+ - 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.
88
+ - There is no native main-axis scrollbar. Add `scrollbar()` for an accessible custom scrollbar. The synthetic entry rejects the `"native"` and `"none"` scrollbar strings.
81
89
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
82
90
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
83
91
 
84
- Measurement corrections from autosize preserve ongoing motion. Synthetic input adds **2.6 KB gzipped** over the base entry (**12.5 KB** total before plugins); ordinary `vlist` imports exclude this driver. See [RFC-014](https://github.com/floor/vlist/discussions/127).
92
+ 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).
85
93
 
86
- ## Deprecated in 2.8, removed in 3.0
94
+ ## Migrating to 3.0
87
95
 
88
- These notices prepare the 3.0 migration; 2.x behavior and defaults stay unchanged. Native scrolling stays the default in 3.0, and synthetic input stays the opt-in `vlist/synthetic` entry. Bounded mode remains supported in 2.x, including carousel and sortable; it emits no deprecation warning.
96
+ `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`.
89
97
 
90
- | Option or API | In 3.0 | Since |
91
- |---|---|---|
92
- | `scroll.mode` | Removed. `vlist` is native by default; for lists beyond the browser's element size limit, import `createVList` from `vlist/synthetic` instead of using bounded mode. | 2.8 |
93
- | `scroll.runway` | Removed with bounded mode. | 2.8 |
94
- | `PluginContext.setScrollFns`, `disableDefaultScroll` | Removed; use `setScrollSource`. | 2.8 |
95
- | `scale()` | Removed; use `vlist/synthetic` for huge lists. | 2.4 |
98
+ | 2.x option or API | 3.0 replacement |
99
+ |---|---|
100
+ | `scroll.mode` (all values, both entries) | Omit it. `vlist` provides native scrolling; import `vlist/synthetic` for synthetic input. |
101
+ | `scroll.runway` (both entries) | Remove it. For huge lists use `vlist/synthetic`, which needs no native runway. |
102
+ | `scroll.scrollbar: "native"` or `"none"` | Retained in default `ScrollConfig`. With synthetic input, omit the string and optionally install `scrollbar()`. |
103
+ | `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
104
+ | `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
96
105
 
97
- `scroll.scrollbar: "native"` and `"none"` are not deprecated: they stay available on the native default in 3.0. Only explicit `scale()` calls warn, once per process, and 3.0's `vlist/config` no longer installs the scale compatibility stub. Release 2.8.0 described native scrolling and the scrollbar strings as moving to a `vlist/native` entry in 3.0; that plan was revised. See the [v2 to v3 migration guide](https://vlist.io/docs/migration-v3).
106
+ Removed mode/runway options throw a migration error before creating DOM. `vlist/config` retains scrollbar omission/options convenience and its top-level `scrollbar: "none"` option. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.setBoundedWrap`.
98
107
 
99
108
  ## Plugins
100
109
 
101
- | Plugin | Size | Description |
102
- |--------|------|-------------|
103
- | **Base** | 9.9 KB | Virtualization, ARIA, keyboard nav, gap, padding, bounded scroll (1M+ items) |
104
- | `vlist/synthetic` entry | +2.6 KB | Opt-in synthetic scroll input (12.5 KB total before plugins) |
105
- | `data()` | +4.8 KB | Lazy loading with velocity-aware fetching |
106
- | `selection()` | +2.8 KB | Single/multiple selection with 2D keyboard nav |
107
- | `search()` | +3.2 KB | Search bar: filter/navigate modes, match highlighting |
108
- | `groups()` | +5.3 KB | Sticky/inline headers with grid + masonry + table + data integration |
109
- | `autosize()` | +1.0 KB | Auto-measure items via ResizeObserver |
110
- | `scrollbar()` | +2.0 KB | Custom scrollbar UI |
111
- | `grid()` | +2.5 KB | 2D grid layout |
112
- | `masonry()` | +4.1 KB | Pinterest-style masonry with lane-aware keyboard nav |
113
- | `carousel()` | +3.6 KB | Paged horizontal carousel with snap and keyboard nav |
114
- | `table()` | +5.8 KB | Data table with columns, resize, sort |
115
- | `tree()` | +5.0 KB | Collapsible tree with async loading and indent guides |
116
- | `page()` | +0.8 KB | Window-level scrolling |
117
- | `sortable()` | +3.0 KB | Drag-and-drop reordering with auto-scroll |
118
- | `snapshots()` | +1.2 KB | Scroll position save/restore |
119
- | `transition()` | +2.0 KB | FLIP-based enter/exit animations for insert & remove |
110
+ | Entry / export | Minified | Gzipped |
111
+ |---|---:|---:|
112
+ | **Base (`vlist`)** | 25.4 KB | 9.3 KB |
113
+ | `vlist/synthetic` | 31.1 KB | 11.4 KB |
114
+ | `vlist/native` (alias) | 25.4 KB | 9.3 KB |
115
+ | `a11y()` | 28.7 KB | 10.5 KB |
116
+ | `selection()` | 34.8 KB | 12.1 KB |
117
+ | `data()` | 39.0 KB | 14.1 KB |
118
+ | `scrollbar()` | 33.6 KB | 12.2 KB |
119
+ | `sortable()` | 34.9 KB | 12.2 KB |
120
+ | `groups()` | 41.3 KB | 14.6 KB |
121
+ | `page()` | 27.9 KB | 10.2 KB |
122
+ | `snapshots()` | 28.7 KB | 10.4 KB |
123
+ | `transition()` | 32.1 KB | 11.3 KB |
124
+ | `autosize()` | 28.5 KB | 10.3 KB |
125
+ | `grid()` | 32.5 KB | 11.8 KB |
126
+ | `table()` | 43.8 KB | 15.2 KB |
127
+ | `masonry()` | 36.8 KB | 13.4 KB |
128
+ | `tree()` | 40.6 KB | 14.3 KB |
129
+ | `search()` | 34.5 KB | 12.4 KB |
130
+ | `carousel()` | 38.0 KB | 13.7 KB |
131
+
132
+ 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,523 bytes gzipped**, below the 9.9 KB target; synthetic input is **11,687 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
120
133
 
121
134
  ## Framework Adapters
122
135
 
@@ -127,7 +140,7 @@ These notices prepare the 3.0 migration; 2.x behavior and defaults stay unchange
127
140
  | SolidJS | [`vlist-solidjs`](https://github.com/floor/vlist-solidjs) | 0.5 KB |
128
141
  | React | [`vlist-react`](https://github.com/floor/vlist-react) | 0.6 KB |
129
142
 
130
- With vlist 2.8 and an adapter that forwards the `factory` option, opt into synthetic input explicitly:
143
+ Adapters use native scrolling by default. With an adapter that forwards `factory`, select synthetic input explicitly:
131
144
 
132
145
  ```ts
133
146
  import { useVList } from "vlist-react";
@@ -135,13 +148,12 @@ import { createVList } from "vlist/synthetic";
135
148
 
136
149
  useVList({
137
150
  factory: createVList,
138
- scroll: { mode: "synthetic" },
139
151
  items,
140
152
  item: { height: 48, template: item => String(item.id) },
141
153
  });
142
154
  ```
143
155
 
144
- The same factory option is available to the other adapters. `vlist/config` keeps the synthetic driver out of its default bundle; importing the factory opts in. The factory is structural configuration: changing it requires recreating the list.
156
+ 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.
145
157
 
146
158
  ## Docs & Examples
147
159
 
package/dist/config.d.ts CHANGED
@@ -32,10 +32,8 @@ export type VListFactory<T extends VListItem = VListItem> = typeof createVList<T
32
32
  * translated into plugins by {@link resolvePlugins}.
33
33
  */
34
34
  export interface VListConfig<T extends VListItem = VListItem> extends Omit<CreateVListConfig<T>, "container" | "scroll"> {
35
- /** Synthetic mode requires a factory imported from vlist/synthetic. */
36
- scroll?: Omit<ScrollConfig, "mode"> & {
37
- mode?: ScrollConfig["mode"] | "synthetic";
38
- };
35
+ /** Scroll options; select synthetic input with a factory from vlist/synthetic. */
36
+ scroll?: ScrollConfig;
39
37
  /** List factory; defaults to core createVList. Fixed for this instance. */
40
38
  factory?: VListFactory<T>;
41
39
  /** Layout mode. Wires the grid or masonry plugin from `grid`/`masonry`. */
@@ -65,7 +63,7 @@ export interface VListConfig<T extends VListItem = VListItem> extends Omit<Creat
65
63
  /**
66
64
  * Translate a {@link VListConfig} into the ordered plugin array that the core
67
65
  * `createVList` expects. Mirrors the adapters' historical behavior exactly:
68
- * `scale` and `snapshots` are always included, and `selection` is always
66
+ * `snapshots` is always included, and `selection` is always
69
67
  * present (in `"none"` mode when unset) so its API is available. Any user
70
68
  * `plugins` are appended last as an escape hatch.
71
69
  */