vlist 1.9.1 → 2.0.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.
Files changed (182) hide show
  1. package/README.github.md +104 -97
  2. package/README.md +46 -33
  3. package/dist/constants.d.ts +11 -6
  4. package/dist/constants.js +83 -0
  5. package/dist/core/create.d.ts +10 -0
  6. package/dist/core/create.js +740 -0
  7. package/dist/core/dom.d.ts +8 -0
  8. package/dist/core/dom.js +47 -0
  9. package/dist/core/hooks.d.ts +16 -0
  10. package/dist/core/hooks.js +67 -0
  11. package/dist/core/index.d.ts +17 -0
  12. package/dist/core/index.js +13 -0
  13. package/dist/core/pipeline.d.ts +51 -0
  14. package/dist/core/pipeline.js +307 -0
  15. package/dist/core/pool.d.ts +9 -0
  16. package/dist/core/pool.js +42 -0
  17. package/dist/core/scroll.d.ts +32 -0
  18. package/dist/core/scroll.js +137 -0
  19. package/dist/core/sizes.d.ts +8 -0
  20. package/dist/core/sizes.js +6 -0
  21. package/dist/core/state.d.ts +47 -0
  22. package/dist/core/state.js +56 -0
  23. package/dist/core/types.d.ts +187 -0
  24. package/dist/core/types.js +7 -0
  25. package/dist/{builder → core}/velocity.d.ts +1 -1
  26. package/dist/core/velocity.js +33 -0
  27. package/dist/events/emitter.js +60 -0
  28. package/dist/events/index.js +6 -0
  29. package/dist/index.d.ts +28 -19
  30. package/dist/index.js +28 -1
  31. package/dist/internals.d.ts +11 -7
  32. package/dist/internals.js +60 -1
  33. package/dist/plugins/a11y/index.d.ts +2 -0
  34. package/dist/plugins/a11y/index.js +1 -0
  35. package/dist/plugins/a11y/plugin.d.ts +13 -0
  36. package/dist/plugins/a11y/plugin.js +259 -0
  37. package/dist/{features → plugins}/async/index.d.ts +1 -1
  38. package/dist/plugins/async/index.js +12 -0
  39. package/dist/{features → plugins}/async/manager.d.ts +5 -1
  40. package/dist/plugins/async/manager.js +568 -0
  41. package/dist/plugins/async/placeholder.js +154 -0
  42. package/dist/plugins/async/plugin.d.ts +48 -0
  43. package/dist/plugins/async/plugin.js +311 -0
  44. package/dist/plugins/async/sparse.js +540 -0
  45. package/dist/plugins/autosize/index.d.ts +5 -0
  46. package/dist/plugins/autosize/index.js +4 -0
  47. package/dist/plugins/autosize/plugin.d.ts +19 -0
  48. package/dist/plugins/autosize/plugin.js +185 -0
  49. package/dist/plugins/grid/index.d.ts +7 -0
  50. package/dist/plugins/grid/index.js +5 -0
  51. package/dist/plugins/grid/layout.js +275 -0
  52. package/dist/plugins/grid/plugin.d.ts +23 -0
  53. package/dist/plugins/grid/plugin.js +347 -0
  54. package/dist/plugins/grid/renderer.js +525 -0
  55. package/dist/plugins/grid/types.js +11 -0
  56. package/dist/plugins/groups/async-bridge.js +246 -0
  57. package/dist/{features → plugins}/groups/index.d.ts +1 -1
  58. package/dist/plugins/groups/index.js +13 -0
  59. package/dist/plugins/groups/layout.js +294 -0
  60. package/dist/plugins/groups/plugin.d.ts +22 -0
  61. package/dist/plugins/groups/plugin.js +571 -0
  62. package/dist/plugins/groups/sticky.js +255 -0
  63. package/dist/plugins/groups/types.js +12 -0
  64. package/dist/plugins/masonry/index.d.ts +8 -0
  65. package/dist/plugins/masonry/index.js +6 -0
  66. package/dist/plugins/masonry/layout.js +261 -0
  67. package/dist/plugins/masonry/plugin.d.ts +32 -0
  68. package/dist/plugins/masonry/plugin.js +381 -0
  69. package/dist/plugins/masonry/renderer.js +354 -0
  70. package/dist/plugins/masonry/types.js +9 -0
  71. package/dist/plugins/page/index.d.ts +5 -0
  72. package/dist/plugins/page/index.js +5 -0
  73. package/dist/plugins/page/plugin.d.ts +21 -0
  74. package/dist/plugins/page/plugin.js +166 -0
  75. package/dist/plugins/scale/index.d.ts +5 -0
  76. package/dist/plugins/scale/index.js +4 -0
  77. package/dist/plugins/scale/plugin.d.ts +24 -0
  78. package/dist/plugins/scale/plugin.js +507 -0
  79. package/dist/plugins/scrollbar/controller.js +574 -0
  80. package/dist/plugins/scrollbar/index.d.ts +7 -0
  81. package/dist/plugins/scrollbar/index.js +6 -0
  82. package/dist/plugins/scrollbar/plugin.d.ts +20 -0
  83. package/dist/plugins/scrollbar/plugin.js +93 -0
  84. package/dist/plugins/scrollbar/scrollbar.js +556 -0
  85. package/dist/plugins/selection/index.d.ts +6 -0
  86. package/dist/plugins/selection/index.js +7 -0
  87. package/dist/plugins/selection/plugin.d.ts +16 -0
  88. package/dist/plugins/selection/plugin.js +601 -0
  89. package/dist/{features → plugins}/selection/state.d.ts +8 -0
  90. package/dist/plugins/selection/state.js +332 -0
  91. package/dist/plugins/snapshots/index.d.ts +5 -0
  92. package/dist/plugins/snapshots/index.js +5 -0
  93. package/dist/plugins/snapshots/plugin.d.ts +17 -0
  94. package/dist/plugins/snapshots/plugin.js +301 -0
  95. package/dist/plugins/sortable/index.d.ts +6 -0
  96. package/dist/plugins/sortable/index.js +6 -0
  97. package/dist/plugins/sortable/plugin.d.ts +34 -0
  98. package/dist/plugins/sortable/plugin.js +753 -0
  99. package/dist/plugins/table/header.js +501 -0
  100. package/dist/{features → plugins}/table/index.d.ts +1 -1
  101. package/dist/plugins/table/index.js +12 -0
  102. package/dist/plugins/table/layout.js +211 -0
  103. package/dist/plugins/table/plugin.d.ts +20 -0
  104. package/dist/plugins/table/plugin.js +391 -0
  105. package/dist/plugins/table/renderer.js +625 -0
  106. package/dist/plugins/table/types.js +12 -0
  107. package/dist/plugins/transition/index.d.ts +5 -0
  108. package/dist/plugins/transition/index.js +5 -0
  109. package/dist/plugins/transition/plugin.d.ts +22 -0
  110. package/dist/plugins/transition/plugin.js +405 -0
  111. package/dist/rendering/aria.js +23 -0
  112. package/dist/rendering/index.js +18 -0
  113. package/dist/rendering/measured.js +98 -0
  114. package/dist/rendering/renderer.js +586 -0
  115. package/dist/rendering/scale.js +267 -0
  116. package/dist/rendering/scroll.js +71 -0
  117. package/dist/rendering/sizes.js +193 -0
  118. package/dist/rendering/sort.js +65 -0
  119. package/dist/rendering/viewport.js +268 -0
  120. package/dist/size.json +1 -1
  121. package/dist/types.js +5 -0
  122. package/dist/utils/padding.d.ts +2 -4
  123. package/dist/utils/padding.js +49 -0
  124. package/dist/utils/stats.js +124 -0
  125. package/dist/vlist-grid.css +1 -1
  126. package/dist/vlist-masonry.css +1 -1
  127. package/dist/vlist-table.css +1 -1
  128. package/dist/vlist.css +1 -1
  129. package/package.json +9 -4
  130. package/dist/builder/a11y.d.ts +0 -16
  131. package/dist/builder/api.d.ts +0 -21
  132. package/dist/builder/context.d.ts +0 -36
  133. package/dist/builder/core.d.ts +0 -16
  134. package/dist/builder/data.d.ts +0 -71
  135. package/dist/builder/dom.d.ts +0 -15
  136. package/dist/builder/index.d.ts +0 -25
  137. package/dist/builder/materialize.d.ts +0 -166
  138. package/dist/builder/pool.d.ts +0 -10
  139. package/dist/builder/range.d.ts +0 -10
  140. package/dist/builder/scroll.d.ts +0 -24
  141. package/dist/builder/types.d.ts +0 -512
  142. package/dist/features/async/feature.d.ts +0 -72
  143. package/dist/features/autosize/feature.d.ts +0 -34
  144. package/dist/features/autosize/index.d.ts +0 -2
  145. package/dist/features/grid/feature.d.ts +0 -48
  146. package/dist/features/grid/index.d.ts +0 -9
  147. package/dist/features/groups/feature.d.ts +0 -75
  148. package/dist/features/masonry/feature.d.ts +0 -45
  149. package/dist/features/masonry/index.d.ts +0 -9
  150. package/dist/features/page/feature.d.ts +0 -109
  151. package/dist/features/page/index.d.ts +0 -9
  152. package/dist/features/scale/feature.d.ts +0 -42
  153. package/dist/features/scale/index.d.ts +0 -10
  154. package/dist/features/scrollbar/feature.d.ts +0 -81
  155. package/dist/features/scrollbar/index.d.ts +0 -8
  156. package/dist/features/selection/feature.d.ts +0 -91
  157. package/dist/features/selection/index.d.ts +0 -7
  158. package/dist/features/snapshots/feature.d.ts +0 -79
  159. package/dist/features/snapshots/index.d.ts +0 -9
  160. package/dist/features/sortable/feature.d.ts +0 -101
  161. package/dist/features/sortable/index.d.ts +0 -6
  162. package/dist/features/table/feature.d.ts +0 -67
  163. package/dist/features/transition/feature.d.ts +0 -30
  164. package/dist/features/transition/index.d.ts +0 -9
  165. /package/dist/{features → plugins}/async/placeholder.d.ts +0 -0
  166. /package/dist/{features → plugins}/async/sparse.d.ts +0 -0
  167. /package/dist/{features → plugins}/grid/layout.d.ts +0 -0
  168. /package/dist/{features → plugins}/grid/renderer.d.ts +0 -0
  169. /package/dist/{features → plugins}/grid/types.d.ts +0 -0
  170. /package/dist/{features → plugins}/groups/async-bridge.d.ts +0 -0
  171. /package/dist/{features → plugins}/groups/layout.d.ts +0 -0
  172. /package/dist/{features → plugins}/groups/sticky.d.ts +0 -0
  173. /package/dist/{features → plugins}/groups/types.d.ts +0 -0
  174. /package/dist/{features → plugins}/masonry/layout.d.ts +0 -0
  175. /package/dist/{features → plugins}/masonry/renderer.d.ts +0 -0
  176. /package/dist/{features → plugins}/masonry/types.d.ts +0 -0
  177. /package/dist/{features → plugins}/scrollbar/controller.d.ts +0 -0
  178. /package/dist/{features → plugins}/scrollbar/scrollbar.d.ts +0 -0
  179. /package/dist/{features → plugins}/table/header.d.ts +0 -0
  180. /package/dist/{features → plugins}/table/layout.d.ts +0 -0
  181. /package/dist/{features → plugins}/table/renderer.d.ts +0 -0
  182. /package/dist/{features → plugins}/table/types.d.ts +0 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * vlist v2 — Scroll Handling
