e8ee70e7fc
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>
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import type {MaskTokens} from 'maska'
|
|
|
|
// Tokens maska bornant le PREMIER chiffre de chaque champ d'une date/heure.
|
|
// Objectif : rendre impossible la frappe de valeurs absurdes (ex. 99/99/9999)
|
|
// dès la saisie. Les impossibilités plus fines (31/02, 29/02 non bissextile,
|
|
// dépassement min/max) restent du ressort de la validation, en filet de sécurité.
|
|
const BOUND_TOKENS: MaskTokens = {
|
|
d: {pattern: /[0-3]/}, // jour : dizaine 0-3
|
|
m: {pattern: /[0-1]/}, // mois : dizaine 0-1
|
|
h: {pattern: /[0-2]/}, // heure : dizaine 0-2
|
|
n: {pattern: /[0-5]/}, // minute : dizaine 0-5
|
|
}
|
|
|
|
/**
|
|
* Construit un masque maska borné à partir d'un gabarit d'affichage
|
|
* (ex. `JJ/MM/AAAA`, `JJ/MM/AAAA HH:MM`).
|
|
*
|
|
* Chaque lettre devient un slot chiffre : le premier chiffre d'un champ est borné
|
|
* (token dédié), les suivants restent libres (`#`). Les séparateurs sont conservés.
|
|
*
|
|
* Le `M` désigne le mois avant les heures, et les minutes après — d'où le suivi de
|
|
* `seenHour` pour ne pas borner les minutes comme un mois (0-1 au lieu de 0-5).
|
|
*/
|
|
export function buildBoundedMask(template: string): {mask: string, tokens: MaskTokens} {
|
|
let mask = ''
|
|
let prev = ''
|
|
let seenHour = false
|
|
|
|
for (const ch of template) {
|
|
if (!/[A-Za-z]/.test(ch)) {
|
|
mask += ch // séparateur (/, espace, :)
|
|
prev = ch
|
|
continue
|
|
}
|
|
|
|
const letter = ch.toUpperCase()
|
|
if (letter === 'H') seenHour = true
|
|
|
|
const isFirstOfField = ch !== prev
|
|
if (!isFirstOfField) {
|
|
mask += '#' // unités : chiffre libre
|
|
} else if (letter === 'J') {
|
|
mask += 'd'
|
|
} else if (letter === 'M') {
|
|
mask += seenHour ? 'n' : 'm'
|
|
} else if (letter === 'H') {
|
|
mask += 'h'
|
|
} else {
|
|
mask += '#' // année (ou tout autre champ) : libre
|
|
}
|
|
|
|
prev = ch
|
|
}
|
|
|
|
return {mask, tokens: BOUND_TOKENS}
|
|
}
|