Files
malio-layer-ui/docs/superpowers/specs/2026-06-09-maliodate-saisie-manuelle-design.md
T
tristan 9f772a84ed
Release / release (push) Successful in 1m9s
fix: accessibilité des composants (#70)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

---------

Co-authored-by: admin malio <malio@yuno.malio.fr>
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #70
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-09 15:40:44 +00:00

6.3 KiB

MalioDate — saisie manuelle au clavier

Date : 2026-06-09 Statut : Validé, prêt pour plan d'implémentation Périmètre : MalioDate uniquement (la famille DateTime/DateRange/DateWeek n'est pas concernée pour l'instant).

Objectif

Permettre à l'utilisateur de saisir une date au clavier (JJ/MM/AAAA) dans MalioDate, en plus de la sélection via le calendrier. Aujourd'hui l'<input> de CalendarField est codé en dur en readonly : seule la sélection au calendrier est possible.

Décisions d'UX (validées)

Sujet Décision
Ouverture du popover Le focus (ou le clic) ouvre le calendrier, tout en laissant taper en même temps.
Masque / validation Masque maska ##/##/#### pendant la frappe ; validation au blur (pas à chaque touche).
Activation Opt-in via une prop editable (défaut false). Aucune régression pour les consommateurs existants.
Saisie invalide au blur On garde le texte tapé et on affiche un état d'erreur visuel (bordure rouge + message).
Message d'erreur par défaut « Date invalide » (couvre aussi le hors-bornes min/max), surchargeable via prop.
Touche Entrée Déclenche le commit (parse immédiat) + ferme le popover.

Approche retenue

Mode editable dans CalendarField, parsing dans MalioDate.

CalendarField reste agnostique au format : il expose un mode éditable (input non readonly, masque, buffer local) et émet du texte brut. MalioDate conserve toute la logique propre à la date (parse, validation min/max, état d'erreur). Cela évite de coupler CalendarField à un format date spécifique et garde le terrain prêt pour une éventuelle extension future à la famille Date.

Approches écartées :

  • CalendarField générique avec fonction parse injectée : trop générique pour le périmètre actuel (YAGNI).
  • MalioDate gère son propre <input> : duplication du rendu / label flottant / styles de CalendarField.

Conception détaillée

1. MalioDate — props ajoutées

  • editable?: boolean — défaut false. Active la saisie clavier.
  • invalidMessage?: string — défaut 'Date invalide'. Message affiché en cas de saisie invalide/hors-bornes.

Quand editable === false, le comportement est strictement identique à aujourd'hui (lecture seule, sélection calendrier uniquement).

2. CalendarField — mode éditable

Ajout d'une prop editable?: boolean (défaut false). Quand true :

  • L'<input> perd l'attribut readonly et reçoit v-maska="'##/##/####'".
  • Un buffer local draft (ref) alimente l'input : :value="editable ? draft : displayValue".
  • draft est resynchronisé sur displayValue via un watch → couvre la sélection au calendrier, le clear, et tout changement externe de modelValue. Cette resynchro efface aussi l'état d'erreur côté MalioDate (via le nouveau displayValue émis).
  • À la frappe (@input) : met à jour draft et émet input(text). Pas de validation à ce stade.
  • Au blur (@blur) : émet commit(text).
  • À la touche Entrée (@keydown.enter) : émet commit(text) + ferme le popover.
  • @focus ouvre le popover, tout en laissant taper (input non readonly).

Quand editable === false, aucun de ces comportements ne s'applique : le chemin de code actuel reste inchangé.

disabled et readonly priment toujours sur editable (champ non éditable).

3. MalioDate — parsing, validation, état d'erreur

Une ref locale internalError est fusionnée avec la prop error du consommateur et transmise à CalendarField : :error="error || internalError" (l'erreur métier du consommateur reste prioritaire).

Sur réception de commit(text) :

  • Texte videemit('update:modelValue', null) ; internalError = ''.
  • Valide (parseDisplayToIso(text) non null et isDateInRange(iso, min, max)) → emit('update:modelValue', iso) ; internalError = ''.
  • Invalide ou hors-bornes → on n'émet pas de nouveau modelValue ; internalError = props.invalidMessage. Le texte tapé reste affiché.

L'état d'erreur s'efface dès qu'une saisie valide ou une sélection calendrier ultérieure produit un nouveau displayValue.

Flux de données

Frappe clavier
  └─ CalendarField: maj draft + émet input(text)   (pas de validation)
Blur / Entrée
  └─ CalendarField: émet commit(text)
       └─ MalioDate: parseDisplayToIso + isDateInRange
            ├─ valide  → emit update:modelValue(iso) ; internalError=''
            ├─ vide    → emit update:modelValue(null) ; internalError=''
            └─ invalide→ internalError = invalidMessage ; (texte conservé)

Sélection calendrier
  └─ emit update:modelValue(iso)
       └─ displayValue change → CalendarField resync draft → erreur effacée

Réutilisation de l'existant

Les helpers nécessaires existent déjà dans app/components/malio/date/composables/dateFormat.ts :

  • parseDisplayToIso(display)string | null
  • isValidIso(iso)boolean
  • isDateInRange(iso, min?, max?)boolean
  • formatIsoToDisplay(iso)string

maska est déjà une dépendance du projet (utilisée par InputText/InputPhone via v-maska + vMaska de maska/vue).

Tests (Date.test.ts)

  • Frappe valide + blur → émet l'ISO attendu.
  • Saisie invalide (32/13/2026) au blur → texte conservé, message « Date invalide », aria-invalid.
  • Date valide hors min/max au blur → état d'erreur.
  • Saisie vide au blur → émet null.
  • Sélection au calendrier après une saisie invalide → erreur effacée, valeur mise à jour.
  • Touche Entrée → commit + fermeture popover.
  • editable=false (défaut) → input reste readonly, aucun nouveau comportement (non-régression).
  • invalidMessage personnalisé → message affiché respecté.

Livrables documentaires

  • Mise à jour de COMPONENTS.md (props editable, invalidMessage).
  • Entrée dans CHANGELOG.md.
  • Mise à jour de la story Histoire + page playground de MalioDate pour exposer la prop editable.

Hors périmètre

  • Extension de la saisie manuelle à DateTime, DateRange, DateWeek.
  • Saisie partielle « intelligente » (auto-complétion d'année, etc.).
  • Validation à la frappe (on reste sur validation au blur).