vlist 1.6.4 → 1.7.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 +302 -20
- package/README.md +1 -1
- package/dist/builder/types.d.ts +1 -0
- package/dist/features/sortable/feature.d.ts +95 -0
- package/dist/features/sortable/index.d.ts +6 -0
- package/dist/features/table/header.d.ts +1 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/internals.js +1 -1
- package/dist/rendering/renderer.d.ts +2 -0
- package/dist/size.json +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/vlist-table.css +1 -1
- package/dist/vlist.css +1 -1
- package/package.json +2 -1
package/README.github.md
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
# vlist
|
|
2
2
|
|
|
3
|
-
The virtual list library for every framework. Accessible by default, batteries-included, with composable features — in 10.
|
|
3
|
+
The virtual list library for every framework. Accessible by default, batteries-included, with composable features — in 10.5 KB.
|
|
4
|
+
|
|
5
|
+
**v1.7.0** — [Changelog](./changelog.txt)
|
|
4
6
|
|
|
5
7
|
[](https://www.npmjs.com/package/vlist)
|
|
6
8
|
[](https://github.com/floor/vlist/actions/workflows/ci.yml)
|
|
7
9
|
[](https://github.com/floor/vlist/blob/main/LICENSE)
|
|
8
10
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
+
- **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering, ARIA live region
|
|
12
|
+
- **Zero dependencies** — framework-agnostic core with tiny adapters for Vue, Svelte, Solid, React
|
|
11
13
|
- **10.6 KB gzipped** — composable features with perfect tree-shaking
|
|
12
14
|
- **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
|
|
15
|
+
- **Grid, masonry, table, groups, async, selection, scale** — all opt-in
|
|
16
|
+
- **Vertical & horizontal** — dimension-agnostic API, every layout mode works in both orientations
|
|
17
|
+
|
|
18
|
+
**18 interactive examples, docs & benchmarks → [vlist.io](https://vlist.io)**
|
|
19
|
+
|
|
20
|
+
## Why vlist
|
|
21
|
+
|
|
22
|
+
| | vlist | TanStack Virtual | react-virtuoso | virtua | vue-virtual-scroller |
|
|
23
|
+
|---|---|---|---|---|---|
|
|
24
|
+
| **A11y built-in** | WAI-ARIA + 2D keyboard | None (DIY) | Partial | Minimal | None |
|
|
25
|
+
| **Grid + Masonry + Table** | All | Grid only | Grid + Table | Grid only | None |
|
|
26
|
+
| **Vue** | 0.6 KB adapter | Yes | — | Yes | 11.8 KB |
|
|
27
|
+
| **Svelte** | 0.5 KB adapter | Yes | — | Yes | — |
|
|
28
|
+
| **Solid** | 0.5 KB adapter | Yes | — | Yes | — |
|
|
29
|
+
| **Vanilla JS** | Native | Yes | — | — | — |
|
|
30
|
+
| **Constant memory** | ~0.1 MB at 1M | No | No | No | No |
|
|
31
|
+
|
|
32
|
+
## Framework Adapters
|
|
13
33
|
|
|
14
|
-
|
|
34
|
+
| Framework | Package | Size |
|
|
35
|
+
|-----------|---------|------|
|
|
36
|
+
| Vanilla JS | `vlist` | Native — no adapter needed |
|
|
37
|
+
| Vue | [`vlist-vue`](https://github.com/floor/vlist-vue) | 0.6 KB gzip |
|
|
38
|
+
| Svelte | [`vlist-svelte`](https://github.com/floor/vlist-svelte) | 0.5 KB gzip |
|
|
39
|
+
| SolidJS | [`vlist-solidjs`](https://github.com/floor/vlist-solidjs) | 0.5 KB gzip |
|
|
40
|
+
| React | [`vlist-react`](https://github.com/floor/vlist-react) | 0.6 KB gzip |
|
|
15
41
|
|
|
16
42
|
```bash
|
|
17
|
-
npm install vlist
|
|
43
|
+
npm install vlist # vanilla JS
|
|
44
|
+
npm install vlist vlist-vue # or vlist-svelte / vlist-solidjs / vlist-react
|
|
18
45
|
```
|
|
19
46
|
|
|
20
47
|
## Quick Start
|
|
@@ -35,27 +62,41 @@ const list = vlist({
|
|
|
35
62
|
template: (item) => `<div>${item.name}</div>`,
|
|
36
63
|
},
|
|
37
64
|
}).build()
|
|
65
|
+
|
|
66
|
+
list.scrollToIndex(10)
|
|
67
|
+
list.setItems(newItems)
|
|
68
|
+
list.on('item:click', ({ item }) => console.log(item))
|
|
38
69
|
```
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
## Builder Pattern
|
|
72
|
+
|
|
73
|
+
Start with the base, add only what you need:
|
|
41
74
|
|
|
42
75
|
```typescript
|
|
43
|
-
import { vlist, withGrid, withSelection } from 'vlist'
|
|
76
|
+
import { vlist, withGrid, withGroups, withSelection } from 'vlist'
|
|
44
77
|
|
|
45
|
-
const list = vlist({
|
|
78
|
+
const list = vlist({
|
|
79
|
+
container: '#app',
|
|
80
|
+
items: photos,
|
|
81
|
+
item: { height: 200, template: renderPhoto },
|
|
82
|
+
})
|
|
46
83
|
.use(withGrid({ columns: 4, gap: 16 }))
|
|
84
|
+
.use(withGroups({
|
|
85
|
+
getGroupForIndex: (i) => photos[i].category,
|
|
86
|
+
header: { height: 40, template: (cat) => `<h2>${cat}</h2>` },
|
|
87
|
+
}))
|
|
47
88
|
.use(withSelection({ mode: 'multiple' }))
|
|
48
89
|
.build()
|
|
49
90
|
```
|
|
50
91
|
|
|
51
|
-
|
|
92
|
+
### Features
|
|
52
93
|
|
|
53
94
|
| Feature | Size | Description |
|
|
54
95
|
|---------|------|-------------|
|
|
55
96
|
| **Base** | 10.6 KB | Virtualization, ARIA, keyboard nav, gap, padding |
|
|
56
97
|
| `withAsync()` | +4.6 KB | Lazy loading with velocity-aware fetching |
|
|
57
98
|
| `withSelection()` | +2.7 KB | Single/multiple selection with 2D keyboard nav |
|
|
58
|
-
| `withScale()` | +3.
|
|
99
|
+
| `withScale()` | +3.7 KB | 1M+ items via scroll compression |
|
|
59
100
|
| `withGroups()` | +2.7 KB | Sticky/inline headers |
|
|
60
101
|
| `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
|
|
61
102
|
| `withScrollbar()` | +1.7 KB | Custom scrollbar UI |
|
|
@@ -65,19 +106,260 @@ const list = vlist({ container: '#app', items, item: { height: 200, template: re
|
|
|
65
106
|
| `withPage()` | +0.8 KB | Window-level scrolling |
|
|
66
107
|
| `withSnapshots()` | +0.8 KB | Scroll position save/restore |
|
|
67
108
|
|
|
68
|
-
##
|
|
109
|
+
## Examples
|
|
69
110
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
111
|
+
More examples at **[vlist.io](https://vlist.io)**.
|
|
112
|
+
|
|
113
|
+
### Data Table
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { vlist, withTable, withSelection } from 'vlist'
|
|
117
|
+
|
|
118
|
+
const table = vlist({
|
|
119
|
+
container: '#my-table',
|
|
120
|
+
items: contacts,
|
|
121
|
+
item: { height: 36, template: () => '' },
|
|
122
|
+
})
|
|
123
|
+
.use(withTable({
|
|
124
|
+
columns: [
|
|
125
|
+
{ key: 'name', label: 'Name', width: 200, sortable: true },
|
|
126
|
+
{ key: 'email', label: 'Email', width: 260, sortable: true },
|
|
127
|
+
{ key: 'role', label: 'Role', width: 160, sortable: true },
|
|
128
|
+
{ key: 'status', label: 'Status', width: 100, align: 'center' },
|
|
129
|
+
],
|
|
130
|
+
rowHeight: 36,
|
|
131
|
+
headerHeight: 36,
|
|
132
|
+
resizable: true,
|
|
133
|
+
}))
|
|
134
|
+
.use(withSelection({ mode: 'single' }))
|
|
135
|
+
.build()
|
|
136
|
+
|
|
137
|
+
table.on('column:sort', ({ key, direction }) => { /* re-sort data */ })
|
|
138
|
+
table.on('column:resize', ({ key, width }) => { /* persist widths */ })
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Grid Layout
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { vlist, withGrid, withScrollbar } from 'vlist'
|
|
145
|
+
|
|
146
|
+
const gallery = vlist({
|
|
147
|
+
container: '#gallery',
|
|
148
|
+
items: photos,
|
|
149
|
+
item: {
|
|
150
|
+
height: 200,
|
|
151
|
+
template: (photo) => `
|
|
152
|
+
<div class="card">
|
|
153
|
+
<img src="${photo.url}" />
|
|
154
|
+
<span>${photo.title}</span>
|
|
155
|
+
</div>
|
|
156
|
+
`,
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
.use(withGrid({ columns: 4, gap: 16 }))
|
|
160
|
+
.use(withScrollbar({ autoHide: true }))
|
|
161
|
+
.build()
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Async Loading
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { vlist, withAsync } from 'vlist'
|
|
76
168
|
|
|
77
|
-
|
|
169
|
+
const list = vlist({
|
|
170
|
+
container: '#list',
|
|
171
|
+
item: {
|
|
172
|
+
height: 64,
|
|
173
|
+
template: (item) => item
|
|
174
|
+
? `<div>${item.name}</div>`
|
|
175
|
+
: `<div class="placeholder">Loading…</div>`,
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
.use(withAsync({
|
|
179
|
+
adapter: {
|
|
180
|
+
read: async ({ offset, limit }) => {
|
|
181
|
+
const res = await fetch(`/api/users?offset=${offset}&limit=${limit}`)
|
|
182
|
+
const data = await res.json()
|
|
183
|
+
return { items: data.items, total: data.total, hasMore: data.hasMore }
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}))
|
|
187
|
+
.build()
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Accessibility
|
|
191
|
+
|
|
192
|
+
Every vlist is accessible by default following the [WAI-ARIA listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/):
|
|
193
|
+
|
|
194
|
+
- **Arrow keys** move focus between items with a visible focus ring
|
|
195
|
+
- **2D navigation** in grids and masonry — Up/Down by row, Left/Right by cell
|
|
196
|
+
- **Masonry lane-aware nav** — arrows stay in the same visual column
|
|
197
|
+
- **Home/End, PageUp/PageDown, Ctrl+Home/End** — full keyboard coverage
|
|
198
|
+
- **Screen-reader DOM ordering** — items reordered on scroll idle for correct reading order
|
|
199
|
+
- **ARIA live region** — announces loading state changes
|
|
200
|
+
- **Focus recovery** — maintains focus when items are removed
|
|
201
|
+
|
|
202
|
+
Set `interactive: false` for display-only lists (log viewers, activity feeds) where items contain their own interactive elements.
|
|
203
|
+
|
|
204
|
+
## API
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const list = vlist(config).use(...features).build()
|
|
208
|
+
```
|
|
78
209
|
|
|
79
|
-
|
|
210
|
+
### Data
|
|
211
|
+
|
|
212
|
+
| Method | Description |
|
|
213
|
+
|--------|-------------|
|
|
214
|
+
| `list.setItems(items)` | Replace all items |
|
|
215
|
+
| `list.appendItems(items)` | Add to end (auto-scrolls in reverse mode) |
|
|
216
|
+
| `list.prependItems(items)` | Add to start (preserves scroll position) |
|
|
217
|
+
| `list.updateItem(index, partial)` | Update a single item by index |
|
|
218
|
+
| `list.removeItem(index)` | Remove by index |
|
|
219
|
+
| `list.getItemAt(index)` | Get item at index |
|
|
220
|
+
| `list.getIndexById(id)` | Get index by item ID |
|
|
221
|
+
| `list.reload()` | Re-fetch from adapter (async) |
|
|
222
|
+
|
|
223
|
+
### Navigation
|
|
224
|
+
|
|
225
|
+
| Method | Description |
|
|
226
|
+
|--------|-------------|
|
|
227
|
+
| `list.scrollToIndex(i, align?)` | Scroll to index (`'start'` \| `'center'` \| `'end'`) |
|
|
228
|
+
| `list.scrollToIndex(i, opts?)` | With `{ align, behavior: 'smooth', duration }` |
|
|
229
|
+
| `list.cancelScroll()` | Cancel smooth scroll animation |
|
|
230
|
+
| `list.getScrollPosition()` | Current scroll offset |
|
|
231
|
+
|
|
232
|
+
### Selection (with `withSelection()`)
|
|
233
|
+
|
|
234
|
+
| Method | Description |
|
|
235
|
+
|--------|-------------|
|
|
236
|
+
| `list.select(...ids)` | Select item(s) |
|
|
237
|
+
| `list.deselect(...ids)` | Deselect item(s) |
|
|
238
|
+
| `list.toggleSelect(id)` | Toggle |
|
|
239
|
+
| `list.selectAll()` / `list.clearSelection()` | Bulk operations |
|
|
240
|
+
| `list.getSelected()` | Array of selected IDs |
|
|
241
|
+
| `list.getSelectedItems()` | Array of selected items |
|
|
242
|
+
|
|
243
|
+
### Events
|
|
244
|
+
|
|
245
|
+
`list.on()` returns an unsubscribe function. You can also use `list.off(event, handler)`.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
list.on('scroll', ({ scrollPosition, direction }) => {})
|
|
249
|
+
list.on('range:change', ({ range }) => {})
|
|
250
|
+
list.on('item:click', ({ item, index, event }) => {})
|
|
251
|
+
list.on('item:dblclick', ({ item, index, event }) => {})
|
|
252
|
+
list.on('selection:change', ({ selectedIds, selectedItems }) => {})
|
|
253
|
+
list.on('load:start', ({ offset, limit }) => {})
|
|
254
|
+
list.on('load:end', ({ items, offset, total }) => {})
|
|
255
|
+
list.on('load:error', ({ error, offset, limit }) => {})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Properties
|
|
259
|
+
|
|
260
|
+
| Property | Description |
|
|
261
|
+
|----------|-------------|
|
|
262
|
+
| `list.element` | Root DOM element |
|
|
263
|
+
| `list.items` | Current items (readonly) |
|
|
264
|
+
| `list.total` | Total item count |
|
|
265
|
+
| `list.destroy()` | Cleanup and remove from DOM |
|
|
266
|
+
|
|
267
|
+
## Feature Configuration
|
|
268
|
+
|
|
269
|
+
Each feature's config is fully typed — hover in your IDE for details.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
withGrid({ columns: 4, gap: 16 })
|
|
273
|
+
withMasonry({ columns: 4, gap: 16 })
|
|
274
|
+
withGroups({ getGroupForIndex, header: { height, template }, sticky?: true })
|
|
275
|
+
withSelection({ mode: 'single' | 'multiple', initial?: [...ids] })
|
|
276
|
+
withAsync({ adapter: { read }, loading?: { cancelThreshold? } })
|
|
277
|
+
withTable({ columns, rowHeight, headerHeight?, resizable? })
|
|
278
|
+
withAutoSize() // auto-measure items (requires estimatedHeight)
|
|
279
|
+
withScale() // auto-activates at 16.7M px
|
|
280
|
+
withScrollbar({ autoHide?, autoHideDelay?, minThumbSize? })
|
|
281
|
+
withPage() // no config — uses document scroll
|
|
282
|
+
withSnapshots({ autoSave: 'key' }) // automatic sessionStorage save/restore
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Full configuration reference → **[vlist.io](https://vlist.io)**
|
|
286
|
+
|
|
287
|
+
## Base Configuration
|
|
288
|
+
|
|
289
|
+
| Option | Default | Description |
|
|
290
|
+
|--------|---------|-------------|
|
|
291
|
+
| `overscan` | `3` | Extra items rendered outside viewport |
|
|
292
|
+
| `ariaLabel` | — | Accessible label for the listbox |
|
|
293
|
+
| `orientation` | `'vertical'` | `'vertical'` or `'horizontal'` scroll direction |
|
|
294
|
+
| `padding` | `0` | Content inset — number, `[v, h]`, or `[top, right, bottom, left]` |
|
|
295
|
+
| `interactive` | `true` | Enable built-in keyboard navigation |
|
|
296
|
+
| `reverse` | `false` | Reverse mode for chat UIs |
|
|
297
|
+
| `scroll.wrap` | `false` | Wrap focus around at boundaries |
|
|
298
|
+
|
|
299
|
+
## Styling
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import 'vlist/styles' // core (always required)
|
|
303
|
+
import 'vlist/styles/grid' // when using withGrid()
|
|
304
|
+
import 'vlist/styles/masonry' // when using withMasonry()
|
|
305
|
+
import 'vlist/styles/table' // when using withTable()
|
|
306
|
+
import 'vlist/styles/extras' // optional (variants, loading states, animations)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Dark mode works out of the box via `prefers-color-scheme`, Tailwind's `.dark` class, or `data-theme-mode="dark"`. Override CSS custom properties to match your design system. See [vlist.io/tutorials/styling](https://vlist.io/tutorials/styling) for the full guide.
|
|
310
|
+
|
|
311
|
+
## Performance
|
|
312
|
+
|
|
313
|
+
| Dataset Size | After Render | Scroll Delta |
|
|
314
|
+
|--------------|-------------|--------------|
|
|
315
|
+
| 10K items | 0.07 MB | ~0 MB |
|
|
316
|
+
| 100K items | 0.08 MB | ~0 MB |
|
|
317
|
+
| 1M items | 0.09 MB | 0.19 MB |
|
|
318
|
+
|
|
319
|
+
- **Initial render:** ~8ms (constant, regardless of item count)
|
|
320
|
+
- **Scroll:** 120 FPS at any scale
|
|
321
|
+
- **DOM nodes:** ~26 in document with 100K items (visible + overscan only)
|
|
322
|
+
|
|
323
|
+
Live benchmarks against 9 competitors → **[vlist.io/benchmarks](https://vlist.io/benchmarks)**
|
|
324
|
+
|
|
325
|
+
## TypeScript
|
|
326
|
+
|
|
327
|
+
Fully typed. Generic over your item type:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { vlist, withGrid, type VList } from 'vlist'
|
|
331
|
+
|
|
332
|
+
interface Photo { id: number; url: string; title: string }
|
|
333
|
+
|
|
334
|
+
const list: VList<Photo> = vlist<Photo>({
|
|
335
|
+
container: '#gallery',
|
|
336
|
+
items: photos,
|
|
337
|
+
item: {
|
|
338
|
+
height: 200,
|
|
339
|
+
template: (photo) => `<img src="${photo.url}" />`,
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
.use(withGrid({ columns: 4 }))
|
|
343
|
+
.build()
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Contributing
|
|
347
|
+
|
|
348
|
+
1. Fork → branch → make changes → add tests → pull request
|
|
349
|
+
2. Run `bun test` and `bun run build` before submitting
|
|
80
350
|
|
|
81
351
|
## License
|
|
82
352
|
|
|
83
|
-
[MIT](LICENSE)
|
|
353
|
+
[MIT](LICENSE)
|
|
354
|
+
|
|
355
|
+
## Links
|
|
356
|
+
|
|
357
|
+
- **Docs & Examples:** [vlist.io](https://vlist.io)
|
|
358
|
+
- **Staging:** [staging.vlist.io](https://staging.vlist.io)
|
|
359
|
+
- **GitHub:** [github.com/floor/vlist](https://github.com/floor/vlist)
|
|
360
|
+
- **NPM:** [vlist](https://www.npmjs.com/package/vlist)
|
|
361
|
+
- **Issues:** [GitHub Issues](https://github.com/floor/vlist/issues)
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
Built by [FloorIO](https://floor.io)
|
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ const list = vlist({ container: '#app', items, item: { height: 200, template: re
|
|
|
55
55
|
| **Base** | 10.6 KB | Virtualization, ARIA, keyboard nav, gap, padding |
|
|
56
56
|
| `withAsync()` | +4.6 KB | Lazy loading with velocity-aware fetching |
|
|
57
57
|
| `withSelection()` | +2.7 KB | Single/multiple selection with 2D keyboard nav |
|
|
58
|
-
| `withScale()` | +3.
|
|
58
|
+
| `withScale()` | +3.7 KB | 1M+ items via scroll compression |
|
|
59
59
|
| `withGroups()` | +2.7 KB | Sticky/inline headers |
|
|
60
60
|
| `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
|
|
61
61
|
| `withScrollbar()` | +1.7 KB | Custom scrollbar UI |
|
package/dist/builder/types.d.ts
CHANGED
|
@@ -476,6 +476,7 @@ export interface VList<T extends VListItem = VListItem> {
|
|
|
476
476
|
selectPrevious?: () => void;
|
|
477
477
|
getScrollSnapshot?: () => ScrollSnapshot;
|
|
478
478
|
restoreScroll?: (snapshot: ScrollSnapshot) => void;
|
|
479
|
+
isSorting?: () => boolean;
|
|
479
480
|
[key: string]: unknown;
|
|
480
481
|
}
|
|
481
482
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist/sortable - Builder Feature
|
|
3
|
+
* Drag-and-drop reordering for virtual lists.
|
|
4
|
+
*
|
|
5
|
+
* Priority: 55 (runs after selection at 50, before scrollbar at 60)
|
|
6
|
+
*
|
|
7
|
+
* What it wires:
|
|
8
|
+
* - Pointer event handlers on the items container for drag initiation
|
|
9
|
+
* - Creates a drag ghost element that follows the pointer
|
|
10
|
+
* - Items shift via CSS transforms to make room (like iOS list reordering)
|
|
11
|
+
* - Auto-scrolls when dragging near viewport edges
|
|
12
|
+
* - Keyboard reordering: Space to grab, arrows to move, Space to drop, Escape to cancel
|
|
13
|
+
* - ARIA attributes (aria-roledescription, aria-describedby) and live region announcements
|
|
14
|
+
* - Emits sort:start, sort:end, and sort:cancel events
|
|
15
|
+
*
|
|
16
|
+
* The feature is purely visual during drag — it does NOT reorder data.
|
|
17
|
+
* On drop, it emits a `sort:end` event with `{ fromIndex, toIndex }`.
|
|
18
|
+
* The consumer is responsible for reordering their data array and
|
|
19
|
+
* calling `setItems()` with the new order.
|
|
20
|
+
*
|
|
21
|
+
* Keyboard reordering emits `sort:end` per arrow key press (incremental moves).
|
|
22
|
+
* On Escape, it emits `sort:cancel` with `{ originalItems }` so the consumer
|
|
23
|
+
* can restore the original order via `setItems(originalItems)`.
|
|
24
|
+
*
|
|
25
|
+
* When composed with withSelection, Space is intercepted for grab/drop.
|
|
26
|
+
* Use Enter to toggle selection on focused items.
|
|
27
|
+
*
|
|
28
|
+
* IMPORTANT: vlist positions items via `style.transform: translateY(offset)`.
|
|
29
|
+
* The shift must ADD to that existing offset, not replace it.
|
|
30
|
+
*
|
|
31
|
+
* Added methods: isSorting
|
|
32
|
+
* Added events: sort:start, sort:end, sort:cancel
|
|
33
|
+
*/
|
|
34
|
+
import type { VListItem } from "../../types";
|
|
35
|
+
import type { VListFeature } from "../../builder/types";
|
|
36
|
+
/** Sortable feature configuration */
|
|
37
|
+
export interface SortableConfig {
|
|
38
|
+
/**
|
|
39
|
+
* CSS selector for the drag handle within each item.
|
|
40
|
+
* When set, only elements matching this selector initiate a drag.
|
|
41
|
+
* When omitted, the entire item is draggable.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* withSortable({ handle: '.drag-handle' })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
handle?: string;
|
|
48
|
+
/**
|
|
49
|
+
* CSS class added to the drag ghost element (default: 'vlist-sort-ghost').
|
|
50
|
+
* The ghost is a clone of the dragged item that follows the pointer.
|
|
51
|
+
*/
|
|
52
|
+
ghostClass?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Transition duration for item shift animations in milliseconds (default: 150).
|
|
55
|
+
* Set to 0 for instant shifts.
|
|
56
|
+
*/
|
|
57
|
+
shiftDuration?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Size of the auto-scroll zone at viewport edges in pixels (default: 40).
|
|
60
|
+
* When the pointer enters this zone during drag, the list auto-scrolls.
|
|
61
|
+
*/
|
|
62
|
+
edgeScrollZone?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Auto-scroll speed in pixels per frame (default: 8).
|
|
65
|
+
*/
|
|
66
|
+
edgeScrollSpeed?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Minimum distance in pixels the pointer must move before drag starts (default: 5).
|
|
69
|
+
* Prevents accidental drags on click.
|
|
70
|
+
*/
|
|
71
|
+
dragThreshold?: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a sortable feature for the builder.
|
|
75
|
+
*
|
|
76
|
+
* Enables drag-and-drop reordering of items in the virtual list.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { vlist } from 'vlist'
|
|
80
|
+
* import { withSortable } from 'vlist/sortable'
|
|
81
|
+
*
|
|
82
|
+
* const list = vlist({ ... })
|
|
83
|
+
* .use(withSortable({ handle: '.drag-handle' }))
|
|
84
|
+
* .build()
|
|
85
|
+
*
|
|
86
|
+
* list.on('sort:end', ({ fromIndex, toIndex }) => {
|
|
87
|
+
* const reordered = [...items]
|
|
88
|
+
* const [moved] = reordered.splice(fromIndex, 1)
|
|
89
|
+
* reordered.splice(toIndex, 0, moved)
|
|
90
|
+
* list.setItems(reordered)
|
|
91
|
+
* })
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare const withSortable: <T extends VListItem = VListItem>(config?: SortableConfig) => VListFeature<T>;
|
|
95
|
+
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -37,7 +37,6 @@ import type { TableHeader, ColumnSortEvent, ColumnClickEvent } from "./types";
|
|
|
37
37
|
* Create a TableHeader instance.
|
|
38
38
|
*
|
|
39
39
|
* @param root - The vlist root element (.vlist)
|
|
40
|
-
* @param viewport - The vlist viewport element (for scroll sync)
|
|
41
40
|
* @param headerHeight - Height of the header row in pixels
|
|
42
41
|
* @param classPrefix - CSS class prefix (default: 'vlist')
|
|
43
42
|
* @param onResize - Callback when a column is resized (receives column index and new width)
|
|
@@ -45,5 +44,5 @@ import type { TableHeader, ColumnSortEvent, ColumnClickEvent } from "./types";
|
|
|
45
44
|
* @param onClick - Callback when any header cell is clicked
|
|
46
45
|
* @returns TableHeader instance
|
|
47
46
|
*/
|
|
48
|
-
export declare const createTableHeader: <T extends VListItem = VListItem>(root: HTMLElement,
|
|
47
|
+
export declare const createTableHeader: <T extends VListItem = VListItem>(root: HTMLElement, headerHeight: number, classPrefix: string, onResize: (columnIndex: number, newWidth: number) => void, onSort?: (event: ColumnSortEvent) => void, onClick?: (event: ColumnClickEvent) => void) => TableHeader<T>;
|
|
49
48
|
//# sourceMappingURL=header.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export { withMasonry } from "./features/masonry";
|
|
|
21
21
|
export { withSelection } from "./features/selection";
|
|
22
22
|
export { withSnapshots } from "./features/snapshots";
|
|
23
23
|
export { withTable } from "./features/table";
|
|
24
|
+
export { withSortable } from "./features/sortable";
|
|
25
|
+
export type { SortableConfig } from "./features/sortable";
|
|
24
26
|
export { withAutoSize } from "./features/autosize";
|
|
25
27
|
export { createStats } from "./utils/stats";
|
|
26
28
|
export type { Stats, StatsConfig, StatsState } from "./utils/stats";
|