@cnamts/synapse 1.0.26 → 1.0.27

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 (253) hide show
  1. package/dist/{AutocompleteFilter-BPR-a55G.js → AutocompleteFilter-C9eLKyW8.js} +3 -3
  2. package/dist/{DateFilter-CknrJWs2.js → DateFilter-y-GLkAkn.js} +8 -8
  3. package/dist/{NumberFilter-DJ-yNlzv.js → NumberFilter-DN6hIBS7.js} +1 -1
  4. package/dist/{PeriodFilter-CiB5Oa9Z.js → PeriodFilter-MoUUp9qS.js} +1 -1
  5. package/dist/{SelectFilter-EiafX97M.js → SelectFilter-bCbrdLmu.js} +1 -1
  6. package/dist/{TextFilter-BzOmpdxj.js → TextFilter-CvjgEaoM.js} +4 -4
  7. package/dist/apLightTheme2026-ug4Y23ns.js +611 -0
  8. package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +2369 -353
  9. package/dist/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.d.ts +18 -0
  10. package/dist/components/Customs/Selects/SyAutocomplete/utils/ariaManager.d.ts +1 -1
  11. package/dist/components/Customs/Selects/SyAutocomplete/utils/useKeyboardHandler.d.ts +3 -1
  12. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +9 -10
  13. package/dist/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.d.ts +1 -0
  14. package/dist/components/Customs/Selects/SySelect/composables/useSySelectValidation.d.ts +15 -0
  15. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +3 -3
  16. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +3 -3
  17. package/dist/components/Customs/SyIconButton/SyIconButton.d.ts +18 -0
  18. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +20 -38
  19. package/dist/components/Customs/SyRadioGroup/composables/useSyRadioGroupValidation.d.ts +50 -0
  20. package/dist/components/Customs/SyTextField/SyTextField.d.ts +6 -6
  21. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +147 -136
  22. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +62 -54
  23. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +27 -24
  24. package/dist/components/DatePicker/composables/index.d.ts +1 -0
  25. package/dist/components/DatePicker/composables/useDatePickerValidationBridge.d.ts +51 -0
  26. package/dist/components/MonthPicker/MonthPicker.d.ts +23 -23
  27. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +23 -23
  28. package/dist/components/NirField/NirField.d.ts +56 -56
  29. package/dist/components/PasswordField/PasswordField.d.ts +3 -3
  30. package/dist/components/PeriodField/PeriodField.d.ts +236 -212
  31. package/dist/components/PhoneField/PhoneField.d.ts +23 -23
  32. package/dist/components/SyTextArea/SyTextArea.d.ts +25 -15
  33. package/dist/components/SyTextArea/composables/useSyTextAreaValidation.d.ts +20 -0
  34. package/dist/components/SyTextArea/locales.d.ts +1 -0
  35. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +1 -0
  36. package/dist/components/Tables/SyTable/SyTable.d.ts +1 -0
  37. package/dist/components/Tables/common/SyTablePagination.d.ts +25 -25
  38. package/dist/components/Tables/common/types.d.ts +2 -0
  39. package/dist/components/index.d.ts +1 -0
  40. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +160 -160
  41. package/dist/composables/unifyValidation/useValidation.d.ts +16 -14
  42. package/dist/design-system-v3.js +81 -80
  43. package/dist/designTokens/tokens/amelipro/apContextual.d.ts +6 -6
  44. package/dist/designTokens/tokens/amelipro/apDarkTheme.d.ts +3 -1
  45. package/dist/designTokens/tokens/amelipro/apLightTheme.d.ts +53 -100
  46. package/dist/designTokens/tokens/baseContextualTokens.d.ts +0 -6
  47. package/dist/designTokens/tokens/baseTokens.d.ts +232 -0
  48. package/dist/designTokens/tokens/cnam/cnamContextual.d.ts +6 -6
  49. package/dist/designTokens/tokens/cnam/cnamDarkTheme.d.ts +1 -1
  50. package/dist/designTokens/tokens/cnam/cnamLightTheme.d.ts +57 -101
  51. package/dist/designTokens/tokens/pa/paContextual.d.ts +0 -6
  52. package/dist/designTokens/tokens/pa/paDarkTheme.d.ts +1 -1
  53. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +53 -97
  54. package/dist/designTokens/tokens/pa/paSemantic.d.ts +1 -0
  55. package/dist/designTokens/tokens/semanticTokens.d.ts +112 -0
  56. package/dist/main-CI6Q9nmO.js +39234 -0
  57. package/dist/synapse.css +1 -1
  58. package/dist/vuetifyConfig.js +208 -72
  59. package/package.json +4 -2
  60. package/src/assets/overrides/_icons.scss +5 -4
  61. package/src/assets/overrides/_otp.scss +4 -4
  62. package/src/assets/overrides/_typography.scss +2 -1
  63. package/src/assets/overrides/_utilities.scss +1 -42
  64. package/src/components/ChipList/ChipList.vue +30 -18
  65. package/src/components/ChipList/tests/chipList.spec.ts +4 -4
  66. package/src/components/CopyBtn/CopyBtn.vue +2 -2
  67. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.stories.ts +4 -0
  68. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +7 -6
  69. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +223 -0
  70. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +283 -351
  71. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +182 -218
  72. package/src/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.ts +101 -0
  73. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +761 -1
  74. package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +3 -1
  75. package/src/components/Customs/Selects/SyAutocomplete/utils/useKeyboardHandler.ts +79 -5
  76. package/src/components/Customs/Selects/SyAutocomplete/validation/Validation.stories.ts +1029 -0
  77. package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +9 -491
  78. package/src/components/Customs/Selects/SySelect/SySelect.vue +46 -79
  79. package/src/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.ts +3 -0
  80. package/src/components/Customs/Selects/SySelect/composables/useSySelectValidation.ts +64 -0
  81. package/src/components/Customs/Selects/SySelect/tests/SySelect.spec.ts +196 -0
  82. package/src/components/Customs/Selects/SySelect/validation/Validation.stories.ts +1026 -0
  83. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +18 -7
  84. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +2 -2
  85. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +8 -8
  86. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +8 -8
  87. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.spec.ts +1 -1
  88. package/src/components/Customs/SyIcon/accessibilite/Accessibility.mdx +0 -6
  89. package/src/components/Customs/SyIcon/utils/tests/iconUtils.spec.ts +107 -0
  90. package/src/components/Customs/SyRadioGroup/SyRadioGroup.mdx +2 -2
  91. package/src/components/Customs/SyRadioGroup/SyRadioGroup.stories.ts +395 -200
  92. package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +82 -127
  93. package/src/components/Customs/SyRadioGroup/composables/useSyRadioGroupValidation.ts +127 -0
  94. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.a11y.spec.ts +93 -1
  95. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.spec.ts +146 -9
  96. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.visual.cy.ts +165 -0
  97. package/src/components/Customs/SyRadioGroup/validation/Validation.stories.ts +773 -0
  98. package/src/components/Customs/SyTabs/config.ts +3 -3
  99. package/src/components/Customs/SyTabs/tests/SyTabs.spec.ts +265 -0
  100. package/src/components/Customs/SyTabs/tests/useTabTransition.spec.ts +188 -0
  101. package/src/components/Customs/SyTextField/SyTextField.stories.ts +10 -29
  102. package/src/components/Customs/SyTextField/SyTextField.vue +23 -15
  103. package/src/components/DataList/DataList.stories.ts +1 -1
  104. package/src/components/DataListItem/tests/DataListItem.spec.ts +3 -1
  105. package/src/components/DatePicker/CalendarMode/DatePicker.vue +37 -142
  106. package/src/components/DatePicker/CalendarMode/tests/DatePicker.coverage.spec.ts +156 -0
  107. package/src/components/DatePicker/CalendarMode/tests/DatePicker.spec.ts +495 -4
  108. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +47 -66
  109. package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.spec.ts +206 -0
  110. package/src/components/DatePicker/ComplexDatePicker/tests/bridge-integration.regression.spec.ts +210 -0
  111. package/src/components/DatePicker/ComplexDatePicker/tests/calendar-navigation.regression.spec.ts +214 -0
  112. package/src/components/DatePicker/ComplexDatePicker/tests/validation-cross.regression.spec.ts +194 -0
  113. package/src/components/DatePicker/ComplexDatePicker/tests/validation-success-messages.regression.spec.ts +83 -0
  114. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +129 -54
  115. package/src/components/DatePicker/DateTextInput/tests/DateTextInput.spec.ts +320 -0
  116. package/src/components/DatePicker/composables/index.ts +1 -0
  117. package/src/components/DatePicker/composables/tests/useCalendarKeyboardNavigation.spec.ts +360 -0
  118. package/src/components/DatePicker/composables/tests/useDatePickerValidationBridge.spec.ts +129 -0
  119. package/src/components/DatePicker/composables/useDatePickerValidationBridge.ts +205 -0
  120. package/src/components/DatePicker/docExamples/BidirectionalComplexValidation.vue +1 -1
  121. package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +1 -1
  122. package/src/components/DatePicker/tests/exposed-methods.coverage.spec.ts +75 -0
  123. package/src/components/DialogBox/DialogBox.vue +1 -1
  124. package/src/components/FileList/UploadItem/UploadItem.vue +4 -4
  125. package/src/components/FileUpload/FileUpload.vue +2 -2
  126. package/src/components/FileUpload/FileUploadContent.vue +1 -1
  127. package/src/components/FilterInline/FilterInline.mdx +2 -2
  128. package/src/components/FilterSideBar/FilterSideBar.stories.ts +1 -1
  129. package/src/components/FilterSideBar/FilterSideBar.vue +2 -2
  130. package/src/components/FooterBar/FooterBar.vue +7 -7
  131. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +1 -1
  132. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +2 -2
  133. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +7 -7
  134. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +2 -2
  135. package/src/components/HeaderLoading/tests/HeaderLoading.spec.ts +87 -8
  136. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +3 -3
  137. package/src/components/HeaderNavigationBar/HorizontalNavbar/tests/HorizontalNavbar.spec.ts +589 -0
  138. package/src/components/HeaderToolbar/tests/HeaderToolBar.spec.ts +153 -1
  139. package/src/components/HeaderToolbar/tests/useMobileRightMenu.spec.ts +258 -0
  140. package/src/components/LogoBrandSection/tests/LogoBrandSection.spec.ts +2 -2
  141. package/src/components/LogoBrandSection/tests/__snapshots__/LogoBrandSection.spec.ts.snap +1 -1
  142. package/src/components/LunarCalendar/tests/useLunarCalendarRules.spec.ts +184 -0
  143. package/src/components/MonthPicker/MonthPickerVisual/MonthSelector.vue +3 -3
  144. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.vue +1 -1
  145. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +2 -2
  146. package/src/components/MonthPicker/MonthPickerVisual/YearSelector.vue +1 -1
  147. package/src/components/NirField/NirField.vue +3 -3
  148. package/src/components/NotificationBar/Notification/Notification.vue +12 -12
  149. package/src/components/NotificationBar/NotificationBar.stories.ts +8 -8
  150. package/src/components/PaginatedTable/Pagination.vue +2 -2
  151. package/src/components/PasswordField/PasswordField.vue +8 -8
  152. package/src/components/PasswordField/tests/PasswordField.spec.ts +3 -3
  153. package/src/components/RangeField/RangeSlider/RangeSlider.vue +2 -2
  154. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +1 -1
  155. package/src/components/StatusPage/tests/StatusPage.spec.ts +149 -0
  156. package/src/components/SubHeader/SubHeader.vue +1 -1
  157. package/src/components/SyAlert/SyAlert.vue +23 -23
  158. package/src/components/SyTextArea/SyTextArea.stories.ts +177 -131
  159. package/src/components/SyTextArea/SyTextArea.vue +235 -83
  160. package/src/components/SyTextArea/composables/useSyTextAreaValidation.ts +81 -0
  161. package/src/components/SyTextArea/locales.ts +1 -0
  162. package/src/components/SyTextArea/tests/SyTextArea.spec.ts +449 -1
  163. package/src/components/SyTextArea/useDefaultValidationRules.ts +2 -7
  164. package/src/components/SyTextArea/validation/Validation.stories.ts +856 -0
  165. package/src/components/TableToolbar/TableToolbar.vue +6 -6
  166. package/src/components/TableToolbar/accessibilite/Accessibility.mdx +81 -7
  167. package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +163 -0
  168. package/src/components/Tables/SyServerTable/SyServerTable.vue +2 -1
  169. package/src/components/Tables/SyServerTable/tests/SyServerTable.spec.ts +67 -0
  170. package/src/components/Tables/SyTable/SyTable.stories.ts +94 -0
  171. package/src/components/Tables/SyTable/SyTable.vue +2 -1
  172. package/src/components/Tables/SyTable/tests/SyTable.spec.ts +64 -0
  173. package/src/components/Tables/common/TableHeader.vue +2 -2
  174. package/src/components/Tables/common/filters/logics/tests/NumberFilterLogic.spec.ts +176 -0
  175. package/src/components/Tables/common/filters/logics/tests/SelectFilterLogic.spec.ts +111 -0
  176. package/src/components/Tables/common/tableStyles.scss +6 -6
  177. package/src/components/Tables/common/types.ts +2 -0
  178. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +2 -0
  179. package/src/components/index.ts +1 -0
  180. package/src/composables/date/tests/useDateFormatDayjs.spec.ts +31 -0
  181. package/src/composables/date/tests/useHolidayDay.spec.ts +109 -0
  182. package/src/composables/rules/tests/useFieldValidation.spec.ts +374 -0
  183. package/src/composables/tests/useError.spec.ts +30 -0
  184. package/src/composables/tests/useFormFieldErrorHandling.spec.ts +234 -0
  185. package/src/composables/unifyValidation/documentationValidationProps.ts +5 -5
  186. package/src/composables/unifyValidation/tests/documentationValidationProps.spec.ts +177 -0
  187. package/src/composables/unifyValidation/tests/useCustomValidation.spec.ts +30 -0
  188. package/src/composables/unifyValidation/tests/useValidation.spec.ts +6 -2
  189. package/src/composables/unifyValidation/useCustomValidation.ts +19 -9
  190. package/src/composables/unifyValidation/useValidation.ts +18 -21
  191. package/src/composables/useFilterable/useFilterable.spec.ts +42 -0
  192. package/src/composables/useFilterable/useFilterable.ts +11 -7
  193. package/src/composables/useFormFieldErrorHandling.ts +2 -2
  194. package/src/composantsVuetify/VBtn/VBtn.mdx +9 -39
  195. package/src/composantsVuetify/VBtn/v-btn.stories.ts +26 -86
  196. package/src/designTokens/tokens/amelipro/apContextual.ts +6 -0
  197. package/src/designTokens/tokens/amelipro/apDarkTheme.ts +2 -2
  198. package/src/designTokens/tokens/amelipro/apLightTheme.ts +72 -103
  199. package/src/designTokens/tokens/amelipro/apSemantic.ts +1 -1
  200. package/src/designTokens/tokens/baseContextualTokens.ts +1 -6
  201. package/src/designTokens/tokens/baseTokens.ts +232 -0
  202. package/src/designTokens/tokens/cnam/cnamContextual.ts +6 -0
  203. package/src/designTokens/tokens/cnam/cnamDarkTheme.ts +2 -2
  204. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +76 -104
  205. package/src/designTokens/tokens/pa/paDarkTheme.ts +2 -2
  206. package/src/designTokens/tokens/pa/paLightTheme.ts +73 -99
  207. package/src/designTokens/tokens/pa/paSemantic.ts +2 -0
  208. package/src/designTokens/tokens/semanticTokens.ts +114 -0
  209. package/src/stories/Components/Components.stories.ts +7 -3
  210. package/src/stories/DesignTokens/ColorIntegrationExample.vue +2 -3
  211. package/src/stories/DesignTokens/Colors.mdx +6 -8
  212. package/src/stories/DesignTokens/colors.stories.ts +244 -1081
  213. package/src/utils/amelipro/toKebabCase/tests/toKebabCase.spec.ts +52 -0
  214. package/src/utils/formatNir/tests/formatNir.spec.ts +34 -0
  215. package/src/utils/tests/insertAt.spec.ts +44 -0
  216. package/dist/apLightTheme-DS0Uy44H.js +0 -954
  217. package/dist/components/RatingPicker/tests/RatingPicker.a11y.spect.d.ts +0 -1
  218. package/dist/main-BsJ9ec3i.js +0 -38954
  219. package/src/components/BackBtn/tests/__snapshots__/back-btn-custom-bg.snap.png +0 -0
  220. package/src/components/BackBtn/tests/__snapshots__/back-btn-dark-mode.snap.png +0 -0
  221. package/src/components/BackBtn/tests/__snapshots__/back-btn-default.snap.png +0 -0
  222. package/src/components/BackBtn/tests/__snapshots__/back-btn-no-icon.snap.png +0 -0
  223. package/src/components/DatePicker/CalendarMode/tests/DatePicker.events.spec.ts +0 -178
  224. package/src/components/DialogBox/tests/__snapshots__/dialog-box-custom-texts.snap.png +0 -0
  225. package/src/components/DialogBox/tests/__snapshots__/dialog-box-default.snap.png +0 -0
  226. package/src/components/DialogBox/tests/__snapshots__/dialog-box-no-actions.snap.png +0 -0
  227. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated-submenu-open.snap.png +0 -0
  228. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated.snap.png +0 -0
  229. package/src/components/HeaderBar/tests/__snapshots__/header-bar-custom-width.snap.png +0 -0
  230. package/src/components/HeaderBar/tests/__snapshots__/header-bar-default.snap.png +0 -0
  231. package/src/components/HeaderBar/tests/__snapshots__/header-bar-no-sticky.snap.png +0 -0
  232. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-prepend.snap.png +0 -0
  233. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-side.snap.png +0 -0
  234. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-subtitle.snap.png +0 -0
  235. package/src/components/Logo/tests/__snapshots__/logo-avatar.snap.png +0 -0
  236. package/src/components/Logo/tests/__snapshots__/logo-dark.snap.png +0 -0
  237. package/src/components/Logo/tests/__snapshots__/logo-default.snap.png +0 -0
  238. package/src/components/Logo/tests/__snapshots__/logo-no-organism.snap.png +0 -0
  239. package/src/components/Logo/tests/__snapshots__/logo-no-signature.snap.png +0 -0
  240. package/src/components/Logo/tests/__snapshots__/logo-risque-pro.snap.png +0 -0
  241. package/src/components/RangeField/tests/__snapshots__/range-field-custom-bg.snap.png +0 -0
  242. package/src/components/RangeField/tests/__snapshots__/range-field-custom-range.snap.png +0 -0
  243. package/src/components/RangeField/tests/__snapshots__/range-field-default.snap.png +0 -0
  244. package/src/components/RangeField/tests/__snapshots__/range-field-step.snap.png +0 -0
  245. package/src/components/RangeField/tests/__snapshots__/range-field-with-label.snap.png +0 -0
  246. package/src/components/SyAlert/tests/__snapshots__/sy-alert-closable.snap.png +0 -0
  247. package/src/components/SyAlert/tests/__snapshots__/sy-alert-error.snap.png +0 -0
  248. package/src/components/SyAlert/tests/__snapshots__/sy-alert-info.snap.png +0 -0
  249. package/src/components/SyAlert/tests/__snapshots__/sy-alert-success.snap.png +0 -0
  250. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-outlined.snap.png +0 -0
  251. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-tonal.snap.png +0 -0
  252. package/src/components/SyAlert/tests/__snapshots__/sy-alert-warning.snap.png +0 -0
  253. /package/src/components/RatingPicker/tests/{RatingPicker.a11y.spect.ts → RatingPicker.a11y.spec.ts} +0 -0
