Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/app/src/components/dialog-select-mcp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component, createMemo, createSignal, Show } from "solid-js"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
Expand All @@ -16,6 +17,7 @@ const statusLabels = {
export const DialogSelectMcp: Component = () => {
const sync = useSync()
const sdk = useSDK()
const dialog = useDialog()
const language = useLanguage()
const [loading, setLoading] = createSignal<string | null>(null)

Expand Down Expand Up @@ -58,6 +60,12 @@ export const DialogSelectMcp: Component = () => {
items={items}
filterKeys={["name", "status"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
onKeyEvent={(event) => {
if (event.key !== "Escape") return
event.preventDefault()
event.stopPropagation()
dialog.close()
}}
onSelect={(x) => {
if (x) toggle(x.name)
}}
Expand Down
24 changes: 24 additions & 0 deletions packages/app/src/components/list-escape-key.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test } from "bun:test"
import { dispatchListKeyEvent } from "@opencode-ai/ui/list-keyboard"

describe("list keyboard handling", () => {
test("forwards Escape to onKeyEvent for searchable lists", () => {
const events: string[] = []
const items = [{ id: "github", name: "github" }]
const event = new KeyboardEvent("keydown", { key: "Escape" })

const { selected, index } = dispatchListKeyEvent(
event,
items,
null,
(item) => item.id,
(keyboardEvent, item) => {
events.push(`${keyboardEvent.key}:${item?.id ?? "none"}`)
},
)

expect(selected).toBeUndefined()
expect(index).toBe(-1)
expect(events).toEqual(["Escape:none"])
})
})
17 changes: 17 additions & 0 deletions packages/ui/src/components/list-keyboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function resolveListSelection<T>(items: T[], activeKey: string | null, keyOf: (item: T) => string) {
const selected = items.find((item) => keyOf(item) === activeKey)
const index = selected ? items.indexOf(selected) : -1
return { selected, index }
}

export function dispatchListKeyEvent<T>(
event: KeyboardEvent,
items: T[],
activeKey: string | null,
keyOf: (item: T) => string,
onKeyEvent: ((event: KeyboardEvent, item: T | undefined) => void) | undefined,
) {
const { selected, index } = resolveListSelection(items, activeKey, keyOf)
onKeyEvent?.(event, selected)
return { selected, index }
}
7 changes: 3 additions & 4 deletions packages/ui/src/components/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createStore } from "solid-js/store"
import { useI18n } from "../context/i18n"
import { Icon, type IconProps } from "./icon"
import { IconButton } from "./icon-button"
import { dispatchListKeyEvent } from "./list-keyboard"
import { TextField } from "./text-field"

function findByKey(container: HTMLElement, key: string) {
Expand Down Expand Up @@ -166,14 +167,12 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })

const handleKey = (e: KeyboardEvent) => {
setStore("mouseActive", false)
if (e.key === "Escape") return

const all = flat()
const selected = all.find((x) => props.key(x) === active())
const index = selected ? all.indexOf(selected) : -1
props.onKeyEvent?.(e, selected)
const { selected, index } = dispatchListKeyEvent(e, all, active(), props.key, props.onKeyEvent)

if (e.defaultPrevented) return
if (e.key === "Escape") return

if (e.key === "Enter" && !e.isComposing) {
e.preventDefault()
Expand Down
Loading