vlist 1.9.1 → 2.0.0
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 +104 -97
- package/README.md +46 -33
- package/dist/constants.d.ts +11 -6
- package/dist/constants.js +83 -0
- package/dist/core/create.d.ts +10 -0
- package/dist/core/create.js +740 -0
- package/dist/core/dom.d.ts +8 -0
- package/dist/core/dom.js +47 -0
- package/dist/core/hooks.d.ts +16 -0
- package/dist/core/hooks.js +67 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +13 -0
- package/dist/core/pipeline.d.ts +51 -0
- package/dist/core/pipeline.js +307 -0
- package/dist/core/pool.d.ts +9 -0
- package/dist/core/pool.js +42 -0
- package/dist/core/scroll.d.ts +32 -0
- package/dist/core/scroll.js +137 -0
- package/dist/core/sizes.d.ts +8 -0
- package/dist/core/sizes.js +6 -0
- package/dist/core/state.d.ts +47 -0
- package/dist/core/state.js +56 -0
- package/dist/core/types.d.ts +187 -0
- package/dist/core/types.js +7 -0
- package/dist/{builder → core}/velocity.d.ts +1 -1
- package/dist/core/velocity.js +33 -0
- package/dist/events/emitter.js +60 -0
- package/dist/events/index.js +6 -0
- package/dist/index.d.ts +28 -19
- package/dist/index.js +28 -1
- package/dist/internals.d.ts +11 -7
- package/dist/internals.js +60 -1
- package/dist/plugins/a11y/index.d.ts +2 -0
- package/dist/plugins/a11y/index.js +1 -0
- package/dist/plugins/a11y/plugin.d.ts +13 -0
- package/dist/plugins/a11y/plugin.js +259 -0
- package/dist/{features → plugins}/async/index.d.ts +1 -1
- package/dist/plugins/async/index.js +12 -0
- package/dist/{features → plugins}/async/manager.d.ts +5 -1
- package/dist/plugins/async/manager.js +568 -0
- package/dist/plugins/async/placeholder.js +154 -0
- package/dist/plugins/async/plugin.d.ts +48 -0
- package/dist/plugins/async/plugin.js +311 -0
- package/dist/plugins/async/sparse.js +540 -0
- package/dist/plugins/autosize/index.d.ts +5 -0
- package/dist/plugins/autosize/index.js +4 -0
- package/dist/plugins/autosize/plugin.d.ts +19 -0
- package/dist/plugins/autosize/plugin.js +185 -0
- package/dist/plugins/grid/index.d.ts +7 -0
- package/dist/plugins/grid/index.js +5 -0
- package/dist/plugins/grid/layout.js +275 -0
- package/dist/plugins/grid/plugin.d.ts +23 -0
- package/dist/plugins/grid/plugin.js +347 -0
- package/dist/plugins/grid/renderer.js +525 -0
- package/dist/plugins/grid/types.js +11 -0
- package/dist/plugins/groups/async-bridge.js +246 -0
- package/dist/{features → plugins}/groups/index.d.ts +1 -1
- package/dist/plugins/groups/index.js +13 -0
- package/dist/plugins/groups/layout.js +294 -0
- package/dist/plugins/groups/plugin.d.ts +22 -0
- package/dist/plugins/groups/plugin.js +571 -0
- package/dist/plugins/groups/sticky.js +255 -0
- package/dist/plugins/groups/types.js +12 -0
- package/dist/plugins/masonry/index.d.ts +8 -0
- package/dist/plugins/masonry/index.js +6 -0
- package/dist/plugins/masonry/layout.js +261 -0
- package/dist/plugins/masonry/plugin.d.ts +32 -0
- package/dist/plugins/masonry/plugin.js +381 -0
- package/dist/plugins/masonry/renderer.js +354 -0
- package/dist/plugins/masonry/types.js +9 -0
- package/dist/plugins/page/index.d.ts +5 -0
- package/dist/plugins/page/index.js +5 -0
- package/dist/plugins/page/plugin.d.ts +21 -0
- package/dist/plugins/page/plugin.js +166 -0
- package/dist/plugins/scale/index.d.ts +5 -0
- package/dist/plugins/scale/index.js +4 -0
- package/dist/plugins/scale/plugin.d.ts +24 -0
- package/dist/plugins/scale/plugin.js +507 -0
- package/dist/plugins/scrollbar/controller.js +574 -0
- package/dist/plugins/scrollbar/index.d.ts +7 -0
- package/dist/plugins/scrollbar/index.js +6 -0
- package/dist/plugins/scrollbar/plugin.d.ts +20 -0
- package/dist/plugins/scrollbar/plugin.js +93 -0
- package/dist/plugins/scrollbar/scrollbar.js +556 -0
- package/dist/plugins/selection/index.d.ts +6 -0
- package/dist/plugins/selection/index.js +7 -0
- package/dist/plugins/selection/plugin.d.ts +16 -0
- package/dist/plugins/selection/plugin.js +601 -0
- package/dist/{features → plugins}/selection/state.d.ts +8 -0
- package/dist/plugins/selection/state.js +332 -0
- package/dist/plugins/snapshots/index.d.ts +5 -0
- package/dist/plugins/snapshots/index.js +5 -0
- package/dist/plugins/snapshots/plugin.d.ts +17 -0
- package/dist/plugins/snapshots/plugin.js +301 -0
- package/dist/plugins/sortable/index.d.ts +6 -0
- package/dist/plugins/sortable/index.js +6 -0
- package/dist/plugins/sortable/plugin.d.ts +34 -0
- package/dist/plugins/sortable/plugin.js +753 -0
- package/dist/plugins/table/header.js +501 -0
- package/dist/{features → plugins}/table/index.d.ts +1 -1
- package/dist/plugins/table/index.js +12 -0
- package/dist/plugins/table/layout.js +211 -0
- package/dist/plugins/table/plugin.d.ts +20 -0
- package/dist/plugins/table/plugin.js +391 -0
- package/dist/plugins/table/renderer.js +625 -0
- package/dist/plugins/table/types.js +12 -0
- package/dist/plugins/transition/index.d.ts +5 -0
- package/dist/plugins/transition/index.js +5 -0
- package/dist/plugins/transition/plugin.d.ts +22 -0
- package/dist/plugins/transition/plugin.js +405 -0
- package/dist/rendering/aria.js +23 -0
- package/dist/rendering/index.js +18 -0
- package/dist/rendering/measured.js +98 -0
- package/dist/rendering/renderer.js +586 -0
- package/dist/rendering/scale.js +267 -0
- package/dist/rendering/scroll.js +71 -0
- package/dist/rendering/sizes.js +193 -0
- package/dist/rendering/sort.js +65 -0
- package/dist/rendering/viewport.js +268 -0
- package/dist/size.json +1 -1
- package/dist/types.js +5 -0
- package/dist/utils/padding.d.ts +2 -4
- package/dist/utils/padding.js +49 -0
- package/dist/utils/stats.js +124 -0
- package/dist/vlist-grid.css +1 -1
- package/dist/vlist-masonry.css +1 -1
- package/dist/vlist-table.css +1 -1
- package/dist/vlist.css +1 -1
- package/package.json +9 -4
- package/dist/builder/a11y.d.ts +0 -16
- package/dist/builder/api.d.ts +0 -21
- package/dist/builder/context.d.ts +0 -36
- package/dist/builder/core.d.ts +0 -16
- package/dist/builder/data.d.ts +0 -71
- package/dist/builder/dom.d.ts +0 -15
- package/dist/builder/index.d.ts +0 -25
- package/dist/builder/materialize.d.ts +0 -166
- package/dist/builder/pool.d.ts +0 -10
- package/dist/builder/range.d.ts +0 -10
- package/dist/builder/scroll.d.ts +0 -24
- package/dist/builder/types.d.ts +0 -512
- package/dist/features/async/feature.d.ts +0 -72
- package/dist/features/autosize/feature.d.ts +0 -34
- package/dist/features/autosize/index.d.ts +0 -2
- package/dist/features/grid/feature.d.ts +0 -48
- package/dist/features/grid/index.d.ts +0 -9
- package/dist/features/groups/feature.d.ts +0 -75
- package/dist/features/masonry/feature.d.ts +0 -45
- package/dist/features/masonry/index.d.ts +0 -9
- package/dist/features/page/feature.d.ts +0 -109
- package/dist/features/page/index.d.ts +0 -9
- package/dist/features/scale/feature.d.ts +0 -42
- package/dist/features/scale/index.d.ts +0 -10
- package/dist/features/scrollbar/feature.d.ts +0 -81
- package/dist/features/scrollbar/index.d.ts +0 -8
- package/dist/features/selection/feature.d.ts +0 -91
- package/dist/features/selection/index.d.ts +0 -7
- package/dist/features/snapshots/feature.d.ts +0 -79
- package/dist/features/snapshots/index.d.ts +0 -9
- package/dist/features/sortable/feature.d.ts +0 -101
- package/dist/features/sortable/index.d.ts +0 -6
- package/dist/features/table/feature.d.ts +0 -67
- package/dist/features/transition/feature.d.ts +0 -30
- package/dist/features/transition/index.d.ts +0 -9
- /package/dist/{features → plugins}/async/placeholder.d.ts +0 -0
- /package/dist/{features → plugins}/async/sparse.d.ts +0 -0
- /package/dist/{features → plugins}/grid/layout.d.ts +0 -0
- /package/dist/{features → plugins}/grid/renderer.d.ts +0 -0
- /package/dist/{features → plugins}/grid/types.d.ts +0 -0
- /package/dist/{features → plugins}/groups/async-bridge.d.ts +0 -0
- /package/dist/{features → plugins}/groups/layout.d.ts +0 -0
- /package/dist/{features → plugins}/groups/sticky.d.ts +0 -0
- /package/dist/{features → plugins}/groups/types.d.ts +0 -0
- /package/dist/{features → plugins}/masonry/layout.d.ts +0 -0
- /package/dist/{features → plugins}/masonry/renderer.d.ts +0 -0
- /package/dist/{features → plugins}/masonry/types.d.ts +0 -0
- /package/dist/{features → plugins}/scrollbar/controller.d.ts +0 -0
- /package/dist/{features → plugins}/scrollbar/scrollbar.d.ts +0 -0
- /package/dist/{features → plugins}/table/header.d.ts +0 -0
- /package/dist/{features → plugins}/table/layout.d.ts +0 -0
- /package/dist/{features → plugins}/table/renderer.d.ts +0 -0
- /package/dist/{features → plugins}/table/types.d.ts +0 -0
package/README.github.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
# vlist
|
|
2
2
|
|
|
3
|
-
The virtual list library for every framework.
|
|
3
|
+
The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 7.7 KB.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**v2.0.0** — [Changelog](./CHANGELOG.md) · **v2 is a ground-up rewrite** with a new plugin API. Coming from v1? See [Migration Guide](https://vlist.io/docs/migration).
|
|
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
|
-
- **
|
|
13
|
-
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering, ARIA live region
|
|
12
|
+
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
14
13
|
- **Zero dependencies** — framework-agnostic core with tiny adapters for Vue, Svelte, Solid, React
|
|
15
|
-
- **
|
|
14
|
+
- **7.7 KB gzipped** — composable plugins with perfect tree-shaking
|
|
16
15
|
- **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
|
|
17
|
-
- **Grid, masonry, table, groups,
|
|
18
|
-
- **Vertical & horizontal** — single axis-neutral code path, every
|
|
16
|
+
- **Grid, masonry, table, groups, data, selection, sortable, transition, scale** — all opt-in
|
|
17
|
+
- **Vertical & horizontal** — single axis-neutral code path, every plugin works in both orientations
|
|
19
18
|
|
|
20
19
|
**18 interactive examples, docs & benchmarks → [vlist.io](https://vlist.io)**
|
|
21
20
|
|
|
@@ -49,10 +48,10 @@ npm install vlist vlist-vue # or vlist-svelte / vlist-solidjs / vlist-react
|
|
|
49
48
|
## Quick Start
|
|
50
49
|
|
|
51
50
|
```typescript
|
|
52
|
-
import {
|
|
51
|
+
import { createVList } from 'vlist'
|
|
53
52
|
import 'vlist/styles'
|
|
54
53
|
|
|
55
|
-
const list =
|
|
54
|
+
const list = createVList({
|
|
56
55
|
container: '#my-list',
|
|
57
56
|
items: [
|
|
58
57
|
{ id: 1, name: 'Alice' },
|
|
@@ -63,52 +62,52 @@ const list = vlist({
|
|
|
63
62
|
height: 48,
|
|
64
63
|
template: (item) => `<div>${item.name}</div>`,
|
|
65
64
|
},
|
|
66
|
-
})
|
|
65
|
+
})
|
|
67
66
|
|
|
68
67
|
list.scrollToIndex(10)
|
|
69
68
|
list.setItems(newItems)
|
|
70
69
|
list.on('item:click', ({ item }) => console.log(item))
|
|
71
70
|
```
|
|
72
71
|
|
|
73
|
-
##
|
|
72
|
+
## Plugin System
|
|
74
73
|
|
|
75
74
|
Start with the base, add only what you need:
|
|
76
75
|
|
|
77
76
|
```typescript
|
|
78
|
-
import {
|
|
77
|
+
import { createVList, grid, groups, selection } from 'vlist'
|
|
79
78
|
|
|
80
|
-
const list =
|
|
79
|
+
const list = createVList({
|
|
81
80
|
container: '#app',
|
|
82
81
|
items: photos,
|
|
83
82
|
item: { height: 200, template: renderPhoto },
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
}, [
|
|
84
|
+
grid({ columns: 4, gap: 16 }),
|
|
85
|
+
groups({
|
|
87
86
|
getGroupForIndex: (i) => photos[i].category,
|
|
88
87
|
header: { height: 40, template: (cat) => `<h2>${cat}</h2>` },
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
}),
|
|
89
|
+
selection({ mode: 'multiple' }),
|
|
90
|
+
])
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
###
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
| **Base** |
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
93
|
+
### Plugins
|
|
94
|
+
|
|
95
|
+
| Plugin | Size | Description |
|
|
96
|
+
|--------|------|-------------|
|
|
97
|
+
| **Base** | 7.7 KB | Virtualization, ARIA, keyboard nav, gap, padding |
|
|
98
|
+
| `data()` | +4.7 KB | Lazy loading with velocity-aware fetching |
|
|
99
|
+
| `selection()` | +2.4 KB | Single/multiple selection with 2D keyboard nav |
|
|
100
|
+
| `scale()` | +3.9 KB | 1M+ items via scroll compression |
|
|
101
|
+
| `groups()` | +3.7 KB | Sticky/inline headers with async group discovery |
|
|
102
|
+
| `autosize()` | +0.7 KB | Auto-measure items via ResizeObserver |
|
|
103
|
+
| `scrollbar()` | +2.0 KB | Custom scrollbar UI |
|
|
104
|
+
| `grid()` | +2.1 KB | 2D grid layout |
|
|
105
|
+
| `masonry()` | +3.5 KB | Pinterest-style masonry with lane-aware keyboard nav |
|
|
106
|
+
| `table()` | +5.8 KB | Data table with columns, resize, sort |
|
|
107
|
+
| `page()` | +0.7 KB | Window-level scrolling |
|
|
108
|
+
| `sortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
|
|
109
|
+
| `snapshots()` | +1.3 KB | Scroll position save/restore |
|
|
110
|
+
| `transition()` | +1.8 KB | FLIP-based enter/exit animations for insert & remove |
|
|
112
111
|
|
|
113
112
|
## Examples
|
|
114
113
|
|
|
@@ -117,14 +116,14 @@ More examples at **[vlist.io](https://vlist.io)**.
|
|
|
117
116
|
### Data Table
|
|
118
117
|
|
|
119
118
|
```typescript
|
|
120
|
-
import {
|
|
119
|
+
import { createVList, table, selection } from 'vlist'
|
|
121
120
|
|
|
122
|
-
const
|
|
121
|
+
const myTable = createVList({
|
|
123
122
|
container: '#my-table',
|
|
124
123
|
items: contacts,
|
|
125
124
|
item: { height: 36, template: () => '' },
|
|
126
|
-
}
|
|
127
|
-
|
|
125
|
+
}, [
|
|
126
|
+
table({
|
|
128
127
|
columns: [
|
|
129
128
|
{ key: 'name', label: 'Name', width: 200, sortable: true },
|
|
130
129
|
{ key: 'email', label: 'Email', width: 260, sortable: true },
|
|
@@ -134,20 +133,20 @@ const table = vlist({
|
|
|
134
133
|
rowHeight: 36,
|
|
135
134
|
headerHeight: 36,
|
|
136
135
|
resizable: true,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
}),
|
|
137
|
+
selection({ mode: 'single' }),
|
|
138
|
+
])
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
myTable.on('column:sort', ({ key, direction }) => { /* re-sort data */ })
|
|
141
|
+
myTable.on('column:resize', ({ key, width }) => { /* persist widths */ })
|
|
143
142
|
```
|
|
144
143
|
|
|
145
144
|
### Grid Layout
|
|
146
145
|
|
|
147
146
|
```typescript
|
|
148
|
-
import {
|
|
147
|
+
import { createVList, grid, scrollbar } from 'vlist'
|
|
149
148
|
|
|
150
|
-
const gallery =
|
|
149
|
+
const gallery = createVList({
|
|
151
150
|
container: '#gallery',
|
|
152
151
|
items: photos,
|
|
153
152
|
item: {
|
|
@@ -159,25 +158,25 @@ const gallery = vlist({
|
|
|
159
158
|
</div>
|
|
160
159
|
`,
|
|
161
160
|
},
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
}, [
|
|
162
|
+
grid({ columns: 4, gap: 16 }),
|
|
163
|
+
scrollbar({ autoHide: true }),
|
|
164
|
+
])
|
|
166
165
|
```
|
|
167
166
|
|
|
168
167
|
### Animated Insert & Remove
|
|
169
168
|
|
|
170
169
|
```typescript
|
|
171
|
-
import {
|
|
170
|
+
import { createVList, transition, selection } from 'vlist'
|
|
172
171
|
|
|
173
|
-
const list =
|
|
172
|
+
const list = createVList({
|
|
174
173
|
container: '#playlist',
|
|
175
174
|
items: tracks,
|
|
176
175
|
item: { height: 64, template: renderTrack },
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
}, [
|
|
177
|
+
transition({ duration: 200 }),
|
|
178
|
+
selection({ mode: 'multiple' }),
|
|
179
|
+
])
|
|
181
180
|
|
|
182
181
|
// Single item — collapses with fade-out, siblings slide up
|
|
183
182
|
list.removeItem(trackId)
|
|
@@ -192,9 +191,9 @@ list.insertItem({ id: 42, title: 'New Track' }, 0)
|
|
|
192
191
|
### Async Loading
|
|
193
192
|
|
|
194
193
|
```typescript
|
|
195
|
-
import {
|
|
194
|
+
import { createVList, data } from 'vlist'
|
|
196
195
|
|
|
197
|
-
const list =
|
|
196
|
+
const list = createVList({
|
|
198
197
|
container: '#list',
|
|
199
198
|
item: {
|
|
200
199
|
height: 64,
|
|
@@ -202,8 +201,8 @@ const list = vlist({
|
|
|
202
201
|
? `<div>${item.name}</div>`
|
|
203
202
|
: `<div class="placeholder">Loading…</div>`,
|
|
204
203
|
},
|
|
205
|
-
}
|
|
206
|
-
|
|
204
|
+
}, [
|
|
205
|
+
data({
|
|
207
206
|
adapter: {
|
|
208
207
|
read: async ({ offset, limit }) => {
|
|
209
208
|
const res = await fetch(`/api/users?offset=${offset}&limit=${limit}`)
|
|
@@ -211,8 +210,8 @@ const list = vlist({
|
|
|
211
210
|
return { items: data.items, total: data.total, hasMore: data.hasMore }
|
|
212
211
|
},
|
|
213
212
|
},
|
|
214
|
-
})
|
|
215
|
-
|
|
213
|
+
}),
|
|
214
|
+
])
|
|
216
215
|
```
|
|
217
216
|
|
|
218
217
|
## Accessibility
|
|
@@ -224,7 +223,6 @@ Every vlist is accessible by default following the [WAI-ARIA listbox pattern](ht
|
|
|
224
223
|
- **Masonry lane-aware nav** — arrows stay in the same visual column
|
|
225
224
|
- **Home/End, PageUp/PageDown, Ctrl+Home/End** — full keyboard coverage
|
|
226
225
|
- **Screen-reader DOM ordering** — items reordered on scroll idle for correct reading order
|
|
227
|
-
- **ARIA live region** — announces loading state changes
|
|
228
226
|
- **Focus recovery** — maintains focus when items are removed
|
|
229
227
|
|
|
230
228
|
Set `interactive: false` for display-only lists (log viewers, activity feeds) where items contain their own interactive elements.
|
|
@@ -232,7 +230,7 @@ Set `interactive: false` for display-only lists (log viewers, activity feeds) wh
|
|
|
232
230
|
## API
|
|
233
231
|
|
|
234
232
|
```typescript
|
|
235
|
-
const list =
|
|
233
|
+
const list = createVList(config, [plugin1(), plugin2()])
|
|
236
234
|
```
|
|
237
235
|
|
|
238
236
|
### Data
|
|
@@ -242,13 +240,12 @@ const list = vlist(config).use(...features).build()
|
|
|
242
240
|
| `list.setItems(items)` | Replace all items |
|
|
243
241
|
| `list.appendItems(items)` | Add to end (auto-scrolls in reverse mode) |
|
|
244
242
|
| `list.prependItems(items)` | Add to start (preserves scroll position) |
|
|
245
|
-
| `list.updateItem(
|
|
246
|
-
| `list.insertItem(item, index?)` | Insert at index (animated with `
|
|
247
|
-
| `list.removeItem(id)` | Remove by ID (animated with `
|
|
243
|
+
| `list.updateItem(id, partial)` | Update a single item by ID |
|
|
244
|
+
| `list.insertItem(item, index?)` | Insert at index (animated with `transition`) |
|
|
245
|
+
| `list.removeItem(id)` | Remove by ID (animated with `transition`) |
|
|
248
246
|
| `list.removeItems(ids)` | Batch remove (simultaneous animations) |
|
|
249
247
|
| `list.getItemAt(index)` | Get item at index |
|
|
250
248
|
| `list.getIndexById(id)` | Get index by item ID |
|
|
251
|
-
| `list.reload()` | Re-fetch from adapter (async) |
|
|
252
249
|
|
|
253
250
|
### Navigation
|
|
254
251
|
|
|
@@ -256,10 +253,9 @@ const list = vlist(config).use(...features).build()
|
|
|
256
253
|
|--------|-------------|
|
|
257
254
|
| `list.scrollToIndex(i, align?)` | Scroll to index (`'start'` \| `'center'` \| `'end'`) |
|
|
258
255
|
| `list.scrollToIndex(i, opts?)` | With `{ align, behavior: 'smooth', duration }` |
|
|
259
|
-
| `list.cancelScroll()` | Cancel smooth scroll animation |
|
|
260
256
|
| `list.getScrollPosition()` | Current scroll offset |
|
|
261
257
|
|
|
262
|
-
### Selection (with `
|
|
258
|
+
### Selection (with `selection()`)
|
|
263
259
|
|
|
264
260
|
| Method | Description |
|
|
265
261
|
|--------|-------------|
|
|
@@ -296,24 +292,24 @@ list.on('sort:cancel', ({ originalItems }) => {})
|
|
|
296
292
|
| `list.total` | Total item count |
|
|
297
293
|
| `list.destroy()` | Cleanup and remove from DOM |
|
|
298
294
|
|
|
299
|
-
##
|
|
295
|
+
## Plugin Configuration
|
|
300
296
|
|
|
301
|
-
Each
|
|
297
|
+
Each plugin's config is fully typed — hover in your IDE for details.
|
|
302
298
|
|
|
303
299
|
```typescript
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
grid({ columns: 4, gap: 16 })
|
|
301
|
+
masonry({ columns: 4, gap: 16 })
|
|
302
|
+
groups({ getGroupForIndex, header: { height, template }, sticky?: true })
|
|
303
|
+
selection({ mode: 'single' | 'multiple', initial?: [...ids] })
|
|
304
|
+
data({ adapter: { read }, loading?: { cancelThreshold? } })
|
|
305
|
+
table({ columns, rowHeight, headerHeight?, resizable? })
|
|
306
|
+
autosize() // auto-measure items (requires estimatedHeight)
|
|
307
|
+
scale() // auto-activates at 16.7M px
|
|
308
|
+
scrollbar({ autoHide?, autoHideDelay?, minThumbSize? })
|
|
309
|
+
transition({ duration?: 200, insert?: timing, remove?: timing })
|
|
310
|
+
sortable({ handle?: '.drag-handle' }) // drag-and-drop reordering
|
|
311
|
+
page() // no config — uses document scroll
|
|
312
|
+
snapshots({ autoSave: 'key' }) // automatic sessionStorage save/restore
|
|
317
313
|
```
|
|
318
314
|
|
|
319
315
|
Full configuration reference → **[vlist.io](https://vlist.io)**
|
|
@@ -328,15 +324,14 @@ Full configuration reference → **[vlist.io](https://vlist.io)**
|
|
|
328
324
|
| `padding` | `0` | Content inset — number, `[v, h]`, or `[top, right, bottom, left]` |
|
|
329
325
|
| `interactive` | `true` | Enable built-in keyboard navigation |
|
|
330
326
|
| `reverse` | `false` | Reverse mode for chat UIs |
|
|
331
|
-
| `scroll.wrap` | `false` | Wrap focus around at boundaries |
|
|
332
327
|
|
|
333
328
|
## Styling
|
|
334
329
|
|
|
335
330
|
```typescript
|
|
336
331
|
import 'vlist/styles' // core (always required)
|
|
337
|
-
import 'vlist/styles/grid' // when using
|
|
338
|
-
import 'vlist/styles/masonry' // when using
|
|
339
|
-
import 'vlist/styles/table' // when using
|
|
332
|
+
import 'vlist/styles/grid' // when using grid()
|
|
333
|
+
import 'vlist/styles/masonry' // when using masonry()
|
|
334
|
+
import 'vlist/styles/table' // when using table()
|
|
340
335
|
import 'vlist/styles/extras' // optional (variants, loading states, animations)
|
|
341
336
|
```
|
|
342
337
|
|
|
@@ -350,7 +345,7 @@ Dark mode works out of the box via `prefers-color-scheme`, Tailwind's `.dark` cl
|
|
|
350
345
|
| 100K items | 0.08 MB | ~0 MB |
|
|
351
346
|
| 1M items | 0.09 MB | 0.19 MB |
|
|
352
347
|
|
|
353
|
-
- **Initial render:** ~
|
|
348
|
+
- **Initial render:** ~2ms (constant, regardless of item count)
|
|
354
349
|
- **Scroll:** 120 FPS at any scale
|
|
355
350
|
- **DOM nodes:** ~26 in document with 100K items (visible + overscan only)
|
|
356
351
|
|
|
@@ -361,22 +356,34 @@ Live benchmarks against 9 competitors → **[vlist.io/benchmarks](https://vlist.
|
|
|
361
356
|
Fully typed. Generic over your item type:
|
|
362
357
|
|
|
363
358
|
```typescript
|
|
364
|
-
import {
|
|
359
|
+
import { createVList, grid, type VList } from 'vlist'
|
|
365
360
|
|
|
366
361
|
interface Photo { id: number; url: string; title: string }
|
|
367
362
|
|
|
368
|
-
const list: VList<Photo> =
|
|
363
|
+
const list: VList<Photo> = createVList<Photo>({
|
|
369
364
|
container: '#gallery',
|
|
370
365
|
items: photos,
|
|
371
366
|
item: {
|
|
372
367
|
height: 200,
|
|
373
368
|
template: (photo) => `<img src="${photo.url}" />`,
|
|
374
369
|
},
|
|
375
|
-
})
|
|
376
|
-
.use(withGrid({ columns: 4 }))
|
|
377
|
-
.build()
|
|
370
|
+
}, [grid({ columns: 4 })])
|
|
378
371
|
```
|
|
379
372
|
|
|
373
|
+
## Migrating from v1
|
|
374
|
+
|
|
375
|
+
v2 is a ground-up rewrite — simpler API, 55% smaller base bundle, zero-allocation scroll path. [Full announcement →](https://vlist.io/blog/v2)
|
|
376
|
+
|
|
377
|
+
| v1 | v2 |
|
|
378
|
+
|----|-----|
|
|
379
|
+
| `vlist(config).use(withGrid()).build()` | `createVList(config, [grid()])` |
|
|
380
|
+
| `withGrid`, `withSelection`, … | `grid`, `selection`, … |
|
|
381
|
+
| `VListFeature` | `VListPlugin` |
|
|
382
|
+
| `BuilderContext` | `PluginContext` |
|
|
383
|
+
| `.vlist-items` | `.vlist-content` |
|
|
384
|
+
|
|
385
|
+
The instance API (`setItems`, `scrollToIndex`, `on`, `destroy`) is unchanged.
|
|
386
|
+
|
|
380
387
|
## Contributing
|
|
381
388
|
|
|
382
389
|
1. Fork → branch → make changes → add tests → pull request
|
package/README.md
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
# vlist
|
|
2
2
|
|
|
3
|
-
The virtual list library for every framework.
|
|
3
|
+
The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 7.7 KB.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**v2.0.0** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md) · **v2 is a ground-up rewrite** with a new plugin API. Coming from v1? See [Migration Guide](https://vlist.io/docs/migration).
|
|
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
|
-
- **New: `withTransition()`** — FLIP-based enter/exit animations for insert and remove
|
|
13
|
-
- **Zero dependencies** — framework-agnostic core, tiny adapters for Vue, Svelte, Solid, React
|
|
14
12
|
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
|
|
15
|
-
- **
|
|
13
|
+
- **Zero dependencies** — framework-agnostic core, tiny adapters for Vue, Svelte, Solid, React
|
|
14
|
+
- **7.7 KB gzipped** — composable plugins with perfect tree-shaking
|
|
16
15
|
- **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
|
|
17
|
-
- **Vertical & horizontal** — single axis-neutral code path, every
|
|
16
|
+
- **Vertical & horizontal** — single axis-neutral code path, every plugin works in both orientations
|
|
18
17
|
|
|
19
18
|
## Install
|
|
20
19
|
|
|
@@ -25,10 +24,10 @@ npm install vlist
|
|
|
25
24
|
## Quick Start
|
|
26
25
|
|
|
27
26
|
```typescript
|
|
28
|
-
import {
|
|
27
|
+
import { createVList } from 'vlist'
|
|
29
28
|
import 'vlist/styles'
|
|
30
29
|
|
|
31
|
-
const list =
|
|
30
|
+
const list = createVList({
|
|
32
31
|
container: '#my-list',
|
|
33
32
|
items: [
|
|
34
33
|
{ id: 1, name: 'Alice' },
|
|
@@ -39,38 +38,38 @@ const list = vlist({
|
|
|
39
38
|
height: 48,
|
|
40
39
|
template: (item) => `<div>${item.name}</div>`,
|
|
41
40
|
},
|
|
42
|
-
})
|
|
41
|
+
})
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
Add
|
|
44
|
+
Add plugins as the second argument:
|
|
46
45
|
|
|
47
46
|
```typescript
|
|
48
|
-
import {
|
|
47
|
+
import { createVList, grid, selection } from 'vlist'
|
|
49
48
|
|
|
50
|
-
const list =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const list = createVList({ container: '#app', items, item: { height: 200, template: render } }, [
|
|
50
|
+
grid({ columns: 4, gap: 16 }),
|
|
51
|
+
selection({ mode: 'multiple' }),
|
|
52
|
+
])
|
|
54
53
|
```
|
|
55
54
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
60
|
-
| **Base** |
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
55
|
+
## Plugins
|
|
56
|
+
|
|
57
|
+
| Plugin | Size | Description |
|
|
58
|
+
|--------|------|-------------|
|
|
59
|
+
| **Base** | 7.7 KB | Virtualization, ARIA, keyboard nav, gap, padding |
|
|
60
|
+
| `data()` | +4.7 KB | Lazy loading with velocity-aware fetching |
|
|
61
|
+
| `selection()` | +2.4 KB | Single/multiple selection with 2D keyboard nav |
|
|
62
|
+
| `scale()` | +3.9 KB | 1M+ items via scroll compression |
|
|
63
|
+
| `groups()` | +3.7 KB | Sticky/inline headers with async group discovery |
|
|
64
|
+
| `autosize()` | +0.7 KB | Auto-measure items via ResizeObserver |
|
|
65
|
+
| `scrollbar()` | +2.0 KB | Custom scrollbar UI |
|
|
66
|
+
| `grid()` | +2.1 KB | 2D grid layout |
|
|
67
|
+
| `masonry()` | +3.5 KB | Pinterest-style masonry with lane-aware keyboard nav |
|
|
68
|
+
| `table()` | +5.8 KB | Data table with columns, resize, sort |
|
|
69
|
+
| `page()` | +0.7 KB | Window-level scrolling |
|
|
70
|
+
| `sortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
|
|
71
|
+
| `snapshots()` | +1.3 KB | Scroll position save/restore |
|
|
72
|
+
| `transition()` | +1.8 KB | FLIP-based enter/exit animations for insert & remove |
|
|
74
73
|
|
|
75
74
|
## Framework Adapters
|
|
76
75
|
|
|
@@ -85,6 +84,20 @@ const list = vlist({ container: '#app', items, item: { height: 200, template: re
|
|
|
85
84
|
|
|
86
85
|
**18 interactive examples, full API reference, tutorials, and live benchmarks → [vlist.io](https://vlist.io)**
|
|
87
86
|
|
|
87
|
+
## Migrating from v1
|
|
88
|
+
|
|
89
|
+
v2 is a ground-up rewrite — simpler API, 55% smaller base bundle, zero-allocation scroll path. [Full announcement →](https://vlist.io/blog/v2)
|
|
90
|
+
|
|
91
|
+
| v1 | v2 |
|
|
92
|
+
|----|-----|
|
|
93
|
+
| `vlist(config).use(withGrid()).build()` | `createVList(config, [grid()])` |
|
|
94
|
+
| `withGrid`, `withSelection`, … | `grid`, `selection`, … |
|
|
95
|
+
| `VListFeature` | `VListPlugin` |
|
|
96
|
+
| `BuilderContext` | `PluginContext` |
|
|
97
|
+
| `.vlist-items` | `.vlist-content` |
|
|
98
|
+
|
|
99
|
+
The instance API (`setItems`, `scrollToIndex`, `on`, `destroy`) is unchanged.
|
|
100
|
+
|
|
88
101
|
## License
|
|
89
102
|
|
|
90
103
|
[MIT](LICENSE) — Built by [Floor IO](https://floor.io)
|
package/dist/constants.d.ts
CHANGED
|
@@ -14,18 +14,19 @@ export declare const INITIAL_LOAD_SIZE = 50;
|
|
|
14
14
|
export declare const LOAD_SIZE = 50;
|
|
15
15
|
/** Number of extra items to preload ahead of scroll direction */
|
|
16
16
|
export declare const PRELOAD_AHEAD = 50;
|
|
17
|
-
/**
|
|
18
|
-
* Velocity threshold above which data loading is cancelled (px/ms)
|
|
19
|
-
* When scrolling faster than this, we skip loading data since the user
|
|
20
|
-
* is likely scrolling quickly past content they don't want to see.
|
|
21
|
-
*/
|
|
22
|
-
export declare const LOAD_VELOCITY_THRESHOLD = 12;
|
|
23
17
|
/**
|
|
24
18
|
* Velocity threshold for preloading (px/ms)
|
|
25
19
|
* When scrolling faster than this but slower than LOAD_VELOCITY_THRESHOLD,
|
|
26
20
|
* we preload extra items in the scroll direction to reduce placeholder flashing.
|
|
27
21
|
*/
|
|
28
22
|
export declare const PRELOAD_VELOCITY_THRESHOLD = 2;
|
|
23
|
+
/**
|
|
24
|
+
* Velocity threshold above which data loading is cancelled (px/ms)
|
|
25
|
+
* Above this: skip loading, show placeholders, defer to idle.
|
|
26
|
+
*/
|
|
27
|
+
export declare const LOAD_VELOCITY_THRESHOLD = 15;
|
|
28
|
+
/** Maximum concurrent chunk requests (0 = unlimited) */
|
|
29
|
+
export declare const MAX_CONCURRENT_LOADS = 6;
|
|
29
30
|
/**
|
|
30
31
|
* Maximum virtual size in pixels along the main axis
|
|
31
32
|
* Most browsers support ~16.7M pixels, we use 16M for safety margin
|
|
@@ -35,6 +36,10 @@ export declare const MAX_VIRTUAL_SIZE = 16000000;
|
|
|
35
36
|
export declare const SCROLL_IDLE_TIMEOUT = 150;
|
|
36
37
|
/** Default wheel sensitivity multiplier */
|
|
37
38
|
export declare const WHEEL_SENSITIVITY = 1;
|
|
39
|
+
/** Default easing for smooth scroll animations */
|
|
40
|
+
export declare const SCROLL_EASING: (t: number) => number;
|
|
41
|
+
/** Default smooth scroll duration (ms) */
|
|
42
|
+
export declare const SCROLL_DURATION = 300;
|
|
38
43
|
/** Default auto-hide behavior */
|
|
39
44
|
export declare const SCROLLBAR_AUTO_HIDE = true;
|
|
40
45
|
/** Default auto-hide delay in milliseconds */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Constants
|
|
3
|
+
* All default values and magic numbers in one place
|
|
4
|
+
*/
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// Core
|
|
7
|
+
// =============================================================================
|
|
8
|
+
/** Default number of extra items to render outside viewport */
|
|
9
|
+
export const OVERSCAN = 3;
|
|
10
|
+
/** Default CSS class prefix */
|
|
11
|
+
export const CLASS_PREFIX = "vlist";
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Async Loading
|
|
14
|
+
// =============================================================================
|
|
15
|
+
/** Distance from bottom (in pixels) to trigger infinite scroll */
|
|
16
|
+
export const LOAD_THRESHOLD = 200;
|
|
17
|
+
/** Default number of items to load per request */
|
|
18
|
+
export const INITIAL_LOAD_SIZE = 50;
|
|
19
|
+
/** Default load size for data manager */
|
|
20
|
+
export const LOAD_SIZE = 50;
|
|
21
|
+
/** Number of extra items to preload ahead of scroll direction */
|
|
22
|
+
export const PRELOAD_AHEAD = 50;
|
|
23
|
+
/**
|
|
24
|
+
* Velocity threshold for preloading (px/ms)
|
|
25
|
+
* When scrolling faster than this but slower than LOAD_VELOCITY_THRESHOLD,
|
|
26
|
+
* we preload extra items in the scroll direction to reduce placeholder flashing.
|
|
27
|
+
*/
|
|
28
|
+
export const PRELOAD_VELOCITY_THRESHOLD = 2;
|
|
29
|
+
/**
|
|
30
|
+
* Velocity threshold above which data loading is cancelled (px/ms)
|
|
31
|
+
* Above this: skip loading, show placeholders, defer to idle.
|
|
32
|
+
*/
|
|
33
|
+
export const LOAD_VELOCITY_THRESHOLD = 15;
|
|
34
|
+
/** Maximum concurrent chunk requests (0 = unlimited) */
|
|
35
|
+
export const MAX_CONCURRENT_LOADS = 6;
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Scale
|
|
38
|
+
// =============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Maximum virtual size in pixels along the main axis
|
|
41
|
+
* Most browsers support ~16.7M pixels, we use 16M for safety margin
|
|
42
|
+
*/
|
|
43
|
+
export const MAX_VIRTUAL_SIZE = 16000000;
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Scrolling
|
|
46
|
+
// =============================================================================
|
|
47
|
+
/** Idle timeout for scroll detection (ms) */
|
|
48
|
+
export const SCROLL_IDLE_TIMEOUT = 150;
|
|
49
|
+
/** Default wheel sensitivity multiplier */
|
|
50
|
+
export const WHEEL_SENSITIVITY = 1;
|
|
51
|
+
/** Default easing for smooth scroll animations */
|
|
52
|
+
export const SCROLL_EASING = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
53
|
+
/** Default smooth scroll duration (ms) */
|
|
54
|
+
export const SCROLL_DURATION = 300;
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Scrollbar
|
|
57
|
+
// =============================================================================
|
|
58
|
+
/** Default auto-hide behavior */
|
|
59
|
+
export const SCROLLBAR_AUTO_HIDE = true;
|
|
60
|
+
/** Default auto-hide delay in milliseconds */
|
|
61
|
+
export const SCROLLBAR_AUTO_HIDE_DELAY = 1000;
|
|
62
|
+
/** Default minimum thumb size in pixels */
|
|
63
|
+
export const SCROLLBAR_MIN_THUMB_SIZE = 30;
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Sparse Storage
|
|
66
|
+
// =============================================================================
|
|
67
|
+
/** Default chunk size for sparse storage */
|
|
68
|
+
export const CHUNK_SIZE = 100;
|
|
69
|
+
/** Default maximum cached items before eviction */
|
|
70
|
+
export const MAX_CACHED_ITEMS = 10000;
|
|
71
|
+
/** Buffer for eviction (keep extra items around visible range) */
|
|
72
|
+
export const EVICTION_BUFFER = 500;
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// Placeholder
|
|
75
|
+
// =============================================================================
|
|
76
|
+
/** Default character used for masking text in placeholders */
|
|
77
|
+
export const MASK_CHARACTER = "x";
|
|
78
|
+
/** Maximum items to sample for placeholder structure analysis */
|
|
79
|
+
export const MAX_SAMPLE_SIZE = 20;
|
|
80
|
+
/** Internal flag to identify placeholder items */
|
|
81
|
+
export const PLACEHOLDER_FLAG = "_isPlaceholder";
|
|
82
|
+
/** Prefix for placeholder item IDs */
|
|
83
|
+
export const PLACEHOLDER_ID_PREFIX = "__placeholder_";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist v2 — createVList()
|
|
3
|
+
*
|
|
4
|
+
* Factory function. Resolves config, creates DOM, compiles hooks from
|
|
5
|
+
* plugins, wires the 2-phase pipeline, returns the public VList API.
|
|
6
|
+
*/
|
|
7
|
+
import type { VListItem } from "../types";
|
|
8
|
+
import type { CreateVListConfig, VListPlugin, VList } from "./types";
|
|
9
|
+
export declare function createVList<T extends VListItem = VListItem>(rawConfig: CreateVListConfig<T>, plugins?: VListPlugin<T>[]): VList<T>;
|
|
10
|
+
//# sourceMappingURL=create.d.ts.map
|