From 842ab28f59cb17197d6857c51bdbb49ba2ec7dca Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 10:17:47 +0200 Subject: [PATCH] fix(persons): keyboard navigation now updates PersonTypeSelector reactive state radioGroupNav now accepts an onChange callback; PersonTypeSelector passes select() as the callback so ArrowLeft/Right navigation updates the hidden input value. aria-live region starts empty and announces only on user interaction (fixes initial page-load announcement). Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/actions/radioGroupNav.ts | 11 ++++++++++- .../src/lib/components/PersonTypeSelector.svelte | 14 ++++++++++---- .../components/PersonTypeSelector.svelte.spec.ts | 12 ++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/actions/radioGroupNav.ts b/frontend/src/lib/actions/radioGroupNav.ts index 65164327..65721e3a 100644 --- a/frontend/src/lib/actions/radioGroupNav.ts +++ b/frontend/src/lib/actions/radioGroupNav.ts @@ -1,4 +1,9 @@ -export function radioGroupNav(node: HTMLElement): { destroy: () => void } { +export function radioGroupNav( + node: HTMLElement, + onChange?: (value: string) => void +): { destroy: () => void; update: (onChange?: (value: string) => void) => void } { + let onChangeFn = onChange; + function getRadios(): HTMLElement[] { return Array.from(node.querySelectorAll('[role="radio"]')); } @@ -16,11 +21,15 @@ export function radioGroupNav(node: HTMLElement): { destroy: () => void } { radios[current].setAttribute('aria-checked', 'false'); radios[next].setAttribute('aria-checked', 'true'); radios[next].focus(); + onChangeFn?.(radios[next].getAttribute('value') ?? ''); } node.addEventListener('keydown', handleKeydown); return { + update(newOnChange) { + onChangeFn = newOnChange; + }, destroy() { node.removeEventListener('keydown', handleKeydown); } diff --git a/frontend/src/lib/components/PersonTypeSelector.svelte b/frontend/src/lib/components/PersonTypeSelector.svelte index fe879f4e..253c5673 100644 --- a/frontend/src/lib/components/PersonTypeSelector.svelte +++ b/frontend/src/lib/components/PersonTypeSelector.svelte @@ -16,6 +16,8 @@ let selected = $state( untrack(() => (TYPES.includes(value as PersonType) ? (value as PersonType) : 'PERSON')) ); +let announcement = $state(''); + const labels: Record string> = { PERSON: m.person_type_PERSON, INSTITUTION: m.person_type_INSTITUTION, @@ -25,15 +27,21 @@ const labels: Record string> = { function select(type: PersonType) { selected = type; + announcement = m.a11y_type_changed({ type: labels[type]() }); onchange?.(type); } -
+
{ if (TYPES.includes(v as PersonType)) select(v as PersonType); }} +> {#each TYPES as type (type)}