9f9723d01c
Ticket MUI-43 : exposer l'état de validité de MalioDate (saisie invalide avalée silencieusement) + portage de la saisie clavier sur MalioDateTime.
## Contenu
**MalioDate**
- Nouvel event `update:valid(boolean)` : `false` sur saisie malformée ou hors min/max (qui n'émet pas `modelValue`), `true` sinon ; émis dès le montage. La validité ne couvre pas `required` (champ vide = valide).
**MalioDateTime**
- Prop `editable` : saisie clavier `JJ/MM/AAAA HH:MM` (masque maska, validation au blur/Entrée, `invalidMessage`) + même `update:valid`.
- Nouveau parseur `parseDisplayToIsoDateTime`.
**Famille Date editable (Date + DateTime)**
- Gabarit fantôme progressif : le format s'affiche en gris et se remplit au fil de la saisie (overlay ghost mirror, texte de l'input transparent).
- Séparateurs (/, espace, :) posés automatiquement (maska `eager`), espace insécable pour éviter le collage `12/12/1999HH:MM`.
- `CalendarField` : prop `placeholderTemplate` (le masque maska en est dérivé).
**Corrections**
- La croix d'effacement réinitialise la saisie clavier même après une date invalide (le v-model restant null, le champ ne se vidait pas).
- Fix d'un test `Date.test.ts` cassé sur develop (`trigger('keydown.enter')` envoie key='enter' ≠ handler `e.key === 'Enter'`).
## Portée
MalioDate seul pour la validité (les cousins DateRange/DateWeek n'ont pas de saisie clavier donc pas le bug). Sémantique `valid` = malformé only.
## Tests
`app/components/malio/date/` : 187/187, ESLint propre. Vérifié visuellement dans le playground (page Date & heure).
## Doc
COMPONENTS.md + CHANGELOG.md à jour.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: #71
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import {isValidIso, parseDisplayToIso} from './dateFormat'
|
|
|
|
const DATETIME_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})$/
|
|
|
|
export function isValidIsoDateTime(s: string): boolean {
|
|
const m = DATETIME_RE.exec(s)
|
|
if (!m) return false
|
|
const [, date, hh, mm, ss] = m
|
|
if (!isValidIso(date)) return false
|
|
const h = Number(hh)
|
|
const min = Number(mm)
|
|
const sec = Number(ss)
|
|
return h >= 0 && h <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59
|
|
}
|
|
|
|
export function formatIsoDateTimeToDisplay(s: string | null): string {
|
|
if (!s || !isValidIsoDateTime(s)) return ''
|
|
const [date, time] = s.split('T')
|
|
const [y, mo, d] = date.split('-')
|
|
const [hh, mm] = time.split(':')
|
|
return `${d}/${mo}/${y} ${hh}:${mm}`
|
|
}
|
|
|
|
export function splitDateTime(s: string | null): {date: string | null; time: string} {
|
|
if (!s || !isValidIsoDateTime(s)) return {date: null, time: ''}
|
|
const [date, time] = s.split('T')
|
|
return {date, time: time.slice(0, 5)}
|
|
}
|
|
|
|
export function parseDisplayToIsoDateTime(display: string): string | null {
|
|
const match = /^(\d{2}\/\d{2}\/\d{4}) (\d{2}):(\d{2})$/.exec(display.trim())
|
|
if (!match) return null
|
|
const [, datePart, hh, mm] = match
|
|
const iso = parseDisplayToIso(datePart)
|
|
if (!iso) return null
|
|
if (Number(hh) > 23 || Number(mm) > 59) return null
|
|
return `${iso}T${hh}:${mm}:00`
|
|
}
|
|
|
|
export function composeDateTime(date: string, time: string): string {
|
|
const t = time || '00:00'
|
|
return `${date}T${t}:00`
|
|
}
|