gestion_table_tournante_libre/src/interface/tables/ChoixRegle.svelte
Mathieu Benoit 59f847a3fe [IMP] screens a11y: table-named controls, focus back after a remedy
Controls repeated under every table (places, goals, rule, remove, the
remedies of their refusals) all read the same to a screen reader, and a
remedy that cleared its own refusal dropped focus on the page body. Each
such control now carries an accessible name with its table, through a
declared prop on Champ and Commande; every refusal is linked to its
control by id, and after a remedy focus returns to that control, nested
refusals and the priority editor included. Visible text is unchanged.
Checked: node 4590, navigateur 954, walkthrough green in verify mode.

--- FR ---

[IMP] écrans, accessibilité : noms par table, focus rendu après remède

Les commandes répétées sous chaque table (places, objectifs, règle,
retrait, remèdes de leurs refus) se lisaient pareil au lecteur d'écran,
et un remède qui retirait son propre refus laissait le focus sur le corps
de la page. Chacune porte désormais un nom accessible qui nomme sa table,
par une prop déclarée de Champ et Commande ; chaque refus est relié à sa
commande par id, et après un remède le focus y revient, refus imbriqués et
éditeur des priorités compris. Le texte visible ne change pas.
Vérifié : node 4590, navigateur 954, parcours vert en vérification.

Assisted-by: Claude Opus 5.5
2026-10-11 19:04:09 -04:00

51 lines
1.7 KiB
Svelte

<!-- © 2026 TechnoLibre (http://www.technolibre.ca)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -->
<script>
// Le sélecteur d'une règle d'objectifs (§ 4.5, D3), que partagent la
// règle par défaut (RegleObjectifs) et celle de chaque objectif
// (ListeTables) : un <select> et son style. Le porteur pose l'étiquette et
// le refus. options est [{valeur, libelle}], le libellé déjà traduit ;
// champ nomme sa désignation data-champ. idRefus est l'identifiant du
// refus affiché, null sans refus : il pose aria-invalid et le lien
// aria-describedby vers le message. nom, déjà traduit, est le nom
// accessible du sélecteur quand son étiquette seule ne le distingue pas
// d'un autre ; null, l'étiquette le nomme. surChoix reçoit l'événement
// change, dont currentTarget est le sélecteur.
let { id, champ, valeur, options, idRefus = null, nom = null, surChoix } = $props();
</script>
<select
{id}
data-champ={champ}
value={valeur}
aria-label={nom ?? undefined}
aria-invalid={idRefus === null ? undefined : 'true'}
aria-describedby={idRefus ?? undefined}
onchange={surChoix}
>
{#each options as option (option.valeur)}
<option value={option.valeur}>{option.libelle}</option>
{/each}
</select>
<style>
select {
padding: var(--espace-1) var(--espace-2);
border: 1px solid var(--couleur-page-filet);
border-radius: var(--rayon-2);
background-color: var(--couleur-carte-fond);
color: var(--couleur-page-texte);
font: inherit;
font-size: var(--taille-texte);
}
select:focus-visible {
outline: 2px solid var(--couleur-focus);
outline-offset: 1px;
}
select[aria-invalid='true'] {
border-color: var(--couleur-refus);
}
</style>