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:
@@ -1,5 +1,65 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-1 items-start gap-6">
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-1 text-xl font-bold">Bac à sable (tous les cas)</h2>
|
||||
<p class="mb-4 text-sm text-m-muted">
|
||||
Règle les paramètres puis <strong>redimensionne le cadre en pointillés</strong>
|
||||
(poignée en bas à droite) pour voir le nombre d'onglets s'adapter et les flèches apparaître.
|
||||
</p>
|
||||
|
||||
<div class="mb-4 flex flex-wrap items-end gap-4 text-sm">
|
||||
<label class="flex flex-col gap-1">Nb onglets : {{ sbCount }}
|
||||
<input
|
||||
v-model.number="sbCount"
|
||||
type="range"
|
||||
min="1"
|
||||
max="15"
|
||||
class="w-40"
|
||||
>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">maxVisibleTabs (0 = auto)
|
||||
<input
|
||||
v-model.number="sbMax"
|
||||
type="number"
|
||||
min="0"
|
||||
max="15"
|
||||
class="w-20 rounded border px-2 py-1"
|
||||
>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="sbIcons"
|
||||
type="checkbox"
|
||||
> Icônes
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="sbLong"
|
||||
type="checkbox"
|
||||
> Labels longs
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="resize-x overflow-hidden rounded border-2 border-dashed border-m-muted p-3"
|
||||
style="width: 100%; min-width: 280px;"
|
||||
>
|
||||
<MalioTabList
|
||||
v-model="sbValue"
|
||||
:tabs="sbTabs"
|
||||
:max-visible-tabs="sbMaxProp"
|
||||
>
|
||||
<template
|
||||
v-for="t in sbTabs"
|
||||
#[t.key]
|
||||
:key="t.key"
|
||||
>
|
||||
<p class="p-4">Contenu : {{ t.label }}</p>
|
||||
</template>
|
||||
</MalioTabList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border p-4">
|
||||
<h2 class="mb-4 text-xl font-bold">Simple</h2>
|
||||
<MalioTabList v-model="simpleValue" :tabs="tabs">
|
||||
@@ -70,7 +130,35 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
// --- Bac à sable interactif ---
|
||||
const sbCount = ref(9)
|
||||
const sbMax = ref(0)
|
||||
const sbIcons = ref(true)
|
||||
const sbLong = ref(false)
|
||||
|
||||
const SB_LABELS = [
|
||||
'Informations', 'Adresses', 'Contacts', 'Comptabilité', 'Documents',
|
||||
'Historique', 'Paramètres', 'Qualité', 'Facturation', 'Accueil',
|
||||
'Notifications', 'Statistiques', 'Équipe', 'Sécurité', 'Étiquettes',
|
||||
]
|
||||
const SB_ICONS = [
|
||||
'mdi:information-outline', 'mdi:map-marker-outline', 'mdi:account-box-outline', 'mdi:web',
|
||||
'mdi:file-document-outline', 'mdi:history', 'mdi:cog-outline', 'mdi:check-decagram-outline',
|
||||
'mdi:receipt-text-outline', 'mdi:home', 'mdi:bell-outline', 'mdi:chart-bar',
|
||||
'mdi:account-group-outline', 'mdi:lock-outline', 'mdi:tag-outline',
|
||||
]
|
||||
|
||||
const sbTabs = computed(() =>
|
||||
Array.from({ length: sbCount.value }, (_, i) => ({
|
||||
key: `sb${i}`,
|
||||
label: sbLong.value ? `${SB_LABELS[i % SB_LABELS.length]} détaillé` : SB_LABELS[i % SB_LABELS.length],
|
||||
icon: sbIcons.value ? SB_ICONS[i % SB_ICONS.length] : undefined,
|
||||
})),
|
||||
)
|
||||
const sbMaxProp = computed(() => (sbMax.value > 0 ? sbMax.value : undefined))
|
||||
const sbValue = ref('sb0')
|
||||
|
||||
const tabs = [
|
||||
{ key: 'qualimat', label: 'Qualimat', icon: 'mdi:certificate-outline' },
|
||||
|
||||
Reference in New Issue
Block a user