Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WindowScroller imperative scrolling #540

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

herschelrs
Copy link

Adds imperative scrolling api to WindowScroller. Only react bindings implemented, also no tests written.

@inokawa
Copy link
Owner

inokawa commented Nov 10, 2024

#495

@herschelrs
Copy link
Author

That issue was requesting a two-dimensional windowscroller with imperative scrolling--which you said presents problems. This PR doesn't solve that problem but currently there's not even a one dimensional window scroller with imperative scrolling, so this is surely an improvement?

(If you're likely to merge I'll fix the errors)

Copy link
Owner

@inokawa inokawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, could you add some e2e tests for scrollToIndex like this?

virtua/e2e/VList.spec.ts

Lines 596 to 868 in 231fb7e

test.describe("check if scrollToIndex works", () => {
test.beforeEach(async ({ page }) => {
await page.goto(storyUrl("basics-vlist--scroll-to"));
});
test.describe("align start", () => {
test("mid", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("700");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const firstItem = await getFirstItem(component);
await expect(firstItem.text).toEqual("700");
await expect(firstItem.top).toEqual(0);
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("650");
await expect(await component.innerText()).not.toContain("750");
});
test("start", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("500");
await button.click();
await component.waitForElementState("stable");
await expect(await component.innerText()).toContain("500");
await clearInput(input);
await input.fill("0");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const firstItem = await getFirstItem(component);
await expect(firstItem.text).toEqual("0");
await expect(firstItem.top).toEqual(0);
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("50\n");
});
test("end", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("999");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const lastItem = await getLastItem(component);
await expect(lastItem.text).toEqual("999");
expectInRange(lastItem.bottom, { min: -0.9, max: 1 });
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("949");
});
test("mid smooth", async ({ page, browserName }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
await page.getByRole("checkbox", { name: "smooth" }).click();
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
const scrollListener = listenScrollCount(component);
await clearInput(input);
await input.fill("700");
await button.click();
await page.waitForTimeout(500);
const called = await scrollListener;
// Check if this is smooth scrolling
await expect(called).toBeGreaterThanOrEqual(
// TODO find better way to check in webkit
browserName === "webkit" ? 2 : 10
);
// Check if scrolled precisely
const firstItem = await getFirstItem(component);
await expect(firstItem.text).toEqual("700");
expectInRange(firstItem.top, { min: 0, max: 1 });
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("650");
await expect(await component.innerText()).not.toContain("750");
});
});
test.describe("align end", () => {
test.beforeEach(async ({ page }) => {
await page.getByRole("radio", { name: "end" }).click();
});
test("mid", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("700");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const lastItem = await getLastItem(component);
await expect(lastItem.text).toEqual("700");
expectInRange(lastItem.bottom, { min: 0, max: 1 });
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("650");
await expect(await component.innerText()).not.toContain("750");
});
test("start", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("500");
await button.click();
await component.waitForElementState("stable");
await expect(await component.innerText()).toContain("500");
await clearInput(input);
await input.fill("0");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const firstItem = await getFirstItem(component);
await expect(firstItem.text).toEqual("0");
await expect(firstItem.top).toEqual(0);
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("50\n");
});
test("end", async ({ page }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
await clearInput(input);
await input.fill("999");
await button.click();
await component.waitForElementState("stable");
// Check if scrolled precisely
const lastItem = await getLastItem(component);
await expect(lastItem.text).toEqual("999");
expectInRange(lastItem.bottom, { min: 0, max: 1 });
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("949");
});
test("mid smooth", async ({ page, browserName }) => {
const component = await getScrollable(page);
await component.waitForElementState("stable");
// check if start is displayed
await expect((await getFirstItem(component)).text).toEqual("0");
await page.getByRole("checkbox", { name: "smooth" }).click();
const button = page.getByRole("button", { name: "scroll to index" });
const input = await button.evaluateHandle(
(el) => el.previousSibling as HTMLInputElement
);
const scrollListener = listenScrollCount(component);
await clearInput(input);
await input.fill("700");
await button.click();
await page.waitForTimeout(500);
const called = await scrollListener;
// Check if this is smooth scrolling
await expect(called).toBeGreaterThanOrEqual(
// TODO find better way to check in webkit
browserName === "webkit" ? 2 : 10
);
// Check if scrolled precisely
const lastItem = await getLastItem(component);
await expect(lastItem.text).toEqual("700");
expectInRange(lastItem.bottom, { min: 0, max: 1 });
// Check if unnecessary items are not rendered
await expect(await component.innerText()).not.toContain("650");
await expect(await component.innerText()).not.toContain("750");
});
});
});

Comment on lines +112 to +115
/**
* If you put an element before virtualizer, you have to define its height with this prop.
*/
startMargin: number;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prop is not required by WindowVirtualizer, because it is automatically calculated from DOM.

Suggested change
/**
* If you put an element before virtualizer, you have to define its height with this prop.
*/
startMargin: number;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed like this was necessary, as in, without it my scrolls were off, but I might have been confused. will check.

src/react/WindowVirtualizer.tsx Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants