mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-08-29 18:31:33 +00:00
Fix command palette keyboard navigation (#1304)
This commit is contained in:
+6
-4
@@ -40,6 +40,7 @@ import { useUiPreferencesContext } from "@/contexts/UiPreferencesContext";
|
|||||||
import { defaultSizes, SplitView, type RowColSizes } from "@/shell/SplitView";
|
import { defaultSizes, SplitView, type RowColSizes } from "@/shell/SplitView";
|
||||||
import { renderTabContent } from "@/shell/tabUtils";
|
import { renderTabContent } from "@/shell/tabUtils";
|
||||||
import { TabBar } from "@/shell/TabBar";
|
import { TabBar } from "@/shell/TabBar";
|
||||||
|
import { dispatchCtrlW, isShiftKey } from "@/lib/app-keyboard-shortcuts";
|
||||||
|
|
||||||
// Shell surfaces that are not needed for first paint.
|
// Shell surfaces that are not needed for first paint.
|
||||||
const CommandPalette = lazy(() =>
|
const CommandPalette = lazy(() =>
|
||||||
@@ -466,9 +467,10 @@ export function AppShell({
|
|||||||
activeTabIdRef.current = activeTabId;
|
activeTabIdRef.current = activeTabId;
|
||||||
}, [activeTabId]);
|
}, [activeTabId]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return window.electronAPI?.onCloseActiveTab?.(() =>
|
return window.electronAPI?.onCloseActiveTab?.(() => {
|
||||||
closeActiveTabRef.current(),
|
if (dispatchCtrlW(document.activeElement)) return;
|
||||||
);
|
closeActiveTabRef.current();
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
const skipSplitSyncRef = useRef(false);
|
const skipSplitSyncRef = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -575,7 +577,7 @@ export function AppShell({
|
|||||||
// hard to discover.
|
// hard to discover.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.code === "ShiftLeft" && !e.repeat) {
|
if (isShiftKey(e) && !e.repeat) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastShiftTime.current < 300 && commandPaletteShortcutEnabled)
|
if (now - lastShiftTime.current < 300 && commandPaletteShortcutEnabled)
|
||||||
setCommandPaletteOpen((prev) => !prev);
|
setCommandPaletteOpen((prev) => !prev);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -140,6 +140,7 @@ export function CommandPalette({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [snippets, setSnippets] = useState<Snippet[]>([]);
|
const [snippets, setSnippets] = useState<Snippet[]>([]);
|
||||||
|
const [selectedValue, setSelectedValue] = useState("");
|
||||||
const { runSnippet, dialog: runSnippetDialog } = useSnippetRunner();
|
const { runSnippet, dialog: runSnippetDialog } = useSnippetRunner();
|
||||||
|
|
||||||
useEffect(() => {
|
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 =
|
const activeTargetTab =
|
||||||
terminalTabs.find((tab) => tab.id === activeTabId) ?? terminalTabs[0];
|
terminalTabs.find((tab) => tab.id === activeTabId) ?? terminalTabs[0];
|
||||||
|
|
||||||
@@ -234,7 +249,13 @@ export function CommandPalette({
|
|||||||
)}
|
)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
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">
|
<div className="flex items-center border-b border-border px-4 py-1">
|
||||||
<Search className="size-4 text-muted-foreground mr-3" />
|
<Search className="size-4 text-muted-foreground mr-3" />
|
||||||
<CommandPrimitive.Input
|
<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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user