Fix command palette keyboard navigation (#1304)

This commit is contained in:
ZacharyZcR
2026-08-24 01:05:24 +08:00
committed by GitHub
parent 0a9086fb79
commit f1226b1f1b
4 changed files with 78 additions and 5 deletions
+6 -4
View File
@@ -40,6 +40,7 @@ import { useUiPreferencesContext } from "@/contexts/UiPreferencesContext";
import { defaultSizes, SplitView, type RowColSizes } from "@/shell/SplitView";
import { renderTabContent } from "@/shell/tabUtils";
import { TabBar } from "@/shell/TabBar";
import { dispatchCtrlW, isShiftKey } from "@/lib/app-keyboard-shortcuts";
// Shell surfaces that are not needed for first paint.
const CommandPalette = lazy(() =>
@@ -466,9 +467,10 @@ export function AppShell({
activeTabIdRef.current = activeTabId;
}, [activeTabId]);
useEffect(() => {
return window.electronAPI?.onCloseActiveTab?.(() =>
closeActiveTabRef.current(),
);
return window.electronAPI?.onCloseActiveTab?.(() => {
if (dispatchCtrlW(document.activeElement)) return;
closeActiveTabRef.current();
});
}, []);
const skipSplitSyncRef = useRef(false);
useEffect(() => {
@@ -575,7 +577,7 @@ export function AppShell({
// hard to discover.
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.code === "ShiftLeft" && !e.repeat) {
if (isShiftKey(e) && !e.repeat) {
const now = Date.now();
if (now - lastShiftTime.current < 300 && commandPaletteShortcutEnabled)
setCommandPaletteOpen((prev) => !prev);
+20
View File
@@ -0,0 +1,20 @@
export function isShiftKey(event: Pick<KeyboardEvent, "key" | "code">) {
return (
event.key === "Shift" ||
event.code === "ShiftLeft" ||
event.code === "ShiftRight"
);
}
export function dispatchCtrlW(target: EventTarget | null) {
if (!target) return false;
const event = new KeyboardEvent("keydown", {
key: "w",
code: "KeyW",
ctrlKey: true,
bubbles: true,
cancelable: true,
});
return !target.dispatchEvent(event);
}
+22 -1
View File
@@ -140,6 +140,7 @@ export function CommandPalette({
[],
);
const [snippets, setSnippets] = useState<Snippet[]>([]);
const [selectedValue, setSelectedValue] = useState("");
const { runSnippet, dialog: runSnippetDialog } = useSnippetRunner();
useEffect(() => {
@@ -207,6 +208,20 @@ export function CommandPalette({
})
: [];
useEffect(() => {
if (!isOpen) return;
const firstHost = filteredHosts[0];
if (search.trim() && firstHost) {
setSelectedValue(`host-${firstHost.id}`);
return;
}
setSelectedValue(
window.electronAPI?.isElectron
? "quick-action-local-terminal"
: "quick-action-add-host",
);
}, [filteredHosts, isOpen, search]);
const activeTargetTab =
terminalTabs.find((tab) => tab.id === activeTabId) ?? terminalTabs[0];
@@ -234,7 +249,13 @@ export function CommandPalette({
)}
onClick={(e) => e.stopPropagation()}
>
<Command className="rounded-none" shouldFilter={false} loop>
<Command
className="rounded-none"
shouldFilter={false}
loop
value={selectedValue}
onValueChange={setSelectedValue}
>
<div className="flex items-center border-b border-border px-4 py-1">
<Search className="size-4 text-muted-foreground mr-3" />
<CommandPrimitive.Input
@@ -0,0 +1,30 @@
import { describe, expect, it, vi } from "vitest";
import { dispatchCtrlW, isShiftKey } from "../../lib/app-keyboard-shortcuts";
describe("app keyboard shortcuts", () => {
it.each(["ShiftLeft", "ShiftRight"])(
"accepts %s for double Shift",
(code) => {
expect(isShiftKey({ key: "Shift", code })).toBe(true);
},
);
it("does not classify other keys as Shift", () => {
expect(isShiftKey({ key: "w", code: "KeyW" })).toBe(false);
});
it("lets the focused control consume Electron Ctrl+W", () => {
const target = document.createElement("input");
const listener = vi.fn((event: KeyboardEvent) => event.preventDefault());
target.addEventListener("keydown", listener);
expect(dispatchCtrlW(target)).toBe(true);
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({ key: "w", code: "KeyW", ctrlKey: true }),
);
});
it("falls back to closing the active tab when Ctrl+W is not consumed", () => {
expect(dispatchCtrlW(document.createElement("div"))).toBe(false);
});
});