@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
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { computed, ref, watch } from 'vue'
|
|
3
3
|
import type { IconType, VariantStyle, ColorType } from './types'
|
|
4
|
-
import {
|
|
5
|
-
import type { RuleOptions } from '@/composables/rules/useFieldValidation'
|
|
4
|
+
import { useValidation, type ValidationRule } from '@/composables/validation/useValidation'
|
|
6
5
|
import {
|
|
7
6
|
mdiAlertOutline,
|
|
8
7
|
mdiCheck,
|
|
@@ -19,6 +18,9 @@
|
|
|
19
18
|
appendIcon?: IconType
|
|
20
19
|
prependInnerIcon?: IconType
|
|
21
20
|
appendInnerIcon?: IconType
|
|
21
|
+
prependTooltip?: string
|
|
22
|
+
appendTooltip?: string
|
|
23
|
+
tooltipLocation?: 'top' | 'bottom' | 'start' | 'end'
|
|
22
24
|
variantStyle?: VariantStyle
|
|
23
25
|
color?: ColorType
|
|
24
26
|
isClearable?: boolean
|
|
@@ -70,9 +72,11 @@
|
|
|
70
72
|
width?: string | number
|
|
71
73
|
displayAsterisk?: boolean
|
|
72
74
|
noIcon?: boolean
|
|
73
|
-
customRules?:
|
|
74
|
-
customWarningRules?:
|
|
75
|
+
customRules?: ValidationRule[]
|
|
76
|
+
customWarningRules?: ValidationRule[]
|
|
77
|
+
customSuccessRules?: ValidationRule[]
|
|
75
78
|
showSuccessMessages?: boolean
|
|
79
|
+
isValidateOnBlur?: boolean
|
|
76
80
|
}>(),
|
|
77
81
|
{
|
|
78
82
|
modelValue: undefined,
|
|
@@ -80,6 +84,9 @@
|
|
|
80
84
|
appendIcon: undefined,
|
|
81
85
|
appendInnerIcon: undefined,
|
|
82
86
|
prependInnerIcon: undefined,
|
|
87
|
+
prependTooltip: undefined,
|
|
88
|
+
appendTooltip: undefined,
|
|
89
|
+
tooltipLocation: 'top',
|
|
83
90
|
variantStyle: 'outlined',
|
|
84
91
|
color: 'primary',
|
|
85
92
|
label: 'custom label',
|
|
@@ -106,7 +113,7 @@
|
|
|
106
113
|
hint: undefined,
|
|
107
114
|
id: undefined,
|
|
108
115
|
loading: false,
|
|
109
|
-
maxErrors:
|
|
116
|
+
maxErrors: undefined,
|
|
110
117
|
maxWidth: undefined,
|
|
111
118
|
messages: undefined,
|
|
112
119
|
minWidth: undefined,
|
|
@@ -130,7 +137,9 @@
|
|
|
130
137
|
noIcon: false,
|
|
131
138
|
customRules: () => [],
|
|
132
139
|
customWarningRules: () => [],
|
|
140
|
+
customSuccessRules: () => [],
|
|
133
141
|
showSuccessMessages: true,
|
|
142
|
+
isValidateOnBlur: true,
|
|
134
143
|
},
|
|
135
144
|
)
|
|
136
145
|
|
|
@@ -143,137 +152,118 @@
|
|
|
143
152
|
calendar: mdiCalendar,
|
|
144
153
|
}
|
|
145
154
|
|
|
155
|
+
const emit = defineEmits([
|
|
156
|
+
'update:modelValue',
|
|
157
|
+
'clear',
|
|
158
|
+
'prepend-icon-click',
|
|
159
|
+
'append-icon-click',
|
|
160
|
+
])
|
|
161
|
+
|
|
146
162
|
const model = computed({
|
|
147
|
-
get
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
get() {
|
|
164
|
+
return props.modelValue
|
|
165
|
+
},
|
|
166
|
+
set(value) {
|
|
167
|
+
emit('update:modelValue', value)
|
|
150
168
|
},
|
|
151
169
|
})
|
|
152
170
|
|
|
153
171
|
const isBlurred = ref(false)
|
|
154
|
-
const errors = ref<string[]>([])
|
|
155
|
-
const warnings = ref<string[]>([])
|
|
156
|
-
const successes = ref<string[]>([])
|
|
157
172
|
|
|
173
|
+
// Initialisation du composable de validation
|
|
174
|
+
const validation = useValidation({
|
|
175
|
+
customRules: props.customRules,
|
|
176
|
+
warningRules: props.customWarningRules,
|
|
177
|
+
successRules: props.customSuccessRules,
|
|
178
|
+
showSuccessMessages: props.showSuccessMessages,
|
|
179
|
+
fieldIdentifier: props.label,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
// Synchronisation des messages externes
|
|
158
183
|
watch(() => props.errorMessages, (newVal) => {
|
|
159
|
-
errors.value = newVal || []
|
|
184
|
+
validation.errors.value = newVal || []
|
|
160
185
|
}, { immediate: true })
|
|
161
186
|
|
|
162
187
|
watch(() => props.warningMessages, (newVal) => {
|
|
163
|
-
warnings.value = newVal || []
|
|
188
|
+
validation.warnings.value = newVal || []
|
|
164
189
|
}, { immediate: true })
|
|
165
190
|
|
|
166
191
|
watch(() => props.successMessages, (newVal) => {
|
|
167
|
-
successes.value = newVal || []
|
|
192
|
+
validation.successes.value = newVal || []
|
|
168
193
|
}, { immediate: true })
|
|
169
194
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
options: {
|
|
182
|
-
message: `Le champ ${props.label || 'ce champ'} est requis.`,
|
|
183
|
-
fieldIdentifier: props.label,
|
|
184
|
-
},
|
|
185
|
-
}]
|
|
186
|
-
: []
|
|
187
|
-
|
|
188
|
-
return generateRules([...defaultRules, ...customRules.value])
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
const warningValidationRules = computed(() => {
|
|
192
|
-
const rulesWithWarning = customWarningRules.value.map(rule => ({
|
|
193
|
-
type: rule.type,
|
|
194
|
-
options: { ...(rule.options || {}), isWarning: true },
|
|
195
|
-
}))
|
|
196
|
-
return generateRules(rulesWithWarning)
|
|
197
|
-
})
|
|
195
|
+
// Construction des règles de validation
|
|
196
|
+
const defaultRules = computed<ValidationRule[]>(() => props.required
|
|
197
|
+
? [{
|
|
198
|
+
type: 'required',
|
|
199
|
+
options: {
|
|
200
|
+
message: `Le champ ${props.label || 'ce champ'} est requis.`,
|
|
201
|
+
fieldIdentifier: props.label,
|
|
202
|
+
},
|
|
203
|
+
}]
|
|
204
|
+
: [],
|
|
205
|
+
)
|
|
198
206
|
|
|
199
207
|
const validateField = (value: string | number | null) => {
|
|
200
|
-
errors.value = []
|
|
201
|
-
warnings.value = []
|
|
202
|
-
successes.value = []
|
|
203
|
-
|
|
204
208
|
// Si le champ est vide et non requis, on ne fait pas de validation
|
|
205
209
|
if (!value && !props.required) {
|
|
210
|
+
validation.clearValidation()
|
|
206
211
|
return true
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (result.error) {
|
|
215
|
-
errors.value.push(result.error)
|
|
216
|
-
}
|
|
217
|
-
else if (props.showSuccessMessages && result.success && !errors.value.length && !hasSuccess) {
|
|
218
|
-
successes.value = [result.success]
|
|
219
|
-
hasSuccess = true
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
// Si on a des erreurs, on n'affiche pas les warnings ni les succès
|
|
224
|
-
if (errors.value.length) {
|
|
225
|
-
warnings.value = []
|
|
226
|
-
successes.value = []
|
|
227
|
-
return false
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Validation des règles d'avertissement
|
|
231
|
-
warningValidationRules.value.forEach((rule) => {
|
|
232
|
-
const result = rule(value)
|
|
233
|
-
if (result.warning) {
|
|
234
|
-
warnings.value.push(result.warning)
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
// Si on a des warnings, on n'affiche pas les succès
|
|
239
|
-
if (warnings.value.length) {
|
|
240
|
-
successes.value = []
|
|
241
|
-
}
|
|
214
|
+
const result = validation.validateField(
|
|
215
|
+
value,
|
|
216
|
+
[...defaultRules.value, ...props.customRules],
|
|
217
|
+
props.customWarningRules,
|
|
218
|
+
)
|
|
242
219
|
|
|
243
|
-
return
|
|
220
|
+
return !result.hasError
|
|
244
221
|
}
|
|
245
222
|
|
|
246
223
|
const validateOnSubmit = () => {
|
|
247
224
|
isBlurred.value = true
|
|
248
|
-
// On s'assure que model.value n'est pas undefined
|
|
249
225
|
return validateField(model.value ?? null)
|
|
250
226
|
}
|
|
251
227
|
|
|
252
|
-
const hasError = computed(() => errors.value.length > 0)
|
|
253
|
-
const hasWarning = computed(() => warnings.value.length > 0)
|
|
254
|
-
const hasSuccess = computed(() => successes.value.length > 0 && !hasError.value && !hasWarning.value)
|
|
255
|
-
|
|
256
228
|
const checkErrorOnBlur = () => {
|
|
257
229
|
isBlurred.value = true
|
|
258
230
|
validateField(model.value ?? null)
|
|
231
|
+
emit('update:modelValue', model.value)
|
|
259
232
|
}
|
|
260
233
|
|
|
261
234
|
watch(model, (newValue) => {
|
|
262
|
-
|
|
235
|
+
if (!props.isValidateOnBlur) {
|
|
236
|
+
validateField(newValue ?? null)
|
|
237
|
+
}
|
|
263
238
|
if (props.isClearable && newValue === '') {
|
|
264
239
|
emit('clear')
|
|
265
240
|
}
|
|
266
241
|
})
|
|
267
242
|
|
|
243
|
+
// Computed pour l'affichage des états
|
|
244
|
+
const hasError = computed(() => validation.hasError.value)
|
|
245
|
+
const hasWarning = computed(() => validation.hasWarning.value)
|
|
246
|
+
const hasSuccess = computed(() => validation.hasSuccess.value)
|
|
247
|
+
|
|
248
|
+
const errors = computed(() => validation.errors.value)
|
|
249
|
+
const warnings = computed(() => validation.warnings.value)
|
|
250
|
+
const successes = computed(() => validation.successes.value)
|
|
251
|
+
|
|
252
|
+
// Computed pour les icônes
|
|
268
253
|
const appendInnerIconColor = computed(() => {
|
|
269
|
-
if (
|
|
270
|
-
if (
|
|
271
|
-
|
|
272
|
-
return props.appendInnerIcon === 'error' || props.appendInnerIcon === 'success' || props.appendInnerIcon === 'warning'
|
|
273
|
-
? props.appendInnerIcon
|
|
274
|
-
: 'black'
|
|
254
|
+
if (props.appendInnerIcon === 'error') return 'error'
|
|
255
|
+
if (props.appendInnerIcon === 'success') return 'success'
|
|
256
|
+
return 'rgba(0, 0, 0, 1)'
|
|
275
257
|
})
|
|
276
258
|
|
|
259
|
+
const handlePrependIconClick = () => {
|
|
260
|
+
emit('prepend-icon-click')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const handleAppendIconClick = () => {
|
|
264
|
+
emit('append-icon-click')
|
|
265
|
+
}
|
|
266
|
+
|
|
277
267
|
const validationIcon = computed(() => {
|
|
278
268
|
if (hasError.value) return ICONS['error']
|
|
279
269
|
if (hasWarning.value) return ICONS['warning']
|
|
@@ -296,14 +286,10 @@
|
|
|
296
286
|
opacity: '1',
|
|
297
287
|
}
|
|
298
288
|
|
|
299
|
-
const emit = defineEmits(['update:model-value', 'clear', 'prepend-icon-click', 'append-icon-click'])
|
|
300
|
-
|
|
301
289
|
defineExpose({
|
|
302
|
-
|
|
290
|
+
validation,
|
|
303
291
|
validateOnSubmit,
|
|
304
|
-
|
|
305
|
-
warnings,
|
|
306
|
-
successes,
|
|
292
|
+
checkErrorOnBlur,
|
|
307
293
|
})
|
|
308
294
|
</script>
|
|
309
295
|
|
|
@@ -312,6 +298,7 @@
|
|
|
312
298
|
:id="props.id"
|
|
313
299
|
v-model="model"
|
|
314
300
|
:active="props.isActive"
|
|
301
|
+
:title="props.label"
|
|
315
302
|
:aria-label="props.label"
|
|
316
303
|
:base-color="props.baseColor"
|
|
317
304
|
:bg-color="props.bgColor"
|
|
@@ -336,7 +323,7 @@
|
|
|
336
323
|
:loading="props.loading"
|
|
337
324
|
:max-errors="props.maxErrors"
|
|
338
325
|
:max-width="props.maxWidth"
|
|
339
|
-
:messages="hasWarning ? warnings : (hasSuccess && props.showSuccessMessages ? successes : [])"
|
|
326
|
+
:messages="hasError ? errors : (hasWarning ? warnings : (hasSuccess && props.showSuccessMessages ? successes : []))"
|
|
340
327
|
:min-width="props.minWidth"
|
|
341
328
|
:name="props.name"
|
|
342
329
|
:no-icon="props.noIcon"
|
|
@@ -360,35 +347,70 @@
|
|
|
360
347
|
:class="{
|
|
361
348
|
'error-field': hasError,
|
|
362
349
|
'warning-field': hasWarning,
|
|
363
|
-
'success-field': hasSuccess
|
|
350
|
+
'success-field': hasSuccess,
|
|
351
|
+
'basic-field': !hasError && !hasWarning && !hasSuccess
|
|
364
352
|
}"
|
|
365
353
|
@blur="checkErrorOnBlur"
|
|
366
354
|
>
|
|
367
355
|
<template
|
|
368
|
-
v-if="props.prependIcon
|
|
356
|
+
v-if="props.prependIcon || props.prependTooltip"
|
|
369
357
|
#prepend
|
|
370
358
|
>
|
|
371
359
|
<slot name="prepend">
|
|
360
|
+
<template v-if="props.prependTooltip">
|
|
361
|
+
<VTooltip
|
|
362
|
+
:text="props.prependTooltip"
|
|
363
|
+
:location="props.tooltipLocation"
|
|
364
|
+
>
|
|
365
|
+
<template #activator="{ props: tooltipProps }">
|
|
366
|
+
<VIcon
|
|
367
|
+
v-bind="tooltipProps"
|
|
368
|
+
:aria-label="props.label ? `${props.label} - info` : 'Info'"
|
|
369
|
+
:color="appendInnerIconColor"
|
|
370
|
+
:icon="ICONS.info"
|
|
371
|
+
role="button"
|
|
372
|
+
/>
|
|
373
|
+
</template>
|
|
374
|
+
</VTooltip>
|
|
375
|
+
</template>
|
|
372
376
|
<VIcon
|
|
377
|
+
v-else-if="props.prependIcon"
|
|
373
378
|
:aria-label="props.label ? `${props.label} - bouton ${props.prependIcon}` : `Bouton ${props.prependIcon}`"
|
|
374
379
|
:color="appendInnerIconColor"
|
|
375
380
|
:icon="ICONS[props.prependIcon]"
|
|
376
381
|
role="button"
|
|
377
|
-
@click="
|
|
382
|
+
@click="handlePrependIconClick"
|
|
378
383
|
/>
|
|
379
384
|
</slot>
|
|
380
385
|
</template>
|
|
381
386
|
<template
|
|
382
|
-
v-if="props.appendIcon
|
|
387
|
+
v-if="props.appendIcon || props.appendTooltip"
|
|
383
388
|
#append
|
|
384
389
|
>
|
|
385
390
|
<slot name="append">
|
|
391
|
+
<template v-if="props.appendTooltip">
|
|
392
|
+
<VTooltip
|
|
393
|
+
:text="props.appendTooltip"
|
|
394
|
+
:location="props.tooltipLocation"
|
|
395
|
+
>
|
|
396
|
+
<template #activator="{ props: tooltipProps }">
|
|
397
|
+
<VIcon
|
|
398
|
+
v-bind="tooltipProps"
|
|
399
|
+
:aria-label="props.label ? `${props.label} - info` : 'Info'"
|
|
400
|
+
:color="appendInnerIconColor"
|
|
401
|
+
:icon="ICONS.info"
|
|
402
|
+
role="button"
|
|
403
|
+
/>
|
|
404
|
+
</template>
|
|
405
|
+
</VTooltip>
|
|
406
|
+
</template>
|
|
386
407
|
<VIcon
|
|
408
|
+
v-else-if="props.appendIcon"
|
|
387
409
|
:aria-label="props.label ? `${props.label} - bouton ${props.appendIcon}` : `Bouton ${props.appendIcon}`"
|
|
388
410
|
:color="appendInnerIconColor"
|
|
389
411
|
:icon="ICONS[props.appendIcon]"
|
|
390
412
|
role="button"
|
|
391
|
-
@click="
|
|
413
|
+
@click="handleAppendIconClick"
|
|
392
414
|
/>
|
|
393
415
|
</slot>
|
|
394
416
|
</template>
|
|
@@ -494,4 +516,10 @@
|
|
|
494
516
|
}
|
|
495
517
|
}
|
|
496
518
|
}
|
|
519
|
+
|
|
520
|
+
.basic-field {
|
|
521
|
+
:deep(.v-icon__svg) {
|
|
522
|
+
fill: rgb(0 0 0 / 100%);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
497
525
|
</style>
|
|
@@ -1,36 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
1
2
|
import { mount } from '@vue/test-utils'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
3
|
+
import { createVuetify } from 'vuetify'
|
|
4
|
+
import * as components from 'vuetify/components'
|
|
5
|
+
import * as directives from 'vuetify/directives'
|
|
4
6
|
import { VIcon } from 'vuetify/components'
|
|
5
|
-
import { vuetify } from '@tests/unit/setup'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import SyTextField from '../SyTextField.vue'
|
|
9
|
+
import type { IconType } from '../types'
|
|
10
|
+
|
|
11
|
+
const vuetify = createVuetify({
|
|
12
|
+
components,
|
|
13
|
+
directives,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('SyTextField.vue', () => {
|
|
17
|
+
let wrapper: ReturnType<typeof mount<typeof SyTextField>>
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
wrapper = mount(SyTextField, {
|
|
12
21
|
global: {
|
|
13
22
|
plugins: [vuetify],
|
|
14
23
|
},
|
|
24
|
+
props: {
|
|
25
|
+
modelValue: undefined,
|
|
26
|
+
required: true,
|
|
27
|
+
showSuccessMessages: true,
|
|
28
|
+
outlined: true,
|
|
29
|
+
},
|
|
15
30
|
})
|
|
16
|
-
}
|
|
31
|
+
})
|
|
17
32
|
|
|
18
33
|
it('renders correctly with default props', () => {
|
|
19
|
-
const wrapper = factory()
|
|
20
34
|
expect(wrapper.exists()).toBe(true)
|
|
21
|
-
expect(wrapper.findComponent(VIcon).exists()).toBe(false) // No icons by default
|
|
35
|
+
expect(wrapper.findComponent({ name: 'VIcon' }).exists()).toBe(false) // No icons by default
|
|
22
36
|
})
|
|
23
37
|
|
|
24
38
|
it('applies the correct variant style', () => {
|
|
25
|
-
|
|
39
|
+
wrapper = mount(SyTextField, {
|
|
40
|
+
global: { plugins: [vuetify] },
|
|
41
|
+
props: { variantStyle: 'filled' },
|
|
42
|
+
})
|
|
26
43
|
const textField = wrapper.findComponent({ name: 'VTextField' })
|
|
27
44
|
expect(textField.props('variant')).toBe('filled')
|
|
28
45
|
})
|
|
29
46
|
|
|
30
47
|
it('renders default slots correctly', () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
wrapper = mount(SyTextField, {
|
|
49
|
+
global: { plugins: [vuetify] },
|
|
50
|
+
slots: {
|
|
51
|
+
prepend: '<div data-testid="prepend-slot">Prepend Slot Content</div>',
|
|
52
|
+
append: '<div data-testid="append-slot">Append Slot Content</div>',
|
|
53
|
+
},
|
|
34
54
|
})
|
|
35
55
|
|
|
36
56
|
const prependSlot = wrapper.find('.v-field--prepended')
|
|
@@ -41,9 +61,12 @@ describe('SyTextField', () => {
|
|
|
41
61
|
})
|
|
42
62
|
|
|
43
63
|
it('renders inner slots correctly', () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
wrapper = mount(SyTextField, {
|
|
65
|
+
global: { plugins: [vuetify] },
|
|
66
|
+
slots: {
|
|
67
|
+
'prepend-inner': '<div data-testid="prepend-inner-slot">Prepend Inner Slot Content</div>',
|
|
68
|
+
'append-inner': '<div data-testid="append-inner-slot">Append Inner Slot Content</div>',
|
|
69
|
+
},
|
|
47
70
|
})
|
|
48
71
|
|
|
49
72
|
const prependInnerSlot = wrapper.find('[data-testid="prepend-inner-slot"]')
|
|
@@ -55,38 +78,167 @@ describe('SyTextField', () => {
|
|
|
55
78
|
expect(appendInnerSlot.text()).toBe('Append Inner Slot Content')
|
|
56
79
|
})
|
|
57
80
|
|
|
58
|
-
it('
|
|
81
|
+
it('should update icon when validation state changes', async () => {
|
|
82
|
+
wrapper = mount(SyTextField, {
|
|
83
|
+
global: { plugins: [vuetify] },
|
|
84
|
+
props: { appendInnerIcon: 'success' as IconType },
|
|
85
|
+
})
|
|
86
|
+
expect(wrapper.props('appendInnerIcon')).toBe('success')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should update icon when validation state changes with warning', async () => {
|
|
90
|
+
wrapper = mount(SyTextField, {
|
|
91
|
+
global: { plugins: [vuetify] },
|
|
92
|
+
props: { appendInnerIcon: 'warning' as IconType },
|
|
93
|
+
})
|
|
94
|
+
expect(wrapper.props('appendInnerIcon')).toBe('warning')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should update icon when validation state changes with error', async () => {
|
|
98
|
+
wrapper = mount(SyTextField, {
|
|
99
|
+
global: { plugins: [vuetify] },
|
|
100
|
+
props: { appendInnerIcon: 'error' as IconType },
|
|
101
|
+
})
|
|
102
|
+
expect(wrapper.props('appendInnerIcon')).toBe('error')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should update icon when validation state changes with success', async () => {
|
|
106
|
+
wrapper = mount(SyTextField, {
|
|
107
|
+
global: { plugins: [vuetify] },
|
|
108
|
+
props: { appendInnerIcon: 'success' as IconType },
|
|
109
|
+
})
|
|
110
|
+
expect(wrapper.props('appendInnerIcon')).toBe('success')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('emits prepend-icon-click event when prepend icon is clicked', async () => {
|
|
114
|
+
const wrapper = mount(SyTextField, {
|
|
115
|
+
global: { plugins: [vuetify] },
|
|
116
|
+
props: { prependIcon: 'info' as IconType },
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
await wrapper.vm.$nextTick()
|
|
120
|
+
const prependIcon = wrapper.findComponent(VIcon)
|
|
121
|
+
expect(prependIcon.exists()).toBe(true)
|
|
122
|
+
await prependIcon.trigger('click')
|
|
123
|
+
await wrapper.vm.$nextTick()
|
|
124
|
+
expect(wrapper.emitted('prepend-icon-click')).toBeTruthy()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('emits append-icon-click event when append icon is clicked', async () => {
|
|
128
|
+
const wrapper = mount(SyTextField, {
|
|
129
|
+
global: { plugins: [vuetify] },
|
|
130
|
+
props: { appendIcon: 'info' as IconType },
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await wrapper.vm.$nextTick()
|
|
134
|
+
const appendIcon = wrapper.findComponent(VIcon)
|
|
135
|
+
expect(appendIcon.exists()).toBe(true)
|
|
136
|
+
await appendIcon.trigger('click')
|
|
137
|
+
await wrapper.vm.$nextTick()
|
|
138
|
+
expect(wrapper.emitted('append-icon-click')).toBeTruthy()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('shows validation error message', async () => {
|
|
59
142
|
const wrapper = mount(SyTextField, {
|
|
143
|
+
global: { plugins: [vuetify] },
|
|
60
144
|
props: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
errorMessages: ['Test error message'],
|
|
145
|
+
required: true,
|
|
146
|
+
label: 'Test Field',
|
|
64
147
|
},
|
|
65
|
-
|
|
66
|
-
|
|
148
|
+
})
|
|
149
|
+
await wrapper.find('input').trigger('blur')
|
|
150
|
+
await wrapper.vm.$nextTick()
|
|
151
|
+
expect(wrapper.find('.v-messages').text()).toContain('Le champ Test Field est requis')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('validates field with custom rules', async () => {
|
|
155
|
+
const customRule = {
|
|
156
|
+
type: 'custom',
|
|
157
|
+
options: {
|
|
158
|
+
validate: (value: string) => value.length > 2,
|
|
159
|
+
message: 'Test error message',
|
|
67
160
|
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
wrapper = mount(SyTextField, {
|
|
164
|
+
global: { plugins: [vuetify] },
|
|
165
|
+
props: { customRules: [customRule] },
|
|
68
166
|
})
|
|
69
167
|
|
|
70
|
-
|
|
71
|
-
|
|
168
|
+
await wrapper.setProps({ modelValue: 'ab' })
|
|
169
|
+
await wrapper.find('input').trigger('blur')
|
|
170
|
+
await wrapper.vm.$nextTick()
|
|
72
171
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
expect(wrapper.vm.appendInnerIconColor).toBe('error')
|
|
172
|
+
const messages = wrapper.find('.v-messages')
|
|
173
|
+
expect(messages.text()).toContain('Test error message')
|
|
76
174
|
})
|
|
77
175
|
|
|
78
|
-
it('
|
|
79
|
-
const
|
|
80
|
-
|
|
176
|
+
it('validates field with custom warning rules', async () => {
|
|
177
|
+
const warningRule = {
|
|
178
|
+
type: 'custom',
|
|
179
|
+
options: {
|
|
180
|
+
validate: (value: string) => value.length <= 3,
|
|
181
|
+
message: 'Test warning message',
|
|
182
|
+
isWarning: true,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const wrapper = mount(SyTextField, {
|
|
187
|
+
global: { plugins: [vuetify] },
|
|
188
|
+
props: {
|
|
189
|
+
modelValue: 'test',
|
|
190
|
+
customWarningRules: [warningRule],
|
|
191
|
+
showSuccessMessages: true,
|
|
192
|
+
label: 'Test Field',
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
await wrapper.vm.$nextTick()
|
|
197
|
+
await wrapper.find('input').trigger('blur')
|
|
198
|
+
await wrapper.vm.$nextTick()
|
|
199
|
+
|
|
200
|
+
const messages = wrapper.find('.v-messages')
|
|
201
|
+
expect(messages.exists()).toBe(true)
|
|
202
|
+
expect(messages.text()).toContain('Attention : Test Field peut contenir une erreur')
|
|
81
203
|
})
|
|
82
204
|
|
|
83
|
-
it('
|
|
84
|
-
|
|
85
|
-
|
|
205
|
+
it('maintains input value without validation rules', async () => {
|
|
206
|
+
wrapper = mount(SyTextField, {
|
|
207
|
+
global: { plugins: [vuetify] },
|
|
208
|
+
})
|
|
209
|
+
const input = wrapper.find('input')
|
|
210
|
+
|
|
211
|
+
await input.setValue('test value')
|
|
212
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['test value'])
|
|
86
213
|
})
|
|
87
214
|
|
|
88
|
-
it('
|
|
89
|
-
const
|
|
90
|
-
|
|
215
|
+
it('validates field immediately when isValidateOnBlur is false', async () => {
|
|
216
|
+
const customRule = {
|
|
217
|
+
type: 'custom',
|
|
218
|
+
options: {
|
|
219
|
+
validate: (value: string) => value.length > 2,
|
|
220
|
+
message: 'Test error message',
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
wrapper = mount(SyTextField, {
|
|
225
|
+
global: { plugins: [vuetify] },
|
|
226
|
+
props: {
|
|
227
|
+
customRules: [customRule],
|
|
228
|
+
isValidateOnBlur: false,
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
await wrapper.setProps({ modelValue: 'ab' })
|
|
233
|
+
await wrapper.vm.$nextTick()
|
|
234
|
+
|
|
235
|
+
const messages = wrapper.find('.v-messages')
|
|
236
|
+
expect(messages.text()).toContain('Test error message')
|
|
237
|
+
|
|
238
|
+
// Vérifie que l'erreur disparaît quand la valeur devient valide
|
|
239
|
+
await wrapper.setProps({ modelValue: 'abc' })
|
|
240
|
+
await wrapper.vm.$nextTick()
|
|
241
|
+
|
|
242
|
+
expect(messages.text()).not.toContain('Test error message')
|
|
91
243
|
})
|
|
92
244
|
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type IconType = 'info' | 'success' | 'warning' | 'error' | 'close' | 'calendar' | undefined
|
|
2
2
|
export type VariantStyle = 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'underlined'
|
|
3
3
|
export type ColorType = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error'
|
|
4
|
+
export type ValidateOnType = 'eager' | 'lazy' | 'blur' | 'input' | 'submit' | 'invalid-input' | 'blur lazy' | 'input lazy' | 'submit lazy' | 'invalid-input lazy' | 'blur eager' | 'input eager' | 'submit eager' | 'invalid-input eager' | 'lazy blur' | 'lazy input' | 'lazy submit' | 'lazy invalid-input' | 'eager blur' | 'eager input' | 'eager submit' | 'eager invalid-input' | undefined
|