fix(date) : borne la saisie clavier pour empêcher les dates absurdes (99/99/9999) #79

Merged
tristan merged 9 commits from feature/date-bounded-mask into develop 2026-06-19 13:04:12 +00:00

9 Commits

Author SHA1 Message Date
tristan 4f0d4c1b7a style(sidebar) : padding bas des en-têtes de section (pb-3 → pb-2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:56:52 +02:00
tristan ac1d6e7df3 feat(ui) : cohérence du mode disabled sur la famille formulaire
Calqué sur InputText (texte + label grisés, cursor-not-allowed, aucune affordance
interactive). Quand disabled :
- bouton « + » d'ajout masqué (InputPhone, InputEmail)
- œil de révélation masqué (InputPassword)
- chevron masqué (Select, SelectCheckbox, InputAutocomplete)
- croix d'effacement déjà masquée (date, upload, time)
- label en text-m-muted (Select, SelectCheckbox, CalendarField → famille Date, TimePicker, InputNumber aligné)
- tags du SelectCheckbox + valeur du Select grisés ; icône horloge (TimePicker) et icône calendrier (date, même rempli) grisées

Playground : page « Champs disabled » (DIVERS) en miroir de la page readonly,
avec le SelectCheckbox en version tags. Tests par composant ajoutés/adaptés.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:53:13 +02:00
tristan 0558d8c58a feat(tab) : onglets visibles adaptés à la largeur + survol = style actif
- Le nombre d'onglets affichés en mode fenêtré s'adapte automatiquement à la
  largeur réelle (ResizeObserver + ligne de mesure cachée). Les chevrons restent
  fixés aux bords ; le nombre est choisi pour que les onglets tiennent (pas de
  chevauchement ni de rognage). Calcul isolé en fonction pure testable (tabFit.ts,
  basée sur les vraies largeurs). maxVisibleTabs devient un plafond optionnel.
- BREAKING : suppression de la prop maxWidth (la barre prend toute la largeur).
- Survol d'un onglet inactif : même style que l'actif (texte m-primary + barre).
- Playground : bac à sable interactif (nb onglets, plafond, icônes, labels longs,
  cadre redimensionnable) pour tester tous les cas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:06:09 +02:00
tristan 14c719da51 feat(sidebar) : état actif + survol des liens (fond primary/10, texte primary, semi-bold)
- Survol : highlight pleine largeur porté par le <li> (fond m-primary/10 +
  texte m-primary + semi-bold), espacement pt-1 pb-1. Couleur de base text-black
  sur le <li> et le <a> n'impose plus sa couleur (héritage) : sinon, sur les
  bandes pt-1/pb-1 hors du <a>, le fond passait bleu mais le texte restait noir.
- Lien actif : texte m-primary + semi-bold, sans fond (active-class avec
  !important car appliqué hors twMerge).
- Pas de transition sur le hover (texte + fond basculent ensemble, sans délai).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:09:35 +02:00
tristan f2489a7ab0 feat(sidebar) : hover texte m-primary sur les liens de navigation
Les liens passent le texte en m-primary au survol (hover:text-m-primary),
en plus du fond hover:bg-m-surface existant. Inclut l'alignement du test de
largeur sur la valeur actuelle (w-[232px]).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:25:23 +02:00
tristan cddd174876 fix(date) : ouvre le popover au clic sur le picto calendrier
L'icône calendrier (overlay absolu) interceptait le clic sans le traiter et ne
le laissait pas retomber sur le @click de l'input. Ajout d'un handler onFieldClick
sur l'icône (+ cursor-pointer). Couvre Date, DateTime, DateRange, DateWeek via le
CalendarField partagé. La croix d'effacement garde son comportement (efface, n'ouvre pas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:49:08 +02:00
tristan 4a933da19e fix(date) : borne le 2e chiffre par champ (jour 1-31, mois 1-12, heure 0-23, minute 0-59)
Le bornage par tokens ne contraignait que le 1er chiffre (positionnel, sans
mémoire du chiffre précédent) : 33 (jour) ou 19 (mois) restaient tapables.

Remplacé par un preProcess maska qui valide chaque champ progressivement : un
chiffre n'est accepté que s'il existe encore une complétion dans [min, max].
Borne donc le 1er ET le 2e chiffre ; les impossibilités calendaires fines
(31/02, 29/02 non bissextile, hors min/max) restent captées par la validation.

Tests d'intégration : 32/13 (désormais non tapable) remplacé par 31/02 comme
date « champs valides mais inexistante » ; garde sur l'exemple métier 33/19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:35:25 +02:00
tristan 428f30aabe test(date) : garde de non-régression sur le masque borné du DateTime
Le DateTime partage CalendarField, donc buildBoundedMask bornait déjà sa saisie
(heure 0-2, minute 0-5) — couvert par maskTemplate.test.ts. Ce test rend la
garantie explicite côté composant : 99/99/9999 99:99 ne peut pas être tapé et
n'émet aucun datetime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:25:46 +02:00
tristan e8ee70e7fc fix(date) : borne la saisie clavier pour empêcher les dates absurdes (99/99/9999)
Le masque maska n'imposait que la forme (##/##/####), donc 99/99/9999 était
saisissable puis rejeté a posteriori par la validation. Le métier veut que ce
soit impossible à taper.

buildBoundedMask(template) borne le premier chiffre de chaque champ (jour 0-3,
mois 0-1, heure 0-2, minute 0-5) ; il distingue le mois des minutes (même lettre
M) selon la présence d'heures. Les impossibilités fines (31/02, 29/02 non
bissextile, hors min/max) restent captées par la validation, en filet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:18:54 +02:00