feat(timeline): add EventForm curator create/edit form

One component for both routes: /new renders it empty, /[id]/edit seeds it from a
TimelineEventView. Composes EventTypeSelect, the shared DatePrecisionField, a
plain-textarea description, PersonMultiSelect and DocumentMultiSelect (personIds
/documentIds hidden inputs). lg:grid-cols-[2fr_1fr] collapsing to one column
below lg, sticky save bar, beforeNavigate unsaved-changes guard, submitting flag
via use:enhance (disabled submit), and a delete form gated by getConfirmService()
read lazily so the component mounts cleanly in isolation. Title/description/chip
labels render via default {...} escaping (CWE-79). Seeded DocumentRefs degrade
gracefully to DocumentOption (no precision fields). Pickers gain an inputId prop
so <label for> associates the control; eslint boundaries now lets timeline import
person+document (mirrors the geschichte editor). 6/6 component specs green.

Refs #781
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-13 22:45:39 +02:00
parent 54f9d8fdd5
commit 15ff6db1d3
5 changed files with 398 additions and 3 deletions

View File

@@ -13,13 +13,16 @@ interface Props {
hiddenInputName?: string;
/** Empty-state text shown inside the chip container when nothing is selected. */
emptyLabel?: string;
/** id of the search input so a <label for=...> can be associated. */
inputId?: string;
}
let {
selectedDocuments = $bindable([]),
placeholder = m.geschichte_editor_search_document(),
hiddenInputName = 'documentIds',
emptyLabel = undefined
emptyLabel = undefined,
inputId = undefined
}: Props = $props();
let searchTerm = $state('');
@@ -97,6 +100,7 @@ function removeDocument(id: string | undefined) {
<input
bind:this={inputEl}
id={inputId}
type="text"
autocomplete="off"
bind:value={searchTerm}