,
): JSX.Element {
- const [virtual, onScroll] = createVirtualList({
+ const [virtual, onScroll, { scrollToItem }] = createVirtualList({
items: () => props.each,
rootHeight: () => props.rootHeight,
rowHeight: () => props.rowHeight,
overscanCount: () => props.overscanCount || 1,
});
+ props.setScrollToItem((itemIndex: number) => scrollToItem(itemIndex, scrollContainer));
+
+ let scrollContainer!: HTMLDivElement;
+
return (
Date: Mon, 30 Jun 2025 18:23:18 -0700
Subject: [PATCH 2/5] Add note to doc about rowHeight's dynamic capability
---
packages/virtual/src/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/virtual/src/index.tsx b/packages/virtual/src/index.tsx
index 339516e9c..3dd0ed7d4 100644
--- a/packages/virtual/src/index.tsx
+++ b/packages/virtual/src/index.tsx
@@ -120,7 +120,7 @@ type VirtualListProps = {
* @param fallback the optional fallback to display if the list of items to display is empty
* @param overscanCount the number of elements to render both before and after the visible section of the list, so passing 5 will render 5 items before the list, and 5 items after. Defaults to 1, cannot be set to zero. This is necessary to hide the blank space around list items when scrolling
* @param rootHeight the height of the root element of the virtualizedList itself
- * @param rowHeight the height of individual rows in the virtualizedList
+ * @param rowHeight the height of individual rows in the virtualizedList—can be static if just a number is provided, or dynamic if a callback is passed
* @returns virtualized list component
*/
export function VirtualList(
From a129664811460982a2e84083bae1d40b731e235e Mon Sep 17 00:00:00 2001
From: jaydevelopsstuff <70743392+jaydevelopsstuff@users.noreply.github.com>
Date: Mon, 30 Jun 2025 18:40:06 -0700
Subject: [PATCH 3/5] Minor comment on use of binary search
---
packages/virtual/src/index.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/virtual/src/index.tsx b/packages/virtual/src/index.tsx
index 3dd0ed7d4..d8dd476e3 100644
--- a/packages/virtual/src/index.tsx
+++ b/packages/virtual/src/index.tsx
@@ -54,6 +54,7 @@ export function createVirtualList({
});
});
+ // Binary Search for performance
const findRowIndexAtOffset = (offset: number) => {
const offsets = rowOffsets();
From 9dc934b741dfc000113807a00b977815bf3a9313 Mon Sep 17 00:00:00 2001
From: jaydevelopsstuff <70743392+jaydevelopsstuff@users.noreply.github.com>
Date: Tue, 1 Jul 2025 15:03:08 -0700
Subject: [PATCH 4/5] Fix reactivity in createVirtualList
---
packages/virtual/src/index.tsx | 35 ++++++++++++++++------------------
1 file changed, 16 insertions(+), 19 deletions(-)
diff --git a/packages/virtual/src/index.tsx b/packages/virtual/src/index.tsx
index d8dd476e3..8f29b26f6 100644
--- a/packages/virtual/src/index.tsx
+++ b/packages/virtual/src/index.tsx
@@ -29,27 +29,21 @@ type VirtualListReturn = [
* @param overscanCount the number of elements to render both before and after the visible section of the list, so passing 5 will render 5 items before the list, and 5 items after. Defaults to 1, cannot be set to zero. This is necessary to hide the blank space around list items when scrolling
* @returns {VirtualListReturn} to use in the list's jsx
*/
-export function createVirtualList({
- items,
- rootHeight,
- rowHeight,
- overscanCount,
-}: VirtualListConfig): VirtualListReturn {
- items = access(items) || ([] as any as T);
- rootHeight = access(rootHeight);
- rowHeight = access(rowHeight);
- overscanCount = access(overscanCount) || 1;
-
- const resolveRowHeight =
- typeof rowHeight === "function" ? rowHeight : (_: T[number], _i: number) => rowHeight;
+export function createVirtualList(
+ cfg: VirtualListConfig,
+): VirtualListReturn {
+ const items = () => access(cfg.items) || ([] as any as T);
+ const overscanCount = () => access(cfg.overscanCount) || 1;
const [offset, setOffset] = createSignal(0);
const rowOffsets = createMemo(() => {
let offset = 0;
- return items.map((item, i) => {
+ return items().map((item, i) => {
const current = offset;
- offset += resolveRowHeight(item, i);
+ const rowHeight = access(cfg.rowHeight);
+
+ offset += typeof rowHeight === "function" ? rowHeight(item, i) : rowHeight;
return current;
});
});
@@ -72,12 +66,15 @@ export function createVirtualList({
return lo;
};
- const getFirstIdx = () => Math.max(0, findRowIndexAtOffset(offset()) - overscanCount);
+ const getFirstIdx = () => Math.max(0, findRowIndexAtOffset(offset()) - overscanCount());
// const getFirstIdx = () => Math.max(0, Math.floor(offset() / rowHeight) - overscanCount);
const getLastIdx = () =>
- Math.min(items.length, findRowIndexAtOffset(offset() + rootHeight) + overscanCount);
+ Math.min(
+ items().length,
+ findRowIndexAtOffset(offset() + access(cfg.rootHeight)) + overscanCount(),
+ );
// const getLastIdx = () =>
// Math.min(
@@ -87,9 +84,9 @@ export function createVirtualList({
return [
() => ({
- containerHeight: items.length !== 0 ? rowOffsets()[items.length - 1]! : 0,
+ containerHeight: items().length !== 0 ? rowOffsets()[items().length - 1]! : 0,
viewerTop: rowOffsets()[getFirstIdx()]!,
- visibleItems: items.slice(getFirstIdx(), getLastIdx()) as unknown as T,
+ visibleItems: items().slice(getFirstIdx(), getLastIdx()) as unknown as T,
}),
e => {
// @ts-expect-error
From c09050d7f9527ac49c2342fe79ae1850ca707d32 Mon Sep 17 00:00:00 2001
From: jaydevelopsstuff <70743392+jaydevelopsstuff@users.noreply.github.com>
Date: Thu, 3 Jul 2025 02:52:11 -0700
Subject: [PATCH 5/5] Ensure parent width is retained within actual scroll
viewer and make callback indexes more useful
---
packages/virtual/src/index.tsx | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/packages/virtual/src/index.tsx b/packages/virtual/src/index.tsx
index 8f29b26f6..b3b573e3b 100644
--- a/packages/virtual/src/index.tsx
+++ b/packages/virtual/src/index.tsx
@@ -17,7 +17,11 @@ type VirtualListReturn = [
visibleItems: T;
}>,
onScroll: (e: Event) => void,
- { scrollToItem: (itemIndex: number, scrollContainer: HTMLElement) => void },
+ {
+ getFirstIdx: () => number;
+ getLastIdx: () => number;
+ scrollToItem: (itemIndex: number, scrollContainer: HTMLElement) => void;
+ },
];
/**
@@ -93,6 +97,8 @@ export function createVirtualList(
if (e.target?.scrollTop !== undefined) setOffset(e.target.scrollTop);
},
{
+ getFirstIdx,
+ getLastIdx,
scrollToItem: (itemIndex: number, scrollContainer: HTMLElement) => {
scrollContainer.scrollTop = rowOffsets()[itemIndex]!;
},
@@ -101,7 +107,7 @@ export function createVirtualList(
}
type VirtualListProps = {
- children: (item: T[number], index: Accessor) => U;
+ children: (item: T[number], index: Accessor, rawIndex: Accessor) => U;
each: T | undefined | null | false;
fallback?: JSX.Element;
overscanCount?: number;
@@ -124,7 +130,7 @@ type VirtualListProps = {
export function VirtualList(
props: VirtualListProps,
): JSX.Element {
- const [virtual, onScroll, { scrollToItem }] = createVirtualList({
+ const [virtual, onScroll, { scrollToItem, getFirstIdx }] = createVirtualList({
items: () => props.each,
rootHeight: () => props.rootHeight,
rowHeight: () => props.rowHeight,
@@ -155,10 +161,11 @@ export function VirtualList(
style={{
position: "absolute",
top: `${virtual().viewerTop}px`,
+ width: "inherit",
}}
>
- {props.children}
+ {(item, index) => props.children(item, () => getFirstIdx() + index(), index)}