From 3cff9509c64d96ff246811bdab9565892ebf172f Mon Sep 17 00:00:00 2001 From: murashit Date: Mon, 25 May 2026 22:54:51 +0900 Subject: [PATCH] Use toolbar panel row for thread rename --- src/features/chat/ui/toolbar.ts | 95 +++++++++++-------- styles.css | 8 +- tests/features/chat/ui/view-renderers.test.ts | 2 +- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/features/chat/ui/toolbar.ts b/src/features/chat/ui/toolbar.ts index f5776f48..41342310 100644 --- a/src/features/chat/ui/toolbar.ts +++ b/src/features/chat/ui/toolbar.ts @@ -368,34 +368,37 @@ function renderThreadList(parent: HTMLElement, threads: ToolbarThreadRow[], acti } function renderThreadRenameRow(parent: HTMLElement, thread: ToolbarThreadRow, actions: ToolbarActions): void { - const form = parent.createDiv({ cls: "menu-item codex-panel__toolbar-panel-item codex-panel__thread codex-panel__thread-rename" }); - form.createSpan({ - cls: "codex-panel__toolbar-panel-check codex-panel__thread-rename-spacer", - attr: { "aria-hidden": "true" }, - }); - const field = form.createDiv({ cls: "codex-panel__thread-rename-field" }); - const input = field.createEl("input", { - cls: "codex-panel__thread-rename-input", - attr: { - type: "text", - value: thread.rename?.draft ?? thread.title, - "aria-label": `Rename ${thread.title}`, + let input!: HTMLInputElement; + createToolbarPanelRow(parent, thread.title, { + className: "codex-panel__thread codex-panel__thread-rename", + interactive: false, + title: `Rename ${thread.title}`, + renderContent: (row) => { + const field = row.createDiv({ cls: "codex-panel__thread-rename-field" }); + input = field.createEl("input", { + cls: "codex-panel__thread-rename-input", + attr: { + type: "text", + value: thread.rename?.draft ?? thread.title, + "aria-label": `Rename ${thread.title}`, + }, + }); + input.oninput = () => { + actions.updateRenameDraft(thread.threadId, input.value); + }; + input.onkeydown = (event) => { + if (event.key === "Enter") { + event.preventDefault(); + if (!event.isComposing && !(thread.rename?.generating ?? false)) actions.saveRenameThread(thread.threadId, input.value); + return; + } + if (event.key === "Escape") { + event.preventDefault(); + actions.cancelRenameThread(thread.threadId); + } + }; }, }); - input.oninput = () => { - actions.updateRenameDraft(thread.threadId, input.value); - }; - input.onkeydown = (event) => { - if (event.key === "Enter") { - event.preventDefault(); - if (!event.isComposing && !(thread.rename?.generating ?? false)) actions.saveRenameThread(thread.threadId, input.value); - return; - } - if (event.key === "Escape") { - event.preventDefault(); - actions.cancelRenameThread(thread.threadId); - } - }; input.win.setTimeout(() => { if (input.ownerDocument.activeElement !== input) { input.focus(); @@ -436,33 +439,43 @@ function createToolbarPanelRow( title?: string; meta?: string; className?: string; + interactive?: boolean; + renderContent?: (parent: HTMLElement) => void; onClick?: () => void; } = {}, ): HTMLElement { const selected = Boolean(options.selected); const disabled = Boolean(options.disabled); + const interactive = options.interactive ?? true; + const attr: Record = { title: options.title ?? label }; + if (interactive) { + attr["role"] = "button"; + attr["tabindex"] = disabled ? "-1" : "0"; + attr["aria-disabled"] = disabled ? "true" : "false"; + attr["aria-selected"] = selected ? "true" : "false"; + } const item = parent.createDiv({ cls: ["codex-panel__toolbar-panel-item", options.className ?? "", selected ? "is-checked" : "", disabled ? "is-disabled" : ""] .filter(Boolean) .join(" "), - attr: { - role: "button", - tabindex: disabled ? "-1" : "0", - title: options.title ?? label, - "aria-disabled": disabled ? "true" : "false", - "aria-selected": selected ? "true" : "false", - }, + attr, }); - item.onclick = () => { - if (!disabled) options.onClick?.(); - }; - item.onkeydown = (event) => { - if (disabled || (event.key !== "Enter" && event.key !== " ")) return; - event.preventDefault(); - options.onClick?.(); - }; + if (interactive) { + item.onclick = () => { + if (!disabled) options.onClick?.(); + }; + item.onkeydown = (event) => { + if (disabled || (event.key !== "Enter" && event.key !== " ")) return; + event.preventDefault(); + options.onClick?.(); + }; + } const check = item.createSpan({ cls: "codex-panel__toolbar-panel-check" }); if (selected) setIcon(check, "check"); + if (options.renderContent) { + options.renderContent(item); + return item; + } item.createSpan({ cls: "codex-panel__toolbar-panel-label", text: label }); if (options.meta) item.createSpan({ cls: "codex-panel__toolbar-panel-meta", text: options.meta }); return item; diff --git a/styles.css b/styles.css index dc0e11f8..442c215a 100644 --- a/styles.css +++ b/styles.css @@ -1139,7 +1139,7 @@ background: transparent; } -.codex-panel .menu-item.codex-panel__thread-rename { +.codex-panel__thread-rename { position: relative; box-sizing: border-box; min-height: var(--codex-panel-size-nav-item); @@ -1147,7 +1147,7 @@ cursor: text; } -.codex-panel .menu-item.codex-panel__thread-rename::before { +.codex-panel__thread-rename::before { position: absolute; top: 0; right: 0; @@ -1158,10 +1158,6 @@ content: ""; } -.codex-panel__thread-rename-spacer { - align-self: center; -} - .codex-panel__thread-rename-field { position: relative; display: flex; diff --git a/tests/features/chat/ui/view-renderers.test.ts b/tests/features/chat/ui/view-renderers.test.ts index 453e60e3..ef9b505d 100644 --- a/tests/features/chat/ui/view-renderers.test.ts +++ b/tests/features/chat/ui/view-renderers.test.ts @@ -1864,7 +1864,7 @@ describe("toolbar renderer decisions", () => { const input = parent.querySelector(".codex-panel__thread-rename-input"); if (!input) throw new Error("Missing thread rename input"); - expect(parent.querySelector(".codex-panel__thread-rename-spacer")).not.toBeNull(); + expect(input.closest(".codex-panel__thread-rename")?.querySelector(".codex-panel__toolbar-panel-check")).not.toBeNull(); expect(input.value).toBe("Draft title"); input.value = "New title"; input.dispatchEvent(new Event("input", { bubbles: true }));