vlist 1.8.3 → 1.9.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 CHANGED
@@ -1,19 +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.0 KB.
3
+ The virtual list library for every framework. Accessible by default, batteries-included, with composable features — in 11.2 KB.
4
4
 
5
- **v1.8.3** — [Changelog](./CHANGELOG.md)
5
+ **v1.9.0** — [Changelog](./CHANGELOG.md)
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
8
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
9
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](https://github.com/floor/vlist/blob/main/LICENSE)
10
11
 
11
- - **New: 11.0 KB base** optimized from 11.2 KB, every feature bundle reduced
12
+ - **New: `withTransition()`** FLIP-based enter/exit animations for insert and remove, with batch `removeItems()` for simultaneous multi-item transitions
12
13
  - **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering, ARIA live region
13
14
  - **Zero dependencies** — framework-agnostic core with tiny adapters for Vue, Svelte, Solid, React
14
- - **11.0 KB gzipped** — composable features with perfect tree-shaking
15
+ - **11.2 KB gzipped** — composable features with perfect tree-shaking
15
16
  - **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
+ - **Grid, masonry, table, groups, async, selection, sortable, transition, scale** — all opt-in
17
18
  - **Vertical & horizontal** — single axis-neutral code path, every feature works in both orientations
18
19
 
19
20
  **18 interactive examples, docs & benchmarks → [vlist.io](https://vlist.io)**
@@ -94,19 +95,20 @@ const list = vlist({
94
95
 
95
96
  | Feature | Size | Description |
96
97
  |---------|------|-------------|
97
- | **Base** | 11.0 KB | Virtualization, ARIA, keyboard nav, gap, padding |
98
- | `withAsync()` | +4.5 KB | Lazy loading with velocity-aware fetching |
99
- | `withSelection()` | +3.0 KB | Single/multiple selection with 2D keyboard nav |
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 |
100
101
  | `withScale()` | +3.6 KB | 1M+ items via scroll compression |
101
- | `withGroups()` | +4.5 KB | Sticky/inline headers with async group discovery |
102
+ | `withGroups()` | +4.7 KB | Sticky/inline headers with async group discovery |
102
103
  | `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
103
- | `withScrollbar()` | +1.9 KB | Custom scrollbar UI |
104
+ | `withScrollbar()` | +1.8 KB | Custom scrollbar UI |
104
105
  | `withGrid()` | +4.1 KB | 2D grid layout |
105
- | `withMasonry()` | +3.5 KB | Pinterest-style masonry with lane-aware keyboard nav |
106
+ | `withMasonry()` | +3.4 KB | Pinterest-style masonry with lane-aware keyboard nav |
106
107
  | `withTable()` | +5.8 KB | Data table with columns, resize, sort |
107
108
  | `withPage()` | +0.7 KB | Window-level scrolling |
108
109
  | `withSortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
109
110
  | `withSnapshots()` | +1.2 KB | Scroll position save/restore |
111
+ | `withTransition()` | +2.1 KB | FLIP-based enter/exit animations for insert & remove |
110
112
 
111
113
  ## Examples
112
114
 
@@ -163,6 +165,30 @@ const gallery = vlist({
163
165
  .build()
164
166
  ```
165
167
 
168
+ ### Animated Insert & Remove
169
+
170
+ ```typescript
171
+ import { vlist, withTransition, withSelection } from 'vlist'
172
+
173
+ const list = vlist({
174
+ container: '#playlist',
175
+ items: tracks,
176
+ item: { height: 64, template: renderTrack },
177
+ })
178
+ .use(withTransition({ duration: 200 }))
179
+ .use(withSelection({ mode: 'multiple' }))
180
+ .build()
181
+
182
+ // Single item — collapses with fade-out, siblings slide up
183
+ list.removeItem(trackId)
184
+
185
+ // Batch — all items animate simultaneously
186
+ list.removeItems(list.getSelected())
187
+
188
+ // Insert — expands in, siblings slide down
189
+ list.insertItem({ id: 42, title: 'New Track' }, 0)
190
+ ```
191
+
166
192
  ### Async Loading
167
193
 
168
194
  ```typescript
@@ -217,7 +243,9 @@ const list = vlist(config).use(...features).build()
217
243
  | `list.appendItems(items)` | Add to end (auto-scrolls in reverse mode) |
218
244
  | `list.prependItems(items)` | Add to start (preserves scroll position) |
219
245
  | `list.updateItem(index, partial)` | Update a single item by index |
220
- | `list.removeItem(index)` | Remove by index |
246
+ | `list.insertItem(item, index?)` | Insert at index (animated with `withTransition`) |
247
+ | `list.removeItem(id)` | Remove by ID (animated with `withTransition`) |
248
+ | `list.removeItems(ids)` | Batch remove (simultaneous animations) |
221
249
  | `list.getItemAt(index)` | Get item at index |
222
250
  | `list.getIndexById(id)` | Get index by item ID |
223
251
  | `list.reload()` | Re-fetch from adapter (async) |
@@ -282,6 +310,7 @@ withTable({ columns, rowHeight, headerHeight?, resizable? })
282
310
  withAutoSize() // auto-measure items (requires estimatedHeight)
283
311
  withScale() // auto-activates at 16.7M px
284
312
  withScrollbar({ autoHide?, autoHideDelay?, minThumbSize? })
313
+ withTransition({ duration?: 200, insert?: timing, remove?: timing })
285
314
  withSortable({ handle?: '.drag-handle' }) // drag-and-drop reordering
286
315
  withPage() // no config — uses document scroll
287
316
  withSnapshots({ autoSave: 'key' }) // automatic sessionStorage save/restore
package/README.md CHANGED
@@ -1,17 +1,18 @@
1
1
  # vlist
2
2
 
3
- The virtual list library for every framework. Accessible by default, batteries-included, with composable features — in 11.0 KB.
3
+ The virtual list library for every framework. Accessible by default, batteries-included, with composable features — in 11.2 KB.
4
4
 
5
- **v1.8.2** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md)
5
+ **v1.9.0** — [Changelog](https://github.com/floor/vlist/blob/main/CHANGELOG.md)
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/vlist.svg)](https://www.npmjs.com/package/vlist)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/vlist)](https://bundlephobia.com/package/vlist)
8
9
  [![CI](https://github.com/floor/vlist/actions/workflows/ci.yml/badge.svg)](https://github.com/floor/vlist/actions/workflows/ci.yml)
9
10
  [![license](https://img.shields.io/npm/l/vlist.svg)](https://github.com/floor/vlist/blob/main/LICENSE)
10
11
 
11
- - **New: 11.0 KB base** optimized from 11.2 KB, every feature bundle reduced
12
+ - **New: `withTransition()`**FLIP-based enter/exit animations for insert and remove
12
13
  - **Zero dependencies** — framework-agnostic core, tiny adapters for Vue, Svelte, Solid, React
13
14
  - **Accessible** — WAI-ARIA, 2D keyboard navigation, focus recovery, screen-reader DOM ordering
14
- - **11.0 KB gzipped** — composable features with perfect tree-shaking
15
+ - **11.2 KB gzipped** — composable features with perfect tree-shaking
15
16
  - **Constant memory** — ~0.1 MB overhead at any scale, from 10K to 1M+ items
16
17
  - **Vertical & horizontal** — single axis-neutral code path, every feature works in both orientations
17
18
 
@@ -56,19 +57,20 @@ const list = vlist({ container: '#app', items, item: { height: 200, template: re
56
57
 
57
58
  | Feature | Size | Description |
58
59
  |---------|------|-------------|
59
- | **Base** | 11.0 KB | Virtualization, ARIA, keyboard nav, gap, padding |
60
- | `withAsync()` | +4.5 KB | Lazy loading with velocity-aware fetching |
61
- | `withSelection()` | +3.0 KB | Single/multiple selection with 2D keyboard nav |
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 |
62
63
  | `withScale()` | +3.6 KB | 1M+ items via scroll compression |
63
- | `withGroups()` | +4.5 KB | Sticky/inline headers with async group discovery |
64
+ | `withGroups()` | +4.7 KB | Sticky/inline headers with async group discovery |
64
65
  | `withAutoSize()` | +0.9 KB | Auto-measure items via ResizeObserver |
65
- | `withScrollbar()` | +1.9 KB | Custom scrollbar UI |
66
+ | `withScrollbar()` | +1.8 KB | Custom scrollbar UI |
66
67
  | `withGrid()` | +4.1 KB | 2D grid layout |
67
- | `withMasonry()` | +3.5 KB | Pinterest-style masonry with lane-aware keyboard nav |
68
+ | `withMasonry()` | +3.4 KB | Pinterest-style masonry with lane-aware keyboard nav |
68
69
  | `withTable()` | +5.8 KB | Data table with columns, resize, sort |
69
70
  | `withPage()` | +0.7 KB | Window-level scrolling |
70
71
  | `withSortable()` | +2.9 KB | Drag-and-drop reordering with auto-scroll |
71
72
  | `withSnapshots()` | +1.2 KB | Scroll position save/restore |
73
+ | `withTransition()` | +2.1 KB | FLIP-based enter/exit animations for insert & remove |
72
74
 
73
75
  ## Framework Adapters
74
76
 
@@ -42,6 +42,7 @@ export interface SimpleDataManager<T extends VListItem = VListItem> {
42
42
  setTotal: (total: number) => void;
43
43
  setItems: (items: T[], offset?: number, total?: number) => void;
44
44
  updateItem: (index: number, updates: Partial<T>) => boolean;
45
+ insertItem: (item: T, index: number) => void;
45
46
  removeItem: (id: string | number) => boolean;
46
47
  loadRange: (start: number, end: number) => Promise<void>;
47
48
  ensureRange: (start: number, end: number) => Promise<void>;
@@ -481,7 +481,11 @@ export interface VList<T extends VListItem = VListItem> {
481
481
  appendItems: (items: T[]) => void;
482
482
  prependItems: (items: T[]) => void;
483
483
  updateItem: (id: string | number, updates: Partial<T>) => void;
484
+ insertItem: (item: T, index?: number) => void;
485
+ /** @deprecated Use `insertItem` instead. */
486
+ addItem: (item: T, index?: number) => void;
484
487
  removeItem: (id: string | number) => void;
488
+ removeItems: (ids: ReadonlyArray<string | number>) => number;
485
489
  getItemAt: (index: number) => T | undefined;
486
490
  getIndexById: (id: string | number) => number;
487
491
  reload: (options?: ReloadOptions) => Promise<void>;
@@ -75,6 +75,8 @@ export interface DataManager<T extends VListItem = VListItem> {
75
75
  setItems: (items: T[], offset?: number, total?: number) => void;
76
76
  /** Update item at index */
77
77
  updateItem: (index: number, updates: Partial<T>) => boolean;
78
+ /** Insert item at index */
79
+ insertItem: (item: T, index: number) => void;
78
80
  /** Remove item by ID */
79
81
  removeItem: (id: string | number) => boolean;
80
82
  /** Load items for a range */
@@ -45,6 +45,8 @@ export interface SparseStorage<T extends VListItem = VListItem> {
45
45
  set: (index: number, item: T) => void;
46
46
  /** Set multiple items starting at offset */
47
47
  setRange: (offset: number, items: T[]) => void;
48
+ /** Insert item at index, shifting subsequent items up by 1 */
49
+ insert: (index: number, item: T) => void;
48
50
  /** Delete item at index */
49
51
  delete: (index: number) => boolean;
50
52
  /** Get items in range (includes undefined for unloaded) */
@@ -57,8 +59,10 @@ export interface SparseStorage<T extends VListItem = VListItem> {
57
59
  findUnloadedRanges: (start: number, end: number) => Range[];
58
60
  /** Get chunk index for item index */
59
61
  getChunkIndex: (itemIndex: number) => number;
60
- /** Check if chunk is loaded */
62
+ /** Check if chunk is loaded (has any items) */
61
63
  isChunkLoaded: (chunkIndex: number) => boolean;
64
+ /** Check if chunk has all expected items (count matches expected for total) */
65
+ isChunkFullyLoaded: (chunkIndex: number) => boolean;
62
66
  /** Mark chunk as accessed (for LRU) */
63
67
  touchChunk: (chunkIndex: number) => void;
64
68
  /** Mark all chunks in a range as accessed with a single Date.now() call */
@@ -48,6 +48,8 @@ export interface AsyncGroupBridge {
48
48
  getGroupAtLayoutIndex(layoutIndex: number): GroupBoundary;
49
49
  /** Get the group boundary at a data index */
50
50
  getGroupAtDataIndex(dataIndex: number): GroupBoundary;
51
+ /** Insert item at data index — shifts group keys up and rebuilds */
52
+ insertAt(dataIndex: number, item: VListItem): void;
51
53
  /** Remove item at data index — shifts group keys and rebuilds */
52
54
  removeAt(dataIndex: number): void;
53
55
  /** Reset all state (e.g. on reload) */
@@ -0,0 +1,30 @@
1
+ /**
2
+ * withTransition — FLIP-based enter/exit animations for insertItem and removeItem.
3
+ *
4
+ * Opt-in feature that replaces the immediate insert/remove with animated versions:
5
+ * - removeItem: clone collapses via scaleY(0), siblings slide up
6
+ * - insertItem: new element expands in, siblings slide down
7
+ *
8
+ * Without this feature, insert/remove are instantaneous.
9
+ */
10
+ import type { VListItem } from "../../types";
11
+ import type { VListFeature } from "../../builder/types";
12
+ /** Per-animation timing overrides. */
13
+ export interface TransitionTiming {
14
+ /** Duration in ms */
15
+ duration?: number;
16
+ /** CSS easing function */
17
+ easing?: string;
18
+ }
19
+ export interface TransitionConfig {
20
+ /** Shared duration in ms (default: 200). Overridden by add/remove sub-configs. */
21
+ duration?: number;
22
+ /** Shared CSS easing (default: MD3 emphasized). Overridden by add/remove sub-configs. */
23
+ easing?: string;
24
+ /** Insert animation config, or `false` to disable. */
25
+ insert?: TransitionTiming | false;
26
+ /** Remove animation config, or `false` to disable. */
27
+ remove?: TransitionTiming | false;
28
+ }
29
+ export declare function withTransition<T extends VListItem = VListItem>(config?: TransitionConfig): VListFeature<T>;
30
+ //# sourceMappingURL=feature.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * vlist/transition — FLIP-based enter/exit animations
3
+ * Opt-in animated add/remove for smooth list transitions
4
+ *
5
+ * Usage: import { withTransition } from 'vlist'
6
+ */
7
+ export { withTransition } from "./feature";
8
+ export type { TransitionConfig, TransitionTiming } from "./feature";
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export { withTable } from "./features/table";
24
24
  export { withSortable } from "./features/sortable";
25
25
  export type { SortableConfig } from "./features/sortable";
26
26
  export { withAutoSize } from "./features/autosize";
27
+ export { withTransition } from "./features/transition";
28
+ export type { TransitionConfig, TransitionTiming } from "./features/transition";
27
29
  export { createStats } from "./utils/stats";
28
30
  export type { Stats, StatsConfig, StatsState } from "./utils/stats";
29
31
  export type { VListItem, VListEvents, ItemConfig, ItemTemplate, ItemState, SelectionMode, SelectionConfig, SelectionState, ScrollbarConfig, ScrollbarPadding, ScrollbarOptions, ScrollConfig, ScrollToOptions, ScrollSnapshot, VListAdapter, AdapterParams, AdapterResponse, Range, ViewportState, EventHandler, Unsubscribe, GridConfig, MasonryConfig, GroupsConfig, GroupHeaderConfig, GridSizeContext, GridHeightContext, } from "./types";