@@ -7,12 +7,13 @@
7
7
  useDateInputEditing,
8
8
  useDateAutoClamp,
9
9
  useDateTextField,
10
+ useDatePickerValidationBridge,
10
11
  } from '../composables'
11
12
  import { ref, computed, watch, nextTick, onMounted, toRefs } from 'vue'
12
13
  import SyTextField from '../../Customs/SyTextField/SyTextField.vue'
13
14
  import dayjs from 'dayjs'
14
15
  import customParseFormat from 'dayjs/plugin/customParseFormat'
15
- import { useValidation, type ValidationRule, type ValidationResult } from '@/composables/validation/useValidation'
16
+ import type { ValidationRule, ValidationResult } from '@/composables/validation/useValidation'
16
17
  import { useValidatable } from '@/composables/validation/useValidatable'
17
18
  import { useDateFormat } from '@/composables/date/useDateFormatDayjs'
18
19
  import { DATE_PICKER_MESSAGES } from '../constants/messages'
@@ -48,8 +49,10 @@
48
49
  required?: boolean
49
50
  showSuccessMessages?: boolean
50
51
  title?: string | false
52
+ /** @internal Désactive la validation interne quand utilisé dans un parent avec validation */
53
+ skipInternalValidation?: boolean
51
54
  }>(), {
52
- autoClamp: true,
55
+ autoClamp: false,
53
56
  bgColor: 'white',
54
57
  customRules: () => [],
55
58
  customWarningRules: () => [],
@@ -74,6 +77,7 @@
74
77
  required: false,
75
78
  showSuccessMessages: true,
76
79
  title: false,
80
+ skipInternalValidation: false,
77
81
  })
