فهرست منبع

fix(app): wait for connected project picker anchor (#36879)

Luke Parker 1 ماه پیش
والد
کامیت
5f7091ab4e
1فایلهای تغییر یافته به همراه33 افزوده شده و 8 حذف شده
  1. 33 8
      packages/app/src/components/prompt-project-selector.tsx

+ 33 - 8
packages/app/src/components/prompt-project-selector.tsx

@@ -1,6 +1,14 @@
-import { createEffect, For, onCleanup, Show, splitProps, type Accessor, type ComponentProps } from "solid-js"
+import {
+  createEffect,
+  createSignal,
+  For,
+  onCleanup,
+  Show,
+  splitProps,
+  type Accessor,
+  type ComponentProps,
+} from "solid-js"
 import { createStore } from "solid-js/store"
-import { Dynamic } from "solid-js/web"
 import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
 import { Icon } from "@opencode-ai/ui/icon"
 import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
@@ -185,9 +193,28 @@ export function PromptProjectSelector(props: {
   controller: PromptProjectController
   placement?: "bottom" | "bottom-start"
 }) {
+  const [triggerReady, setTriggerReady] = createSignal(false)
   let contentRef: HTMLDivElement | undefined
+  let triggerFrame: number | undefined
   let restoreTrigger = true
 
+  // Floating UI requires a connected anchor; route transitions can construct this trigger before adoption.
+  const setTriggerRef = (element: HTMLButtonElement) => {
+    const ready = () => {
+      if (!element.isConnected) {
+        triggerFrame = requestAnimationFrame(ready)
+        return
+      }
+      triggerFrame = undefined
+      setTriggerReady(true)
+    }
+    ready()
+  }
+
+  onCleanup(() => {
+    if (triggerFrame !== undefined) cancelAnimationFrame(triggerFrame)
+  })
+
   const activeItem = () =>
     props.controller.active()
       ? contentRef?.querySelector<HTMLElement>(`[data-option-key="${CSS.escape(props.controller.active())}"]`)
@@ -258,13 +285,13 @@ export function PromptProjectSelector(props: {
 
   return (
     <DropdownMenu
-      open={props.controller.open()}
+      open={triggerReady() && props.controller.open()}
       placement={props.placement ?? "bottom"}
       gutter={4}
       modal={false}
       onOpenChange={(open) => props.controller.setOpen(open)}
     >
-      <DropdownMenu.Trigger as={ProjectTrigger} controller={props.controller} />
+      <DropdownMenu.Trigger as={ProjectTrigger} ref={setTriggerRef} controller={props.controller} />
       <DropdownMenu.Portal>
         <DropdownMenu.Content
           ref={contentRef}
@@ -434,9 +461,7 @@ function ProjectTrigger(props: ComponentProps<"button"> & { controller: PromptPr
   const [local, rest] = splitProps(props, ["controller", "class", "classList", "onClick", "onKeyDown"])
   const project = () => local.controller.selected()
   return (
-    // Avoid an inert template clone that Floating UI can measure before browser adoption.
-    <Dynamic
-      component="button"
+    <button
       {...rest}
       data-action="prompt-project"
       type="button"
@@ -473,7 +498,7 @@ function ProjectTrigger(props: ComponentProps<"button"> & { controller: PromptPr
         {project() ? displayName(project()!) : local.controller.labels.new()}
       </span>
       <Icon name="chevron-down" size="small" class="shrink-0 text-v2-icon-icon-muted" />
-    </Dynamic>
+    </button>
   )
 }