fix(tui): preserve toast hover state (#42419)

This commit is contained in:
Kit Langton
2026-08-13 20:31:37 -04:00
committed by GitHub
Unverified
parent 655b3c4c22
commit 5816fcbc61
2 changed files with 23 additions and 8 deletions
+12 -4
View File
@@ -1,7 +1,7 @@
import { createContext, createSignal, onCleanup, useContext, type ParentProps, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { useTheme } from "../context/theme"
import { useTerminalDimensions } from "@opentui/solid"
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
import { SplitBorder } from "./border"
import { TextAttributes } from "@opentui/core"
import { tint } from "../theme/color"
@@ -25,6 +25,7 @@ function ToastSurface(props: {
}) {
const theme = useTheme("overlay")
const dimensions = useTerminalDimensions()
const renderer = useRenderer()
const [hovered, setHovered] = createSignal(false)
const hover = (value: boolean) => {
setHovered(value)
@@ -44,7 +45,10 @@ function ToastSurface(props: {
customBorderChars={SplitBorder.customBorderChars}
onMouseOver={() => hover(true)}
onMouseOut={() => hover(false)}
onMouseUp={props.onActivate}
onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return
props.onActivate()
}}
>
<box
width="100%"
@@ -153,11 +157,15 @@ function init() {
const dismiss = () => {
clear()
paused = false
const next = store.queue[0]
setStore("queue", (queue) => queue.slice(1))
setStore("currentToast", next ?? null)
if (next) start(next.duration)
if (!next) {
paused = false
return
}
remaining = next.duration
if (!paused) start(next.duration)
}
const toast = {
+11 -4
View File
@@ -12,7 +12,7 @@ function captureToast(setToast: (toast: ToastContext) => void) {
}
}
test("clicking runs an action and advances a queued toast", async () => {
test("activation runs an action and keeps a queued toast paused", async () => {
let toast: ToastContext | undefined
const Capture = captureToast((value) => (toast = value))
const app = await testRender(() => (
@@ -30,7 +30,7 @@ test("clicking runs an action and advances a queued toast", async () => {
action: { label: "Open plugins", run: () => (activated = true) },
})
toast!.pause()
toast!.show({ message: "Copied", variant: "success" })
toast!.show({ message: "Copied", variant: "success", duration: 5 })
expect(toast!.currentToast?.message).toBe("Plugin failed")
expect(toast!.pending).toBe(1)
@@ -40,12 +40,19 @@ test("clicking runs an action and advances a queued toast", async () => {
expect(activated).toBe(true)
expect(toast!.currentToast?.message).toBe("Copied")
expect(toast!.pending).toBe(0)
await Bun.sleep(10)
expect(toast!.currentToast?.message).toBe("Copied")
toast!.resume()
await Bun.sleep(10)
expect(toast!.currentToast).toBeNull()
} finally {
app.renderer.destroy()
}
})
test("clicking a toast without an action dismisses it", async () => {
test("activation dismisses a toast without an action", async () => {
let toast: ToastContext | undefined
const Capture = captureToast((value) => (toast = value))
const app = await testRender(() => (
@@ -56,7 +63,7 @@ test("clicking a toast without an action dismisses it", async () => {
try {
await app.waitFor(() => toast !== undefined)
toast!.show({ message: "Copied", variant: "success" })
toast!.show({ message: "Copied", variant: "success", duration: 5 })
toast!.activate()
expect(toast!.currentToast).toBeNull()
} finally {