78
82
 
79
83
  const emit = defineEmits<{
@@ -98,13 +102,35 @@
98
102
 
99
103
  /**
100
104
  * =====================
101
- * Validation setup (safe wrapper for readonly, reactive to toggles)
105
+ * Validation setup (using DatePickerValidationBridge)
102
106
  * =====================
103
107
  */
104
- const baseValidation = useValidation({
108
+ const selectedDates = ref<DateObjectValue>(null)
109
+ const currentRangeIsValid = ref(true)
110
+ const getRangeValidationError = ref('')
111
+ const isUpdatingFromInternal = ref(false)
112
+
113
+ // Quand skipInternalValidation est true, on utilise le readonlyValidation (pas de validation active)
114
+ // pour éviter la double validation avec le parent
115
+ const shouldUseInternalValidation = computed(() => !props.skipInternalValidation && !readonly.value)
116
+
117
+ const bridgeValidation = useDatePickerValidationBridge({
105
118
  showSuccessMessages: props.showSuccessMessages,
106
- fieldIdentifier: props.label || props.placeholder,
107
119
  disableErrorHandling: props.disableErrorHandling,
120
+ noCalendar: true,
121
+ required: props.required,
122
+ displayRange: props.displayRange,
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Compatibility with legacy rule format
124
+ customRules: computed(() => shouldUseInternalValidation.value ? props.customRules as any : []),
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Compatibility with legacy rule format
126
+ customWarningRules: computed(() => shouldUseInternalValidation.value ? props.customWarningRules as any : []),
127
+ selectedDates,
128
+ isUpdatingFromInternal,
129
+ currentRangeIsValid,
130
+ getRangeValidationError,
131
+ skipValidationWhenReadonly: true,
132
+ readonly: readonly,
133
+ fieldIdentifier: props.label || props.placeholder || 'Date',
108
134
  })
109
135
 
110
136
  const readonlyValidation = {
@@ -122,39 +148,56 @@
122
148
  } as ValidationResult),
123
149
  }
124
150
 
125
- const validationApi = computed(() => (readonly.value ? readonlyValidation : baseValidation))
126
-
127
151
  const errors = computed({
128
- get: () => validationApi.value.errors.value,
129
- set: (value) => { validationApi.value.errors.value = value },
152
+ get: () => readonly.value ? readonlyValidation.errors.value : bridgeValidation.errors.value,
153
+ set: (value) => {
154
+ if (!readonly.value) bridgeValidation.errors.value = value
155
+ },
130
156
  })
131
157
  const warnings = computed({
132
- get: () => validationApi.value.warnings.value,
133
- set: (value) => { validationApi.value.warnings.value = value },
158
+ get: () => readonly.value ? readonlyValidation.warnings.value : bridgeValidation.warnings.value,
159
+ set: (value) => {
160
+ if (!readonly.value) bridgeValidation.warnings.value = value
161
+ },
134
162
  })
135
163
  const successes = computed({
136
- get: () => validationApi.value.successes.value,
137
- set: (value) => { validationApi.value.successes.value = value },
164
+ get: () => readonly.value ? readonlyValidation.successes.value : bridgeValidation.successes.value,
165
+ set: (value) => {
166
+ if (!readonly.value) bridgeValidation.successes.value = value
167
+ },
138
168
  })
139
169
  const hasError = computed(() => {
140
- const api = validationApi.value
141
- // baseValidation exposes a computed hasError, readonly stub exposes a ref
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- computed/ref dual shape
143
- return (api as any).hasError?.value ?? api.errors.value.length > 0
170
+ if (readonly.value) return false
171
+ return bridgeValidation.errors.value.length > 0
144
172
  })
145
173
 
146
- const clearValidation = () => validationApi.value.clearValidation()
174
+ const clearValidation = () => {
175
+ if (readonly.value) {
176
+ readonlyValidation.clearValidation()
177
+ }
178
+ else {
179
+ bridgeValidation.clearValidation()
180
+ }
181
+ }
147
182
 
148
183
  const validateField = async (
149
184
  value: unknown,
150
185
  rules?: ValidationRule[],
151
186
  warningRules?: ValidationRule[],
152
- ): Promise<ValidationResult> => await validationApi.value.validateField(value, rules, warningRules)
187
+ ): Promise<ValidationResult> => {
188
+ if (readonly.value) {
189
+ return readonlyValidation.validateField()
190
+ }
191
+ return await bridgeValidation.validateField(value, rules, warningRules)
192
+ }
153
193
 
