@cnamts/synapse 1.1.0 → 1.1.1

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.
Files changed (202) hide show
  1. package/dist/{AutocompleteFilter-DXd4szWO.js → AutocompleteFilter-CGF33skz.js} +1 -1
  2. package/dist/{DateFilter-BD59Kgwf.js → DateFilter-D7-MsKtx.js} +1 -1
  3. package/dist/{NumberFilter-BSMZE7uw.js → NumberFilter-bjQPPfsj.js} +1 -1
  4. package/dist/{PeriodFilter-keUdSSk0.js → PeriodFilter-B3wJpK8-.js} +1 -1
  5. package/dist/{SelectFilter-Dhvvwazl.js → SelectFilter-BN6DbKAV.js} +1 -1
  6. package/dist/{TextFilter-CU8FpXz0.js → TextFilter-BffP0J2f.js} +1 -1
  7. package/dist/{apLightTheme2026-DbS7BPUf.js → apLightTheme2026-C4ygwMHC.js} +11 -11
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +6 -6
  9. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +6 -6
  10. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +6 -6
  11. package/dist/components/Captcha/Captcha.d.ts +27 -16
  12. package/dist/components/Captcha/CaptchaForm.d.ts +29 -3
  13. package/dist/components/Captcha/types.d.ts +14 -0
  14. package/dist/components/Captcha/useCaptchaValidation.d.ts +37 -0
  15. package/dist/components/Customs/Selects/SelectBtnField/SelectBtnField.d.ts +33 -13
  16. package/dist/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.d.ts +23 -0
  17. package/dist/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.d.ts +2 -2
  18. package/dist/components/Customs/Selects/SySelect/composables/useSySelectValidation.d.ts +2 -2
  19. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +17 -48
  20. package/dist/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.d.ts +29 -0
  21. package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +46 -0
  22. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +16 -51
  23. package/dist/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.d.ts +27 -0
  24. package/dist/components/Customs/SyCheckbox/types.d.ts +49 -0
  25. package/dist/components/Customs/SyTextField/FieldState.d.ts +5 -0
  26. package/dist/components/Customs/SyTextField/useSyTextFieldValidation.d.ts +3 -3
  27. package/dist/components/DialogBox/DialogBox.d.ts +2 -0
  28. package/dist/components/DialogBox/locales.d.ts +1 -0
  29. package/dist/components/FilterSideBar/FilterSideBar.d.ts +4 -0
  30. package/dist/components/LunarCalendar/LunarCalendar.d.ts +43 -14
  31. package/dist/components/LunarCalendar/types.d.ts +35 -0
  32. package/dist/components/LunarCalendar/useLunarCalendarValidation.d.ts +11 -12
  33. package/dist/components/MonthPicker/MonthPicker.d.ts +72 -1747
  34. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +21 -1733
  35. package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +5 -0
  36. package/dist/components/MonthPicker/locales.d.ts +1 -0
  37. package/dist/components/MonthPicker/types.d.ts +11 -0
  38. package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +37 -24
  39. package/dist/components/NirField/NirField.d.ts +6 -4
  40. package/dist/components/NirField/useNirValidation.d.ts +7 -5
  41. package/dist/components/PageContainer/PageContainer.d.ts +8 -0
  42. package/dist/components/PasswordField/PasswordField.d.ts +2 -2
  43. package/dist/components/PasswordField/usePasswordFieldValidation.d.ts +2 -2
  44. package/dist/components/PhoneField/PhoneField.d.ts +960 -1938
  45. package/dist/components/PhoneField/indicatifs.d.ts +715 -8
  46. package/dist/components/PhoneField/locales.d.ts +7 -0
  47. package/dist/components/PhoneField/types.d.ts +29 -0
  48. package/dist/components/PhoneField/usePhoneFieldValidation.d.ts +45 -0
  49. package/dist/components/PhoneField/usePhoneIndicatifs.d.ts +947 -0
  50. package/dist/components/SyTextArea/composables/useSyTextAreaValidation.d.ts +2 -2
  51. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +1 -1
  52. package/dist/composables/unifyValidation/useValidation.d.ts +4 -5
  53. package/dist/design-system-v3.js +2 -2
  54. package/dist/designTokens/tokens/amelipro/apLightTheme.d.ts +10 -10
  55. package/dist/designTokens/tokens/baseTokens.d.ts +18 -18
  56. package/dist/designTokens/tokens/cnam/cnamLightTheme.d.ts +10 -10
  57. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +10 -10
  58. package/dist/designTokens/tokens/semanticTokens.d.ts +14 -14
  59. package/dist/{main-D8ryUoS5.js → main-C4wAktOs.js} +13718 -12991
  60. package/dist/synapse.css +1 -1
  61. package/dist/vuetifyConfig.js +1 -1
  62. package/package.json +7 -7
  63. package/src/assets/compat/_legacy-tokens.scss +91 -0
  64. package/src/assets/overrides/_utilities.scss +23 -0
  65. package/src/components/Accordion/Accordion.stories.ts +121 -1
  66. package/src/components/BackBtn/BackBtn.mdx +1 -1
  67. package/src/components/BackToTopBtn/BackToTopBtn.mdx +0 -1
  68. package/src/components/Captcha/Captcha.stories.ts +134 -31
  69. package/src/components/Captcha/Captcha.vue +95 -28
  70. package/src/components/Captcha/CaptchaForm.vue +51 -22
  71. package/src/components/Captcha/tests/Captcha.focus.spec.ts +214 -0
  72. package/src/components/Captcha/tests/Captcha.spec.ts +233 -24
  73. package/src/components/Captcha/tests/CaptchaForm.spec.ts +82 -0
  74. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +16 -42
  75. package/src/components/Captcha/types.ts +15 -0
  76. package/src/components/Captcha/useCaptchaValidation.ts +87 -0
  77. package/src/components/Captcha/validation/validation.stories.ts +1194 -0
  78. package/src/components/ChipList/ChipList.mdx +0 -1
  79. package/src/components/CollapsibleList/CollapsibleList.mdx +0 -1
  80. package/src/components/CookieBanner/CookieBanner.mdx +0 -1
  81. package/src/components/CopyBtn/CopyBtn.mdx +0 -1
  82. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.stories.ts +123 -439
  83. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +147 -41
  84. package/src/components/Customs/Selects/SelectBtnField/Validation/Validation.stories.ts +600 -0
  85. package/src/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.ts +87 -0
  86. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +402 -33
  87. package/src/components/Customs/Selects/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +52 -38
  88. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +342 -162
  89. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +77 -129
  90. package/src/components/Customs/SyCheckBoxGroup/Validation/Validation.stories.ts +1008 -0
  91. package/src/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.ts +107 -0
  92. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +180 -7
  93. package/src/components/Customs/SyCheckBoxGroup/types.ts +49 -0
  94. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +41 -161
  95. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +71 -148
  96. package/src/components/Customs/SyCheckbox/Validation/Validation.stories.ts +654 -0
  97. package/src/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.ts +105 -0
  98. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.spec.ts +106 -0
  99. package/src/components/Customs/SyCheckbox/tests/useSyCheckboxValidation.spec.ts +98 -0
  100. package/src/components/Customs/SyCheckbox/types.ts +51 -0
  101. package/src/components/Customs/SyTextField/FieldState.vue +50 -0
  102. package/src/components/Customs/SyTextField/SyTextField.vue +12 -9
  103. package/src/components/Customs/SyTextField/useSyTextFieldValidation.ts +2 -11
  104. package/src/components/DataList/DataList.mdx +0 -1
  105. package/src/components/DataListGroup/DataListGroup.mdx +0 -1
  106. package/src/components/DiacriticPicker/DiacriticPicker.mdx +0 -1
  107. package/src/components/DialogBox/DialogBox.mdx +0 -1
  108. package/src/components/DialogBox/DialogBox.stories.ts +399 -4
  109. package/src/components/DialogBox/DialogBox.vue +20 -0
  110. package/src/components/DialogBox/locales.ts +1 -0
  111. package/src/components/DialogBox/tests/DialogBox.spec.ts +73 -0
  112. package/src/components/DialogBox/tests/DialogBox.visual.cy.ts +24 -0
  113. package/src/components/ErrorPage/ErrorPage.mdx +1 -1
  114. package/src/components/ExternalLinks/ExternalLinks.mdx +0 -1
  115. package/src/components/FileList/FileList.mdx +0 -1
  116. package/src/components/FilterInline/FilterInline.mdx +0 -1
  117. package/src/components/FilterSideBar/FilterSideBar.mdx +8 -1
  118. package/src/components/FilterSideBar/FilterSideBar.stories.ts +133 -1
  119. package/src/components/FilterSideBar/FilterSideBar.vue +19 -2
  120. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +55 -0
  121. package/src/components/FooterBar/FooterBar.mdx +0 -1
  122. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +0 -1
  123. package/src/components/HeaderBar/HeaderBar.mdx +0 -1
  124. package/src/components/HeaderLoading/HeaderLoading.mdx +0 -1
  125. package/src/components/LangBtn/LangBtn.mdx +0 -1
  126. package/src/components/Logo/Logo.mdx +1 -1
  127. package/src/components/LunarCalendar/LunarCalendar.mdx +6 -9
  128. package/src/components/LunarCalendar/LunarCalendar.stories.ts +243 -46
  129. package/src/components/LunarCalendar/LunarCalendar.vue +61 -26
  130. package/src/components/LunarCalendar/Validation/Validation.stories.ts +717 -0
  131. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +1 -1
  132. package/src/components/LunarCalendar/tests/LunarCalendar.spec.ts +197 -6
  133. package/src/components/LunarCalendar/tests/useLunarCalendarValidation.spec.ts +287 -0
  134. package/src/components/LunarCalendar/types.ts +39 -0
  135. package/src/components/LunarCalendar/useLunarCalendarValidation.ts +115 -39
  136. package/src/components/MonthPicker/MonthPicker.stories.ts +38 -281
  137. package/src/components/MonthPicker/MonthPicker.vue +66 -17
  138. package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +44 -20
  139. package/src/components/MonthPicker/MonthPickerText/useTextField.ts +5 -0
  140. package/src/components/MonthPicker/Validation/Validation.stories.ts +1117 -0
  141. package/src/components/MonthPicker/locales.ts +1 -0
  142. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +353 -2
  143. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +12 -8
  144. package/src/components/MonthPicker/types.ts +16 -0
  145. package/src/components/MonthPicker/useMonthPickerValidation.ts +64 -27
  146. package/src/components/NirField/NirField.mdx +120 -66
  147. package/src/components/NirField/NirField.stories.ts +216 -0
  148. package/src/components/NirField/useNirValidation.ts +16 -17
  149. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +263 -245
  150. package/src/components/NotificationBar/NotificationBar.mdx +0 -1
  151. package/src/components/PageContainer/PageContainer.mdx +0 -1
  152. package/src/components/PageContainer/PageContainer.stories.ts +170 -2
  153. package/src/components/PageContainer/PageContainer.vue +63 -8
  154. package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +19 -11
  155. package/src/components/PaginatedTable/PaginatedTable.mdx +0 -1
  156. package/src/components/PeriodField/PeriodField.mdx +0 -1
  157. package/src/components/PhoneField/PhoneField.mdx +2 -3
  158. package/src/components/PhoneField/PhoneField.stories.ts +227 -410
  159. package/src/components/PhoneField/PhoneField.vue +204 -438
  160. package/src/components/PhoneField/indicatifs.ts +1 -1
  161. package/src/components/PhoneField/locales.ts +7 -0
  162. package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -1
  163. package/src/components/PhoneField/tests/PhoneField.spec.ts +517 -220
  164. package/src/components/PhoneField/types.ts +30 -0
  165. package/src/components/PhoneField/usePhoneFieldValidation.ts +119 -0
  166. package/src/components/PhoneField/usePhoneIndicatifs.ts +89 -0
  167. package/src/components/PhoneField/validation/validation.stories.ts +717 -0
  168. package/src/components/RangeField/RangeField.mdx +0 -1
  169. package/src/components/RatingPicker/RatingPicker.mdx +0 -1
  170. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +0 -1
  171. package/src/components/StatusPage/StatusPage.vue +1 -0
  172. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +248 -230
  173. package/src/components/SubHeader/SubHeader.mdx +5 -6
  174. package/src/components/Tables/common/tests/SyTableFilter.spec.ts +11 -12
  175. package/src/components/UploadWorkflow/UploadWorkflow.mdx +0 -1
  176. package/src/components/UserMenuBtn/UserMenuBtn.mdx +0 -1
  177. package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +177 -0
  178. package/src/composables/unifyValidation/documentationValidationProps.ts +1 -1
  179. package/src/composables/unifyValidation/tests/useValidation.spec.ts +13 -1
  180. package/src/composables/unifyValidation/useValidation.ts +37 -33
  181. package/src/composantsVuetify/VCard/VCard.mdx +4 -0
  182. package/src/composantsVuetify/VCard/v-card.stories.ts +93 -1
  183. package/src/composantsVuetify/VCarousel/VCarousel.mdx +74 -0
  184. package/src/composantsVuetify/VCarousel/v-carousel.stories.ts +531 -0
  185. package/src/composantsVuetify/VNavigationDrawer/VNavgationDrawer.mdx +53 -0
  186. package/src/composantsVuetify/VNavigationDrawer/v-navigation-drawer.stories.ts +310 -0
  187. package/src/composantsVuetify/VSlideGroup/VSlideGroup.mdx +105 -0
  188. package/src/composantsVuetify/VSlideGroup/v-slide-group.stories.ts +463 -0
  189. package/src/designTokens/tokens/baseColors.ts +1 -1
  190. package/src/designTokens/tokens/baseTokens.ts +18 -18
  191. package/src/stories/Components/Components.stories.ts +34 -1
  192. package/src/stories/Demarrer/Releases.stories.ts +16 -2
  193. package/src/stories/DesignTokens/Arrondis.mdx +1 -1
  194. package/src/stories/DesignTokens/Correspondances.mdx +219 -0
  195. package/src/stories/DesignTokens/UtiliserLesTokens.mdx +235 -0
  196. package/src/stories/DesignTokens/colors.stories.ts +569 -569
  197. package/src/stories/GuideDuDev/Amelipro.stories.ts +335 -267
  198. package/dist/components/LunarCalendar/useLunarCalendarRules.d.ts +0 -5
  199. package/dist/components/PhoneField/tests/types.d.ts +0 -18
  200. package/src/components/LunarCalendar/tests/useLunarCalendarRules.spec.ts +0 -184
  201. package/src/components/LunarCalendar/useLunarCalendarRules.ts +0 -96
  202. package/src/components/PhoneField/tests/types.d.ts +0 -19
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { mdiCached, mdiImageOutline, mdiPause } from '@mdi/js'
3
- import { ref, watch } from 'vue'
3
+ import { computed, ref, toRef, watch } from 'vue'
4
4
  import CaptchaAlert from './CaptchaAlert.vue'
