diff --git a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx index b2e6fcaaaf402..10bf817b7363f 100644 --- a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx +++ b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx @@ -29,6 +29,7 @@ describe("HistorySwipeNavigation", () => { ["a swipe that started in a closed dialog keeps the thread", "closed-dialog", 0, -130, false], ] as const)("%s", async (_name, scroller, scrollLeft, deltaX, navigates) => { navigateHistory.mockReset(); + using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("MacIntel"); await using screen = await render(); const panel = screen.container.appendChild(document.createElement("div")); const root = scroller.startsWith("shadow") ? panel.attachShadow({ mode: "open" }) : panel; @@ -58,4 +59,16 @@ describe("HistorySwipeNavigation", () => { expect(navigateHistory).toHaveBeenCalledTimes(navigates ? 1 : 0); }); + + it("a horizontal mouse wheel tick off macOS keeps the thread", async () => { + navigateHistory.mockReset(); + using _platform = vi.spyOn(navigator, "platform", "get").mockReturnValue("Win32"); + await using screen = await render(); + const content = screen.container.appendChild(document.createElement("div")); + content.style.cssText = "height: 20px; width: 100px"; + + swipe(content, -120); + + expect(navigateHistory).not.toHaveBeenCalled(); + }); }); diff --git a/apps/web/src/components/HistorySwipeNavigation.tsx b/apps/web/src/components/HistorySwipeNavigation.tsx index 767b4285c696f..e1adc04491a91 100644 --- a/apps/web/src/components/HistorySwipeNavigation.tsx +++ b/apps/web/src/components/HistorySwipeNavigation.tsx @@ -1,7 +1,7 @@ import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; import { useEffect, useState } from "react"; -import { cn } from "../lib/utils"; +import { cn, isMacPlatform } from "../lib/utils"; import { canNavigateHistory, navigateHistory, type HistoryDirection } from "../navigationHistory"; const SWIPE_TRIGGER_DISTANCE = 120; @@ -88,12 +88,16 @@ export function isMomentumTail(deltas: readonly number[]): boolean { ); } -/** Two-finger horizontal trackpad swipes go back and forward, like a browser. */ +/** + * Two-finger horizontal trackpad swipes go back and forward, like a browser. + * macOS only: on Windows and Linux a mouse tilt wheel is indistinguishable from a trackpad swipe. + */ export function HistorySwipeNavigation() { const [swipe, setSwipe] = useState(null); // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- window wheel and desktop gesture listeners track trackpad swipes useEffect(() => { + if (!isMacPlatform(navigator.platform)) return; let gesture: SwipeGesture | null = null; const hideIndicator = () => setSwipe((current) => (current === null ? null : { ...current, tracking: false }));