Jelajahi Sumber

fix(app): prevent stale session hover preview on refocus (#18727)

Shoubhit Dash 5 bulan lalu
induk
melakukan
5ea95451dd

+ 16 - 2
packages/app/src/pages/layout.tsx

@@ -211,13 +211,22 @@ export default function Layout(props: ParentProps) {
 
   onMount(() => {
     const stop = () => setState("sizing", false)
+    const blur = () => reset()
+    const hide = () => {
+      if (document.visibilityState !== "hidden") return
+      reset()
+    }
     window.addEventListener("pointerup", stop)
     window.addEventListener("pointercancel", stop)
     window.addEventListener("blur", stop)
+    window.addEventListener("blur", blur)
+    document.addEventListener("visibilitychange", hide)
     onCleanup(() => {
       window.removeEventListener("pointerup", stop)
       window.removeEventListener("pointercancel", stop)
       window.removeEventListener("blur", stop)
+      window.removeEventListener("blur", blur)
+      document.removeEventListener("visibilitychange", hide)
     })
   })
 
@@ -237,6 +246,12 @@ export default function Layout(props: ParentProps) {
     navLeave.current = undefined
   }
 
+  const reset = () => {
+    disarm()
+    setState("hoverSession", undefined)
+    setHoverProject(undefined)
+  }
+
   const arm = () => {
     if (layout.sidebar.opened()) return
     if (state.hoverProject === undefined) return
@@ -305,8 +320,7 @@ export default function Layout(props: ParentProps) {
 
   const clearSidebarHoverState = () => {
     if (layout.sidebar.opened()) return
-    setState("hoverSession", undefined)
-    setHoverProject(undefined)
+    reset()
   }
 
   const navigateWithSidebarReset = (href: string) => {

+ 38 - 27
packages/app/src/pages/layout/sidebar-items.tsx

@@ -157,34 +157,45 @@ const SessionHoverPreview = (props: {
   messageLabel: (message: Message) => string | undefined
   onMessageSelect: (message: Message) => void
   trigger: JSX.Element
-}): JSX.Element => (
-  <HoverCard
-    openDelay={1000}
-    closeDelay={props.sidebarHovering() ? 600 : 0}
-    placement="right-start"
-    gutter={16}
-    shift={-2}
-    trigger={props.trigger}
-    open={props.hoverSession() === props.session.id}
-    onOpenChange={(open) => props.setHoverSession(open ? props.session.id : undefined)}
-  >
-    <Show
-      when={props.hoverReady()}
-      fallback={<div class="text-12-regular text-text-weak">{props.language.t("session.messages.loading")}</div>}
+}): JSX.Element => {
+  let ref: HTMLDivElement | undefined
+
+  return (
+    <HoverCard
+      openDelay={1000}
+      closeDelay={props.sidebarHovering() ? 600 : 0}
+      placement="right-start"
+      gutter={16}
+      shift={-2}
+      trigger={<div ref={ref}>{props.trigger}</div>}
+      open={props.hoverSession() === props.session.id}
+      onOpenChange={(open) => {
+        if (!open) {
+          props.setHoverSession(undefined)
+          return
+        }
+        if (!ref?.matches(":hover")) return
+        props.setHoverSession(props.session.id)
+      }}
     >
-      <div class="overflow-y-auto overflow-x-hidden max-h-72 h-full">
-        <MessageNav
-          messages={props.hoverMessages() ?? []}
-          current={undefined}
-          getLabel={props.messageLabel}
-          onMessageSelect={props.onMessageSelect}
-          size="normal"
-          class="w-60"
-        />
-      </div>
-    </Show>
-  </HoverCard>
-)
+      <Show
+        when={props.hoverReady()}
+        fallback={<div class="text-12-regular text-text-weak">{props.language.t("session.messages.loading")}</div>}
+      >
+        <div class="overflow-y-auto overflow-x-hidden max-h-72 h-full">
+          <MessageNav
+            messages={props.hoverMessages() ?? []}
+            current={undefined}
+            getLabel={props.messageLabel}
+            onMessageSelect={props.onMessageSelect}
+            size="normal"
+            class="w-60"
+          />
+        </div>
+      </Show>
+    </HoverCard>
+  )
+}
 
 export const SessionItem = (props: SessionItemProps): JSX.Element => {
   const params = useParams()