"`;
+exports[`SSR should render items with renderToStaticMarkup and horizontal 1`] = `"
"`;
-exports[`SSR should render items with renderToStaticMarkup and vertical 1`] = `"
"`;
+exports[`SSR should render items with renderToStaticMarkup and vertical 1`] = `"
"`;
-exports[`SSR should render items with renderToString and horizontal 1`] = `"
"`;
+exports[`SSR should render items with renderToString and horizontal 1`] = `"
"`;
-exports[`SSR should render items with renderToString and vertical 1`] = `"
"`;
+exports[`SSR should render items with renderToString and vertical 1`] = `"
"`;
diff --git a/src/react/__snapshots__/Virtualizer.spec.tsx.snap b/src/react/__snapshots__/Virtualizer.spec.tsx.snap
new file mode 100644
index 000000000..673bbcad7
--- /dev/null
+++ b/src/react/__snapshots__/Virtualizer.spec.tsx.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should change components 1`] = `
+
+
+
+ -
+
+ 0
+
+
+ -
+
+ 1
+
+
+ -
+
+ 2
+
+
+ -
+
+ 3
+
+
+ -
+
+ 4
+
+
+
+
+
+`;
diff --git a/src/react/__snapshots__/WVList.rtl.spec.tsx.snap b/src/react/__snapshots__/WVList.rtl.spec.tsx.snap
deleted file mode 100644
index 6f8924d0f..000000000
--- a/src/react/__snapshots__/WVList.rtl.spec.tsx.snap
+++ /dev/null
@@ -1,272 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`rtl should not work in vertical 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`rtl should work in horizontal 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/react/__snapshots__/WVList.spec.tsx.snap b/src/react/__snapshots__/WVList.spec.tsx.snap
deleted file mode 100644
index 8b2fda765..000000000
--- a/src/react/__snapshots__/WVList.spec.tsx.snap
+++ /dev/null
@@ -1,1505 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`horizontal should render 1 children 1`] = `
-
-
-
-`;
-
-exports[`horizontal should render 5 children 1`] = `
-
-
-
-`;
-
-exports[`horizontal should render 100 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`horizontal should render 1000 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`horizontal should render 10000 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`horizontal should render component 1`] = `
-
-
-
-`;
-
-exports[`horizontal should render fragments 1`] = `
-
-
-
-
-
- fragment
-
-
- fragment
-
-
- fragment
-
-
-
-
-
-
-`;
-
-exports[`horizontal should render non elements 1`] = `
-
-
-
-`;
-
-exports[`horizontal should render with given width / height 1`] = `
-
-
-
-`;
-
-exports[`should change components 1`] = `
-
-
-
- -
-
- 0
-
-
- -
-
- 1
-
-
- -
-
- 2
-
-
- -
-
- 3
-
-
- -
-
- 4
-
-
-
-
-
-`;
-
-exports[`should pass attributes to element 1`] = `
-
-
-
-`;
-
-exports[`should pass index to items 1`] = `
-
-
-
-`;
-
-exports[`should render with render prop 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`vertical should render 1 children 1`] = `
-
-
-
-`;
-
-exports[`vertical should render 5 children 1`] = `
-
-
-
-`;
-
-exports[`vertical should render 100 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`vertical should render 1000 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`vertical should render 10000 children 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`vertical should render component 1`] = `
-
-
-
-`;
-
-exports[`vertical should render fragments 1`] = `
-
-
-
-
-
- fragment
-
-
- fragment
-
-
- fragment
-
-
-
-
-
-
-`;
-
-exports[`vertical should render non elements 1`] = `
-
-
-
-`;
-
-exports[`vertical should render with given width / height 1`] = `
-
-
-
-`;
diff --git a/src/react/__snapshots__/WVList.ssr.spec.tsx.snap b/src/react/__snapshots__/WVList.ssr.spec.tsx.snap
deleted file mode 100644
index 339dfa456..000000000
--- a/src/react/__snapshots__/WVList.ssr.spec.tsx.snap
+++ /dev/null
@@ -1,9 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`SSR should render items with renderToStaticMarkup and horizontal 1`] = `"
"`;
-
-exports[`SSR should render items with renderToStaticMarkup and vertical 1`] = `"
"`;
-
-exports[`SSR should render items with renderToString and horizontal 1`] = `"
"`;
-
-exports[`SSR should render items with renderToString and vertical 1`] = `"
"`;
diff --git a/src/react/__snapshots__/WindowVirtualizer.rtl.spec.tsx.snap b/src/react/__snapshots__/WindowVirtualizer.rtl.spec.tsx.snap
new file mode 100644
index 000000000..09406806f
--- /dev/null
+++ b/src/react/__snapshots__/WindowVirtualizer.rtl.spec.tsx.snap
@@ -0,0 +1,264 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`rtl should not work in vertical 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`rtl should work in horizontal 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/react/__snapshots__/WindowVirtualizer.spec.tsx.snap b/src/react/__snapshots__/WindowVirtualizer.spec.tsx.snap
new file mode 100644
index 000000000..2b6f16767
--- /dev/null
+++ b/src/react/__snapshots__/WindowVirtualizer.spec.tsx.snap
@@ -0,0 +1,1360 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`horizontal should render 1 children 1`] = `
+
+
+
+`;
+
+exports[`horizontal should render 5 children 1`] = `
+
+
+
+`;
+
+exports[`horizontal should render 100 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`horizontal should render 1000 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`horizontal should render 10000 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`horizontal should render component 1`] = `
+
+
+
+`;
+
+exports[`horizontal should render fragments 1`] = `
+
+
+
+
+ fragment
+
+
+ fragment
+
+
+ fragment
+
+
+
+
+
+`;
+
+exports[`horizontal should render non elements 1`] = `
+
+
+
+`;
+
+exports[`horizontal should render with given width / height 1`] = `
+
+
+
+`;
+
+exports[`should pass index to items 1`] = `
+
+
+
+`;
+
+exports[`should render with render prop 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`vertical should render 1 children 1`] = `
+
+
+
+`;
+
+exports[`vertical should render 5 children 1`] = `
+
+
+
+`;
+
+exports[`vertical should render 100 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`vertical should render 1000 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`vertical should render 10000 children 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`vertical should render component 1`] = `
+
+
+
+`;
+
+exports[`vertical should render fragments 1`] = `
+
+
+
+
+ fragment
+
+
+ fragment
+
+
+ fragment
+
+
+
+
+
+`;
+
+exports[`vertical should render non elements 1`] = `
+
+
+
+`;
+
+exports[`vertical should render with given width / height 1`] = `
+
+
+
+`;
diff --git a/src/react/__snapshots__/WindowVirtualizer.ssr.spec.tsx.snap b/src/react/__snapshots__/WindowVirtualizer.ssr.spec.tsx.snap
new file mode 100644
index 000000000..64d6815a5
--- /dev/null
+++ b/src/react/__snapshots__/WindowVirtualizer.ssr.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SSR should render items with renderToStaticMarkup and horizontal 1`] = `"
"`;
+
+exports[`SSR should render items with renderToStaticMarkup and vertical 1`] = `"
"`;
+
+exports[`SSR should render items with renderToString and horizontal 1`] = `"
"`;
+
+exports[`SSR should render items with renderToString and vertical 1`] = `"
"`;
diff --git a/src/react/index.ts b/src/react/index.ts
index 9a99a9940..566665097 100644
--- a/src/react/index.ts
+++ b/src/react/index.ts
@@ -1,7 +1,12 @@
export { VList } from "./VList";
export type { VListProps, VListHandle } from "./VList";
-export { WVList } from "./WVList";
-export type { WVListProps, WVListHandle } from "./WVList";
+export { Virtualizer } from "./Virtualizer";
+export type { VirtualizerProps, VirtualizerHandle } from "./Virtualizer";
+export { WindowVirtualizer } from "./WindowVirtualizer";
+export type {
+ WindowVirtualizerProps,
+ WindowVirtualizerHandle,
+} from "./WindowVirtualizer";
export { VGrid as experimental_VGrid } from "./VGrid";
export type {
VGridProps,
@@ -9,9 +14,5 @@ export type {
CustomCellComponent,
CustomCellComponentProps,
} from "./VGrid";
-export type {
- ViewportComponentAttributes,
- CustomViewportComponent,
- CustomViewportComponentProps,
-} from "./Viewport";
+export type * from "./types";
export type { CustomItemComponent, CustomItemComponentProps } from "./ListItem";
diff --git a/src/react/types.ts b/src/react/types.ts
new file mode 100644
index 000000000..8595fa531
--- /dev/null
+++ b/src/react/types.ts
@@ -0,0 +1,17 @@
+import { CSSProperties, ReactNode } from "react";
+
+export type ViewportComponentAttributes = Pick<
+ React.HTMLAttributes
,
+ "className" | "style" | "id" | "role" | "tabIndex" | "onKeyDown"
+> &
+ React.AriaAttributes;
+
+export interface CustomContainerComponentProps {
+ style: CSSProperties;
+ children: ReactNode;
+}
+
+export type CustomContainerComponent = React.ForwardRefExoticComponent<
+ React.PropsWithoutRef &
+ React.RefAttributes
+>;
diff --git a/src/react/utils.ts b/src/react/utils.ts
index cfbc3ddc4..441e6ee93 100644
--- a/src/react/utils.ts
+++ b/src/react/utils.ts
@@ -6,11 +6,6 @@ import { exists, isArray } from "../core/utils";
*/
export const refKey = "current";
-/**
- * @internal
- */
-export const emptyComponents = {};
-
/**
* @internal
*/
diff --git a/src/vue/VList.tsx b/src/vue/VList.tsx
index efc286f7f..a93f3a108 100644
--- a/src/vue/VList.tsx
+++ b/src/vue/VList.tsx
@@ -15,12 +15,14 @@ import {
SCROLL_IDLE,
UPDATE_SCROLL_STATE,
UPDATE_SCROLL_EVENT,
- UPDATE_SCROLL_STOP_EVENT,
+ UPDATE_SCROLL_END_EVENT,
UPDATE_SIZE_STATE,
overscanEndIndex,
overscanStartIndex,
createVirtualStore,
ACTION_ITEMS_LENGTH_CHANGE,
+ getScrollSize,
+ getMinContainerSize,
} from "../core/store";
import { createResizer } from "../core/resizer";
import { createScroller } from "../core/scroller";
@@ -75,7 +77,7 @@ const props = {
* - If not set, initial item sizes will be automatically estimated from measured sizes. This is recommended for most cases.
* - If set, you can opt out estimation and use the value as initial item size.
*/
- initialItemSize: Number,
+ itemSize: Number,
/**
* While true is set, scroll position will be maintained from the end not usual start when items are added to/removed from start. It's recommended to set false if you add to/remove from mid/end of the list because it can cause unexpected behavior. This prop is useful for reverse infinite scrolling.
*/
@@ -88,16 +90,16 @@ const props = {
export const VList = /*#__PURE__*/ defineComponent({
props: props,
- emits: ["scroll", "scrollStop", "rangeChange"],
+ emits: ["scroll", "scrollEnd", "rangeChange"],
setup(props, { emit, expose, slots }) {
const isHorizontal = props.horizontal;
const rootRef = ref();
const store = createVirtualStore(
props.data.length,
- props.initialItemSize ?? 40,
+ props.itemSize ?? 40,
undefined,
undefined,
- !props.initialItemSize
+ !props.itemSize
);
const resizer = createResizer(store, isHorizontal);
const scroller = createScroller(store, isHorizontal);
@@ -113,27 +115,25 @@ export const VList = /*#__PURE__*/ defineComponent({
const unsubscribeOnScroll = store._subscribe(UPDATE_SCROLL_EVENT, () => {
emit("scroll", store._getScrollOffset());
});
- const unsubscribeOnScrollStop = store._subscribe(
- UPDATE_SCROLL_STOP_EVENT,
+ const unsubscribeOnScrollEnd = store._subscribe(
+ UPDATE_SCROLL_END_EVENT,
() => {
- emit("scrollStop");
+ emit("scrollEnd");
}
);
- let cleanupResizer: (() => void) | undefined;
- let cleanupScroller: (() => void) | undefined;
onMounted(() => {
const root = rootRef.value;
if (!root) return;
- cleanupResizer = resizer._observeRoot(root);
- cleanupScroller = scroller._observe(root);
+ resizer._observeRoot(root);
+ scroller._observe(root);
});
onUnmounted(() => {
unsubscribeStore();
unsubscribeOnScroll();
- unsubscribeOnScrollStop();
- if (cleanupResizer) cleanupResizer();
- if (cleanupScroller) cleanupScroller();
+ unsubscribeOnScrollEnd();
+ resizer._dispose();
+ scroller._dispose();
});
watch(
@@ -171,7 +171,7 @@ export const VList = /*#__PURE__*/ defineComponent({
return store._getScrollOffset();
},
get scrollSize() {
- return store._getScrollSize();
+ return getScrollSize(store);
},
get viewportSize() {
return store._getViewportSize();
@@ -188,22 +188,23 @@ export const VList = /*#__PURE__*/ defineComponent({
const [startIndex, endIndex] = store._getRange();
const scrollDirection = store._getScrollDirection();
- const scrollSize = store._getScrollSize();
+ const totalSize = store._getTotalSize();
- const overscanedStartIndex = overscanStartIndex(
- startIndex,
- props.overscan,
- scrollDirection
- );
- const overscanedEndIndex = overscanEndIndex(
- endIndex,
- props.overscan,
- scrollDirection,
- count
- );
+ // https://github.com/inokawa/virtua/issues/252#issuecomment-1822861368
+ const minSize = getMinContainerSize(store);
const items: VNode[] = [];
- for (let i = overscanedStartIndex; i <= overscanedEndIndex; i++) {
+ for (
+ let i = overscanStartIndex(startIndex, props.overscan, scrollDirection),
+ j = overscanEndIndex(
+ endIndex,
+ props.overscan,
+ scrollDirection,
+ count
+ );
+ i <= j;
+ i++
+ ) {
const e = slots.default(props.data![i]!)[0]! as VNode;
const key = e.key;
items.push(
@@ -237,8 +238,9 @@ export const VList = /*#__PURE__*/ defineComponent({
contain: "content",
position: "relative",
visibility: "hidden",
- width: isHorizontal ? scrollSize + "px" : "100%",
- height: isHorizontal ? "100%" : scrollSize + "px",
+ width: isHorizontal ? totalSize + "px" : "100%",
+ height: isHorizontal ? "100%" : totalSize + "px",
+ [isHorizontal ? "minWidth" : "minHeight"]: minSize,
pointerEvents: scrollDirection !== SCROLL_IDLE ? "none" : "auto",
}}
>
@@ -265,7 +267,7 @@ export const VList = /*#__PURE__*/ defineComponent({
/**
* Callback invoked when scrolling stops.
*/
- scrollStop: () => void;
+ scrollEnd: () => void;
/**
* Callback invoked when visible items range changes.
*/
diff --git a/src/vue/__snapshots__/VList.spec.ts.snap b/src/vue/__snapshots__/VList.spec.ts.snap
index b08f4cad6..a4f098adf 100644
--- a/src/vue/__snapshots__/VList.spec.ts.snap
+++ b/src/vue/__snapshots__/VList.spec.ts.snap
@@ -7,7 +7,7 @@ exports[`horizontal should render 0 children 1`] = `
style="display: inline-block; overflow-x: auto; contain: strict; width: 100%; height: 100%;"
>