Compare commits

...

1 Commits

Author SHA1 Message Date
Aiden Cline 870a10a7ba fix(tui): stabilize dialog mouse selection 2026-07-18 19:54:41 +00:00
2 changed files with 35 additions and 3 deletions
+1 -1
View File
@@ -283,7 +283,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
setTimeout(() => { setTimeout(() => {
if (filter.length > 0) { if (filter.length > 0) {
moveTo(0, true, false) moveTo(0, true, false)
} else if (current && props.focusCurrent !== false) { } else if (current && props.focusCurrent !== false && store.input !== "mouse") {
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current)) const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
if (currentIndex >= 0) { if (currentIndex >= 0) {
moveTo(currentIndex, true) moveTo(currentIndex, true)
@@ -79,7 +79,7 @@ async function renderSelect(
return app return app
} }
async function mountSelect(root: string, initial: DialogSelectOption<string>[]) { async function mountSelect(root: string, initial: DialogSelectOption<string>[], trackCurrent = false) {
const state = path.join(root, "state") const state = path.join(root, "state")
await mkdir(state, { recursive: true }) await mkdir(state, { recursive: true })
const config = createTuiResolvedConfig() const config = createTuiResolvedConfig()
@@ -105,6 +105,7 @@ async function mountSelect(root: string, initial: DialogSelectOption<string>[])
function Harness() { function Harness() {
const [options, setOptions] = createSignal(initial) const [options, setOptions] = createSignal(initial)
const [current, setCurrent] = createSignal(initial[0]?.value)
replaceOptions = setOptions replaceOptions = setOptions
function Fixture() { function Fixture() {
@@ -114,7 +115,11 @@ async function mountSelect(root: string, initial: DialogSelectOption<string>[])
<DialogSelect <DialogSelect
title="Mutable options" title="Mutable options"
options={options()} options={options()}
onMove={(option) => moved.push(option.value)} current={trackCurrent ? current() : undefined}
onMove={(option) => {
moved.push(option.value)
if (trackCurrent) setCurrent(option.value)
}}
onSelect={(option) => selected.push(option.value)} onSelect={(option) => selected.push(option.value)}
/> />
)), )),
@@ -272,3 +277,30 @@ test("selects a repopulated option after removing the only option", async () =>
select.app.renderer.destroy() select.app.renderer.destroy()
} }
}) })
test("hover stays on the row when current follows selection", async () => {
await using tmp = await tmpdir()
const select = await mountSelect(
tmp.path,
["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth"].map((value) => ({
title: value,
value,
})),
true,
)
try {
await select.app.mockMouse.moveTo(20, 11)
await select.app.flush()
await select.app.mockMouse.moveTo(20, 12)
await select.app.waitFor(() => select.moved.includes("second"))
select.moved.length = 0
await select.app.mockMouse.moveTo(20, 14)
await select.app.waitFor(() => select.moved.length > 0)
await select.app.waitForVisualIdle()
expect(select.moved).toEqual(["fourth"])
} finally {
select.app.renderer.destroy()
}
})