@@ -163,13 +163,30 @@ const main = async () => {
163163 events . fire ( 'camera.fly.up' , false ) ;
164164 }
165165 } ;
166- document . addEventListener ( 'mousedown' , ( ) => {
167- mouseButtonsPressed ++ ;
166+ // Track mouse button state using window-level pointer events with capture phase.
167+ // window is the highest element in the DOM tree; capture phase on window ensures
168+ // this fires before ANY other handler (including PCUI on document/body).
169+ // Using e.buttons reads the hardware button bitmap directly — more reliable than
170+ // manual increment/decrement which can get out of sync.
171+ window . addEventListener ( 'pointerdown' , ( e : PointerEvent ) => {
172+ if ( e . pointerType === 'mouse' ) {
173+ mouseButtonsPressed = e . buttons ;
174+ }
168175 } , true ) ;
169- document . addEventListener ( 'mouseup' , ( ) => {
170- mouseButtonsPressed = Math . max ( 0 , mouseButtonsPressed - 1 ) ;
171- if ( mouseButtonsPressed === 0 ) {
172- releaseFlyKeys ( ) ;
176+ window . addEventListener ( 'pointerup' , ( e : PointerEvent ) => {
177+ if ( e . pointerType === 'mouse' ) {
178+ mouseButtonsPressed = e . buttons ;
179+ if ( mouseButtonsPressed === 0 ) {
180+ releaseFlyKeys ( ) ;
181+ }
182+ }
183+ } , true ) ;
184+ // Fallback: sync button state from pointermove.
185+ // PCUI's orbit/pan handlers may call stopPropagation on pointermove,
186+ // so we register on window in capture phase to always receive it.
187+ window . addEventListener ( 'pointermove' , ( e : PointerEvent ) => {
188+ if ( e . pointerType === 'mouse' ) {
189+ mouseButtonsPressed = e . buttons ;
173190 }
174191 } , true ) ;
175192 // Reset mouse state when the pointer leaves the window to prevent stuck state
0 commit comments