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

This commit is contained in:
Luke Parker 2026-07-15 01:36:45 +10:00 committed by GitHub
parent e7e7a97648
commit 5f7091ab4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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>
)
}