154
194
  // Agrégation des erreurs internes et externes
155
195
  const errorMessages = computed(() => [...errors.value, ...props.externalErrorMessages])
156
196
  const warningMessages = warnings
157
- const displaySuccesses = computed(() => (validationApi.value as typeof baseValidation).displaySuccesses?.value ?? [])
197
+ const displaySuccesses = computed(() => {
198
+ if (readonly.value) return []
199
+ return (bridgeValidation.validation as { displaySuccesses?: { value: string[] } }).displaySuccesses?.value ?? []
200
+ })
158
201
  const successMessages = displaySuccesses
159
202
 
160
203
  /**
@@ -176,7 +219,6 @@
176
219
  * Range input + validations
177
220
  * =====================
178
221
  */
179
- const selectedDates = ref<DateObjectValue>(null)
180
222
  const {
181
223
  handleRangeInput,
182
224
  resetState,
@@ -187,14 +229,22 @@
187
229
  handlePaste: handlePasteRange,
188
230
  } = useDateRangeInput(displayFormat.value, isRange.value, parseDate, formatDate)
189
231
 
190
- const { currentRangeIsValid, getRangeValidationError } = useDateRangeValidation(selectedDates, isRange.value)
232
+ // Note: currentRangeIsValid et getRangeValidationError sont déjà définis pour le Bridge
233
+ // On met juste à jour les refs depuis useDateRangeValidation
234
+ const rangeValidation = useDateRangeValidation(selectedDates, isRange.value)
235
+ watch(() => rangeValidation.currentRangeIsValid.value, (v) => {
236
+ currentRangeIsValid.value = v
237
+ })
238
+ watch(() => rangeValidation.getRangeValidationError.value, (v) => {
239
+ getRangeValidationError.value = v
240
+ })
191
241
 