5
5
  import CaptchaBase from './CaptchaBase.vue'
6
6
  import CaptchaBtn from './CaptchaBtn.vue'
@@ -10,28 +10,20 @@
10
10
  import CaptchaInformation from './CaptchaInformation.vue'
11
11
  import volumeUp from './icons/volumeUp.vue'
12
12
  import { locales as defaultLocales } from './locales'
13
- import { type CaptchaType, type StateType } from './types'
13
+ import { type CaptchaProps, type CaptchaType, type StateType } from './types'
14
+ import { useCaptchaValidation } from './useCaptchaValidation'
14
15
  import SyIcon from '@/components/Customs/SyIcon/SyIcon.vue'
16
+ import { validationPropsDefaults } from '@/composables/unifyValidation/useValidation.ts'
15
17
 
16
- const props = withDefaults(defineProps<{
17
- urlCreate: string
18
- urlGetImage: string
19
- urlGetAudio: string
20
- modelValue?: string | undefined
21
- errorMessage?: string
22
- type?: CaptchaType
23
- tagTitle?: string
24
- helpDesk?: string | false
25
- locale?: string
26
- locales?: typeof defaultLocales
27
- }>(), {
18
+ const props = withDefaults(defineProps<CaptchaProps>(), {
28
19
  modelValue: undefined,
29
- errorMessage: undefined,
30
20
  type: 'image',
31
21
  helpDesk: '3648',
32
22
  tagTitle: 'h3',
23
+ isClearable: false,
33
24
  locale: navigator.language,
34
25
  locales: () => defaultLocales,
26
+ ...validationPropsDefaults,
35
27
  })
36
28
 
37
29
  const emit = defineEmits<{
@@ -41,19 +33,19 @@
41
33
  (e: 'audioError'): void
42
34
  (e: 'creationError'): void
43
35
  }>()
44
- const text = ref<string | null>(null)
36
+ const text = ref<string | null>(props.modelValue ?? null)
45
37
  const type = ref<CaptchaType>(props.type)
46
38
  const id = ref<string | null>(null)
47
39
  const state = ref<StateType>('idle')
48
- const captchaValid = ref<boolean>(false)
49
40
 
50
41
  watch(() => props.modelValue, (val) => {
51
42
  text.value = val ?? null
52
- }, { immediate: true })
43
+ })
53
44
 
