perf(app): let the folder picker scroll on the compositor (#57557)

This commit is contained in:
Kit Langton
2026-10-20 16:17:49 -07:00
committed by GitHub
Unverified
parent bff968194f
commit 4b7c6aed57
2 changed files with 42 additions and 21 deletions
@@ -0,0 +1,39 @@
import { expect, test } from "@playwright/test"
import { fileNode, openSettings } from "../utils/workspace"
test.use({ viewport: { width: 1440, height: 900 } })
test("the folder picker's tree scrolls with the mouse wheel inside the modal", async ({ page }) => {
const directory = "/projects/big"
let listing = false
await openSettings(page, {
name: "Picker",
directory,
fileList: () =>
listing
? Array.from({ length: 500 }, (_, index) =>
fileNode(directory, `dir-${String(index).padStart(3, "0")}`, "directory"),
)
: [],
seed: { tabs: [] },
})
await page.getByTestId("settings-screen").getByRole("tab", { name: "Projects", exact: true }).click()
listing = true
await page.getByRole("button", { name: "Add project", exact: true }).click()
const picker = page.getByRole("dialog", { name: "Open project", exact: true })
const scrollTop = () =>
page.evaluate(
() =>
document
.querySelector(".directory-picker-tree")
?.shadowRoot?.querySelector<HTMLElement>("[data-file-tree-virtualized-scroll]")?.scrollTop,
)
await expect.poll(scrollTop).toBe(0)
const browser = await picker.locator(".directory-picker-browser").boundingBox()
await page.mouse.move(browser!.x + browser!.width / 2, browser!.y + browser!.height / 2)
await page.mouse.wheel(0, 300)
await expect.poll(scrollTop).toBeGreaterThan(0)
})
@@ -14,7 +14,6 @@ import {
activeTreeNavigation,
advanceTreePreload,
nextSuggestionIndex,
nextTreeScrollTop,
pickerFileSearchQuery,
pickerAbsoluteInput,
pickerMode,
@@ -389,27 +388,10 @@ export function DirectoryPickerDialog(props: DirectoryPickerDialogProps) {
<div
class="directory-picker-browser"
ref={container}
// The modal's scroll lock sees only the shadow host, not the tree's inner scroller.
// The modal's scroll lock sees only the shadow host, not the tree's inner scroller. Stopping the event here
// keeps the lock from cancelling it, and the passive listener leaves the scroller to the compositor.
on:touchmove={(event) => event.stopPropagation()}
onWheel={(event) => {
const scroller = tree
?.getFileTreeContainer()
?.shadowRoot?.querySelector<HTMLElement>("[data-file-tree-virtualized-scroll]")
if (!scroller) return
const next = nextTreeScrollTop(
scroller.scrollTop,
event.deltaY,
scroller.scrollHeight,
scroller.clientHeight,
)
if (next === scroller.scrollTop) return
event.preventDefault()
scroller.scrollTop = next
scroller.dispatchEvent(new Event("scroll"))
}}
on:wheel={{ handleEvent: (event: WheelEvent) => event.stopPropagation(), passive: true }}
>
<Show when={loading()}>
<div class="directory-picker-state">{language.t("common.loading")}</div>