diff options
| author | Adam Malczewski <[email protected]> | 2026-05-29 20:33:08 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-29 20:33:08 +0900 |
| commit | c8e76ef506da32884ccf9ea2ac83a4d344c62943 (patch) | |
| tree | c0a04721d9898ed868520d86c07762b87c522632 | |
| parent | fd08733f751650b93d54c5cecb33556e95e0274d (diff) | |
| download | dispatch-c8e76ef506da32884ccf9ea2ac83a4d344c62943.tar.gz dispatch-c8e76ef506da32884ccf9ea2ac83a4d344c62943.zip | |
Make chat input an auto-growing textarea
Replace the single-line input with a textarea that starts at one line,
grows vertically as text wraps (up to 7 lines), then scrolls. Override
daisyUI's .textarea min-height:5rem so the resting state is one line.
Enter sends, Shift+Enter inserts a newline.
| -rw-r--r-- | packages/frontend/src/lib/components/ChatInput.svelte | 42 |
1 files changed, 34 insertions, 8 deletions
diff --git a/packages/frontend/src/lib/components/ChatInput.svelte b/packages/frontend/src/lib/components/ChatInput.svelte index 098856a..0c99078 100644 --- a/packages/frontend/src/lib/components/ChatInput.svelte +++ b/packages/frontend/src/lib/components/ChatInput.svelte @@ -1,7 +1,9 @@ <script lang="ts"> import { tabStore } from "../tabs.svelte.js"; -let inputEl: HTMLInputElement | undefined; +const MAX_LINES = 7; + +let inputEl: HTMLTextAreaElement | undefined; let inputValue = $state(""); const agentStatus = $derived(tabStore.activeTab?.agentStatus ?? "idle"); @@ -11,6 +13,29 @@ $effect(() => { inputEl?.focus(); }); +function resize() { + const el = inputEl; + if (!el) return; + // Reset height so scrollHeight reflects the content's natural height. + el.style.height = "auto"; + const style = getComputedStyle(el); + const lineHeight = Number.parseFloat(style.lineHeight) || 20; + const paddingY = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom); + const borderY = + Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth); + const maxHeight = lineHeight * MAX_LINES + paddingY + borderY; + const next = Math.min(el.scrollHeight, maxHeight); + el.style.height = `${next}px`; + el.style.overflowY = el.scrollHeight > maxHeight ? "auto" : "hidden"; +} + +// Re-run resize whenever the value changes (covers programmatic clears too). +$effect(() => { + // Touch inputValue so this effect tracks it. + void inputValue; + resize(); +}); + function handleKeydown(e: KeyboardEvent) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); @@ -26,7 +51,7 @@ function submit() { } </script> -<div class="flex items-center gap-2 p-3"> +<div class="flex items-end gap-2 p-3"> {#if agentStatus === "running"} <button type="button" @@ -38,24 +63,25 @@ function submit() { <span class="text-xs">Stop</span> </button> {:else if agentStatus === "idle"} - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-success"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-success shrink-0 mb-2"> <polyline points="20 6 9 17 4 12"></polyline> </svg> {:else if agentStatus === "error"} - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-error"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-error shrink-0 mb-2"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line> </svg> {/if} - <input + <textarea bind:this={inputEl} bind:value={inputValue} - type="text" + rows="1" placeholder="Type a message..." - class="input input-ghost flex-1" + class="textarea textarea-ghost flex-1 resize-none leading-normal !min-h-0 h-auto" onkeydown={handleKeydown} - /> + oninput={resize} + ></textarea> <button type="button" class="btn btn-primary" |
