From 45c471a33126ece91a010c3fc5fee63e88535db7 Mon Sep 17 00:00:00 2001 From: murashit Date: Wed, 27 May 2026 21:25:08 +0900 Subject: [PATCH] Use React handlers for rename inputs --- src/features/chat/ui/toolbar.tsx | 45 +++++++++---------- src/features/threads-view/renderer.tsx | 41 ++++++++--------- tests/features/chat/ui/view-renderers.test.ts | 6 +-- tests/features/threads-view/view.test.ts | 2 +- 4 files changed, 42 insertions(+), 52 deletions(-) diff --git a/src/features/chat/ui/toolbar.tsx b/src/features/chat/ui/toolbar.tsx index 253d0098..0843db34 100644 --- a/src/features/chat/ui/toolbar.tsx +++ b/src/features/chat/ui/toolbar.tsx @@ -517,38 +517,16 @@ function ArchiveModeButton({ function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarActions }): ReactNode { const inputRef = useRef(null); const generating = thread.rename?.generating ?? false; + const draft = thread.rename?.draft ?? thread.title; useLayoutEffect(() => { const input = inputRef.current; if (!input) return; - input.oninput = () => { - actions.updateRenameDraft(thread.threadId, input.value); - }; - input.onkeydown = (event) => { - if (event.key === "Enter") { - event.preventDefault(); - if (!event.isComposing && !generating) actions.saveRenameThread(thread.threadId, input.value); - return; - } - if (event.key === "Escape") { - event.preventDefault(); - actions.cancelRenameThread(thread.threadId); - } - }; - input.onblur = () => { - if (!generating) actions.saveRenameThread(thread.threadId, input.value); - }; - const draft = thread.rename?.draft ?? thread.title; if (input.value !== draft) input.value = draft; if (input.ownerDocument.activeElement !== input) { input.focus(); input.select(); } - return () => { - input.oninput = null; - input.onkeydown = null; - input.onblur = null; - }; - }, [actions, generating, thread.rename?.draft, thread.threadId, thread.title]); + }, [draft]); return ( <> @@ -562,8 +540,25 @@ function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; action ref={inputRef} className="codex-panel__thread-rename-input" type="text" - defaultValue={thread.rename?.draft ?? thread.title} + defaultValue={draft} aria-label={`Rename ${thread.title}`} + onInput={(event) => { + actions.updateRenameDraft(thread.threadId, event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + if (!event.nativeEvent.isComposing && !generating) actions.saveRenameThread(thread.threadId, event.currentTarget.value); + return; + } + if (event.key === "Escape") { + event.preventDefault(); + actions.cancelRenameThread(thread.threadId); + } + }} + onBlur={(event) => { + if (!generating) actions.saveRenameThread(thread.threadId, event.currentTarget.value); + }} /> )} diff --git a/src/features/threads-view/renderer.tsx b/src/features/threads-view/renderer.tsx index 1faa9af4..7075c4f9 100644 --- a/src/features/threads-view/renderer.tsx +++ b/src/features/threads-view/renderer.tsx @@ -185,34 +185,12 @@ function RenameRow({ row, actions }: { row: ThreadsRowModel; actions: ThreadsVie useLayoutEffect(() => { const input = inputRef.current; if (!input) return; - input.oninput = () => { - actions.updateRename(row.thread.id, input.value); - }; - input.onkeydown = (event) => { - if (event.key === "Enter") { - event.preventDefault(); - if (!event.isComposing && !row.rename.generating) actions.saveRename(row.thread.id, input.value); - return; - } - if (event.key === "Escape") { - event.preventDefault(); - actions.cancelRename(row.thread.id); - } - }; - input.onblur = () => { - if (!row.rename.generating) actions.saveRename(row.thread.id, input.value); - }; if (input.value !== row.rename.draft) input.value = row.rename.draft; if (input.ownerDocument.activeElement !== input) { input.focus(); input.select(); } - return () => { - input.oninput = null; - input.onkeydown = null; - input.onblur = null; - }; - }, [actions, row.rename.draft, row.rename.generating, row.thread.id]); + }, [row.rename.draft]); return ( <> @@ -229,6 +207,23 @@ function RenameRow({ row, actions }: { row: ThreadsRowModel; actions: ThreadsVie type="text" aria-label="Thread name" defaultValue={row.rename.draft} + onInput={(event) => { + actions.updateRename(row.thread.id, event.currentTarget.value); + }} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + if (!event.nativeEvent.isComposing && !row.rename.generating) actions.saveRename(row.thread.id, event.currentTarget.value); + return; + } + if (event.key === "Escape") { + event.preventDefault(); + actions.cancelRename(row.thread.id); + } + }} + onBlur={(event) => { + if (!row.rename.generating) actions.saveRename(row.thread.id, event.currentTarget.value); + }} /> diff --git a/tests/features/chat/ui/view-renderers.test.ts b/tests/features/chat/ui/view-renderers.test.ts index ac2272e5..1448451c 100644 --- a/tests/features/chat/ui/view-renderers.test.ts +++ b/tests/features/chat/ui/view-renderers.test.ts @@ -1985,7 +1985,7 @@ describe("toolbar renderer decisions", () => { input.dispatchEvent(new Event("input", { bubbles: true })); expect(updateRenameDraft).toHaveBeenCalledWith("editing", "New title"); - input.dispatchEvent(new FocusEvent("blur")); + input.dispatchEvent(new FocusEvent("focusout", { bubbles: true })); expect(saveRenameThread).toHaveBeenCalledWith("editing", "New title"); input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); expect(cancelRenameThread).toHaveBeenCalledWith("editing"); @@ -2179,8 +2179,8 @@ describe("threads view renderer decisions", () => { const input = expectPresent(parent.querySelector(".codex-panel-threads__rename-input")); input.value = "New name"; - input.dispatchEvent(new Event("input")); - input.dispatchEvent(new FocusEvent("blur")); + input.dispatchEvent(new Event("input", { bubbles: true })); + input.dispatchEvent(new FocusEvent("focusout", { bubbles: true })); expect(actions.updateRename).toHaveBeenCalledWith("thread", "New name"); expect(actions.saveRename).toHaveBeenCalledWith("thread", "New name"); diff --git a/tests/features/threads-view/view.test.ts b/tests/features/threads-view/view.test.ts index d2837376..4a6cf4da 100644 --- a/tests/features/threads-view/view.test.ts +++ b/tests/features/threads-view/view.test.ts @@ -273,7 +273,7 @@ describe("CodexThreadsView", () => { expect(input).not.toBeNull(); if (!input) return; input.value = "Renamed thread"; - input.dispatchEvent(new FocusEvent("blur")); + input.dispatchEvent(new FocusEvent("focusout", { bubbles: true })); await vi.waitFor(() => { expect(setThreadName).toHaveBeenCalledWith("thread", "Renamed thread");