Workers P4 Endprüfung W4: Esc und „Abbrechen“ schließen die Felder und geben den Fokus an den Knopf zurück

„Als Mensch antworten“ und „Antwort des Kunden einfügen“ ließen sich nur durch erneuten Klick auf den
Auslöser schließen. Esc (aus dem ganzen Feld, mit preventDefault für die Handy-Leiste) und der neue Knopf
„Abbrechen“ schließen das Feld, der Text bleibt in der Liste erhalten, der Fokus geht an den Auslöser
zurück (data-opens am Knopf, gesucht in der Aktionsgruppe: Button reicht kein ref durch). Während einer
Anfrage sind beide gesperrt. Neue Gruppe .wk-pult-panel-btns, auf dem Handy mit dem Hauptknopf oben.
Test-Läufer: ref-Objekte an Tags und eine document-Attrappe für den Fokus.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-18 23:11:38 +00:00
1 parent c7061837fb
commit ff3d042c0e
4 files changed
+141 -11

No files matched your search

+28 -7
View File
@@ -47,16 +47,28 @@ function rowName(label, title, joiner = ': ') {
}
// Aufgeklapptes Feld unter den Aktionen: „Als Mensch antworten“ (.wk-pult-reply) und „Antwort des Kunden einfügen“ (.wk-pult-paste).
// Textfeld, Zähler (Zeichen wie der Dienst sie zählt, höchstens REPLY_TEXT_MAX) und ein Knopf; die Prüfung macht die Zeile beim Absenden.
function TextPanel({ className, label, hint, value, onChange, submitLabel, onSubmit, busy }) {
// Textfeld, Zähler (Zeichen wie der Dienst sie zählt, höchstens REPLY_TEXT_MAX) und zwei Knöpfe; die Prüfung macht die Zeile beim Absenden.
// Esc (aus dem ganzen Feld heraus, nicht nur aus dem Textfeld) und „Abbrechen“ schließen das Feld, der eingegebene Text bleibt in der Liste erhalten.
// Während einer Anfrage ist beides gesperrt: das Feld soll nicht mitten im Senden verschwinden.
function TextPanel({ className, label, hint, value, onChange, submitLabel, onSubmit, onClose, busy }) {
const count = charCount(value)
// preventDefault: die Handy-Leiste (App.jsx) schließt bei Esc nur, wenn das Ereignis nicht behandelt wurde
const onKeyDown = (e) => {
if (e.key !== 'Escape' || busy) return
e.preventDefault()
e.stopPropagation()
onClose()
}
return (
<div className={className}>
<div className={className} onKeyDown={onKeyDown}>
{hint && <p className="wk-hint">{hint}</p>}
<textarea className="textarea" value={value} onChange={(e) => onChange(e.target.value)} aria-label={label} autoFocus />
<div className="wk-pult-panel-foot">
<span className={count > REPLY_TEXT_MAX ? 'wk-pult-count over' : 'wk-pult-count'}>{count} / {REPLY_TEXT_MAX}</span>
<Button type="button" variant="primary" onClick={onSubmit} disabled={busy}>{submitLabel}</Button>
<div className="wk-pult-panel-btns">
<Button type="button" onClick={onClose} disabled={busy}>Abbrechen</Button>
<Button type="button" variant="primary" onClick={onSubmit} disabled={busy}>{submitLabel}</Button>
</div>
</div>
</div>
)
@@ -72,6 +84,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
const [note, setNote] = useState(null) // { kind: 'ok' | 'err', text }
const busyRef = useRef(false) // sperrt schon im selben Takt, in dem busy noch nicht greift (Doppelklick)
const aliveRef = useRef(true)
const actionsRef = useRef(null) // die Aktionsgruppe: darin sucht close() den Knopf, der ein Feld geöffnet hat
useEffect(() => {
aliveRef.current = true
@@ -173,6 +186,12 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
const replyValue = fields.reply ?? text
const pasteValue = fields.paste ?? ''
const toggle = (which) => setOpen((current) => (current === which ? null : which))
// Feld schließen (Esc, „Abbrechen“) und den Fokus an den Knopf zurückgeben, der es geöffnet hat: der bleibt eingehängt, der Fokus kann sofort dorthin.
// Der Knopf trägt seine Kennung in data-opens (Button reicht sie weiter; ein ref ginge an Button vorbei).
const close = (which) => {
setOpen(null)
actionsRef.current?.querySelector(`[data-opens="${which}"]`)?.focus()
}
const sendReply = () => {
const check = validateReplyText(replyValue)
@@ -254,7 +273,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
)}
{hasActions && (
<div className="row-actions wk-pult-actions" role="group" aria-label={rowName('Aktionen', row.title)}>
<div className="row-actions wk-pult-actions" role="group" aria-label={rowName('Aktionen', row.title)} ref={actionsRef}>
{row.canSend && row.kind === 'mail_antwort' && (
mail
? <a className="btn btn-primary" href={mail}>E-Mail schreiben</a>
@@ -276,7 +295,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
</Button>
)}
{row.canReply && (
<Button type="button" onClick={() => toggle('reply')} aria-expanded={open === 'reply'} disabled={busy}>
<Button type="button" onClick={() => toggle('reply')} aria-expanded={open === 'reply'} data-opens="reply" disabled={busy}>
Als Mensch antworten
</Button>
)}
@@ -299,7 +318,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
</>
)}
{row.canPaste && (
<Button type="button" onClick={() => toggle('paste')} aria-expanded={open === 'paste'} disabled={busy}>
<Button type="button" onClick={() => toggle('paste')} aria-expanded={open === 'paste'} data-opens="paste" disabled={busy}>
Antwort des Kunden einfügen
</Button>
)}
@@ -328,6 +347,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
onChange={(value) => onField(row.id, 'reply', value)}
submitLabel="Jetzt senden"
onSubmit={sendReply}
onClose={() => close('reply')}
busy={busy}
/>
)}
@@ -340,6 +360,7 @@ function PultRow({ api, item, row, text, fields, onText, onField, refresh, onFin
onChange={(value) => onField(row.id, 'paste', value)}
submitLabel="Einfügen"
onSubmit={sendPaste}
onClose={() => close('paste')}
busy={busy}
/>
)}
+4
View File
@@ -212,6 +212,8 @@
.wk-pult-reply .wk-hint, .wk-pult-paste .wk-hint { margin: 0; }
.wk-pult-reply .textarea, .wk-pult-paste .textarea { display: block; width: 100%; min-height: 140px; }
.wk-pult-panel-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
/* „Abbrechen“ links vom Hauptknopf */
.wk-pult-panel-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.wk-pult-count { color: var(--text-2); font-size: 13px; font-variant-numeric: tabular-nums; }
.wk-pult-count.over { color: var(--red-fg); }
@@ -254,6 +256,8 @@
.wk-pult-row .wk-pult-actions .btn, .wk-pult-row .wk-pult-actions .link-danger { width: 100%; min-height: 40px; justify-content: center; text-align: center; }
/* Felder: Zähler und Knopf untereinander, der Knopf über die volle Breite */
.wk-pult-panel-foot { flex-direction: column; align-items: stretch; }
/* Hauptknopf oben, „Abbrechen“ darunter */
.wk-pult-panel-btns { flex-direction: column-reverse; align-items: stretch; }
.wk-pult-panel-foot .btn { width: 100%; min-height: 40px; justify-content: center; text-align: center; }
}