agent terminal: multi-line textarea input
swap the single-line <input> for an auto-growing <textarea>. enter submits, shift+enter newlines, ime composition respected (skip submit while isComposing). height caps at ~12em then scrolls. submit-hint updates to '↵ send · ⇧↵ newline'. async-form handler now also clears textareas on success.
This commit is contained in:
parent
fd39226883
commit
85e1f1a8f4
2 changed files with 36 additions and 12 deletions
|
|
@ -47,7 +47,7 @@
|
|||
return;
|
||||
}
|
||||
// Clear text inputs the operator typed into (the form value was sent).
|
||||
f.querySelectorAll('input[type="text"], input:not([type])').forEach((i) => { i.value = ''; });
|
||||
f.querySelectorAll('input[type="text"], input:not([type]), textarea').forEach((i) => { i.value = ''; });
|
||||
// Re-enable the button — refreshState() often skips re-rendering the
|
||||
// form (status unchanged), so without this the spinner sticks and
|
||||
// the operator can't submit again.
|
||||
|
|
@ -165,20 +165,37 @@
|
|||
action: '/send', method: 'POST',
|
||||
class: 'sendform-term', 'data-async': '',
|
||||
});
|
||||
const ta = el('textarea', {
|
||||
name: 'body', placeholder: 'message ' + label + '…',
|
||||
required: '', autocomplete: 'off', rows: '1',
|
||||
});
|
||||
// Enter submits, Shift+Enter inserts a newline. Auto-grow up to
|
||||
// ~8 rows of content, then scroll inside the textarea.
|
||||
const MAX_PX = 12 * 16; // ~8 lines @ 1.5 line-height, 1em base
|
||||
const grow = () => {
|
||||
ta.style.height = 'auto';
|
||||
ta.style.height = Math.min(ta.scrollHeight, MAX_PX) + 'px';
|
||||
};
|
||||
ta.addEventListener('input', grow);
|
||||
ta.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
|
||||
e.preventDefault();
|
||||
if (ta.value.trim()) form.requestSubmit();
|
||||
}
|
||||
});
|
||||
// Reset height after async submit clears the value.
|
||||
form.addEventListener('submit', () => setTimeout(grow, 0));
|
||||
form.append(
|
||||
el('span', { class: 'prompt' }, 'operator@' + label + ' ▸'),
|
||||
el('input', {
|
||||
name: 'body', placeholder: 'message ' + label + '…',
|
||||
required: '', autocomplete: 'off',
|
||||
}),
|
||||
el('span', { class: 'submit-hint' }, 'enter ↵'),
|
||||
ta,
|
||||
el('span', { class: 'submit-hint' }, '↵ send · ⇧↵ newline'),
|
||||
);
|
||||
slot.append(form);
|
||||
termInputRendered = true;
|
||||
}
|
||||
slot.classList.toggle('disabled', !online);
|
||||
const input = slot.querySelector('input');
|
||||
if (input) input.disabled = !online;
|
||||
const ta = slot.querySelector('textarea');
|
||||
if (ta) ta.disabled = !online;
|
||||
}
|
||||
|
||||
// Track banner activity by reference-counting in-flight turns. A turn
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue