Timeline: curator event create/edit forms (#781) #832

Open
marcel wants to merge 23 commits from feat/issue-781-timeline-curator-forms into main
3 changed files with 25 additions and 10 deletions
Showing only changes of commit 4dc5e3278f - Show all commits

View File

@@ -35,6 +35,7 @@ let {
suggestedDateIso = '',
dateLabel = m.form_label_date(),
dateRequired = true,
dateError = '',
dateTestId = undefined,
precisionTestId = undefined,
endDateInnerTestId = undefined
@@ -49,6 +50,8 @@ let {
suggestedDateIso?: string;
dateLabel?: string;
dateRequired?: boolean;
/** Server-side date error (e.g. blank required field) wired to the field's aria-invalid. */
dateError?: string;
dateTestId?: string;
precisionTestId?: string;
endDateInnerTestId?: string;
@@ -83,6 +86,9 @@ onMount(() => {
});
const dateInvalid = $derived(dateDirty && dateDisplay.length > 0 && dateIso === '');
// Either the client-side malformed-date cue or a server-provided required-field
// error marks the field invalid (REQ-011 per-field aria-invalid).
const dateFieldInvalid = $derived(dateInvalid || dateError.length > 0);
// Inline mirror of the server guard (#678). ISO YYYY-MM-DD strings compare
// lexicographically, so no Date object is needed. Server stays the gate —
@@ -128,17 +134,21 @@ $effect(() => {
maxlength="10"
aria-required={dateRequired ? 'true' : undefined}
class="block min-h-[48px] w-full rounded border border-line px-2 py-3 text-sm shadow-sm
{dateInvalid
{dateFieldInvalid
? 'border-red-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500'
: 'focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring'}"
aria-invalid={dateInvalid ? 'true' : undefined}
aria-describedby={dateInvalid ? `${dateInputName}-error` : undefined}
aria-invalid={dateFieldInvalid ? 'true' : undefined}
aria-describedby={dateFieldInvalid ? `${dateInputName}-error` : undefined}
/>
<input type="hidden" name={dateInputName} value={dateIso} />
{#if dateInvalid}
<p id="{dateInputName}-error" class="mt-1 text-xs text-red-600">
<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
<span aria-hidden="true"></span>{m.form_date_error()}
</p>
{:else if dateError}
<p id="{dateInputName}-error" class="mt-1 text-xs text-danger">
<span aria-hidden="true"></span>{dateError}
</p>
{/if}
</div>
@@ -183,7 +193,7 @@ $effect(() => {
/>
{#if endBeforeStart}
<!-- Non-colour cue (WCAG 1.4.1): warning glyph + text, not red alone. -->
<p id="{dateInputName}-end-error" class="mt-1 text-xs text-red-600">
<p id="{dateInputName}-end-error" class="mt-1 text-xs text-danger">
<span aria-hidden="true"></span>{m.error_invalid_date_range()}
</p>
{/if}

View File

@@ -221,16 +221,12 @@ async function confirmDelete(e: SubmitEvent) {
endDateInputName="eventDateEnd"
precisionInputName="precision"
dateLabel={m.form_label_date()}
dateError={dateError}
dateTestId="event-date"
precisionTestId="event-precision"
endDateInnerTestId="event-end-date"
/>
</div>
{#if dateError}
<p class="mt-2 text-sm text-danger" role="alert">
<span aria-hidden="true"></span>{dateError}
</p>
{/if}
</div>
<!-- Beschreibung -->

View File

@@ -73,6 +73,15 @@ describe('EventForm — required-field error (REQ-010)', () => {
});
});
describe('EventForm — server date error wired per-field (REQ-011)', () => {
it('marks the date field aria-invalid and shows the message on a server date error', async () => {
render(EventForm, { form: { dateError: 'Bitte ein Datum eingeben.' } });
await expect.element(page.getByText('Bitte ein Datum eingeben.')).toBeInTheDocument();
const dateInput = document.querySelector('#eventDate') as HTMLInputElement;
expect(dateInput.getAttribute('aria-invalid')).toBe('true');
});
});
describe('EventForm — submitting state (named AC)', () => {
it('renders an enabled submit button initially', async () => {
render(EventForm, { event: makeEvent() });