54
45
  watch(() => props.type, (val) => {
55
46
  type.value = val
56
- }, { immediate: true })
47
+ clearValidation()
48
+ })
57
49
 
58
50
  watch(text, (val) => {
59
51
  if (val !== props.modelValue) {
@@ -61,12 +53,21 @@
61
53
  }
62
54
  })
63
55
 
56
+ let firstLoading = true
64
57
  function createCaptchaInit() {
58
+ if (firstLoading) {
59
+ return
60
+ }
65
61
  text.value = null
66
62
  }
67
63
 
68
64
  function createCaptchaSuccess(captchaId: string | null) {
69
65
  id.value = captchaId
66
+ if (firstLoading) {
67
+ firstLoading = false
68
+ return
69
+ }
70
+ clearValidation()
70
71
  }
71
72
 
72
73
  function emitChangeValueEvent(val: string) {
@@ -74,6 +75,7 @@
74
75
  }
75
76
 
76
77
  function emitChangeTypeEvent() {
78
+ clearValidation()
77
79
  emit('update:type', type.value)
78
80
  }
79
81
 
@@ -86,6 +88,54 @@
86
88
  }
87
89
  }
88
90
 
91
+ const focused = ref(false)
92
+
93
+ const { validate, clearValidation, errors, warnings, successes, hasError, hasWarning, hasSuccess } = useCaptchaValidation({
94
+ modelValue: text,
95
+ readonly: toRef(props, 'readonly'),
96
+ disabled: toRef(props, 'disabled'),
97
+ required: toRef(props, 'required'),
98
+ isValidateOnBlur: toRef(props, 'isValidateOnBlur'),
99
+ showSuccessMessages: toRef(props, 'showSuccessMessages'),
100
+ disableErrorHandling: toRef(props, 'disableErrorHandling'),
101
+ useVuetifyValidation: toRef(props, 'useVuetifyValidation'),
102
+ label: computed(() => type.value === 'image' ? props.locales.image.textfieldLabel : props.locales.audio.textfieldLabel),
103
+ rules: toRef(props, 'rules'),
104
+ customRules: toRef(props, 'customRules'),
105
+ customWarningRules: toRef(props, 'customWarningRules'),
106
+ customSuccessRules: toRef(props, 'customSuccessRules'),
107
+ errorMessages: toRef(props, 'errorMessages'),
108
+ warningMessages: toRef(props, 'warningMessages'),
109
+ successMessages: toRef(props, 'successMessages'),
110
+ hasErrorProp: toRef(props, 'hasError'),
111
+ hasWarningProp: toRef(props, 'hasWarning'),
112
+ hasSuccessProp: toRef(props, 'hasSuccess'),
113
+ maxErrors: toRef(props, 'maxErrors'),
114
+ focused,
115
+ locales: toRef(props, 'locales'),
116
+ })
117
+
118
+ function onFocus() {
119
+ focused.value = true
120
+ }
121
+
122
+ function onBlur() {
123
+ focused.value = false
124
+ }
125
+
126
+ function reset() {
127
+ text.value = null
128
+ state.value = 'idle'
129
+ clearValidation()
130
+ }
131
+
132
+ defineExpose({
133
+ validateOnSubmit: validate,
134
+ clearValidation,
135
+ reset,
136
+ captchaId: id,
137
+ })
138
+
89
139
  </script>
