@cnamts/synapse 1.0.12 → 1.0.13

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 (124) hide show
  1. package/dist/{DateFilter-DoCcOfDW.js → DateFilter-_EFzsvvM.js} +1 -1
  2. package/dist/{NumberFilter-9uR8uo6p.js → NumberFilter-CUxEbKJh.js} +1 -1
  3. package/dist/{PeriodFilter-CxN5ini7.js → PeriodFilter-D5ueqtKy.js} +1 -1
  4. package/dist/{SelectFilter-bfxipgvt.js → SelectFilter-BciBNydy.js} +1 -1
  5. package/dist/{TextFilter-yCnWcmW2.js → TextFilter-DMN_WAQB.js} +1 -1
  6. package/dist/components/Amelipro/AmeliproAccordion/AmeliproAccordion.d.ts +1 -1
  7. package/dist/components/Amelipro/AmeliproAccordion/AmeliproAccordionTemplate/AmeliproAccordionTemplate.d.ts +1 -1
  8. package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.d.ts +1 -1
  9. package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.d.ts +1 -1
  10. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +44 -62
  11. package/dist/components/Amelipro/AmeliproCard/AmeliproCard.d.ts +1 -1
  12. package/dist/components/Amelipro/AmeliproIcon/AmeliproIcon.d.ts +1 -1
  13. package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +5 -5
  14. package/dist/components/Amelipro/AmeliproMultipleFoldingCard/AmeliproMultipleFoldingCard.d.ts +1 -1
  15. package/dist/components/Amelipro/AmeliproNumberedCard/AmeliproNumberedCard.d.ts +1 -1
  16. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.d.ts +24 -32
  17. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +36 -48
  18. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +44 -62
  19. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +44 -62
  20. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +0 -4
  21. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +12 -16
  22. package/dist/components/Captcha/Captcha.d.ts +68 -0
  23. package/dist/components/Captcha/CaptchaAlert.d.ts +13 -0
  24. package/dist/components/Captcha/CaptchaBase.d.ts +55 -0
  25. package/dist/components/Captcha/CaptchaBtn.d.ts +12 -0
  26. package/dist/components/Captcha/CaptchaForm.d.ts +16 -0
  27. package/dist/components/Captcha/CaptchaImg.d.ts +12 -0
  28. package/dist/components/Captcha/CaptchaInformation.d.ts +20 -0
  29. package/dist/components/Captcha/captchaApi.d.ts +41 -0
  30. package/dist/components/Captcha/icons/volumeUp.d.ts +2 -0
  31. package/dist/components/Captcha/locales.d.ts +35 -0
  32. package/dist/components/Captcha/types.d.ts +2 -0
  33. package/dist/components/ChipList/ChipList.d.ts +2 -2
  34. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +2 -2
  35. package/dist/components/Customs/SyForm/SyForm.d.ts +6 -3
  36. package/dist/components/Customs/SyTextField/SyTextField.d.ts +13 -17
  37. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +56 -64
  38. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +47 -64
  39. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +18 -17
  40. package/dist/components/DatePicker/tests/setup.d.ts +448 -512
  41. package/dist/components/HeaderToolbar/HeaderToolbar.d.ts +4 -4
  42. package/dist/components/NirField/NirField.d.ts +29 -34
  43. package/dist/components/NirField/locales.d.ts +1 -3
  44. package/dist/components/PasswordField/PasswordField.d.ts +2 -0
  45. package/dist/components/PeriodField/PeriodField.d.ts +112 -128
  46. package/dist/components/PhoneField/PhoneField.d.ts +13 -17
  47. package/dist/components/SearchListField/SearchListField.d.ts +2 -2
  48. package/dist/components/SyTextArea/SyTextArea.d.ts +0 -4
  49. package/dist/components/Tables/common/SyTablePagination.d.ts +2 -2
  50. package/dist/components/index.d.ts +1 -0
  51. package/dist/composables/validation/useFormValidation.d.ts +10 -0
  52. package/dist/composables/validation/useValidatable.d.ts +10 -2
  53. package/dist/design-system-v3.js +126 -125
  54. package/dist/design-system-v3.umd.cjs +155 -155
  55. package/dist/main-DISHlqcd.js +34217 -0
  56. package/dist/style.css +1 -1
  57. package/package.json +1 -1
  58. package/src/components/Amelipro/AmeliproFooter/AmeliproFooter.vue +6 -7
  59. package/src/components/Amelipro/AmeliproFooter/__tests__/AmeliproFooter.spec.ts +787 -0
  60. package/src/components/Amelipro/AmeliproFooter/__tests__/__snapshots__/AmeliproFooter.spec.ts.snap +318 -0
  61. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/__tests__/AmeliproHeaderBrandSection.spec.ts +167 -0
  62. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/__tests__/__snapshots__/AmeliproHeaderBrandSection.spec.ts.snap +100 -0
  63. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/__tests__/AmeliproHeaderBar.spec.ts +312 -0
  64. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/__tests__/__snapshots__/AmeliproHeaderBar.spec.ts.snap +98 -0
  65. package/src/components/Amelipro/AmeliproHeader/__tests__/AmeliproHeader.spec.ts +361 -0
  66. package/src/components/Amelipro/AmeliproHeader/__tests__/__snapshots__/AmeliproHeader.spec.ts.snap +22 -0
  67. package/src/components/Amelipro/AmeliproMenu/__tests__/AmeliproMenu.spec.ts +168 -0
  68. package/src/components/Amelipro/AmeliproMenu/__tests__/__snapshots__/AmeliproMenu.spec.ts.snap +295 -0
  69. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/AmeliproDropdownMenuBtn/__tests__/AmeliproDropdownMenuBtn.spec.ts +128 -0
  70. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/AmeliproDropdownMenuBtn/__tests__/__snapshots__/AmeliproDropdownMenuBtn.spec.ts.snap +67 -0
  71. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/__tests__/AmeliproDropdownMenu.spec.ts +266 -0
  72. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/__tests__/__snapshots__/AmeliproDropdownMenu.spec.ts.snap +134 -0
  73. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproMessagingMenuBtn/__tests__/AmeliproMessagingMenuBtn.spec.ts +72 -0
  74. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproMessagingMenuBtn/__tests__/__snapshots__/AmeliproMessagingMenuBtn.spec.ts.snap +71 -0
  75. package/src/components/Amelipro/AmeliproPageLayout/tests/__snapshots__/AmeliproPageLayout.spec.ts.snap +12 -0
  76. package/src/components/Captcha/Captcha.mdx +72 -0
  77. package/src/components/Captcha/Captcha.stories.ts +276 -0
  78. package/src/components/Captcha/Captcha.vue +325 -0
  79. package/src/components/Captcha/CaptchaAlert.vue +60 -0
  80. package/src/components/Captcha/CaptchaBase.vue +219 -0
  81. package/src/components/Captcha/CaptchaBtn.vue +35 -0
  82. package/src/components/Captcha/CaptchaForm.vue +58 -0
  83. package/src/components/Captcha/CaptchaImg.vue +41 -0
  84. package/src/components/Captcha/CaptchaInformation.vue +64 -0
  85. package/src/components/Captcha/captchaApi.ts +111 -0
  86. package/src/components/Captcha/icons/volumeUp.vue +11 -0
  87. package/src/components/Captcha/locales.ts +35 -0
  88. package/src/components/Captcha/readme.md +5 -0
  89. package/src/components/Captcha/tests/Captcha.spec.ts +298 -0
  90. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +716 -0
  91. package/src/components/Captcha/types.ts +2 -0
  92. package/src/components/Customs/Selects/SySelect/SySelect.vue +2 -2
  93. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +4 -0
  94. package/src/components/Customs/SyForm/SyForm.stories.ts +133 -23
  95. package/src/components/Customs/SyForm/SyForm.vue +17 -1
  96. package/src/components/Customs/SyTextField/SyTextField.vue +2 -2
  97. package/src/components/DatePicker/CalendarMode/DatePicker.vue +1 -1
  98. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +110 -6
  99. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +28 -3
  100. package/src/components/NirField/NirField.stories.ts +74 -0
  101. package/src/components/NirField/NirField.vue +34 -9
  102. package/src/components/NirField/locales.ts +1 -3
  103. package/src/components/PasswordField/PasswordField.vue +39 -7
  104. package/src/components/PhoneField/PhoneField.vue +43 -10
  105. package/src/components/index.ts +1 -0
  106. package/src/composables/validation/useFormValidation.ts +46 -8
  107. package/src/composables/validation/useValidatable.ts +19 -8
  108. package/dist/main-DMXtXK3y.js +0 -33458
  109. package/src/components/Amelipro/AmeliproFooter/tests/AmeliproFooter.spec.ts +0 -15
  110. package/src/components/Amelipro/AmeliproFooter/tests/__snapshots__/AmeliproFooter.spec.ts.snap +0 -432
  111. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/tests/AmeliproHeaderBrandSection.spec.ts +0 -15
  112. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/tests/__snapshots__/AmeliproHeaderBrandSection.spec.ts.snap +0 -131
  113. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/tests/AmeliproHeaderBar.spec.ts +0 -15
  114. package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/tests/__snapshots__/AmeliproHeaderBar.spec.ts.snap +0 -172
  115. package/src/components/Amelipro/AmeliproHeader/tests/AmeliproHeader.spec.ts +0 -159
  116. package/src/components/Amelipro/AmeliproHeader/tests/__snapshots__/AmeliproHeader.spec.ts.snap +0 -841
  117. package/src/components/Amelipro/AmeliproMenu/tests/AmeliproMenu.spec.ts +0 -85
  118. package/src/components/Amelipro/AmeliproMenu/tests/__snapshots__/AmeliproMenu.spec.ts.snap +0 -537
  119. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/AmeliproDropdownMenuBtn/tests/AmeliproDropdownMenuBtn.spec.ts +0 -16
  120. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/AmeliproDropdownMenuBtn/tests/__snapshots__/AmeliproDropdownMenuBtn.spec.ts.snap +0 -56
  121. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/tests/AmeliproDropdownMenu.spec.ts +0 -28
  122. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproDropdownMenu/tests/__snapshots__/AmeliproDropdownMenu.spec.ts.snap +0 -300
  123. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproMessagingMenuBtn/tests/AmeliproMessagingMenuBtn.spec.ts +0 -16
  124. package/src/components/Amelipro/AmeliproMessagingLayout/AmeliproMessagingMenuBtn/tests/__snapshots__/AmeliproMessagingMenuBtn.spec.ts.snap +0 -89
