fix(radial): use backdrop element for click-outside dismiss

Replace unreliable window event listener with transparent full-screen
backdrop element. Clicking anywhere outside the radial menu now properly
dismisses it. Also handles right-click on backdrop for dismiss.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-04-26 06:12:46 +00:00
commit 741d760760

View file

@ -44,24 +44,6 @@ export default function RadialMenu({
return () => window.removeEventListener('keydown', handleKey)
}, [open, onDismiss])
// Handle click outside
useEffect(() => {
if (!open) return
const handleClick = (e) => {
if (containerRef.current && !containerRef.current.contains(e.target)) {
onDismiss?.()
}
}
// Delay to avoid triggering on the same click that opened the menu
const timer = setTimeout(() => {
window.addEventListener('click', handleClick)
}, 50)
return () => {
clearTimeout(timer)
window.removeEventListener('click', handleClick)
}
}, [open, onDismiss])
// Calculate which wedge the pointer is over
const getWedgeAtPoint = useCallback((clientX, clientY) => {
const dx = clientX - x
@ -109,6 +91,17 @@ export default function RadialMenu({
onDismiss?.()
}, [getWedgeAtPoint, lat, lon, onDismiss])
// Handle backdrop click (dismiss menu)
const handleBackdropClick = useCallback((e) => {
e.stopPropagation()
onDismiss?.()
}, [onDismiss])
// Prevent menu container clicks from reaching backdrop
const handleContainerClick = useCallback((e) => {
e.stopPropagation()
}, [])
// Generate wedge paths
const generateWedgePath = (index) => {
const startAngle = (index * wedgeAngle - 90) * (Math.PI / 180)
@ -144,9 +137,25 @@ export default function RadialMenu({
const clampedY = Math.max(padding, Math.min(window.innerHeight - padding, y))
const content = (
<>
{/* Full-screen transparent backdrop for dismiss */}
<div
onClick={handleBackdropClick}
onContextMenu={handleBackdropClick}
style={{
position: 'fixed',
inset: 0,
zIndex: 9998,
background: 'transparent',
cursor: 'default',
}}
/>
{/* Radial menu container */}
<div
ref={containerRef}
className="radial-menu-container"
onClick={handleContainerClick}
style={{
position: 'fixed',
left: clampedX,
@ -264,6 +273,7 @@ export default function RadialMenu({
}
`}</style>
</div>
</>
)
return createPortal(content, document.body)