vlist 3.0.1-next.1 → 3.1.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.github.md +149 -58
- package/README.md +149 -51
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core/switching.d.ts +15 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/plugins/carousel/input.d.ts +16 -0
- package/dist/react.d.ts +38 -0
- package/dist/react.js +1 -0
- package/dist/size.json +1 -1
- package/dist/solid.d.ts +34 -0
- package/dist/solid.js +1 -0
- package/dist/svelte.d.ts +30 -0
- package/dist/svelte.js +1 -0
- package/dist/synthetic.js +1 -1
- package/dist/version.json +1 -1
- package/dist/vue.d.ts +30 -0
- package/dist/vue.js +1 -0
- package/package.json +49 -2
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.
|
|
5
|
+
**v3.1.0-next.3** (prerelease on the npm `next` tag) — Vue, Svelte, Solid and React entries in the package itself: `vlist/vue`, `vlist/svelte`, `vlist/solid` and `vlist/react`, feature-first and tree-shaken like the vanilla builder; the separate adapter packages are deprecated. With `scroll.mode` from 3.1.0-next.2: the default, `"auto"`, scrolls natively and hands a list past the browser's size limit to synthetic input. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/vlist)
|
|
8
8
|
[](https://bundlephobia.com/package/vlist)
|
|
@@ -10,7 +10,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
|
|
|
10
10
|
[](https://github.com/floor/vlist/blob/main/LICENSE)
|
|
11
11
|
|
|
12
12
|
- **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
13
|
-
- **Zero dependencies** — framework-agnostic core with
|
|
13
|
+
- **Zero dependencies** — framework-agnostic core, with Vue, Svelte, Solid and React entries in the same package
|
|
14
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
|
|
@@ -24,42 +24,121 @@ The virtual list library for every framework. Ultra efficient, batteries-include
|
|
|
24
24
|
|---|---|---|---|---|---|
|
|
25
25
|
| **A11y** | WAI-ARIA + 2D keyboard, one plugin | None (DIY) | Partial | Minimal | None |
|
|
26
26
|
| **Grid + Masonry + Table** | All | Grid only | Grid + Table | Grid only | None |
|
|
27
|
-
| **Vue** | 0.
|
|
28
|
-
| **Svelte** | 0.
|
|
29
|
-
| **Solid** | 0.
|
|
27
|
+
| **Vue** | `vlist/vue`, 0.4 KB | Yes | — | Yes | 11.8 KB |
|
|
28
|
+
| **Svelte** | `vlist/svelte`, 0.3 KB | Yes | — | Yes | — |
|
|
29
|
+
| **Solid** | `vlist/solid`, 0.3 KB | Yes | — | Yes | — |
|
|
30
30
|
| **Vanilla JS** | Native | Yes | — | — | — |
|
|
31
31
|
| **Constant memory** | ~0.1 MB at 1M | No | No | No | No |
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Frameworks
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
35
|
+
Vue, Svelte, Solid and React each have an entry in the `vlist` package itself. Like the vanilla builder, they are feature-first: you pass plugins from `vlist`, so an app only bundles the features it uses. Every entry imports the core from `vlist`, and none carries a copy of its own.
|
|
36
|
+
|
|
37
|
+
| Framework | Import | Entry size |
|
|
38
|
+
|-----------|--------|------------|
|
|
39
|
+
| Vue 3 | `vlist/vue` | 0.4 KB gzip |
|
|
40
|
+
| Svelte 4 and 5 | `vlist/svelte` | 0.3 KB gzip |
|
|
41
|
+
| SolidJS | `vlist/solid` | 0.3 KB gzip |
|
|
42
|
+
| Vanilla JS | `vlist` | — |
|
|
43
|
+
| React 17+ | `vlist/react` | 0.4 KB gzip |
|
|
42
44
|
|
|
43
45
|
```bash
|
|
44
|
-
npm install vlist
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
npm install vlist
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Frameworks are optional peer dependencies, so install only the one you use.
|
|
50
|
+
|
|
51
|
+
**Vue**
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { useVList } from "vlist/vue";
|
|
56
|
+
import { grid } from "vlist";
|
|
57
|
+
|
|
58
|
+
const { containerRef, instance } = useVList(
|
|
59
|
+
{ items, item: { height: 200, template: renderPhoto } },
|
|
60
|
+
[grid({ columns: 4, gap: 16 })],
|
|
61
|
+
);
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template><div ref="containerRef" style="height: 600px" /></template>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If you pass a `ref` as the config, the list updates when its `items` change. `useVListEvent(instance, "item:click", handler)` subscribes to an event for the component's lifetime.
|
|
68
|
+
|
|
69
|
+
**Svelte**
|
|
70
|
+
|
|
71
|
+
```svelte
|
|
72
|
+
<script>
|
|
73
|
+
import { vlist } from "vlist/svelte";
|
|
74
|
+
import { grid } from "vlist";
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div
|
|
78
|
+
style="height: 600px"
|
|
79
|
+
use:vlist={{
|
|
80
|
+
config: { items, item: { height: 200, template: renderPhoto } },
|
|
81
|
+
plugins: [grid({ columns: 4, gap: 16 })],
|
|
82
|
+
onInstance: (list) => (instance = list),
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
47
85
|
```
|
|
48
86
|
|
|
49
|
-
|
|
87
|
+
**Solid**
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { createVList } from "vlist/solid";
|
|
91
|
+
import { grid } from "vlist";
|
|
92
|
+
|
|
93
|
+
const { setRef, instance } = createVList(
|
|
94
|
+
() => ({ items: items(), item: { height: 200, template: renderPhoto } }),
|
|
95
|
+
[grid({ columns: 4, gap: 16 })],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return <div ref={setRef} style={{ height: "600px" }} />;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`createVList` keeps the Solid primitive idiom. In a file that also uses the core builder, alias one of the two: `import { createVList as createSolidVList } from "vlist/solid"`.
|
|
102
|
+
|
|
103
|
+
**React**
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { useVList, useVListEvent } from "vlist/react";
|
|
107
|
+
import { grid } from "vlist";
|
|
108
|
+
|
|
109
|
+
function Photos({ items }) {
|
|
110
|
+
const { containerRef, instanceRef } = useVList(
|
|
111
|
+
{ items, item: { height: 200, template: renderPhoto } },
|
|
112
|
+
[grid({ columns: 4, gap: 16 })],
|
|
113
|
+
);
|
|
114
|
+
useVListEvent(instanceRef, "item:click", ({ item }) => open(item));
|
|
115
|
+
return <div ref={containerRef} style={{ height: 600 }} />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Plugins are read once, at mount; changing them means remounting the list (in React, with a `key`). An `items` change updates the list in place. `scroll.mode` and other structural options also take effect only at mount.
|
|
120
|
+
|
|
121
|
+
**Migrating from `vlist-vue`, `vlist-svelte`, `vlist-solidjs` or `vlist-react`.** Those packages are deprecated. Change the import path, and pass features as plugins instead of config fields:
|
|
122
|
+
|
|
123
|
+
| Deprecated package | Use |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `vlist-vue` | `vlist/vue` |
|
|
126
|
+
| `vlist-svelte` | `vlist/svelte` |
|
|
127
|
+
| `vlist-solidjs` | `vlist/solid` |
|
|
128
|
+
| `vlist-react` | `vlist/react` |
|
|
50
129
|
|
|
51
130
|
```ts
|
|
131
|
+
// before
|
|
52
132
|
import { useVList } from "vlist-react";
|
|
53
|
-
|
|
133
|
+
useVList({ items, item, selection: { mode: "single" } });
|
|
54
134
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
135
|
+
// after
|
|
136
|
+
import { useVList } from "vlist/react";
|
|
137
|
+
import { selection } from "vlist";
|
|
138
|
+
useVList({ items, item }, [selection({ mode: "single" })]);
|
|
60
139
|
```
|
|
61
140
|
|
|
62
|
-
|
|
141
|
+
Their 3.1 releases will be built on these entries and keep the config-based API, so existing code keeps working while you migrate.
|
|
63
142
|
|
|
64
143
|
## Quick Start
|
|
65
144
|
|
|
@@ -108,47 +187,54 @@ const list = createVList({
|
|
|
108
187
|
|
|
109
188
|
## Scroll input
|
|
110
189
|
|
|
111
|
-
|
|
190
|
+
`scroll.mode` chooses who owns scroll input, on the one `vlist` entry. Plugins come from `vlist` in every mode.
|
|
112
191
|
|
|
113
|
-
|
|
|
192
|
+
| `scroll.mode` | Input | Use |
|
|
114
193
|
|---|---|---|
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
194
|
+
| `"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 |
|
|
195
|
+
| `"native"` | Always native | Browser scrolling only; past the limit the last rows are out of reach, and `content:size:overflow` says so |
|
|
196
|
+
| `"synthetic"` | vlist owns wheel, touch and keys from the start | Application-owned touch motion |
|
|
118
197
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
Select synthetic input by importing its factory; there is no `scroll.mode` option. Plugins still come from `vlist`.
|
|
198
|
+
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
199
|
|
|
123
200
|
```typescript
|
|
124
|
-
import { createVList } from 'vlist
|
|
125
|
-
import { scrollbar } from 'vlist'
|
|
201
|
+
import { createVList } from 'vlist'
|
|
126
202
|
import 'vlist/styles'
|
|
127
203
|
|
|
128
204
|
const list = createVList({
|
|
129
205
|
container: '#my-list',
|
|
130
206
|
items: Array.from({ length: 1000 }, (_, id) => ({ id, name: `Row ${id}` })),
|
|
131
207
|
item: { height: 48, template: item => `<div>${item.name}</div>` },
|
|
132
|
-
},
|
|
208
|
+
scroll: { mode: 'synthetic' },
|
|
209
|
+
})
|
|
133
210
|
```
|
|
134
211
|
|
|
135
|
-
|
|
212
|
+
| Entry | Use |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `vlist` | Every list; input chosen by `scroll.mode` |
|
|
215
|
+
| `vlist/synthetic` | Deprecated: `scroll: { mode: "synthetic" }`. Kept through 3.x; it bundles the driver instead of loading it |
|
|
216
|
+
| `vlist/native` | Deprecated alias of `vlist` |
|
|
217
|
+
|
|
218
|
+
### Synthetic input
|
|
219
|
+
|
|
220
|
+
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.
|
|
221
|
+
|
|
222
|
+
`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
223
|
|
|
137
|
-
`carousel()`
|
|
224
|
+
`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
225
|
|
|
139
226
|
Synthetic input limitations:
|
|
140
227
|
|
|
141
|
-
- Horizontal RTL lists throw at creation
|
|
142
|
-
- Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use
|
|
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.
|
|
228
|
+
- 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.
|
|
229
|
+
- 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
230
|
- Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
|
|
145
231
|
- Wheel input at an edge is left to the page when it cannot move the list. Native cross-axis scrolling remains available.
|
|
146
232
|
|
|
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).
|
|
233
|
+
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
234
|
|
|
149
235
|
### Sortable gestures
|
|
150
236
|
|
|
151
|
-
`sortable()` works
|
|
237
|
+
`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
238
|
|
|
153
239
|
```typescript
|
|
154
240
|
sortable({ touchDelay: 350, dragThreshold: 5 })
|
|
@@ -161,46 +247,51 @@ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts
|
|
|
161
247
|
|
|
162
248
|
## Migrating to 3.0
|
|
163
249
|
|
|
164
|
-
|
|
250
|
+
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
251
|
|
|
166
252
|
| 2.x option or API | 3.0 replacement |
|
|
167
253
|
|---|---|
|
|
168
|
-
| `scroll.mode`
|
|
169
|
-
| `scroll.runway`
|
|
170
|
-
| `scroll.scrollbar: "native"` or `"none"` | Retained
|
|
254
|
+
| `scroll.mode` | Since 3.1: `"auto"` (default), `"native"` or `"synthetic"`. The 2.x `"bounded"` is refused. |
|
|
255
|
+
| `scroll.runway` | Remove it. Huge lists need nothing: the default `scroll.mode`, `"auto"`, hands them to synthetic input. |
|
|
256
|
+
| `scroll.scrollbar: "native"` or `"none"` | Retained. `"native"` needs native input; a synthetic list draws its own scrollbar, which `"none"` skips. |
|
|
171
257
|
| `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `ctx.scroll.setSource` to supply an external position source and commit callback. |
|
|
172
258
|
| 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;
|
|
259
|
+
| `scale()` and `ScalePluginConfig` | Remove them; the default `scroll.mode` covers the full logical range. Config no longer installs a scale stub. |
|
|
174
260
|
|
|
175
|
-
|
|
261
|
+
`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
262
|
|
|
177
263
|
## Plugins
|
|
178
264
|
|
|
179
265
|
| Entry / export | Minified | Gzipped |
|
|
180
266
|
|---|---:|---:|
|
|
181
267
|
| **Base (`vlist`)** | 27.3 KB | 10.3 KB |
|
|
182
|
-
| `vlist/synthetic` | 34.2 KB | 12.9 KB |
|
|
268
|
+
| `vlist/synthetic` (deprecated) | 34.2 KB | 12.9 KB |
|
|
183
269
|
| `vlist/native` (alias) | 27.3 KB | 10.3 KB |
|
|
184
270
|
| `a11y()` | 31.2 KB | 11.7 KB |
|
|
185
|
-
| `selection()` | 36.9 KB | 13.
|
|
186
|
-
| `data()` |
|
|
271
|
+
| `selection()` | 36.9 KB | 13.3 KB |
|
|
272
|
+
| `data()` | 41.0 KB | 15.1 KB |
|
|
187
273
|
| `scrollbar()` | 35.5 KB | 13.2 KB |
|
|
188
274
|
| `sortable()` | 39.2 KB | 13.9 KB |
|
|
189
275
|
| `groups()` | 43.4 KB | 15.7 KB |
|
|
190
|
-
| `page()` | 29.
|
|
276
|
+
| `page()` | 29.9 KB | 11.2 KB |
|
|
191
277
|
| `snapshots()` | 30.6 KB | 11.4 KB |
|
|
192
|
-
| `transition()` | 34.
|
|
193
|
-
| `autosize()` | 30.6 KB | 11.
|
|
278
|
+
| `transition()` | 34.1 KB | 12.3 KB |
|
|
279
|
+
| `autosize()` | 30.6 KB | 11.5 KB |
|
|
194
280
|
| `grid()` | 34.7 KB | 12.9 KB |
|
|
195
281
|
| `table()` | 45.8 KB | 16.2 KB |
|
|
196
282
|
| `masonry()` | 39.4 KB | 14.7 KB |
|
|
197
283
|
| `tree()` | 43.5 KB | 15.7 KB |
|
|
198
|
-
| `search()` | 36.
|
|
199
|
-
| `carousel()` |
|
|
200
|
-
| `vlist/synthetic` + `carousel()` |
|
|
284
|
+
| `search()` | 36.9 KB | 13.6 KB |
|
|
285
|
+
| `carousel()` | 43.2 KB | 15.8 KB |
|
|
286
|
+
| `vlist/synthetic` + `carousel()` | 49.3 KB | 18.1 KB |
|
|
201
287
|
| `vlist/synthetic` + `sortable()` | 46.1 KB | 16.5 KB |
|
|
288
|
+
| `vlist/vue` `useVList` | 27.7 KB | 10.5 KB |
|
|
289
|
+
| `vlist/svelte` `vlist` | 27.5 KB | 10.4 KB |
|
|
290
|
+
| `vlist/solid` `createVList` | 27.6 KB | 10.4 KB |
|
|
291
|
+
| `vlist/react` `useVList` | 27.8 KB | 10.5 KB |
|
|
292
|
+
| `vlist/react` + `grid()` | 35.2 KB | 13.1 KB |
|
|
202
293
|
|
|
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,
|
|
294
|
+
Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin; framework rows add the entry, with the framework itself external. The base is **10,539 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,192 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
295
|
|
|
205
296
|
## Examples
|
|
206
297
|
|
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.
|
|
5
|
+
**v3.1.0-next.3** (prerelease on the npm `next` tag) — Vue, Svelte, Solid and React entries in the package itself: `vlist/vue`, `vlist/svelte`, `vlist/solid` and `vlist/react`, feature-first and tree-shaken like the vanilla builder; the separate adapter packages are deprecated. With `scroll.mode` from 3.1.0-next.2: the default, `"auto"`, scrolls natively and hands a list past the browser's size limit to synthetic input. See the [changelog](https://github.com/floor/vlist/blob/next/CHANGELOG.md).
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/vlist)
|
|
8
8
|
[](https://bundlephobia.com/package/vlist)
|
|
@@ -10,7 +10,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
|
|
|
10
10
|
[](https://github.com/floor/vlist/blob/main/LICENSE)
|
|
11
11
|
|
|
12
12
|
- **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
13
|
-
- **Zero dependencies** — framework-agnostic core,
|
|
13
|
+
- **Zero dependencies** — framework-agnostic core, with Vue, Svelte, Solid and React entries in the same package
|
|
14
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
|
|
@@ -55,47 +55,54 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
|
|
|
55
55
|
|
|
56
56
|
## Scroll input
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
`scroll.mode` chooses who owns scroll input, on the one `vlist` entry. Plugins come from `vlist` in every mode.
|
|
59
59
|
|
|
60
|
-
|
|
|
60
|
+
| `scroll.mode` | Input | Use |
|
|
61
61
|
|---|---|---|
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
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
|
-
|
|
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
|
|
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
|
-
},
|
|
76
|
+
scroll: { mode: 'synthetic' },
|
|
77
|
+
})
|
|
80
78
|
```
|
|
81
79
|
|
|
82
|
-
|
|
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.
|
|
83
89
|
|
|
84
|
-
`
|
|
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.
|
|
91
|
+
|
|
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
|
|
89
|
-
- Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use
|
|
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
|
|
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,69 +115,160 @@ The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts
|
|
|
108
115
|
|
|
109
116
|
## Migrating to 3.0
|
|
110
117
|
|
|
111
|
-
|
|
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`
|
|
116
|
-
| `scroll.runway`
|
|
117
|
-
| `scroll.scrollbar: "native"` or `"none"` | Retained
|
|
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;
|
|
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
|
-
|
|
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
134
|
| **Base (`vlist`)** | 27.3 KB | 10.3 KB |
|
|
128
|
-
| `vlist/synthetic` | 34.2 KB | 12.9 KB |
|
|
135
|
+
| `vlist/synthetic` (deprecated) | 34.2 KB | 12.9 KB |
|
|
129
136
|
| `vlist/native` (alias) | 27.3 KB | 10.3 KB |
|
|
130
137
|
| `a11y()` | 31.2 KB | 11.7 KB |
|
|
131
|
-
| `selection()` | 36.9 KB | 13.
|
|
132
|
-
| `data()` |
|
|
138
|
+
| `selection()` | 36.9 KB | 13.3 KB |
|
|
139
|
+
| `data()` | 41.0 KB | 15.1 KB |
|
|
133
140
|
| `scrollbar()` | 35.5 KB | 13.2 KB |
|
|
134
141
|
| `sortable()` | 39.2 KB | 13.9 KB |
|
|
135
142
|
| `groups()` | 43.4 KB | 15.7 KB |
|
|
136
|
-
| `page()` | 29.
|
|
143
|
+
| `page()` | 29.9 KB | 11.2 KB |
|
|
137
144
|
| `snapshots()` | 30.6 KB | 11.4 KB |
|
|
138
|
-
| `transition()` | 34.
|
|
139
|
-
| `autosize()` | 30.6 KB | 11.
|
|
145
|
+
| `transition()` | 34.1 KB | 12.3 KB |
|
|
146
|
+
| `autosize()` | 30.6 KB | 11.5 KB |
|
|
140
147
|
| `grid()` | 34.7 KB | 12.9 KB |
|
|
141
148
|
| `table()` | 45.8 KB | 16.2 KB |
|
|
142
149
|
| `masonry()` | 39.4 KB | 14.7 KB |
|
|
143
150
|
| `tree()` | 43.5 KB | 15.7 KB |
|
|
144
|
-
| `search()` | 36.
|
|
145
|
-
| `carousel()` |
|
|
146
|
-
| `vlist/synthetic` + `carousel()` |
|
|
151
|
+
| `search()` | 36.9 KB | 13.6 KB |
|
|
152
|
+
| `carousel()` | 43.2 KB | 15.8 KB |
|
|
153
|
+
| `vlist/synthetic` + `carousel()` | 49.3 KB | 18.1 KB |
|
|
147
154
|
| `vlist/synthetic` + `sortable()` | 46.1 KB | 16.5 KB |
|
|
155
|
+
| `vlist/vue` `useVList` | 27.7 KB | 10.5 KB |
|
|
156
|
+
| `vlist/svelte` `vlist` | 27.5 KB | 10.4 KB |
|
|
157
|
+
| `vlist/solid` `createVList` | 27.6 KB | 10.4 KB |
|
|
158
|
+
| `vlist/react` `useVList` | 27.8 KB | 10.5 KB |
|
|
159
|
+
| `vlist/react` + `grid()` | 35.2 KB | 13.1 KB |
|
|
160
|
+
|
|
161
|
+
Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin; framework rows add the entry, with the framework itself external. The base is **10,539 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,192 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.
|
|
162
|
+
|
|
163
|
+
## Frameworks
|
|
164
|
+
|
|
165
|
+
Vue, Svelte, Solid and React each have an entry in the `vlist` package itself. Like the vanilla builder, they are feature-first: you pass plugins from `vlist`, so an app only bundles the features it uses. Every entry imports the core from `vlist`, and none carries a copy of its own.
|
|
166
|
+
|
|
167
|
+
| Framework | Import | Entry size |
|
|
168
|
+
|-----------|--------|------------|
|
|
169
|
+
| Vue 3 | `vlist/vue` | 0.4 KB gzip |
|
|
170
|
+
| Svelte 4 and 5 | `vlist/svelte` | 0.3 KB gzip |
|
|
171
|
+
| SolidJS | `vlist/solid` | 0.3 KB gzip |
|
|
172
|
+
| Vanilla JS | `vlist` | — |
|
|
173
|
+
| React 17+ | `vlist/react` | 0.4 KB gzip |
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm install vlist
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Frameworks are optional peer dependencies, so install only the one you use.
|
|
180
|
+
|
|
181
|
+
**Vue**
|
|
148
182
|
|
|
149
|
-
|
|
183
|
+
```vue
|
|
184
|
+
<script setup lang="ts">
|
|
185
|
+
import { useVList } from "vlist/vue";
|
|
186
|
+
import { grid } from "vlist";
|
|
150
187
|
|
|
151
|
-
|
|
188
|
+
const { containerRef, instance } = useVList(
|
|
189
|
+
{ items, item: { height: 200, template: renderPhoto } },
|
|
190
|
+
[grid({ columns: 4, gap: 16 })],
|
|
191
|
+
);
|
|
192
|
+
</script>
|
|
152
193
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
194
|
+
<template><div ref="containerRef" style="height: 600px" /></template>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
If you pass a `ref` as the config, the list updates when its `items` change. `useVListEvent(instance, "item:click", handler)` subscribes to an event for the component's lifetime.
|
|
198
|
+
|
|
199
|
+
**Svelte**
|
|
200
|
+
|
|
201
|
+
```svelte
|
|
202
|
+
<script>
|
|
203
|
+
import { vlist } from "vlist/svelte";
|
|
204
|
+
import { grid } from "vlist";
|
|
205
|
+
</script>
|
|
206
|
+
|
|
207
|
+
<div
|
|
208
|
+
style="height: 600px"
|
|
209
|
+
use:vlist={{
|
|
210
|
+
config: { items, item: { height: 200, template: renderPhoto } },
|
|
211
|
+
plugins: [grid({ columns: 4, gap: 16 })],
|
|
212
|
+
onInstance: (list) => (instance = list),
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Solid**
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { createVList } from "vlist/solid";
|
|
221
|
+
import { grid } from "vlist";
|
|
159
222
|
|
|
160
|
-
|
|
223
|
+
const { setRef, instance } = createVList(
|
|
224
|
+
() => ({ items: items(), item: { height: 200, template: renderPhoto } }),
|
|
225
|
+
[grid({ columns: 4, gap: 16 })],
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return <div ref={setRef} style={{ height: "600px" }} />;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`createVList` keeps the Solid primitive idiom. In a file that also uses the core builder, alias one of the two: `import { createVList as createSolidVList } from "vlist/solid"`.
|
|
232
|
+
|
|
233
|
+
**React**
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
import { useVList, useVListEvent } from "vlist/react";
|
|
237
|
+
import { grid } from "vlist";
|
|
238
|
+
|
|
239
|
+
function Photos({ items }) {
|
|
240
|
+
const { containerRef, instanceRef } = useVList(
|
|
241
|
+
{ items, item: { height: 200, template: renderPhoto } },
|
|
242
|
+
[grid({ columns: 4, gap: 16 })],
|
|
243
|
+
);
|
|
244
|
+
useVListEvent(instanceRef, "item:click", ({ item }) => open(item));
|
|
245
|
+
return <div ref={containerRef} style={{ height: 600 }} />;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Plugins are read once, at mount; changing them means remounting the list (in React, with a `key`). An `items` change updates the list in place. `scroll.mode` and other structural options also take effect only at mount.
|
|
250
|
+
|
|
251
|
+
**Migrating from `vlist-vue`, `vlist-svelte`, `vlist-solidjs` or `vlist-react`.** Those packages are deprecated. Change the import path, and pass features as plugins instead of config fields:
|
|
252
|
+
|
|
253
|
+
| Deprecated package | Use |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `vlist-vue` | `vlist/vue` |
|
|
256
|
+
| `vlist-svelte` | `vlist/svelte` |
|
|
257
|
+
| `vlist-solidjs` | `vlist/solid` |
|
|
258
|
+
| `vlist-react` | `vlist/react` |
|
|
161
259
|
|
|
162
260
|
```ts
|
|
261
|
+
// before
|
|
163
262
|
import { useVList } from "vlist-react";
|
|
164
|
-
|
|
263
|
+
useVList({ items, item, selection: { mode: "single" } });
|
|
165
264
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
265
|
+
// after
|
|
266
|
+
import { useVList } from "vlist/react";
|
|
267
|
+
import { selection } from "vlist";
|
|
268
|
+
useVList({ items, item }, [selection({ mode: "single" })]);
|
|
171
269
|
```
|
|
172
270
|
|
|
173
|
-
|
|
271
|
+
Their 3.1 releases will be built on these entries and keep the config-based API, so existing code keeps working while you migrate.
|
|
174
272
|
|
|
175
273
|
## Docs & Examples
|
|
176
274
|
|
package/dist/config.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
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, ScrollConfig } from "./types";
|
|
19
|
-
import { createVList } from "./
|
|
19
|
+
import { createVList } from "./index";
|
|
20
20
|
import type { CreateVListConfig, VList, VListPlugin, PluginMethods } from "./core/types";
|
|
21
21
|
import type { AutosizeMethods } from "./plugins/autosize/plugin";
|
|
22
22
|
import type { DataMethods } from "./plugins/data/plugin";
|