summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoseph Campuzano <[email protected]>2026-01-19 14:46:17 -0600
committerGitHub <[email protected]>2026-01-19 14:46:17 -0600
commit091e88c1e157c53552e215efd05122ddd973de4f (patch)
treed4723370425d7f2590ec18f5a4c364244b3f0896
parentd19e76d96c7316dffb1bca1593fcc80bcdc0a9ff (diff)
downloadopencode-091e88c1e157c53552e215efd05122ddd973de4f.tar.gz
opencode-091e88c1e157c53552e215efd05122ddd973de4f.zip
fix(opencode): sets input mode based on whether mouse vs keyboard is in use to prevent mouse events firing (#9449)
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx23
-rw-r--r--packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx21
2 files changed, 43 insertions, 1 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
index e27c32dfb..b6ca88410 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx
@@ -85,6 +85,7 @@ export function Autocomplete(props: {
index: 0,
selected: 0,
visible: false as AutocompleteRef["visible"],
+ input: "keyboard" as "keyboard" | "mouse",
})
const [positionTick, setPositionTick] = createSignal(0)
@@ -128,6 +129,14 @@ export function Autocomplete(props: {
return props.input().getTextRange(store.index + 1, props.input().cursorOffset)
})
+ // When the filter changes due to how TUI works, the mousemove might still be triggered
+ // via a synthetic event as the layout moves underneath the cursor. This is a workaround to make sure the input mode remains keyboard so
+ // that the mouseover event doesn't trigger when filtering.
+ createEffect(() => {
+ filter();
+ setStore("input", "keyboard")
+ })
+
function insertPart(text: string, part: PromptInfo["parts"][number]) {
const input = props.input()
const currentCursorOffset = input.cursorOffset
@@ -525,11 +534,13 @@ export function Autocomplete(props: {
const isNavDown = name === "down" || (ctrlOnly && name === "n")
if (isNavUp) {
+ setStore("input", "keyboard")
move(-1)
e.preventDefault()
return
}
if (isNavDown) {
+ setStore("input", "keyboard")
move(1)
e.preventDefault()
return
@@ -612,7 +623,17 @@ export function Autocomplete(props: {
paddingRight={1}
backgroundColor={index === store.selected ? theme.primary : undefined}
flexDirection="row"
- onMouseOver={() => moveTo(index)}
+ onMouseMove={() => {
+ setStore("input", "mouse")
+ }}
+ onMouseOver={() => {
+ if (store.input !== "mouse") return
+ moveTo(index)
+ }}
+ onMouseDown={() => {
+ setStore("input", "mouse")
+ moveTo(index)
+ }}
onMouseUp={() => select()}
>
<text fg={index === store.selected ? selectedForeground(theme) : theme.text} flexShrink={0}>
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index a8671f466..f7c2fed85 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -52,6 +52,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const [store, setStore] = createStore({
selected: 0,
filter: "",
+ input: "keyboard" as "keyboard" | "mouse",
})
createEffect(
@@ -83,6 +84,14 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
return result
})
+ // When the filter changes due to how TUI works, the mousemove might still be triggered
+ // via a synthetic event as the layout moves underneath the cursor. This is a workaround to make sure the input mode remains keyboard
+ // that the mouseover event doesn't trigger when filtering.
+ createEffect(() => {
+ filtered();
+ setStore("input", "keyboard")
+ })
+
const grouped = createMemo(() => {
const result = pipe(
filtered(),
@@ -157,12 +166,15 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const keybind = useKeybind()
useKeyboard((evt) => {
+ setStore("input", "keyboard")
+
if (evt.name === "up" || (evt.ctrl && evt.name === "p")) move(-1)
if (evt.name === "down" || (evt.ctrl && evt.name === "n")) move(1)
if (evt.name === "pageup") move(-10)
if (evt.name === "pagedown") move(10)
if (evt.name === "home") moveTo(0)
if (evt.name === "end") moveTo(flat().length - 1)
+
if (evt.name === "return") {
const option = selected()
if (option) {
@@ -259,11 +271,20 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
<box
id={JSON.stringify(option.value)}
flexDirection="row"
+ onMouseMove={() => {
+ setStore("input", "mouse")
+ }}
onMouseUp={() => {
option.onSelect?.(dialog)
props.onSelect?.(option)
}}
onMouseOver={() => {
+ if (store.input !== "mouse") return
+ const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
+ if (index === -1) return
+ moveTo(index)
+ }}
+ onMouseDown={() => {
const index = flat().findIndex((x) => isDeepEqual(x.value, option.value))
if (index === -1) return
moveTo(index)