Use React handlers for rename inputs

This commit is contained in:
murashit 2026-05-27 21:25:08 +09:00
parent f3600e8947
commit 45c471a331
4 changed files with 42 additions and 52 deletions

View file

@ -517,38 +517,16 @@ function ArchiveModeButton({
function ThreadRenameRow({ thread, actions }: { thread: ToolbarThreadRow; actions: ToolbarActions }): ReactNode {
const inputRef = useRef<HTMLInputElement | null>(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);
}}
/>
</div>
)}

View file

@ -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);
}}
/>
</div>
</div>

View file

@ -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<HTMLInputElement>(".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");

View file

@ -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");