3
+ *
4
+ * Wheel interception for synchronous rendering, scroll idle detection,
5
+ * and smooth scroll animation.
6
+ */
7
+ import { SCROLL_IDLE_TIMEOUT, WHEEL_SENSITIVITY, SCROLL_EASING } from "../constants";
8
+ export function createScrollHandler(config) {
9
+ const { state, viewport, horizontal, wheelEnabled, onFrame, onIdle } = config;
10
+ const idleTimeout = config.idleTimeout || SCROLL_IDLE_TIMEOUT;
11
+ const target = config.scrollTarget ?? viewport;
12
+ let idleTimer = null;
13
+ let animationId = null;
14
+ // ── Scroll event (passive, for native/touch scrolling) ──────────
15
+ function onScrollEvent() {
16
+ const pos = horizontal ? viewport.scrollLeft : viewport.scrollTop;
17
+ if (Math.abs(pos - state.scrollPosition) < 0.5)
18
+ return;
19
+ state.prevScrollPosition = state.scrollPosition;
20
+ state.scrollPosition = pos;
21
+ state.scrollDirection = pos > state.prevScrollPosition ? 1 : pos < state.prevScrollPosition ? -1 : 0;
22
+ onFrame();
23
+ scheduleIdle();
24
+ }
25
+ // ── Wheel event (non-passive, synchronous rendering) ────────────
26
+ function onWheelEvent(event) {
27
+ if (state.isCompressed)
28
+ return;
29
+ let next;
30
+ if (horizontal) {
31
+ if (Math.abs(event.deltaX) > Math.abs(event.deltaY))
32
+ return;
33
+ const current = viewport.scrollLeft;
34
+ const max = viewport.scrollWidth - viewport.clientWidth;
35
+ next = Math.max(0, Math.min(current + event.deltaY * WHEEL_SENSITIVITY, max));
36
+ if (Math.abs(next - current) < 1)
37
+ return;
38
+ event.preventDefault();
39
+ viewport.scrollLeft = next;
40
+ }
41
+ else {
42
+ const crossOverflow = viewport.scrollWidth > viewport.clientWidth;
43
+ if (crossOverflow && Math.abs(event.deltaX) > Math.abs(event.deltaY))
44
+ return;
45
+ if (crossOverflow && event.deltaX !== 0) {
46
+ event.preventDefault();
47
+ viewport.scrollLeft += event.deltaX;
48
+ }
49
+ const current = viewport.scrollTop;
50
+ const max = viewport.scrollHeight - viewport.clientHeight;
51
+ next = Math.max(0, Math.min(current + event.deltaY * WHEEL_SENSITIVITY, max));
52
+ if (Math.abs(next - current) < 1)
53
+ return;
54
+ event.preventDefault();
55
+ viewport.scrollTop = next;
56
+ }
57
+ state.prevScrollPosition = state.scrollPosition;
58
+ state.scrollPosition = next;
59
+ state.scrollDirection = next > state.prevScrollPosition ? 1 : -1;
60
+ onFrame();
61
+ scheduleIdle();
62
+ }
63
+ // ── Idle detection ──────────────────────────────────────────────
64
+ function scheduleIdle() {
65
+ if (idleTimer !== null)
66
+ clearTimeout(idleTimer);
67
+ idleTimer = setTimeout(() => {
68
+ idleTimer = null;
69
+ state.scrollDirection = 0;
70
+ onIdle();
71
+ }, idleTimeout);
72
+ }
73
+ // ── Smooth scroll animation ─────────────────────────────────────
74
+ function cancelScroll() {
75
+ if (animationId !== null) {
76
+ cancelAnimationFrame(animationId);
77
+ animationId = null;
78
+ }
79
+ }
80
+ function smoothScrollTo(target, duration, setFn, easing = SCROLL_EASING) {
81
+ cancelScroll();
82
+ const from = state.scrollPosition;
83
+ if (Math.abs(target - from) < 1) {
84
+ if (setFn)
85
+ setFn(target);
86
+ else if (horizontal)
87
+ viewport.scrollLeft = target;
88
+ else
89
+ viewport.scrollTop = target;
90
+ return;
91
+ }
92
+ const start = performance.now();
93
+ function tick(now) {
94
+ const elapsed = now - start;
95
+ const t = Math.min(elapsed / duration, 1);
96
+ const pos = from + (target - from) * easing(t);
97
+ if (setFn)
98
+ setFn(pos);
99
+ else if (horizontal)
100
+ viewport.scrollLeft = pos;
101
+ else
102
+ viewport.scrollTop = pos;
103
+ if (!setFn)
104
+ state.scrollPosition = pos;
105
+ onFrame();
106
+ if (t < 1) {
107
+ animationId = requestAnimationFrame(tick);
108
+ }
109
+ else {
110
+ animationId = null;
111
+ }
112
+ }
113
+ animationId = requestAnimationFrame(tick);
114
+ }
115
+ // ── Public interface ────────────────────────────────────────────
116
+ return {
117
+ attach() {
118
+ target.addEventListener("scroll", onScrollEvent, { passive: true });
119
+ if (wheelEnabled) {
120
+ target.addEventListener("wheel", onWheelEvent, { passive: false });
121
+ }
122
+ },
123
+ detach() {
124
+ target.removeEventListener("scroll", onScrollEvent);
125
+ if (wheelEnabled) {
126
+ target.removeEventListener("wheel", onWheelEvent);
127
+ }
128
+ cancelScroll();
129
+ if (idleTimer !== null) {
130
+ clearTimeout(idleTimer);
131
+ idleTimer = null;
132
+ }
133
+ },
134
+ cancelScroll,
135
+ smoothScrollTo,
136
+ };
137
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * vlist v2 — Size Cache
3
+ *
4
+ * Float64Array prefix sums with O(1) offset lookups and O(log n) binary search.
5
+ */
6
+ export { createSizeCache, countVisibleItems, countItemsFittingFromBottom, getOffsetForVirtualIndex, } from "../rendering/sizes";
7
+ export type { SizeCache } from "../rendering/sizes";
8
+ //# sourceMappingURL=sizes.d.ts.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * vlist v2 — Size Cache
3
+ *
4
+ * Float64Array prefix sums with O(1) offset lookups and O(log n) binary search.
5
+ */
6
+ export { createSizeCache, countVisibleItems, countItemsFittingFromBottom, getOffsetForVirtualIndex, } from "../rendering/sizes";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * vlist v2 — EngineState
3
+ *
4
+ * Persistent singleton instantiated once during createVList().
5
+ * All hot-path state lives in pre-allocated TypedArrays.
6
+ * Phase 1 mutates in place. Phase 2 reads directly. Zero allocation.
7
+ */
8
+ export interface EngineState {
9
+ /** Data indices of visible items — Phase 1 writes, Phase 2 reads */
10
+ visibleIndices: Int32Array;
11
+ /** Pixel offsets along main axis for each visible item */
12
+ visibleOffsets: Float64Array;
13
+ /** Sizes along main axis for each visible item */
14
+ visibleSizes: Float64Array;
15
+ /** Number of valid entries in the buffers (0 = empty range sentinel) */
16
+ visibleCount: number;
17
+ /** First data index in the visible window (contiguous layouts) */
18
+ startIndex: number;
19
+ /** Total content size in pixels */
20
+ totalSize: number;
21
+ /** Current buffer capacity */
22
+ capacity: number;
23
+ scrollPosition: number;
24
+ prevScrollPosition: number;
25
+ /** 1 = forward, -1 = backward, 0 = idle */
26
+ scrollDirection: number;
27
+ containerSize: number;
28
+ crossSize: number;
29
+ totalItems: number;
30
+ prevRangeStart: number;
31
+ prevRangeEnd: number;
32
+ renderPending: boolean;
33
+ initialized: boolean;
34
+ destroyed: boolean;
35
+ isCompressed: boolean;
36
+ compressionRatio: number;
37
+ prevAriaTotal: number;
38
+ /**
39
+ * Resize buffers when container changes. Cold path only.
40
+ * Heuristic: capacity = ceil(containerSize / minItemSize) + overscan * 2
41
+ */
42
+ resizeCapacity(containerSize: number, minItemSize: number, overscan?: number): void;
43
+ /** Reset to empty range sentinel. */
44
+ clear(): void;
45
+ }
46
+ export declare function createEngineState(initialCapacity: number): EngineState;
47
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * vlist v2 — EngineState
3
+ *
4
+ * Persistent singleton instantiated once during createVList().
5
+ * All hot-path state lives in pre-allocated TypedArrays.
6
+ * Phase 1 mutates in place. Phase 2 reads directly. Zero allocation.
7
+ */
8
+ import { OVERSCAN } from "../constants";
9
+ export function createEngineState(initialCapacity) {
10
+ const state = {
11
+ visibleIndices: new Int32Array(initialCapacity),
12
+ visibleOffsets: new Float64Array(initialCapacity),
13
+ visibleSizes: new Float64Array(initialCapacity),
14
+ visibleCount: 0,
15
+ startIndex: 0,
16
+ totalSize: 0,
17
+ capacity: initialCapacity,
18
+ scrollPosition: 0,
19
+ prevScrollPosition: 0,
20
+ scrollDirection: 0,
21
+ containerSize: 0,
22
+ crossSize: 0,
23
+ totalItems: 0,
24
+ prevRangeStart: 0,
25
+ prevRangeEnd: -1,
26
+ renderPending: false,
27
+ initialized: false,
28
+ destroyed: false,
29
+ isCompressed: false,
30
+ compressionRatio: 1,
31
+ prevAriaTotal: -1,
32
+ resizeCapacity(containerSize, minItemSize, overscan = OVERSCAN) {
33
+ if (minItemSize <= 0 || containerSize <= 0)
34
+ return;
35
+ const needed = Math.ceil(containerSize / minItemSize) + overscan * 2;
36
+ if (needed <= state.capacity)
37
+ return;
38
+ const newCapacity = needed + 8;
39
+ const newIndices = new Int32Array(newCapacity);
40
+ const newOffsets = new Float64Array(newCapacity);
41
+ const newSizes = new Float64Array(newCapacity);
42
+ newIndices.set(state.visibleIndices);
43
+ newOffsets.set(state.visibleOffsets);
44
+ newSizes.set(state.visibleSizes);
45
+ state.visibleIndices = newIndices;
46
+ state.visibleOffsets = newOffsets;
47
+ state.visibleSizes = newSizes;
48
+ state.capacity = newCapacity;
49
+ },
50
+ clear() {
51
+ state.visibleCount = 0;
52
+ state.startIndex = 0;
53
+ },
54
+ };
55
+ return state;
56
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * vlist v2 — Core Type Definitions
3
+ *
4
+ * Zero-allocation pipeline types. All hot-path state lives in TypedArrays
5
+ * on the EngineState singleton — no intermediate object allocation.
6
+ */
7
+ import type { VListItem, ItemTemplate, ItemState } from "../types";
8
+ import type { SizeCache } from "./sizes";
9
+ import type { EngineState } from "./state";
10
+ import type { Emitter } from "../events";
11
+ /** Phase 1 hook — mutates EngineState in place, zero allocation */
12
+ export type CalculateHook = (state: EngineState) => void;
13
+ /** Phase 2 hook — runs after DOM commit */
14
+ export type CommitHook = (state: EngineState) => void;
15
+ /** After-scroll hook — runs after both phases complete */
16
+ export type AfterScrollHook = (scrollPosition: number, direction: number) => void;
17
+ /** Idle hook — runs when scrolling stops */
18
+ export type IdleHook = () => void;
19
+ /** Resize hook — runs on container resize (cold path) */
20
+ export type ResizeHook = (width: number, height: number) => void;
21
+ export interface CompiledHooks {
22
+ readonly calculate: readonly CalculateHook[];
23
+ readonly commit: readonly CommitHook[];
24
+ readonly afterScroll: readonly AfterScrollHook[];
25
+ readonly idle: readonly IdleHook[];
26
+ readonly resize: readonly ResizeHook[];
27
+ }
28
+ export interface ResolvedConfig {
29
+ readonly overscan: number;
30
+ readonly horizontal: boolean;
31
+ readonly reverse: boolean;
32
+ readonly classPrefix: string;
33
+ readonly interactive: boolean;
34
+ readonly mainAxisPadding: number;
35
+ readonly crossAxisPadding: number;
36
+ readonly startPadding: number;
37
+ readonly endPadding: number;
38
+ readonly crossPadStart: number;
39
+ readonly crossPadEnd: number;
40
+ readonly striped: boolean | "data" | "even" | "odd";
41
+ readonly gap: number;
42
+ }
43
+ export interface DOMStructure {
44
+ readonly root: HTMLElement;
45
+ readonly viewport: HTMLElement;
46
+ readonly content: HTMLElement;
47
+ readonly liveRegion: HTMLElement;
48
+ }
49
+ export interface ElementPool {
50
+ acquire(): HTMLElement;
51
+ release(element: HTMLElement): void;
52
+ readonly size: number;
53
+ clear(): void;
54
+ }
55
+ export type VisibleRangeFn = (scrollPosition: number, containerSize: number, sizeCache: SizeCache, totalItems: number, outStart: {
56
+ value: number;
57
+ }, outEnd: {
58
+ value: number;
59
+ }) => void;
60
+ export interface PluginContext<T extends VListItem = VListItem> {
61
+ readonly dom: DOMStructure;
62
+ readonly sizeCache: SizeCache;
63
+ readonly pool: ElementPool;
64
+ readonly config: ResolvedConfig;
65
+ readonly emitter: Emitter<import("../types").VListEvents<T>>;
66
+ readonly template: ItemTemplate<T>;
67
+ registerMethod(name: string, fn: Function): void;
68
+ getMethod(name: string): Function | undefined;
69
+ registerClickHandler(handler: (event: MouseEvent) => void): void;
70
+ registerKeydownHandler(handler: (event: KeyboardEvent) => void): void;
71
+ registerDestroyHandler(handler: () => void): void;
72
+ setSizeConfig(config: number | ((index: number) => number)): void;
73
+ setVisibleRangeFn(fn: VisibleRangeFn): void;
74
+ setScrollFns(get: () => number, set: (pos: number) => void): void;
75
+ setVirtualTotalFn(fn: () => number): void;
76
+ getItems(): readonly T[];
77
+ getItem(index: number): T | undefined;
78
+ getState(): EngineState;
79
+ rebuildSizeCache(): void;
80
+ updateContentSize(size: number): void;
81
+ setRenderFn(renderIfNeeded: () => void, forceRender: () => void): void;
82
+ renderIfNeeded(): void;
83
+ forceRender(): void;
84
+ setGetItemFn(fn: (index: number) => T | undefined): void;
85
+ setItemStateFn(fn: (index: number, state: ItemState) => void): void;
86
+ getItemStateFn(): ((index: number, state: ItemState) => void) | null;
87
+ readonly rawSizeSpec: number | ((index: number, ...args: unknown[]) => number);
88
+ scrollTo(position: number): void;
89
+ smoothScrollTo(position: number, duration: number, easing?: (t: number) => number): void;
90
+ disableDefaultScroll(): void;
91
+ disableDefaultResize(): void;
92
+ setScrollTarget(target: EventTarget): void;
93
+ setScrollToPosFn(fn: (index: number, sizeCache: import("./sizes").SizeCache, containerSize: number, totalItems: number, align: string) => number): void;
94
+ setScrollToIndexFn(fn: (index: number, align: string, behavior?: string, duration?: number, easing?: (t: number) => number) => void | false): void;
95
+ onScrollFrame(): void;
96
+ onScrollIdle(): void;
97
+ removeItemById(id: string | number): number;
98
+ insertItemAt(item: T, index: number): void;
99
+ setRemoveItemFn(fn: (id: string | number) => number): void;
100
+ setInsertItemFn(fn: (item: T, index: number) => void): void;
101
+ setUpdateItemFn(fn: (id: string | number, updates: Partial<T>) => boolean): void;
102
+ getRenderedElement(index: number): HTMLElement | null;
103
+ setNavConfig(config: {
104
+ total?: () => number;
105
+ ud?: number;
106
+ lr?: number;
107
+ scrollIndex?: (itemIndex: number) => number;
108
+ navigate?: (currentIndex: number, key: string, total: number) => number;
109
+ }): void;
110
+ getNavConfig(): {
111
+ ud: number;
112
+ lr: number;
113
+ scrollIndex: ((itemIndex: number) => number) | null;
114
+ navigate: ((currentIndex: number, key: string, total: number) => number) | null;
115
+ };
116
+ }
117
+ export interface VListPlugin<T extends VListItem = VListItem> {
118
+ readonly name: string;
119
+ readonly priority?: number;
120
+ readonly conflicts?: readonly string[];
121
+ /** Cold path: one-time wiring during createVList(). */
122
+ setup?(ctx: PluginContext<T>): void;
123
+ /** Hot path: compiled into linear arrays, iterated per frame. */
124
+ hooks?: {
125
+ onCalculate?(state: EngineState): void;
126
+ onCommit?(state: EngineState): void;
127
+ onAfterScroll?(scrollPosition: number, direction: number): void;
128
+ onIdle?(): void;
129
+ onResize?(width: number, height: number): void;
130
+ };
131
+ /** Cleanup on destroy. */
132
+ destroy?(): void;
133
+ }
134
+ export interface VList<T extends VListItem = VListItem> {
135
+ readonly element: HTMLElement;
136
+ readonly items: readonly T[];
137
+ readonly total: number;
138
+ setItems(items: T[]): void;
139
+ appendItems(items: T[]): void;
140
+ prependItems(items: T[]): void;
141
+ updateItem(id: string | number, updates: Partial<T>): void;
142
+ insertItem(item: T, index?: number): void;
143
+ removeItem(id: string | number): void;
144
+ removeItems(ids: ReadonlyArray<string | number>): number;
145
+ getItemAt(index: number): T | undefined;
146
+ getIndexById(id: string | number): number;
147
+ scrollToIndex(index: number, align?: "start" | "center" | "end" | {
148
+ align?: "start" | "center" | "end";
149
+ behavior?: "auto" | "smooth";
150
+ duration?: number;
151
+ easing?: (t: number) => number;
152
+ }): void;
153
+ getScrollPosition(): number;
154
+ on<K extends keyof import("../types").VListEvents<T>>(event: K, handler: import("../types").EventHandler<import("../types").VListEvents<T>[K]>): import("../types").Unsubscribe;
155
+ off<K extends keyof import("../types").VListEvents<T>>(event: K, handler: import("../types").EventHandler<import("../types").VListEvents<T>[K]>): void;
156
+ destroy(): void;
157
+ [key: string]: unknown;
158
+ }
159
+ export interface CreateVListConfig<T extends VListItem = VListItem> {
160
+ container: HTMLElement | string;
161
+ item: {
162
+ height?: number | ((index: number) => number);
163
+ width?: number | ((index: number) => number);
164
+ estimatedHeight?: number;
165
+ estimatedWidth?: number;
166
+ template: ItemTemplate<T>;
167
+ gap?: number;
168
+ striped?: boolean | "data" | "even" | "odd";
169
+ };
170
+ items?: T[];
171
+ overscan?: number;
172
+ classPrefix?: string;
173
+ orientation?: "vertical" | "horizontal";
174
+ padding?: number | [number, number] | [number, number, number, number];
175
+ interactive?: boolean;
176
+ reverse?: boolean;
177
+ ariaLabel?: string;
178
+ scroll?: {
179
+ wheel?: boolean;
180
+ scrollbar?: "none";
181
+ gutter?: "auto" | "stable";
182
+ idleTimeout?: number;
183
+ };
184
+ /** Defer initial render to the next animation frame. */
185
+ defer?: boolean;
186
+ }
187
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * vlist v2 — Core Type Definitions
3
+ *
4
+ * Zero-allocation pipeline types. All hot-path state lives in TypedArrays
5
+ * on the EngineState singleton — no intermediate object allocation.
6
+ */
7
+ export {};
@@ -1,5 +1,5 @@
1
1
  /**
2
- * vlist/builder — Velocity Tracking
2
+ * vlist v2 — Velocity Tracking
3
3
  * Lightweight 2-sample velocity tracker for scroll momentum detection.
4
4
  */
5
5
  export declare const MIN_RELIABLE_SAMPLES = 2;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * vlist v2 — Velocity Tracking
3
+ * Lightweight 2-sample velocity tracker for scroll momentum detection.
4
+ */
5
+ export const MIN_RELIABLE_SAMPLES = 2;
6
+ /** Create a velocity tracker. */
7
+ export const createVelocityTracker = (_initialPosition = 0) => ({
8
+ velocity: 0,
9
+ sampleCount: 0,
10
+ });
11
+ /** Update velocity from new scroll position. Mutates tracker in place. */
12
+ export const updateVelocityTracker = (tracker, newPosition) => {
13
+ const now = performance.now();
14
+ const lastTime = tracker._lt ?? now;
15
+ const lastPos = tracker._lp ?? newPosition;
16
+ const dt = now - lastTime;
17
+ tracker._lp = newPosition;
18
+ tracker._lt = now;
19
+ // Zero time delta — record position but can't compute velocity
20
+ if (dt === 0) {
21
+ tracker.sampleCount = Math.min(tracker.sampleCount + 1, 5);
22
+ return tracker;
23
+ }
24
+ // Stale gap — reset
25
+ if (dt > 100) {
26
+ tracker.velocity = 0;
27
+ tracker.sampleCount = 1;
28
+ return tracker;
29
+ }
30
+ tracker.velocity = Math.abs(newPosition - lastPos) / dt;
31
+ tracker.sampleCount = Math.min(tracker.sampleCount + 1, 5);
32
+ return tracker;
33
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * vlist - Event Emitter
3
+ * Lightweight, type-safe event system
4
+ */
5
+ // =============================================================================
6
+ // Event Emitter
7
+ // =============================================================================
8
+ /**
9
+ * Create a type-safe event emitter
10
+ * Functional approach - returns an object with methods
11
+ */
12
+ export const createEmitter = () => {
13
+ const listeners = {};
14
+ /**
15
+ * Subscribe to an event
16
+ */
17
+ const on = (event, handler) => {
18
+ if (!listeners[event]) {
19
+ listeners[event] = new Set();
20
+ }
21
+ listeners[event].add(handler);
22
+ // Return unsubscribe function
23
+ return () => off(event, handler);
24
+ };
25
+ /**
26
+ * Unsubscribe from an event
27
+ */
28
+ const off = (event, handler) => {
29
+ listeners[event]?.delete(handler);
30
+ };
31
+ /**
32
+ * Emit an event to all subscribers
33
+ */
34
+ const emit = (event, payload) => {
35
+ listeners[event]?.forEach((handler) => {
36
+ try {
37
+ handler(payload);
38
+ }
39
+ catch (error) {
40
+ if (process.env.NODE_ENV !== "production") {
41
+ console.error(`[vlist] Error in event handler for "${String(event)}":`, error);
42
+ }
43
+ }
44
+ });
45
+ };
46
+ /**
47
+ * Remove all listeners for an event (or all events if no event specified)
48
+ */
49
+ const clear = (event) => {
50
+ if (event) {
51
+ delete listeners[event];
52
+ }
53
+ else {
54
+ for (const key in listeners) {
55
+ delete listeners[key];
56
+ }
57
+ }
58
+ };
59
+ return { on, off, emit, clear };
60
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * vlist - Events Domain
3
+ * Event emitter system
4
+ */
5
+ // Event Emitter
6
+ export { createEmitter } from "./emitter";
package/dist/index.d.ts CHANGED
@@ -8,26 +8,35 @@
8
8
  *
9
9
  * @packageDocumentation
10
10
  */
11
- export { vlist } from "./builder";
12
- export { withScale } from "./features/scale";
13
- export type { ScaleConfig } from "./features/scale";
14
- export { withAsync } from "./features/async";
15
- export { withScrollbar } from "./features/scrollbar";
16
- export { withPage } from "./features/page";
17
- export type { WithPageOptions } from "./features/page";
18
- export { withGroups } from "./features/groups";
19
- export { withGrid } from "./features/grid";
20
- export { withMasonry } from "./features/masonry";
21
- export { withSelection } from "./features/selection";
22
- export { withSnapshots } from "./features/snapshots";
23
- export { withTable } from "./features/table";
24
- export { withSortable } from "./features/sortable";
25
- export type { SortableConfig } from "./features/sortable";
26
- export { withAutoSize } from "./features/autosize";
27
- export { withTransition } from "./features/transition";
28
- export type { TransitionConfig, TransitionTiming } from "./features/transition";
11
+ export { createVList } from "./core/create";
12
+ export { scale } from "./plugins/scale";
13
+ export type { ScalePluginConfig } from "./plugins/scale";
14
+ export { scrollbar } from "./plugins/scrollbar";
15
+ export type { ScrollbarPluginConfig } from "./plugins/scrollbar";
16
+ export { grid } from "./plugins/grid";
17
+ export { a11y } from "./plugins/a11y";
18
+ export { selection } from "./plugins/selection";
19
+ export type { SelectionPluginConfig } from "./plugins/selection";
20
+ export { page } from "./plugins/page";
21
+ export type { PagePluginConfig } from "./plugins/page";
22
+ export { snapshots } from "./plugins/snapshots";
23
+ export type { SnapshotsPluginConfig } from "./plugins/snapshots";
24
+ export { transition } from "./plugins/transition";
25
+ export type { TransitionPluginConfig } from "./plugins/transition";
26
+ export { autosize } from "./plugins/autosize";
27
+ export type { AutosizePluginConfig } from "./plugins/autosize";
28
+ export { masonry } from "./plugins/masonry";
29
+ export type { MasonryPluginConfig } from "./plugins/masonry";
30
+ export { data } from "./plugins/async";
31
+ export type { DataPluginConfig } from "./plugins/async";
32
+ export { groups } from "./plugins/groups";
33
+ export type { GroupsPluginConfig } from "./plugins/groups";
34
+ export { table } from "./plugins/table";
35
+ export type { TablePluginConfig } from "./plugins/table";
36
+ export { sortable } from "./plugins/sortable";
37
+ export type { SortablePluginConfig } from "./plugins/sortable";
29
38
  export { createStats } from "./utils/stats";
30
39
  export type { Stats, StatsConfig, StatsState } from "./utils/stats";
31
40
  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";
32
- export type { VListBuilder, VList, BuilderConfig, VListConfig, VListFeature, BuilderContext, ReloadOptions, } from "./builder";
41
+ export type { VList, VListPlugin, PluginContext, CreateVListConfig, CompiledHooks, ResolvedConfig, DOMStructure, ElementPool, } from "./core/types";
33
42
  //# sourceMappingURL=index.d.ts.map