90
140
 
91
141
  <template>
@@ -141,10 +191,20 @@
141
191
  :locales
142
192
  :label="locales.image.textfieldLabel"
143
193
  :state="createCaptchaState"
144
- :errors="props.errorMessage ? [props.errorMessage] : []"
145
194
  :loading="state === 'pending'"
146
- :success="captchaValid"
195
+ :error-messages="errors"
196
+ :warning-messages="warnings"
197
+ :success-messages="successes"
198
+ :has-error="hasError"
199
+ :has-warning="hasWarning"
200
+ :has-success="hasSuccess"
201
+ :show-success-messages="props.showSuccessMessages"
202
+ :required="props.required"
203
+ :max-errors="props.maxErrors"
204
+ :is-clearable="props.isClearable"
147
205
  @update:model-value="emitChangeValueEvent"
206
+ @focus="onFocus"
207
+ @blur="onBlur"
148
208
  />
149
209
 
150
210
  <div class="captcha-config pt-4 d-flex flex-column ga-2 align-start">
@@ -233,12 +293,22 @@
233
293
  <CaptchaForm
234
294
  v-model="text"
235
295
  :locales
236
- :label="locales.audio.textfieldLabel"
296
+ :label="locales.image.textfieldLabel"
237
297
  :state="createCaptchaState"
