mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 20:21:39 +03:00
fix(session-ui): stop the message footer flickering as its menu closes (#62144)
This commit is contained in:
committed by
GitHub
Unverified
parent
324c89dd63
commit
f7cf3a10f4
@@ -122,6 +122,43 @@ story.describe("message actions menu", () => {
|
||||
})
|
||||
})
|
||||
|
||||
story("keeps the footer in view while an outside click closes the actions menu", async ({ mount, page }) => {
|
||||
const timeline = await mount("current-prompt-message--pending")
|
||||
const footer = timeline.locator('[data-slot="user-message-copy-wrapper"]')
|
||||
|
||||
await timeline.getByLabel("Delivery state").selectOption("delivered")
|
||||
await timeline.locator('[data-component="user-message"]').hover()
|
||||
await timeline.getByRole("button", { name: "More actions", exact: true }).click()
|
||||
await expect(page.getByRole("menu")).toBeVisible()
|
||||
await page.mouse.move(0, 0)
|
||||
|
||||
// Kobalte drops the expanded state a task before it returns focus to the trigger, so record every frame from the
|
||||
// click until a few frames after the menu has gone rather than the settled state.
|
||||
const frames = await footer.evaluateHandle((element) => {
|
||||
const opacities: string[] = []
|
||||
const closed = { frames: 0 }
|
||||
|
||||
const done = new Promise<string[]>((resolve) => {
|
||||
const tick = () => {
|
||||
opacities.push(getComputedStyle(element).opacity)
|
||||
|
||||
if (!document.querySelector('[role="menu"]')) closed.frames += 1
|
||||
|
||||
if (closed.frames < 5) return requestAnimationFrame(tick)
|
||||
|
||||
resolve(opacities)
|
||||
}
|
||||
|
||||
requestAnimationFrame(tick)
|
||||
})
|
||||
|
||||
return { done }
|
||||
})
|
||||
|
||||
await page.mouse.click(0, 0)
|
||||
expect(new Set(await frames.evaluate((sampler) => sampler.done))).toEqual(new Set(["1"]))
|
||||
})
|
||||
|
||||
story("keeps pending steer actions usable after a request fails", async ({ mount }) => {
|
||||
const timeline = await mount("current-prompt-message--pending")
|
||||
const queue = timeline.getByRole("button", { name: "Move to queue", exact: true })
|
||||
|
||||
@@ -418,7 +418,7 @@
|
||||
|
||||
&:hover [data-slot="user-message-copy-wrapper"],
|
||||
&:focus-within [data-slot="user-message-copy-wrapper"],
|
||||
[data-slot="user-message-copy-wrapper"]:has([data-expanded]),
|
||||
[data-slot="user-message-copy-wrapper"]:has([data-expanded], [data-closing]),
|
||||
&:hover [data-slot="user-message-meta-tail"],
|
||||
&:focus-within [data-slot="user-message-meta-tail"] {
|
||||
opacity: 1;
|
||||
@@ -468,7 +468,7 @@
|
||||
|
||||
&:hover [data-slot="user-message-run-actions"],
|
||||
&:focus-within [data-slot="user-message-run-actions"],
|
||||
[data-slot="user-message-run-actions"]:has([data-expanded]) {
|
||||
[data-slot="user-message-run-actions"]:has([data-expanded], [data-closing]) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DropdownMenu } from "@kobalte/core/dropdown-menu"
|
||||
import { DropdownMenu, type DropdownMenuTriggerProps } from "@kobalte/core/dropdown-menu"
|
||||
import { ContextMenu } from "@kobalte/core/context-menu"
|
||||
import type { PolymorphicProps } from "@kobalte/core/polymorphic"
|
||||
import {
|
||||
createComputed,
|
||||
createContext,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
type ComponentProps,
|
||||
type JSX,
|
||||
type ParentProps,
|
||||
type ValidComponent,
|
||||
} from "solid-js"
|
||||
import {
|
||||
createMenuState,
|
||||
@@ -316,6 +318,14 @@ function MenuSeparator(props: ComponentProps<typeof DropdownMenu.Separator>) {
|
||||
)
|
||||
}
|
||||
|
||||
// Kobalte drops `data-expanded` as the menu closes but returns focus to the trigger a task later, so for a frame the
|
||||
// trigger is neither expanded nor focused. `data-closing` covers that gap for UI revealed by either.
|
||||
function MenuTrigger<T extends ValidComponent = "button">(props: PolymorphicProps<T, DropdownMenuTriggerProps<T>>) {
|
||||
const ctx = useMenuContext()
|
||||
|
||||
return <DropdownMenu.Trigger {...props} data-closing={ctx.state().status === "closing" ? "" : undefined} />
|
||||
}
|
||||
|
||||
function MenuContent(props: ComponentProps<typeof DropdownMenu.Content>) {
|
||||
const ctx = useMenuContext()
|
||||
const [s, r] = splitProps(props, ["class", "classList", "onCloseAutoFocus", "onInteractOutside"])
|
||||
@@ -439,7 +449,7 @@ const MenuContext = Object.assign(MenuContextRoot, {
|
||||
})
|
||||
|
||||
export const Menu = Object.assign(MenuRoot, {
|
||||
Trigger: DropdownMenu.Trigger,
|
||||
Trigger: MenuTrigger,
|
||||
Portal: DropdownMenu.Portal,
|
||||
Content: MenuContent,
|
||||
Item: MenuItem,
|
||||
|
||||
Reference in New Issue
Block a user