fix(app): wait for connected project picker anchor (#36879)
This commit is contained in:
parent
e7e7a97648
commit
5f7091ab4e
1 changed files with 33 additions and 8 deletions
|
|
@ -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 { createStore } from "solid-js/store"
|
||||||
import { Dynamic } from "solid-js/web"
|
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
|
|
@ -185,9 +193,28 @@ export function PromptProjectSelector(props: {
|
||||||
controller: PromptProjectController
|
controller: PromptProjectController
|
||||||
placement?: "bottom" | "bottom-start"
|
placement?: "bottom" | "bottom-start"
|
||||||
}) {
|
}) {
|
||||||
|
const [triggerReady, setTriggerReady] = createSignal(false)
|
||||||
let contentRef: HTMLDivElement | undefined
|
let contentRef: HTMLDivElement | undefined
|
||||||
|
let triggerFrame: number | undefined
|
||||||
let restoreTrigger = true
|
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 = () =>
|
const activeItem = () =>
|
||||||
props.controller.active()
|
props.controller.active()
|
||||||
? contentRef?.querySelector<HTMLElement>(`[data-option-key="${CSS.escape(props.controller.active())}"]`)
|
? contentRef?.querySelector<HTMLElement>(`[data-option-key="${CSS.escape(props.controller.active())}"]`)
|
||||||
|
|
@ -258,13 +285,13 @@ export function PromptProjectSelector(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
open={props.controller.open()}
|
open={triggerReady() && props.controller.open()}
|
||||||
placement={props.placement ?? "bottom"}
|
placement={props.placement ?? "bottom"}
|
||||||
gutter={4}
|
gutter={4}
|
||||||
modal={false}
|
modal={false}
|
||||||
onOpenChange={(open) => props.controller.setOpen(open)}
|
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.Portal>
|
||||||
<DropdownMenu.Content
|
<DropdownMenu.Content
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
|
|
@ -434,9 +461,7 @@ function ProjectTrigger(props: ComponentProps<"button"> & { controller: PromptPr
|
||||||
const [local, rest] = splitProps(props, ["controller", "class", "classList", "onClick", "onKeyDown"])
|
const [local, rest] = splitProps(props, ["controller", "class", "classList", "onClick", "onKeyDown"])
|
||||||
const project = () => local.controller.selected()
|
const project = () => local.controller.selected()
|
||||||
return (
|
return (
|
||||||
// Avoid an inert template clone that Floating UI can measure before browser adoption.
|
<button
|
||||||
<Dynamic
|
|
||||||
component="button"
|
|
||||||
{...rest}
|
{...rest}
|
||||||
data-action="prompt-project"
|
data-action="prompt-project"
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -473,7 +498,7 @@ function ProjectTrigger(props: ComponentProps<"button"> & { controller: PromptPr
|
||||||
{project() ? displayName(project()!) : local.controller.labels.new()}
|
{project() ? displayName(project()!) : local.controller.labels.new()}
|
||||||
</span>
|
</span>
|
||||||
<Icon name="chevron-down" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
<Icon name="chevron-down" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||||
</Dynamic>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue