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>
99 lines
3.0 KiB
Svelte
99 lines
3.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { m } from '$lib/paraglide/messages.js';
|
|
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
|
|
// sense for a birth or death, and APPROX stays display-only for legacy imports (#773).
|
|
const PERSON_DATE_PRECISIONS: { value: DatePrecision; label: () => string }[] = [
|
|
{ value: 'DAY', label: m.person_precision_day },
|
|
{ value: 'MONTH', label: m.person_precision_month },
|
|
{ value: 'YEAR', label: m.person_precision_year }
|
|
];
|
|
|
|
let {
|
|
name,
|
|
legend,
|
|
precisionLabel,
|
|
initialIso = '',
|
|
initialPrecision = null
|
|
}: {
|
|
name: string;
|
|
legend: string;
|
|
precisionLabel: string;
|
|
initialIso?: string | null;
|
|
initialPrecision?: string | null;
|
|
} = $props();
|
|
|
|
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
|
|
// stomp the user's in-progress edit.
|
|
onMount(() => {
|
|
if (initialIso) {
|
|
iso = initialIso;
|
|
}
|
|
const offered = PERSON_DATE_PRECISIONS.some((p) => p.value === initialPrecision);
|
|
if (offered) {
|
|
precision = initialPrecision as DatePrecision;
|
|
} else if (initialIso) {
|
|
// Legacy APPROX/SEASON/RANGE precision is not editable here — seed YEAR so an
|
|
// untouched save does not silently claim DAY precision for the stored date.
|
|
precision = 'YEAR';
|
|
}
|
|
});
|
|
|
|
// 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';
|
|
</script>
|
|
|
|
<fieldset>
|
|
<legend class="mb-1 block text-xs font-bold tracking-widest text-ink-3 uppercase">
|
|
{legend}
|
|
</legend>
|
|
<div class="flex flex-col gap-2 sm:flex-row">
|
|
<div class="flex-1">
|
|
<DateInput
|
|
bind:value={iso}
|
|
bind:errorMessage={errorMessage}
|
|
bind:inputEl={inputEl}
|
|
name={name}
|
|
id={name}
|
|
placeholder="TT.MM.JJJJ"
|
|
ariaLabel={legend}
|
|
ariaDescribedby={errorMessage ? `${name}-error` : undefined}
|
|
class={controlCls}
|
|
/>
|
|
{#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
|
|
id="{name}Precision"
|
|
name="{name}Precision"
|
|
aria-label="{legend}: {precisionLabel}"
|
|
bind:value={precision}
|
|
class="{controlCls} bg-surface"
|
|
>
|
|
{#each PERSON_DATE_PRECISIONS as p (p.value)}
|
|
<option value={p.value}>{p.label()}</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<p class="mt-1 font-sans text-xs text-ink-3">
|
|
{m.person_precision_hint()} · {m.person_date_placeholder_hint()}
|
|
</p>
|
|
</fieldset>
|