Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/virtual-solid2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@solid-primitives/virtual": major
---

Migrate to Solid.js v2.0 (beta.10)

## Breaking Changes

**Peer dependency**: `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10` are now required.

### `@solid-primitives/virtual`

- **`createVirtualList`**: returns `[Accessor<VirtualState>, onScroll]` — the first element is now an Accessor that must be called to read `containerHeight`, `viewerTop`, and `visibleItems`.

- **`VirtualList` children**: the child render function now receives `(item: Accessor<T>, index: Accessor<number>)` — `item` is an Accessor and must be called as `item()` to get the value. This matches Solid 2.0's `<For>` component behavior.

- **`{ ownedWrite: true }`**: the internal scroll offset signal uses `ownedWrite` to allow writes from the `onScroll` event handler outside the owning reactive scope.
19 changes: 10 additions & 9 deletions packages/virtual/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function MyComp(): JSX.Element {
const rowHeight = 10;
const overscanCount = 5;

const [{ containerHeight, viewerTop, visibleItems }, onScroll] = createVirtualList({
const [virtual, onScroll] = createVirtualList({
// the list of items - can be a signal
items,
// the height of the root element of the virtualizedList - can be a signal
Expand All @@ -59,20 +59,20 @@ function MyComp(): JSX.Element {
style={{
position: "relative",
width: "100%",
// list container element's height must be set to containerHeight()
height: `${containerHeight()}px`,
// list container element's height must be set to virtual().containerHeight
height: `${virtual().containerHeight}px`,
}}
>
<div
style={{
position: "absolute",
// viewer element's top must be set to viewerTop()
top: `${viewerTop()}px`,
// viewer element's top must be set to virtual().viewerTop
top: `${virtual().viewerTop}px`,
}}
>
{/* only visibleItems() are ultimately rendered */}
<For fallback={"no items"} each={visibleItems()}>
{item => <div>{item}</div>}
{/* only virtual().visibleItems are ultimately rendered */}
<For fallback={"no items"} each={virtual().visibleItems}>
{item => <div>{item()}</div>}
</For>
</div>
</div>
Expand Down Expand Up @@ -100,7 +100,8 @@ function MyComp(): JSX.Element {
>
{
// the flowComponent that will be used to transform the items into rows in the list
item => <div>{item}</div>
// item is an Accessor — call it to get the value
item => <div>{item()}</div>
}
</VirtualList>
```
Expand Down
8 changes: 4 additions & 4 deletions packages/virtual/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createSignal, onMount, onCleanup, type Component } from "solid-js";
import { isServer } from "solid-js/web";
import { createSignal, onSettled, onCleanup, type Component } from "solid-js";
import { isServer } from "@solidjs/web";
import { VirtualList } from "../src/index.jsx";

const intl = new Intl.NumberFormat();
Expand Down Expand Up @@ -71,7 +71,7 @@ const App: Component = () => {
rootHeight={rootHeight()}
rowHeight={rowHeight()}
>
{item => <VirtualListItem item={item} height={rowHeight()} />}
{item => <VirtualListItem item={item()} height={rowHeight()} />}
</VirtualList>
</div>
</div>
Expand Down Expand Up @@ -113,7 +113,7 @@ type VirtualListItemProps = {
};

const VirtualListItem: Component<VirtualListItemProps> = props => {
onMount(() => {
onSettled(() => {
if (!isServer) console.log("item added:", props.item);
});

Expand Down
12 changes: 8 additions & 4 deletions packages/virtual/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@solid-primitives/virtual",
"version": "0.2.3",
"version": "0.3.0",
"description": "A virtualized list component for performantly rendering lists with many elements",
"author": "Spencer Whitehead <spencerwhitehead7@gmail.com>",
"contributors": [],
Expand Down Expand Up @@ -48,17 +48,21 @@
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"vitest": "vitest -c vitest.config.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
},
"dependencies": {
"@solid-primitives/utils": "workspace:^"
},
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "^2.0.0-beta.10",
"solid-js": "^2.0.0-beta.10"
},
"devDependencies": {
"solid-js": "^1.9.7"
"@babel/core": "^7.27.0",
"@solidjs/web": "2.0.0-beta.10",
"babel-preset-solid": "2.0.0-beta.10",
"solid-js": "2.0.0-beta.10"
}
}
48 changes: 26 additions & 22 deletions packages/virtual/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { For, createSignal } from "solid-js";
import type { Accessor, JSX } from "solid-js";
import type { Accessor } from "solid-js";
import type { JSX } from "@solidjs/web";
import { access } from "@solid-primitives/utils";
import type { MaybeAccessor } from "@solid-primitives/utils";

Expand Down Expand Up @@ -34,36 +35,39 @@ export function createVirtualList<T extends readonly any[]>({
rowHeight,
overscanCount,
}: VirtualListConfig<T>): VirtualListReturn<T> {
items = access(items) || ([] as any as T);
rootHeight = access(rootHeight);
rowHeight = access(rowHeight);
overscanCount = access(overscanCount) || 1;
const [offset, setOffset] = createSignal(0, { ownedWrite: true });

const [offset, setOffset] = createSignal(0);

const getFirstIdx = () => Math.max(0, Math.floor(offset() / rowHeight) - overscanCount);

const getLastIdx = () =>
Math.min(
items.length,
Math.floor(offset() / rowHeight) + Math.ceil(rootHeight / rowHeight) + overscanCount,
);
const getItems = () => (access(items) || []) as unknown as T;
const getRootHeight = () => access(rootHeight);
const getRowHeight = () => access(rowHeight);
const getOverscanCount = () => access(overscanCount) || 1;

return [
() => ({
containerHeight: items.length * rowHeight,
viewerTop: getFirstIdx() * rowHeight,
visibleItems: items.slice(getFirstIdx(), getLastIdx()) as unknown as T,
}),
() => {
const resolvedItems = getItems();
const rowH = getRowHeight();
const currentOffset = offset();
const overscan = getOverscanCount();
const firstIdx = Math.max(0, Math.floor(currentOffset / rowH) - overscan);
const lastIdx = Math.min(
resolvedItems.length,
Math.floor(currentOffset / rowH) + Math.ceil(getRootHeight() / rowH) + overscan,
);
return {
containerHeight: resolvedItems.length * rowH,
viewerTop: firstIdx * rowH,
visibleItems: resolvedItems.slice(firstIdx, lastIdx) as unknown as T,
};
},
e => {
// @ts-expect-error
if (e.target?.scrollTop !== undefined) setOffset(e.target.scrollTop);
const target = e.target as HTMLElement | null;
if (target?.scrollTop !== undefined) setOffset(target.scrollTop);
},
];
}

type VirtualListProps<T extends readonly any[], U extends JSX.Element> = {
children: (item: T[number], index: Accessor<number>) => U;
children: (item: Accessor<T[number]>, index: Accessor<number>) => U;
each: T | undefined | null | false;
fallback?: JSX.Element;
overscanCount?: number;
Expand Down
Loading