@cnamts/synapse 0.0.11-alpha → 0.0.12-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/design-system-v3.js +3878 -3189
- package/dist/design-system-v3.umd.cjs +1 -1
- package/dist/src/components/Amelipro/types/languages.d.ts +6 -0
- package/dist/src/components/Amelipro/types/types.d.ts +65 -0
- package/dist/src/components/CookieBanner/CookieBanner.d.ts +1 -1
- package/dist/src/components/Customs/SyInputSelect/SyInputSelect.d.ts +2 -0
- package/dist/src/components/Customs/SyTextField/SyTextField.d.ts +29 -23
- package/dist/src/components/Customs/SyTextField/types.d.ts +1 -0
- package/dist/src/components/DatePicker/DatePicker.d.ts +70 -59
- package/dist/src/components/DatePicker/DateTextInput.d.ts +67 -56
- package/dist/src/components/ErrorPage/ErrorPage.d.ts +1 -1
- package/dist/src/components/FileList/FileList.d.ts +1 -0
- package/dist/src/components/FileList/UploadItem/UploadItem.d.ts +1 -1
- package/dist/src/components/FilterSideBar/FilterSideBar.d.ts +31 -0
- package/dist/src/components/FilterSideBar/locales.d.ts +7 -0
- package/dist/src/components/FilterSideBar/tests/FilterSideBar.spec.d.ts +1 -0
- package/dist/src/components/LangBtn/LangBtn.d.ts +2 -2
- package/dist/src/components/NirField/NirField.d.ts +940 -0
- package/dist/src/components/NotificationBar/NotificationBar.d.ts +1 -1
- package/dist/src/components/PasswordField/PasswordField.d.ts +40 -8
- package/dist/src/components/PeriodField/PeriodField.d.ts +142 -120
- package/dist/src/components/PhoneField/PhoneField.d.ts +11 -2
- package/dist/src/components/RatingPicker/EmotionPicker/EmotionPicker.d.ts +1 -1
- package/dist/src/components/RatingPicker/NumberPicker/NumberPicker.d.ts +1 -1
- package/dist/src/components/RatingPicker/StarsPicker/StarsPicker.d.ts +1 -1
- package/dist/src/components/UploadWorkflow/config.d.ts +29 -0
- package/dist/src/components/UploadWorkflow/locales.d.ts +7 -0
- package/dist/src/components/UploadWorkflow/tests/UploadWorkflow.spec.d.ts +1 -0
- package/dist/src/components/UploadWorkflow/types.d.ts +19 -0
- package/dist/src/components/UploadWorkflow/useFileList.d.ts +10 -0
- package/dist/src/components/UploadWorkflow/useFileUploadJourney.d.ts +9 -0
- package/dist/src/components/index.d.ts +2 -0
- package/dist/src/composables/rules/useFieldValidation.d.ts +1 -0
- package/dist/src/composables/validation/tests/useValidation.spec.d.ts +1 -0
- package/dist/src/composables/validation/useValidation.d.ts +39 -0
- package/dist/src/designTokens/index.d.ts +3 -1
- package/dist/src/vuetifyConfig.d.ts +81 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/_elevations.scss +89 -0
- package/src/assets/_fonts.scss +6 -0
- package/src/assets/_radius.scss +86 -0
- package/src/assets/_spacers.scss +149 -0
- package/src/assets/settings.scss +7 -3
- package/src/assets/tokens.scss +32 -29
- package/src/components/Amelipro/types/languages.d.ts +6 -0
- package/src/components/Amelipro/types/types.d.ts +65 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +65 -0
- package/src/components/Customs/SyInputSelect/SyInputSelect.vue +13 -3
- package/src/components/Customs/SySelect/SySelect.stories.ts +88 -5
- package/src/components/Customs/SySelect/SySelect.vue +36 -10
- package/src/components/Customs/SySelect/tests/SySelect.spec.ts +135 -2
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +576 -85
- package/src/components/Customs/SyTextField/SyTextField.vue +132 -104
- package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +190 -38
- package/src/components/Customs/SyTextField/types.d.ts +1 -0
- package/src/components/DatePicker/DatePicker.vue +405 -137
- package/src/components/DatePicker/DateTextInput.vue +15 -0
- package/src/components/DatePicker/tests/DatePicker.spec.ts +8 -15
- package/src/components/FileList/FileList.vue +2 -1
- package/src/components/FileList/UploadItem/UploadItem.vue +10 -0
- package/src/components/FileUpload/FileUpload.stories.ts +84 -0
- package/src/components/FileUpload/FileUpload.vue +1 -0
- package/src/components/FileUpload/tests/FileUpload.spec.ts +4 -4
- package/src/components/FilterInline/FilterInline.mdx +180 -34
- package/src/components/FilterInline/FilterInline.stories.ts +363 -6
- package/src/components/FilterSideBar/FilterSideBar.mdx +237 -0
- package/src/components/FilterSideBar/FilterSideBar.stories.ts +798 -0
- package/src/components/FilterSideBar/FilterSideBar.vue +193 -0
- package/src/components/FilterSideBar/locales.ts +8 -0
- package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +305 -0
- package/src/components/FilterSideBar/tests/__snapshots__/FilterSideBar.spec.ts.snap +39 -0
- package/src/components/HeaderBar/Usages.mdx +1 -1
- package/src/components/NirField/NirField.stories.ts +573 -29
- package/src/components/NirField/NirField.vue +397 -359
- package/src/components/NirField/tests/NirField.spec.ts +88 -52
- package/src/components/NirField/tests//342/200/257dataset/342/200/257.md +12 -0
- package/src/components/NotificationBar/Accessibilite.stories.ts +4 -0
- package/src/components/NotificationBar/NotificationBar.stories.ts +18 -13
- package/src/components/PasswordField/PasswordField.mdx +129 -47
- package/src/components/PasswordField/PasswordField.stories.ts +924 -120
- package/src/components/PasswordField/PasswordField.vue +209 -99
- package/src/components/PasswordField/tests/PasswordField.spec.ts +138 -9
- package/src/components/PeriodField/PeriodField.vue +55 -54
- package/src/components/PhoneField/PhoneField.stories.ts +69 -0
- package/src/components/PhoneField/PhoneField.vue +3 -0
- package/src/components/PhoneField/indicatifs.ts +1 -1
- package/src/components/UploadWorkflow/UploadWorkflow.mdx +75 -0
- package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +943 -0
- package/src/components/UploadWorkflow/UploadWorkflow.vue +230 -0
- package/src/components/UploadWorkflow/config.ts +29 -0
- package/src/components/UploadWorkflow/locales.ts +8 -0
- package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +257 -0
- package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +54 -0
- package/src/components/UploadWorkflow/types.ts +21 -0
- package/src/components/UploadWorkflow/useFileList.ts +84 -0
- package/src/components/UploadWorkflow/useFileUploadJourney.ts +18 -0
- package/src/components/index.ts +2 -0
- package/src/composables/rules/useFieldValidation.ts +5 -2
- package/src/composables/validation/tests/useValidation.spec.ts +154 -0
- package/src/composables/validation/useValidation.ts +165 -0
- package/src/designTokens/index.ts +4 -0
- package/src/stories/Demarrer/Accueil.mdx +1 -1
- package/src/stories/DesignTokens/ThemePA.mdx +4 -30
- package/src/stories/GuideDuDev/UtiliserLesRules.mdx +319 -76
- package/src/stories/GuideDuDev/moduleDeNotification.mdx +1 -1
- package/src/vuetifyConfig.ts +61 -0
- package/src/composables/useFilterable/__snapshots__/useFilterable.spec.ts.snap +0 -3
|
@@ -69,22 +69,35 @@
|
|
|
69
69
|
|
|
70
70
|
if (parts.length !== dateParts.length) return null
|
|
71
71
|
|
|
72
|
-
let day =
|
|
72
|
+
let day = 0, month = 0, year = 0
|
|
73
73
|
|
|
74
74
|
// Extraire les valeurs selon leur position dans le format
|
|
75
75
|
parts.forEach((part, index) => {
|
|
76
|
-
const value = dateParts[index]
|
|
77
|
-
if (
|
|
78
|
-
|
|
76
|
+
const value = parseInt(dateParts[index], 10)
|
|
77
|
+
if (isNaN(value)) return
|
|
78
|
+
|
|
79
|
+
if (part.includes('DD') || part.includes('D')) day = value
|
|
80
|
+
else if (part.includes('MM') || part.includes('M')) month = value - 1 // Les mois en JS sont 0-indexés
|
|
79
81
|
else if (part.includes('YYYY')) year = value
|
|
80
|
-
else if (part.includes('YY'))
|
|
82
|
+
else if (part.includes('YY')) {
|
|
83
|
+
// Gestion intelligente des années à 2 chiffres
|
|
84
|
+
// Si l'année est < 50, on considère qu'elle est dans le 21ème siècle
|
|
85
|
+
// Sinon, elle est dans le 20ème siècle
|
|
86
|
+
year = value < 50 ? 2000 + value : 1900 + value
|
|
87
|
+
}
|
|
81
88
|
})
|
|
82
89
|
|
|
83
|
-
// Vérifier que nous avons toutes les parties nécessaires
|
|
84
|
-
if (
|
|
90
|
+
// Vérifier que nous avons toutes les parties nécessaires et qu'elles sont dans des plages valides
|
|
91
|
+
if (day < 1 || day > 31 || month < 0 || month > 11 || year < 1000 || year > 9999) return null
|
|
92
|
+
|
|
93
|
+
// Créer la date à midi (12:00) pour éviter les problèmes de décalage de fuseau horaire
|
|
94
|
+
// Cela garantit que la date reste la même lors de la conversion en UTC
|
|
95
|
+
const date = new Date(year, month, day, 12, 0, 0)
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
// Vérifier que la date est valide (par exemple, 31 février n'existe pas)
|
|
98
|
+
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) return null
|
|
99
|
+
|
|
100
|
+
return date
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
function initializeSelectedDates(
|
|
@@ -92,24 +105,45 @@
|
|
|
92
105
|
): Date | Date[] | null {
|
|
93
106
|
if (!modelValue) return null
|
|
94
107
|
|
|
108
|
+
// Déterminer le format à utiliser pour l'analyse
|
|
109
|
+
const parseFormat = props.dateFormatReturn || props.format
|
|
110
|
+
|
|
95
111
|
if (Array.isArray(modelValue)) {
|
|
96
112
|
if (modelValue.length >= 2) {
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
// Essayer d'abord avec le format de retour, puis avec le format d'affichage
|
|
114
|
+
let dates = [parseDate(modelValue[0], parseFormat), parseDate(modelValue[1], parseFormat)]
|
|
115
|
+
|
|
116
|
+
// Si l'une des dates est invalide avec le format de retour, essayer avec le format d'affichage
|
|
117
|
+
if (dates.some(date => date === null) && props.dateFormatReturn) {
|
|
118
|
+
dates = [parseDate(modelValue[0], props.format), parseDate(modelValue[1], props.format)]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Vérifie si l'une des dates est toujours invalide
|
|
99
122
|
if (dates.some(date => date === null)) {
|
|
100
123
|
return []
|
|
101
124
|
}
|
|
125
|
+
|
|
102
126
|
// Vérifie si la première date est après la seconde
|
|
103
127
|
if (dates[0] && dates[1] && dates[0] > dates[1]) {
|
|
104
128
|
return []
|
|
105
129
|
}
|
|
130
|
+
|
|
106
131
|
// Filtrer les dates nulles et convertir en tableau de Date
|
|
107
132
|
return dates.filter((date): date is Date => date !== null)
|
|
108
133
|
}
|
|
134
|
+
|
|
109
135
|
if (modelValue.length === 1) {
|
|
110
|
-
|
|
136
|
+
// Essayer d'abord avec le format de retour, puis avec le format d'affichage
|
|
137
|
+
let date = parseDate(modelValue[0], parseFormat)
|
|
138
|
+
|
|
139
|
+
// Si la date est invalide avec le format de retour, essayer avec le format d'affichage
|
|
140
|
+
if (date === null && props.dateFormatReturn) {
|
|
141
|
+
date = parseDate(modelValue[0], props.format)
|
|
142
|
+
}
|
|
143
|
+
|
|
111
144
|
return date === null ? [] : [date]
|
|
112
145
|
}
|
|
146
|
+
|
|
113
147
|
return []
|
|
114
148
|
}
|
|
115
149
|
|
|
@@ -118,7 +152,14 @@
|
|
|
118
152
|
return null
|
|
119
153
|
}
|
|
120
154
|
|
|
121
|
-
|
|
155
|
+
// Essayer d'abord avec le format de retour, puis avec le format d'affichage
|
|
156
|
+
let date = parseDate(modelValue, parseFormat)
|
|
157
|
+
|
|
158
|
+
// Si la date est invalide avec le format de retour, essayer avec le format d'affichage
|
|
159
|
+
if (date === null && props.dateFormatReturn) {
|
|
160
|
+
date = parseDate(modelValue, props.format)
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
return date === null ? null : date
|
|
123
164
|
}
|
|
124
165
|
|
|
@@ -135,8 +176,125 @@
|
|
|
135
176
|
|
|
136
177
|
const textInputValue = ref<string>('')
|
|
137
178
|
|
|
179
|
+
// Variable pour éviter les mises à jour récursives
|
|
180
|
+
const isUpdatingFromInternal = ref(false)
|
|
181
|
+
|
|
182
|
+
// Déclaration des règles de validation
|
|
183
|
+
type Rule = { type: string, options: RuleOptions }
|
|
184
|
+
const customRules = ref<Rule[]>(props.customRules || [])
|
|
185
|
+
const customWarningRules = ref<Rule[]>(props.customWarningRules || [])
|
|
186
|
+
|
|
187
|
+
const { generateRules } = useFieldValidation()
|
|
188
|
+
const validationRules = generateRules(customRules.value)
|
|
189
|
+
const warningValidationRules = generateRules(customWarningRules.value)
|
|
190
|
+
|
|
191
|
+
// Déclaration de la fonction validateDates avant son utilisation
|
|
192
|
+
const validateDates = (forceValidation = false) => {
|
|
193
|
+
// Réinitialiser tous les messages
|
|
194
|
+
errorMessages.value = []
|
|
195
|
+
successMessages.value = []
|
|
196
|
+
warningMessages.value = []
|
|
197
|
+
|
|
198
|
+
if (props.noCalendar) {
|
|
199
|
+
// En mode no-calendar, on délègue la validation au DateTextInput
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Vérifier si le champ est requis et vide
|
|
204
|
+
// Si forceValidation est true, on ignore les conditions de validation interactive
|
|
205
|
+
if ((forceValidation || !isUpdatingFromInternal.value) && props.required && (!selectedDates.value || (Array.isArray(selectedDates.value) && selectedDates.value.length === 0))) {
|
|
206
|
+
errorMessages.value.push('La date est requise.')
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!selectedDates.value) return
|
|
211
|
+
|
|
212
|
+
// Préparer les dates à valider
|
|
213
|
+
const datesToValidate = Array.isArray(selectedDates.value)
|
|
214
|
+
? selectedDates.value
|
|
215
|
+
: [selectedDates.value]
|
|
216
|
+
|
|
217
|
+
// Collecter tous les messages
|
|
218
|
+
const allErrors: string[] = []
|
|
219
|
+
const allWarnings: string[] = []
|
|
220
|
+
const allSuccess: string[] = []
|
|
221
|
+
|
|
222
|
+
// Appliquer les règles de validation
|
|
223
|
+
datesToValidate.forEach((date) => {
|
|
224
|
+
// Appliquer d'abord les règles de validation standard
|
|
225
|
+
validationRules.forEach((rule) => {
|
|
226
|
+
const result = rule(date)
|
|
227
|
+
if (result?.error) allErrors.push(result.error)
|
|
228
|
+
else if (result?.success) allSuccess.push(result.success)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// Ensuite appliquer les règles d'avertissement
|
|
232
|
+
warningValidationRules.forEach((rule) => {
|
|
233
|
+
const result = rule(date)
|
|
234
|
+
if (result?.warning) allWarnings.push(result.warning)
|
|
235
|
+
else if (result?.success && !allErrors.length) allSuccess.push(result.success)
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Dédoublonner et assigner les messages
|
|
240
|
+
errorMessages.value = [...new Set(allErrors)]
|
|
241
|
+
warningMessages.value = [...new Set(allWarnings)]
|
|
242
|
+
successMessages.value = [...new Set(allSuccess)]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Fonction centralisée pour mettre à jour le modèle
|
|
246
|
+
const updateModel = (value: DateValue) => {
|
|
247
|
+
// Éviter les mises à jour inutiles
|
|
248
|
+
if (JSON.stringify(value) === JSON.stringify(props.modelValue)) return
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
isUpdatingFromInternal.value = true
|
|
252
|
+
emit('update:modelValue', value)
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
// S'assurer que le flag est toujours réinitialisé
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
isUpdatingFromInternal.value = false
|
|
258
|
+
}, 0)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Watcher pour mettre à jour le modèle lorsque les dates sélectionnées changent
|
|
138
263
|
watch(selectedDates, (newValue) => {
|
|
264
|
+
// Valider les dates
|
|
139
265
|
validateDates()
|
|
266
|
+
|
|
267
|
+
// Mettre à jour le modèle si nécessaire
|
|
268
|
+
if (newValue !== null) {
|
|
269
|
+
updateModel(formattedDate.value)
|
|
270
|
+
|
|
271
|
+
// Mettre à jour textInputValue pour le DateTextInput
|
|
272
|
+
try {
|
|
273
|
+
isUpdatingFromInternal.value = true
|
|
274
|
+
if (Array.isArray(newValue)) {
|
|
275
|
+
// Pour les plages de dates, utiliser la première date
|
|
276
|
+
if (newValue.length > 0) {
|
|
277
|
+
textInputValue.value = formatDate(newValue[0], props.format)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// Pour une date unique
|
|
282
|
+
textInputValue.value = formatDate(newValue, props.format)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
isUpdatingFromInternal.value = false
|
|
288
|
+
}, 0)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
updateModel(null)
|
|
293
|
+
// Réinitialiser textInputValue
|
|
294
|
+
textInputValue.value = ''
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Gérer la visibilité du date picker
|
|
140
298
|
if (props.displayRange) {
|
|
141
299
|
if (Array.isArray(newValue) && newValue.length >= 2) {
|
|
142
300
|
isDatePickerVisible.value = false
|
|
@@ -163,11 +321,27 @@
|
|
|
163
321
|
// Formate une date unique au format spécifié
|
|
164
322
|
const formatDate = (date: Date, format: string): string => {
|
|
165
323
|
if (!date) return ''
|
|
324
|
+
|
|
325
|
+
// Formats de base
|
|
166
326
|
const day = date.getDate().toString().padStart(2, '0')
|
|
167
327
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
168
328
|
const year = date.getFullYear().toString()
|
|
169
329
|
const shortYear = year.slice(-2)
|
|
170
|
-
|
|
330
|
+
|
|
331
|
+
// Formats sans padding
|
|
332
|
+
const dayNoPad = date.getDate().toString()
|
|
333
|
+
const monthNoPad = (date.getMonth() + 1).toString()
|
|
334
|
+
|
|
335
|
+
// Remplacer les tokens dans l'ordre correct (du plus spécifique au moins spécifique)
|
|
336
|
+
let result = format
|
|
337
|
+
.replace(/YYYY/g, year)
|
|
338
|
+
.replace(/YY/g, shortYear)
|
|
339
|
+
.replace(/MM/g, month)
|
|
340
|
+
.replace(/M/g, monthNoPad)
|
|
341
|
+
.replace(/DD/g, day)
|
|
342
|
+
.replace(/D/g, dayNoPad)
|
|
343
|
+
|
|
344
|
+
return result
|
|
171
345
|
}
|
|
172
346
|
|
|
173
347
|
// Date(s) formatée(s) en chaîne de caractères pour la valeur de retour
|
|
@@ -208,6 +382,9 @@
|
|
|
208
382
|
}, { immediate: true })
|
|
209
383
|
|
|
210
384
|
watch(textInputValue, (newValue) => {
|
|
385
|
+
// Éviter les mises à jour récursives
|
|
386
|
+
if (isUpdatingFromInternal.value) return
|
|
387
|
+
|
|
211
388
|
// Parse la date avec le format d'affichage
|
|
212
389
|
const date = parseDate(newValue, props.format)
|
|
213
390
|
if (date) {
|
|
@@ -215,12 +392,50 @@
|
|
|
215
392
|
const formattedValue = props.dateFormatReturn
|
|
216
393
|
? formatDate(date, props.dateFormatReturn)
|
|
217
394
|
: formatDate(date, props.format)
|
|
218
|
-
|
|
395
|
+
updateModel(formattedValue)
|
|
396
|
+
|
|
397
|
+
// Mettre à jour selectedDates sans déclencher de watchers supplémentaires
|
|
398
|
+
try {
|
|
399
|
+
isUpdatingFromInternal.value = true
|
|
400
|
+
selectedDates.value = date
|
|
401
|
+
// Mettre à jour l'affichage formaté
|
|
402
|
+
displayFormattedDate.value = formatDate(date, props.format)
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
isUpdatingFromInternal.value = false
|
|
407
|
+
}, 0)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (newValue) {
|
|
411
|
+
// Même si la date n'est pas valide, conserver la valeur saisie
|
|
412
|
+
// pour éviter que la date ne disparaisse
|
|
413
|
+
updateModel(newValue)
|
|
414
|
+
// Mettre à jour l'affichage formaté pour qu'il corresponde à ce qui est saisi
|
|
415
|
+
try {
|
|
416
|
+
isUpdatingFromInternal.value = true
|
|
417
|
+
displayFormattedDate.value = newValue
|
|
418
|
+
}
|
|
419
|
+
finally {
|
|
420
|
+
setTimeout(() => {
|
|
421
|
+
isUpdatingFromInternal.value = false
|
|
422
|
+
}, 0)
|
|
423
|
+
}
|
|
219
424
|
}
|
|
220
425
|
else {
|
|
221
|
-
|
|
426
|
+
updateModel(null)
|
|
427
|
+
// Réinitialiser l'affichage formaté
|
|
428
|
+
try {
|
|
429
|
+
isUpdatingFromInternal.value = true
|
|
430
|
+
displayFormattedDate.value = ''
|
|
431
|
+
selectedDates.value = null
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
setTimeout(() => {
|
|
435
|
+
isUpdatingFromInternal.value = false
|
|
436
|
+
}, 0)
|
|
437
|
+
}
|
|
222
438
|
}
|
|
223
|
-
updateSelectedDates(newValue)
|
|
224
439
|
})
|
|
225
440
|
|
|
226
441
|
// Date(s) formatée(s) en chaîne de caractères pour l'affichage
|
|
@@ -284,8 +499,6 @@
|
|
|
284
499
|
|
|
285
500
|
// Si on clique dans le conteneur du DatePicker, on ne fait rien
|
|
286
501
|
if (container) return
|
|
287
|
-
|
|
288
|
-
isDatePickerVisible.value = false
|
|
289
502
|
emit('closed')
|
|
290
503
|
// Déclencher la validation à la fermeture
|
|
291
504
|
validateDates()
|
|
@@ -299,19 +512,50 @@
|
|
|
299
512
|
})).replace(/\b\w/g, l => l.toUpperCase())
|
|
300
513
|
})
|
|
301
514
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
515
|
+
// Watcher pour le modelValue pour synchroniser les dates sélectionnées
|
|
516
|
+
watch(() => props.modelValue, (newValue) => {
|
|
517
|
+
// Éviter les mises à jour récursives
|
|
518
|
+
if (isUpdatingFromInternal.value) return
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
isUpdatingFromInternal.value = true
|
|
522
|
+
|
|
523
|
+
if (!newValue || newValue === '') {
|
|
524
|
+
// Réinitialiser les valeurs
|
|
525
|
+
selectedDates.value = null
|
|
526
|
+
textInputValue.value = ''
|
|
527
|
+
displayFormattedDate.value = ''
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
// Initialiser les dates sélectionnées
|
|
531
|
+
selectedDates.value = initializeSelectedDates(newValue)
|
|
532
|
+
|
|
533
|
+
// Mettre à jour l'affichage
|
|
534
|
+
if (selectedDates.value) {
|
|
535
|
+
displayFormattedDate.value = displayFormattedDateComputed.value || ''
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Valider les dates
|
|
305
540
|
validateDates()
|
|
306
541
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
542
|
+
finally {
|
|
543
|
+
setTimeout(() => {
|
|
544
|
+
isUpdatingFromInternal.value = false
|
|
545
|
+
}, 0)
|
|
311
546
|
}
|
|
547
|
+
}, { immediate: true })
|
|
548
|
+
|
|
549
|
+
onMounted(() => {
|
|
550
|
+
document.addEventListener('click', handleClickOutside)
|
|
551
|
+
|
|
552
|
+
// Initialiser l'affichage formaté
|
|
312
553
|
if (displayFormattedDateComputed.value) {
|
|
313
554
|
displayFormattedDate.value = displayFormattedDateComputed.value
|
|
314
555
|
}
|
|
556
|
+
|
|
557
|
+
// Valider les dates au montage
|
|
558
|
+
validateDates()
|
|
315
559
|
})
|
|
316
560
|
|
|
317
561
|
onBeforeUnmount(() => {
|
|
@@ -324,7 +568,8 @@
|
|
|
324
568
|
if (props.noCalendar) {
|
|
325
569
|
return dateTextInputRef.value?.validateOnSubmit()
|
|
326
570
|
}
|
|
327
|
-
|
|
571
|
+
// Forcer la validation pour ignorer les conditions de validation interactive
|
|
572
|
+
validateDates(true)
|
|
328
573
|
return errorMessages.value.length === 0
|
|
329
574
|
}
|
|
330
575
|
|
|
@@ -337,25 +582,69 @@
|
|
|
337
582
|
initializeSelectedDates,
|
|
338
583
|
})
|
|
339
584
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
585
|
+
// Fonction pour améliorer l'accessibilité du DatePicker
|
|
586
|
+
const updateAccessibility = async () => {
|
|
587
|
+
await nextTick()
|
|
588
|
+
|
|
589
|
+
// Utiliser des attributs data pour sélectionner les éléments, ce qui est plus stable que les classes CSS
|
|
590
|
+
const datePickerEl = document.querySelector('.v-date-picker')
|
|
591
|
+
if (!datePickerEl) return
|
|
592
|
+
|
|
593
|
+
// Ajouter un attribut role="application" au conteneur principal
|
|
594
|
+
datePickerEl.setAttribute('role', 'application')
|
|
595
|
+
datePickerEl.setAttribute('aria-label', 'Sélecteur de date')
|
|
596
|
+
|
|
597
|
+
// Sélectionner tous les boutons de navigation
|
|
598
|
+
const navigationButtons = datePickerEl.querySelectorAll('button')
|
|
346
599
|
|
|
347
|
-
|
|
348
|
-
|
|
600
|
+
// Attribuer des labels significatifs basés sur la position ou l'icône
|
|
601
|
+
navigationButtons.forEach((button) => {
|
|
602
|
+
const iconEl = button.querySelector('.v-icon')
|
|
603
|
+
if (!iconEl) return
|
|
604
|
+
|
|
605
|
+
// Utiliser le contenu de l'icône pour déterminer sa fonction
|
|
606
|
+
const iconContent = iconEl.textContent || ''
|
|
607
|
+
const iconClasses = iconEl.className || ''
|
|
608
|
+
|
|
609
|
+
if (iconClasses.includes('mdi-chevron-left') || iconContent.includes('chevron-left')) {
|
|
610
|
+
button.setAttribute('aria-label', 'Mois précédent')
|
|
611
|
+
}
|
|
612
|
+
else if (iconClasses.includes('mdi-chevron-right') || iconContent.includes('chevron-right')) {
|
|
613
|
+
button.setAttribute('aria-label', 'Mois suivant')
|
|
349
614
|
}
|
|
615
|
+
else if (iconClasses.includes('mdi-chevron-down') || iconContent.includes('chevron-down')
|
|
616
|
+
|| iconClasses.includes('mdi-menu-down') || iconContent.includes('menu-down')) {
|
|
617
|
+
button.setAttribute('aria-label', 'Changer de vue')
|
|
618
|
+
}
|
|
619
|
+
})
|
|
350
620
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
621
|
+
// Ajouter des instructions pour les lecteurs d'écran
|
|
622
|
+
let srOnlyEl = datePickerEl.querySelector('.sr-only-instructions')
|
|
623
|
+
if (!srOnlyEl) {
|
|
624
|
+
srOnlyEl = document.createElement('span')
|
|
625
|
+
srOnlyEl.className = 'sr-only-instructions'
|
|
626
|
+
srOnlyEl.setAttribute('aria-live', 'polite')
|
|
627
|
+
// Utiliser HTMLElement pour accéder aux propriétés de style
|
|
628
|
+
const srOnlyHtmlEl = srOnlyEl as HTMLElement
|
|
629
|
+
srOnlyHtmlEl.style.position = 'absolute'
|
|
630
|
+
srOnlyHtmlEl.style.width = '1px'
|
|
631
|
+
srOnlyHtmlEl.style.height = '1px'
|
|
632
|
+
srOnlyHtmlEl.style.padding = '0'
|
|
633
|
+
srOnlyHtmlEl.style.margin = '-1px'
|
|
634
|
+
srOnlyHtmlEl.style.overflow = 'hidden'
|
|
635
|
+
srOnlyHtmlEl.style.clip = 'rect(0, 0, 0, 0)'
|
|
636
|
+
srOnlyHtmlEl.style.whiteSpace = 'nowrap'
|
|
637
|
+
srOnlyHtmlEl.style.border = '0'
|
|
638
|
+
srOnlyEl.textContent = 'Utilisez les flèches pour naviguer entre les dates et Entrée pour sélectionner une date'
|
|
639
|
+
|
|
640
|
+
datePickerEl.prepend(srOnlyEl)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Appliquer les améliorations d'accessibilité quand le DatePicker devient visible
|
|
645
|
+
watch(isDatePickerVisible, async (newValue) => {
|
|
646
|
+
if (newValue) {
|
|
647
|
+
await updateAccessibility()
|
|
359
648
|
}
|
|
360
649
|
})
|
|
361
650
|
|
|
@@ -367,71 +656,7 @@
|
|
|
367
656
|
isDatePickerVisible.value = true
|
|
368
657
|
}
|
|
369
658
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const customRules = ref<Rule[]>(props.customRules || [])
|
|
373
|
-
const customWarningRules = ref<Rule[]>(props.customWarningRules || [])
|
|
374
|
-
|
|
375
|
-
const { generateRules } = useFieldValidation()
|
|
376
|
-
const validationRules = generateRules(customRules.value)
|
|
377
|
-
const warningValidationRules = generateRules(customWarningRules.value)
|
|
378
|
-
|
|
379
|
-
const validateDates = () => {
|
|
380
|
-
errorMessages.value = []
|
|
381
|
-
successMessages.value = []
|
|
382
|
-
warningMessages.value = []
|
|
383
|
-
|
|
384
|
-
if (props.noCalendar) {
|
|
385
|
-
// En mode no-calendar, on délègue la validation au DateTextInput
|
|
386
|
-
return
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const addMessages = (dates, rules) => {
|
|
390
|
-
dates.forEach((date) => {
|
|
391
|
-
rules.forEach((rule) => {
|
|
392
|
-
const result = rule(date)
|
|
393
|
-
if (result?.error) {
|
|
394
|
-
errorMessages.value.push(result.error)
|
|
395
|
-
errorMessages.value = [...new Set(errorMessages.value)]
|
|
396
|
-
}
|
|
397
|
-
else if (result?.warning) {
|
|
398
|
-
warningMessages.value.push(result.warning)
|
|
399
|
-
warningMessages.value = [...new Set(warningMessages.value)]
|
|
400
|
-
}
|
|
401
|
-
else if (result?.success) {
|
|
402
|
-
successMessages.value.push(result.success)
|
|
403
|
-
successMessages.value = [...new Set(successMessages.value)]
|
|
404
|
-
}
|
|
405
|
-
})
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const handleValidation = (dates) => {
|
|
410
|
-
if (Array.isArray(dates) && dates.length > 1) {
|
|
411
|
-
// Pour une plage, on ne vérifie que le premier et le dernier jour
|
|
412
|
-
const [firstDate, ...rest] = dates
|
|
413
|
-
const lastDate = rest[rest.length - 1]
|
|
414
|
-
const datesToValidate = [firstDate, lastDate]
|
|
415
|
-
|
|
416
|
-
// Validation des règles
|
|
417
|
-
addMessages(datesToValidate, validationRules)
|
|
418
|
-
addMessages(datesToValidate, warningValidationRules)
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
// Pour une date unique, on valide normalement
|
|
422
|
-
const datesToValidate = Array.isArray(dates) ? dates : [dates]
|
|
423
|
-
addMessages(datesToValidate, validationRules)
|
|
424
|
-
addMessages(datesToValidate, warningValidationRules)
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (props.required && (!selectedDates.value || (Array.isArray(selectedDates.value) && selectedDates.value.length === 0))) {
|
|
429
|
-
errorMessages.value.push('La date est requise.')
|
|
430
|
-
}
|
|
431
|
-
else if (selectedDates.value) {
|
|
432
|
-
handleValidation(Array.isArray(selectedDates.value) ? selectedDates.value : [selectedDates.value])
|
|
433
|
-
}
|
|
434
|
-
}
|
|
659
|
+
// Fonctions et constantes déjà déclarées plus haut dans le code
|
|
435
660
|
|
|
436
661
|
const getIcon = () => {
|
|
437
662
|
if (props.noCalendar) {
|
|
@@ -451,13 +676,43 @@
|
|
|
451
676
|
|
|
452
677
|
// Watch sur modelValue pour gérer les changements externes
|
|
453
678
|
watch(() => props.modelValue, (newValue) => {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
679
|
+
// Éviter les mises à jour récursives
|
|
680
|
+
if (isUpdatingFromInternal.value) return
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
isUpdatingFromInternal.value = true
|
|
684
|
+
|
|
685
|
+
if (!newValue || newValue === '') {
|
|
686
|
+
selectedDates.value = null
|
|
687
|
+
textInputValue.value = ''
|
|
688
|
+
displayFormattedDate.value = ''
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// Initialiser les dates sélectionnées
|
|
692
|
+
selectedDates.value = initializeSelectedDates(newValue)
|
|
693
|
+
|
|
694
|
+
// Mettre à jour l'affichage et le textInputValue
|
|
695
|
+
if (selectedDates.value) {
|
|
696
|
+
if (Array.isArray(selectedDates.value)) {
|
|
697
|
+
if (selectedDates.value.length > 0) {
|
|
698
|
+
textInputValue.value = formatDate(selectedDates.value[0], props.format)
|
|
699
|
+
displayFormattedDate.value = displayFormattedDateComputed.value || ''
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
textInputValue.value = formatDate(selectedDates.value, props.format)
|
|
704
|
+
displayFormattedDate.value = displayFormattedDateComputed.value || ''
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Valider les dates
|
|
710
|
+
validateDates()
|
|
458
711
|
}
|
|
459
|
-
|
|
460
|
-
|
|
712
|
+
finally {
|
|
713
|
+
setTimeout(() => {
|
|
714
|
+
isUpdatingFromInternal.value = false
|
|
715
|
+
}, 0)
|
|
461
716
|
}
|
|
462
717
|
}, { immediate: true })
|
|
463
718
|
</script>
|
|
@@ -515,29 +770,42 @@
|
|
|
515
770
|
@append-icon-click="handleAppendIconClick"
|
|
516
771
|
/>
|
|
517
772
|
</template>
|
|
518
|
-
<
|
|
519
|
-
<
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
773
|
+
<div>
|
|
774
|
+
<VMenu
|
|
775
|
+
v-if="!props.noCalendar"
|
|
776
|
+
v-model="isDatePickerVisible"
|
|
777
|
+
activator="parent"
|
|
778
|
+
:min-width="0"
|
|
779
|
+
location="bottom"
|
|
780
|
+
:close-on-content-click="false"
|
|
781
|
+
:open-on-click="false"
|
|
782
|
+
transition="fade-transition"
|
|
783
|
+
attach="body"
|
|
784
|
+
:offset="[-20, 5]"
|
|
785
|
+
>
|
|
786
|
+
<transition name="fade">
|
|
787
|
+
<VDatePicker
|
|
788
|
+
v-if="isDatePickerVisible && !props.noCalendar"
|
|
789
|
+
v-model="selectedDates"
|
|
790
|
+
:first-day-of-week="1"
|
|
791
|
+
:multiple="props.displayRange ? 'range' : false"
|
|
792
|
+
:show-adjacent-months="true"
|
|
793
|
+
:show-week="props.showWeekNumber"
|
|
794
|
+
:view-mode="props.isBirthDate ? 'year' : 'month'"
|
|
795
|
+
color="primary"
|
|
796
|
+
>
|
|
797
|
+
<template #title>
|
|
798
|
+
Sélectionnez une date
|
|
799
|
+
</template>
|
|
800
|
+
<template #header>
|
|
801
|
+
<h3 class="mx-auto my-auto ml-5 mb-4">
|
|
802
|
+
{{ todayInString }}
|
|
803
|
+
</h3>
|
|
804
|
+
</template>
|
|
805
|
+
</VDatePicker>
|
|
806
|
+
</transition>
|
|
807
|
+
</VMenu>
|
|
808
|
+
</div>
|
|
541
809
|
</div>
|
|
542
810
|
</template>
|
|
543
811
|
|