vlist 1.9.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.github.md +104 -97
  2. package/README.md +46 -33
  3. package/dist/constants.d.ts +11 -6
  4. package/dist/core/create.d.ts +10 -0
  5. package/dist/core/dom.d.ts +8 -0
  6. package/dist/core/hooks.d.ts +16 -0
  7. package/dist/core/index.d.ts +17 -0
  8. package/dist/core/pipeline.d.ts +51 -0
  9. package/dist/core/pool.d.ts +9 -0
  10. package/dist/core/scroll.d.ts +32 -0
  11. package/dist/core/sizes.d.ts +8 -0
  12. package/dist/core/state.d.ts +47 -0
  13. package/dist/core/types.d.ts +187 -0
  14. package/dist/{builder → core}/velocity.d.ts +1 -1
  15. package/dist/index.d.ts +28 -19
  16. package/dist/index.js +1 -1
  17. package/dist/internals.d.ts +11 -7
  18. package/dist/internals.js +1 -1
  19. package/dist/plugins/a11y/index.d.ts +2 -0
  20. package/dist/plugins/a11y/plugin.d.ts +13 -0
  21. package/dist/{features → plugins}/async/index.d.ts +1 -1
  22. package/dist/{features → plugins}/async/manager.d.ts +5 -1
  23. package/dist/plugins/async/plugin.d.ts +48 -0
  24. package/dist/plugins/autosize/index.d.ts +5 -0
  25. package/dist/plugins/autosize/plugin.d.ts +19 -0
  26. package/dist/plugins/grid/index.d.ts +7 -0
  27. package/dist/plugins/grid/plugin.d.ts +23 -0
  28. package/dist/{features → plugins}/groups/index.d.ts +1 -1
  29. package/dist/plugins/groups/plugin.d.ts +22 -0
  30. package/dist/plugins/masonry/index.d.ts +8 -0
  31. package/dist/plugins/masonry/plugin.d.ts +32 -0
  32. package/dist/plugins/page/index.d.ts +5 -0
  33. package/dist/plugins/page/plugin.d.ts +21 -0
  34. package/dist/plugins/scale/index.d.ts +5 -0
  35. package/dist/plugins/scale/plugin.d.ts +24 -0
  36. package/dist/plugins/scrollbar/index.d.ts +7 -0
  37. package/dist/plugins/scrollbar/plugin.d.ts +20 -0
  38. package/dist/plugins/selection/index.d.ts +6 -0
  39. package/dist/plugins/selection/plugin.d.ts +16 -0
  40. package/dist/{features → plugins}/selection/state.d.ts +8 -0
  41. package/dist/plugins/snapshots/index.d.ts +5 -0
  42. package/dist/plugins/snapshots/plugin.d.ts +17 -0
  43. package/dist/plugins/sortable/index.d.ts +6 -0
  44. package/dist/plugins/sortable/plugin.d.ts +34 -0
  45. package/dist/{features → plugins}/table/index.d.ts +1 -1
  46. package/dist/plugins/table/plugin.d.ts +20 -0
  47. package/dist/plugins/transition/index.d.ts +5 -0
  48. package/dist/plugins/transition/plugin.d.ts +22 -0
  49. package/dist/size.json +1 -1
  50. package/dist/utils/padding.d.ts +2 -4
  51. package/dist/vlist-grid.css +1 -1
  52. package/dist/vlist-masonry.css +1 -1
  53. package/dist/vlist-table.css +1 -1
  54. package/dist/vlist.css +1 -1
  55. package/package.json +9 -4
  56. package/dist/builder/a11y.d.ts +0 -16
  57. package/dist/builder/api.d.ts +0 -21
  58. package/dist/builder/context.d.ts +0 -36
  59. package/dist/builder/core.d.ts +0 -16
  60. package/dist/builder/data.d.ts +0 -71
  61. package/dist/builder/dom.d.ts +0 -15
  62. package/dist/builder/index.d.ts +0 -25
  63. package/dist/builder/materialize.d.ts +0 -166
  64. package/dist/builder/pool.d.ts +0 -10
  65. package/dist/builder/range.d.ts +0 -10
  66. package/dist/builder/scroll.d.ts +0 -24
  67. package/dist/builder/types.d.ts +0 -512
  68. package/dist/features/async/feature.d.ts +0 -72
  69. package/dist/features/autosize/feature.d.ts +0 -34
  70. package/dist/features/autosize/index.d.ts +0 -2
  71. package/dist/features/grid/feature.d.ts +0 -48
  72. package/dist/features/grid/index.d.ts +0 -9
  73. package/dist/features/groups/feature.d.ts +0 -75
  74. package/dist/features/masonry/feature.d.ts +0 -45
  75. package/dist/features/masonry/index.d.ts +0 -9
  76. package/dist/features/page/feature.d.ts +0 -109
  77. package/dist/features/page/index.d.ts +0 -9
  78. package/dist/features/scale/feature.d.ts +0 -42
  79. package/dist/features/scale/index.d.ts +0 -10
  80. package/dist/features/scrollbar/feature.d.ts +0 -81
  81. package/dist/features/scrollbar/index.d.ts +0 -8
  82. package/dist/features/selection/feature.d.ts +0 -91
  83. package/dist/features/selection/index.d.ts +0 -7
  84. package/dist/features/snapshots/feature.d.ts +0 -79
  85. package/dist/features/snapshots/index.d.ts +0 -9
  86. package/dist/features/sortable/feature.d.ts +0 -101
  87. package/dist/features/sortable/index.d.ts +0 -6
  88. package/dist/features/table/feature.d.ts +0 -67
  89. package/dist/features/transition/feature.d.ts +0 -30
  90. package/dist/features/transition/index.d.ts +0 -9
  91. /package/dist/{features → plugins}/async/placeholder.d.ts +0 -0
  92. /package/dist/{features → plugins}/async/sparse.d.ts +0 -0
  93. /package/dist/{features → plugins}/grid/layout.d.ts +0 -0
  94. /package/dist/{features → plugins}/grid/renderer.d.ts +0 -0
  95. /package/dist/{features → plugins}/grid/types.d.ts +0 -0
  96. /package/dist/{features → plugins}/groups/async-bridge.d.ts +0 -0
  97. /package/dist/{features → plugins}/groups/layout.d.ts +0 -0
  98. /package/dist/{features → plugins}/groups/sticky.d.ts +0 -0
  99. /package/dist/{features → plugins}/groups/types.d.ts +0 -0
  100. /package/dist/{features → plugins}/masonry/layout.d.ts +0 -0
  101. /package/dist/{features → plugins}/masonry/renderer.d.ts +0 -0
  102. /package/dist/{features → plugins}/masonry/types.d.ts +0 -0
  103. /package/dist/{features → plugins}/scrollbar/controller.d.ts +0 -0
  104. /package/dist/{features → plugins}/scrollbar/scrollbar.d.ts +0 -0
  105. /package/dist/{features → plugins}/table/header.d.ts +0 -0
  106. /package/dist/{features → plugins}/table/layout.d.ts +0 -0
  107. /package/dist/{features → plugins}/table/renderer.d.ts +0 -0
  108. /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. Accessible by default, batteries-included, with composable features — in 11.2 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 7.7 KB.
