diff --git a/wasm/dist-repl/repl.css b/wasm/dist-repl/repl.css index 7ea2382..66ca2d4 100644 --- a/wasm/dist-repl/repl.css +++ b/wasm/dist-repl/repl.css @@ -310,6 +310,12 @@ body.repl { color: var(--green); font-weight: 600; font-size: 0.95em; + /* Top-align so multi-line input keeps λ> on the first line instead + * of drifting to vertical-center as the textarea grows. The padding + * matches the textarea's padding-top so the sigil sits on the same + * baseline as the first character. */ + align-self: start; + padding-top: 0.4rem; } .prompt-bar textarea { resize: none; diff --git a/wasm/dist-repl/repl.js b/wasm/dist-repl/repl.js index da5f681..623a2ce 100644 --- a/wasm/dist-repl/repl.js +++ b/wasm/dist-repl/repl.js @@ -534,6 +534,7 @@ function historyPrev() { } inputEl.value = entries[history.idx]; inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function historyNext() { @@ -548,6 +549,7 @@ function historyNext() { inputEl.value = entries[history.idx]; } inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function resetHistory() { @@ -555,6 +557,16 @@ function resetHistory() { history.draft = ""; } +// Auto-grow the textarea to fit its content (up to the max-height the +// CSS pins it at, after which the textarea scrolls internally). Paste +// a 40-line program and the prompt-bar expands instead of leaving you +// editing the program through a one-line keyhole. Fires on every input +// event and every programmatic value set (sendInput, history, etc.). +function autosizeInput() { + inputEl.style.height = "auto"; + inputEl.style.height = inputEl.scrollHeight + "px"; +} + // ─── Input handling ───────────────────────────────────────────────── async function sendInput() { const src = inputEl.value.trim(); @@ -577,6 +589,7 @@ async function sendInput() { }; tab.transcript.push(entry); inputEl.value = ""; + autosizeInput(); resetHistory(); sendBtn.disabled = true; cancelBtn.disabled = false; @@ -737,6 +750,7 @@ inputEl.addEventListener("keydown", (e) => { } }); inputEl.addEventListener("input", () => { + autosizeInput(); // Any keystroke that isn't an arrow drops history navigation — // edits are now the user's own draft, not the historical entry. if (history.idx !== null && document.activeElement === inputEl) { diff --git a/wasm/repl/repl.css b/wasm/repl/repl.css index 7ea2382..66ca2d4 100644 --- a/wasm/repl/repl.css +++ b/wasm/repl/repl.css @@ -310,6 +310,12 @@ body.repl { color: var(--green); font-weight: 600; font-size: 0.95em; + /* Top-align so multi-line input keeps λ> on the first line instead + * of drifting to vertical-center as the textarea grows. The padding + * matches the textarea's padding-top so the sigil sits on the same + * baseline as the first character. */ + align-self: start; + padding-top: 0.4rem; } .prompt-bar textarea { resize: none; diff --git a/wasm/repl/repl.js b/wasm/repl/repl.js index da5f681..623a2ce 100644 --- a/wasm/repl/repl.js +++ b/wasm/repl/repl.js @@ -534,6 +534,7 @@ function historyPrev() { } inputEl.value = entries[history.idx]; inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function historyNext() { @@ -548,6 +549,7 @@ function historyNext() { inputEl.value = entries[history.idx]; } inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function resetHistory() { @@ -555,6 +557,16 @@ function resetHistory() { history.draft = ""; } +// Auto-grow the textarea to fit its content (up to the max-height the +// CSS pins it at, after which the textarea scrolls internally). Paste +// a 40-line program and the prompt-bar expands instead of leaving you +// editing the program through a one-line keyhole. Fires on every input +// event and every programmatic value set (sendInput, history, etc.). +function autosizeInput() { + inputEl.style.height = "auto"; + inputEl.style.height = inputEl.scrollHeight + "px"; +} + // ─── Input handling ───────────────────────────────────────────────── async function sendInput() { const src = inputEl.value.trim(); @@ -577,6 +589,7 @@ async function sendInput() { }; tab.transcript.push(entry); inputEl.value = ""; + autosizeInput(); resetHistory(); sendBtn.disabled = true; cancelBtn.disabled = false; @@ -737,6 +750,7 @@ inputEl.addEventListener("keydown", (e) => { } }); inputEl.addEventListener("input", () => { + autosizeInput(); // Any keystroke that isn't an arrow drops history navigation — // edits are now the user's own draft, not the historical entry. if (history.idx !== null && document.activeElement === inputEl) { diff --git a/www/repl/repl.css b/www/repl/repl.css index 7ea2382..66ca2d4 100644 --- a/www/repl/repl.css +++ b/www/repl/repl.css @@ -310,6 +310,12 @@ body.repl { color: var(--green); font-weight: 600; font-size: 0.95em; + /* Top-align so multi-line input keeps λ> on the first line instead + * of drifting to vertical-center as the textarea grows. The padding + * matches the textarea's padding-top so the sigil sits on the same + * baseline as the first character. */ + align-self: start; + padding-top: 0.4rem; } .prompt-bar textarea { resize: none; diff --git a/www/repl/repl.js b/www/repl/repl.js index da5f681..623a2ce 100644 --- a/www/repl/repl.js +++ b/www/repl/repl.js @@ -534,6 +534,7 @@ function historyPrev() { } inputEl.value = entries[history.idx]; inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function historyNext() { @@ -548,6 +549,7 @@ function historyNext() { inputEl.value = entries[history.idx]; } inputEl.setSelectionRange(inputEl.value.length, inputEl.value.length); + autosizeInput(); } function resetHistory() { @@ -555,6 +557,16 @@ function resetHistory() { history.draft = ""; } +// Auto-grow the textarea to fit its content (up to the max-height the +// CSS pins it at, after which the textarea scrolls internally). Paste +// a 40-line program and the prompt-bar expands instead of leaving you +// editing the program through a one-line keyhole. Fires on every input +// event and every programmatic value set (sendInput, history, etc.). +function autosizeInput() { + inputEl.style.height = "auto"; + inputEl.style.height = inputEl.scrollHeight + "px"; +} + // ─── Input handling ───────────────────────────────────────────────── async function sendInput() { const src = inputEl.value.trim(); @@ -577,6 +589,7 @@ async function sendInput() { }; tab.transcript.push(entry); inputEl.value = ""; + autosizeInput(); resetHistory(); sendBtn.disabled = true; cancelBtn.disabled = false; @@ -737,6 +750,7 @@ inputEl.addEventListener("keydown", (e) => { } }); inputEl.addEventListener("input", () => { + autosizeInput(); // Any keystroke that isn't an arrow drops history navigation — // edits are now the user's own draft, not the historical entry. if (history.idx !== null && document.activeElement === inputEl) {