diff --git a/src/renderer/src/components/link-actions/LinkActionPopover.tsx b/src/renderer/src/components/link-actions/LinkActionPopover.tsx index a5499f467f5..29338b208f9 100644 --- a/src/renderer/src/components/link-actions/LinkActionPopover.tsx +++ b/src/renderer/src/components/link-actions/LinkActionPopover.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef } from 'react' +import { useEffect, useEffectEvent, useMemo, useRef } from 'react' import { Check, Copy, ExternalLink, Globe, Settings } from 'lucide-react' import { toast } from 'sonner' import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' @@ -64,6 +64,23 @@ export function LinkActionPopover({ const copyableDestination = request?.kind === 'url' ? request.destination : '' const { copyText, status: copyStatus } = useClipboardTextCopyFeedback(copyableDestination) const copyInFlightRef = useRef(false) + const contentRef = useRef(null) + const dismissOnOutsidePointerDown = useEffectEvent((event: PointerEvent): void => { + if (!request || (event.target instanceof Node && contentRef.current?.contains(event.target))) { + return + } + onPointerDownOutside?.(event) + onClose(request) + }) + useEffect(() => { + if (!request) { + return + } + // Why: Radix defers outside dismissal to the click and skips it when the click's + // propagation is stopped, which hover cards and sidebar cards both do. + document.addEventListener('pointerdown', dismissOnOutsidePointerDown, true) + return () => document.removeEventListener('pointerdown', dismissOnOutsidePointerDown, true) + }, [request]) const virtualRef = useMemo( () => ({ current: { @@ -133,6 +150,7 @@ export function LinkActionPopover({ {request ? ( ({ onOpenAutoFocus={(event) => event.preventDefault()} onCloseAutoFocus={(event) => event.preventDefault()} onEscapeKeyDown={() => request.restoreFocus()} - onPointerDownOutside={onPointerDownOutside} >
{ 'true' ) }) + it('dismisses the link chooser when an outside click stops propagation', async () => { + Object.defineProperty(window, 'api', { + configurable: true, + value: { shell: { openUrl: vi.fn() } } + }) + render( + <> + + + + ) + fireEvent.click(screen.getByRole('button', { name: 'Hover workspace' })) + fireEvent.click(screen.getByRole('link', { name: 'Open MR !42' })) + // Why: Radix arms its outside-pointerdown listener a tick after opening. + await act(() => new Promise((resolve) => setTimeout(resolve, 0))) + const otherCard = screen.getByRole('button', { name: 'Other card' }) + fireEvent.pointerDown(otherCard) + fireEvent.click(otherCard) + expect(screen.queryByRole('button', { name: /System Browser/ })).toBeNull() + }) it('opens link management from either section header and dismisses hover', () => { const onManage = vi.fn() const { container } = render()