vlist 2.7.2 → 3.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.7 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v2.7.2** [Changelog](./CHANGELOG.md) · Tree keeps the bounded runway for large trees; the scale() deprecation link works; bounded-mode touch limitation documented.
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).
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
+ - **11.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
  - **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,9 +42,25 @@ 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
 
49
+ Adapters use synthetic input by default. With an adapter that forwards `factory`, select native input explicitly:
50
+
51
+ ```ts
52
+ import { useVList } from "vlist-react";
53
+ import { createVList } from "vlist/native";
54
+
55
+ useVList({
56
+ factory: createVList,
57
+ items,
58
+ item: { height: 48, template: item => String(item.id) },
59
+ });
60
+ ```
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.
63
+
48
64
  ## Quick Start
49
65
 
50
66
  ```typescript
@@ -90,58 +106,84 @@ const list = createVList({
90
106
  ])
91
107
  ```
92
108
 
93
- ## Synthetic scroll input
94
-
95
- 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`:
96
- 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.
109
+ ## Scroll input
97
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.
98
112
 
99
113
  ```typescript
100
- import { createVList } from 'vlist/synthetic'
101
- import { scrollbar } from 'vlist'
114
+ import { createVList, scrollbar } from 'vlist'
102
115
  import 'vlist/styles'
103
116
 
104
117
  const list = createVList({
105
118
  container: '#my-list',
106
119
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
107
120
  item: { height: 48, template: item => `<div>${item.name}</div>` },
108
- scroll: { mode: 'synthetic' },
109
121
  }, [scrollbar()])
110
122
  ```
111
123
 
112
- 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.
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.
125
+
126
+ ```typescript
127
+ import { createVList } from 'vlist/native'
128
+ import { carousel } from 'vlist'
129
+
130
+ const list = createVList({
131
+ container: '#slides',
132
+ orientation: 'horizontal',
133
+ items: slides,
134
+ item: { width: 320, template: renderSlide },
135
+ }, [carousel()])
136
+ ```
137
+
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.
113
139
 
114
140
  Known limitations:
115
141
 
116
- - 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.
117
- - 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.
118
- - 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.
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`.
119
145
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
120
146
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
121
147
 
122
- 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).
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).
149
+
150
+ ## Migrating to 3.0
151
+
152
+ | Removed public API | Replacement |
153
+ |---|---|
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. |
159
+
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.
123
161
 
124
162
  ## Plugins
125
163
 
126
- | Plugin | Size | Description |
127
- |--------|------|-------------|
128
- | **Base** | 9.9 KB | Virtualization, ARIA, keyboard nav, gap, padding, bounded scroll (1M+ items) |
129
- | `vlist/synthetic` entry | +2.6 KB | Opt-in synthetic scroll input (12.5 KB total before plugins) |
130
- | `data()` | +4.8 KB | Lazy loading with velocity-aware fetching |
131
- | `selection()` | +2.8 KB | Single/multiple selection with 2D keyboard nav |
132
- | `search()` | +3.2 KB | Search bar: filter/navigate modes, match highlighting |
133
- | `groups()` | +5.3 KB | Sticky/inline headers with grid + masonry + table + data integration |
134
- | `autosize()` | +1.0 KB | Auto-measure items via ResizeObserver |
135
- | `scrollbar()` | +2.0 KB | Custom scrollbar UI |
136
- | `grid()` | +2.5 KB | 2D grid layout |
137
- | `masonry()` | +4.1 KB | Pinterest-style masonry with lane-aware keyboard nav |
138
- | `carousel()` | +3.5 KB | Paged horizontal carousel with snap and keyboard nav |
139
- | `table()` | +5.8 KB | Data table with columns, resize, sort |
140
- | `tree()` | +5.0 KB | Collapsible tree with async loading and indent guides |
141
- | `page()` | +0.8 KB | Window-level scrolling |
142
- | `sortable()` | +3.0 KB | Drag-and-drop reordering with auto-scroll |
143
- | `snapshots()` | +1.1 KB | Scroll position save/restore |
144
- | `transition()` | +2.0 KB | FLIP-based enter/exit animations for insert & remove |
164
+ | Entry / export | Minified | Gzipped |
165
+ |---|---:|---:|
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.
145
187
 
146
188
  ## Examples
147
189
 
