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