diff --git a/frontend/src/lib/person/PersonTypeahead.svelte b/frontend/src/lib/person/PersonTypeahead.svelte index bff2386c..7622fb84 100644 --- a/frontend/src/lib/person/PersonTypeahead.svelte +++ b/frontend/src/lib/person/PersonTypeahead.svelte @@ -21,6 +21,7 @@ interface Props { restrictToCorrespondentsOf?: string; excludePersonId?: string; badge?: 'additive' | 'replace'; + resetKey?: number; onchange?: (value: string) => void; onfocused?: () => void; } @@ -39,17 +40,20 @@ let { restrictToCorrespondentsOf, excludePersonId, badge, + resetKey = 0, onchange, onfocused }: Props = $props(); // searchTerm must be both prop-derived AND locally writable (user typing), so $state + // $effect is the correct pattern here — writable $derived is read-only and won't work. -// eslint-disable-next-line svelte/prefer-writable-derived let searchTerm = $state(initialName); -// Sync display text when the selected person changes externally (e.g. swap, navigation). +// Sync display text when initialName changes OR when resetKey increments (navigation reset). +// resetKey is incremented by the page on every SvelteKit navigation so that a manually-typed +// term that was never committed (no person selected) gets cleared even if initialName stays ''. $effect(() => { + void resetKey; searchTerm = initialName; }); diff --git a/frontend/src/lib/person/PersonTypeahead.svelte.spec.ts b/frontend/src/lib/person/PersonTypeahead.svelte.spec.ts index 060d0fbd..81761078 100644 --- a/frontend/src/lib/person/PersonTypeahead.svelte.spec.ts +++ b/frontend/src/lib/person/PersonTypeahead.svelte.spec.ts @@ -270,6 +270,33 @@ describe('PersonTypeahead – correspondent mode', () => { }); }); +// ─── resetKey ───────────────────────────────────────────────────────────────── + +describe('PersonTypeahead – resetKey', () => { + // Note: rerender() in vitest-browser-svelte causes a full re-mount, not an in-place prop + // update. This is a smoke test — the $effect(resetKey) path that fires during SvelteKit + // navigation (prop update on a live instance) cannot be isolated at this level. + it('clears a manually-typed term when resetKey changes even if initialName stays empty', async () => { + mockFetchWithPersons([]); + const { rerender } = render(PersonTypeahead, { + name: 'senderId', + label: 'Absender', + initialName: '', + resetKey: 0 + }); + const input = page.getByPlaceholder('Namen tippen...'); + + // User types something without selecting a person + await input.fill('Max'); + await waitForDebounce(); + await expect.element(input).toHaveValue('Max'); + + // Navigation resets: initialName stays '', but resetKey increments + await rerender({ name: 'senderId', label: 'Absender', initialName: '', resetKey: 1 }); + await expect.element(input).toHaveValue(''); + }); +}); + // ─── Click outside ──────────────────────────────────────────────────────────── describe('PersonTypeahead – click outside', () => { diff --git a/frontend/src/lib/shared/primitives/DateInput.svelte b/frontend/src/lib/shared/primitives/DateInput.svelte index 08fed63a..ab234c81 100644 --- a/frontend/src/lib/shared/primitives/DateInput.svelte +++ b/frontend/src/lib/shared/primitives/DateInput.svelte @@ -1,4 +1,5 @@