@floor/vlist 0.5.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/LICENSE +21 -0
- package/README.md +839 -0
- package/dist/adapters/index.d.ts +20 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/react.d.ts +119 -0
- package/dist/adapters/react.d.ts.map +1 -0
- package/dist/adapters/svelte.d.ts +198 -0
- package/dist/adapters/svelte.d.ts.map +1 -0
- package/dist/adapters/vue.d.ts +151 -0
- package/dist/adapters/vue.d.ts.map +1 -0
- package/dist/builder/context.d.ts +36 -0
- package/dist/builder/context.d.ts.map +1 -0
- package/dist/builder/core.d.ts +16 -0
- package/dist/builder/core.d.ts.map +1 -0
- package/dist/builder/data.d.ts +71 -0
- package/dist/builder/data.d.ts.map +1 -0
- package/dist/builder/index.d.ts +25 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +1 -0
- package/dist/builder/types.d.ts +269 -0
- package/dist/builder/types.d.ts.map +1 -0
- package/dist/compression/index.js +1 -0
- package/dist/constants.d.ts +65 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core-light.d.ts +104 -0
- package/dist/core-light.d.ts.map +1 -0
- package/dist/core-light.js +1 -0
- package/dist/core.d.ts +129 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/data/index.js +1 -0
- package/dist/events/emitter.d.ts +20 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/index.d.ts +6 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/grid/index.js +1 -0
- package/dist/groups/index.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/plugins/compression/index.d.ts +10 -0
- package/dist/plugins/compression/index.d.ts.map +1 -0
- package/dist/plugins/compression/plugin.d.ts +42 -0
- package/dist/plugins/compression/plugin.d.ts.map +1 -0
- package/dist/plugins/data/index.d.ts +9 -0
- package/dist/plugins/data/index.d.ts.map +1 -0
- package/dist/plugins/data/manager.d.ts +103 -0
- package/dist/plugins/data/manager.d.ts.map +1 -0
- package/dist/plugins/data/placeholder.d.ts +62 -0
- package/dist/plugins/data/placeholder.d.ts.map +1 -0
- package/dist/plugins/data/plugin.d.ts +60 -0
- package/dist/plugins/data/plugin.d.ts.map +1 -0
- package/dist/plugins/data/sparse.d.ts +91 -0
- package/dist/plugins/data/sparse.d.ts.map +1 -0
- package/dist/plugins/grid/index.d.ts +9 -0
- package/dist/plugins/grid/index.d.ts.map +1 -0
- package/dist/plugins/grid/layout.d.ts +29 -0
- package/dist/plugins/grid/layout.d.ts.map +1 -0
- package/dist/plugins/grid/plugin.d.ts +48 -0
- package/dist/plugins/grid/plugin.d.ts.map +1 -0
- package/dist/plugins/grid/renderer.d.ts +55 -0
- package/dist/plugins/grid/renderer.d.ts.map +1 -0
- package/dist/plugins/grid/types.d.ts +71 -0
- package/dist/plugins/grid/types.d.ts.map +1 -0
- package/dist/plugins/groups/index.d.ts +10 -0
- package/dist/plugins/groups/index.d.ts.map +1 -0
- package/dist/plugins/groups/layout.d.ts +46 -0
- package/dist/plugins/groups/layout.d.ts.map +1 -0
- package/dist/plugins/groups/plugin.d.ts +63 -0
- package/dist/plugins/groups/plugin.d.ts.map +1 -0
- package/dist/plugins/groups/sticky.d.ts +33 -0
- package/dist/plugins/groups/sticky.d.ts.map +1 -0
- package/dist/plugins/groups/types.d.ts +86 -0
- package/dist/plugins/groups/types.d.ts.map +1 -0
- package/dist/plugins/scroll/controller.d.ts +121 -0
- package/dist/plugins/scroll/controller.d.ts.map +1 -0
- package/dist/plugins/scroll/index.d.ts +8 -0
- package/dist/plugins/scroll/index.d.ts.map +1 -0
- package/dist/plugins/scroll/plugin.d.ts +60 -0
- package/dist/plugins/scroll/plugin.d.ts.map +1 -0
- package/dist/plugins/scroll/scrollbar.d.ts +73 -0
- package/dist/plugins/scroll/scrollbar.d.ts.map +1 -0
- package/dist/plugins/selection/index.d.ts +7 -0
- package/dist/plugins/selection/index.d.ts.map +1 -0
- package/dist/plugins/selection/plugin.d.ts +44 -0
- package/dist/plugins/selection/plugin.d.ts.map +1 -0
- package/dist/plugins/selection/state.d.ts +102 -0
- package/dist/plugins/selection/state.d.ts.map +1 -0
- package/dist/plugins/snapshots/index.d.ts +8 -0
- package/dist/plugins/snapshots/index.d.ts.map +1 -0
- package/dist/plugins/snapshots/plugin.d.ts +44 -0
- package/dist/plugins/snapshots/plugin.d.ts.map +1 -0
- package/dist/plugins/window/index.d.ts +8 -0
- package/dist/plugins/window/index.d.ts.map +1 -0
- package/dist/plugins/window/plugin.d.ts +53 -0
- package/dist/plugins/window/plugin.d.ts.map +1 -0
- package/dist/react/index.js +1 -0
- package/dist/render/compression.d.ts +116 -0
- package/dist/render/compression.d.ts.map +1 -0
- package/dist/render/heights.d.ts +63 -0
- package/dist/render/heights.d.ts.map +1 -0
- package/dist/render/index.d.ts +9 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/renderer.d.ts +103 -0
- package/dist/render/renderer.d.ts.map +1 -0
- package/dist/render/virtual.d.ts +139 -0
- package/dist/render/virtual.d.ts.map +1 -0
- package/dist/scroll/index.js +1 -0
- package/dist/selection/index.js +1 -0
- package/dist/snapshots/index.js +1 -0
- package/dist/svelte/index.js +1 -0
- package/dist/types.d.ts +559 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vlist-extras.css +1 -0
- package/dist/vlist.css +1 -0
- package/dist/vlist.d.ts +22 -0
- package/dist/vlist.d.ts.map +1 -0
- package/dist/vue/index.js +1 -0
- package/dist/window/index.js +1 -0
- package/package.json +137 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist/groups - Builder Plugin
|
|
3
|
+
* Adds grouped lists with sticky section headers.
|
|
4
|
+
*
|
|
5
|
+
* Priority: 10 (runs first — transforms item list and height function before rendering)
|
|
6
|
+
*
|
|
7
|
+
* What it wires:
|
|
8
|
+
* - Transforms item list — inserts header items at group boundaries
|
|
9
|
+
* - Replaces height function — headers use headerHeight, data items use configured item.height
|
|
10
|
+
* - Unified template — dispatches to headerTemplate for headers, user template for items
|
|
11
|
+
* - Sticky header DOM — creates a positioned header element that updates as you scroll
|
|
12
|
+
* - Index mapping — translates between data indices and layout indices
|
|
13
|
+
* - CSS class — adds .vlist--grouped to the root element
|
|
14
|
+
*
|
|
15
|
+
* Restrictions:
|
|
16
|
+
* - Items must be pre-sorted by group
|
|
17
|
+
* - Cannot be combined with direction: 'horizontal'
|
|
18
|
+
* - Cannot be combined with reverse: true
|
|
19
|
+
*
|
|
20
|
+
* Can be combined with withGrid for grouped 2D layouts.
|
|
21
|
+
*/
|
|
22
|
+
import type { VListItem } from "../../types";
|
|
23
|
+
import type { VListPlugin } from "../../builder/types";
|
|
24
|
+
/** Groups plugin configuration */
|
|
25
|
+
export interface GroupsPluginConfig {
|
|
26
|
+
/** Returns group key for item at index (required) */
|
|
27
|
+
getGroupForIndex: (index: number) => string;
|
|
28
|
+
/** Height of group headers in pixels (required) */
|
|
29
|
+
headerHeight: number;
|
|
30
|
+
/** Render function for headers (required) */
|
|
31
|
+
headerTemplate: (key: string, groupIndex: number) => HTMLElement | string;
|
|
32
|
+
/** Enable sticky headers — iOS Contacts style (default: true) */
|
|
33
|
+
sticky?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a groups plugin for the builder.
|
|
37
|
+
*
|
|
38
|
+
* Adds grouped lists with sticky section headers.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { vlist } from 'vlist/builder'
|
|
42
|
+
* import { withGroups } from 'vlist/groups'
|
|
43
|
+
*
|
|
44
|
+
* const contacts = vlist({
|
|
45
|
+
* container: '#contacts',
|
|
46
|
+
* item: { height: 56, template: renderContact },
|
|
47
|
+
* items: sortedContacts,
|
|
48
|
+
* })
|
|
49
|
+
* .use(withGroups({
|
|
50
|
+
* getGroupForIndex: (i) => sortedContacts[i].lastName[0],
|
|
51
|
+
* headerHeight: 32,
|
|
52
|
+
* headerTemplate: (letter) => {
|
|
53
|
+
* const el = document.createElement('div')
|
|
54
|
+
* el.className = 'letter-header'
|
|
55
|
+
* el.textContent = letter
|
|
56
|
+
* return el
|
|
57
|
+
* },
|
|
58
|
+
* }))
|
|
59
|
+
* .build()
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare const withGroups: <T extends VListItem = VListItem>(config: GroupsPluginConfig) => VListPlugin<T>;
|
|
63
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/groups/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,qBAAqB,CAAC;AAuBvE,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAE5C,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IAErB,6CAA6C;IAC7C,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,WAAW,GAAG,MAAM,CAAC;IAE1E,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EACxD,QAAQ,kBAAkB,KACzB,WAAW,CAAC,CAAC,CAoQf,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Sticky Header
|
|
3
|
+
* Manages a floating header element that "sticks" to the top of the viewport
|
|
4
|
+
* and transitions smoothly when the next group's header approaches.
|
|
5
|
+
*
|
|
6
|
+
* The sticky header is a separate DOM element positioned absolutely at the
|
|
7
|
+
* top of the root container. It overlays the scrolling content and shows
|
|
8
|
+
* the current group's header. When the next group's inline header scrolls
|
|
9
|
+
* into view, the sticky header is pushed upward to create the classic
|
|
10
|
+
* iOS Contacts-style transition effect.
|
|
11
|
+
*
|
|
12
|
+
* Layout:
|
|
13
|
+
* .vlist (root, position: relative)
|
|
14
|
+
* ├── .vlist-sticky-header (position: absolute, top: 0, z-index: 5)
|
|
15
|
+
* │ └── (content rendered by headerTemplate)
|
|
16
|
+
* └── .vlist-viewport
|
|
17
|
+
* └── .vlist-content
|
|
18
|
+
* └── .vlist-items
|
|
19
|
+
*/
|
|
20
|
+
import type { GroupLayout, GroupsConfig, StickyHeader } from "./types";
|
|
21
|
+
import type { HeightCache } from "../../render/heights";
|
|
22
|
+
/**
|
|
23
|
+
* Create a sticky header manager.
|
|
24
|
+
*
|
|
25
|
+
* @param root - The vlist root element (.vlist) — sticky header is appended here
|
|
26
|
+
* @param layout - Group layout for index/group resolution
|
|
27
|
+
* @param heightCache - The LAYOUT height cache (includes headers)
|
|
28
|
+
* @param config - Groups configuration (headerTemplate, headerHeight)
|
|
29
|
+
* @param classPrefix - CSS class prefix (default: 'vlist')
|
|
30
|
+
* @returns StickyHeader instance
|
|
31
|
+
*/
|
|
32
|
+
export declare const createStickyHeader: (root: HTMLElement, layout: GroupLayout, heightCache: HeightCache, config: GroupsConfig, classPrefix: string) => StickyHeader;
|
|
33
|
+
//# sourceMappingURL=sticky.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky.d.ts","sourceRoot":"","sources":["../../../src/plugins/groups/sticky.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMxD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,WAAW,EACjB,QAAQ,WAAW,EACnB,aAAa,WAAW,EACxB,QAAQ,YAAY,EACpB,aAAa,MAAM,KAClB,YAgMF,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Group Types
|
|
3
|
+
* Types for sticky headers and grouped lists
|
|
4
|
+
*/
|
|
5
|
+
import type { VListItem, GroupsConfig } from "../../types";
|
|
6
|
+
export type { GroupsConfig };
|
|
7
|
+
/** A single group boundary */
|
|
8
|
+
export interface GroupBoundary {
|
|
9
|
+
/** Group key (return value of getGroupForIndex) */
|
|
10
|
+
key: string;
|
|
11
|
+
/** Sequential group index (0-based) */
|
|
12
|
+
groupIndex: number;
|
|
13
|
+
/** Layout index of this group's header */
|
|
14
|
+
headerLayoutIndex: number;
|
|
15
|
+
/** Data index of the first item in this group */
|
|
16
|
+
firstDataIndex: number;
|
|
17
|
+
/** Number of data items in this group */
|
|
18
|
+
count: number;
|
|
19
|
+
}
|
|
20
|
+
/** Entry type in the layout — either a group header or a data item */
|
|
21
|
+
export type LayoutEntry = {
|
|
22
|
+
type: "header";
|
|
23
|
+
group: GroupBoundary;
|
|
24
|
+
} | {
|
|
25
|
+
type: "item";
|
|
26
|
+
dataIndex: number;
|
|
27
|
+
group: GroupBoundary;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Internal marker for group header pseudo-items inserted into the layout.
|
|
31
|
+
* These are interleaved with real data items in the transformed items array.
|
|
32
|
+
*/
|
|
33
|
+
export interface GroupHeaderItem extends VListItem {
|
|
34
|
+
/** Always `__group_header_{groupIndex}` */
|
|
35
|
+
id: string;
|
|
36
|
+
/** Discriminator flag */
|
|
37
|
+
__groupHeader: true;
|
|
38
|
+
/** The group key */
|
|
39
|
+
groupKey: string;
|
|
40
|
+
/** Sequential group index (0-based) */
|
|
41
|
+
groupIndex: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Type guard: check if an item is a group header pseudo-item
|
|
45
|
+
*/
|
|
46
|
+
export declare const isGroupHeader: (item: unknown) => item is GroupHeaderItem;
|
|
47
|
+
/** Group layout — maps between data indices and layout indices */
|
|
48
|
+
export interface GroupLayout {
|
|
49
|
+
/** Total layout entries (data items + group headers) */
|
|
50
|
+
readonly totalEntries: number;
|
|
51
|
+
/** Number of groups */
|
|
52
|
+
readonly groupCount: number;
|
|
53
|
+
/** All group boundaries, in order */
|
|
54
|
+
readonly groups: readonly GroupBoundary[];
|
|
55
|
+
/** Get the layout entry at a layout index — O(log g) */
|
|
56
|
+
getEntry: (layoutIndex: number) => LayoutEntry;
|
|
57
|
+
/** Map layout index → data index, or -1 if it's a header — O(log g) */
|
|
58
|
+
layoutToDataIndex: (layoutIndex: number) => number;
|
|
59
|
+
/** Map data index → layout index — O(log g) */
|
|
60
|
+
dataToLayoutIndex: (dataIndex: number) => number;
|
|
61
|
+
/** Get the group boundary that contains a given layout index — O(log g) */
|
|
62
|
+
getGroupAtLayoutIndex: (layoutIndex: number) => GroupBoundary;
|
|
63
|
+
/** Get the group boundary that contains a given data index — O(log g) */
|
|
64
|
+
getGroupAtDataIndex: (dataIndex: number) => GroupBoundary;
|
|
65
|
+
/** Get header height for a group */
|
|
66
|
+
getHeaderHeight: (groupIndex: number) => number;
|
|
67
|
+
/**
|
|
68
|
+
* Rebuild the layout from scratch.
|
|
69
|
+
* Call when items change (setItems, append, prepend, remove, etc.)
|
|
70
|
+
*/
|
|
71
|
+
rebuild: (itemCount: number) => void;
|
|
72
|
+
}
|
|
73
|
+
/** Sticky header manager */
|
|
74
|
+
export interface StickyHeader {
|
|
75
|
+
/** Update sticky header position and content based on scroll position */
|
|
76
|
+
update: (scrollTop: number) => void;
|
|
77
|
+
/** Force refresh the sticky header content (e.g. after items change) */
|
|
78
|
+
refresh: () => void;
|
|
79
|
+
/** Show the sticky header */
|
|
80
|
+
show: () => void;
|
|
81
|
+
/** Hide the sticky header */
|
|
82
|
+
hide: () => void;
|
|
83
|
+
/** Destroy and remove the sticky header DOM element */
|
|
84
|
+
destroy: () => void;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/plugins/groups/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3D,YAAY,EAAE,YAAY,EAAE,CAAC;AAM7B,8BAA8B;AAC9B,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IAEZ,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IAEnB,0CAA0C;IAC1C,iBAAiB,EAAE,MAAM,CAAC;IAE1B,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IAEvB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sEAAsE;AACtE,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAM9D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IAEX,yBAAyB;IACzB,aAAa,EAAE,IAAI,CAAC;IAEpB,oBAAoB;IACpB,QAAQ,EAAE,MAAM,CAAC;IAEjB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,OAAO,KAAG,IAAI,IAAI,eAMrD,CAAC;AAMF,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,uBAAuB;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAE1C,wDAAwD;IACxD,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,WAAW,CAAC;IAE/C,uEAAuE;IACvE,iBAAiB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,CAAC;IAEnD,+CAA+C;IAC/C,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjD,2EAA2E;IAC3E,qBAAqB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,aAAa,CAAC;IAE9D,yEAAyE;IACzE,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,aAAa,CAAC;IAE1D,oCAAoC;IACpC,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAEhD;;;OAGG;IACH,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAMD,4BAA4B;AAC5B,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAEpC,wEAAwE;IACxE,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,uDAAuD;IACvD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Scroll Controller
|
|
3
|
+
* Handles both native scrolling and manual wheel-based scrolling for compressed lists
|
|
4
|
+
*
|
|
5
|
+
* When compression is active (large lists exceeding browser height limits),
|
|
6
|
+
* we switch from native scrolling to manual wheel event handling.
|
|
7
|
+
* This allows smooth scrolling through millions of items.
|
|
8
|
+
*/
|
|
9
|
+
import type { CompressionState } from "../../render/compression";
|
|
10
|
+
/** Scroll direction */
|
|
11
|
+
export type ScrollDirection = "up" | "down";
|
|
12
|
+
/** Scroll event data */
|
|
13
|
+
export interface ScrollEventData {
|
|
14
|
+
scrollTop: number;
|
|
15
|
+
direction: ScrollDirection;
|
|
16
|
+
velocity: number;
|
|
17
|
+
}
|
|
18
|
+
/** Scroll controller configuration */
|
|
19
|
+
export interface ScrollControllerConfig {
|
|
20
|
+
/** Enable compressed scroll mode (manual wheel handling) */
|
|
21
|
+
compressed?: boolean;
|
|
22
|
+
/** Compression state for calculating bounds */
|
|
23
|
+
compression?: CompressionState;
|
|
24
|
+
/**
|
|
25
|
+
* External scroll element for window/document scrolling.
|
|
26
|
+
* When set, the controller listens to this element's scroll events
|
|
27
|
+
* and computes list-relative positions from the viewport's bounding rect.
|
|
28
|
+
*/
|
|
29
|
+
scrollElement?: Window;
|
|
30
|
+
/** Allow mouse wheel to scroll (default: true) */
|
|
31
|
+
wheel?: boolean;
|
|
32
|
+
/** Wheel sensitivity multiplier (default: 1) */
|
|
33
|
+
sensitivity?: number;
|
|
34
|
+
/** Enable smooth scrolling interpolation */
|
|
35
|
+
smoothing?: boolean;
|
|
36
|
+
/** Scroll idle detection timeout in ms (default: 150) */
|
|
37
|
+
idleTimeout?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Enable horizontal scrolling mode.
|
|
40
|
+
* When true, the controller reads scrollLeft instead of scrollTop,
|
|
41
|
+
* uses clientWidth instead of clientHeight, and maps wheel deltaX.
|
|
42
|
+
*/
|
|
43
|
+
horizontal?: boolean;
|
|
44
|
+
/** Callback when scroll position changes */
|
|
45
|
+
onScroll?: (data: ScrollEventData) => void;
|
|
46
|
+
/** Callback when scrolling becomes idle */
|
|
47
|
+
onIdle?: () => void;
|
|
48
|
+
}
|
|
49
|
+
/** Scroll controller instance */
|
|
50
|
+
export interface ScrollController {
|
|
51
|
+
/** Get current scroll position */
|
|
52
|
+
getScrollTop: () => number;
|
|
53
|
+
/** Set scroll position */
|
|
54
|
+
scrollTo: (position: number, smooth?: boolean) => void;
|
|
55
|
+
/** Scroll by delta */
|
|
56
|
+
scrollBy: (delta: number) => void;
|
|
57
|
+
/** Check if at top */
|
|
58
|
+
isAtTop: () => boolean;
|
|
59
|
+
/** Check if at bottom */
|
|
60
|
+
isAtBottom: (threshold?: number) => boolean;
|
|
61
|
+
/** Get scroll percentage (0-1) */
|
|
62
|
+
getScrollPercentage: () => number;
|
|
63
|
+
/** Get current scroll velocity (px/ms, absolute value) */
|
|
64
|
+
getVelocity: () => number;
|
|
65
|
+
/**
|
|
66
|
+
* Check if the velocity tracker is actively tracking with enough samples.
|
|
67
|
+
* Returns false during ramp-up (first few frames of a new scroll gesture)
|
|
68
|
+
* when the tracker doesn't have enough samples yet.
|
|
69
|
+
*/
|
|
70
|
+
isTracking: () => boolean;
|
|
71
|
+
/** Check if currently scrolling */
|
|
72
|
+
isScrolling: () => boolean;
|
|
73
|
+
/** Update configuration (e.g., when compression state changes) */
|
|
74
|
+
updateConfig: (config: Partial<ScrollControllerConfig>) => void;
|
|
75
|
+
/** Enable compressed mode */
|
|
76
|
+
enableCompression: (compression: CompressionState) => void;
|
|
77
|
+
/** Disable compressed mode (revert to native scroll) */
|
|
78
|
+
disableCompression: () => void;
|
|
79
|
+
/** Check if compressed mode is active */
|
|
80
|
+
isCompressed: () => boolean;
|
|
81
|
+
/** Check if in window scroll mode */
|
|
82
|
+
isWindowMode: () => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Update the container height used for scroll calculations.
|
|
85
|
+
* In window mode, call this when the window resizes.
|
|
86
|
+
*/
|
|
87
|
+
updateContainerHeight: (height: number) => void;
|
|
88
|
+
/** Destroy and cleanup */
|
|
89
|
+
destroy: () => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a scroll controller for a viewport element
|
|
93
|
+
*
|
|
94
|
+
* Supports two modes:
|
|
95
|
+
* 1. Native scrolling (default) - uses browser's built-in scroll
|
|
96
|
+
* 2. Compressed scrolling - manual wheel handling for large lists
|
|
97
|
+
*/
|
|
98
|
+
export declare const createScrollController: (viewport: HTMLElement, config?: ScrollControllerConfig) => ScrollController;
|
|
99
|
+
/**
|
|
100
|
+
* Throttle scroll handler using requestAnimationFrame
|
|
101
|
+
*/
|
|
102
|
+
export declare const rafThrottle: <T extends (...args: any[]) => void>(fn: T) => ((...args: Parameters<T>) => void) & {
|
|
103
|
+
cancel: () => void;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Check if scroll position is at bottom
|
|
107
|
+
*/
|
|
108
|
+
export declare const isAtBottom: (scrollTop: number, scrollHeight: number, clientHeight: number, threshold?: number) => boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Check if scroll position is at top
|
|
111
|
+
*/
|
|
112
|
+
export declare const isAtTop: (scrollTop: number, threshold?: number) => boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Get scroll percentage (0-1)
|
|
115
|
+
*/
|
|
116
|
+
export declare const getScrollPercentage: (scrollTop: number, scrollHeight: number, clientHeight: number) => number;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a range is visible in the scroll viewport
|
|
119
|
+
*/
|
|
120
|
+
export declare const isRangeVisible: (rangeStart: number, rangeEnd: number, visibleStart: number, visibleEnd: number) => boolean;
|
|
121
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/plugins/scroll/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Scroll Domain
|
|
3
|
+
* Scroll controller and custom scrollbar
|
|
4
|
+
*/
|
|
5
|
+
export { withScrollbar, type ScrollbarPluginConfig } from "./plugin";
|
|
6
|
+
export { createScrollController, rafThrottle, isAtBottom, isAtTop, getScrollPercentage, isRangeVisible, type ScrollController, type ScrollControllerConfig, type ScrollEventData, type ScrollDirection, } from "./controller";
|
|
7
|
+
export { createScrollbar, type Scrollbar, type ScrollbarConfig, type ScrollCallback, } from "./scrollbar";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/scroll/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGrE,OAAO,EACL,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist/scroll - Builder Plugin
|
|
3
|
+
* Wraps the custom scrollbar into a VListPlugin for the composable builder.
|
|
4
|
+
*
|
|
5
|
+
* Priority: 30 (runs after renderer/data setup but before selection)
|
|
6
|
+
*
|
|
7
|
+
* What it wires:
|
|
8
|
+
* - DOM elements — track, thumb, and optional hover zone appended to viewport
|
|
9
|
+
* - CSS class — .vlist-viewport--custom-scrollbar hides native scrollbar
|
|
10
|
+
* - Drag handlers — mousedown on thumb, mousemove/mouseup on document
|
|
11
|
+
* - Track click — click on track to jump to position
|
|
12
|
+
* - Hover handlers — mouseenter/leave on track, hover zone, and viewport
|
|
13
|
+
* - Scroll sync — updates thumb position on every scroll frame
|
|
14
|
+
* - Resize sync — updates thumb size when container or content height changes
|
|
15
|
+
*
|
|
16
|
+
* No public methods are added — the scrollbar is entirely automatic.
|
|
17
|
+
*/
|
|
18
|
+
import type { VListItem } from "../../types";
|
|
19
|
+
import type { VListPlugin } from "../../builder/types";
|
|
20
|
+
/** Scrollbar plugin configuration */
|
|
21
|
+
export interface ScrollbarPluginConfig {
|
|
22
|
+
/** Auto-hide scrollbar after idle (default: true) */
|
|
23
|
+
autoHide?: boolean;
|
|
24
|
+
/** Auto-hide delay in milliseconds (default: 1000) */
|
|
25
|
+
autoHideDelay?: number;
|
|
26
|
+
/** Minimum thumb size in pixels (default: 30) */
|
|
27
|
+
minThumbSize?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Show scrollbar when hovering near the scrollbar edge (default: true).
|
|
30
|
+
* When true, an invisible hover zone is placed along the scrollbar edge.
|
|
31
|
+
*/
|
|
32
|
+
showOnHover?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Width of the invisible hover zone in pixels (default: 16).
|
|
35
|
+
* Only used when `showOnHover` is true.
|
|
36
|
+
*/
|
|
37
|
+
hoverZoneWidth?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Show scrollbar when the mouse enters the list viewport (default: true).
|
|
40
|
+
* When false, the scrollbar only appears on scroll or hover near edge.
|
|
41
|
+
*/
|
|
42
|
+
showOnViewportEnter?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a scrollbar plugin for the builder.
|
|
46
|
+
*
|
|
47
|
+
* Replaces the native browser scrollbar with a custom, cross-browser
|
|
48
|
+
* consistent scrollbar.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { vlist } from 'vlist/builder'
|
|
52
|
+
* import { withScrollbar } from 'vlist/scroll'
|
|
53
|
+
*
|
|
54
|
+
* const list = vlist({ ... })
|
|
55
|
+
* .use(withScrollbar({ autoHide: true, autoHideDelay: 1000 }))
|
|
56
|
+
* .build()
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare const withScrollbar: <T extends VListItem = VListItem>(config?: ScrollbarPluginConfig) => VListPlugin<T>;
|
|
60
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/scroll/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,qBAAqB,CAAC;AAQvE,qCAAqC;AACrC,MAAM,WAAW,qBAAqB;IACpC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAC3D,SAAS,qBAAqB,KAC7B,WAAW,CAAC,CAAC,CAgFf,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Custom Scrollbar
|
|
3
|
+
* Provides visual scroll indication for compressed mode where native scrollbar is hidden
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Visual track and thumb
|
|
7
|
+
* - Thumb size proportional to visible content
|
|
8
|
+
* - Click on track to jump to position
|
|
9
|
+
* - Drag thumb to scroll
|
|
10
|
+
* - Auto-hide after idle (optional)
|
|
11
|
+
* - Show on hover with configurable hover zone (optional)
|
|
12
|
+
* - CSS variables for customization
|
|
13
|
+
* - Horizontal mode support (direction-aware axis)
|
|
14
|
+
*/
|
|
15
|
+
/** Scrollbar configuration */
|
|
16
|
+
export interface ScrollbarConfig {
|
|
17
|
+
/** Enable scrollbar (default: true when compressed) */
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/** Auto-hide scrollbar after idle (default: true) */
|
|
20
|
+
autoHide?: boolean;
|
|
21
|
+
/** Auto-hide delay in milliseconds (default: 1000) */
|
|
22
|
+
autoHideDelay?: number;
|
|
23
|
+
/** Minimum thumb size in pixels (default: 30) */
|
|
24
|
+
minThumbSize?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Show scrollbar when hovering near the scrollbar edge (default: true).
|
|
27
|
+
* When true, an invisible hover zone is placed along the scrollbar edge.
|
|
28
|
+
* Moving the mouse into this zone reveals the scrollbar; it stays visible
|
|
29
|
+
* as long as the cursor remains over the zone or the track.
|
|
30
|
+
*/
|
|
31
|
+
showOnHover?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Width of the invisible hover zone in pixels (default: 16).
|
|
34
|
+
* Only used when `showOnHover` is true.
|
|
35
|
+
* A wider zone makes the scrollbar easier to discover;
|
|
36
|
+
* a narrower zone avoids interference with content near the edge.
|
|
37
|
+
*/
|
|
38
|
+
hoverZoneWidth?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Show scrollbar when the mouse enters the list viewport (default: true).
|
|
41
|
+
* When false, the scrollbar only appears on scroll or when hovering
|
|
42
|
+
* near the scrollbar edge (if `showOnHover` is true).
|
|
43
|
+
*/
|
|
44
|
+
showOnViewportEnter?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Scrollbar instance */
|
|
47
|
+
export interface Scrollbar {
|
|
48
|
+
/** Show the scrollbar */
|
|
49
|
+
show: () => void;
|
|
50
|
+
/** Hide the scrollbar */
|
|
51
|
+
hide: () => void;
|
|
52
|
+
/** Update scrollbar dimensions based on content/container size */
|
|
53
|
+
updateBounds: (totalHeight: number, containerHeight: number) => void;
|
|
54
|
+
/** Update thumb position based on scroll position */
|
|
55
|
+
updatePosition: (scrollTop: number) => void;
|
|
56
|
+
/** Check if scrollbar is visible */
|
|
57
|
+
isVisible: () => boolean;
|
|
58
|
+
/** Destroy and cleanup */
|
|
59
|
+
destroy: () => void;
|
|
60
|
+
}
|
|
61
|
+
/** Callback for scroll position changes */
|
|
62
|
+
export type ScrollCallback = (position: number) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Create a scrollbar instance
|
|
65
|
+
*
|
|
66
|
+
* @param viewport - The viewport element to attach scrollbar to
|
|
67
|
+
* @param onScroll - Callback when scrollbar interaction causes scroll
|
|
68
|
+
* @param config - Scrollbar configuration
|
|
69
|
+
* @param classPrefix - CSS class prefix (default: 'vlist')
|
|
70
|
+
* @param horizontal - Whether the scrollbar is horizontal (default: false)
|
|
71
|
+
*/
|
|
72
|
+
export declare const createScrollbar: (viewport: HTMLElement, onScroll: ScrollCallback, config?: ScrollbarConfig, classPrefix?: string, horizontal?: boolean) => Scrollbar;
|
|
73
|
+
//# sourceMappingURL=scrollbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrollbar.d.ts","sourceRoot":"","sources":["../../../src/plugins/scroll/scrollbar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,yBAAyB;AACzB,MAAM,WAAW,SAAS;IACxB,yBAAyB;IACzB,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,kEAAkE;IAClE,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAErE,qDAAqD;IACrD,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAE5C,oCAAoC;IACpC,SAAS,EAAE,MAAM,OAAO,CAAC;IAEzB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,2CAA2C;AAC3C,MAAM,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAiBxD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,UAAU,WAAW,EACrB,UAAU,cAAc,EACxB,SAAQ,eAAoB,EAC5B,oBAAqB,EACrB,oBAAkB,KACjB,SA4XF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Selection Domain
|
|
3
|
+
* Selection state management
|
|
4
|
+
*/
|
|
5
|
+
export { withSelection, type SelectionPluginConfig } from "./plugin";
|
|
6
|
+
export { createSelectionState, selectItems, deselectItems, toggleSelection, selectAll, clearSelection, setFocusedIndex, moveFocusUp, moveFocusDown, moveFocusToFirst, moveFocusToLast, moveFocusByPage, selectFocused, selectRange, getSelectedIds, getSelectedItems, getSelectionCount, isSelected, isSelectionEmpty, type SelectionState, } from "./state";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/selection/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAGrE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,aAAa,EACb,eAAe,EACf,SAAS,EACT,cAAc,EACd,eAAe,EACf,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist/selection - Builder Plugin
|
|
3
|
+
* Wraps the selection domain into a VListPlugin for the composable builder.
|
|
4
|
+
*
|
|
5
|
+
* Priority: 50 (runs after renderer and data are ready)
|
|
6
|
+
*
|
|
7
|
+
* What it wires:
|
|
8
|
+
* - Click handler on items container — toggles selection on item click
|
|
9
|
+
* - Keyboard handler on root — ArrowUp/Down for focus, Space/Enter for toggle
|
|
10
|
+
* - ARIA attributes — aria-selected on items, aria-activedescendant on root
|
|
11
|
+
* - Live region — announces selection changes to screen readers
|
|
12
|
+
* - Render integration — passes selection state to render pipeline
|
|
13
|
+
*
|
|
14
|
+
* Added methods: select, deselect, toggleSelect, selectAll, clearSelection,
|
|
15
|
+
* getSelected, getSelectedItems
|
|
16
|
+
*
|
|
17
|
+
* Added events: item:click, selection:change
|
|
18
|
+
*/
|
|
19
|
+
import type { VListItem, SelectionMode } from "../../types";
|
|
20
|
+
import type { VListPlugin } from "../../builder/types";
|
|
21
|
+
/** Selection plugin configuration */
|
|
22
|
+
export interface SelectionPluginConfig {
|
|
23
|
+
/** Selection mode (default: 'single') */
|
|
24
|
+
mode?: SelectionMode;
|
|
25
|
+
/** Initially selected item IDs */
|
|
26
|
+
initial?: Array<string | number>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Create a selection plugin for the builder.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { vlist } from 'vlist/builder'
|
|
33
|
+
* import { withSelection } from 'vlist/selection'
|
|
34
|
+
*
|
|
35
|
+
* const list = vlist({ ... })
|
|
36
|
+
* .use(withSelection({ mode: 'multiple', initial: ['id-1'] }))
|
|
37
|
+
* .build()
|
|
38
|
+
*
|
|
39
|
+
* list.select('id-2')
|
|
40
|
+
* list.getSelected() // ['id-1', 'id-2']
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare const withSelection: <T extends VListItem = VListItem>(config?: SelectionPluginConfig) => VListPlugin<T>;
|
|
44
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/selection/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,qBAAqB,CAAC;AAyBvE,qCAAqC;AACrC,MAAM,WAAW,qBAAqB;IACpC,yCAAyC;IACzC,IAAI,CAAC,EAAE,aAAa,CAAC;IAErB,kCAAkC;IAClC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CAClC;AAMD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EAC3D,SAAS,qBAAqB,KAC7B,WAAW,CAAC,CAAC,CA0Wf,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vlist - Selection State Management
|
|
3
|
+
* Pure functions for managing selection state
|
|
4
|
+
*/
|
|
5
|
+
import type { VListItem, SelectionMode, SelectionState } from "../../types";
|
|
6
|
+
export type { SelectionState } from "../../types";
|
|
7
|
+
/**
|
|
8
|
+
* Create initial selection state
|
|
9
|
+
* Pure function - no side effects
|
|
10
|
+
*/
|
|
11
|
+
export declare const createSelectionState: (initial?: Array<string | number>) => SelectionState;
|
|
12
|
+
/**
|
|
13
|
+
* Select items by ID
|
|
14
|
+
* Pure function - returns new state
|
|
15
|
+
*/
|
|
16
|
+
export declare const selectItems: (state: SelectionState, ids: Array<string | number>, mode: SelectionMode) => SelectionState;
|
|
17
|
+
/**
|
|
18
|
+
* Deselect items by ID
|
|
19
|
+
* Pure function - returns new state
|
|
20
|
+
*/
|
|
21
|
+
export declare const deselectItems: (state: SelectionState, ids: Array<string | number>) => SelectionState;
|
|
22
|
+
/**
|
|
23
|
+
* Toggle item selection
|
|
24
|
+
* Pure function - returns new state
|
|
25
|
+
*/
|
|
26
|
+
export declare const toggleSelection: (state: SelectionState, id: string | number, mode: SelectionMode) => SelectionState;
|
|
27
|
+
/**
|
|
28
|
+
* Select all items
|
|
29
|
+
* Pure function - returns new state
|
|
30
|
+
*/
|
|
31
|
+
export declare const selectAll: <T extends VListItem>(state: SelectionState, items: T[], mode: SelectionMode) => SelectionState;
|
|
32
|
+
/**
|
|
33
|
+
* Clear all selection
|
|
34
|
+
* Pure function - returns new state
|
|
35
|
+
*/
|
|
36
|
+
export declare const clearSelection: (state: SelectionState) => SelectionState;
|
|
37
|
+
/**
|
|
38
|
+
* Set focused index
|
|
39
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
40
|
+
*/
|
|
41
|
+
export declare const setFocusedIndex: (state: SelectionState, index: number) => SelectionState;
|
|
42
|
+
/**
|
|
43
|
+
* Move focus up
|
|
44
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
45
|
+
*/
|
|
46
|
+
export declare const moveFocusUp: (state: SelectionState, totalItems: number, wrap?: boolean) => SelectionState;
|
|
47
|
+
/**
|
|
48
|
+
* Move focus down
|
|
49
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
50
|
+
*/
|
|
51
|
+
export declare const moveFocusDown: (state: SelectionState, totalItems: number, wrap?: boolean) => SelectionState;
|
|
52
|
+
/**
|
|
53
|
+
* Move focus to first item
|
|
54
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
55
|
+
*/
|
|
56
|
+
export declare const moveFocusToFirst: (state: SelectionState, totalItems: number) => SelectionState;
|
|
57
|
+
/**
|
|
58
|
+
* Move focus to last item
|
|
59
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
60
|
+
*/
|
|
61
|
+
export declare const moveFocusToLast: (state: SelectionState, totalItems: number) => SelectionState;
|
|
62
|
+
/**
|
|
63
|
+
* Move focus by page (for Page Up/Down)
|
|
64
|
+
* Mutates state in-place to avoid allocation on hot path
|
|
65
|
+
*/
|
|
66
|
+
export declare const moveFocusByPage: (state: SelectionState, totalItems: number, pageSize: number, direction: "up" | "down") => SelectionState;
|
|
67
|
+
/**
|
|
68
|
+
* Check if an item is selected
|
|
69
|
+
* Pure function - no side effects
|
|
70
|
+
*/
|
|
71
|
+
export declare const isSelected: (state: SelectionState, id: string | number) => boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Get selected IDs as array
|
|
74
|
+
* Pure function - no side effects
|
|
75
|
+
*/
|
|
76
|
+
export declare const getSelectedIds: (state: SelectionState) => Array<string | number>;
|
|
77
|
+
/**
|
|
78
|
+
* Get selected items using ID lookup (O(k) where k = selected count)
|
|
79
|
+
* Pure function - no side effects
|
|
80
|
+
*/
|
|
81
|
+
export declare const getSelectedItems: <T extends VListItem>(state: SelectionState, getItemById: (id: string | number) => T | undefined) => T[];
|
|
82
|
+
/**
|
|
83
|
+
* Get selection count
|
|
84
|
+
* Pure function - no side effects
|
|
85
|
+
*/
|
|
86
|
+
export declare const getSelectionCount: (state: SelectionState) => number;
|
|
87
|
+
/**
|
|
88
|
+
* Check if selection is empty
|
|
89
|
+
* Pure function - no side effects
|
|
90
|
+
*/
|
|
91
|
+
export declare const isSelectionEmpty: (state: SelectionState) => boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Handle keyboard selection (Space/Enter on focused item)
|
|
94
|
+
* Pure function - returns new state
|
|
95
|
+
*/
|
|
96
|
+
export declare const selectFocused: <T extends VListItem>(state: SelectionState, items: T[], mode: SelectionMode) => SelectionState;
|
|
97
|
+
/**
|
|
98
|
+
* Handle shift+click range selection
|
|
99
|
+
* Pure function - returns new state
|
|
100
|
+
*/
|
|
101
|
+
export declare const selectRange: <T extends VListItem>(state: SelectionState, items: T[], fromIndex: number, toIndex: number, mode: SelectionMode) => SelectionState;
|
|
102
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/plugins/selection/state.ts"],"names":[],"mappings":"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"}
|