diff --git a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
index b2e6fcaaaf402..1edd2c591627a 100644
--- a/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
+++ b/apps/web/src/components/HistorySwipeNavigation.browser.test.tsx
@@ -58,4 +58,33 @@ describe("HistorySwipeNavigation", () => {
expect(navigateHistory).toHaveBeenCalledTimes(navigates ? 1 : 0);
});
+
+ it("a vertical scroll that started slightly diagonal and drifts sideways keeps the thread", async () => {
+ navigateHistory.mockReset();
+ await using screen = await render();
+ const content = screen.container.appendChild(document.createElement("div"));
+ const wheel = (deltaX: number, deltaY: number) =>
+ content.dispatchEvent(new WheelEvent("wheel", { deltaX, deltaY, bubbles: true }));
+
+ for (let index = 0; index < 2; index++) wheel(-4, -3);
+ for (let index = 0; index < 40; index++) wheel(-3, -25);
+ wheel(0, 0);
+
+ expect(navigateHistory).not.toHaveBeenCalled();
+ });
+
+ it("a swipe that wobbles vertically at the start still navigates", async () => {
+ navigateHistory.mockReset();
+ await using screen = await render();
+ const content = screen.container.appendChild(document.createElement("div"));
+ const wheel = (deltaX: number, deltaY: number) =>
+ content.dispatchEvent(new WheelEvent("wheel", { deltaX, deltaY, bubbles: true }));
+
+ wheel(-5, -4);
+ wheel(0, -2);
+ for (let index = 0; index < 4; index++) wheel(-40, 0);
+ wheel(0, 0);
+
+ expect(navigateHistory).toHaveBeenCalledWith("back");
+ });
});
diff --git a/apps/web/src/components/HistorySwipeNavigation.tsx b/apps/web/src/components/HistorySwipeNavigation.tsx
index 767b4285c696f..3e7341106dddc 100644
--- a/apps/web/src/components/HistorySwipeNavigation.tsx
+++ b/apps/web/src/components/HistorySwipeNavigation.tsx
@@ -17,6 +17,8 @@ type SwipeGesture =
direction: HistoryDirection;
distance: number;
deltas: number[];
+ travelX: number;
+ travelY: number;
y: number;
bounds: SwipeBounds;
}
@@ -68,6 +70,8 @@ function lockSwipeAxis(
direction,
distance: Math.abs(sumX),
deltas: [],
+ travelX: Math.abs(sumX),
+ travelY: Math.abs(sumY),
y: pending.y,
bounds: readMainContentBounds(),
};
@@ -96,10 +100,14 @@ export function HistorySwipeNavigation() {
useEffect(() => {
let gesture: SwipeGesture | null = null;
const hideIndicator = () =>
- setSwipe((current) => (current === null ? null : { ...current, tracking: false }));
+ setSwipe((current) => (current?.tracking ? { ...current, tracking: false } : current));
// The fingers lifted: navigate if the swipe went far enough, then ignore its momentum.
const release = () => {
- if (gesture?.kind === "tracking" && gesture.distance >= SWIPE_TRIGGER_DISTANCE) {
+ if (
+ gesture?.kind === "tracking" &&
+ gesture.travelX >= gesture.travelY &&
+ gesture.distance >= SWIPE_TRIGGER_DISTANCE
+ ) {
navigateHistory(gesture.direction);
}
gesture = { kind: "ignored" };
@@ -130,9 +138,13 @@ export function HistorySwipeNavigation() {
} else {
const delta = gesture.direction === "back" ? -event.deltaX : event.deltaX;
if (event.deltaX === 0 && event.deltaY === 0) return release();
+ gesture.travelX += Math.abs(event.deltaX);
+ gesture.travelY += Math.abs(event.deltaY);
gesture.deltas = [...gesture.deltas.slice(1 - MOMENTUM_SAMPLE_SIZE), delta];
if (isMomentumTail(gesture.deltas)) return release();
gesture.distance = Math.max(0, gesture.distance + delta);
+ // A scroll that locked on a diagonal start can turn out to be vertical.
+ if (gesture.travelY > gesture.travelX) return hideIndicator();
}
setSwipe({
direction: gesture.direction,