@pyreon/virtual 0.22.0 → 0.23.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 +42 -50
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# @pyreon/virtual
|
|
2
2
|
|
|
3
|
-
Pyreon adapter for TanStack Virtual
|
|
3
|
+
Pyreon adapter for TanStack Virtual — efficient rendering of very large lists.
|
|
4
|
+
|
|
5
|
+
`@pyreon/virtual` wraps `@tanstack/virtual-core` so a Pyreon app can render 10k+ items by only drawing the slice in the viewport. `useVirtualizer` is for element-scoped scrolling (an inner scroll container); `useWindowVirtualizer` is for window-scoped scrolling and is SSR-safe. Both take **options as a function** so reactive signals (count, estimateSize, scrollElement ref) trigger automatic recalculation. The exposed reactive surface — `virtualItems`, `totalSize`, `isScrolling` — is updated in a single `batch()` so consumers don't see torn state mid-scroll.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
bun add @pyreon/virtual @
|
|
10
|
+
bun add @pyreon/virtual @pyreon/core @pyreon/reactivity
|
|
11
|
+
# @tanstack/virtual-core is a hard dependency, installed automatically
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
## Quick
|
|
14
|
+
## Quick start (element-scoped)
|
|
12
15
|
|
|
13
16
|
```tsx
|
|
14
17
|
import { signal } from '@pyreon/reactivity'
|
|
@@ -30,7 +33,6 @@ function VirtualList() {
|
|
|
30
33
|
<div style={`height: ${totalSize()}px; position: relative;`}>
|
|
31
34
|
{virtualItems().map((row) => (
|
|
32
35
|
<div
|
|
33
|
-
key={row.key}
|
|
34
36
|
style={`position: absolute; top: 0; width: 100%; height: ${row.size}px; transform: translateY(${row.start}px);`}
|
|
35
37
|
>
|
|
36
38
|
{items[row.index]}
|
|
@@ -42,26 +44,18 @@ function VirtualList() {
|
|
|
42
44
|
}
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
### `useVirtualizer(options)`
|
|
48
|
-
|
|
49
|
-
Create a reactive virtualizer for element-based scrolling. Options are passed as a function so reactive signals can be read inside, and the virtualizer updates automatically when they change.
|
|
50
|
-
|
|
51
|
-
| Parameter | Type | Description |
|
|
52
|
-
| --------- | ----------------------------- | ------------------------------------- |
|
|
53
|
-
| `options` | `() => UseVirtualizerOptions` | Function returning virtualizer config |
|
|
47
|
+
## `useVirtualizer(() => options)`
|
|
54
48
|
|
|
55
|
-
|
|
49
|
+
Element-scoped virtualizer. Pre-fills `observeElementRect`, `observeElementOffset`, and `scrollToFn` for DOM element scrolling — override if you need custom scroll handling.
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
Returns `UseVirtualizerResult`:
|
|
58
52
|
|
|
59
|
-
| Property | Type
|
|
60
|
-
| -------------- |
|
|
61
|
-
| `instance` | `Virtualizer
|
|
62
|
-
| `virtualItems` | `Signal<VirtualItem[]>`
|
|
63
|
-
| `totalSize` | `Signal<number>`
|
|
64
|
-
| `isScrolling` | `Signal<boolean>`
|
|
53
|
+
| Property | Type | Notes |
|
|
54
|
+
| -------------- | --------------------------------------- | ------------------------------------------------------ |
|
|
55
|
+
| `instance` | `Virtualizer<TScrollElement, TItemElement>` | Raw TanStack instance — use for `scrollToIndex`, etc. |
|
|
56
|
+
| `virtualItems` | `Signal<VirtualItem[]>` | Visible items |
|
|
57
|
+
| `totalSize` | `Signal<number>` | Total scrollable size (px) |
|
|
58
|
+
| `isScrolling` | `Signal<boolean>` | Active scroll |
|
|
65
59
|
|
|
66
60
|
```ts
|
|
67
61
|
const parentRef = signal<HTMLDivElement | null>(null)
|
|
@@ -74,19 +68,14 @@ const { virtualItems, totalSize, isScrolling, instance } = useVirtualizer(() =>
|
|
|
74
68
|
overscan: 5,
|
|
75
69
|
}))
|
|
76
70
|
|
|
77
|
-
//
|
|
71
|
+
// Imperative scroll:
|
|
78
72
|
instance.scrollToIndex(500)
|
|
73
|
+
instance.scrollToOffset(2000)
|
|
79
74
|
```
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
## `useWindowVirtualizer(() => options)`
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| Parameter | Type | Description |
|
|
86
|
-
| --------- | ----------------------------------- | -------------------------------------------------------- |
|
|
87
|
-
| `options` | `() => UseWindowVirtualizerOptions` | Function returning config (no `getScrollElement` needed) |
|
|
88
|
-
|
|
89
|
-
**Returns:** `UseWindowVirtualizerResult` — same shape as `UseVirtualizerResult` but typed with `Window` as the scroll element.
|
|
78
|
+
Window-scoped virtualizer. Pre-fills `observeElementRect: observeWindowRect`, `observeElementOffset: observeWindowOffset`, and `scrollToFn: windowScroll`. SSR-safe — checks for `window` / `document` availability before mounting observers.
|
|
90
79
|
|
|
91
80
|
```tsx
|
|
92
81
|
function WindowList() {
|
|
@@ -101,7 +90,6 @@ function WindowList() {
|
|
|
101
90
|
<div style={`height: ${totalSize()}px; position: relative;`}>
|
|
102
91
|
{virtualItems().map((row) => (
|
|
103
92
|
<div
|
|
104
|
-
key={row.key}
|
|
105
93
|
style={`position: absolute; top: 0; width: 100%; height: ${row.size}px; transform: translateY(${row.start}px);`}
|
|
106
94
|
>
|
|
107
95
|
{items[row.index]}
|
|
@@ -114,9 +102,9 @@ function WindowList() {
|
|
|
114
102
|
|
|
115
103
|
## Patterns
|
|
116
104
|
|
|
117
|
-
### Dynamic
|
|
105
|
+
### Dynamic item sizes via `measureElement`
|
|
118
106
|
|
|
119
|
-
|
|
107
|
+
For variable-height items that need to be measured after render:
|
|
120
108
|
|
|
121
109
|
```tsx
|
|
122
110
|
import { measureElement } from '@pyreon/virtual'
|
|
@@ -128,17 +116,15 @@ const { virtualItems, totalSize, instance } = useVirtualizer(() => ({
|
|
|
128
116
|
measureElement,
|
|
129
117
|
}))
|
|
130
118
|
|
|
131
|
-
//
|
|
119
|
+
// Per row:
|
|
132
120
|
virtualItems().map((row) => (
|
|
133
|
-
<div
|
|
121
|
+
<div ref={(el) => instance.measureElement(el)} data-index={row.index}>
|
|
134
122
|
{items[row.index]}
|
|
135
123
|
</div>
|
|
136
124
|
))
|
|
137
125
|
```
|
|
138
126
|
|
|
139
|
-
### Horizontal
|
|
140
|
-
|
|
141
|
-
Set the `horizontal` option for horizontal virtualization.
|
|
127
|
+
### Horizontal lists
|
|
142
128
|
|
|
143
129
|
```ts
|
|
144
130
|
const { virtualItems, totalSize } = useVirtualizer(() => ({
|
|
@@ -149,32 +135,38 @@ const { virtualItems, totalSize } = useVirtualizer(() => ({
|
|
|
149
135
|
}))
|
|
150
136
|
```
|
|
151
137
|
|
|
152
|
-
### Reactive
|
|
153
|
-
|
|
154
|
-
Since options are a function, changing the count signal re-calculates virtual items automatically.
|
|
138
|
+
### Reactive count (filtered lists)
|
|
155
139
|
|
|
156
140
|
```ts
|
|
157
141
|
const filteredItems = signal(allItems)
|
|
142
|
+
|
|
158
143
|
const { virtualItems } = useVirtualizer(() => ({
|
|
159
144
|
count: filteredItems().length,
|
|
160
145
|
getScrollElement: () => parentRef(),
|
|
161
146
|
estimateSize: () => 40,
|
|
162
147
|
}))
|
|
163
148
|
|
|
164
|
-
//
|
|
165
|
-
filteredItems.set(allItems.filter((i) => i.includes(search())))
|
|
149
|
+
// filteredItems.set(allItems.filter(…)) → automatic recalculation
|
|
166
150
|
```
|
|
167
151
|
|
|
168
152
|
## Re-exports from `@tanstack/virtual-core`
|
|
169
153
|
|
|
170
|
-
**Runtime
|
|
154
|
+
**Runtime**: `defaultKeyExtractor`, `defaultRangeExtractor`, `observeElementOffset`, `observeElementRect`, `observeWindowOffset`, `observeWindowRect`, `elementScroll`, `windowScroll`, `measureElement`, `Virtualizer`.
|
|
171
155
|
|
|
172
|
-
**Types
|
|
156
|
+
**Types**: `VirtualizerOptions`, `VirtualItem`, `Range`, `Rect`, `ScrollToOptions`.
|
|
173
157
|
|
|
174
158
|
## Gotchas
|
|
175
159
|
|
|
176
|
-
- Options must be a function `() => opts` for reactive tracking.
|
|
177
|
-
-
|
|
178
|
-
- `virtualItems`, `totalSize`, and `isScrolling`
|
|
179
|
-
-
|
|
180
|
-
-
|
|
160
|
+
- **Options must be a function** `() => opts` for reactive tracking. Reading signals inside is the mechanism for live recalculation.
|
|
161
|
+
- **`instance` is the raw TanStack Virtualizer** — use it for imperative methods (`scrollToIndex`, `scrollToOffset`, `getVirtualItemForOffset`). The signals are the reactive subset.
|
|
162
|
+
- **Signals update via `batch()`** — `virtualItems`, `totalSize`, and `isScrolling` flip together; consumers don't see torn state mid-scroll frame.
|
|
163
|
+
- **Observers mount via `onMount`, dispose via `onUnmount`** — the component must be mounted before scroll observation starts. SSR renders see an empty `virtualItems` array until hydration.
|
|
164
|
+
- **`useWindowVirtualizer` is SSR-safe** — checks for `window` and `document` before mounting; non-browser environments get the safe fallback shape.
|
|
165
|
+
|
|
166
|
+
## Documentation
|
|
167
|
+
|
|
168
|
+
Full docs: [docs.pyreon.dev/docs/virtual](https://docs.pyreon.dev/docs/virtual) (or `docs/docs/virtual.md` in this repo).
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/virtual",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Pyreon adapter for TanStack Virtual",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/virtual#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"lint": "oxlint ."
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@pyreon/core": "^0.
|
|
46
|
-
"@pyreon/reactivity": "^0.
|
|
45
|
+
"@pyreon/core": "^0.23.0",
|
|
46
|
+
"@pyreon/reactivity": "^0.23.0",
|
|
47
47
|
"@tanstack/virtual-core": "^3.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@happy-dom/global-registrator": "^20.8.9",
|
|
51
|
-
"@pyreon/core": "^0.
|
|
51
|
+
"@pyreon/core": "^0.23.0",
|
|
52
52
|
"@pyreon/manifest": "0.13.1",
|
|
53
|
-
"@pyreon/reactivity": "^0.
|
|
54
|
-
"@pyreon/runtime-dom": "^0.
|
|
53
|
+
"@pyreon/reactivity": "^0.23.0",
|
|
54
|
+
"@pyreon/runtime-dom": "^0.23.0"
|
|
55
55
|
}
|
|
56
56
|
}
|