@tanstack/marko-virtual 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/cjs/index.cjs +10 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +54 -0
- package/dist/cjs/tags/virtualizer/options.d.cts +16 -0
- package/dist/cjs/tags/window-virtualizer/options.d.cts +13 -0
- package/dist/esm/index.d.ts +54 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/tags/virtualizer/options.d.ts +16 -0
- package/dist/esm/tags/window-virtualizer/options.d.ts +13 -0
- package/marko.json +1 -0
- package/package.json +70 -0
- package/src/tags/virtualizer/index.marko +78 -0
- package/src/tags/virtualizer/options.ts +50 -0
- package/src/tags/window-virtualizer/index.marko +75 -0
- package/src/tags/window-virtualizer/options.ts +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# @tanstack/marko-virtual
|
|
2
|
+
|
|
3
|
+
Headless UI for virtualizing scrollable elements in [Marko 6](https://markojs.com), built on top of [`@tanstack/virtual-core`](https://tanstack.com/virtual).
|
|
4
|
+
|
|
5
|
+
Part of the [TanStack Virtual](https://tanstack.com/virtual) family.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @tanstack/marko-virtual
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @tanstack/marko-virtual
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Peer dependency:** `marko ^6.0.0`
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
Add to your project's `marko.json` to make the tags available:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"taglib-imports": ["@tanstack/marko-virtual/marko.json"]
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### `<virtualizer>` — scrollable container
|
|
30
|
+
|
|
31
|
+
Use when you control the scroll element (a div with `overflow: auto`).
|
|
32
|
+
|
|
33
|
+
```marko
|
|
34
|
+
<!DOCTYPE html>
|
|
35
|
+
<html>
|
|
36
|
+
<body>
|
|
37
|
+
|
|
38
|
+
<let/mounted = false/>
|
|
39
|
+
<script() { mounted = true }/>
|
|
40
|
+
|
|
41
|
+
<div/scrollEl style="height: 400px; overflow-y: auto">
|
|
42
|
+
<if=mounted>
|
|
43
|
+
<virtualizer|{ virtualItems, totalSize }|
|
|
44
|
+
count=10000
|
|
45
|
+
estimateSize=() => 35
|
|
46
|
+
getScrollElement=scrollEl
|
|
47
|
+
>
|
|
48
|
+
<div style=`height: ${totalSize}px; position: relative`>
|
|
49
|
+
<for|item| of=virtualItems>
|
|
50
|
+
<div
|
|
51
|
+
data-index=item.index
|
|
52
|
+
style=`position: absolute; transform: translateY(${item.start}px);
|
|
53
|
+
height: ${item.size}px; width: 100%`
|
|
54
|
+
>
|
|
55
|
+
Row ${item.index}
|
|
56
|
+
</div>
|
|
57
|
+
</for>
|
|
58
|
+
</div>
|
|
59
|
+
</virtualizer>
|
|
60
|
+
</if>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **Why `mounted`?** The virtualizer measures the scroll element's dimensions on mount. During SSR the DOM has no layout, so `offsetHeight` is 0 and no items would be calculated. The `<if=mounted>` guard ensures the virtualizer only renders after the browser has real dimensions available.
|
|
68
|
+
|
|
69
|
+
### `<window-virtualizer>` — full-page scrolling
|
|
70
|
+
|
|
71
|
+
Use when the page itself is the scroll container.
|
|
72
|
+
|
|
73
|
+
```marko
|
|
74
|
+
<!DOCTYPE html>
|
|
75
|
+
<html>
|
|
76
|
+
<body>
|
|
77
|
+
|
|
78
|
+
<window-virtualizer|{ virtualItems, totalSize }| count=10000 estimateSize=() => 35>
|
|
79
|
+
<div style=`height: ${totalSize}px; position: relative`>
|
|
80
|
+
<for|item| of=virtualItems>
|
|
81
|
+
<div
|
|
82
|
+
data-index=item.index
|
|
83
|
+
style=`position: absolute; transform: translateY(${item.start}px);
|
|
84
|
+
height: ${item.size}px; width: 100%`
|
|
85
|
+
>
|
|
86
|
+
Row ${item.index}
|
|
87
|
+
</div>
|
|
88
|
+
</for>
|
|
89
|
+
</div>
|
|
90
|
+
</window-virtualizer>
|
|
91
|
+
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Tag parameters
|
|
97
|
+
|
|
98
|
+
Both tags use Marko 6's tag parameters pattern. The body receives virtual state via `|{ ... }|` destructuring:
|
|
99
|
+
|
|
100
|
+
```marko
|
|
101
|
+
<virtualizer|{ virtualItems, totalSize, measureElement, scrollToIndex, scrollToOffset }|
|
|
102
|
+
count=...
|
|
103
|
+
getScrollElement=...
|
|
104
|
+
>
|
|
105
|
+
<!-- virtualItems, totalSize etc are in scope here -->
|
|
106
|
+
</virtualizer>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Reference
|
|
110
|
+
|
|
111
|
+
### `<virtualizer>`
|
|
112
|
+
|
|
113
|
+
| Attribute | Type | Required | Description |
|
|
114
|
+
| -------------------- | --------------------------- | -------- | --------------------------------------------------- |
|
|
115
|
+
| `count` | `number` | ✅ | Total number of items |
|
|
116
|
+
| `getScrollElement` | `() => Element \| null` | ✅ | Returns the scroll container |
|
|
117
|
+
| `estimateSize` | `(index: number) => number` | | Estimated item size in px (default: `50`) |
|
|
118
|
+
| `overscan` | `number` | | Items to render outside the viewport (default: `5`) |
|
|
119
|
+
| `horizontal` | `boolean` | | Enable horizontal scrolling (default: `false`) |
|
|
120
|
+
| `paddingStart` | `number` | | Padding before the first item in px |
|
|
121
|
+
| `paddingEnd` | `number` | | Padding after the last item in px |
|
|
122
|
+
| `scrollPaddingStart` | `number` | | Scroll padding at the start |
|
|
123
|
+
| `scrollPaddingEnd` | `number` | | Scroll padding at the end |
|
|
124
|
+
| `gap` | `number` | | Gap between items in px |
|
|
125
|
+
| `lanes` | `number` | | Number of lanes for grid layouts |
|
|
126
|
+
| `initialOffset` | `number \| (() => number)` | | Initial scroll offset |
|
|
127
|
+
|
|
128
|
+
**Tag parameters provided to body:**
|
|
129
|
+
|
|
130
|
+
| Parameter | Type | Description |
|
|
131
|
+
| ---------------- | ----------------------------------------------------- | -------------------------------------------------------- |
|
|
132
|
+
| `virtualItems` | `VirtualItem[]` | Items to render, with `index`, `start`, `size`, `key` |
|
|
133
|
+
| `totalSize` | `number` | Total scrollable size in px — set on the inner container |
|
|
134
|
+
| `measureElement` | `(el: Element \| null) => void` | Pass to `ref` for dynamic size measurement |
|
|
135
|
+
| `scrollToIndex` | `(index: number, options?: ScrollToOptions) => void` | Scroll to an item by index |
|
|
136
|
+
| `scrollToOffset` | `(offset: number, options?: ScrollToOptions) => void` | Scroll to a px offset |
|
|
137
|
+
|
|
138
|
+
### `<window-virtualizer>`
|
|
139
|
+
|
|
140
|
+
Same as `<virtualizer>` except there is no `getScrollElement`, `horizontal`, or `initialOffset` — the window is always the scroll container.
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
All examples use `@marko/run`. Run any with:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm --filter tanstack-marko-virtual-example-<name> dev
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| Example | Description |
|
|
151
|
+
| ----------------- | ----------------------------------------------- |
|
|
152
|
+
| `fixed` | Fixed-size rows, columns, and grid |
|
|
153
|
+
| `variable` | Variable sizes via `estimateSize` |
|
|
154
|
+
| `dynamic` | Unknown sizes measured via `measureElement` |
|
|
155
|
+
| `grid` | Two virtualizers sharing one scroll element |
|
|
156
|
+
| `smooth-scroll` | `scrollToIndex` with CSS smooth scrolling |
|
|
157
|
+
| `infinite-scroll` | Lazy data loading with a fixed total count |
|
|
158
|
+
| `window` | Full-page scrolling with `<window-virtualizer>` |
|
|
159
|
+
|
|
160
|
+
## Dynamic sizing
|
|
161
|
+
|
|
162
|
+
Use `measureElement` to measure items whose size isn't known upfront:
|
|
163
|
+
|
|
164
|
+
```marko
|
|
165
|
+
<virtualizer|{ virtualItems, totalSize, measureElement }|
|
|
166
|
+
count=items.length
|
|
167
|
+
estimateSize=() => 50
|
|
168
|
+
getScrollElement=scrollEl
|
|
169
|
+
>
|
|
170
|
+
<div style=`height: ${totalSize}px; position: relative`>
|
|
171
|
+
<for|item| of=virtualItems>
|
|
172
|
+
<div
|
|
173
|
+
ref=measureElement
|
|
174
|
+
data-index=item.index
|
|
175
|
+
style=`position: absolute; transform: translateY(${item.start}px); width: 100%`
|
|
176
|
+
>
|
|
177
|
+
${items[item.index]}
|
|
178
|
+
</div>
|
|
179
|
+
</for>
|
|
180
|
+
</div>
|
|
181
|
+
</virtualizer>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> `data-index` is required on measured elements — the virtualizer uses it to map measurements back to items.
|
|
185
|
+
|
|
186
|
+
## TypeScript
|
|
187
|
+
|
|
188
|
+
The tags are fully typed. The Marko language server reads the source `.marko` files directly — no `.d.ts` generation is needed. Ensure `@marko/language-tools` is installed in your editor for IDE support.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT © [Tanner Linsley](https://github.com/tannerlinsley)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const virtualCore = require("@tanstack/virtual-core");
|
|
4
|
+
Object.keys(virtualCore).forEach((k) => {
|
|
5
|
+
if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: () => virtualCore[k]
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tanstack/marko-virtual — Marko 6 adapter for @tanstack/virtual-core
|
|
3
|
+
*
|
|
4
|
+
* Provides row, column, and grid virtualisation for Marko 6 applications
|
|
5
|
+
* via the `<virtualizer>` and `<window-virtualizer>` tags.
|
|
6
|
+
*
|
|
7
|
+
* Tags are auto-discovered by the Marko compiler when this package is
|
|
8
|
+
* installed — no imports needed. Just use `<virtualizer>` or
|
|
9
|
+
* `<window-virtualizer>` directly in your `.marko` files.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```marko
|
|
13
|
+
* // Row virtualisation
|
|
14
|
+
* <div/scrollEl style="height: 400px; overflow-y: auto">
|
|
15
|
+
* <virtualizer|{ virtualItems, totalSize }|
|
|
16
|
+
* count=10000
|
|
17
|
+
* estimateSize=() => 35
|
|
18
|
+
* getScrollElement=scrollEl
|
|
19
|
+
* >
|
|
20
|
+
* <div style=`height: ${totalSize}px; position: relative`>
|
|
21
|
+
* <for|item| of=virtualItems>
|
|
22
|
+
* <div style=`position: absolute; top: 0; transform: translateY(${item.start}px); height: ${item.size}px`>
|
|
23
|
+
* Row ${item.index}
|
|
24
|
+
* </div>
|
|
25
|
+
* </for>
|
|
26
|
+
* </div>
|
|
27
|
+
* </virtualizer>
|
|
28
|
+
* </div>
|
|
29
|
+
*
|
|
30
|
+
* // Column virtualisation — same tag, horizontal=true
|
|
31
|
+
* <virtualizer|{ virtualItems, totalSize }|
|
|
32
|
+
* count=10000
|
|
33
|
+
* estimateSize=() => 100
|
|
34
|
+
* horizontal=true
|
|
35
|
+
* getScrollElement=scrollEl
|
|
36
|
+
* >
|
|
37
|
+
* ...
|
|
38
|
+
* </virtualizer>
|
|
39
|
+
*
|
|
40
|
+
* // Grid — compose two <virtualizer> tags
|
|
41
|
+
* <virtualizer|{ virtualItems: rowItems, totalSize: rowTotal }|
|
|
42
|
+
* count=10000 estimateSize=() => 35 getScrollElement=scrollEl
|
|
43
|
+
* >
|
|
44
|
+
* <virtualizer|{ virtualItems: colItems, totalSize: colTotal }|
|
|
45
|
+
* count=200 estimateSize=() => 100 horizontal=true getScrollElement=scrollEl
|
|
46
|
+
* >
|
|
47
|
+
* ...
|
|
48
|
+
* </virtualizer>
|
|
49
|
+
* </virtualizer>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @packageDocumentation
|
|
53
|
+
*/
|
|
54
|
+
export * from '@tanstack/virtual-core';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VirtualizerOptions } from '@tanstack/virtual-core';
|
|
2
|
+
export interface VirtualizerInput {
|
|
3
|
+
count: number;
|
|
4
|
+
estimateSize?: (index: number) => number;
|
|
5
|
+
getScrollElement: () => Element | null;
|
|
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
|
+
}
|
|
16
|
+
export declare function buildOptions(input: VirtualizerInput, notify: () => void): VirtualizerOptions<Element, Element>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VirtualizerOptions } from '@tanstack/virtual-core';
|
|
2
|
+
export interface WindowVirtualizerInput {
|
|
3
|
+
count: number;
|
|
4
|
+
estimateSize?: (index: number) => number;
|
|
5
|
+
overscan?: number;
|
|
6
|
+
paddingStart?: number;
|
|
7
|
+
paddingEnd?: number;
|
|
8
|
+
scrollPaddingStart?: number;
|
|
9
|
+
scrollPaddingEnd?: number;
|
|
10
|
+
gap?: number;
|
|
11
|
+
lanes?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildOptions(input: WindowVirtualizerInput, notify: () => void): VirtualizerOptions<Window, Element>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tanstack/marko-virtual — Marko 6 adapter for @tanstack/virtual-core
|
|
3
|
+
*
|
|
4
|
+
* Provides row, column, and grid virtualisation for Marko 6 applications
|
|
5
|
+
* via the `<virtualizer>` and `<window-virtualizer>` tags.
|
|
6
|
+
*
|
|
7
|
+
* Tags are auto-discovered by the Marko compiler when this package is
|
|
8
|
+
* installed — no imports needed. Just use `<virtualizer>` or
|
|
9
|
+
* `<window-virtualizer>` directly in your `.marko` files.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```marko
|
|
13
|
+
* // Row virtualisation
|
|
14
|
+
* <div/scrollEl style="height: 400px; overflow-y: auto">
|
|
15
|
+
* <virtualizer|{ virtualItems, totalSize }|
|
|
16
|
+
* count=10000
|
|
17
|
+
* estimateSize=() => 35
|
|
18
|
+
* getScrollElement=scrollEl
|
|
19
|
+
* >
|
|
20
|
+
* <div style=`height: ${totalSize}px; position: relative`>
|
|
21
|
+
* <for|item| of=virtualItems>
|
|
22
|
+
* <div style=`position: absolute; top: 0; transform: translateY(${item.start}px); height: ${item.size}px`>
|
|
23
|
+
* Row ${item.index}
|
|
24
|
+
* </div>
|
|
25
|
+
* </for>
|
|
26
|
+
* </div>
|
|
27
|
+
* </virtualizer>
|
|
28
|
+
* </div>
|
|
29
|
+
*
|
|
30
|
+
* // Column virtualisation — same tag, horizontal=true
|
|
31
|
+
* <virtualizer|{ virtualItems, totalSize }|
|
|
32
|
+
* count=10000
|
|
33
|
+
* estimateSize=() => 100
|
|
34
|
+
* horizontal=true
|
|
35
|
+
* getScrollElement=scrollEl
|
|
36
|
+
* >
|
|
37
|
+
* ...
|
|
38
|
+
* </virtualizer>
|
|
39
|
+
*
|
|
40
|
+
* // Grid — compose two <virtualizer> tags
|
|
41
|
+
* <virtualizer|{ virtualItems: rowItems, totalSize: rowTotal }|
|
|
42
|
+
* count=10000 estimateSize=() => 35 getScrollElement=scrollEl
|
|
43
|
+
* >
|
|
44
|
+
* <virtualizer|{ virtualItems: colItems, totalSize: colTotal }|
|
|
45
|
+
* count=200 estimateSize=() => 100 horizontal=true getScrollElement=scrollEl
|
|
46
|
+
* >
|
|
47
|
+
* ...
|
|
48
|
+
* </virtualizer>
|
|
49
|
+
* </virtualizer>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @packageDocumentation
|
|
53
|
+
*/
|
|
54
|
+
export * from '@tanstack/virtual-core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { VirtualizerOptions } from '@tanstack/virtual-core';
|
|
2
|
+
export interface VirtualizerInput {
|
|
3
|
+
count: number;
|
|
4
|
+
estimateSize?: (index: number) => number;
|
|
5
|
+
getScrollElement: () => Element | null;
|
|
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
|
+
}
|
|
16
|
+
export declare function buildOptions(input: VirtualizerInput, notify: () => void): VirtualizerOptions<Element, Element>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VirtualizerOptions } from '@tanstack/virtual-core';
|
|
2
|
+
export interface WindowVirtualizerInput {
|
|
3
|
+
count: number;
|
|
4
|
+
estimateSize?: (index: number) => number;
|
|
5
|
+
overscan?: number;
|
|
6
|
+
paddingStart?: number;
|
|
7
|
+
paddingEnd?: number;
|
|
8
|
+
scrollPaddingStart?: number;
|
|
9
|
+
scrollPaddingEnd?: number;
|
|
10
|
+
gap?: number;
|
|
11
|
+
lanes?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildOptions(input: WindowVirtualizerInput, notify: () => void): VirtualizerOptions<Window, Element>;
|
package/marko.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "tags-dir": "./src/tags", "script-lang": "ts" }
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tanstack/marko-virtual",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Headless UI for virtualizing scrollable elements in Marko 6",
|
|
5
|
+
"author": "Tanner Linsley",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/virtual.git",
|
|
10
|
+
"directory": "packages/marko-virtual"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/virtual",
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/tannerlinsley"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"marko",
|
|
19
|
+
"virtual",
|
|
20
|
+
"virtual-core",
|
|
21
|
+
"virtualizer",
|
|
22
|
+
"datagrid"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"types": "dist/esm/index.d.ts",
|
|
26
|
+
"main": "dist/cjs/index.cjs",
|
|
27
|
+
"module": "dist/esm/index.js",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": {
|
|
31
|
+
"types": "./dist/esm/index.d.ts",
|
|
32
|
+
"default": "./dist/esm/index.js"
|
|
33
|
+
},
|
|
34
|
+
"require": {
|
|
35
|
+
"types": "./dist/cjs/index.d.cts",
|
|
36
|
+
"default": "./dist/cjs/index.cjs"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json",
|
|
40
|
+
"./marko.json": "./marko.json",
|
|
41
|
+
"./src/tags/*": "./src/tags/*"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"src/tags",
|
|
47
|
+
"marko.json",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"clean": "premove ./dist ./coverage",
|
|
52
|
+
"test:eslint": "eslint ./src",
|
|
53
|
+
"test:types": "tsc",
|
|
54
|
+
"test:lib": "vitest",
|
|
55
|
+
"test:lib:dev": "pnpm run test:lib --watch",
|
|
56
|
+
"test:build": "publint --strict",
|
|
57
|
+
"build": "vite build"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@tanstack/virtual-core": "workspace:*"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@marko/vite": "^3.0.0",
|
|
64
|
+
"@testing-library/dom": "^10.0.0",
|
|
65
|
+
"marko": "^6.0.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"marko": "^6.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
/>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
elementScroll,
|
|
3
|
+
observeElementOffset,
|
|
4
|
+
observeElementRect,
|
|
5
|
+
} from '@tanstack/virtual-core'
|
|
6
|
+
import type { VirtualizerOptions } from '@tanstack/virtual-core'
|
|
7
|
+
|
|
8
|
+
// The subset of tag input that maps onto virtual-core options. Kept here
|
|
9
|
+
// (rather than imported from index.marko) so this helper has no dependency
|
|
10
|
+
// on the Marko template's generated types.
|
|
11
|
+
export interface VirtualizerInput {
|
|
12
|
+
count: number
|
|
13
|
+
estimateSize?: (index: number) => number
|
|
14
|
+
getScrollElement: () => Element | null
|
|
15
|
+
overscan?: number
|
|
16
|
+
horizontal?: boolean
|
|
17
|
+
paddingStart?: number
|
|
18
|
+
paddingEnd?: number
|
|
19
|
+
scrollPaddingStart?: number
|
|
20
|
+
scrollPaddingEnd?: number
|
|
21
|
+
gap?: number
|
|
22
|
+
lanes?: number
|
|
23
|
+
initialOffset?: number | (() => number)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Single source of truth for the input -> virtual-core option mapping,
|
|
27
|
+
// shared by onMount (construction) and onUpdate (setOptions).
|
|
28
|
+
export function buildOptions(
|
|
29
|
+
input: VirtualizerInput,
|
|
30
|
+
notify: () => void,
|
|
31
|
+
): VirtualizerOptions<Element, Element> {
|
|
32
|
+
return {
|
|
33
|
+
count: input.count,
|
|
34
|
+
estimateSize: input.estimateSize ?? (() => 50),
|
|
35
|
+
getScrollElement: input.getScrollElement,
|
|
36
|
+
overscan: input.overscan ?? 5,
|
|
37
|
+
horizontal: input.horizontal ?? false,
|
|
38
|
+
paddingStart: input.paddingStart,
|
|
39
|
+
paddingEnd: input.paddingEnd,
|
|
40
|
+
scrollPaddingStart: input.scrollPaddingStart,
|
|
41
|
+
scrollPaddingEnd: input.scrollPaddingEnd,
|
|
42
|
+
gap: input.gap,
|
|
43
|
+
lanes: input.lanes,
|
|
44
|
+
initialOffset: input.initialOffset,
|
|
45
|
+
observeElementRect,
|
|
46
|
+
observeElementOffset,
|
|
47
|
+
scrollToFn: elementScroll,
|
|
48
|
+
onChange: notify,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
overscan?: number
|
|
9
|
+
paddingStart?: number
|
|
10
|
+
paddingEnd?: number
|
|
11
|
+
scrollPaddingStart?: number
|
|
12
|
+
scrollPaddingEnd?: number
|
|
13
|
+
gap?: number
|
|
14
|
+
lanes?: number
|
|
15
|
+
content: Marko.Body<[{
|
|
16
|
+
virtualItems: VirtualItem[]
|
|
17
|
+
totalSize: number
|
|
18
|
+
measureElement: ((el: Element | null) => void) | undefined
|
|
19
|
+
scrollToIndex: (index: number, options?: ScrollToOptions) => void
|
|
20
|
+
scrollToOffset: (offset: number, options?: ScrollToOptions) => void
|
|
21
|
+
}]>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
<let/v = (null as Virtualizer<Window, Element> | null)/>
|
|
25
|
+
<let/cleanup = (null as (() => void) | null)/>
|
|
26
|
+
<let/items = ([] as VirtualItem[])/>
|
|
27
|
+
<let/size = 0/>
|
|
28
|
+
|
|
29
|
+
<lifecycle<{}>
|
|
30
|
+
onMount() {
|
|
31
|
+
const notify = () => {
|
|
32
|
+
const inst = v
|
|
33
|
+
if (!inst) return
|
|
34
|
+
items = inst.getVirtualItems()
|
|
35
|
+
size = inst.getTotalSize()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
v = new Virtualizer<Window, Element>(buildOptions(input, notify))
|
|
39
|
+
cleanup = v!._didMount()
|
|
40
|
+
v!._willUpdate()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onUpdate() {
|
|
44
|
+
const current = v
|
|
45
|
+
if (!current) return
|
|
46
|
+
|
|
47
|
+
const notify = () => {
|
|
48
|
+
const inst = v
|
|
49
|
+
if (!inst) return
|
|
50
|
+
items = inst.getVirtualItems()
|
|
51
|
+
size = inst.getTotalSize()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
current.setOptions({ ...current.options, ...buildOptions(input, notify) })
|
|
55
|
+
|
|
56
|
+
// _willUpdate() is a no-op when the scroll element is unchanged, so
|
|
57
|
+
// notify() explicitly afterwards to recompute on count/option changes.
|
|
58
|
+
current._willUpdate()
|
|
59
|
+
notify()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onDestroy() {
|
|
63
|
+
cleanup?.()
|
|
64
|
+
v = null
|
|
65
|
+
cleanup = null
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<${input.content}
|
|
70
|
+
virtualItems=items
|
|
71
|
+
totalSize=size
|
|
72
|
+
measureElement=v?.measureElement
|
|
73
|
+
scrollToIndex=(index: number, options?: ScrollToOptions) => v?.scrollToIndex(index, options)
|
|
74
|
+
scrollToOffset=(offset: number, options?: ScrollToOptions) => v?.scrollToOffset(offset, options)
|
|
75
|
+
/>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
observeWindowOffset,
|
|
3
|
+
observeWindowRect,
|
|
4
|
+
windowScroll,
|
|
5
|
+
} from '@tanstack/virtual-core'
|
|
6
|
+
import type { VirtualizerOptions } from '@tanstack/virtual-core'
|
|
7
|
+
|
|
8
|
+
// The subset of tag input that maps onto virtual-core options. Kept here
|
|
9
|
+
// (rather than imported from index.marko) so this helper has no dependency
|
|
10
|
+
// on the Marko template's generated types.
|
|
11
|
+
export interface WindowVirtualizerInput {
|
|
12
|
+
count: number
|
|
13
|
+
estimateSize?: (index: number) => number
|
|
14
|
+
overscan?: number
|
|
15
|
+
paddingStart?: number
|
|
16
|
+
paddingEnd?: number
|
|
17
|
+
scrollPaddingStart?: number
|
|
18
|
+
scrollPaddingEnd?: number
|
|
19
|
+
gap?: number
|
|
20
|
+
lanes?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Single source of truth for the input -> virtual-core option mapping,
|
|
24
|
+
// shared by onMount (construction) and onUpdate (setOptions).
|
|
25
|
+
export function buildOptions(
|
|
26
|
+
input: WindowVirtualizerInput,
|
|
27
|
+
notify: () => void,
|
|
28
|
+
): VirtualizerOptions<Window, Element> {
|
|
29
|
+
return {
|
|
30
|
+
count: input.count,
|
|
31
|
+
estimateSize: input.estimateSize ?? (() => 50),
|
|
32
|
+
getScrollElement: () => (typeof document !== 'undefined' ? window : null),
|
|
33
|
+
overscan: input.overscan ?? 5,
|
|
34
|
+
paddingStart: input.paddingStart,
|
|
35
|
+
paddingEnd: input.paddingEnd,
|
|
36
|
+
scrollPaddingStart: input.scrollPaddingStart,
|
|
37
|
+
scrollPaddingEnd: input.scrollPaddingEnd,
|
|
38
|
+
gap: input.gap,
|
|
39
|
+
lanes: input.lanes,
|
|
40
|
+
initialOffset: () => (typeof document !== 'undefined' ? window.scrollY : 0),
|
|
41
|
+
observeElementRect: observeWindowRect,
|
|
42
|
+
observeElementOffset: observeWindowOffset,
|
|
43
|
+
scrollToFn: windowScroll,
|
|
44
|
+
onChange: notify,
|
|
45
|
+
}
|
|
46
|
+
}
|