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>
This commit is contained in:
2026-06-18 16:18:54 +02:00
parent 06c739cdc7
commit e8ee70e7fc
6 changed files with 126 additions and 7 deletions
@@ -128,6 +128,7 @@ import CalendarHeader from './CalendarHeader.vue'
import MonthPicker from './MonthPicker.vue'
import {useCalendarPopover} from '../composables/useCalendarPopover'
import {useCalendarView} from '../composables/useCalendarView'
import {buildBoundedMask} from '../composables/maskTemplate'
import {useKbdFocusRing} from '../../shared/useKbdFocusRing'
defineOptions({name: 'MalioCalendarField', inheritAttrs: false})
@@ -190,12 +191,15 @@ const generatedId = useId()
const root = ref<HTMLElement | null>(null)
const draft = ref(props.displayValue)
// Le masque maska est dérivé du gabarit (lettres → slot `#`, séparateurs conservés).
// Le masque maska est dérivé du gabarit : chaque lettre devient un slot chiffre,
// le premier chiffre de chaque champ est borné (jour 0-3, mois 0-1, heure 0-2,
// minute 0-5) pour empêcher la frappe de valeurs absurdes (ex. 99/99/9999).
// eager : pose les séparateurs (/, espace, :) dès qu'un groupe est complet.
const maskaOptions = computed<MaskInputOptions>(() => ({
mask: props.editable ? props.placeholderTemplate.replace(/[A-Za-z]/g, '#') : undefined,
eager: props.editable,
}))
const maskaOptions = computed<MaskInputOptions>(() => {
if (!props.editable) return {eager: false}
const {mask, tokens} = buildBoundedMask(props.placeholderTemplate)
return {mask, tokens, eager: true}
})
const inputReadonly = computed(() => !props.editable || props.readonly || props.disabled)
// Gabarit fantôme : la partie saisie (noire) + le reste du gabarit (gris), affiché