192
242
  /**
193
243
  * =====================
194
244
  * Format + manual validation
195
245
  * =====================
196
246
  */
197
- const isUpdatingFromInternal = ref(false)
247
+ // isUpdatingFromInternal est déjà déclaré plus haut pour le Bridge
198
248
  const isFocused = ref(false)
199
249
  const hasInteracted = ref(false)
200
250
  const ariaLabel = ref(props.label || props.placeholder || DATE_PICKER_MESSAGES.LABEL_DEFAULT)
@@ -523,6 +573,19 @@
523
573
  const nextCursorPosition = nextEditableIndex(dateFormat, editPosition + 1)
524
574
  updateDateValue(updatedDateText, nextCursorPosition)
525
575
  }
576
+ else if (isEditingLeftDate && separatorIndex !== -1) {
577
+ const rightStartPosition = nextEditableIndex(dateFormat, 0)
578
+ const updatedRightDateText = overwriteAt(rightDateText, rightStartPosition, keyboardEvent.key)
579
+ const nextCursorPosition = nextEditableIndex(dateFormat, rightStartPosition + 1)
580
+
581
+ isOverwriteEditing.value = true
582
+ inputValue.value = `${leftDateText}${rangeSeparator}${updatedRightDateText}`
583
+ requestAnimationFrame(() => {
584
+ const absoluteCursorPosition = separatorIndex + rangeSeparator.length + nextCursorPosition
585
+ inputElement.setSelectionRange(absoluteCursorPosition, absoluteCursorPosition)
586
+ isOverwriteEditing.value = false
587
+ })
588
+ }
526
589
  return
527
590
  }
528
591
 
@@ -659,6 +722,30 @@
659
722
  else handlePasteSingle(evt)
660
723
  }
661
724
 
725
+ function applyAutoClampOnCurrentInput(syncModel = true): boolean {
726
+ if (!props.autoClamp || !inputValue.value) return false
727
+
728
+ const clamped = clampIfNeeded(inputValue.value)
729
+ if (clamped === inputValue.value) return false
730
+
731
+ inputValue.value = clamped
732
+ if (!syncModel) return true
733
+
734
+ // Sync model après clamp uniquement si la valeur a changé.
735
+ isFormatting.value = true
736
+ if (isRange.value) {
737
+ const [startDate, endDate] = parseRangeInput(inputValue.value)
738
+ if (startDate && endDate) emitModel([toReturnFormat(startDate), toReturnFormat(endDate)])
739
+ else if (startDate) emit('date-selected', toReturnFormat(startDate))
740
+ }
741
+ else {
742
+ const parsedDate = parseDate(inputValue.value, displayFormat.value)
743
+ if (parsedDate) emitModel(returnFormat.value !== displayFormat.value ? toReturnFormat(parsedDate) : formatDate(parsedDate, displayFormat.value))
744
+ }
745
+
746
+ return true
747
+ }
748
+
662
749
  async function onFocus() {
663
750
  isFocused.value = true
664
751
  // Si aucun chiffre n'a été saisi (champ vide ou squelette), bootstrap et place le caret au début
@@ -684,6 +771,14 @@
684
771
  return
685
772
  }
686
773
 
774
+ // Le mode overwrite désactive le clamp pendant la frappe pour préserver le curseur.
775
+ // On l'applique donc avant la validation au blur, sinon une date comme 31/04
776
+ // sort en erreur avant d'atteindre la logique d'autoClamp.
777
+ // isFormatting bloque le watcher inputValue pour éviter une double émission du modèle.
778
+ isFormatting.value = true
779
+ applyAutoClampOnCurrentInput(false)
780
+ isFormatting.value = false
781
+
687
782
  if (inputValue.value) {
688
783
  const formatValidationResult = validateDateFormatForSingleOrRange(inputValue.value)
689
784
  const customRulesValidationResult = await safeValidateField(inputValue.value, computed(() => props.customRules).value, computed(() => props.customWarningRules).value)
@@ -729,26 +824,6 @@
729
824
  }
730
825
  }
731
826
 
