Skip to content

Commit 56064f6

Browse files
committed
paint-canvas-pan: fix SVG overlay blocking panning; suppress Alt-menu on keyup
- Switch pointerdown/move/up to document-level capture listeners so events targeting the SVG overlay (a sibling of the canvas, not a descendant) are intercepted correctly for middle-button and space-drag panning. - Read paper.view.element dynamically instead of caching it at startup, so panning still works after the paint editor unmounts and remounts when the user navigates away from and back to the Costumes tab. - Suppress Alt keyup in capture phase while the canvas is connected, preventing the browser from focusing its menu bar after Alt+scroll zoom. - onWheel bounds check now uses getBoundingClientRect instead of canvas.contains(), so Alt+scroll zoom also works over the SVG overlay.
1 parent 7ab57a9 commit 56064f6

1 file changed

Lines changed: 40 additions & 20 deletions

File tree

addons/paint-canvas-pan/userscript.js

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export default async function ({ addon, console }) {
22
const paper = await addon.tab.traps.getPaper();
3-
const canvas = paper.view.element;
3+
// Do not cache paper.view.element: the canvas element may be replaced when
4+
// the paint editor unmounts and remounts across tab navigation.
5+
// Always read paper.view.element dynamically at call time.
46

57
let spaceHeld = false;
68
let panning = false;
@@ -14,19 +16,33 @@ export default async function ({ addon, console }) {
1416
e.preventDefault();
1517
if (spaceHeld) return;
1618
spaceHeld = true;
17-
if (!panning) canvas.style.cursor = "grab";
19+
if (!panning) paper.view.element.style.cursor = "grab";
1820
};
1921

2022
const onKeyUp = (e) => {
21-
if (e.code !== "Space") return;
22-
spaceHeld = false;
23-
if (!panning) canvas.style.cursor = "";
23+
if (e.code === "Space") {
24+
spaceHeld = false;
25+
if (!panning) paper.view.element.style.cursor = "";
26+
return;
27+
}
28+
// Releasing Alt alone causes browsers on Windows to focus the menu bar.
29+
// Suppress this while the paint editor canvas is on the page.
30+
if (e.code === "AltLeft" || e.code === "AltRight") {
31+
if (paper.view.element?.isConnected) e.preventDefault();
32+
}
2433
};
2534

2635
const onPointerDown = (e) => {
2736
const isSpaceDrag = spaceHeld && e.button === 0;
2837
const isMiddleClick = e.button === 1;
2938
if (!isSpaceDrag && !isMiddleClick) return;
39+
// Only pan when the pointer is within the paint canvas area.
40+
// Using getBoundingClientRect rather than target containment so that
41+
// events on the SVG overlay (which is a sibling of the canvas, not a
42+
// descendant) are also caught correctly.
43+
const canvas = paper.view.element;
44+
const rect = canvas.getBoundingClientRect();
45+
if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return;
3046
// Intercept before paper.js so no tool action starts.
3147
e.stopPropagation();
3248
e.preventDefault();
@@ -53,13 +69,17 @@ export default async function ({ addon, console }) {
5369
const onPointerUp = () => {
5470
if (!panning) return;
5571
panning = false;
56-
canvas.style.cursor = spaceHeld ? "grab" : "";
72+
paper.view.element.style.cursor = spaceHeld ? "grab" : "";
5773
};
5874

5975
const onWheel = (e) => {
6076
if (!e.altKey) return;
61-
// Only act when the pointer is over the paint canvas.
62-
if (!canvas.contains(e.target) && e.target !== canvas) return;
77+
// Only act when the pointer is over the paint canvas area.
78+
// Use getBoundingClientRect so this works even when the event target is
79+
// on the SVG overlay sitting above the canvas.
80+
const canvas = paper.view.element;
81+
const rect = canvas.getBoundingClientRect();
82+
if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return;
6383
e.preventDefault();
6484
e.stopPropagation();
6585

@@ -73,7 +93,6 @@ export default async function ({ addon, console }) {
7393
// Normalise cursor position to [0,1] within the canvas's rendered rect,
7494
// then interpolate into the currently visible project-space bounds.
7595
// This avoids viewToProject entirely and works regardless of CSS scaling.
76-
const rect = canvas.getBoundingClientRect();
7796
const nx = (e.clientX - rect.left) / rect.width;
7897
const ny = (e.clientY - rect.top) / rect.height;
7998
const b = paper.view.bounds;
@@ -90,24 +109,25 @@ export default async function ({ addon, console }) {
90109

91110
const init = () => {
92111
window.addEventListener("keydown", onKeyDown);
93-
window.addEventListener("keyup", onKeyUp);
94-
canvas.addEventListener("pointerdown", onPointerDown, { capture: true });
95-
canvas.addEventListener("pointermove", onPointerMove);
96-
canvas.addEventListener("pointerup", onPointerUp);
97-
canvas.addEventListener("wheel", onWheel, { passive: false });
112+
window.addEventListener("keyup", onKeyUp, { capture: true });
113+
// Use document-level capture so that:
114+
// (a) events targeting the SVG overlay (sibling of canvas) are caught too, and
115+
// (b) stale canvas references after paint-editor remounts don't break panning.
116+
document.addEventListener("pointerdown", onPointerDown, { capture: true });
117+
document.addEventListener("pointermove", onPointerMove);
118+
document.addEventListener("pointerup", onPointerUp);
98119
window.addEventListener("wheel", onWheel, { capture: true, passive: false });
99120
};
100121

101122
const cleanup = () => {
102123
spaceHeld = false;
103124
panning = false;
104-
canvas.style.cursor = "";
125+
paper.view.element.style.cursor = "";
105126
window.removeEventListener("keydown", onKeyDown);
106-
window.removeEventListener("keyup", onKeyUp);
107-
canvas.removeEventListener("pointerdown", onPointerDown, { capture: true });
108-
canvas.removeEventListener("pointermove", onPointerMove);
109-
canvas.removeEventListener("pointerup", onPointerUp);
110-
canvas.removeEventListener("wheel", onWheel);
127+
window.removeEventListener("keyup", onKeyUp, { capture: true });
128+
document.removeEventListener("pointerdown", onPointerDown, { capture: true });
129+
document.removeEventListener("pointermove", onPointerMove);
130+
document.removeEventListener("pointerup", onPointerUp);
111131
window.removeEventListener("wheel", onWheel, { capture: true });
112132
};
113133

0 commit comments

Comments
 (0)