238
298
  :loading="state === 'pending'"
239
- :errors="props.errorMessage ? [props.errorMessage] : []"
240
- :success="captchaValid"
299
+ :error-messages="errors"
300
+ :warning-messages="warnings"
301
+ :success-messages="successes"
302
+ :has-error="hasError"
303
+ :has-warning="hasWarning"
304
+ :has-success="hasSuccess"
305
+ :show-success-messages="props.showSuccessMessages"
306
+ :required="props.required"
307
+ :max-errors="props.maxErrors"
308
+ :is-clearable="props.isClearable"
241
309
  @update:model-value="emitChangeValueEvent"
310
+ @focus="onFocus"
311
+ @blur="onBlur"
242
312
  />
243
313
  <div class="captcha-config pt-4 d-flex flex-column ga-2 align-start">
244
314
  <p class="label-options text-textSubdued">
@@ -282,9 +352,7 @@
282
352
  >
283
353
  {{ locales.choiceCaptcha.image }}
284
354
  </CaptchaBtn>
285
- <CaptchaBtn
286
- @click="chooseAudio"
287
- >
355
+ <CaptchaBtn @click="chooseAudio">
288
356
  <volume-up
289
357
  fill="#0C419A"
290
358
  aria-hidden="true"
@@ -311,5 +379,4 @@
311
379
  gap: var(--v-gap-2);