732
- // autoClamp au blur
733
- if (props.autoClamp) {
734
- const clamped = clampIfNeeded(inputValue.value)
735
- if (clamped !== inputValue.value) {
736
- inputValue.value = clamped
737
-
738
- // Sync model après clamp uniquement si la valeur a changé
739
- isFormatting.value = true
740
- if (isRange.value) {
741
- const [startDate, endDate] = parseRangeInput(inputValue.value)
742
- if (startDate && endDate) emitModel([toReturnFormat(startDate), toReturnFormat(endDate)])
743
- else if (startDate) emit('date-selected', toReturnFormat(startDate))
744
- }
745
- else {
746
- const parsedDate = parseDate(inputValue.value, displayFormat.value)
747
- if (parsedDate) emitModel(returnFormat.value !== displayFormat.value ? toReturnFormat(parsedDate) : formatDate(parsedDate, displayFormat.value))
748
- }
749
- }
750
- }
751
-
752
827
  runRules(inputValue.value)
753
828
 
754
829
  // Release isFormatting after the current microtask so that
@@ -906,7 +981,7 @@
906
981
  })).validateDates()
907
982
  }
908
983
  finally {
909
- setTimeout(() => (isUpdatingFromInternal.value = false), 0)
984
+ queueMicrotask(() => (isUpdatingFromInternal.value = false))
910
985
  }
911
986
 
912
987
  if (result.isComplete && result.dates[1]) {
@@ -924,7 +999,7 @@
924
999
 
925
1000
  emit('input', result.formattedValue)
926
1001
  if (result.cursorPosition !== undefined && !isHandlingBackspace.value) {
927
- setTimeout(() => inputEl?.setSelectionRange(result.cursorPosition!, result.cursorPosition!), 0)
1002
+ queueMicrotask(() => inputEl?.setSelectionRange(result.cursorPosition!, result.cursorPosition!))
928
1003
  }
929
1004
  }
930
1005
  else {
@@ -1024,7 +1099,7 @@
1024
1099
  successes,
1025
1100
  })).validateDates()
1026
1101
  }
1027
- finally { setTimeout(() => (isUpdatingFromInternal.value = false), 0) }
1102
+ finally { queueMicrotask(() => (isUpdatingFromInternal.value = false)) }
1028
1103
  inputValue.value = formatRangeForDisplay(sd, ed)
1029
1104
  runRules(inputValue.value)
1030
1105
  }
@@ -1154,10 +1229,10 @@
1154
1229
  }
1155
1230
 
1156
1231
  :deep(.v-field) {
1157
- color: rgb(var(--v-theme-borderWarning)) !important;
1232
+ color: rgb(var(--v-theme-warning)) !important;
1158
1233
 
1159
1234
  .v-field__outline {
1160
- color: rgb(var(--v-theme-borderWarning)) !important;
1235
+ color: rgb(var(--v-theme-warning)) !important;
1161
1236
  }
1162
1237
  }
1163
1238
 
@@ -1165,7 +1240,7 @@
1165
1240
  opacity: 1 !important;
1166
1241
 
1167
1242
  .v-messages__message {
1168
- color: rgb(var(--v-theme-borderWarning)) !important;
1243
+ color: rgb(var(--v-theme-warning)) !important;
1169
1244
  }
1170
1245
  }
1171
1246
  }
@@ -1173,11 +1248,11 @@
1173
1248
  .error-field {
1174
1249
  :deep(.v-input__control),
1175
1250
  :deep(.v-messages__message) {
1176
- color: rgb(var(--v-theme-textError)) !important;
1251
+ color: rgb(var(--v-theme-error)) !important;
1177
1252
  }
1178
1253
 
1179
1254
  .v-field--active & {
1180
- color: rgb(var(--v-theme-borderError)) !important;
1255
+ color: rgb(var(--v-theme-error)) !important;
1181
1256
  }
1182
1257
  }
1183
1258
 
@@ -1189,10 +1264,10 @@
1189
1264
  }
1190
1265
 
1191
1266
  :deep(.v-field) {
1192
- color: rgb(var(--v-theme-borderSuccess)) !important;
1267
+ color: rgb(var(--v-theme-onSuccessVariant)) !important;
1193
1268
 
1194
1269
  .v-field__outline {
1195
- color: rgb(var(--v-theme-borderSuccess)) !important;
1270
+ color: rgb(var(--v-theme-onSuccessVariant)) !important;
1196
1271
  }
1197
1272
  }
1198
1273
 
@@ -1200,7 +1275,7 @@
1200
1275
  opacity: 1 !important;
1201
1276
 
1202
1277
  .v-messages__message {
1203
- color: rgb(var(--v-theme-borderSuccess)) !important;
1278
+ color: rgb(var(--v-theme-onSuccessVariant)) !important;
1204
1279
  }
1205
1280
  }
1206
1281
  }
@@ -8,6 +8,13 @@ describe('DateTextInput.clean', () => {
8
8
  props: { label: 'Date', ...props },
9
9
  })
10
10
 
