From 2a5bb6304d7899b12b0bbbc4640205c284db150a Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Sun, 8 Feb 2026 21:06:32 +0000 Subject: [PATCH] fix(ui): keep timeline preview tooltip interactive Allow pointer interaction with the message preview tooltip and delay hover dismissal so users can move from the timeline segment onto the preview to copy or delete. --- .../ui/src/components/message-timeline.tsx | 32 ++++++++++++++++--- .../src/styles/messaging/message-timeline.css | 3 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/message-timeline.tsx b/packages/ui/src/components/message-timeline.tsx index 7faaca97..c0e1d9d7 100644 --- a/packages/ui/src/components/message-timeline.tsx +++ b/packages/ui/src/components/message-timeline.tsx @@ -276,6 +276,7 @@ const MessageTimeline: Component = (props) => { const [tooltipSize, setTooltipSize] = createSignal<{ width: number; height: number }>({ width: 360, height: 420 }) const [tooltipElement, setTooltipElement] = createSignal(null) let hoverTimer: number | null = null + let closeTimer: number | null = null const showTools = () => props.showToolSegments ?? true const registerButtonRef = (segmentId: string, element: HTMLButtonElement | null) => { @@ -292,10 +293,30 @@ const MessageTimeline: Component = (props) => { hoverTimer = null } } + + const clearCloseTimer = () => { + if (closeTimer !== null && typeof window !== "undefined") { + window.clearTimeout(closeTimer) + closeTimer = null + } + } + + const scheduleClose = () => { + if (typeof window === "undefined") return + clearHoverTimer() + clearCloseTimer() + // Small delay so the pointer can travel from the segment to the tooltip. + closeTimer = window.setTimeout(() => { + closeTimer = null + setHoveredSegment(null) + setHoverAnchorRect(null) + }, 160) + } const handleMouseEnter = (segment: TimelineSegment, event: MouseEvent) => { if (typeof window === "undefined") return clearHoverTimer() + clearCloseTimer() const target = event.currentTarget as HTMLButtonElement hoverTimer = window.setTimeout(() => { const rect = target.getBoundingClientRect() @@ -305,9 +326,7 @@ const MessageTimeline: Component = (props) => { } const handleMouseLeave = () => { - clearHoverTimer() - setHoveredSegment(null) - setHoverAnchorRect(null) + scheduleClose() } createEffect(() => { @@ -326,7 +345,10 @@ const MessageTimeline: Component = (props) => { setTooltipCoords({ top: clampedTop, left: clampedLeft }) }) - onCleanup(() => clearHoverTimer()) + onCleanup(() => { + clearHoverTimer() + clearCloseTimer() + }) createEffect(() => { const activeId = props.activeMessageId @@ -432,6 +454,8 @@ const MessageTimeline: Component = (props) => { ref={(element) => setTooltipElement(element)} class="message-timeline-tooltip" style={{ top: `${tooltipCoords().top}px`, left: `${tooltipCoords().left}px` }} + onMouseEnter={() => clearCloseTimer()} + onMouseLeave={() => scheduleClose()} >