312
380
  letter-spacing: 0%;
313
381
  }
314
-
315
382
  </style>
@@ -8,23 +8,53 @@
8
8
  modelValue?: string | null
9
9
  state?: string | null
10
10
  loading?: boolean
11
- errors: string[] | undefined
12
- success: boolean
13
11
  locales: typeof defaultLocales
12
+ success?: boolean // This prop is used to determine if the captcha validation was successful, and to set the input to readonly if true
13
+ errorMessages?: string[]
14
+ warningMessages?: string[]
15
+ successMessages?: string[]
16
+ hasError?: boolean
17
+ hasWarning?: boolean
18
+ hasSuccess?: boolean
19
+ showSuccessMessages?: boolean
20
+ required?: boolean
21
+ maxErrors?: number
22
+ isClearable?: boolean
14
23
  }
15
24
 
16
- const props = defineProps<Props>()
17
- const emit = defineEmits(['update:modelValue'])
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ modelValue: null,
27
+ state: null,
28
+ loading: false,
29
+ errorMessages: undefined,
30
+ warningMessages: undefined,
31
+ successMessages: undefined,
32
+ hasError: false,
33
+ hasWarning: false,
34
+ hasSuccess: false,
35
+ showSuccessMessages: false,
36
+ required: false,
37
+ maxErrors: undefined,
38
+ })
39
+ const emit = defineEmits(['update:modelValue', 'focus', 'blur'])
18
40
 
19
41
  const text = ref<string | null>(props.modelValue ?? null)
20
42
 
21
43
  watch(() => props.modelValue, (newVal) => {
22
44
  text.value = newVal ?? null
23
- }, { immediate: true })
45
+ })
24
46
 
25
47
  function emitChangeEvent() {
26
48
  emit('update:modelValue', text.value)
27
49
  }
50
+
51
+ function emitFocusEvent() {
52
+ emit('focus')
53
+ }
54
+
55
+ function emitBlurEvent() {
56
+ emit('blur')
57
+ }
28
58
  </script>
29
59
 
30
60
  <template>
@@ -33,26 +63,25 @@
33
63
  v-model="text"
34
64
  class="mt-4"
35
65
  variant="outlined"
36
- :error-messages="props.errors"
37
- :custom-rules="[
38
- {
39
- type: 'required',
40
- options: { message: locales.required },
41
- }
42
- ]"
43
- :show-success-messages="false"
44
- :has-success="success"
66
+ :error-messages="props.errorMessages ?? []"
67
+ :warning-messages="props.warningMessages ?? []"
68
+ :success-messages="props.successMessages ?? []"
69
+ :has-error="props.hasError"
70
+ :has-warning="props.hasWarning"
71
+ :has-success="props.hasSuccess"
72
+ disable-error-handling
73
+ :show-success-messages="props.showSuccessMessages"
74
+ :required="props.required"
75
+ :max-errors="props.maxErrors"
45
76
  :disabled="state === 'rejected'"
46
77
  :label="label"
47
- hide-details="auto"
48
- :readonly="success"
49
- :is-clearable="!success"
78
+ :hide-details="!props.hasError && !props.hasWarning && !props.hasSuccess"
79
+ :readonly="props.success"
80
+ :is-clearable="props.isClearable && !props.success"
81
+ :aria-required="required"
50
82
  @update:model-value="emitChangeEvent"
83
+ @focus="emitFocusEvent"
84
+ @blur="emitBlurEvent"
51
85
  />