11
+ const typeDigits = async (input: ReturnType<ReturnType<typeof mountComponent>['find']>, digits: string) => {
12
+ for (const digit of digits) {
13
+ await input.trigger('keydown', { key: digit })
14
+ await flushPromises()
15
+ }
16
+ }
17
+
11
18
  it('renders a single-date text field by default', () => {
12
19
  const wrapper = mountComponent({
13
20
  label: 'Date',
@@ -32,6 +39,18 @@ describe('DateTextInput.clean', () => {
32
39
  const emitted = wrapper.emitted('update:model-value')
33
40
  expect(emitted).toBeTruthy()
34
41
  expect(emitted && emitted[0]?.[0]).toBe('01/01/2025')
42
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy()
43
+ })
44
+
45
+ it('forwards externalErrorMessages to the underlying text field', () => {
46
+ const wrapper = mountComponent({
47
+ label: 'Date',
48
+ format: 'DD/MM/YYYY',
49
+ externalErrorMessages: ['Erreur externe de contrat DateTextInput'],
50
+ })
51
+
52
+ const textField = wrapper.findComponent(SyTextField)
53
+ expect(textField.props('errorMessages')).toEqual(['Erreur externe de contrat DateTextInput'])
35
54
  })
36
55
 
37
56
  it('formats modelValue according to dateFormatReturn in single mode', async () => {
@@ -51,6 +70,103 @@ describe('DateTextInput.clean', () => {
51
70
  expect(emitted && emitted[0]?.[0]).toBe('2025-01-01')
52
71
  })
53
72
 
73
+ it('auto-clamps invalid day on blur in single mode', async () => {
74
+ const wrapper = mountComponent({
75
+ label: 'Date',
76
+ format: 'DD/MM/YYYY',
77
+ autoClamp: true,
78
+ })
79
+
80
+ const input = wrapper.find('input')
81
+ await input.setValue('31/04/2025')
82
+ await input.trigger('blur')
83
+ await flushPromises()
84
+
85
+ expect(input.element.value).toBe('30/04/2025')
86
+ const emitted = wrapper.emitted('update:model-value')
87
+ expect(emitted).toBeTruthy()
88
+ expect(emitted && emitted[emitted.length - 1]?.[0]).toBe('30/04/2025')
89
+ })
90
+
91
+ it('auto-clamps a masked keyboard input on blur in single mode', async () => {
92
+ const wrapper = mountComponent({
93
+ label: 'Date',
94
+ format: 'DD/MM/YYYY',
95
+ autoClamp: true,
96
+ })
97
+
98
+ const input = wrapper.find('input')
99
+ await typeDigits(input, '31042025')
100
+ await input.trigger('blur')
101
+ await flushPromises()
102
+
103
+ expect(input.element.value).toBe('30/04/2025')
104
+ const emitted = wrapper.emitted('update:model-value')
105
+ expect(emitted).toBeTruthy()
106
+ expect(emitted && emitted[emitted.length - 1]?.[0]).toBe('30/04/2025')
107
+ })
108
+
109
+ it.each([
110
+ ['DD-MM-YYYY', '31042025', '30-04-2025'],
111
+ ['YYYY.MM.DD', '20250431', '2025.04.30'],
112
+ ])('auto-clamps masked keyboard input with %s format', async (format, digits, expected) => {
113
+ const wrapper = mountComponent({
114
+ label: 'Date',
115
+ format,
116
+ autoClamp: true,
117
+ })
118
+
119
+ const input = wrapper.find('input')
120
+ await typeDigits(input, digits)
121
+ await input.trigger('blur')
122
+ await flushPromises()
123
+
124
+ expect(input.element.value).toBe(expected)
125
+ const emitted = wrapper.emitted('update:model-value')
126
+ expect(emitted).toBeTruthy()
127
+ expect(emitted && emitted[emitted.length - 1]?.[0]).toBe(expected)
128
+ })
129
+
130
+ it('auto-clamps invalid days on blur in range mode', async () => {
131
+ const wrapper = mountComponent({
132
+ label: 'Plage de dates',
133
+ format: 'DD/MM/YYYY',
134
+ displayRange: true,
135
+ autoClamp: true,
136
+ })
137
+
138
+ const input = wrapper.find('input')
139
+ await input.setValue('31/04/2025 - 29/02/2025')
140
+ await input.trigger('blur')
141
+ await flushPromises()
142
+
143
+ expect(input.element.value).toBe('30/04/2025 - 28/02/2025')
144
+ const emitted = wrapper.emitted('update:model-value')
145
+ expect(emitted).toBeTruthy()
146
+ const last = emitted && emitted[emitted.length - 1]?.[0]
147
+ expect(last).toEqual(['30/04/2025', '28/02/2025'])
148
+ })
149
+
150
+ it('auto-clamps a masked keyboard input on blur in range mode', async () => {
151
+ const wrapper = mountComponent({
152
+ label: 'Plage de dates',
153
+ format: 'DD/MM/YYYY',
154
+ displayRange: true,
155
+ autoClamp: true,
156
+ })
157
+
158
+ const input = wrapper.find('input')
159
+ await typeDigits(input, '2902202531042025')
160
+ await input.trigger('blur')
161
+ await flushPromises()
162
+
163
+ expect(input.element.value).toBe('28/02/2025 - 30/04/2025')
164
+ const emitted = wrapper.emitted('update:model-value')
165
+ expect(emitted).toBeTruthy()
166
+ const last = emitted && emitted[emitted.length - 1]?.[0]
167
+ expect(last).toEqual(['28/02/2025', '30/04/2025'])
168
+ })
169
+
54
170
  it('validates on submit for required single date', async () => {
55
171
  const wrapper = mountComponent({
56
172
  label: 'Date',
@@ -378,6 +494,210 @@ describe('DateTextInput.clean', () => {
378
494
  expect((input.element as HTMLInputElement).value).toBe('01/01/2025')
379
495
  })
380
496
 
497
+ it('keyboard Backspace efface le chiffre précédent en mode single (overwrite)', async () => {
498
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY' })
499
+ const input = wrapper.find('input')
500
+ await typeDigits(input, '01')
501
+ await input.trigger('keydown', { key: 'Backspace' })
502
+ await flushPromises()
503
+ // Le masque de saisie doit avoir remplacé le dernier chiffre par '_'
504
+ expect(input.element.value).toContain('_')
505
+ })
506
+
507
+ it('keyboard digit avec sélection remplace la sélection en mode single', async () => {
508
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY' })
509
+ const input = wrapper.find('input')
510
+ await typeDigits(input, '01012025')
511
+ await flushPromises()
512
+ // Simuler une sélection puis un chiffre
513
+ input.element.setSelectionRange(0, 2)
514
+ await input.trigger('keydown', { key: '2' })
515
+ await flushPromises()
516
+ expect(input.element.value).toBeTruthy()
517
+ })
518
+
519
+ it('keyboard Backspace avec sélection efface la sélection en mode single', async () => {
520
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY' })
521
+ const input = wrapper.find('input')
522
+ await typeDigits(input, '01012025')
523
+ await flushPromises()
524
+ input.element.setSelectionRange(0, 2)
525
+ await input.trigger('keydown', { key: 'Backspace' })
526
+ await flushPromises()
527
+ expect(input.element.value).toContain('_')
528
+ })
529
+
530
+ it('readonly : validateField retourne un résultat sans erreur', async () => {
531
+ const wrapper = mountComponent({
532
+ format: 'DD/MM/YYYY',
533
+ readonly: true,
534
+ })
535
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
536
+ const result = await (wrapper.vm as any).validateField('01/01/2025')
537
+ expect(result).toBeDefined()
538
+ expect(result.hasError).toBe(false)
539
+ })
540
+
541
+ it('watcher modelValue met à jour inputValue pour une plage de dates', async () => {
542
+ const wrapper = mountComponent({
543
+ format: 'DD/MM/YYYY',
544
+ displayRange: true,
545
+ modelValue: ['01/01/2025', '10/01/2025'],
546
+ })
547
+ await flushPromises()
548
+ const input = wrapper.find('input')
549
+ expect(input.element.value).toContain('01/01/2025')
550
+ expect(input.element.value).toContain('10/01/2025')
551
+ })
552
+
553
+ it('watcher modelValue met à jour inputValue pour une date unique', async () => {
554
+ const wrapper = mountComponent({
555
+ format: 'DD/MM/YYYY',
556
+ modelValue: '15/06/2025',
557
+ })
558
+ await flushPromises()
559
+ const input = wrapper.find('input')
560
+ expect(input.element.value).toBe('15/06/2025')
561
+ })
562
+
563
+ it('watcher modelValue efface inputValue si la nouvelle valeur est vide', async () => {
564
+ const wrapper = mountComponent({
565
+ format: 'DD/MM/YYYY',
566
+ modelValue: '15/06/2025',
567
+ })
568
+ await flushPromises()
569
+ await wrapper.setProps({ modelValue: '' })
570
+ await flushPromises()
571
+ const input = wrapper.find('input')
572
+ expect(input.element.value).toBe('')
573
+ })
574
+
575
+ it('keyboard Backspace en mode range efface un chiffre', async () => {
576
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY', displayRange: true })
577
+ const input = wrapper.find('input')
578
+ await typeDigits(input, '01012025')
579
+ await input.trigger('keydown', { key: 'Backspace' })
580
+ await flushPromises()
581
+ expect(input.element.value).toContain('_')
582
+ })
583
+
584
+ it('keyboard digit en mode range saisit un chiffre', async () => {
585
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY', displayRange: true })
586
+ const input = wrapper.find('input')
587
+ await typeDigits(input, '01012025')
588
+ await flushPromises()
589
+ expect(input.element.value).toContain('2025')
590
+ })
591
+
592
+ it('watcher modelValue initialise correctement une plage avec dateFormatReturn différent', async () => {
593
+ const wrapper = mountComponent({
594
+ format: 'DD/MM/YYYY',
595
+ dateFormatReturn: 'YYYY-MM-DD',
596
+ displayRange: true,
597
+ modelValue: ['2025-01-01', '2025-01-10'],
598
+ })
599
+ await flushPromises()
600
+ const input = wrapper.find('input')
601
+ expect(input.element.value).toContain('01/01/2025')
602
+ expect(input.element.value).toContain('10/01/2025')
603
+ })
604
+
605
+ it('watcher modelValue initialise une plage avec un seul élément sans crash', async () => {
606
+ const wrapper = mountComponent({
607
+ format: 'DD/MM/YYYY',
608
+ displayRange: true,
609
+ modelValue: ['01/01/2025'],
610
+ })
611
+ // Ne doit pas crasher ; la valeur peut être partiellement affichée
612
+ await flushPromises()
613
+ expect(wrapper.find('input').exists()).toBe(true)
614
+ })
615
+
616
+ it('autoClamp sync model en mode range (735-746)', async () => {
617
+ const wrapper = mountComponent({
618
+ format: 'DD/MM/YYYY',
619
+ displayRange: true,
620
+ autoClamp: true,
621
+ })
622
+ const input = wrapper.find('input')
623
+ await input.setValue('31/04/2025 - 29/02/2025')
624
+ await input.trigger('blur')
625
+ await flushPromises()
626
+ // Les dates doivent être clampées et le modèle émis
627
+ const emitted = wrapper.emitted('update:model-value')
628
+ expect(emitted).toBeTruthy()
629
+ })
630
+
631
+ it('isOverwriteEditing : émission correcte après saisie overwrite en mode range', async () => {
632
+ const wrapper = mountComponent({ format: 'DD/MM/YYYY', displayRange: true })
633
+ const input = wrapper.find('input')
634
+ await typeDigits(input, '0101202510012025')
635
+ await input.trigger('blur')
636
+ await flushPromises()
637
+ const emitted = wrapper.emitted('update:model-value')
638
+ expect(emitted).toBeTruthy()
639
+ })
640
+
641
+ it('autoClamp range blur : toutes les émissions tableau ont la valeur clampée (pas de double émission avec l\'ancienne valeur)', async () => {
642
+ const wrapper = mountComponent({
643
+ label: 'Plage de dates',
644
+ format: 'DD/MM/YYYY',
645
+ displayRange: true,
646
+ autoClamp: true,
647
+ })
648
+
649
+ const input = wrapper.find('input')
650
+ await input.setValue('31/04/2025 - 29/02/2025')
651
+ await input.trigger('blur')
652
+ await flushPromises()
653
+
654
+ const emitted = wrapper.emitted('update:model-value')
655
+ expect(emitted).toBeTruthy()
656
+ const rangeEmissions = emitted!.filter(e => Array.isArray(e[0]))
657
+ expect(rangeEmissions.length).toBeGreaterThanOrEqual(1)
658
+ // Toutes les émissions tableau doivent avoir la valeur clampée — aucune ne doit contenir l'ancienne valeur non-clampée
659
+ for (const emission of rangeEmissions) {
660
+ expect(emission[0]).toEqual(['30/04/2025', '28/02/2025'])
661
+ }
662
+ })
663
+
664
+ it('autoClamp range : le modèle émis pendant la frappe est un tableau même si une seule date est clampée', async () => {
665
+ const wrapper = mountComponent({
666
+ label: 'Plage de dates',
667
+ format: 'DD/MM/YYYY',
668
+ displayRange: true,
669
+ autoClamp: true,
670
+ })
671
+
672
+ const input = wrapper.find('input')
673
+ await typeDigits(input, '3104202501012025')
674
+ await flushPromises()
675
+
676
+ const emitted = wrapper.emitted('update:model-value')
677
+ if (emitted && emitted.length > 0) {
678
+ const last = emitted[emitted.length - 1]?.[0]
679
+ if (last !== null) {
680
+ expect(Array.isArray(last)).toBe(true)
681
+ }
682
+ }
683
+ })
684
+
685
+ it('autoClamp range blur : séparateur sans espaces est passé tel quel sans crash', async () => {
686
+ const wrapper = mountComponent({
687
+ label: 'Plage de dates',
688
+ format: 'DD/MM/YYYY',
689
+ displayRange: true,
690
+ autoClamp: true,
691
+ })
692
+
693
+ const input = wrapper.find('input')
694
+ await input.setValue('31/04/2025-29/02/2025')
695
+ await input.trigger('blur')
696
+ await flushPromises()
697
+
698
+ expect(wrapper.find('input').exists()).toBe(true)
699
+ })
700
+
381
701
  it('restores range from modelValue when disabled and input is cleared', async () => {
382
702
  const wrapper = mountComponent({
383
703
  label: 'Plage de dates',