From 4224bdc68a1a2abe780c3817dc22f5bbd9fb7753 Mon Sep 17 00:00:00 2001 From: yzxcj797 <1784931579@qq.com> Date: Tue, 18 Aug 2026 17:32:41 +0800 Subject: [PATCH] Fix horizontal wheel panning on the canvas Resolve wheel pan deltas through a helper that accepts both browser reporting conventions for Shift + wheel: Chromium on macOS translates the vertical input to deltaX while other platforms keep deltaY. Also apply plain deltaX so native horizontal wheels and trackpads pan the canvas instead of being ignored. Fixes #1108 --- package.json | 3 +- src/components/EditorCanvas/Canvas.jsx | 13 +++------ src/utils/wheel.js | 17 +++++++++++ src/utils/wheel.test.js | 40 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 src/utils/wheel.js create mode 100644 src/utils/wheel.test.js diff --git a/package.json b/package.json index 848104764..68f03fa92 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "test": "node --test" }, "dependencies": { "@dagrejs/dagre": "^3.1.1", diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx index 976dcd33c..a11614fe7 100644 --- a/src/components/EditorCanvas/Canvas.jsx +++ b/src/components/EditorCanvas/Canvas.jsx @@ -32,6 +32,7 @@ import { useTranslation } from "react-i18next"; import { useEventListener } from "usehooks-ts"; import { areFieldsCompatible, getTableHeight } from "../../utils/utils"; import { getRectFromEndpoints, isInsideRect } from "../../utils/rect"; +import { getWheelPanDelta } from "../../utils/wheel"; import { State, noteWidth } from "../../data/constants"; import { nanoid } from "nanoid"; @@ -697,20 +698,14 @@ export default function Canvas() { }, zoom: e.deltaY <= 0 ? prev.zoom * 1.05 : prev.zoom / 1.05, })); - } else if (e.shiftKey) { - setTransform((prev) => ({ - ...prev, - pan: { - ...prev.pan, - x: prev.pan.x + e.deltaY / prev.zoom, - }, - })); } else { + const delta = getWheelPanDelta(e); setTransform((prev) => ({ ...prev, pan: { ...prev.pan, - y: prev.pan.y + e.deltaY / prev.zoom, + x: prev.pan.x + delta.x / prev.zoom, + y: prev.pan.y + delta.y / prev.zoom, }, })); } diff --git a/src/utils/wheel.js b/src/utils/wheel.js new file mode 100644 index 000000000..5834781c6 --- /dev/null +++ b/src/utils/wheel.js @@ -0,0 +1,17 @@ +/** + * Resolve the canvas pan movement produced by a wheel event. + * + * Browsers disagree on how Shift + wheel is reported: Chromium on macOS + * translates the vertical wheel input into a horizontal event (deltaX with + * deltaY === 0) while other platforms keep the movement in deltaY. Native + * horizontal wheels and trackpads also report deltaX without Shift. + * + * @param {WheelEvent} wheelEvent + * @returns {{x: number, y: number}} Pan delta in diagram units before zoom scaling. + */ +export function getWheelPanDelta(wheelEvent) { + if (wheelEvent.shiftKey) { + return { x: wheelEvent.deltaX || wheelEvent.deltaY, y: 0 }; + } + return { x: wheelEvent.deltaX, y: wheelEvent.deltaY }; +} diff --git a/src/utils/wheel.test.js b/src/utils/wheel.test.js new file mode 100644 index 000000000..208ba0def --- /dev/null +++ b/src/utils/wheel.test.js @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; + +import { getWheelPanDelta } from "./wheel.js"; + +test("Shift + wheel keeps vertical delta on browsers without axis translation", () => { + const delta = getWheelPanDelta({ shiftKey: true, deltaX: 0, deltaY: 120 }); + + assert.deepEqual(delta, { x: 120, y: 0 }); +}); + +test("Shift + wheel uses deltaX on browsers that translate the axis", () => { + const delta = getWheelPanDelta({ shiftKey: true, deltaX: 120, deltaY: 0 }); + + assert.deepEqual(delta, { x: 120, y: 0 }); +}); + +test("wheel pans horizontally when Shift is not held", () => { + const delta = getWheelPanDelta({ shiftKey: false, deltaX: 120, deltaY: 0 }); + + assert.deepEqual(delta, { x: 120, y: 0 }); +}); + +test("wheel pans vertically when Shift is not held", () => { + const delta = getWheelPanDelta({ shiftKey: false, deltaX: 0, deltaY: 120 }); + + assert.deepEqual(delta, { x: 0, y: 120 }); +}); + +test("wheel pans diagonally when both axes report movement", () => { + const delta = getWheelPanDelta({ shiftKey: false, deltaX: 40, deltaY: 80 }); + + assert.deepEqual(delta, { x: 40, y: 80 }); +}); + +test("wheel with no movement produces no pan", () => { + const delta = getWheelPanDelta({ shiftKey: false, deltaX: 0, deltaY: 0 }); + + assert.deepEqual(delta, { x: 0, y: 0 }); +});