52
86
  </VSheet>
53
87
  </template>
54
- <style lang="scss" scoped>
55
- :deep(.v-input__control:not(:has(.v-field--error))+.v-input__details) {
56
- display: none;
57
- }
58
- </style>
@@ -0,0 +1,214 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { defineComponent } from 'vue'
3
+ import { mount } from '@vue/test-utils'
4
+ import Captcha from '../Captcha.vue'
5
+
6
+ const CaptchaBaseStub = {
7
+ name: 'CaptchaBase',
8
+ emits: ['update:modelValue', 'create-captcha:init', 'create-captcha:success', 'create-captcha:error'],
9
+ template: `
10
+ <div>
11
+ <button class="change-type" @click="$emit('update:modelValue', 'audio')" />
12
+ <button class="refresh-captcha" @click="$emit('create-captcha:init')" />
13
+ <button class="captcha-success" @click="$emit('create-captcha:success', 'captcha-id-1')" />
14
+ <slot
15
+ name="image"
16
+ :choose-image="() => {}"
17
+ :choose-audio="() => {}"
18
+ :url="''"
19
+ :state="'idle'"
20
+ :is-error="false"
21
+ :error-message="null"
22
+ />
23
+ </div>
24
+ `,
25
+ }
26
+
27
+ const CaptchaFormStub = {
28
+ name: 'CaptchaForm',
29
+ emits: ['update:modelValue'],
30
+ template: '<div class="captcha-form-stub" />',
31
+ }
32
+
33
+ const CaptchaFormWithFocusStub = defineComponent({
34
+ name: 'CaptchaForm',
35
+ props: {
36
+ errorMessages: { type: Array, default: () => [] },
37
+ },
38
+ emits: ['update:modelValue', 'focus', 'blur'],
39
+ template: '<div class="captcha-form-stub" />',
40
+ })
41
+
42
+ describe('Captcha behavior', () => {
43
+ it('emits update:type when captcha type changes from CaptchaBase', async () => {
44
+ const wrapper = mount(Captcha, {
45
+ props: {
46
+ urlCreate: '/captcha/captcha.json',
47
+ urlGetImage: '/captcha/captcha.png',
48
+ urlGetAudio: '/captcha/captcha.mp3',
49
+ type: 'image',
50
+ },
51
+ global: {
52
+ stubs: {
53
+ CaptchaInformation: true,
54
+ CaptchaBase: CaptchaBaseStub,
55
+ CaptchaImg: true,
56
+ CaptchaAlert: true,
57
+ CaptchaBtn: true,
58
+ CaptchaHelpdesk: true,
59
+ CaptchaForm: CaptchaFormStub,
60
+ volumeUp: true,
61
+ SyIcon: true,
62
+ VBtn: true,
63
+ },
64
+ },
65
+ })
66
+
67
+ await wrapper.find('.change-type').trigger('click')
68
+
69
+ expect(wrapper.emitted('update:type')).toBeTruthy()
70
+ expect(wrapper.emitted('update:type')?.[0]).toEqual(['audio'])
71
+ })
72
+
73
+ it('clears captcha text when a new captcha is initialized', async () => {
74
+ const wrapper = mount(Captcha, {
75
+ props: {
76
+ urlCreate: '/captcha/captcha.json',
77
+ urlGetImage: '/captcha/captcha.png',
78
+ urlGetAudio: '/captcha/captcha.mp3',
79
+ },
80
+ global: {
81
+ stubs: {
82
+ CaptchaInformation: true,
83
+ CaptchaBase: CaptchaBaseStub,
84
+ CaptchaImg: true,
85
+ CaptchaAlert: true,
86
+ CaptchaBtn: true,
87
+ CaptchaHelpdesk: true,
88
+ CaptchaForm: CaptchaFormStub,
89
+ volumeUp: true,
90
+ SyIcon: true,
91
+ VBtn: true,
92
+ },
93
+ },
94
+ })
95
+
96
+ // Complete the initial captcha load so subsequent refreshes work
97
+ await wrapper.find('.captcha-success').trigger('click')
98
+
99
+ const form = wrapper.findComponent(CaptchaFormStub)
100
+ await form.vm.$emit('update:modelValue', 'captcha-text')
101
+ await wrapper.find('.refresh-captcha').trigger('click')
102
+
103
+ const modelEvents = wrapper.emitted('update:modelValue') || []
104
+ expect(modelEvents.length).toBeGreaterThanOrEqual(2)
105
+ expect(modelEvents[0]).toEqual(['captcha-text'])
106
+ expect(modelEvents[modelEvents.length - 1]).toEqual([''])
107
+ })
108
+
109
+ it('does not clear validation errors when the first captcha loads after a blur', async () => {
110
+ const commonStubs = {
111
+ CaptchaInformation: true,
112
+ CaptchaBase: CaptchaBaseStub,
113
+ CaptchaImg: true,
114
+ CaptchaAlert: true,
115
+ CaptchaBtn: true,
116
+ CaptchaHelpdesk: true,
117
+ CaptchaForm: CaptchaFormWithFocusStub,
118
+ volumeUp: true,
119
+ SyIcon: true,
120
+ VBtn: true,
121
+ }
122
+
123
+ const wrapper = mount(Captcha, {
124
+ props: {
125
+ urlCreate: '/captcha/captcha.json',
126
+ urlGetImage: '/captcha/captcha.png',
127
+ urlGetAudio: '/captcha/captcha.mp3',
128
+ customRules: [
129
+ {
130
+ type: 'custom' as const,
131
+ options: {
132
+ validate: (v: unknown) => v ? true : 'Le captcha est requis',
133
+ },
134
+ },
135
+ ],
136
+ },
137
+ global: { stubs: commonStubs },
138
+ })
139
+
140
+ // Simulate: user focuses and blurs without entering a value (triggers validation)
141
+ const form = wrapper.findComponent(CaptchaFormWithFocusStub)
142
+ await form.vm.$emit('focus')
143
+ await form.vm.$emit('blur')
144
+
145
+ // Wait for async validation to complete
146
+ await wrapper.vm.$nextTick()
147
+ await wrapper.vm.$nextTick()
148
+
149
+ // Errors should now be set
150
+ const errorsBeforeSuccess = form.props('errorMessages') as string[]
151
+ expect(errorsBeforeSuccess).toContain('Le captcha est requis')
152
+
153
+ // Simulate: first captcha load completes (race condition – used to clear validation)
154
+ await wrapper.find('.captcha-success').trigger('click')
155
+ await wrapper.vm.$nextTick()
156
+
157
+ // Errors must still be present – first create-captcha:success must NOT clear validation
158
+ const errorsAfterFirstSuccess = form.props('errorMessages') as string[]
159
+ expect(errorsAfterFirstSuccess).toContain('Le captcha est requis')
160
+ })
161
+
162
+ it('clears validation errors when the captcha is refreshed after a blur', async () => {
163
+ const commonStubs = {
164
+ CaptchaInformation: true,
165
+ CaptchaBase: CaptchaBaseStub,
166
+ CaptchaImg: true,
167
+ CaptchaAlert: true,
168
+ CaptchaBtn: true,
169
+ CaptchaHelpdesk: true,
170
+ CaptchaForm: CaptchaFormWithFocusStub,
171
+ volumeUp: true,
172
+ SyIcon: true,
173
+ VBtn: true,
174
+ }
175
+
176
+ const wrapper = mount(Captcha, {
177
+ props: {
178
+ urlCreate: '/captcha/captcha.json',
179
+ urlGetImage: '/captcha/captcha.png',
180
+ urlGetAudio: '/captcha/captcha.mp3',
181
+ customRules: [
182
+ {
183
+ type: 'custom' as const,
184
+ options: {
185
+ validate: (v: unknown) => v ? true : 'Le captcha est requis',
186
+ },
187
+ },
188
+ ],
189
+ },
190
+ global: { stubs: commonStubs },
191
+ })
192
+
193
+ // Complete the initial load first
194
+ await wrapper.find('.captcha-success').trigger('click')
195
+ await wrapper.vm.$nextTick()
196
+
197
+ // User blurs without a value → error shown
198
+ const form = wrapper.findComponent(CaptchaFormWithFocusStub)
199
+ await form.vm.$emit('focus')
200
+ await form.vm.$emit('blur')
201
+ await wrapper.vm.$nextTick()
202
+ await wrapper.vm.$nextTick()
203
+
204
+ expect(form.props('errorMessages') as string[]).toContain('Le captcha est requis')
205
+
206
+ // User asks for a new captcha (init + success)
207
+ await wrapper.find('.refresh-captcha').trigger('click')
208
+ await wrapper.find('.captcha-success').trigger('click')
209
+ await wrapper.vm.$nextTick()
210
+
211
+ // Errors should be cleared now (this is the intended behaviour for a refresh)
212
+ expect(form.props('errorMessages') as string[]).toEqual([])
213
+ })
214
+ })