fix(person): block submit while a life-date input is partial

A partial date (e.g. "14.03.") left the hidden ISO input empty, so
saving the edit form silently cleared a stored date. PersonLifeDateField
now delegates to the shared DateInput primitive (inline format error,
calendar validation) and sets a custom validity while the error is
present, so the browser blocks native submission for both person forms.
A full clear stays submittable - that is the intentional clear path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-12 19:31:39 +02:00
committed by marcel
parent 4419c434a1
commit e712477d2b
4 changed files with 107 additions and 18 deletions

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import { isoToGerman, handleGermanDateInput } from '$lib/shared/utils/date';
import DateInput from '$lib/shared/primitives/DateInput.svelte';
import type { DatePrecision } from '$lib/shared/utils/documentDate';
// Only DAY / MONTH / YEAR are offered for life dates: RANGE and SEASON make no
@@ -26,8 +26,9 @@ let {
initialPrecision?: string | null;
} = $props();
let display = $state('');
let iso = $state('');
let errorMessage = $state<string | null>(null);
let inputEl = $state<HTMLInputElement | undefined>();
let precision = $state<DatePrecision>('DAY');
// Seed once at mount (WhoWhenSection pattern): a later load() rerun must not
@@ -35,7 +36,6 @@ let precision = $state<DatePrecision>('DAY');
onMount(() => {
if (initialIso) {
iso = initialIso;
display = isoToGerman(initialIso);
}
const offered = PERSON_DATE_PRECISIONS.some((p) => p.value === initialPrecision);
if (offered) {
@@ -47,11 +47,11 @@ onMount(() => {
}
});
function handleInput(e: Event) {
const result = handleGermanDateInput(e);
display = result.display;
iso = result.iso;
}
// A partial date leaves the hidden ISO empty — submitting then would silently
// clear a stored date. Block native submission until completed or fully emptied.
$effect(() => {
inputEl?.setCustomValidity(errorMessage ?? '');
});
const controlCls =
'block min-h-[44px] w-full rounded border border-line px-3 py-2 font-serif text-ink focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring';
@@ -63,18 +63,20 @@ const controlCls =
</legend>
<div class="flex flex-col gap-2 sm:flex-row">
<div class="flex-1">
<input
<DateInput
bind:value={iso}
bind:errorMessage={errorMessage}
bind:inputEl={inputEl}
name={name}
id={name}
type="text"
inputmode="numeric"
placeholder="TT.MM.JJJJ"
maxlength="10"
aria-label={legend}
value={display}
oninput={handleInput}
ariaLabel={legend}
ariaDescribedby={errorMessage ? `${name}-error` : undefined}
class={controlCls}
/>
<input type="hidden" name={name} value={iso} />
{#if errorMessage}
<p id="{name}-error" class="mt-1 font-sans text-xs text-red-600">{errorMessage}</p>
{/if}
</div>
<div class="flex-1">
<select