@@ -3,7 +3,7 @@
3
3
  defineOptions({
4
4
  inheritAttrs: false,
5
5
  })
6
- import { mdiInformation, mdiMenuDown, mdiCloseCircle } from '@mdi/js'
6
+ import { mdiInformation, mdiChevronDown, mdiCloseCircle } from '@mdi/js'
7
7
  import { ref, watch, onMounted, computed, nextTick, type PropType } from 'vue'
8
8
  import { useSySelectKeyboard } from './composables/useSySelectKeyboard'
9
9
  import { vRgaaSvgFix } from '../../../../directives/rgaaSvgFix'
@@ -839,7 +839,7 @@
839
839
  </button>
840
840
  <SyIcon
841
841
  class="arrow"
842
- :icon="mdiMenuDown"
842
+ :icon="mdiChevronDown"
843
843
  :decorative="true"
844
844
  />
845
845
  </template>
@@ -364,6 +364,10 @@
364
364
  cursor: pointer;
365
365
  }
366
366
 
367
+ :deep(.v-label) {
368
+ margin-left: 8px;
369
+ }
370
+
367
371
  :deep(.v-selection-control--disabled .v-selection-control__input) {
368
372
  cursor: not-allowed;
369
373
  }
@@ -33,7 +33,7 @@ export const Basic: Story = {
33
33
  setup() {
34
34
  const name = ref('')
35
35
  const email = ref('')
36
- const form = ref<{ validate: () => Promise<boolean> } | null>(null)
36
+ const form = ref<{ validate: () => Promise<boolean>, reset: () => void, clearValidation: () => void } | null>(null)
37
37
 
38
38
  // Règles de validation selon le design system
39
39
  const emailRules = [
@@ -56,9 +56,11 @@ export const Basic: Story = {
56
56
  template: `
57
57
  <SyForm ref="form" v-bind="args" @submit="submitForm">
58
58
  <div class="d-flex flex-column gap-4">
59
- <SyTextField v-model="name" label="Nom" required />
60
- <SyTextField v-model="email" label="Email" :custom-rules="emailRules" />
61
- <v-btn type="submit" color="primary">Soumettre</v-btn>
59
+ <SyTextField v-model="name" label="Nom" required class="mb-2" />
60
+ <SyTextField v-model="email" label="Email" :custom-rules="emailRules" class="mb-2" />
61
+ <div class="d-flex gap-3">
62
+ <v-btn type="submit" color="primary">Soumettre</v-btn>
63
+ </div>
62
64
  </div>
63
65
  </SyForm>
64
66
  `,
@@ -74,9 +76,11 @@ export const Basic: Story = {
74
76
  <template>
75
77
  <SyForm ref="form" v-bind="args" @submit="submitForm">
76
78
  <div class="d-flex flex-column gap-4">
77
- <SyTextField v-model="name" label="Nom" required />
78
- <SyTextField v-model="email" label="Email" :custom-rules="emailRules" />
79
- <v-btn type="submit" color="primary">Soumettre</v-btn>
79
+ <SyTextField v-model="name" label="Nom" required class="mb-2" />
80
+ <SyTextField v-model="email" label="Email" :custom-rules="emailRules" class="mb-2" />
81
+ <div class="d-flex gap-3">
82
+ <v-btn type="submit" color="primary">Soumettre</v-btn>
83
+ </div>
80
84
  </div>
81
85
  </SyForm>
82
86
  </template>
@@ -118,7 +122,7 @@ export const CustomValidation: Story = {
118
122
  const username = ref('')
119
123
  const password = ref('')
120
124
  const confirmPassword = ref('')
121
- const form = ref<{ validate: () => Promise<boolean> } | null>(null)
125
+ const form = ref<{ validate: () => Promise<boolean>, reset: () => void, clearValidation: () => void } | null>(null)
122
126
 
123
127
  // Règles de validation
124
128
  const passwordRules = computed(() => [
@@ -160,17 +164,18 @@ export const CustomValidation: Story = {
160
164
  <div>
161
165
  <SyForm ref="form" v-bind="args" @submit="submitForm">
162
166
  <div class="d-flex flex-column gap-4">
163
- <SyTextField v-model="username" label="Nom d'utilisateur" required />
164
- <SyTextField v-model="password" label="Mot de passe" type="password" :custom-rules="passwordRules" />
167
+ <SyTextField v-model="username" label="Nom d'utilisateur" required class="mb-2" />
168
+ <SyTextField v-model="password" label="Mot de passe" type="password" :custom-rules="passwordRules" class="mb-2" />
165
169
  <SyTextField
166
170
  v-model="confirmPassword"
167
171
  label="Confirmer le mot de passe"
168
172
  type="password"
169
173
  required
170
- :custom-rules="confirmPasswordRules"
174
+ :custom-rules="confirmPasswordRules"
175
+ class="mb-2"
171
176
  />
172
177
  <div class="d-flex gap-3">
173
- <v-btn type="submit" color="primary">S'inscrire</v-btn>
178
+ <v-btn type="submit" color="primary" class="mr-2">S'inscrire</v-btn>
174
179
  <v-btn @click="validateManually" color="secondary">Valider sans soumettre</v-btn>
175
180
  </div>
176
181
  </div>
@@ -187,16 +192,17 @@ export const CustomValidation: Story = {
187
192
  <div>
188
193
  <SyForm ref="form" @submit="onSubmit">
189
194
  <div class="d-flex flex-column gap-4">
190
- <SyTextField v-model="username" label="Nom d'utilisateur" required />
191
- <SyTextField v-model="password" label="Mot de passe" type="password" :custom-rules="passwordRules" />
195
+ <SyTextField v-model="username" label="Nom d'utilisateur" required class="mb-2" />
196
+ <SyTextField v-model="password" label="Mot de passe" type="password" :custom-rules="passwordRules" class="mb-2" />
192
197
  <SyTextField
193
198
  v-model="confirmPassword"
194
199
  label="Confirmer le mot de passe"
195
200
  type="password"
196
201
  :custom-rules="confirmPasswordRules"
202
+ class="mb-2"
197
203
  />
198
204
  <div class="d-flex gap-3">
199
- <v-btn type="submit" color="primary">S'inscrire</v-btn>
205
+ <v-btn type="submit" color="primary" class="mr-2">S'inscrire</v-btn>
200
206
  <v-btn @click="validateManually" color="secondary">Valider sans soumettre</v-btn>
201
207
  </div>
202
208
  </div>
@@ -296,10 +302,12 @@ export const MixedFields: Story = {
296
302
  template: `
297
303
  <SyForm ref="form" v-bind="args" @submit="submitForm">
298
304
  <div class="d-flex flex-column gap-4">
299
- <SyTextField v-model="formData.name" label="Nom complet" required />
300
- <SyTextField v-model="formData.email" label="Email" :custom-rules="emailCustomRules" />
301
- <SySelect v-model="formData.country" :items="countries" label="Pays" required />
302
- <v-btn type="submit" color="primary">Enregistrer</v-btn>
305
+ <SyTextField v-model="formData.name" label="Nom complet" required class="mb-2" />
306
+ <SyTextField v-model="formData.email" label="Email" :custom-rules="emailCustomRules" class="mb-2" />
307
+ <SySelect v-model="formData.country" :items="countries" label="Pays" required class="mb-2" />
308
+ <div class="d-flex gap-3">
309
+ <v-btn type="submit" color="primary">Enregistrer</v-btn>
310
+ </div>
303
311
  </div>
304
312
  </SyForm>
305
313
  `,
@@ -312,10 +320,12 @@ export const MixedFields: Story = {
312
320
  <template>
313
321
  <SyForm ref="form" v-bind="args" @submit="submitForm">
314
322
  <div class="d-flex flex-column gap-4">
315
- <SyTextField v-model="formData.name" label="Nom complet" required />
316
- <SyTextField v-model="formData.email" label="Email" :customRules="emailCustomRules" />
317
- <SySelect v-model="formData.country" :items="countries" label="Pays" required />
318
- <v-btn type="submit" color="primary">Enregistrer</v-btn>
323
+ <SyTextField v-model="formData.name" label="Nom complet" required class="mb-2" />
324
+ <SyTextField v-model="formData.email" label="Email" :customRules="emailCustomRules" class="mb-2" />
325
+ <SySelect v-model="formData.country" :items="countries" label="Pays" required class="mb-2" />
326
+ <div class="d-flex gap-3">
327
+ <v-btn type="submit" color="primary">Enregistrer</v-btn>
328
+ </div>
319
329
  </div>
320
330
  </SyForm>
321
331
  </template>
@@ -361,3 +371,103 @@ export const MixedFields: Story = {
361
371
  ],
362
372
  },
363
373
  }
374
+
375
+ export const Reset: Story = {
376
+ render: args => ({
377
+ components: { SyForm, SyTextField, VBtn },
378
+ setup() {
379
+ const name = ref('')
380
+ const email = ref('')
381
+ const form = ref<{ validate: () => Promise<boolean>, reset: () => void, clearValidation: () => void } | null>(null)
382
+
383
+ // Règles de validation selon le design system
384
+ const emailRules = [
385
+ { type: 'email', options: { message: 'Format d\'email invalide' } },
386
+ { type: 'required', options: { message: 'L\'email est obligatoire' } },
387
+ ]
388
+
389
+ const submitForm = async () => {
390
+ const isValid = await form.value?.validate()
391
+ if (isValid) {
392
+ alert('Formulaire valide !')
393
+ }
394
+ else {
395
+ alert('Formulaire invalide, veuillez corriger les erreurs.')
396
+ }
397
+ }
398
+
399
+ function clearAll() {
400
+ form.value?.reset()
401
+ form.value?.clearValidation()
402
+ }
403
+
404
+ return { name, email, emailRules, form, submitForm, clearAll, args }
405
+ },
406
+ template: `
407
+ <SyForm ref="form" v-bind="args" @submit="submitForm" @reset="onFormReset">
408
+ <div class="d-flex flex-column gap-4">
409
+ <SyTextField v-model="name" label="Nom" required class="mb-2" />
410
+ <SyTextField v-model="email" label="Email" :custom-rules="emailRules" class="mb-2" />
411
+ <div class="d-flex gap-3">
412
+ <v-btn color="secondary" class="mr-2" @click="clearAll">Reset</v-btn>
413
+ <v-btn type="submit" color="primary">Soumettre</v-btn>
414
+ </div>
415
+ </div>
416
+ </SyForm>
417
+ `,
418
+ }),
419
+ args: {
420
+ validateOnSubmit: true,
421
+ },
422
+ parameters: {
423
+ sourceCode: [
424
+ {
425
+ name: 'Template',
426
+ code: `
427
+ <template>
428
+ <SyForm ref="form" v-bind="args" @submit="submitForm" @reset="onFormReset">
429
+ <div class="d-flex flex-column gap-4">
430
+ <SyTextField v-model="name" label="Nom" required class="mb-2" />
431
+ <SyTextField v-model="email" label="Email" :custom-rules="emailRules" class="mb-2" />
432
+ <div class="d-flex gap-3">
433
+ <v-btn color="secondary" class="mr-2" @click="clearAll">Reset</v-btn>
434
+ <v-btn type="submit" color="primary">Soumettre</v-btn>
435
+ </div>
436
+ </div>
437
+ </SyForm>
438
+ </template>
439
+ `,
440
+ },
441
+ {
442
+ name: 'Script',
443
+ code: `
444
+ <script setup lang="ts">
445
+ import { ref } from 'vue'
446
+ const name = ref('')
447
+ const email = ref('')
448
+
449
+ // Règles de validation selon le design system
450
+ const emailRules = [
451
+ { type: 'email', options: { message: "Format d'email invalide" } },
452
+ { type: 'required', options: { message: "L'email est obligatoire" } },
453
+ ]
454
+
455
+ const onSubmit = (event: { isValid: boolean }) => {
456
+ if (event.isValid) {
457
+ alert('Formulaire valide !')
458
+ }
459
+ else {
460
+ alert('Formulaire invalide, veuillez corriger les erreurs.')
461
+ }
462
+ }
463
+
464
+ function clearAll() {
465
+ form.value?.reset()
466
+ form.value?.clearValidation()
467
+ }
468
+ </script>
469
+ `,
470
+ },
471
+ ],
472
+ },
473
+ }
@@ -8,12 +8,13 @@
8
8
 
9
9
  const emit = defineEmits<{
10
10
  (e: 'submit', value: { isValid: boolean }): void
11
+ (e: 'reset'): void
11
12
  }>()
12
13
 
13
14
  // Reference vers le formulaire Vuetify
14
15
  const form = ref<InstanceType<typeof import('vuetify/components').VForm> | null>(null)
15
16
 
16
- const { validateAll } = useFormValidation()
17
+ const { validateAll, clearAll, resetAll } = useFormValidation()
17
18
 
18
19
  // Methode de validation globale qui combine Vuetify et nos composants personnalises
19
20
  const validate = async () => {
@@ -48,7 +49,21 @@
48
49
  }
49
50
 
50
51
  const reset = () => {
52
+ // Reset custom components values
53
+ resetAll()
54
+ // Reset field values and validations for Vuetify form
51
55
  form.value?.reset()
56
+ form.value?.resetValidation()
57
+ // Notify consumers so they can clear external models (e.g., v-model refs)
58
+ emit('reset')
59
+ }
60
+
61
+ // Clear only validation states (keep current values)
62
+ const clearValidation = () => {
63
+ // Clear Vuetify internal validation state
64
+ form.value?.resetValidation()
65
+ // Clear custom components validation states registered in the form
66
+ clearAll()
52
67
  }
53
68
 
54
69
  // Gestion de la soumission du formulaire
@@ -65,6 +80,7 @@
65
80
  defineExpose({
66
81
  validate,
67
82
  reset,
83
+ clearValidation,
68
84
  form,
69
85
  })
70
86
  </script>
@@ -284,7 +284,7 @@
284
284
  const hasWarning = computed(() => validation.hasWarning.value || props.hasWarning)
285
285
  const hasSuccess = computed(() => (validation.hasSuccess.value && !hasError.value && !hasWarning.value) || props.hasSuccess)
286
286
 
287
- const errors = computed(() => validation.errors.value)
287
+ const errors = computed(() => [...validation.errors.value, ...(props.errorMessages || [])])
288
288
  const warnings = computed(() => validation.warnings.value)
289
289
  const successes = computed(() => validation.successes.value)
290
290
 
@@ -361,7 +361,7 @@
361
361
  const syTextFieldRef = ref<ComponentPublicInstance | null>(null)
362
362
 
363
363
  // Intégration avec le système de validation du formulaire
364
- useValidatable(validateOnSubmit)
364
+ useValidatable(validateOnSubmit, validation.clearValidation)
365
365
 
366
366
  onMounted(() => {
367
367
  nextTick(() => {
@@ -592,7 +592,7 @@
592
592
  }
593
593
 
594
594
  // Intégration avec le système de validation du formulaire
595
- useValidatable(validateOnSubmit)
595
+ useValidatable(validateOnSubmit, clearValidation)
596
596
 
597
597
  const openDatePicker = () => {
598
598
  if (props.disabled || props.readonly) return
@@ -246,6 +246,8 @@
246
246
 
247
247
  const textInputValue = ref('')
248
248
  const displayFormattedDate = ref('')
249
+ // Force re-render of DateTextInput/SyTextField when needed (e.g., after reset)
250
+ const fieldKey = ref(0)
249
251
  const isManualInputActive = ref(false)
250
252
  const isFormatting = ref(false)
251
253
  const isUpdatingFromInternal = ref(false)
@@ -399,7 +401,10 @@
399
401
  if (typeof newValue === 'string') {
400
402
  if (props.dateFormatReturn) {
401
403
  const date = parseDate(newValue, returnFormat.value)
402
- if (date) textInputValue.value = formatDate(date, props.format)
404
+ if (date) {
405
+ const formattedForDisplay = formatDate(date, props.format)
406
+ textInputValue.value = formattedForDisplay
407
+ }
403
408
  }
404
409
  else {
405
410
  textInputValue.value = newValue
@@ -415,10 +420,18 @@
415
420
  updateModel(formattedDate.value)
416
421
  withInternalUpdate(() => {
417
422
  if (Array.isArray(newValue)) {
418
- if (newValue.length > 0) textInputValue.value = formatDate(newValue[0], props.format)
423
+ if (newValue.length > 0) {
424
+ const newFormattedValue = formatDate(newValue[0], props.format)
425
+ if (textInputValue.value !== newFormattedValue) {
426
+ textInputValue.value = newFormattedValue
427
+ }
428
+ }
419
429
  }
420
430
  else {
421
- textInputValue.value = formatDate(newValue, props.format)
431
+ const newFormattedValue = formatDate(newValue, props.format)
432
+ if (textInputValue.value !== newFormattedValue) {
433
+ textInputValue.value = newFormattedValue
434
+ }
422
435
  }
423
436
  })
424
437
  }
@@ -436,6 +449,9 @@
436
449
 
437
450
  // Handle manual typing sync → model/selection
438
451
  watch(textInputValue, (newValue) => {
452
+ // En mode plage, on laisse DateTextInput + handleDateTextInputUpdate
453
+ // piloter la mise à jour du modèle et de selectedDates
454
+ if (props.displayRange) return
439
455
  if (isUpdatingFromInternal.value) return
440
456
  const date = parseDate(newValue, props.format)
441
457
  if (date) {
@@ -711,6 +727,67 @@
711
727
  successMessages,
712
728
  })
713
729
 
730
+ /**
731
+ * Gère les mises à jour de DateTextInput avec contrôle
732
+ */
733
+ const handleDateTextInputUpdate = (value: DateValue) => {
734
+ // Ne pas traiter les mises à jour internes pour éviter les boucles
735
+ if (isUpdatingFromInternal.value) return
736
+
737
+ try {
738
+ isUpdatingFromInternal.value = true
739
+
740
+ // 1) Propager la valeur brute vers le v-model externe
741
+ updateModel(value)
742
+
743
+ // 2) Mettre à jour selectedDates / displayFormattedDate pour refléter la saisie manuelle
744
+ if (!value) {
745
+ selectedDates.value = null
746
+ displayFormattedDate.value = ''
747
+ return
748
+ }
749
+
750
+ if (Array.isArray(value) && props.displayRange) {
751
+ const [startStr, endStr] = value
752
+ const rf = returnFormat.value
753
+ const startDate = startStr ? parseDate(startStr, rf) || parseDate(startStr, props.format) : null
754
+ const endDate = endStr ? parseDate(endStr, rf) || parseDate(endStr, props.format) : null
755
+
756
+ if (startDate && endDate) {
757
+ selectedDates.value = [startDate, endDate]
758
+ displayFormattedDate.value
759
+ = `${formatDate(startDate, props.format)} - ${formatDate(endDate, props.format)}`
760
+ }
761
+ else if (startDate) {
762
+ // Première date saisie uniquement
763
+ selectedDates.value = [startDate]
764
+ displayFormattedDate.value = formatDate(startDate, props.format)
765
+ }
766
+ else {
767
+ selectedDates.value = null
768
+ displayFormattedDate.value = ''
769
+ }
770
+ }
771
+ else if (typeof value === 'string') {
772
+ const rf = returnFormat.value
773
+ const date = parseDate(value, rf) || parseDate(value, props.format)
774
+ if (date) {
775
+ selectedDates.value = date
776
+ displayFormattedDate.value = formatDate(date, props.format)
777
+ }
778
+ else {
779
+ selectedDates.value = null
780
+ displayFormattedDate.value = ''
781
+ }
782
+ }
783
+ }
784
+ finally {
785
+ setTimeout(() => {
786
+ isUpdatingFromInternal.value = false
787
+ }, 0)
788
+ }
789
+ }
790
+
714
791
  /**
715
792
  * Sync from external v-model
716
793
  */
@@ -728,7 +805,8 @@
728
805
  const firstDate = Array.isArray(selectedDates.value)
729
806
  ? selectedDates.value[0]
730
807
  : selectedDates.value
731
- textInputValue.value = formatDate(firstDate, props.format)
808
+ const formattedForInput = formatDate(firstDate, props.format)
809
+ textInputValue.value = formattedForInput
732
810
  displayFormattedDate.value = displayFormattedDateComputed.value || ''
733
811
  }
734
812
  validateDates()
@@ -896,8 +974,30 @@
896
974
  return textInputValid && errors.value.length === 0
897
975
  }
898
976
 
977
+ // Reset hook utilisé par SyForm.reset() via useValidatable
978
+ const reset = () => {
979
+ // 1) Nettoyer l'état de validation et d'interaction
980
+ clearValidation()
981
+ isDatePickerVisible.value = false
982
+ hasInteracted.value = false
983
+ isManualInputActive.value = false
984
+
985
+ // 2) Réinitialiser la valeur et la sélection SANS déclencher
986
+ // de validation "required" interactive
987
+ withInternalUpdate(() => {
988
+ selectedDates.value = null
989
+ textInputValue.value = ''
990
+ displayFormattedDate.value = ''
991
+ // Synchroniser le modèle externe
992
+ emit('update:modelValue', null)
993
+ })
994
+
995
+ // 3) Forcer la recréation du champ pour réinitialiser l'état interne de Vuetify
996
+ fieldKey.value++
997
+ }
998
+
899
999
  // Intégration avec le système de validation du formulaire
900
- useValidatable(validateOnSubmit)
1000
+ useValidatable(validateOnSubmit, clearValidation, reset)
901
1001
 
902
1002
  defineExpose({
903
1003
  validateOnSubmit,
@@ -923,6 +1023,7 @@
923
1023
  // Expose for consumers
924
1024
  handleDateSelected,
925
1025
  resetViewMode,
1026
+ reset,
926
1027
  })
927
1028
  </script>
928
1029
 
@@ -937,6 +1038,7 @@
937
1038
  <template v-if="props.noCalendar">
938
1039
  <DateTextInput
939
1040
  ref="dateTextInputRef"
1041
+ :key="fieldKey"
940
1042
  v-model="textInputValue"
941
1043
  :class="[getMessageClasses(), 'label-hidden-on-focus']"
942
1044
  :date-format-return="props.dateFormatReturn"
@@ -984,7 +1086,8 @@
984
1086
  <DateTextInput
985
1087
  v-bind="menuProps"
986
1088
  ref="dateCalendarTextInputRef"
987
- v-model="textInputValue"
1089
+ :key="fieldKey"
1090
+ :model-value="textInputValue"
988
1091
  :label="labelWithAsterisk || ''"
989
1092
  :placeholder="props.placeholder"
990
1093
  :format="props.format"
@@ -1014,6 +1117,7 @@
1014
1117
  :density="props.density"
1015
1118
  :hint="props.hint"
1016
1119
  :persistent-hint="props.persistentHint"
1120
+ @update:model-value="handleDateTextInputUpdate"
1017
1121
  @click="openDatePickerOnClick"
1018
1122
  @focus="openDatePickerOnFocus"
1019
1123
  @blur="handleInputBlur"
@@ -200,6 +200,8 @@
200
200
  const inputValue = ref('')
201
201
  const inputRef = ref<InstanceType<typeof SyTextField> | null>(null)
202
202
  const isFormatting = ref(false)
203
+ // Force re-render of SyTextField when needed (e.g., after reset)
204
+ const fieldKey = ref(0)
203
205
 
204
206
  const updateDisplayValue = (dateDisplayText: string) => (inputValue.value = dateDisplayText)
205
207
  const updateAriaLabel = (ariaLabelText: string) => (ariaLabel.value = ariaLabelText)
@@ -940,15 +942,37 @@
940
942
  isValidating.value = true
941
943
  hasInteracted.value = true
942
944
  const ok = runRules(inputValue.value)
943
- if (!ok || hasError.value) return false
944
- return !hasError.value
945
+ isValidating.value = false
946
+ return ok
947
+ }
948
+
949
+ // Reset hook utilisé par SyForm.reset() via useValidatable
950
+ const reset = () => {
951
+ // 1) Nettoyer l'état de validation et d'interaction
952
+ clearValidation()
953
+ isFocused.value = false
954
+ hasInteracted.value = false
955
+
956
+ // 2) Réinitialiser la valeur sans déclencher de validation interactive
957
+ isFormatting.value = true
958
+ inputValue.value = ''
959
+ selectedDates.value = null
960
+ resetState()
961
+ isFormatting.value = false
962
+
963
+ // 3) Synchroniser le modèle externe
964
+ emitModel(null)
965
+
966
+ // 4) Forcer la recréation du champ pour réinitialiser l'état interne de Vuetify
967
+ fieldKey.value++
945
968
  }
946
969
 
947
970
  // Intégration avec le système de validation du formulaire
948
- useValidatable(validateOnSubmit)
971
+ useValidatable(validateOnSubmit, clearValidation, reset)
949
972
 
950
973
  defineExpose({
951
974
  validateOnSubmit,
975
+ reset,
952
976
  focus() {
953
977
  const el: HTMLInputElement | null | undefined = inputRef.value?.$el?.querySelector?.('input:not([type="hidden"])')
954
978
  el?.focus({ preventScroll: true })
@@ -1002,6 +1026,7 @@
1002
1026
 
1003
1027
  <template>
1004
1028
  <SyTextField
1029
+ :key="fieldKey"
1005
1030
  ref="inputRef"
1006
1031
  v-model="inputValue"
1007
1032
  :append-icon="props.displayIcon && props.displayAppendIcon ? 'calendar' : undefined"
@@ -349,6 +349,16 @@ const meta: Meta<typeof NirField> = {
349
349
  },
350
350
  },
351
351
  },
352
+ customLocale: {
353
+ description: 'Objet permettant de surcharger les messages du composant. Clés supportées : `errorRequiredNumber`, `errorInvalidNumber`, `errorRequiredKey`, `errorInvalidKey`, `successNumberValid`, `successKeyValid`.',
354
+ control: 'object',
355
+ table: {
356
+ type: {
357
+ summary: 'Partial<typeof locales>',
358
+ },
359
+ defaultValue: { summary: '{}' },
360
+ },
361
+ },
352
362
  },
353
363
  } satisfies Meta<typeof NirField>
354
364
 
@@ -1315,3 +1325,67 @@ mais gérer leur affichage différemment, ou utiliser la validation uniquement a
1315
1325
  `,
1316
1326
  }),
1317
1327
  }
1328
+
1329
+ export const WithCustomLocale: Story = {
1330
+ args: {
1331
+ ...Default.args,
1332
+ required: true,
1333
+ showSuccessMessages: true,
1334
+ customLocale: {
1335
+ errorRequiredNumber: 'Veuillez renseigner votre numéro de sécurité sociale (13 caractères).',
1336
+ errorInvalidNumber: 'Format NIR non reconnu, merci de vérifier.',
1337
+ errorRequiredKey: 'La clé (2 chiffres) est requise.',
1338
+ errorInvalidKey: 'La clé ne correspond pas au NIR saisi.',
1339
+ successNumberValid: 'Numéro reconnu ✅',
1340
+ successKeyValid: 'Clé correspondante ✅',
1341
+ },
1342
+ },
1343
+ parameters: {
1344
+ docs: {
1345
+ description: {
1346
+ story: `
1347
+ ### Surcharger les messages avec customLocale
1348
+
1349
+ Utilisez la prop \`customLocale\` pour remplacer les messages par défaut sans toucher au composant.
1350
+
1351
+ Clés supportées :
1352
+ - \`errorRequiredNumber\`
1353
+ - \`erreurInvalidNumber\`
1354
+ - \`errorRequiredKey\`
1355
+ - \`errorInvalidKey\`
1356
+ - \`successNumberValid\`
1357
+ - \`successKeyValid\`
1358
+ `,
1359
+ },
1360
+ },
1361
+ sourceCode: [
1362
+ {
1363
+ name: 'Template',
1364
+ code: `<template>
1365
+ <NirField
1366
+ v-model="value"
1367
+ required
1368
+ show-success-messages
1369
+ :custom-locale="{
1370
+ errorRequiredNumber: 'Veuillez renseigner votre numéro de sécurité sociale (13 caractères).',
1371
+ errorInvalidNumber: 'Format NIR non reconnu, merci de vérifier.',
1372
+ errorRequiredKey: 'La clé (2 chiffres) est requise.',
1373
+ errorInvalidKey: 'La clé ne correspond pas au NIR saisi.',
1374
+ successNumberValid: 'Numéro reconnu ✅',
1375
+ successKeyValid: 'Clé correspondante ✅'
1376
+ }"
1377
+ />
1378
+ </template>`,
1379
+ },
1380
+ {
1381
+ name: 'Script',
1382
+ code: `<script setup lang="ts">
1383
+ import { NirField } from '@cnamts/synapse'
1384
+ import { ref } from 'vue'
1385
+
1386
+ const value = ref('')
1387
+ </script>`,
1388
+ },
1389
+ ],
1390
+ },
1391
+ }