fix(date) : borne la saisie clavier pour empêcher les dates absurdes (99/99/9999) (#79)
## Problème Sur la famille Date editable, le masque maska n'imposait que la *forme* (`##/##/####`). Une valeur structurellement absurde comme `99/99/9999` était donc **saisissable**, puis rejetée *a posteriori* par la validation. Le métier veut que ce soit **impossible à taper**. ## Solution (masque borné + validation en filet) - `composables/maskTemplate.ts` — `buildBoundedMask(template)` : borne le **premier chiffre de chaque champ** (jour `0-3`, mois `0-1`, heure `0-2`, minute `0-5`). Distingue le mois des minutes (même lettre `M`) selon la présence d'heures dans le gabarit, pour ne pas brider la saisie des minutes du DateTime. - `internal/CalendarField.vue` — branche le builder dans `maskaOptions` (remplace le `replace(/[A-Za-z]/g, '#')`). Les impossibilités plus fines (`31/02`, 29/02 non bissextile, hors `min`/`max`) restent captées par la **validation** (`invalidMessage` + `update:valid=false`). ## Tests - `maskTemplate.test.ts` (5) — bornes par champ, structure du masque, non-confusion mois/minutes. - `Date.test.ts` — test `invalidMessage` adapté (`32/13/2026`, typable→invalide) + garde de non-régression : `99/99/9999` ne s'inscrit jamais et n'émet aucune date. - Suite complète : **1004/1004 verte** (DateTime 36 incluse → saisie d'heure intacte). Doc : `COMPONENTS.md` (MalioDate) + `CHANGELOG.md` (Fixed) à jour. Reviewed-on: #79 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #79.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// Calcule combien d'onglets afficher pour qu'ils tiennent dans la largeur dispo,
|
||||
// en gardant la structure « flèches fixes aux bords » : le nombre est choisi pour
|
||||
// que les onglets visibles tiennent → pas de débordement sur les flèches, pas de
|
||||
// rognage, barre d'onglet actif intacte.
|
||||
//
|
||||
// On additionne les VRAIES largeurs d'onglets (pas la pire), donc le résultat
|
||||
// n'est pas sur-conservateur (évite de tomber à 1 onglet inutilement).
|
||||
//
|
||||
// Fonction pure → testable sans DOM. Sans layout (SSR / jsdom : largeurs à 0),
|
||||
// on retombe sur le plafond `maxVisibleTabs` (ou tous les onglets).
|
||||
export interface TabFitInput {
|
||||
count: number // nombre total d'onglets
|
||||
containerWidth: number // largeur dispo mesurée (0 si inconnue)
|
||||
tabWidths: number[] // largeur mesurée de chaque onglet (vide si inconnu)
|
||||
gap: number // espace entre onglets (px)
|
||||
chevronReserve: number // place des chevrons + marges quand fenêtré (px)
|
||||
maxVisibleTabs?: number // plafond optionnel imposé par le consommateur
|
||||
}
|
||||
|
||||
export function computeVisibleCount(input: TabFitInput): number {
|
||||
const {count, containerWidth, tabWidths, gap, chevronReserve, maxVisibleTabs} = input
|
||||
|
||||
// Pas d'info de layout : on respecte le plafond explicite, sinon tout afficher.
|
||||
if (containerWidth <= 0 || tabWidths.length === 0) {
|
||||
return maxVisibleTabs != null ? Math.min(maxVisibleTabs, count) : count
|
||||
}
|
||||
|
||||
const fullAvail = containerWidth
|
||||
const total = tabWidths.reduce((s, w) => s + w, 0) + gap * Math.max(0, count - 1)
|
||||
|
||||
let fit: number
|
||||
if (total <= fullAvail) {
|
||||
fit = count // tout tient, pas de chevrons
|
||||
}
|
||||
else {
|
||||
const avail = fullAvail - chevronReserve
|
||||
let used = 0
|
||||
let n = 0
|
||||
for (const w of tabWidths) {
|
||||
const add = w + (n > 0 ? gap : 0)
|
||||
if (used + add > avail) break
|
||||
used += add
|
||||
n++
|
||||
}
|
||||
fit = Math.max(1, n)
|
||||
}
|
||||
|
||||
return maxVisibleTabs != null ? Math.min(maxVisibleTabs, fit) : fit
|
||||
}
|
||||
Reference in New Issue
Block a user