@@ -266,6 +308,64 @@ const list = createVList({
266
308
  ])
267
309
  ```
268
310
 
311
+ ## Custom scrollbar (3.0 preview)
312
+
313
+ On `next`, `scrollbar()` remains a plugin. macOS and Android default to thin,
314
+ rounded, auto-hiding overlays; Windows defaults to a wider, square, always-visible
315
+ bar. Set `gutter: true` to reserve space. The same behavior works horizontally.
316
+
317
+ ```typescript
318
+ scrollbar({
319
+ platform: 'windows', // optional: macos | windows | android
320
+ width: 'thin', // optional: pixels (number) | auto | thin | none
321
+ radius: 4, // optional: thumb radius in pixels
322
+ thumbColor: '#666', // optional explicit colors
323
+ trackColor: '#eee',
324
+ gutter: true,
325
+ })
326
+ ```
327
+
328
+ Without explicit overrides, the plugin reads the container's standard
329
+ `scrollbar-width` and `scrollbar-color`. `auto` uses the platform width; `thin`
330
+ uses 6 px; `none` disables the track, hover target and gutter. Color order is
331
+ thumb then track. Numeric `width` and `radius` override the corresponding
332
+ `--vlist-custom-scrollbar-width` and `--vlist-custom-scrollbar-radius` variables
333
+ on the container; those variables override platform defaults. Without a custom
334
+ width variable, the width keywords retain their standard meaning (`none` always
335
+ disables the bar). Explicit colors override author standard colors. `autoHide`, `autoHideDelay` and `minThumbSize` remain available.
336
+ After changing author CSS, call `list.refreshScrollbar()` (or `refresh()` on a
337
+ standalone `Scrollbar` instance). Refresh rereads CSS; platform selection remains
338
+ fixed for that instance. Setting `enabled: false` disables the plugin's bar.
339
+
340
+ The focusable track exposes its controlled viewport, orientation, logical range,
341
+ and “Row N of M” value. Arrows move one row along the active axis, PageUp/PageDown
342
+ move a viewport, and Home/End reach the bounds. Focus keeps the bar visible.
343
+ Forced-color themes use system colors for the track, thumb and focus indicator.
344
+ The thumb has a minimum size even for millions of rows; drag positions continue
345
+ to use the full logical range. The real screen-reader acceptance pass is still
346
+ pending; this is not a completed accessibility sign-off.
347
+
348
+ ### Migrating WebKit scrollbar selectors
349
+
350
+ **The `::-webkit-scrollbar*` pseudo-elements are not mirrored.** They style
351
+ browser-owned scrollbars, not this plugin's DOM. Migrate each rule as follows
352
+ (the classes shown use the default `vlist` prefix):
353
+
354
+ | Existing selector | Plugin replacement |
355
+ | --- | --- |
356
+ | `::-webkit-scrollbar` | `.vlist-scrollbar`; `--vlist-custom-scrollbar-width` for thickness |
357
+ | `::-webkit-scrollbar-track` | `.vlist-scrollbar`; `--vlist-custom-scrollbar-track-color` |
358
+ | `::-webkit-scrollbar-thumb` | `.vlist-scrollbar__thumb`; `--vlist-custom-scrollbar-thumb-color`, `--vlist-custom-scrollbar-radius`, `--vlist-custom-scrollbar-min-thumb-size` |
359
+ | `::-webkit-scrollbar-thumb:hover` | `.vlist-scrollbar__thumb:hover`; `--vlist-custom-scrollbar-thumb-hover-color` |
360
+ | `::-webkit-scrollbar-corner` | No separate corner element. Reserved gutter space uses the `.vlist` background (`--vlist-bg`). A dedicated corner rule has no direct equivalent. |
361
+ | `::-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. |
362
+
363
+ For width and base thumb/track colors, prefer standard `scrollbar-width` and
364
+ `scrollbar-color` on the container, or plugin config. The plugin maps these onto
365
+ its custom properties at setup/refresh. Use the plugin classes and remaining
366
+ variables for radius, minimum thumb size and hover styling. High-contrast system
367
+ colors take priority while forced colors are active.
368
+
269
369
  ## Accessibility
270
370
 
