@cnamts/synapse 0.0.14-alpha → 0.0.16-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/components/CookiesSelection/CookiesSelection.d.ts +26 -26
- package/dist/components/Customs/SyInputSelect/SyInputSelect.d.ts +2 -2
- package/dist/components/Customs/SySelect/SySelect.d.ts +24 -12
- package/dist/components/Customs/SySelect/locales.d.ts +3 -0
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +1393 -3
- package/dist/components/DatePicker/DatePicker.d.ts +3532 -22
- package/dist/components/DatePicker/DateTextInput.d.ts +1408 -11
- package/dist/components/DialogBox/config.d.ts +1 -1
- package/dist/components/DownloadBtn/DownloadBtn.d.ts +2 -0
- package/dist/components/LangBtn/LangBtn.d.ts +467 -1
- package/dist/components/LangBtn/config.d.ts +1 -3
- package/dist/components/NirField/NirField.d.ts +2805 -15
- package/dist/components/PasswordField/PasswordField.d.ts +2 -2
- package/dist/components/PeriodField/PeriodField.d.ts +7345 -325
- package/dist/components/PhoneField/PhoneField.d.ts +3 -3
- package/dist/components/SelectBtnField/SelectBtnField.d.ts +1 -1
- package/dist/components/SkipLink/SkipLink.d.ts +3 -2
- package/dist/components/SyAlert/SyAlert.d.ts +72 -1
- package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +26 -26
- package/dist/components/UserMenuBtn/UserMenuBtn.d.ts +2 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/composables/date/useDateFormat.d.ts +2 -2
- package/dist/composables/date/useDateFormatDayjs.d.ts +23 -0
- package/dist/composables/date/useDateInitializationDayjs.d.ts +18 -0
- package/dist/design-system-v3.js +4314 -3987
- package/dist/design-system-v3.umd.cjs +1 -1
- package/dist/style.css +1 -1
- package/dist/vuetifyConfig.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/BackBtn/Accessibilite.stories.ts +4 -0
- package/src/components/BackBtn/BackBtn.vue +2 -1
- package/src/components/BackToTopBtn/Accessibilite.stories.ts +4 -0
- package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +78 -21
- package/src/components/BackToTopBtn/BackToTopBtn.vue +15 -0
- package/src/components/BackToTopBtn/config.ts +2 -2
- package/src/components/BackToTopBtn/tests/__snapshots__/BackToTopBtn.spec.ts.snap +4 -4
- package/src/components/CopyBtn/Accessibilite.stories.ts +4 -0
- package/src/components/Customs/SyBtnSelect/SyBtnSelect.stories.ts +2 -2
- package/src/components/Customs/SyBtnSelect/SyBtnSelect.vue +0 -1
- package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +3 -3
- package/src/components/Customs/SyInputSelect/SyInputSelect.vue +4 -4
- package/src/components/Customs/SySelect/SySelect.stories.ts +4 -0
- package/src/components/Customs/SySelect/SySelect.vue +75 -10
- package/src/components/Customs/SySelect/locales.ts +3 -0
- package/src/components/Customs/SySelect/tests/SySelect.spec.ts +24 -2
- package/src/components/Customs/SyTextField/Accessibilite.stories.ts +7 -0
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +14 -1
- package/src/components/Customs/SyTextField/SyTextField.vue +85 -20
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +795 -0
- package/src/components/DatePicker/DatePicker.stories.ts +432 -1
- package/src/components/DatePicker/DatePicker.vue +143 -76
- package/src/components/DatePicker/DatePickerValidation.mdx +338 -0
- package/src/components/DatePicker/DatePickerValidation.stories.ts +30 -0
- package/src/components/DatePicker/DateTextInput.vue +87 -135
- package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +282 -0
- package/src/components/DatePicker/docExamples/DatePickerValidationExamples.vue +535 -0
- package/src/components/DatePicker/tests/DatePicker.spec.ts +33 -32
- package/src/components/DatePicker/tests/DateTextInput.spec.ts +83 -35
- package/src/components/DialogBox/DialogBox.stories.ts +5 -2
- package/src/components/DialogBox/DialogBox.vue +1 -1
- package/src/components/DialogBox/config.ts +1 -1
- package/src/components/DownloadBtn/Accessibilite.stories.ts +4 -0
- package/src/components/DownloadBtn/DownloadBtn.stories.ts +17 -8
- package/src/components/DownloadBtn/DownloadBtn.vue +13 -6
- package/src/components/DownloadBtn/tests/__snapshots__/DownloadBtn.spec.ts.snap +0 -2
- package/src/components/FranceConnectBtn/Accessibilite.stories.ts +4 -0
- package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +3 -0
- package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +3 -0
- package/src/components/HeaderBar/HeaderBurgerMenu/menu.scss +19 -0
- package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +12 -2
- package/src/components/LangBtn/Accessibilite.stories.ts +4 -0
- package/src/components/LangBtn/LangBtn.stories.ts +1 -4
- package/src/components/LangBtn/LangBtn.vue +68 -9
- package/src/components/LangBtn/config.ts +0 -1
- package/src/components/LangBtn/tests/LangBtn.spec.ts +30 -2
- package/src/components/PageContainer/Accessibilite.stories.ts +36 -23
- package/src/components/PaginatedTable/PaginatedTable.stories.ts +144 -18
- package/src/components/PasswordField/PasswordField.stories.ts +6 -6
- package/src/components/PasswordField/PasswordField.vue +3 -3
- package/src/components/PeriodField/PeriodField.vue +4 -4
- package/src/components/PhoneField/PhoneField.stories.ts +216 -24
- package/src/components/PhoneField/PhoneField.vue +32 -2
- package/src/components/PhoneField/tests/PhoneField.spec.ts +161 -14
- package/src/components/RatingPicker/NumberPicker/NumberPicker.vue +2 -1
- package/src/components/RatingPicker/RatingPicker.stories.ts +1 -1
- package/src/components/SkipLink/Accessibilite.stories.ts +8 -0
- package/src/components/SkipLink/SkipLink.vue +11 -9
- package/src/components/SkipLink/tests/__snapshots__/skipLink.spec.ts.snap +7 -4
- package/src/components/SkipLink/tests/skipLink.spec.ts +120 -6
- package/src/components/SyAlert/Accessibilite.stories.ts +4 -0
- package/src/components/SyAlert/SyAlert.mdx +3 -7
- package/src/components/SyAlert/SyAlert.stories.ts +19 -12
- package/src/components/SyAlert/SyAlert.vue +88 -51
- package/src/components/SyAlert/tests/SyAlert.spec.ts +20 -2
- package/src/components/SyAlert/tests/__snapshots__/SyAlert.spec.ts.snap +83 -75
- package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +56 -0
- package/src/components/UserMenuBtn/UserMenuBtn.vue +4 -2
- package/src/components/UserMenuBtn/tests/UserMenuBtn.spec.ts +41 -0
- package/src/components/index.ts +2 -0
- package/src/composables/date/useDateFormat.ts +17 -1
- package/src/composables/date/useDateFormatDayjs.ts +84 -0
- package/src/composables/date/useDateInitializationDayjs.ts +133 -0
- package/src/composables/rules/useFieldValidation.ts +26 -3
- package/src/stories/Accessibilite/Avancement/Avancement.mdx +12 -0
- package/src/stories/Accessibilite/Avancement/Avancement.stories.ts +134 -0
- package/src/stories/Accessibilite/KitDePreAudit/Echantillonnage.mdx +1 -1
- package/src/stories/GuideDuDev/LesBreackingChanges.mdx +31 -2
- package/src/components/LangBtn/tests/Config.spec.ts +0 -24
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { ref, computed, watch, onMounted } from 'vue'
|
|
3
3
|
import { nextTick } from 'vue'
|
|
4
4
|
import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
|
|
5
|
-
import { useDateFormat } from '@/composables/date/useDateFormat'
|
|
6
5
|
import { useValidation, type ValidationRule } from '@/composables/validation/useValidation'
|
|
6
|
+
import dayjs from 'dayjs'
|
|
7
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// Initialiser les plugins dayjs
|
|
10
|
+
dayjs.extend(customParseFormat)
|
|
9
11
|
|
|
10
12
|
type DateValue = string | null
|
|
11
13
|
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
label?: string
|
|
18
20
|
required?: boolean
|
|
19
21
|
disabled?: boolean
|
|
20
|
-
|
|
22
|
+
readonly?: boolean
|
|
21
23
|
isOutlined?: boolean
|
|
22
24
|
displayIcon?: boolean
|
|
23
25
|
displayAppendIcon?: boolean
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
displayPrependIcon?: boolean
|
|
28
30
|
disableErrorHandling?: boolean
|
|
29
31
|
showSuccessMessages?: boolean
|
|
32
|
+
bgColor?: string
|
|
30
33
|
}>(), {
|
|
31
34
|
modelValue: null,
|
|
32
35
|
placeholder: 'Sélectionner une date',
|
|
@@ -35,7 +38,7 @@
|
|
|
35
38
|
label: undefined,
|
|
36
39
|
required: false,
|
|
37
40
|
disabled: false,
|
|
38
|
-
|
|
41
|
+
readonly: false,
|
|
39
42
|
isOutlined: true,
|
|
40
43
|
displayIcon: true,
|
|
41
44
|
displayAppendIcon: false,
|
|
@@ -45,6 +48,7 @@
|
|
|
45
48
|
displayPrependIcon: true,
|
|
46
49
|
disableErrorHandling: false,
|
|
47
50
|
showSuccessMessages: true,
|
|
51
|
+
bgColor: undefined,
|
|
48
52
|
})
|
|
49
53
|
|
|
50
54
|
const emit = defineEmits<{
|
|
@@ -74,101 +78,37 @@
|
|
|
74
78
|
const isFocused = ref(false)
|
|
75
79
|
const hasInteracted = ref(false)
|
|
76
80
|
|
|
77
|
-
const formatDateToString = (date: Date, format: string): string => {
|
|
78
|
-
const day = date.getDate().toString().padStart(2, '0')
|
|
79
|
-
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
80
|
-
const year = date.getFullYear()
|
|
81
|
-
const shortYear = (year % 100).toString().padStart(2, '0')
|
|
82
|
-
|
|
83
|
-
const separator = format.includes('/') ? '/' : format.includes('-') ? '-' : '.'
|
|
84
|
-
const parts: string[] = []
|
|
85
|
-
|
|
86
|
-
format.split(/[-/.]/).forEach((part) => {
|
|
87
|
-
switch (part.toUpperCase()) {
|
|
88
|
-
case 'DD':
|
|
89
|
-
parts.push(day)
|
|
90
|
-
break
|
|
91
|
-
case 'MM':
|
|
92
|
-
parts.push(month)
|
|
93
|
-
break
|
|
94
|
-
case 'YY':
|
|
95
|
-
parts.push(shortYear)
|
|
96
|
-
break
|
|
97
|
-
case 'YYYY':
|
|
98
|
-
parts.push(year.toString())
|
|
99
|
-
break
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
return parts.join(separator)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
81
|
const formatDateInput = (input: string, cursorPosition?: number): { formatted: string, cursorPos: number } => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
let cleanInput = input
|
|
122
|
-
|
|
123
|
-
if (input.includes(separator)) {
|
|
124
|
-
const parts = input.split(separator)
|
|
125
|
-
cleanInput = parts.map(part => part.replace(/\D/g, '')).join(separator)
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
cleanInput = input.replace(/\D/g, '')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (cleanInput.includes(separator)) {
|
|
132
|
-
const parts = cleanInput.split(separator)
|
|
133
|
-
const formattedParts = Array(3).fill('__')
|
|
134
|
-
formattedParts[yearIndex] = (parts[yearIndex] || '').padEnd(4, '_')
|
|
135
|
-
formattedParts[monthIndex] = (parts[monthIndex] || '').padEnd(2, '_')
|
|
136
|
-
formattedParts[dayIndex] = (parts[dayIndex] || '').padEnd(2, '_')
|
|
137
|
-
|
|
138
|
-
result = formattedParts.join(separator)
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
const formatOrder = [dayIndex, monthIndex, yearIndex]
|
|
142
|
-
let currentDigit = 0
|
|
143
|
-
|
|
144
|
-
for (let partIndex = 0; currentDigit < Math.min(cleanInput.length, 8); partIndex++) {
|
|
145
|
-
const formatPartIndex = formatOrder[partIndex % 3]
|
|
146
|
-
const isYear = formatParts[formatPartIndex].toUpperCase().includes('Y')
|
|
147
|
-
const partLength = isYear ? 4 : 2
|
|
148
|
-
const targetStartPos = formatPartIndex * 3
|
|
149
|
-
|
|
150
|
-
for (let j = 0; j < partLength && currentDigit < cleanInput.length; j++) {
|
|
151
|
-
const digit = cleanInput[currentDigit]
|
|
152
|
-
const targetPos = targetStartPos + j
|
|
153
|
-
result = result.substring(0, targetPos) + digit + result.substring(targetPos + 1)
|
|
154
|
-
currentDigit++
|
|
82
|
+
const cleanedInput = input.replace(/[^\d]/g, '')
|
|
83
|
+
let cursor = cursorPosition || 0
|
|
84
|
+
let result = ''
|
|
85
|
+
|
|
86
|
+
let i = 0
|
|
87
|
+
for (const char of props.format) {
|
|
88
|
+
if (['D', 'M', 'Y'].includes(char.toUpperCase())) {
|
|
89
|
+
if (cleanedInput[i]) {
|
|
90
|
+
result += cleanedInput[i]
|
|
91
|
+
i++
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
result += '_'
|
|
155
95
|
}
|
|
156
96
|
}
|
|
97
|
+
else {
|
|
98
|
+
result += char
|
|
99
|
+
}
|
|
157
100
|
}
|
|
158
101
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
pos++
|
|
163
|
-
}
|
|
102
|
+
const nextChartIsSeparator = props.format[cursor] === result[cursor]
|
|
103
|
+
if (nextChartIsSeparator) {
|
|
104
|
+
cursor++
|
|
164
105
|
}
|
|
165
106
|
|
|
166
107
|
return {
|
|
167
108
|
formatted: result,
|
|
168
|
-
cursorPos:
|
|
109
|
+
cursorPos: cursor,
|
|
169
110
|
}
|
|
170
111
|
}
|
|
171
|
-
|
|
172
112
|
const cleanDateString = (input: string): string => {
|
|
173
113
|
return input.replace(/[^\d/.-]/g, '')
|
|
174
114
|
}
|
|
@@ -184,20 +124,17 @@
|
|
|
184
124
|
if (!/^[\d/.-]*$/.test(dateStr)) {
|
|
185
125
|
return {
|
|
186
126
|
isValid: props.disableErrorHandling,
|
|
187
|
-
message: props.disableErrorHandling ? '' :
|
|
127
|
+
message: props.disableErrorHandling ? '' : `Format de date invalide (${props.format})`,
|
|
188
128
|
}
|
|
189
129
|
}
|
|
190
130
|
|
|
191
|
-
|
|
131
|
+
const isValid = dayjs(dateStr, props.format, true).isValid()
|
|
132
|
+
|| (props.dateFormatReturn ? dayjs(dateStr, props.dateFormatReturn, true).isValid() : false)
|
|
192
133
|
|
|
193
|
-
if (!
|
|
194
|
-
date = parseDate(dateStr, props.dateFormatReturn)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!date) {
|
|
134
|
+
if (!isValid) {
|
|
198
135
|
return {
|
|
199
136
|
isValid: props.disableErrorHandling,
|
|
200
|
-
message: props.disableErrorHandling ? '' :
|
|
137
|
+
message: props.disableErrorHandling ? '' : `Format de date invalide (${props.format})`,
|
|
201
138
|
}
|
|
202
139
|
}
|
|
203
140
|
|
|
@@ -253,9 +190,17 @@
|
|
|
253
190
|
return undefined
|
|
254
191
|
})
|
|
255
192
|
|
|
256
|
-
const handleKeydown = (event: KeyboardEvent) => {
|
|
257
|
-
if
|
|
258
|
-
|
|
193
|
+
const handleKeydown = (event: KeyboardEvent & { target: HTMLInputElement }) => {
|
|
194
|
+
// the cursor have to be set to the previous character if the user delete a non digit character
|
|
195
|
+
if (event.key === 'Backspace') {
|
|
196
|
+
const input = event.target
|
|
197
|
+
if (!input.selectionStart || input.selectionStart !== input.selectionEnd) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
const charBeforeCursor = input.value[input.selectionStart - 1]
|
|
201
|
+
if (!/\d/.test(charBeforeCursor)) {
|
|
202
|
+
input.setSelectionRange(input.selectionStart - 1, input.selectionStart - 1)
|
|
203
|
+
}
|
|
259
204
|
}
|
|
260
205
|
}
|
|
261
206
|
|
|
@@ -305,10 +250,13 @@
|
|
|
305
250
|
if (isDateComplete) {
|
|
306
251
|
const validation = validateDateFormat(formatted)
|
|
307
252
|
if (validation.isValid) {
|
|
308
|
-
const date =
|
|
253
|
+
const date = dayjs(formatted, props.format, true).isValid()
|
|
254
|
+
? dayjs(formatted, props.format).toDate()
|
|
255
|
+
: null
|
|
256
|
+
|
|
309
257
|
if (date) {
|
|
310
258
|
const formattedDate = props.dateFormatReturn
|
|
311
|
-
?
|
|
259
|
+
? dayjs(date).format(props.dateFormatReturn)
|
|
312
260
|
: formatted
|
|
313
261
|
await nextTick()
|
|
314
262
|
emit('update:model-value', formattedDate)
|
|
@@ -334,14 +282,17 @@
|
|
|
334
282
|
return
|
|
335
283
|
}
|
|
336
284
|
|
|
337
|
-
const date =
|
|
285
|
+
const date = dayjs(newValue, props.format, true).isValid()
|
|
286
|
+
? dayjs(newValue, props.format).toDate()
|
|
287
|
+
: null
|
|
288
|
+
|
|
338
289
|
if (date) {
|
|
339
290
|
if (props.dateFormatReturn && props.dateFormatReturn !== props.format) {
|
|
340
|
-
const formattedForReturn =
|
|
291
|
+
const formattedForReturn = dayjs(date).format(props.dateFormatReturn)
|
|
341
292
|
emit('update:model-value', formattedForReturn)
|
|
342
293
|
}
|
|
343
294
|
|
|
344
|
-
inputValue.value =
|
|
295
|
+
inputValue.value = dayjs(date).format(props.format)
|
|
345
296
|
validateRules(inputValue.value)
|
|
346
297
|
}
|
|
347
298
|
else {
|
|
@@ -363,10 +314,13 @@
|
|
|
363
314
|
if (inputValue.value) {
|
|
364
315
|
const validation = validateDateFormat(inputValue.value)
|
|
365
316
|
if (validation.isValid) {
|
|
366
|
-
const date =
|
|
317
|
+
const date = dayjs(inputValue.value, props.format, true).isValid()
|
|
318
|
+
? dayjs(inputValue.value, props.format).toDate()
|
|
319
|
+
: null
|
|
320
|
+
|
|
367
321
|
if (date) {
|
|
368
322
|
const formattedDate = props.dateFormatReturn
|
|
369
|
-
?
|
|
323
|
+
? dayjs(date).format(props.dateFormatReturn)
|
|
370
324
|
: inputValue.value
|
|
371
325
|
emit('update:model-value', formattedDate)
|
|
372
326
|
}
|
|
@@ -407,14 +361,17 @@
|
|
|
407
361
|
|
|
408
362
|
defineExpose({
|
|
409
363
|
validateOnSubmit,
|
|
410
|
-
focus
|
|
411
|
-
|
|
364
|
+
focus() {
|
|
365
|
+
// Utiliser un sélecteur plus spécifique pour cibler l'input principal
|
|
366
|
+
// SyTextField peut contenir plusieurs inputs, donc on cible le premier qui n'est pas caché
|
|
367
|
+
const input = inputRef.value?.$el.querySelector('input:not([type="hidden"])')
|
|
412
368
|
if (input) {
|
|
413
369
|
input.focus()
|
|
414
370
|
}
|
|
415
371
|
},
|
|
416
|
-
blur
|
|
417
|
-
|
|
372
|
+
blur() {
|
|
373
|
+
// Utiliser un sélecteur plus spécifique pour cibler l'input principal
|
|
374
|
+
const input = inputRef.value?.$el.querySelector('input:not([type="hidden"])')
|
|
418
375
|
if (input) {
|
|
419
376
|
input.blur()
|
|
420
377
|
}
|
|
@@ -426,51 +383,46 @@
|
|
|
426
383
|
return
|
|
427
384
|
}
|
|
428
385
|
|
|
429
|
-
const date =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const formattedForReturn = formatDateToString(date, props.dateFormatReturn)
|
|
433
|
-
emit('update:model-value', formattedForReturn)
|
|
434
|
-
}
|
|
386
|
+
const date = dayjs(props.modelValue, props.format, true).isValid()
|
|
387
|
+
? dayjs(props.modelValue, props.format).toDate()
|
|
388
|
+
: null
|
|
435
389
|
|
|
436
|
-
|
|
437
|
-
|
|
390
|
+
if (date) {
|
|
391
|
+
inputValue.value = dayjs(date).format(props.format)
|
|
438
392
|
}
|
|
439
393
|
else {
|
|
440
394
|
inputValue.value = props.modelValue
|
|
441
|
-
validateRules(props.modelValue)
|
|
442
395
|
}
|
|
443
396
|
})
|
|
444
|
-
|
|
445
397
|
</script>
|
|
446
398
|
|
|
447
399
|
<template>
|
|
448
400
|
<SyTextField
|
|
449
401
|
ref="inputRef"
|
|
450
402
|
v-model="inputValue"
|
|
451
|
-
:
|
|
452
|
-
:label="label"
|
|
453
|
-
:error-messages="errorMessages"
|
|
454
|
-
:warning-messages="warningMessages"
|
|
455
|
-
:success-messages="showSuccessMessages ? successMessages : []"
|
|
456
|
-
:is-on-error="isOnError"
|
|
457
|
-
:disabled="disabled"
|
|
458
|
-
:read-only="readOnly"
|
|
459
|
-
:display-icon="displayIcon"
|
|
460
|
-
:display-append-icon="displayAppendIcon"
|
|
461
|
-
:no-icon="noIcon"
|
|
462
|
-
:prepend-icon="props.displayPrependIcon && !props.displayAppendIcon ? 'calendar' : undefined"
|
|
463
|
-
:append-icon="props.displayAppendIcon ? 'calendar' : undefined"
|
|
403
|
+
:append-icon="displayIcon && displayAppendIcon ? 'calendar' : undefined"
|
|
464
404
|
:append-inner-icon="getIcon"
|
|
465
|
-
:variant-style="isOutlined ? 'outlined' : 'filled'"
|
|
466
405
|
:class="{
|
|
467
406
|
'error-field': isOnError,
|
|
468
407
|
'warning-field': isOnWarning,
|
|
469
408
|
'success-field': isOnSuccess
|
|
470
409
|
}"
|
|
471
|
-
|
|
410
|
+
:disabled="props.disabled"
|
|
411
|
+
:error-messages="errorMessages"
|
|
412
|
+
:label="props.label || props.placeholder"
|
|
413
|
+
:no-icon="props.noIcon"
|
|
414
|
+
:prepend-icon="displayIcon && displayPrependIcon ? 'calendar' : undefined"
|
|
415
|
+
:readonly="props.readonly"
|
|
416
|
+
:variant-style="props.isOutlined ? 'outlined' : 'underlined'"
|
|
417
|
+
:warning-messages="warningMessages"
|
|
418
|
+
:success-messages="props.showSuccessMessages ? successMessages : []"
|
|
419
|
+
:bg-color="props.bgColor"
|
|
420
|
+
color="primary"
|
|
421
|
+
is-clearable
|
|
422
|
+
title="Date text input"
|
|
472
423
|
@focus="handleFocus"
|
|
473
424
|
@blur="handleBlur"
|
|
425
|
+
@keydown="handleKeydown"
|
|
474
426
|
@paste="handlePaste"
|
|
475
427
|
/>
|
|
476
428
|
</template>
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { ref, computed, watch } from 'vue'
|
|
3
|
+
import DatePicker from '@/components/DatePicker/DatePicker.vue'
|
|
4
|
+
import { useDateFormat } from '@/composables/date/useDateFormat'
|
|
5
|
+
|
|
6
|
+
const { parseDate } = useDateFormat()
|
|
7
|
+
|
|
8
|
+
// État des dates
|
|
9
|
+
const startDate = ref<string | null>(null)
|
|
10
|
+
const endDate = ref<string | null>(null)
|
|
11
|
+
|
|
12
|
+
// Références aux composants DatePicker pour accéder à leurs méthodes
|
|
13
|
+
const startDatePickerRef = ref<InstanceType<typeof DatePicker> | null>(null)
|
|
14
|
+
const endDatePickerRef = ref<InstanceType<typeof DatePicker> | null>(null)
|
|
15
|
+
|
|
16
|
+
// Règle de validation pour vérifier que la date de fin n'est pas avant la date de début
|
|
17
|
+
const createEndDateValidationRule = () => ({
|
|
18
|
+
type: 'custom',
|
|
19
|
+
options: {
|
|
20
|
+
validate: (value: string) => {
|
|
21
|
+
// Si pas de valeur pour la date de fin, pas besoin de validation
|
|
22
|
+
if (!value) return true
|
|
23
|
+
|
|
24
|
+
// Si pas de date de début mais une date de fin, afficher l'erreur
|
|
25
|
+
if (!startDate.value) return 'Veuillez d\'abord sélectionner une date de début'
|
|
26
|
+
|
|
27
|
+
const start = parseDate(startDate.value, 'DD/MM/YYYY')
|
|
28
|
+
const end = parseDate(value, 'DD/MM/YYYY')
|
|
29
|
+
|
|
30
|
+
if (!start || !end) return true
|
|
31
|
+
|
|
32
|
+
return end >= start || 'La date de fin ne peut pas être antérieure à la date de début'
|
|
33
|
+
},
|
|
34
|
+
message: 'La date de fin ne peut pas être antérieure à la date de début',
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Règle de validation pour vérifier que la date de début n'est pas après la date de fin
|
|
39
|
+
const createStartDateValidationRule = () => ({
|
|
40
|
+
type: 'custom',
|
|
41
|
+
options: {
|
|
42
|
+
validate: (value: string) => {
|
|
43
|
+
// Si pas de valeur pour la date de début ou pas de date de fin, pas besoin de validation
|
|
44
|
+
if (!value || !endDate.value) return true
|
|
45
|
+
|
|
46
|
+
const start = parseDate(value, 'DD/MM/YYYY')
|
|
47
|
+
const end = parseDate(endDate.value, 'DD/MM/YYYY')
|
|
48
|
+
|
|
49
|
+
if (!start || !end) return true
|
|
50
|
+
|
|
51
|
+
return start <= end || 'La date de début ne peut pas être postérieure à la date de fin'
|
|
52
|
+
},
|
|
53
|
+
message: 'La date de début ne peut pas être postérieure à la date de fin',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Règles de validation pour la date de début
|
|
58
|
+
const startDateRules = computed(() => [
|
|
59
|
+
{
|
|
60
|
+
type: 'required',
|
|
61
|
+
options: {
|
|
62
|
+
message: 'La date de début est requise.',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
createStartDateValidationRule(),
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
// Règles de validation pour la date de fin
|
|
69
|
+
const endDateRules = computed(() => [
|
|
70
|
+
{
|
|
71
|
+
type: 'required',
|
|
72
|
+
options: {
|
|
73
|
+
message: 'La date de fin est requise.',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
createEndDateValidationRule(),
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
// Fonction pour forcer la validation de la date de fin quand la date de début change
|
|
80
|
+
const validateEndDate = () => {
|
|
81
|
+
if (endDatePickerRef.value && endDate.value) {
|
|
82
|
+
// On utilise validateOnSubmit pour forcer la validation complète
|
|
83
|
+
endDatePickerRef.value.validateOnSubmit()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fonction pour forcer la validation de la date de début quand la date de fin change
|
|
88
|
+
const validateStartDate = () => {
|
|
89
|
+
if (startDatePickerRef.value && startDate.value) {
|
|
90
|
+
// On utilise validateOnSubmit pour forcer la validation complète
|
|
91
|
+
startDatePickerRef.value.validateOnSubmit()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Watcher pour la date de début qui force la revalidation de la date de fin
|
|
96
|
+
watch(startDate, () => {
|
|
97
|
+
// Laisser le temps au système de mettre à jour les valeurs
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
validateEndDate()
|
|
100
|
+
}, 0)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Watcher pour la date de fin qui force la revalidation de la date de début
|
|
104
|
+
watch(endDate, () => {
|
|
105
|
+
// Laisser le temps au système de mettre à jour les valeurs
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
validateStartDate()
|
|
108
|
+
}, 0)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Fonction pour définir des dates de test
|
|
112
|
+
const setTestDates = () => {
|
|
113
|
+
// Définir une date de début (aujourd'hui)
|
|
114
|
+
const today = new Date()
|
|
115
|
+
const day = String(today.getDate()).padStart(2, '0')
|
|
116
|
+
const month = String(today.getMonth() + 1).padStart(2, '0')
|
|
117
|
+
const year = today.getFullYear()
|
|
118
|
+
startDate.value = `${day}/${month}/${year}`
|
|
119
|
+
|
|
120
|
+
// Définir une date de fin (demain) - pour démontrer une validation valide
|
|
121
|
+
const tomorrow = new Date(today)
|
|
122
|
+
tomorrow.setDate(today.getDate() + 1)
|
|
123
|
+
const tomorrowDay = String(tomorrow.getDate()).padStart(2, '0')
|
|
124
|
+
const tomorrowMonth = String(tomorrow.getMonth() + 1).padStart(2, '0')
|
|
125
|
+
const tomorrowYear = tomorrow.getFullYear()
|
|
126
|
+
endDate.value = `${tomorrowDay}/${tomorrowMonth}/${tomorrowYear}`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Fonction pour définir des dates invalides (date de fin avant date de début)
|
|
130
|
+
const setInvalidDates = () => {
|
|
131
|
+
// Définir une date de début (aujourd'hui)
|
|
132
|
+
const today = new Date()
|
|
133
|
+
const day = String(today.getDate()).padStart(2, '0')
|
|
134
|
+
const month = String(today.getMonth() + 1).padStart(2, '0')
|
|
135
|
+
const year = today.getFullYear()
|
|
136
|
+
startDate.value = `${day}/${month}/${year}`
|
|
137
|
+
|
|
138
|
+
// Définir une date de fin (hier) - pour démontrer une validation invalide
|
|
139
|
+
const yesterday = new Date(today)
|
|
140
|
+
yesterday.setDate(today.getDate() - 1)
|
|
141
|
+
const yesterdayDay = String(yesterday.getDate()).padStart(2, '0')
|
|
142
|
+
const yesterdayMonth = String(yesterday.getMonth() + 1).padStart(2, '0')
|
|
143
|
+
const yesterdayYear = yesterday.getFullYear()
|
|
144
|
+
endDate.value = `${yesterdayDay}/${yesterdayMonth}/${yesterdayYear}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Fonction pour réinitialiser les dates
|
|
148
|
+
const resetDates = () => {
|
|
149
|
+
startDate.value = null
|
|
150
|
+
endDate.value = null
|
|
151
|
+
}
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<template>
|
|
155
|
+
<div class="date-picker-bidirectional-validation">
|
|
156
|
+
<h1 class="text-h5 mb-6">
|
|
157
|
+
Validation bidirectionnelle entre deux DatePickers
|
|
158
|
+
</h1>
|
|
159
|
+
|
|
160
|
+
<p class="text-body-2 mb-4">
|
|
161
|
+
Ce composant démontre la validation bidirectionnelle entre deux DatePickers. Les règles de validation sont appliquées dans les deux sens :
|
|
162
|
+
<ul>
|
|
163
|
+
<li>La date de fin doit être postérieure ou égale à la date de début</li>
|
|
164
|
+
<li>La date de début doit être antérieure ou égale à la date de fin</li>
|
|
165
|
+
<li>Lorsque la date de début change, la validation de la date de fin est mise à jour</li>
|
|
166
|
+
<li>Lorsque la date de fin change, la validation de la date de début est mise à jour</li>
|
|
167
|
+
</ul>
|
|
168
|
+
</p>
|
|
169
|
+
|
|
170
|
+
<div class="date-range-container mb-6">
|
|
171
|
+
<div class="date-picker-wrapper">
|
|
172
|
+
<h3 class="text-subtitle-1 mb-2">
|
|
173
|
+
Date de début
|
|
174
|
+
</h3>
|
|
175
|
+
<DatePicker
|
|
176
|
+
ref="startDatePickerRef"
|
|
177
|
+
v-model="startDate"
|
|
178
|
+
placeholder="Date de début"
|
|
179
|
+
:custom-rules="startDateRules"
|
|
180
|
+
required
|
|
181
|
+
@update:model-value="validateEndDate"
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="date-picker-wrapper">
|
|
185
|
+
<h3 class="text-subtitle-1 mb-2">
|
|
186
|
+
Date de fin
|
|
187
|
+
</h3>
|
|
188
|
+
<DatePicker
|
|
189
|
+
ref="endDatePickerRef"
|
|
190
|
+
v-model="endDate"
|
|
191
|
+
placeholder="Date de fin"
|
|
192
|
+
:custom-rules="endDateRules"
|
|
193
|
+
required
|
|
194
|
+
@update:model-value="validateStartDate"
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="actions mb-4">
|
|
200
|
+
<v-btn
|
|
201
|
+
size="small"
|
|
202
|
+
color="primary"
|
|
203
|
+
class="mr-2"
|
|
204
|
+
@click="resetDates"
|
|
205
|
+
>
|
|
206
|
+
Réinitialiser
|
|
207
|
+
</v-btn>
|
|
208
|
+
|
|
209
|
+
<v-btn
|
|
210
|
+
size="small"
|
|
211
|
+
color="success"
|
|
212
|
+
class="mr-2"
|
|
213
|
+
@click="setTestDates"
|
|
214
|
+
>
|
|
215
|
+
Dates valides
|
|
216
|
+
</v-btn>
|
|
217
|
+
|
|
218
|
+
<v-btn
|
|
219
|
+
size="small"
|
|
220
|
+
color="error"
|
|
221
|
+
@click="setInvalidDates"
|
|
222
|
+
>
|
|
223
|
+
Dates invalides
|
|
224
|
+
</v-btn>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div class="current-values mt-4">
|
|
228
|
+
<p><strong>Date de début :</strong> {{ startDate || 'Non sélectionnée' }}</p>
|
|
229
|
+
<p><strong>Date de fin :</strong> {{ endDate || 'Non sélectionnée' }}</p>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div class="mt-6 pa-4 bg-grey-lighten-4 rounded">
|
|
233
|
+
<h3 class="text-subtitle-1 mb-2">
|
|
234
|
+
Comment fonctionne la validation bidirectionnelle
|
|
235
|
+
</h3>
|
|
236
|
+
<p class="text-body-2">
|
|
237
|
+
La validation bidirectionnelle entre les DatePickers est implémentée grâce à des règles de validation personnalisées
|
|
238
|
+
qui vérifient la relation entre les deux dates. Chaque DatePicker a sa propre règle qui vérifie sa valeur par rapport à l'autre.
|
|
239
|
+
</p>
|
|
240
|
+
<p class="text-body-2 mt-2">
|
|
241
|
+
Lorsqu'une date change, un watcher déclenche la validation de l'autre DatePicker. Cela garantit que les messages d'erreur
|
|
242
|
+
sont toujours à jour, même lorsque les dates sont modifiées dans n'importe quel ordre.
|
|
243
|
+
</p>
|
|
244
|
+
<p class="text-body-2 mt-2">
|
|
245
|
+
Les messages d'erreur apparaissent directement dans les composants DatePicker, ce qui améliore l'expérience utilisateur
|
|
246
|
+
en fournissant un retour immédiat sur la validité des dates sélectionnées.
|
|
247
|
+
</p>
|
|
248
|
+
<p class="text-body-2 mt-2">
|
|
249
|
+
Vous trouverez le code source dans la story Bidirectional Validation.
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</template>
|
|
254
|
+
|
|
255
|
+
<style scoped>
|
|
256
|
+
.date-picker-bidirectional-validation {
|
|
257
|
+
padding: 20px;
|
|
258
|
+
max-width: 800px;
|
|
259
|
+
margin: 0 auto;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.date-range-container {
|
|
263
|
+
display: flex;
|
|
264
|
+
gap: 20px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.date-picker-wrapper {
|
|
268
|
+
flex: 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.actions {
|
|
272
|
+
display: flex;
|
|
273
|
+
flex-wrap: wrap;
|
|
274
|
+
gap: 8px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.current-values {
|
|
278
|
+
padding: 15px;
|
|
279
|
+
background-color: #f5f5f5;
|
|
280
|
+
border-radius: 4px;
|
|
281
|
+
}
|
|
282
|
+
</style>
|