repl: auto-grow input textarea to fit pasted multi-line programs
Until now the input was pinned at rows="1" — paste a 40-line program and you edited it through a one-line keyhole. autosizeInput() now sets height to scrollHeight on every input event, history navigation, and post-send clear. CSS min/max-height (1.5em / 30vh) clamp the bounds; once the textarea hits 30vh it scrolls internally instead of pushing the prompt-bar off screen. Prompt sigil top-aligns (align-self: start + matching padding-top) so the λ> stays on the first line of input instead of drifting to vertical-center as the textarea grows. Smoke-tested headless: 1-line stays 22px, 6-line grows to 97px, 40-line caps at 216px (30vh of 720px) with scrollHeight 608 (internal scroll engaged), send empties back to 22px. Zero page errors.
This commit is contained in:
parent
d4380c64c7
commit
78d836fd09
6 changed files with 60 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue