vlist 3.0.0-next.2 → 3.0.0-next.4
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 +88 -39
- package/README.md +44 -27
- package/dist/config.d.ts +129 -12
- package/dist/config.js +1 -1
- package/dist/core/create.d.ts +22 -4
- package/dist/core/dom.d.ts +22 -2
- package/dist/core/fold.d.ts +52 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/pipeline.d.ts +1 -1
- package/dist/core/pool.d.ts +9 -2
- package/dist/core/runway.d.ts +29 -3
- package/dist/core/scroll.d.ts +1 -1
- package/dist/core/sizes.d.ts +1 -1
- package/dist/core/state.d.ts +13 -1
- package/dist/core/types.d.ts +151 -61
- package/dist/core/velocity.d.ts +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +1 -1
- package/dist/internals.d.ts +0 -1
- package/dist/internals.js +1 -1
- package/dist/plugins/a11y/index.d.ts +1 -0
- package/dist/plugins/a11y/plugin.d.ts +1 -1
- package/dist/plugins/autosize/index.d.ts +2 -1
- package/dist/plugins/autosize/plugin.d.ts +13 -2
- package/dist/plugins/carousel/engine.d.ts +1 -1
- package/dist/plugins/carousel/index.d.ts +1 -0
- package/dist/plugins/carousel/plugin.d.ts +42 -6
- package/dist/plugins/carousel/presets.d.ts +1 -1
- package/dist/plugins/data/index.d.ts +1 -0
- package/dist/plugins/data/plugin.d.ts +15 -2
- package/dist/plugins/grid/index.d.ts +2 -1
- package/dist/plugins/grid/plugin.d.ts +14 -2
- package/dist/plugins/groups/async-bridge.d.ts +6 -6
- package/dist/plugins/groups/index.d.ts +1 -0
- package/dist/plugins/groups/layout.d.ts +2 -2
- package/dist/plugins/groups/plugin.d.ts +12 -4
- package/dist/plugins/groups/sticky.d.ts +2 -1
- package/dist/plugins/groups/types.d.ts +8 -3
- package/dist/plugins/masonry/index.d.ts +2 -1
- package/dist/plugins/masonry/plugin.d.ts +10 -2
- package/dist/plugins/masonry/renderer.d.ts +5 -1
- package/dist/plugins/page/plugin.d.ts +1 -1
- package/dist/plugins/scrollbar/index.d.ts +2 -1
- package/dist/plugins/scrollbar/plugin.d.ts +7 -2
- package/dist/plugins/search/index.d.ts +1 -0
- package/dist/plugins/search/plugin.d.ts +29 -1
- package/dist/plugins/selection/index.d.ts +2 -1
- package/dist/plugins/selection/plugin.d.ts +23 -2
- package/dist/plugins/snapshots/index.d.ts +1 -0
- package/dist/plugins/snapshots/plugin.d.ts +9 -2
- package/dist/plugins/sortable/index.d.ts +1 -0
- package/dist/plugins/sortable/plugin.d.ts +14 -3
- package/dist/plugins/table/index.d.ts +1 -0
- package/dist/plugins/table/plugin.d.ts +19 -3
- package/dist/plugins/transition/plugin.d.ts +1 -1
- package/dist/plugins/tree/index.d.ts +1 -0
- package/dist/plugins/tree/layout.d.ts +3 -1
- package/dist/plugins/tree/plugin.d.ts +37 -3
- package/dist/plugins/tree/types.d.ts +1 -1
- package/dist/rendering/sizes.d.ts +19 -1
- package/dist/size.json +1 -1
- package/dist/synthetic.d.ts +2 -2
- package/dist/synthetic.js +1 -1
- package/dist/types.d.ts +14 -21
- package/dist/utils/apply-scroll-behavior.d.ts +15 -0
- package/dist/utils/scroll-padding.d.ts +36 -0
- package/dist/utils/stats.d.ts +9 -1
- package/dist/vlist-tree.css +1 -1
- package/dist/vlist.css +1 -1
- package/package.json +6 -2
- package/dist/plugins/grid/renderer.d.ts +0 -68
package/README.github.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
|
|
4
4
|
|
|
5
|
-
**v3.0.0-next.
|
|
5
|
+
**v3.0.0-next.4** (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
|
[](https://www.npmjs.com/package/vlist)
|
|
8
8
|
[](https://bundlephobia.com/package/vlist)
|
|
9
9
|
[](https://github.com/floor/vlist/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/floor/vlist/blob/main/LICENSE)
|
|
11
11
|
|
|
12
|
-
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
12
|
+
- **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
13
13
|
- **Zero dependencies** — framework-agnostic core with tiny adapters for Vue, Svelte, Solid, React
|
|
14
|
-
- **
|
|
14
|
+
- **10.0 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
|
|
15
15
|
- **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
|
|
16
16
|
- **Tree, grid, masonry, carousel, table, groups, data, selection, search, sortable, transition** — all opt-in
|
|
17
17
|
- **Axis-neutral** — vertical and horizontal scrolling through a single code path, all plugins work in both orientations
|
|
@@ -22,7 +22,7 @@ The virtual list library for every framework. Ultra efficient, batteries-include
|
|
|
22
22
|
|
|
23
23
|
| | vlist | TanStack Virtual | react-virtuoso | virtua | vue-virtual-scroller |
|
|
24
24
|
|---|---|---|---|---|---|
|
|
25
|
-
| **A11y
|
|
25
|
+
| **A11y** | WAI-ARIA + 2D keyboard, one plugin | None (DIY) | Partial | Minimal | None |
|
|
26
26
|
| **Grid + Masonry + Table** | All | Grid only | Grid + Table | Grid only | None |
|
|
27
27
|
| **Vue** | 0.6 KB adapter | Yes | — | Yes | 11.8 KB |
|
|
28
28
|
| **Svelte** | 0.5 KB adapter | Yes | — | Yes | — |
|
|
@@ -108,7 +108,7 @@ const list = createVList({
|
|
|
108
108
|
|
|
109
109
|
## Scroll input
|
|
110
110
|
|
|
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
|
|
111
|
+
Native scrolling is the default: import `createVList` and plugins from `vlist`. This preserves browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. Carousel and sortable work with this factory; horizontal RTL lists are rejected by both entries. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
|
|
112
112
|
|
|
113
113
|
| Entry | Input model | Use |
|
|
114
114
|
|---|---|---|
|
|
@@ -134,9 +134,11 @@ const list = createVList({
|
|
|
134
134
|
|
|
135
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.
|
|
136
136
|
|
|
137
|
+
`carousel()` works with either entry. Synthetic carousel folds preserve touch motion and smooth snapping across whole laps. `page()` and carousel remain incompatible. The shared carousel plugin includes its native wrap handler even when used with synthetic input; the combined size is reported below.
|
|
138
|
+
|
|
137
139
|
Synthetic input limitations:
|
|
138
140
|
|
|
139
|
-
-
|
|
141
|
+
- Horizontal RTL lists throw at creation — in this entry and in `vlist`. Vertical lists and tables support `dir="rtl"` in both, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
|
|
140
142
|
- Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use the native default when boundary gestures must scroll the parent page.
|
|
141
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.
|
|
142
144
|
- Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
|
|
@@ -144,6 +146,19 @@ Synthetic input limitations:
|
|
|
144
146
|
|
|
145
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).
|
|
146
148
|
|
|
149
|
+
### Sortable gestures
|
|
150
|
+
|
|
151
|
+
`sortable()` works with either input entry. Mouse and trackpad dragging still starts after `dragThreshold` (5 px by default). Touch and pen without a handle use `touchDelay`, a 350 ms long press; moving at least `dragThreshold` pixels in any direction before it completes yields to scrolling. This delay separates a deliberate hold from a quick flick. With `handle`, dragging starts at the threshold without waiting; touches elsewhere scroll.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
sortable({ touchDelay: 350, dragThreshold: 5 })
|
|
155
|
+
sortable({ handle: '.drag-handle' })
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
A touch during scrolling catches the motion and does not arm a hold for that contact. Lift and press again once the list is at rest. A second finger or `pointercancel` cancels the pending press or active drag. Edge auto-scroll uses the list's logical position and stops on drop.
|
|
159
|
+
|
|
160
|
+
The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts on the pressed item. A claimed touch/pen drag adds `.vlist-sort-ghost--touch` to the ghost as a visual cue; override that class in CSS to customize it. Keyboard reordering remains Space to grab/drop, arrows to move and Escape to cancel.
|
|
161
|
+
|
|
147
162
|
## Migrating to 3.0
|
|
148
163
|
|
|
149
164
|
`3.0.0-next.1` used synthetic input by default. `3.0.0-next.2` restores the 2.x default. Import `vlist/synthetic` explicitly to retain synthetic behavior across these prereleases. `vlist/native` remains a deprecated compatibility alias; its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
|
|
@@ -153,36 +168,39 @@ Measurement corrections from autosize preserve ongoing synthetic motion. Existin
|
|
|
153
168
|
| `scroll.mode` (all values, both entries) | Omit it. `vlist` provides native scrolling; import `vlist/synthetic` for synthetic input. |
|
|
154
169
|
| `scroll.runway` (both entries) | Remove it. For huge lists use `vlist/synthetic`, which needs no native runway. |
|
|
155
170
|
| `scroll.scrollbar: "native"` or `"none"` | Retained in default `ScrollConfig`. With synthetic input, omit the string and optionally install `scrollbar()`. |
|
|
156
|
-
| `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `
|
|
171
|
+
| `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `ctx.scroll.setSource` to supply an external position source and commit callback. |
|
|
172
|
+
| Flat `PluginContext` members (`registerMethod`, `forceRender`, `sizeCache`, `scrollTo`, …) | Grouped by capability: `ctx.hooks.method`, `ctx.render.force`, `ctx.sizes.cache`, `ctx.scroll.to`, `ctx.items.all`. `pool`, `config`, `emitter`, `template` and `getState()` stay at the top level. |
|
|
157
173
|
| `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
|
|
158
174
|
|
|
159
|
-
Removed mode/runway options throw a migration error before creating DOM. `vlist/config`
|
|
175
|
+
Removed mode/runway options throw a migration error before creating DOM. `vlist/config` wires only what the config asks for: `selection`, the custom scrollbar, `snapshots` and `a11y` are each opt-in, so an adapter list matches a core list built from the same options. It keeps the scrollbar options convenience and its top-level `scrollbar: "none"`; pass `scrollbar: true` for the overlay scrollbar earlier versions added to every list. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.scroll.setBoundedWrap`.
|
|
160
176
|
|
|
161
177
|
## Plugins
|
|
162
178
|
|
|
163
179
|
| Entry / export | Minified | Gzipped |
|
|
164
180
|
|---|---:|---:|
|
|
165
|
-
| **Base (`vlist`)** |
|
|
166
|
-
| `vlist/synthetic` |
|
|
167
|
-
| `vlist/native` (alias) |
|
|
168
|
-
| `a11y()` |
|
|
169
|
-
| `selection()` |
|
|
170
|
-
| `data()` |
|
|
171
|
-
| `scrollbar()` |
|
|
172
|
-
| `sortable()` |
|
|
173
|
-
| `groups()` |
|
|
174
|
-
| `page()` |
|
|
175
|
-
| `snapshots()` |
|
|
176
|
-
| `transition()` |
|
|
177
|
-
| `autosize()` |
|
|
178
|
-
| `grid()` |
|
|
179
|
-
| `table()` |
|
|
180
|
-
| `masonry()` |
|
|
181
|
-
| `tree()` |
|
|
182
|
-
| `search()` |
|
|
183
|
-
| `carousel()` |
|
|
184
|
-
|
|
185
|
-
|
|
181
|
+
| **Base (`vlist`)** | 26.9 KB | 10.0 KB |
|
|
182
|
+
| `vlist/synthetic` | 33.8 KB | 12.6 KB |
|
|
183
|
+
| `vlist/native` (alias) | 26.9 KB | 10.0 KB |
|
|
184
|
+
| `a11y()` | 30.7 KB | 11.3 KB |
|
|
185
|
+
| `selection()` | 36.4 KB | 12.9 KB |
|
|
186
|
+
| `data()` | 40.6 KB | 14.8 KB |
|
|
187
|
+
| `scrollbar()` | 35.1 KB | 12.8 KB |
|
|
188
|
+
| `sortable()` | 38.7 KB | 13.4 KB |
|
|
189
|
+
| `groups()` | 43.0 KB | 15.4 KB |
|
|
190
|
+
| `page()` | 29.5 KB | 10.9 KB |
|
|
191
|
+
| `snapshots()` | 30.2 KB | 11.1 KB |
|
|
192
|
+
| `transition()` | 33.7 KB | 12.0 KB |
|
|
193
|
+
| `autosize()` | 30.1 KB | 11.1 KB |
|
|
194
|
+
| `grid()` | 34.4 KB | 12.5 KB |
|
|
195
|
+
| `table()` | 45.4 KB | 15.9 KB |
|
|
196
|
+
| `masonry()` | 39.0 KB | 14.4 KB |
|
|
197
|
+
| `tree()` | 43.1 KB | 15.3 KB |
|
|
198
|
+
| `search()` | 36.5 KB | 13.3 KB |
|
|
199
|
+
| `carousel()` | 41.7 KB | 15.1 KB |
|
|
200
|
+
| `vlist/synthetic` + `carousel()` | 47.8 KB | 17.4 KB |
|
|
201
|
+
| `vlist/synthetic` + `sortable()` | 45.7 KB | 16.1 KB |
|
|
202
|
+
|
|
203
|
+
Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin. The base is **10,200 bytes gzipped**, within the 10.0 KB budget (10,240 bytes). `bun run size` fails if a scenario fails to build, an unused plugin leaks into a bundle, or any published size exceeds its gzip budget. Synthetic input is **12,889 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
|
|
186
204
|
|
|
187
205
|
## Examples
|
|
188
206
|
|
|
@@ -307,6 +325,13 @@ const list = createVList({
|
|
|
307
325
|
])
|
|
308
326
|
```
|
|
309
327
|
|
|
328
|
+
Search filters client-side over the items the list holds, so it cannot be
|
|
329
|
+
combined with `data()` — creating a list with both throws. With an adapter, the
|
|
330
|
+
rows in memory are only the loaded window; query the remote dataset through the
|
|
331
|
+
adapter instead. It cannot be combined with `tree()` either: filtering a tree
|
|
332
|
+
means keeping the ancestors of each match, which belongs to the plugin that
|
|
333
|
+
owns the layout, and `tree()` offers no such hook.
|
|
334
|
+
|
|
310
335
|
## Custom scrollbar (3.0 preview)
|
|
311
336
|
|
|
312
337
|
On `next`, `scrollbar()` remains a plugin. macOS and Android default to thin,
|
|
@@ -367,7 +392,9 @@ colors take priority while forced colors are active.
|
|
|
367
392
|
|
|
368
393
|
## Accessibility
|
|
369
394
|
|
|
370
|
-
|
|
395
|
+
Accessibility is a plugin, not a default. Add `a11y()`, or a `selection()` mode
|
|
396
|
+
other than `"none"` which brings the same behaviour, and the list follows the
|
|
397
|
+
[WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
|
|
371
398
|
|
|
372
399
|
- **Arrow keys** move focus between items with a visible focus ring
|
|
373
400
|
- **2D navigation** in grids and masonry — Up/Down by row, Left/Right by cell
|
|
@@ -376,7 +403,9 @@ Every vlist is accessible by default following the [WAI-ARIA listbox pattern](ht
|
|
|
376
403
|
- **Screen-reader DOM ordering** — items reordered on scroll idle for correct reading order
|
|
377
404
|
- **Focus recovery** — maintains focus when items are removed
|
|
378
405
|
|
|
379
|
-
|
|
406
|
+
A list created without either stays display-only: `role="list"`, no item roles
|
|
407
|
+
and out of the tab order. That is the right shape for log viewers and activity
|
|
408
|
+
feeds, and for items carrying their own interactive elements.
|
|
380
409
|
|
|
381
410
|
## API
|
|
382
411
|
|
|
@@ -390,7 +419,7 @@ const list = createVList(config, [plugin1(), plugin2()])
|
|
|
390
419
|
|--------|-------------|
|
|
391
420
|
| `list.setItems(items)` | Replace all items |
|
|
392
421
|
| `list.appendItems(items)` | Add to end (auto-scrolls in reverse mode) |
|
|
393
|
-
| `list.prependItems(items)` | Add to start (
|
|
422
|
+
| `list.prependItems(items)` | Add to start (in reverse mode, the visible rows hold their position) |
|
|
394
423
|
| `list.updateItem(id, partial)` | Update a single item by ID |
|
|
395
424
|
| `list.insertItem(item, index?)` | Insert at index (animated with `transition`) |
|
|
396
425
|
| `list.removeItem(id)` | Remove by ID (animated with `transition`) |
|
|
@@ -410,12 +439,14 @@ const list = createVList(config, [plugin1(), plugin2()])
|
|
|
410
439
|
|
|
411
440
|
| Method | Description |
|
|
412
441
|
|--------|-------------|
|
|
413
|
-
| `list.select(...ids)` | Select item(s) |
|
|
442
|
+
| `list.select(...ids)` | Select item(s) without moving the viewport |
|
|
414
443
|
| `list.deselect(...ids)` | Deselect item(s) |
|
|
415
444
|
| `list.toggleSelect(id)` | Toggle |
|
|
416
445
|
| `list.selectAll()` / `list.clearSelection()` | Bulk operations |
|
|
417
446
|
| `list.getSelected()` | Array of selected IDs |
|
|
418
447
|
| `list.getSelectedItems()` | Array of selected items |
|
|
448
|
+
| `list.selectNext()` | Select the next item and scroll it into view |
|
|
449
|
+
| `list.selectPrevious()` | Select the previous item and scroll it into view |
|
|
419
450
|
|
|
420
451
|
### Events
|
|
421
452
|
|
|
@@ -434,6 +465,10 @@ list.on('sort:end', ({ fromIndex, toIndex }) => {})
|
|
|
434
465
|
list.on('sort:cancel', ({ originalItems }) => {})
|
|
435
466
|
```
|
|
436
467
|
|
|
468
|
+
The `index` on `item:click`, `item:dblclick` and `item:contextmenu` is the data
|
|
469
|
+
index — the one `getItemAt`, `scrollToIndex` and `removeItem` take — not the
|
|
470
|
+
layout index, which counts group headers and carousel laps.
|
|
471
|
+
|
|
437
472
|
### Properties
|
|
438
473
|
|
|
439
474
|
| Property | Description |
|
|
@@ -481,8 +516,7 @@ Full configuration reference → **[vlist.io](https://vlist.io)**
|
|
|
481
516
|
| `ariaLabel` | — | Accessible label for the listbox |
|
|
482
517
|
| `orientation` | `'vertical'` | `'vertical'` or `'horizontal'` scroll direction |
|
|
483
518
|
| `padding` | `0` | Content inset — number, `[v, h]`, or `[top, right, bottom, left]` |
|
|
484
|
-
| `
|
|
485
|
-
| `reverse` | `false` | Reverse mode for chat UIs |
|
|
519
|
+
| `reverse` | `false` | The list reads bottom-up (chat UIs): a view sitting at the end stays there as `appendItems` adds to it, `prependItems` holds the visible rows in place as older history loads above them, arrow keys invert, and `transition()` animates from the bottom. Item order is yours to supply; the layout itself is not reversed. `masonry()` and `table()` reject it |
|
|
486
520
|
|
|
487
521
|
## Styling
|
|
488
522
|
|
|
@@ -512,23 +546,38 @@ Live benchmarks against 9 competitors → **[vlist.io/benchmarks](https://vlist.
|
|
|
512
546
|
|
|
513
547
|
## TypeScript
|
|
514
548
|
|
|
515
|
-
Fully typed
|
|
549
|
+
Fully typed and generic over your item type. An item only needs an `id`, so ordinary
|
|
550
|
+
interfaces work:
|
|
516
551
|
|
|
517
552
|
```typescript
|
|
518
|
-
import { createVList, grid, type
|
|
553
|
+
import { createVList, grid, selection, type CreateVListConfig } from 'vlist'
|
|
519
554
|
|
|
520
555
|
interface Photo { id: number; url: string; title: string }
|
|
521
556
|
|
|
522
|
-
const
|
|
557
|
+
const config: CreateVListConfig<Photo> = {
|
|
523
558
|
container: '#gallery',
|
|
524
559
|
items: photos,
|
|
525
560
|
item: {
|
|
526
561
|
height: 200,
|
|
527
562
|
template: (photo) => `<img src="${photo.url}" />`,
|
|
528
563
|
},
|
|
529
|
-
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const list = createVList(config, [grid({ columns: 4 }), selection<Photo>({ mode: 'multiple' })])
|
|
567
|
+
|
|
568
|
+
list.select(photos[0].id) // typed: selection is in the array
|
|
569
|
+
const chosen: Photo[] = list.getSelectedItems()
|
|
570
|
+
// list.expand(1) // compile error: no tree plugin here
|
|
530
571
|
```
|
|
531
572
|
|
|
573
|
+
`createVList` infers the methods each plugin adds, so a list exposes exactly what its
|
|
574
|
+
plugins provide, and a mistyped call does not compile.
|
|
575
|
+
|
|
576
|
+
TypeScript does not allow a partial type argument list. Writing
|
|
577
|
+
`createVList<Photo>(config, plugins)` supplies the item type and leaves the plugin list on
|
|
578
|
+
its default, so plugin methods are not inferred. Type the config as above, or let the item
|
|
579
|
+
type come from your data.
|
|
580
|
+
|
|
532
581
|
## Migrating from v1
|
|
533
582
|
|
|
534
583
|
v2 is a ground-up rewrite — simpler API, 55% smaller base bundle, zero-allocation scroll path. [Full announcement →](https://vlist.io/blog/v2)
|
package/README.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins.
|
|
4
4
|
|
|
5
|
-
**v3.0.0-next.
|
|
5
|
+
**v3.0.0-next.4** (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
|
[](https://www.npmjs.com/package/vlist)
|
|
8
8
|
[](https://bundlephobia.com/package/vlist)
|
|
9
9
|
[](https://github.com/floor/vlist/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/floor/vlist/blob/main/LICENSE)
|
|
11
11
|
|
|
12
|
-
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
12
|
+
- **Accessible** — `a11y()` or a selection mode adds WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
13
13
|
- **Zero dependencies** — framework-agnostic core, tiny adapters for Vue, Svelte, Solid, React
|
|
14
|
-
- **
|
|
14
|
+
- **10.0 KB gzipped (3.0 prerelease)** — composable plugins with perfect tree-shaking
|
|
15
15
|
- **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
|
|
16
16
|
- **Axis-neutral** — vertical and horizontal scrolling through a single code path, all plugins work in both orientations
|
|
17
17
|
|
|
@@ -55,7 +55,7 @@ const list = createVList({ container: '#app', items, item: { height: 200, templa
|
|
|
55
55
|
|
|
56
56
|
## Scroll input
|
|
57
57
|
|
|
58
|
-
Native scrolling is the default: import `createVList` and plugins from `vlist`. This preserves browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. Carousel
|
|
58
|
+
Native scrolling is the default: import `createVList` and plugins from `vlist`. This preserves browser scrollbars, native touch momentum, boundary handoff and native assistive-technology scrolling. Carousel and sortable work with this factory; horizontal RTL lists are rejected by both entries. Carousel supplies its private wrap runway only when the plugin is imported; bounded scrolling is no longer a public mode.
|
|
59
59
|
|
|
60
60
|
| Entry | Input model | Use |
|
|
61
61
|
|---|---|---|
|
|
@@ -81,9 +81,11 @@ const list = createVList({
|
|
|
81
81
|
|
|
82
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.
|
|
83
83
|
|
|
84
|
+
`carousel()` works with either entry. Synthetic carousel folds preserve touch motion and smooth snapping across whole laps. `page()` and carousel remain incompatible. The shared carousel plugin includes its native wrap handler even when used with synthetic input; the combined size is reported below.
|
|
85
|
+
|
|
84
86
|
Synthetic input limitations:
|
|
85
87
|
|
|
86
|
-
-
|
|
88
|
+
- Horizontal RTL lists throw at creation — in this entry and in `vlist`. Vertical lists and tables support `dir="rtl"` in both, including cross-axis wheel movement, aligned table headers and keyboard column navigation.
|
|
87
89
|
- Same-axis touch stops at either boundary with no parent handoff, including gestures starting inside an edge-pinned list. Use the native default when boundary gestures must scroll the parent page.
|
|
88
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.
|
|
89
91
|
- Inertia initializes its frame clock on the first frame after release, adding up to one frame of release latency.
|
|
@@ -91,6 +93,19 @@ Synthetic input limitations:
|
|
|
91
93
|
|
|
92
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).
|
|
93
95
|
|
|
96
|
+
### Sortable gestures
|
|
97
|
+
|
|
98
|
+
`sortable()` works with either input entry. Mouse and trackpad dragging still starts after `dragThreshold` (5 px by default). Touch and pen without a handle use `touchDelay`, a 350 ms long press; moving at least `dragThreshold` pixels in any direction before it completes yields to scrolling. This delay separates a deliberate hold from a quick flick. With `handle`, dragging starts at the threshold without waiting; touches elsewhere scroll.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
sortable({ touchDelay: 350, dragThreshold: 5 })
|
|
102
|
+
sortable({ handle: '.drag-handle' })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
A touch during scrolling catches the motion and does not arm a hold for that contact. Lift and press again once the list is at rest. A second finger or `pointercancel` cancels the pending press or active drag. Edge auto-scroll uses the list's logical position and stops on drop.
|
|
106
|
+
|
|
107
|
+
The touch-only `.vlist-item--touch-sort` class suppresses selection and callouts on the pressed item. A claimed touch/pen drag adds `.vlist-sort-ghost--touch` to the ghost as a visual cue; override that class in CSS to customize it. Keyboard reordering remains Space to grab/drop, arrows to move and Escape to cancel.
|
|
108
|
+
|
|
94
109
|
## Migrating to 3.0
|
|
95
110
|
|
|
96
111
|
`3.0.0-next.1` used synthetic input by default. `3.0.0-next.2` restores the 2.x default. Import `vlist/synthetic` explicitly to retain synthetic behavior across these prereleases. `vlist/native` remains a deprecated compatibility alias; its `NativeScrollConfig` and `NativeCreateVListConfig` types alias the standard `ScrollConfig` and `CreateVListConfig`.
|
|
@@ -103,33 +118,35 @@ Measurement corrections from autosize preserve ongoing synthetic motion. Existin
|
|
|
103
118
|
| `PluginContext.setScrollFns`, `disableDefaultScroll` | Use `setScrollSource` to supply an external position source and commit callback. |
|
|
104
119
|
| `scale()` and `ScalePluginConfig` | Remove them; use `vlist/synthetic` for the full logical range. Config no longer installs a scale stub. |
|
|
105
120
|
|
|
106
|
-
Removed mode/runway options throw a migration error before creating DOM. `vlist/config`
|
|
121
|
+
Removed mode/runway options throw a migration error before creating DOM. `vlist/config` wires only what the config asks for: `selection`, the custom scrollbar, `snapshots` and `a11y` are each opt-in, so an adapter list matches a core list built from the same options. It keeps the scrollbar options convenience and its top-level `scrollbar: "none"`; pass `scrollbar: true` for the overlay scrollbar earlier versions added to every list. `baseOffset` stays private engine state; plugins use `ctx.scroll.getRenderOrigin()`. Custom wrap providers now supply their handler factory as the second argument of `ctx.scroll.setBoundedWrap`.
|
|
107
122
|
|
|
108
123
|
## Plugins
|
|
109
124
|
|
|
110
125
|
| Entry / export | Minified | Gzipped |
|
|
111
126
|
|---|---:|---:|
|
|
112
|
-
| **Base (`vlist`)** |
|
|
113
|
-
| `vlist/synthetic` |
|
|
114
|
-
| `vlist/native` (alias) |
|
|
115
|
-
| `a11y()` |
|
|
116
|
-
| `selection()` |
|
|
117
|
-
| `data()` |
|
|
118
|
-
| `scrollbar()` |
|
|
119
|
-
| `sortable()` |
|
|
120
|
-
| `groups()` |
|
|
121
|
-
| `page()` |
|
|
122
|
-
| `snapshots()` |
|
|
123
|
-
| `transition()` |
|
|
124
|
-
| `autosize()` |
|
|
125
|
-
| `grid()` |
|
|
126
|
-
| `table()` |
|
|
127
|
-
| `masonry()` |
|
|
128
|
-
| `tree()` |
|
|
129
|
-
| `search()` |
|
|
130
|
-
| `carousel()` |
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
| **Base (`vlist`)** | 26.9 KB | 10.0 KB |
|
|
128
|
+
| `vlist/synthetic` | 33.8 KB | 12.6 KB |
|
|
129
|
+
| `vlist/native` (alias) | 26.9 KB | 10.0 KB |
|
|
130
|
+
| `a11y()` | 30.7 KB | 11.3 KB |
|
|
131
|
+
| `selection()` | 36.4 KB | 12.9 KB |
|
|
132
|
+
| `data()` | 40.6 KB | 14.8 KB |
|
|
133
|
+
| `scrollbar()` | 35.1 KB | 12.8 KB |
|
|
134
|
+
| `sortable()` | 38.7 KB | 13.4 KB |
|
|
135
|
+
| `groups()` | 43.0 KB | 15.4 KB |
|
|
136
|
+
| `page()` | 29.5 KB | 10.9 KB |
|
|
137
|
+
| `snapshots()` | 30.2 KB | 11.1 KB |
|
|
138
|
+
| `transition()` | 33.7 KB | 12.0 KB |
|
|
139
|
+
| `autosize()` | 30.1 KB | 11.1 KB |
|
|
140
|
+
| `grid()` | 34.4 KB | 12.5 KB |
|
|
141
|
+
| `table()` | 45.4 KB | 15.9 KB |
|
|
142
|
+
| `masonry()` | 39.0 KB | 14.4 KB |
|
|
143
|
+
| `tree()` | 43.1 KB | 15.3 KB |
|
|
144
|
+
| `search()` | 36.5 KB | 13.3 KB |
|
|
145
|
+
| `carousel()` | 41.7 KB | 15.1 KB |
|
|
146
|
+
| `vlist/synthetic` + `carousel()` | 47.8 KB | 17.4 KB |
|
|
147
|
+
| `vlist/synthetic` + `sortable()` | 45.7 KB | 16.1 KB |
|
|
148
|
+
|
|
149
|
+
Sizes are tree-shaken totals from `bun run size`, not additive plugin costs. Plugin rows include the native default factory plus that plugin. The base is **10,200 bytes gzipped**, within the 10.0 KB budget (10,240 bytes). `bun run size` fails if a scenario fails to build, an unused plugin leaks into a bundle, or any published size exceeds its gzip budget. Synthetic input is **12,889 bytes** before plugins. The alias row measures the same source factory; the distributed alias re-exports it without duplicating the implementation.
|
|
133
150
|
|
|
134
151
|
## Framework Adapters
|
|
135
152
|
|
package/dist/config.d.ts
CHANGED
|
@@ -17,11 +17,20 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { VListItem, GroupsConfig, VListAdapter, ScrollConfig } from "./types";
|
|
19
19
|
import { createVList } from "./core/create";
|
|
20
|
-
import type { CreateVListConfig, VList, VListPlugin } from "./core/types";
|
|
20
|
+
import type { CreateVListConfig, VList, VListPlugin, PluginMethods } from "./core/types";
|
|
21
|
+
import type { AutosizeMethods } from "./plugins/autosize/plugin";
|
|
22
|
+
import type { DataMethods } from "./plugins/data/plugin";
|
|
23
|
+
import type { GridMethods } from "./plugins/grid/plugin";
|
|
24
|
+
import type { MasonryMethods } from "./plugins/masonry/plugin";
|
|
25
|
+
import type { GroupsMethods } from "./plugins/groups/plugin";
|
|
26
|
+
import type { SelectionMethods } from "./plugins/selection/plugin";
|
|
27
|
+
import type { ScrollbarMethods } from "./plugins/scrollbar/plugin";
|
|
28
|
+
import type { SnapshotsMethods } from "./plugins/snapshots/plugin";
|
|
21
29
|
import type { DataPluginConfig } from "./plugins/data";
|
|
22
30
|
import type { GridPluginConfig } from "./plugins/grid";
|
|
23
31
|
import type { MasonryPluginConfig } from "./plugins/masonry";
|
|
24
32
|
import type { SelectionPluginConfig } from "./plugins/selection";
|
|
33
|
+
import type { A11yPluginConfig } from "./plugins/a11y";
|
|
25
34
|
import type { ScrollbarPluginConfig } from "./plugins/scrollbar";
|
|
26
35
|
/** List creation function injected by an adapter consumer. */
|
|
27
36
|
export type VListFactory<T extends VListItem = VListItem> = typeof createVList<T>;
|
|
@@ -43,11 +52,32 @@ export interface VListConfig<T extends VListItem = VListItem> extends Omit<Creat
|
|
|
43
52
|
/** Masonry layout options — applied when `layout: "masonry"`. */
|
|
44
53
|
masonry?: MasonryPluginConfig;
|
|
45
54
|
/** Sticky group headers. */
|
|
46
|
-
groups?: GroupsConfig
|
|
47
|
-
/**
|
|
55
|
+
groups?: GroupsConfig<T>;
|
|
56
|
+
/**
|
|
57
|
+
* Row selection (single / multi). Omit it for a display-only list: nothing
|
|
58
|
+
* is wired then, so the list keeps `role="list"` and stays out of the tab
|
|
59
|
+
* order, exactly like a core list created without the plugin.
|
|
60
|
+
*/
|
|
48
61
|
selection?: SelectionPluginConfig;
|
|
49
|
-
/**
|
|
50
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Keyboard navigation, focus management and the WAI-ARIA listbox roles.
|
|
64
|
+
* Off by default, as in core: pass `true` for the defaults or an object to
|
|
65
|
+
* tune it. A selection mode other than `"none"` already provides both, so a
|
|
66
|
+
* list with one does not need this as well.
|
|
67
|
+
*/
|
|
68
|
+
a11y?: A11yPluginConfig | boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Custom overlay scrollbar. Off by default, as in core, which leaves the
|
|
71
|
+
* browser's native scrollbar in place: pass `true` for the defaults or an
|
|
72
|
+
* object to tune them. `"none"` is accepted for symmetry with
|
|
73
|
+
* `scroll.scrollbar` and wires nothing.
|
|
74
|
+
*/
|
|
75
|
+
scrollbar?: ScrollbarPluginConfig | "none" | boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Scroll position save/restore (`saveScroll` / `restoreScroll`).
|
|
78
|
+
* Off by default, as in core.
|
|
79
|
+
*/
|
|
80
|
+
snapshots?: boolean;
|
|
51
81
|
/** Async data source — enables the data plugin. */
|
|
52
82
|
adapter?: VListAdapter<T>;
|
|
53
83
|
/** Async loading tuning (used together with `adapter`). */
|
|
@@ -57,23 +87,110 @@ export interface VListConfig<T extends VListItem = VListItem> extends Omit<Creat
|
|
|
57
87
|
* the convenience fields: a plugin whose `name` matches an auto-wired one
|
|
58
88
|
* (e.g. passing `grid()` while `layout: "grid"` is set) replaces it rather
|
|
59
89
|
* than duplicating; plugins with new names are appended.
|
|
90
|
+
*
|
|
91
|
+
* Looser than `createVList`'s plugin list: a plugin explicitly typed for a
|
|
92
|
+
* different item, such as `groups<Other>()`, is accepted with no check.
|
|
60
93
|
*/
|
|
61
|
-
plugins?: VListPlugin<
|
|
94
|
+
plugins?: VListPlugin<any, any>[];
|
|
62
95
|
}
|
|
63
96
|
/**
|
|
64
97
|
* Translate a {@link VListConfig} into the ordered plugin array that the core
|
|
65
|
-
* `createVList` expects.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
98
|
+
* `createVList` expects. Every plugin comes from a field the caller set, so a
|
|
99
|
+
* config with no feature fields resolves to no plugins and an adapter list
|
|
100
|
+
* behaves like a core list given the same options. Any user `plugins` are
|
|
101
|
+
* appended last as an escape hatch.
|
|
69
102
|
*/
|
|
70
103
|
export declare function resolvePlugins<T extends VListItem = VListItem>(config: VListConfig<T>): VListPlugin<T>[];
|
|
104
|
+
/** A field value that wires no plugin. `"none"` is the scrollbar's spelling of off. */
|
|
105
|
+
type Off = false | undefined | null | "none";
|
|
106
|
+
/**
|
|
107
|
+
* The methods a field adds when it is on. Distributive on purpose: a widened
|
|
108
|
+
* `boolean` is `true | false`, so the result is `M | {}` and a call on it is a
|
|
109
|
+
* type error — the honest answer when the compiler cannot know whether the
|
|
110
|
+
* plugin is seated. Narrow the config, or spell the field as a literal.
|
|
111
|
+
*/
|
|
112
|
+
type OnOff<V, M> = V extends Off ? {} : M;
|
|
113
|
+
/**
|
|
114
|
+
* The methods field `K` adds, or nothing when the config has no such field.
|
|
115
|
+
* Indexing `C[K]` directly would fall back to the constraint's type for an
|
|
116
|
+
* absent key and hand a plain config every plugin's methods.
|
|
117
|
+
*/
|
|
118
|
+
type Field<C, K extends PropertyKey, M> = C extends Record<K, infer V> ? OnOff<V, M> : {};
|
|
119
|
+
/** Whether the item spec carries an estimate, which wires autosize(). */
|
|
120
|
+
type Estimated<C> = C extends {
|
|
121
|
+
item: {
|
|
122
|
+
estimatedHeight: number;
|
|
123
|
+
} | {
|
|
124
|
+
estimatedWidth: number;
|
|
125
|
+
};
|
|
126
|
+
} ? C extends {
|
|
127
|
+
item: {
|
|
128
|
+
height: number;
|
|
129
|
+
} | {
|
|
130
|
+
width: number;
|
|
131
|
+
};
|
|
132
|
+
} ? {} : AutosizeMethods : {};
|
|
133
|
+
/**
|
|
134
|
+
* The plugin methods a {@link VListConfig} wires, derived from its fields the
|
|
135
|
+
* same way {@link resolvePlugins} derives the plugins — the two are the type
|
|
136
|
+
* and runtime halves of one mapping, and `test/types/config.ts` asserts they
|
|
137
|
+
* agree. This is what lets an adapter list carry `select()` or `reload()` the
|
|
138
|
+
* way a core list built with `createVList(config, [selection()])` does.
|
|
139
|
+
*/
|
|
140
|
+
export type ConfigMethods<T extends VListItem, C extends VListConfig<T>> = Field<C, "adapter", DataMethods> & (C extends {
|
|
141
|
+
layout: "grid";
|
|
142
|
+
} ? GridMethods : {}) & (C extends {
|
|
143
|
+
layout: "masonry";
|
|
144
|
+
} ? MasonryMethods : {}) & Field<C, "groups", GroupsMethods<T>> & Field<C, "selection", SelectionMethods<T>> & Field<C, "scrollbar", ScrollbarMethods> & Field<C, "snapshots", SnapshotsMethods> & Estimated<C> & (C extends {
|
|
145
|
+
plugins: infer P extends readonly unknown[];
|
|
146
|
+
} ? PluginMethods<P> : {});
|
|
147
|
+
/**
|
|
148
|
+
* The item type a config is written for: the element type of its `items`, else
|
|
149
|
+
* the parameter of its template. `VListConfig<infer T>` would do neither
|
|
150
|
+
* reliably — a config with no `items` inferred `any`.
|
|
151
|
+
*/
|
|
152
|
+
export type ConfigItem<C> = C extends {
|
|
153
|
+
items: readonly (infer T extends VListItem)[];
|
|
154
|
+
} ? T : C extends {
|
|
155
|
+
item: {
|
|
156
|
+
template: (item: infer T extends VListItem, ...rest: never[]) => unknown;
|
|
157
|
+
};
|
|
158
|
+
} ? T : VListItem;
|
|
159
|
+
/**
|
|
160
|
+
* {@link VListConfig} with everything but `items` blocked as an inference site,
|
|
161
|
+
* so `items` decides the item type and every callback is *checked* against it.
|
|
162
|
+
*
|
|
163
|
+
* Blocking the template alone was not enough: a grouping callback annotated for
|
|
164
|
+
* a supertype (`entity?: Entity` on a list of `Row`) is a second candidate, and
|
|
165
|
+
* it widened the whole list to `Entity` — `getItemAt()` lost `category`, and
|
|
166
|
+
* `setItems()` accepted an item without it. A callback for a supertype is still
|
|
167
|
+
* accepted, as it should be: it safely takes every `Row`.
|
|
168
|
+
*/
|
|
169
|
+
type CheckedConfig<T extends VListItem> = VListConfig<NoInfer<T>>;
|
|
71
170
|
/**
|
|
72
171
|
* Create a vlist instance from a high-level {@link VListConfig}, resolving its
|
|
73
172
|
* feature fields into plugins via {@link resolvePlugins}. This is the single
|
|
74
173
|
* entry point every framework adapter delegates to.
|
|
174
|
+
*
|
|
175
|
+
* The item type comes from `items` when present, otherwise the template, and
|
|
176
|
+
* the list's methods from the config's feature fields ({@link ConfigMethods}).
|
|
177
|
+
* Do not pass a type argument — `createVListFromConfig<Row>` does not compile
|
|
178
|
+
* (TypeScript requires both parameters), so the partial-argument trap
|
|
179
|
+
* `createVList<Row>` falls into stays closed.
|
|
180
|
+
*
|
|
181
|
+
* `groups.getGroupForIndex` is typed with that item: an inline callback
|
|
182
|
+
* receives `item?: T`, not `any`, and a callback written for a different
|
|
183
|
+
* item type is a type error, including when `groups` is optional on the
|
|
184
|
+
* input type.
|
|
75
185
|
*/
|
|
76
|
-
export declare function createVListFromConfig<T extends VListItem
|
|
186
|
+
export declare function createVListFromConfig<T extends VListItem, const C extends CheckedConfig<T> & {
|
|
187
|
+
container: HTMLElement | string;
|
|
188
|
+
items: readonly T[];
|
|
189
|
+
}>(config: {
|
|
190
|
+
items: readonly T[];
|
|
191
|
+
} & CheckedConfig<T> & C): VList<T> & ConfigMethods<T, C>;
|
|
192
|
+
export declare function createVListFromConfig<T extends VListItem, const C extends VListConfig<T> & {
|
|
77
193
|
container: HTMLElement | string;
|
|
78
|
-
}): VList<T>;
|
|
194
|
+
}>(config: VListConfig<T> & C): VList<T> & ConfigMethods<T, C>;
|
|
195
|
+
export {};
|
|
79
196
|
//# sourceMappingURL=config.d.ts.map
|