4
4
 
5
- **v1.9.1** — [Changelog](./CHANGELOG.md)
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
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
8
  [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
9
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
10
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](https://github.com/floor/vlist/blob/main/LICENSE)
11
11
 
12
- - **New: `withTransition()`** FLIP-based enter/exit animations for insert and remove, with batch `removeItems()` for simultaneous multi-item transitions
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
- - **11.2 KB gzipped** — composable features with perfect tree-shaking
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, async, selection, sortable, transition, scale** — all opt-in
18
- - **Vertical & horizontal** — single axis-neutral code path, every feature works in both orientations
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 { vlist } from 'vlist'
51
+ import { createVList } from 'vlist'
53
52
  import 'vlist/styles'
54
53
 
55
- const list = vlist({
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
- }).build()
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
- ## Builder Pattern
72
+ ## Plugin System
74
73
 
75
74
  Start with the base, add only what you need:
76
75
 
77
76
  ```typescript
78
- import { vlist, withGrid, withGroups, withSelection } from 'vlist'
77
+ import { createVList, grid, groups, selection } from 'vlist'
79
78
 
80
- const list = vlist({
79
+ const list = createVList({
81
80
  container: '#app',
82
81
  items: photos,
83
82
  item: { height: 200, template: renderPhoto },
84
- })
85
- .use(withGrid({ columns: 4, gap: 16 }))
86
- .use(withGroups({
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
- .use(withSelection({ mode: 'multiple' }))
91
- .build()
88
+ }),
89
+ selection({ mode: 'multiple' }),
90
+ ])
92
91
  ```
93
92
 
94
- ### Features
95
-
96
- | Feature | Size | Description |
97
- |---------|------|-------------|
98
- | **Base** | 11.2 KB | Virtualization, ARIA, keyboard nav, gap, padding |
99
- | `withAsync()` | +4.6 KB | Lazy loading with velocity-aware fetching |
100
- | `withSelection()` | +2.7 KB | Single/multiple selection with 2D keyboard nav |
101
- | `withScale()` | +3.6 KB | 1M+ items via scroll compression |
102
- | `withGroups()` | +4.7 KB | Sticky/inline headers with async group discovery |
103
- | `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
104
- | `withScrollbar()` | +1.8 KB | Custom scrollbar UI |
105
- | `withGrid()` | +4.1 KB | 2D grid layout |
106
- | `withMasonry()` | +3.4 KB | Pinterest-style masonry with lane-aware keyboard nav |
107
- | `withTable()` | +5.8 KB | Data table with columns, resize, sort |
108
- | `withPage()` | +0.7 KB | Window-level scrolling |
109
- | `withSortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
110
- | `withSnapshots()` | +1.2 KB | Scroll position save/restore |
111
- | `withTransition()` | +2.1 KB | FLIP-based enter/exit animations for insert & remove |
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 { vlist, withTable, withSelection } from 'vlist'
119
+ import { createVList, table, selection } from 'vlist'
121
120
 
122
- const table = vlist({
121
+ const myTable = createVList({
123
122
  container: '#my-table',
124
123
  items: contacts,
125
124
  item: { height: 36, template: () => '' },
126
- })
127
- .use(withTable({
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
- .use(withSelection({ mode: 'single' }))
139
- .build()
136
+ }),
137
+ selection({ mode: 'single' }),
138
+ ])
140
139
 
141
- table.on('column:sort', ({ key, direction }) => { /* re-sort data */ })
142
- table.on('column:resize', ({ key, width }) => { /* persist widths */ })
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 { vlist, withGrid, withScrollbar } from 'vlist'
147
+ import { createVList, grid, scrollbar } from 'vlist'
149
148
 
150
- const gallery = vlist({
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
- .use(withGrid({ columns: 4, gap: 16 }))
164
- .use(withScrollbar({ autoHide: true }))
165
- .build()
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 { vlist, withTransition, withSelection } from 'vlist'
170
+ import { createVList, transition, selection } from 'vlist'
172
171
 
173
- const list = vlist({
172
+ const list = createVList({
174
173
  container: '#playlist',
175
174
  items: tracks,
176
175
  item: { height: 64, template: renderTrack },
177
- })
178
- .use(withTransition({ duration: 200 }))
179
- .use(withSelection({ mode: 'multiple' }))
180
- .build()
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 { vlist, withAsync } from 'vlist'
194
+ import { createVList, data } from 'vlist'
196
195
 
197
- const list = vlist({
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
- .use(withAsync({
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
- .build()
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 = vlist(config).use(...features).build()
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(index, partial)` | Update a single item by index |
246
- | `list.insertItem(item, index?)` | Insert at index (animated with `withTransition`) |
247
- | `list.removeItem(id)` | Remove by ID (animated with `withTransition`) |
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 `withSelection()`)
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
- ## Feature Configuration
295
+ ## Plugin Configuration
300
296
 
301
- Each feature's config is fully typed — hover in your IDE for details.
297
+ Each plugin's config is fully typed — hover in your IDE for details.
302
298
 
303
299
  ```typescript
304
- withGrid({ columns: 4, gap: 16 })
305
- withMasonry({ columns: 4, gap: 16 })
306
- withGroups({ getGroupForIndex, header: { height, template }, sticky?: true })
307
- withSelection({ mode: 'single' | 'multiple', initial?: [...ids] })
308
- withAsync({ adapter: { read }, loading?: { cancelThreshold? } })
309
- withTable({ columns, rowHeight, headerHeight?, resizable? })
310
- withAutoSize() // auto-measure items (requires estimatedHeight)
311
- withScale() // auto-activates at 16.7M px
312
- withScrollbar({ autoHide?, autoHideDelay?, minThumbSize? })
313
- withTransition({ duration?: 200, insert?: timing, remove?: timing })
314
- withSortable({ handle?: '.drag-handle' }) // drag-and-drop reordering
315
- withPage() // no config — uses document scroll
316
- withSnapshots({ autoSave: 'key' }) // automatic sessionStorage save/restore
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 withGrid()
338
- import 'vlist/styles/masonry' // when using withMasonry()
339
- import 'vlist/styles/table' // when using withTable()
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:** ~8ms (constant, regardless of item count)
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 { vlist, withGrid, type VList } from 'vlist'
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> = 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. Accessible by default, batteries-included, with composable features — in 11.2 KB.
3
+ The virtual list library for every framework. Ultra efficient, batteries-included, and accessible with composable plugins — in 7.7 KB.
4
4
 
5
- **v1.9.1** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md)
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
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
8
  [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
9
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
10
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](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
- - **11.2 KB gzipped** — composable features with perfect tree-shaking
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 feature works in both orientations
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 { vlist } from 'vlist'
27
+ import { createVList } from 'vlist'
29
28
  import 'vlist/styles'
30
29
 
31
- const list = vlist({
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
- }).build()
41
+ })
43
42
  ```
44
43
 
45
- Add features with the builder pattern:
44
+ Add plugins as the second argument:
46
45
 
47
46
  ```typescript
48
- import { vlist, withGrid, withSelection } from 'vlist'
47
+ import { createVList, grid, selection } from 'vlist'
49
48
 
50
- const list = vlist({ container: '#app', items, item: { height: 200, template: render } })
51
- .use(withGrid({ columns: 4, gap: 16 }))
52
- .use(withSelection({ mode: 'multiple' }))
53
- .build()
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
- ## Features
57
-
58
- | Feature | Size | Description |
59
- |---------|------|-------------|
60
- | **Base** | 11.2 KB | Virtualization, ARIA, keyboard nav, gap, padding |
61
- | `withAsync()` | +4.6 KB | Lazy loading with velocity-aware fetching |
62
- | `withSelection()` | +2.7 KB | Single/multiple selection with 2D keyboard nav |
63
- | `withScale()` | +3.6 KB | 1M+ items via scroll compression |
64
- | `withGroups()` | +4.7 KB | Sticky/inline headers with async group discovery |
65
- | `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
66
- | `withScrollbar()` | +1.8 KB | Custom scrollbar UI |
67
- | `withGrid()` | +4.1 KB | 2D grid layout |
68
- | `withMasonry()` | +3.4 KB | Pinterest-style masonry with lane-aware keyboard nav |
69
- | `withTable()` | +5.8 KB | Data table with columns, resize, sort |
70
- | `withPage()` | +0.7 KB | Window-level scrolling |
71
- | `withSortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
72
- | `withSnapshots()` | +1.2 KB | Scroll position save/restore |
73
- | `withTransition()` | +2.1 KB | FLIP-based enter/exit animations for insert & remove |
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)
@@ -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,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
@@ -0,0 +1,8 @@
1
+ /**
2
+ * vlist v2 — DOM Structure
3
+ * Container resolution and DOM scaffold creation.
4
+ */
5
+ import type { DOMStructure } from "./types";
6
+ export declare function resolveContainer(container: HTMLElement | string): HTMLElement;
7
+ export declare function createDOMStructure(container: HTMLElement, classPrefix: string, horizontal: boolean, interactive: boolean, ariaLabel?: string): DOMStructure;
8
+ //# sourceMappingURL=dom.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * vlist v2 — Build-Time Compiled Hooks
3
+ *
4
+ * Hooks are collected during createVList() and frozen into plain arrays.
5
+ * On the hot path they are iterated with a for-loop — no closures,
6
+ * no dynamic dispatch, no allocation.
7
+ */
8
+ import type { CalculateHook, CommitHook, AfterScrollHook, IdleHook, ResizeHook, CompiledHooks, VListPlugin } from "./types";
9
+ import type { VListItem } from "../types";
10
+ export declare function compileHooks<T extends VListItem>(plugins: readonly VListPlugin<T>[]): CompiledHooks;
11
+ export declare function runCalculateHooks(hooks: readonly CalculateHook[], state: import("./state").EngineState): void;
12
+ export declare function runCommitHooks(hooks: readonly CommitHook[], state: import("./state").EngineState): void;
13
+ export declare function runAfterScrollHooks(hooks: readonly AfterScrollHook[], scrollPosition: number, direction: number): void;
14
+ export declare function runIdleHooks(hooks: readonly IdleHook[]): void;
15
+ export declare function runResizeHooks(hooks: readonly ResizeHook[], width: number, height: number): void;
16
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * vlist v2 — Core Public API
3
+ */
4
+ export { createVList } from "./create";
5
+ export { createEngineState } from "./state";
6
+ export type { EngineState } from "./state";
7
+ export { phase1Calculate, phase2Commit, render, createRenderConfig } from "./pipeline";
8
+ export type { RenderConfig } from "./pipeline";
9
+ export { compileHooks, runCalculateHooks, runCommitHooks, runAfterScrollHooks, runIdleHooks, runResizeHooks } from "./hooks";
10
+ export { createPool } from "./pool";
11
+ export { createSizeCache, countVisibleItems, countItemsFittingFromBottom, getOffsetForVirtualIndex } from "./sizes";
12
+ export { createDOMStructure, resolveContainer } from "./dom";
13
+ export { createScrollHandler } from "./scroll";
14
+ export type { SizeCache } from "./sizes";
15
+ export type { ScrollHandler, ScrollHandlerConfig } from "./scroll";
16
+ export type { CalculateHook, CommitHook, AfterScrollHook, IdleHook, ResizeHook, CompiledHooks, ResolvedConfig, DOMStructure, ElementPool, VisibleRangeFn, PluginContext, VListPlugin, VList, CreateVListConfig, } from "./types";
17
+ //# sourceMappingURL=index.d.ts.map