From b563967610ec69ef1df91afb5e20d77e6d7ff777 Mon Sep 17 00:00:00 2001 From: inokawa <48897392+inokawa@users.noreply.github.com> Date: Wed, 24 Jul 2024 22:11:29 +0900 Subject: [PATCH] Improve jump compensation at end --- e2e/VList.spec.ts | 36 ++++++++++++++++ src/core/store.ts | 4 +- stories/react/advanced/Collapse.stories.tsx | 46 +++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/e2e/VList.spec.ts b/e2e/VList.spec.ts index f49404f87..c78eecf5e 100644 --- a/e2e/VList.spec.ts +++ b/e2e/VList.spec.ts @@ -389,6 +389,42 @@ test.describe("check if scroll jump compensation works", () => { } }); + test("resize at bottom", async ({ page, browserName }) => { + await page.goto(storyUrl("advanced-collapse--two-stage-render")); + const component = await getScrollable(page); + const container = await getVirtualizer(page); + await component.waitForElementState("stable"); + await page.waitForTimeout(500); + + // should reach to the bottom within the specified number of tries + for (let i = 0; i <= 1; i++) { + // scroll to bottom + await scrollToBottom(component); + + const prevBottomItem = getLastItem(component); + + // wait for resize completed + await page.waitForTimeout(500); + await container.waitForElementState("stable"); + + const bottomItem = getLastItem(component); + + // check if distance from the bottom isn't changed by resizes + const prevBottom = (await prevBottomItem).bottom; + const bottom = (await bottomItem).bottom; + if ( + browserName === "firefox" + ? Math.abs(bottom - prevBottom) <= 2 + : bottom === prevBottom + ) { + // succeeded + return; + } + } + + throw new Error(`couldn't reach the bottom`); + }); + test("resize with smooth scroll", async ({ page }) => { await page.goto(storyUrl("advanced-collapse--collapse-and-scroll")); const component = await getScrollable(page); diff --git a/src/core/store.ts b/src/core/store.ts index 895d4084f..1785e3780 100644 --- a/src/core/store.ts +++ b/src/core/store.ts @@ -256,7 +256,9 @@ export const createVirtualStore = ( return [ _flushedJump, // Use absolute position not to exceed scrollable bounds - _scrollMode === SCROLL_BY_SHIFT, + _scrollMode === SCROLL_BY_SHIFT || + // https://github.com/inokawa/virtua/discussions/475 + getRelativeScrollOffset() + viewportSize >= getTotalSize(), ]; }, _subscribe(target, cb) { diff --git a/stories/react/advanced/Collapse.stories.tsx b/stories/react/advanced/Collapse.stories.tsx index 8fb6ef653..ec836ac5b 100644 --- a/stories/react/advanced/Collapse.stories.tsx +++ b/stories/react/advanced/Collapse.stories.tsx @@ -232,3 +232,49 @@ const Collapser2 = ({ id, onHidden }: { id: number; onHidden: () => void }) => { ); }; + +const TwoStageRenderItem = ({ index }: { index: number }) => { + const getRandomHeight = () => Math.floor(Math.random() * 100) + 50; // Random height between 50 and 150 px + const getRandomDelay = () => Math.floor(Math.random() * 300) + 100; // Random delay between 100 and 400 ms + + const [immediateHeight] = useState(() => getRandomHeight()); + const [delayedHeight, setDelayedHeight] = useState(null); + + useEffect(() => { + const delay = getRandomDelay(); + const timer = setTimeout(() => { + setDelayedHeight(getRandomHeight()); + }, delay); + + return () => clearTimeout(timer); + }, []); + + return ( +
+
+ Immediate Content {index} (Height: {immediateHeight}px) +
+ {delayedHeight !== null ? ( +
+ Delayed Content {index} (Height: {delayedHeight}px) +
+ ) : ( +
Loading...
+ )} +
+ ); +}; + +export const TwoStageRender: StoryObj = { + render: () => { + return ( +
+ + {Array.from({ length: 100 }).map((_, index) => ( + + ))} + +
+ ); + }, +};