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 { 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);
|
||||
|
||||
@@ -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 [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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user