271
371
  Every vlist is accessible by default following the [WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
@@ -355,7 +455,7 @@ groups({ getGroupForIndex, header: { height, template }, sticky?: true })
355
455
  selection({ mode: 'single' | 'multiple', initial?: [...ids] })
356
456
  data({ adapter: { read }, loading?: { cancelThreshold? } })
357
457
  table({ columns, rowHeight, headerHeight?, resizable? })
358
- autosize() // auto-measure items (requires estimatedHeight)
458
+ autosize() // auto-measure items (requires estimatedHeight); list.remeasure(index?) after late content
359
459
  scrollbar({ autoHide?, autoHideDelay?, minThumbSize? })
360
460
  transition({ duration?: 200, insert?: timing, remove?: timing })
361
461
  sortable({ handle?: '.drag-handle' }) // drag-and-drop reordering
@@ -363,6 +463,15 @@ page() // no config — uses document scroll
363
463
  snapshots({ autoSave: 'key' }) // automatic sessionStorage save/restore
364
464
  ```
365
465
 
466
+ ### Autosize
467
+
468
+ With `autosize()` and `item.estimatedHeight` (or `estimatedWidth` for horizontal lists), call `remeasure(i)` after content changes size without a `load` or `error` event, such as expanding text or changing a font. Call `remeasure()` to discard every cached measurement: visible items are measured again, and offscreen items use estimates until they render. Unknown or unmeasured indices are a no-op.
469
+
470
+ ```javascript
471
+ list.remeasure(12); // Re-measure one item after its content changes.
472
+ list.remeasure(); // Invalidate all sizes and measure items as they render.
473
+ ```
474
+
366
475
  Full configuration reference → **[vlist.io](https://vlist.io)**
367
476
 
368
477
  ## Base Configuration
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.7 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
4
4
 
5
- **v2.7.2** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md) · Tree keeps the bounded runway for large trees; the scale() deprecation link works; bounded-mode touch limitation documented.
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).
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
+ - **11.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
 
@@ -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,58 +53,84 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
52
53
  ])
53
54
  ```
54
55
 
55
- ## Synthetic scroll input
56
-
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.
56
+ ## Scroll input
59
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.
60
59
 
61
60
  ```typescript
62
- import { createVList } from 'vlist/synthetic'
63
- import { scrollbar } from 'vlist'
61
+ import { createVList, scrollbar } from 'vlist'
64
62
  import 'vlist/styles'
65
63
 
66
64
  const list = createVList({
67
65
  container: '#my-list',
68
66
  items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
69
67
  item: { height: 48, template: item => `<div>${item.name}</div>` },
70
- scroll: { mode: 'synthetic' },
71
68
  }, [scrollbar()])
72
69
  ```
73
70
 
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.
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
+ ```
84
+
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.
75
86
 
76
87
  Known limitations:
77
88
 
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.
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`.
81
92
  - Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
82
93
  - Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
83
94
 
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).
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).
96
+
97
+ ## Migrating to 3.0
98
+
99
+ | Removed public API | Replacement |
100
+ |---|---|
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`. |
104
+ | `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. |
106
+
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.
85
108
 
86
109
  ## Plugins
87
110
 
88
- | Plugin | Size | Description |
89
- |--------|------|-------------|
90
- | **Base** | 9.9 KB | Virtualization, ARIA, keyboard nav, gap, padding, bounded scroll (1M+ items) |
91
- | `vlist/synthetic` entry | +2.6 KB | Opt-in synthetic scroll input (12.5 KB total before plugins) |
92
- | `data()` | +4.8 KB | Lazy loading with velocity-aware fetching |
93
- | `selection()` | +2.8 KB | Single/multiple selection with 2D keyboard nav |
94
- | `search()` | +3.2 KB | Search bar: filter/navigate modes, match highlighting |
95
- | `groups()` | +5.3 KB | Sticky/inline headers with grid + masonry + table + data integration |
96
- | `autosize()` | +1.0 KB | Auto-measure items via ResizeObserver |
97
- | `scrollbar()` | +2.0 KB | Custom scrollbar UI |
98
- | `grid()` | +2.5 KB | 2D grid layout |
99
- | `masonry()` | +4.1 KB | Pinterest-style masonry with lane-aware keyboard nav |
100
- | `carousel()` | +3.5 KB | Paged horizontal carousel with snap and keyboard nav |
101
- | `table()` | +5.8 KB | Data table with columns, resize, sort |
102
- | `tree()` | +5.0 KB | Collapsible tree with async loading and indent guides |
103
- | `page()` | +0.8 KB | Window-level scrolling |
104
- | `sortable()` | +3.0 KB | Drag-and-drop reordering with auto-scroll |
105
- | `snapshots()` | +1.1 KB | Scroll position save/restore |
106
- | `transition()` | +2.0 KB | FLIP-based enter/exit animations for insert & remove |
111
+ | Entry / export | Minified | Gzipped |
112
+ |---|---:|---:|
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.
107
134
 
