From 7eb17efb1685c1b1ed4bb78293f563fd2640fa21 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:12:25 +1000 Subject: [PATCH] fix(app): scroll settings page from the gutter (#57598) --- packages/app/e2e/regression/settings.spec.ts | 7 ++++++ packages/app/src/settings/shell.tsx | 26 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/app/e2e/regression/settings.spec.ts b/packages/app/e2e/regression/settings.spec.ts index 6945e5c97e2..d80edc4256f 100644 --- a/packages/app/e2e/regression/settings.spec.ts +++ b/packages/app/e2e/regression/settings.spec.ts @@ -1697,6 +1697,13 @@ for (const viewport of [ await page.mouse.wheel(0, 10000) await expect(main).toHaveJSProperty("scrollTop", 0) + if (viewport.name === "desktop") { + // Wheel over the settings gutter scrolls the page beside it. + await settings.hover({ position: { x: 16, y: 200 } }) + await page.mouse.wheel(0, 200) + await expect.poll(() => panel.evaluate((element) => element.scrollTop)).toBeGreaterThan(0) + } + const pages = [ "Preferences", "Appearance", diff --git a/packages/app/src/settings/shell.tsx b/packages/app/src/settings/shell.tsx index 09802faa188..7f9485f5ebe 100644 --- a/packages/app/src/settings/shell.tsx +++ b/packages/app/src/settings/shell.tsx @@ -199,6 +199,22 @@ export function SettingsScreen() { surface.back() }} + onWheel={(event) => { + if (!root || event.ctrlKey || !event.deltaY || !(event.target instanceof Element)) return + + // The page scrolls inside its panel, so a wheel over the gutters or sidebar would otherwise do nothing. + if (event.target.closest(".settings-content") || scrollsNatively(event.target, root, event.deltaY)) return + const panel = root.querySelector(".settings-content > .settings-panel") + + if (!panel) return + const unit = + event.deltaMode === WheelEvent.DOM_DELTA_PAGE + ? panel.clientHeight + : event.deltaMode === WheelEvent.DOM_DELTA_LINE + ? 16 + : 1 + panel.scrollBy({ top: event.deltaY * unit }) + }} > @@ -216,6 +232,16 @@ export function SettingsScreen() { ) } +function scrollsNatively(target: Element, root: Element, deltaY: number) { + for (let node: Element | null = target; node && node !== root; node = node.parentElement) { + if (!/auto|scroll/.test(getComputedStyle(node).overflowY)) continue + + if (deltaY < 0 ? node.scrollTop > 0 : node.scrollHeight - node.clientHeight - node.scrollTop > 1) return true + } + + return false +} + function RootSettings() { const language = useLanguage() const dialog = useDialog()