11export 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