From 714f60de8edcbb6b8dbec0c72ea4664ae8795a25 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 02:39:26 +0000 Subject: [PATCH] Fix globe drag not ending when pointer released outside element When dragging the globe to rotate it and releasing the mouse button outside the globe element, OrbitControls could miss the pointerup event (pointer capture can be lost intermittently). This caused the globe to continue rotating on re-entry even without the mouse button held. Fix: listen for pointerup on window and forward it to the canvas when the event originated outside the globe, so OrbitControls properly ends the drag. Co-Authored-By: Konstantin Wohlwend --- .../projects/[projectId]/(overview)/globe.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx index 655c11e0c0..c8f28aa51a 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx @@ -1024,6 +1024,31 @@ function GlobeSectionInner({ countryData, totalUsers, activeUsersByCountry, sate }; }, [globeReady, mounted, liveAvatars]); + // When the pointer leaves the globe canvas and the button is released + // outside, OrbitControls may miss the pointerup (pointer capture can be + // lost intermittently). Listen on window and forward the event to the + // canvas so the controls properly end the drag. + useEffect(() => { + if (!globeReady) return; + const domElement = globeRef.current?.renderer().domElement; + if (!domElement) return; + + const handleWindowPointerUp = (e: PointerEvent) => { + if (e.target !== domElement && !domElement.contains(e.target as Node)) { + domElement.dispatchEvent(new PointerEvent('pointerup', { + pointerId: e.pointerId, + pointerType: e.pointerType, + bubbles: true, + })); + } + }; + + window.addEventListener('pointerup', handleWindowPointerUp); + return () => { + window.removeEventListener('pointerup', handleWindowPointerUp); + }; + }, [globeReady]); + // set globeReady to true after a bit in case onGlobeReady was not called useEffect(() => { const timeout = setTimeout(() => {