108
135
  ## Framework Adapters
109
136
 
@@ -114,6 +141,21 @@ Measurement corrections from autosize preserve ongoing motion. Synthetic input a
114
141
  | SolidJS | [`vlist-solidjs`](https://github.com/floor/vlist-solidjs) | 0.5 KB |
115
142
  | React | [`vlist-react`](https://github.com/floor/vlist-react) | 0.6 KB |
116
143
 
144
+ Adapters use synthetic input by default. With an adapter that forwards `factory`, select native input explicitly:
145
+
146
+ ```ts
147
+ import { useVList } from "vlist-react";
148
+ import { createVList } from "vlist/native";
149
+
150
+ useVList({
151
+ factory: createVList,
152
+ items,
153
+ item: { height: 48, template: item => String(item.id) },
154
+ });
155
+ ```
156
+
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.
158
+
117
159
  ## Docs & Examples
118
160
 
119
161
  **18 interactive examples, full API reference, tutorials, and live benchmarks → [vlist.io](https://vlist.io)**
package/dist/config.d.ts CHANGED
@@ -16,19 +16,27 @@
16
16
  * config (the adapters) pull in this module and, with it, every plugin it wires.
17
17
  */
18
18
  import type { VListItem, GroupsConfig, VListAdapter } from "./types";
19
+ import { createVList } from "./core/create";
20
+ import type { NativeScrollConfig } from "./native";
19
21
  import type { CreateVListConfig, VList, VListPlugin } from "./core/types";
20
22
  import type { DataPluginConfig } from "./plugins/data";
21
23
  import type { GridPluginConfig } from "./plugins/grid";
22
24
  import type { MasonryPluginConfig } from "./plugins/masonry";
23
25
  import type { SelectionPluginConfig } from "./plugins/selection";
24
26
  import type { ScrollbarPluginConfig } from "./plugins/scrollbar";
27
+ /** List creation function injected by an adapter consumer. */
28
+ export type VListFactory<T extends VListItem = VListItem> = typeof createVList<T>;
25
29
  /**
26
30
  * High-level, declarative vlist configuration accepted by the framework
27
31
  * adapters. It is the core `CreateVListConfig` (minus `container`, which the
28
32
  * adapter owns via a ref/node) plus the convenience "feature fields" that are
29
33
  * translated into plugins by {@link resolvePlugins}.
30
34
  */
31
- export interface VListConfig<T extends VListItem = VListItem> extends Omit<CreateVListConfig<T>, "container"> {
35
+ export interface VListConfig<T extends VListItem = VListItem> extends Omit<CreateVListConfig<T>, "container" | "scroll"> {
36
+ /** Input model; native scrolling requires a factory imported from vlist/native. */
37
+ scroll?: NativeScrollConfig;
38
+ /** List factory; defaults to core createVList. Fixed for this instance. */
39
+ factory?: VListFactory<T>;
32
40
  /** Layout mode. Wires the grid or masonry plugin from `grid`/`masonry`. */
33
41
  layout?: "grid" | "masonry";
34
42
  /** Grid layout options — applied when `layout: "grid"`. */
@@ -56,7 +64,7 @@ export interface VListConfig<T extends VListItem = VListItem> extends Omit<Creat
56
64
  /**
57
65
  * Translate a {@link VListConfig} into the ordered plugin array that the core
58
66
  * `createVList` expects. Mirrors the adapters' historical behavior exactly:
59
- * `scale` and `snapshots` are always included, and `selection` is always
67
+ * `snapshots` is always included, and `selection` is always
60
68
  * present (in `"none"` mode when unset) so its API is available. Any user
61
69
  * `plugins` are appended last as an escape hatch.
62
70
  */