@tanstack/marko-virtual 3.14.2 → 3.15.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.md +268 -118
- package/dist/cjs/tags/virtualizer/options.d.cts +30 -1
- package/dist/cjs/tags/window-virtualizer/options.d.cts +32 -1
- package/dist/esm/tags/virtualizer/options.d.ts +30 -1
- package/dist/esm/tags/window-virtualizer/options.d.ts +32 -1
- package/dist/tags/virtualizer/index.d.marko +56 -0
- package/dist/tags/virtualizer/index.marko +59 -0
- package/dist/tags/virtualizer/options.d.ts +46 -0
- package/dist/tags/virtualizer/options.js +63 -0
- package/dist/tags/window-virtualizer/index.d.marko +55 -0
- package/dist/tags/window-virtualizer/index.marko +57 -0
- package/dist/tags/window-virtualizer/options.d.ts +45 -0
- package/dist/tags/window-virtualizer/options.js +67 -0
- package/marko.json +1 -1
- package/package.json +12 -9
- package/src/tags/virtualizer/index.marko +0 -78
- package/src/tags/virtualizer/options.ts +0 -50
- package/src/tags/window-virtualizer/index.marko +0 -75
- package/src/tags/window-virtualizer/options.ts +0 -46
|
@@ -1,13 +1,44 @@
|
|
|
1
|
-
import { VirtualizerOptions } from '@tanstack/virtual-core';
|
|
1
|
+
import { Virtualizer, Range, VirtualItem, VirtualizerOptions } from '@tanstack/virtual-core';
|
|
2
2
|
export interface WindowVirtualizerInput {
|
|
3
3
|
count: number;
|
|
4
4
|
estimateSize?: (index: number) => number;
|
|
5
5
|
overscan?: number;
|
|
6
|
+
horizontal?: boolean;
|
|
6
7
|
paddingStart?: number;
|
|
7
8
|
paddingEnd?: number;
|
|
8
9
|
scrollPaddingStart?: number;
|
|
9
10
|
scrollPaddingEnd?: number;
|
|
10
11
|
gap?: number;
|
|
11
12
|
lanes?: number;
|
|
13
|
+
initialOffset?: number | (() => number);
|
|
14
|
+
initialRect?: {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
getItemKey?: (index: number) => number | string | bigint;
|
|
19
|
+
rangeExtractor?: (range: Range) => Array<number>;
|
|
20
|
+
indexAttribute?: string;
|
|
21
|
+
initialMeasurementsCache?: Array<VirtualItem>;
|
|
22
|
+
anchorTo?: 'start' | 'end';
|
|
23
|
+
followOnAppend?: boolean | ScrollBehavior;
|
|
24
|
+
scrollEndThreshold?: number;
|
|
25
|
+
scrollMargin?: number;
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
isRtl?: boolean;
|
|
28
|
+
isScrollingResetDelay?: number;
|
|
29
|
+
useScrollendEvent?: boolean;
|
|
30
|
+
useAnimationFrameWithResizeObserver?: boolean;
|
|
31
|
+
laneAssignmentMode?: 'estimate' | 'measured';
|
|
32
|
+
useCachedMeasurements?: boolean;
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
measureElement?: (element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<Window, Element>) => number;
|
|
12
35
|
}
|
|
13
36
|
export declare function buildOptions(input: WindowVirtualizerInput, notify: () => void): VirtualizerOptions<Window, Element>;
|
|
37
|
+
export declare function renderSlice(input: WindowVirtualizerInput): {
|
|
38
|
+
items: Array<VirtualItem>;
|
|
39
|
+
size: number;
|
|
40
|
+
range: {
|
|
41
|
+
startIndex: number;
|
|
42
|
+
endIndex: number;
|
|
43
|
+
} | null;
|
|
44
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Virtualizer } from "@tanstack/virtual-core";
|
|
2
|
+
import type { Range, VirtualItem, ScrollToOptions } from "@tanstack/virtual-core";
|
|
3
|
+
export interface VirtualizerHandle {
|
|
4
|
+
virtualItems: VirtualItem[];
|
|
5
|
+
totalSize: number;
|
|
6
|
+
range: {
|
|
7
|
+
startIndex: number;
|
|
8
|
+
endIndex: number;
|
|
9
|
+
} | null;
|
|
10
|
+
measureElement: ((el: Element | null) => void) | undefined;
|
|
11
|
+
scrollToIndex: (index: number, options?: ScrollToOptions) => void;
|
|
12
|
+
scrollToOffset: (offset: number, options?: ScrollToOptions) => void;
|
|
13
|
+
measure: () => void;
|
|
14
|
+
resizeItem: (index: number, size: number) => void;
|
|
15
|
+
scrollToEnd: (options?: {
|
|
16
|
+
behavior?: ScrollToOptions['behavior'];
|
|
17
|
+
}) => void;
|
|
18
|
+
isAtEnd: (threshold?: number) => boolean;
|
|
19
|
+
getDistanceFromEnd: () => number;
|
|
20
|
+
}
|
|
21
|
+
export interface Input {
|
|
22
|
+
count: number;
|
|
23
|
+
estimateSize?: (index: number) => number;
|
|
24
|
+
getScrollElement: () => Element | null;
|
|
25
|
+
overscan?: number;
|
|
26
|
+
horizontal?: boolean;
|
|
27
|
+
paddingStart?: number;
|
|
28
|
+
paddingEnd?: number;
|
|
29
|
+
scrollPaddingStart?: number;
|
|
30
|
+
scrollPaddingEnd?: number;
|
|
31
|
+
gap?: number;
|
|
32
|
+
lanes?: number;
|
|
33
|
+
initialOffset?: number | (() => number);
|
|
34
|
+
initialRect?: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
getItemKey?: (index: number) => number | string | bigint;
|
|
39
|
+
rangeExtractor?: (range: Range) => number[];
|
|
40
|
+
indexAttribute?: string;
|
|
41
|
+
initialMeasurementsCache?: VirtualItem[];
|
|
42
|
+
anchorTo?: 'start' | 'end';
|
|
43
|
+
followOnAppend?: boolean | ScrollBehavior;
|
|
44
|
+
scrollEndThreshold?: number;
|
|
45
|
+
scrollMargin?: number;
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
isRtl?: boolean;
|
|
48
|
+
isScrollingResetDelay?: number;
|
|
49
|
+
useScrollendEvent?: boolean;
|
|
50
|
+
useAnimationFrameWithResizeObserver?: boolean;
|
|
51
|
+
laneAssignmentMode?: 'estimate' | 'measured';
|
|
52
|
+
useCachedMeasurements?: boolean;
|
|
53
|
+
debug?: boolean;
|
|
54
|
+
measureElement?: (element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<Element, Element>) => number;
|
|
55
|
+
}
|
|
56
|
+
<return = (1 as any as VirtualizerHandle)/>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Virtualizer } from "@tanstack/virtual-core";
|
|
2
|
+
import { buildOptions, renderSlice } from "./options.js";
|
|
3
|
+
<!-- Render-time seed: derive the initial window as plain data via a transient virtual-core-->
|
|
4
|
+
<!-- instance (see renderSlice in ./options). On the server this paints the initial slice into-->
|
|
5
|
+
<!-- the HTML (when `initialRect` is given) or an empty container with the correct total height-->
|
|
6
|
+
<!-- (when it is not) — no <if=mounted> guard. The values serialize and recompute identically on-->
|
|
7
|
+
<!-- resume. The live, observing instance is then built client-only in onMount and takes over.-->
|
|
8
|
+
<const/seed=renderSlice(input)/>
|
|
9
|
+
<let/items=seed.items/>
|
|
10
|
+
<let/size=seed.size/>
|
|
11
|
+
<let/range=seed.range/>
|
|
12
|
+
<let/v=null/>
|
|
13
|
+
<let/cleanup=null/>
|
|
14
|
+
<lifecycle<{}> onMount() {
|
|
15
|
+
const notify = () => {
|
|
16
|
+
const inst = v;
|
|
17
|
+
if (!inst) return;
|
|
18
|
+
items = inst.getVirtualItems();
|
|
19
|
+
size = inst.getTotalSize();
|
|
20
|
+
range = inst.range;
|
|
21
|
+
};
|
|
22
|
+
v = new Virtualizer(buildOptions(input, notify));
|
|
23
|
+
cleanup = v._didMount();
|
|
24
|
+
v._willUpdate();
|
|
25
|
+
notify();
|
|
26
|
+
} onUpdate() {
|
|
27
|
+
const current = v;
|
|
28
|
+
if (!current) return;
|
|
29
|
+
const notify = () => {
|
|
30
|
+
const inst = v;
|
|
31
|
+
if (!inst) return;
|
|
32
|
+
items = inst.getVirtualItems();
|
|
33
|
+
size = inst.getTotalSize();
|
|
34
|
+
range = inst.range;
|
|
35
|
+
};
|
|
36
|
+
current.setOptions({
|
|
37
|
+
...current.options,
|
|
38
|
+
...buildOptions(input, notify)
|
|
39
|
+
});
|
|
40
|
+
current._willUpdate();
|
|
41
|
+
notify();
|
|
42
|
+
} onDestroy() {
|
|
43
|
+
cleanup?.();
|
|
44
|
+
v = null;
|
|
45
|
+
cleanup = null;
|
|
46
|
+
}/>
|
|
47
|
+
<return={
|
|
48
|
+
virtualItems: items,
|
|
49
|
+
totalSize: size,
|
|
50
|
+
range: range,
|
|
51
|
+
measureElement: v?.measureElement,
|
|
52
|
+
scrollToIndex: (index, options) => v?.scrollToIndex(index, options),
|
|
53
|
+
scrollToOffset: (offset, options) => v?.scrollToOffset(offset, options),
|
|
54
|
+
measure: () => v?.measure(),
|
|
55
|
+
resizeItem: (index, itemSize) => v?.resizeItem(index, itemSize),
|
|
56
|
+
scrollToEnd: options => v?.scrollToEnd(options),
|
|
57
|
+
isAtEnd: threshold => v?.isAtEnd(threshold) ?? false,
|
|
58
|
+
getDistanceFromEnd: () => v?.getDistanceFromEnd() ?? Infinity
|
|
59
|
+
}/>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Virtualizer } from '@tanstack/virtual-core';
|
|
2
|
+
import type { Range, VirtualItem, VirtualizerOptions } from '@tanstack/virtual-core';
|
|
3
|
+
export interface VirtualizerInput {
|
|
4
|
+
count: number;
|
|
5
|
+
estimateSize?: (index: number) => number;
|
|
6
|
+
getScrollElement: () => Element | null;
|
|
7
|
+
overscan?: number;
|
|
8
|
+
horizontal?: boolean;
|
|
9
|
+
paddingStart?: number;
|
|
10
|
+
paddingEnd?: number;
|
|
11
|
+
scrollPaddingStart?: number;
|
|
12
|
+
scrollPaddingEnd?: number;
|
|
13
|
+
gap?: number;
|
|
14
|
+
lanes?: number;
|
|
15
|
+
initialOffset?: number | (() => number);
|
|
16
|
+
initialRect?: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
getItemKey?: (index: number) => number | string | bigint;
|
|
21
|
+
rangeExtractor?: (range: Range) => Array<number>;
|
|
22
|
+
indexAttribute?: string;
|
|
23
|
+
initialMeasurementsCache?: Array<VirtualItem>;
|
|
24
|
+
anchorTo?: 'start' | 'end';
|
|
25
|
+
followOnAppend?: boolean | ScrollBehavior;
|
|
26
|
+
scrollEndThreshold?: number;
|
|
27
|
+
scrollMargin?: number;
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
isRtl?: boolean;
|
|
30
|
+
isScrollingResetDelay?: number;
|
|
31
|
+
useScrollendEvent?: boolean;
|
|
32
|
+
useAnimationFrameWithResizeObserver?: boolean;
|
|
33
|
+
laneAssignmentMode?: 'estimate' | 'measured';
|
|
34
|
+
useCachedMeasurements?: boolean;
|
|
35
|
+
debug?: boolean;
|
|
36
|
+
measureElement?: (element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<Element, Element>) => number;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildOptions(input: VirtualizerInput, notify: () => void): VirtualizerOptions<Element, Element>;
|
|
39
|
+
export declare function renderSlice(input: VirtualizerInput): {
|
|
40
|
+
items: Array<VirtualItem>;
|
|
41
|
+
size: number;
|
|
42
|
+
range: {
|
|
43
|
+
startIndex: number;
|
|
44
|
+
endIndex: number;
|
|
45
|
+
} | null;
|
|
46
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Virtualizer, elementScroll, observeElementOffset, observeElementRect, } from '@tanstack/virtual-core';
|
|
2
|
+
// Single source of truth for the input -> virtual-core option mapping,
|
|
3
|
+
// shared by the render-time seed, onMount (construction) and onUpdate (setOptions).
|
|
4
|
+
// Optional inputs are forwarded possibly-undefined on purpose: virtual-core's
|
|
5
|
+
// setOptions() skips undefined keys, so its own defaults apply.
|
|
6
|
+
export function buildOptions(input, notify) {
|
|
7
|
+
return {
|
|
8
|
+
count: input.count,
|
|
9
|
+
estimateSize: input.estimateSize ?? (() => 50),
|
|
10
|
+
getScrollElement: input.getScrollElement,
|
|
11
|
+
overscan: input.overscan ?? 5,
|
|
12
|
+
horizontal: input.horizontal ?? false,
|
|
13
|
+
paddingStart: input.paddingStart,
|
|
14
|
+
paddingEnd: input.paddingEnd,
|
|
15
|
+
scrollPaddingStart: input.scrollPaddingStart,
|
|
16
|
+
scrollPaddingEnd: input.scrollPaddingEnd,
|
|
17
|
+
gap: input.gap,
|
|
18
|
+
lanes: input.lanes,
|
|
19
|
+
initialOffset: input.initialOffset,
|
|
20
|
+
initialRect: input.initialRect,
|
|
21
|
+
getItemKey: input.getItemKey,
|
|
22
|
+
rangeExtractor: input.rangeExtractor,
|
|
23
|
+
indexAttribute: input.indexAttribute,
|
|
24
|
+
initialMeasurementsCache: input.initialMeasurementsCache,
|
|
25
|
+
anchorTo: input.anchorTo,
|
|
26
|
+
followOnAppend: input.followOnAppend,
|
|
27
|
+
scrollEndThreshold: input.scrollEndThreshold,
|
|
28
|
+
scrollMargin: input.scrollMargin,
|
|
29
|
+
enabled: input.enabled,
|
|
30
|
+
isRtl: input.isRtl,
|
|
31
|
+
isScrollingResetDelay: input.isScrollingResetDelay,
|
|
32
|
+
useScrollendEvent: input.useScrollendEvent,
|
|
33
|
+
useAnimationFrameWithResizeObserver: input.useAnimationFrameWithResizeObserver,
|
|
34
|
+
laneAssignmentMode: input.laneAssignmentMode,
|
|
35
|
+
useCachedMeasurements: input.useCachedMeasurements,
|
|
36
|
+
debug: input.debug,
|
|
37
|
+
measureElement: input.measureElement,
|
|
38
|
+
observeElementRect,
|
|
39
|
+
observeElementOffset,
|
|
40
|
+
scrollToFn: elementScroll,
|
|
41
|
+
onChange: notify,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Render-time (server / pre-mount) slice. Constructs a transient virtual-core instance,
|
|
45
|
+
// reads the initial window as PLAIN DATA, and discards it — nothing live is retained, so
|
|
46
|
+
// nothing live is serialized, and the values recompute identically on resume. With
|
|
47
|
+
// `initialRect` this is a real slice of rows; without it renderSlice returns the trivial
|
|
48
|
+
// window (no items, size 0). The constructor and getVirtualItems()/getTotalSize() are
|
|
49
|
+
// SSR-safe (no DOM access); getScrollElement is forced to null so the probe never touches
|
|
50
|
+
// the scroll element, which does not exist on the server.
|
|
51
|
+
// `range` is the visible window ({ startIndex, endIndex }) the probe computed — plain data,
|
|
52
|
+
// null when there is no slice.
|
|
53
|
+
export function renderSlice(input) {
|
|
54
|
+
// Opt-in: without an explicit viewport hint there is no server slice. Returning the
|
|
55
|
+
// trivial window keeps every example that does not pass `initialRect` byte-for-byte
|
|
56
|
+
// identical to a client-only build (empty container, filled on mount).
|
|
57
|
+
if (!input.initialRect) {
|
|
58
|
+
return { items: [], size: 0, range: null };
|
|
59
|
+
}
|
|
60
|
+
const probe = new Virtualizer(buildOptions({ ...input, getScrollElement: () => null }, () => { }));
|
|
61
|
+
const items = probe.getVirtualItems();
|
|
62
|
+
return { items, size: probe.getTotalSize(), range: probe.range };
|
|
63
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Virtualizer } from "@tanstack/virtual-core";
|
|
2
|
+
import type { Range, VirtualItem, ScrollToOptions } from "@tanstack/virtual-core";
|
|
3
|
+
export interface WindowVirtualizerHandle {
|
|
4
|
+
virtualItems: VirtualItem[];
|
|
5
|
+
totalSize: number;
|
|
6
|
+
range: {
|
|
7
|
+
startIndex: number;
|
|
8
|
+
endIndex: number;
|
|
9
|
+
} | null;
|
|
10
|
+
measureElement: ((el: Element | null) => void) | undefined;
|
|
11
|
+
scrollToIndex: (index: number, options?: ScrollToOptions) => void;
|
|
12
|
+
scrollToOffset: (offset: number, options?: ScrollToOptions) => void;
|
|
13
|
+
measure: () => void;
|
|
14
|
+
resizeItem: (index: number, size: number) => void;
|
|
15
|
+
scrollToEnd: (options?: {
|
|
16
|
+
behavior?: ScrollToOptions['behavior'];
|
|
17
|
+
}) => void;
|
|
18
|
+
isAtEnd: (threshold?: number) => boolean;
|
|
19
|
+
getDistanceFromEnd: () => number;
|
|
20
|
+
}
|
|
21
|
+
export interface Input {
|
|
22
|
+
count: number;
|
|
23
|
+
estimateSize?: (index: number) => number;
|
|
24
|
+
overscan?: number;
|
|
25
|
+
horizontal?: boolean;
|
|
26
|
+
paddingStart?: number;
|
|
27
|
+
paddingEnd?: number;
|
|
28
|
+
scrollPaddingStart?: number;
|
|
29
|
+
scrollPaddingEnd?: number;
|
|
30
|
+
gap?: number;
|
|
31
|
+
lanes?: number;
|
|
32
|
+
initialOffset?: number | (() => number);
|
|
33
|
+
initialRect?: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
};
|
|
37
|
+
getItemKey?: (index: number) => number | string | bigint;
|
|
38
|
+
rangeExtractor?: (range: Range) => number[];
|
|
39
|
+
indexAttribute?: string;
|
|
40
|
+
initialMeasurementsCache?: VirtualItem[];
|
|
41
|
+
anchorTo?: 'start' | 'end';
|
|
42
|
+
followOnAppend?: boolean | ScrollBehavior;
|
|
43
|
+
scrollEndThreshold?: number;
|
|
44
|
+
scrollMargin?: number;
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
isRtl?: boolean;
|
|
47
|
+
isScrollingResetDelay?: number;
|
|
48
|
+
useScrollendEvent?: boolean;
|
|
49
|
+
useAnimationFrameWithResizeObserver?: boolean;
|
|
50
|
+
laneAssignmentMode?: 'estimate' | 'measured';
|
|
51
|
+
useCachedMeasurements?: boolean;
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
measureElement?: (element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<Window, Element>) => number;
|
|
54
|
+
}
|
|
55
|
+
<return = (1 as any as WindowVirtualizerHandle)/>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Virtualizer } from "@tanstack/virtual-core";
|
|
2
|
+
import { buildOptions, renderSlice } from "./options.js";
|
|
3
|
+
<!-- Render-time seed (window counterpart): paints the initial slice into the server HTML when-->
|
|
4
|
+
<!-- `initialRect` is given (empty container with correct total height otherwise), no <if=mounted>-->
|
|
5
|
+
<!-- guard. The live, observing instance is built client-only in onMount and takes over.-->
|
|
6
|
+
<const/seed=renderSlice(input)/>
|
|
7
|
+
<let/items=seed.items/>
|
|
8
|
+
<let/size=seed.size/>
|
|
9
|
+
<let/range=seed.range/>
|
|
10
|
+
<let/v=null/>
|
|
11
|
+
<let/cleanup=null/>
|
|
12
|
+
<lifecycle<{}> onMount() {
|
|
13
|
+
const notify = () => {
|
|
14
|
+
const inst = v;
|
|
15
|
+
if (!inst) return;
|
|
16
|
+
items = inst.getVirtualItems();
|
|
17
|
+
size = inst.getTotalSize();
|
|
18
|
+
range = inst.range;
|
|
19
|
+
};
|
|
20
|
+
v = new Virtualizer(buildOptions(input, notify));
|
|
21
|
+
cleanup = v._didMount();
|
|
22
|
+
v._willUpdate();
|
|
23
|
+
notify();
|
|
24
|
+
} onUpdate() {
|
|
25
|
+
const current = v;
|
|
26
|
+
if (!current) return;
|
|
27
|
+
const notify = () => {
|
|
28
|
+
const inst = v;
|
|
29
|
+
if (!inst) return;
|
|
30
|
+
items = inst.getVirtualItems();
|
|
31
|
+
size = inst.getTotalSize();
|
|
32
|
+
range = inst.range;
|
|
33
|
+
};
|
|
34
|
+
current.setOptions({
|
|
35
|
+
...current.options,
|
|
36
|
+
...buildOptions(input, notify)
|
|
37
|
+
});
|
|
38
|
+
current._willUpdate();
|
|
39
|
+
notify();
|
|
40
|
+
} onDestroy() {
|
|
41
|
+
cleanup?.();
|
|
42
|
+
v = null;
|
|
43
|
+
cleanup = null;
|
|
44
|
+
}/>
|
|
45
|
+
<return={
|
|
46
|
+
virtualItems: items,
|
|
47
|
+
totalSize: size,
|
|
48
|
+
range: range,
|
|
49
|
+
measureElement: v?.measureElement,
|
|
50
|
+
scrollToIndex: (index, options) => v?.scrollToIndex(index, options),
|
|
51
|
+
scrollToOffset: (offset, options) => v?.scrollToOffset(offset, options),
|
|
52
|
+
measure: () => v?.measure(),
|
|
53
|
+
resizeItem: (index, itemSize) => v?.resizeItem(index, itemSize),
|
|
54
|
+
scrollToEnd: options => v?.scrollToEnd(options),
|
|
55
|
+
isAtEnd: threshold => v?.isAtEnd(threshold) ?? false,
|
|
56
|
+
getDistanceFromEnd: () => v?.getDistanceFromEnd() ?? Infinity
|
|
57
|
+
}/>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Virtualizer } from '@tanstack/virtual-core';
|
|
2
|
+
import type { Range, VirtualItem, VirtualizerOptions } from '@tanstack/virtual-core';
|
|
3
|
+
export interface WindowVirtualizerInput {
|
|
4
|
+
count: number;
|
|
5
|
+
estimateSize?: (index: number) => number;
|
|
6
|
+
overscan?: number;
|
|
7
|
+
horizontal?: boolean;
|
|
8
|
+
paddingStart?: number;
|
|
9
|
+
paddingEnd?: number;
|
|
10
|
+
scrollPaddingStart?: number;
|
|
11
|
+
scrollPaddingEnd?: number;
|
|
12
|
+
gap?: number;
|
|
13
|
+
lanes?: number;
|
|
14
|
+
initialOffset?: number | (() => number);
|
|
15
|
+
initialRect?: {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
getItemKey?: (index: number) => number | string | bigint;
|
|
20
|
+
rangeExtractor?: (range: Range) => Array<number>;
|
|
21
|
+
indexAttribute?: string;
|
|
22
|
+
initialMeasurementsCache?: Array<VirtualItem>;
|
|
23
|
+
anchorTo?: 'start' | 'end';
|
|
24
|
+
followOnAppend?: boolean | ScrollBehavior;
|
|
25
|
+
scrollEndThreshold?: number;
|
|
26
|
+
scrollMargin?: number;
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
isRtl?: boolean;
|
|
29
|
+
isScrollingResetDelay?: number;
|
|
30
|
+
useScrollendEvent?: boolean;
|
|
31
|
+
useAnimationFrameWithResizeObserver?: boolean;
|
|
32
|
+
laneAssignmentMode?: 'estimate' | 'measured';
|
|
33
|
+
useCachedMeasurements?: boolean;
|
|
34
|
+
debug?: boolean;
|
|
35
|
+
measureElement?: (element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<Window, Element>) => number;
|
|
36
|
+
}
|
|
37
|
+
export declare function buildOptions(input: WindowVirtualizerInput, notify: () => void): VirtualizerOptions<Window, Element>;
|
|
38
|
+
export declare function renderSlice(input: WindowVirtualizerInput): {
|
|
39
|
+
items: Array<VirtualItem>;
|
|
40
|
+
size: number;
|
|
41
|
+
range: {
|
|
42
|
+
startIndex: number;
|
|
43
|
+
endIndex: number;
|
|
44
|
+
} | null;
|
|
45
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Virtualizer, observeWindowOffset, observeWindowRect, windowScroll, } from '@tanstack/virtual-core';
|
|
2
|
+
// Single source of truth for the input -> virtual-core option mapping,
|
|
3
|
+
// shared by the render-time seed, onMount (construction) and onUpdate (setOptions).
|
|
4
|
+
// Optional inputs are forwarded possibly-undefined on purpose: virtual-core's
|
|
5
|
+
// setOptions() skips undefined keys, so its own defaults apply.
|
|
6
|
+
export function buildOptions(input, notify) {
|
|
7
|
+
return {
|
|
8
|
+
count: input.count,
|
|
9
|
+
estimateSize: input.estimateSize ?? (() => 50),
|
|
10
|
+
getScrollElement: () => (typeof document !== 'undefined' ? window : null),
|
|
11
|
+
overscan: input.overscan ?? 5,
|
|
12
|
+
horizontal: input.horizontal,
|
|
13
|
+
paddingStart: input.paddingStart,
|
|
14
|
+
paddingEnd: input.paddingEnd,
|
|
15
|
+
scrollPaddingStart: input.scrollPaddingStart,
|
|
16
|
+
scrollPaddingEnd: input.scrollPaddingEnd,
|
|
17
|
+
gap: input.gap,
|
|
18
|
+
lanes: input.lanes,
|
|
19
|
+
// Consumer override first (mirrors react-virtual's spread-over-defaults), then the
|
|
20
|
+
// live window position on the client / 0 (top) on the server.
|
|
21
|
+
initialOffset: input.initialOffset ??
|
|
22
|
+
(() => typeof document !== 'undefined'
|
|
23
|
+
? input.horizontal
|
|
24
|
+
? window.scrollX
|
|
25
|
+
: window.scrollY
|
|
26
|
+
: 0),
|
|
27
|
+
initialRect: input.initialRect,
|
|
28
|
+
getItemKey: input.getItemKey,
|
|
29
|
+
rangeExtractor: input.rangeExtractor,
|
|
30
|
+
indexAttribute: input.indexAttribute,
|
|
31
|
+
initialMeasurementsCache: input.initialMeasurementsCache,
|
|
32
|
+
anchorTo: input.anchorTo,
|
|
33
|
+
followOnAppend: input.followOnAppend,
|
|
34
|
+
scrollEndThreshold: input.scrollEndThreshold,
|
|
35
|
+
scrollMargin: input.scrollMargin,
|
|
36
|
+
enabled: input.enabled,
|
|
37
|
+
isRtl: input.isRtl,
|
|
38
|
+
isScrollingResetDelay: input.isScrollingResetDelay,
|
|
39
|
+
useScrollendEvent: input.useScrollendEvent,
|
|
40
|
+
useAnimationFrameWithResizeObserver: input.useAnimationFrameWithResizeObserver,
|
|
41
|
+
laneAssignmentMode: input.laneAssignmentMode,
|
|
42
|
+
useCachedMeasurements: input.useCachedMeasurements,
|
|
43
|
+
debug: input.debug,
|
|
44
|
+
measureElement: input.measureElement,
|
|
45
|
+
observeElementRect: observeWindowRect,
|
|
46
|
+
observeElementOffset: observeWindowOffset,
|
|
47
|
+
scrollToFn: windowScroll,
|
|
48
|
+
onChange: notify,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Render-time (server / pre-mount) slice — the window counterpart of the element helper.
|
|
52
|
+
// Opt-in: without `initialRect` there is no server slice (trivial window), which keeps every
|
|
53
|
+
// example that does not pass it byte-for-byte identical to a client-only build. With
|
|
54
|
+
// `initialRect` a transient instance derives the initial window as plain data and is discarded;
|
|
55
|
+
// nothing live is retained or serialized, and the values recompute identically on resume.
|
|
56
|
+
// The probe never observes, so it reads `initialRect` directly (getScrollElement returns null on
|
|
57
|
+
// the server and window on the client, but neither is measured before getVirtualItems()).
|
|
58
|
+
// `range` is the visible window ({ startIndex, endIndex }) the probe computed — plain data,
|
|
59
|
+
// null when there is no slice.
|
|
60
|
+
export function renderSlice(input) {
|
|
61
|
+
if (!input.initialRect) {
|
|
62
|
+
return { items: [], size: 0, range: null };
|
|
63
|
+
}
|
|
64
|
+
const probe = new Virtualizer(buildOptions(input, () => { }));
|
|
65
|
+
const items = probe.getVirtualItems();
|
|
66
|
+
return { items, size: probe.getTotalSize(), range: probe.range };
|
|
67
|
+
}
|
package/marko.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "
|
|
1
|
+
{ "exports": "./dist/tags" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/marko-virtual",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "Headless UI for virtualizing scrollable elements in Marko 6",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,22 +38,25 @@
|
|
|
38
38
|
},
|
|
39
39
|
"./package.json": "./package.json",
|
|
40
40
|
"./marko.json": "./marko.json",
|
|
41
|
-
"./
|
|
41
|
+
"./dist/tags/*": "./dist/tags/*"
|
|
42
42
|
},
|
|
43
|
-
"sideEffects":
|
|
43
|
+
"sideEffects": [
|
|
44
|
+
"**/*.marko"
|
|
45
|
+
],
|
|
44
46
|
"files": [
|
|
45
47
|
"dist",
|
|
46
|
-
"src/tags",
|
|
47
48
|
"marko.json",
|
|
48
49
|
"README.md"
|
|
49
50
|
],
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@tanstack/virtual-core": "3.17.
|
|
52
|
+
"@tanstack/virtual-core": "3.17.7"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@marko/
|
|
55
|
+
"@marko/language-tools": "^2.6.2",
|
|
56
|
+
"@marko/type-check": "3.1.1",
|
|
57
|
+
"@marko/vite": "^6.1.0",
|
|
55
58
|
"@testing-library/dom": "^10.0.0",
|
|
56
|
-
"marko": "^6.
|
|
59
|
+
"marko": "^6.2.2"
|
|
57
60
|
},
|
|
58
61
|
"peerDependencies": {
|
|
59
62
|
"marko": "^6.0.0"
|
|
@@ -61,10 +64,10 @@
|
|
|
61
64
|
"scripts": {
|
|
62
65
|
"clean": "premove ./dist ./coverage",
|
|
63
66
|
"test:eslint": "eslint ./src",
|
|
64
|
-
"test:types": "
|
|
67
|
+
"test:types": "mtc -p tsconfig.typecheck.json",
|
|
65
68
|
"test:lib": "vitest",
|
|
66
69
|
"test:lib:dev": "pnpm run test:lib --watch",
|
|
67
70
|
"test:build": "publint --strict",
|
|
68
|
-
"build": "vite build"
|
|
71
|
+
"build": "vite build && marko-type-check -p tsconfig.tags.json && premove ./dist/tsconfig.tags.tsbuildinfo"
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Virtualizer } from "@tanstack/virtual-core"
|
|
2
|
-
import type { VirtualItem, ScrollToOptions } from "@tanstack/virtual-core"
|
|
3
|
-
import { buildOptions } from "./options"
|
|
4
|
-
|
|
5
|
-
export interface Input {
|
|
6
|
-
count: number
|
|
7
|
-
estimateSize?: (index: number) => number
|
|
8
|
-
getScrollElement: () => Element | null
|
|
9
|
-
overscan?: number
|
|
10
|
-
horizontal?: boolean
|
|
11
|
-
paddingStart?: number
|
|
12
|
-
paddingEnd?: number
|
|
13
|
-
scrollPaddingStart?: number
|
|
14
|
-
scrollPaddingEnd?: number
|
|
15
|
-
gap?: number
|
|
16
|
-
lanes?: number
|
|
17
|
-
initialOffset?: number | (() => number)
|
|
18
|
-
content: Marko.Body<[{
|
|
19
|
-
virtualItems: VirtualItem[]
|
|
20
|
-
totalSize: number
|
|
21
|
-
measureElement: ((el: Element | null) => void) | undefined
|
|
22
|
-
scrollToIndex: (index: number, options?: ScrollToOptions) => void
|
|
23
|
-
scrollToOffset: (offset: number, options?: ScrollToOptions) => void
|
|
24
|
-
}]>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
<let/v = (null as Virtualizer<Element, Element> | null)/>
|
|
28
|
-
<let/cleanup = (null as (() => void) | null)/>
|
|
29
|
-
<let/items = ([] as VirtualItem[])/>
|
|
30
|
-
<let/size = 0/>
|
|
31
|
-
|
|
32
|
-
<lifecycle<{}>
|
|
33
|
-
onMount() {
|
|
34
|
-
const notify = () => {
|
|
35
|
-
const inst = v
|
|
36
|
-
if (!inst) return
|
|
37
|
-
items = inst.getVirtualItems()
|
|
38
|
-
size = inst.getTotalSize()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
v = new Virtualizer<Element, Element>(buildOptions(input, notify))
|
|
42
|
-
cleanup = v!._didMount()
|
|
43
|
-
v!._willUpdate()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
onUpdate() {
|
|
47
|
-
const current = v
|
|
48
|
-
if (!current) return
|
|
49
|
-
|
|
50
|
-
const notify = () => {
|
|
51
|
-
const inst = v
|
|
52
|
-
if (!inst) return
|
|
53
|
-
items = inst.getVirtualItems()
|
|
54
|
-
size = inst.getTotalSize()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
current.setOptions({ ...current.options, ...buildOptions(input, notify) })
|
|
58
|
-
|
|
59
|
-
// _willUpdate() is a no-op when the scroll element is unchanged, so
|
|
60
|
-
// notify() explicitly afterwards to recompute on count/option changes.
|
|
61
|
-
current._willUpdate()
|
|
62
|
-
notify()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
onDestroy() {
|
|
66
|
-
cleanup?.()
|
|
67
|
-
v = null
|
|
68
|
-
cleanup = null
|
|
69
|
-
}
|
|
70
|
-
/>
|
|
71
|
-
|
|
72
|
-
<${input.content}
|
|
73
|
-
virtualItems=items
|
|
74
|
-
totalSize=size
|
|
75
|
-
measureElement=v?.measureElement
|
|
76
|
-
scrollToIndex=(index: number, options?: ScrollToOptions) => v?.scrollToIndex(index, options)
|
|
77
|
-
scrollToOffset=(offset: number, options?: ScrollToOptions) => v?.scrollToOffset(offset, options)
|
|
78
|
-
/>
|