Skip to content

Commit 6675650

Browse files
committed
Auto-focus editor on typing and global Shift+Tab mode cycling
1 parent c3af5d9 commit 6675650

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

apps/twig/src/renderer/features/sessions/components/SessionView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
isJsonRpcNotification,
2121
isJsonRpcResponse,
2222
} from "@shared/types/session-events";
23+
import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping";
2324
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2425
import { useHotkeys } from "react-hotkeys-hook";
2526
import { getSessionService } from "../service/service";
@@ -336,6 +337,8 @@ export function SessionView({
336337
editorRef.current?.focus();
337338
}, []);
338339

340+
useAutoFocusOnTyping(editorRef);
341+
339342
return (
340343
<ContextMenu.Root>
341344
<ContextMenu.Trigger>

apps/twig/src/renderer/features/task-detail/components/TaskInput.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import {
1010
} from "@features/sessions/stores/sessionStore";
1111
import type { AgentAdapter } from "@features/settings/stores/settingsStore";
1212
import { useSettingsStore } from "@features/settings/stores/settingsStore";
13+
import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping";
1314
import { useRepositoryIntegration } from "@hooks/useIntegrations";
1415
import { Flex } from "@radix-ui/themes";
1516
import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore";
1617
import { useNavigationStore } from "@stores/navigationStore";
1718
import { useTaskDirectoryStore } from "@stores/taskDirectoryStore";
1819
import { useCallback, useEffect, useRef, useState } from "react";
20+
import { useHotkeys } from "react-hotkeys-hook";
1921
import { usePreviewSession } from "../hooks/usePreviewSession";
2022
import { useTaskCreation } from "../hooks/useTaskCreation";
2123
import { TaskInputEditor } from "./TaskInputEditor";
@@ -107,6 +109,23 @@ export function TaskInput() {
107109
}
108110
}, [modeOption, allowBypassPermissions, previewTaskId]);
109111

112+
// Global shift+tab to cycle mode regardless of focus
113+
useHotkeys(
114+
"shift+tab",
115+
(e) => {
116+
e.preventDefault();
117+
handleCycleMode();
118+
},
119+
{
120+
enableOnFormTags: true,
121+
enableOnContentEditable: true,
122+
enabled: !!modeOption,
123+
},
124+
[handleCycleMode, modeOption],
125+
);
126+
127+
useAutoFocusOnTyping(editorRef, isCreatingTask);
128+
110129
const handleDragEnter = useCallback((e: React.DragEvent) => {
111130
e.preventDefault();
112131
e.stopPropagation();
@@ -253,7 +272,6 @@ export function TaskInput() {
253272
onEmptyChange={setEditorIsEmpty}
254273
adapter={adapter}
255274
previewTaskId={previewTaskId}
256-
onCycleMode={handleCycleMode}
257275
onAdapterChange={setAdapter}
258276
isPreviewConnecting={isConnecting}
259277
/>

apps/twig/src/renderer/features/task-detail/components/TaskInputEditor.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ interface TaskInputEditorProps {
2525
onEmptyChange?: (isEmpty: boolean) => void;
2626
adapter?: "claude" | "codex";
2727
previewTaskId?: string;
28-
onCycleMode?: () => void;
2928
onAdapterChange?: (adapter: AgentAdapter) => void;
3029
isPreviewConnecting?: boolean;
3130
}
@@ -45,7 +44,6 @@ export const TaskInputEditor = forwardRef<
4544
onEmptyChange,
4645
adapter,
4746
previewTaskId,
48-
onCycleMode,
4947
onAdapterChange,
5048
isPreviewConnecting,
5149
},
@@ -148,13 +146,6 @@ export const TaskInputEditor = forwardRef<
148146
focus();
149147
}
150148
}}
151-
onKeyDown={(e) => {
152-
if (e.key === "Tab" && e.shiftKey && onCycleMode) {
153-
e.preventDefault();
154-
e.stopPropagation();
155-
onCycleMode();
156-
}
157-
}}
158149
>
159150
<AttachmentsBar
160151
attachments={attachments}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { MessageEditorHandle } from "@features/message-editor/components/MessageEditor";
2+
import { type RefObject, useEffect } from "react";
3+
4+
export function useAutoFocusOnTyping(
5+
editorRef: RefObject<MessageEditorHandle | null>,
6+
disabled = false,
7+
) {
8+
useEffect(() => {
9+
const handleKeyDown = (e: KeyboardEvent) => {
10+
if (disabled) return;
11+
12+
const activeEl = document.activeElement;
13+
const isInInput =
14+
activeEl &&
15+
(activeEl.tagName === "INPUT" ||
16+
activeEl.tagName === "TEXTAREA" ||
17+
activeEl.tagName === "SELECT" ||
18+
activeEl.getAttribute("contenteditable") === "true");
19+
if (isInInput) return;
20+
21+
if (e.key.length > 1 || e.metaKey || e.ctrlKey || e.altKey) return;
22+
23+
editorRef.current?.focus();
24+
};
25+
26+
document.addEventListener("keydown", handleKeyDown);
27+
return () => document.removeEventListener("keydown", handleKeyDown);
28+
}, [editorRef, disabled]);
29+
}

0 commit comments

Comments
 (0)