@cnamts/synapse 1.0.6 → 1.0.7

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 (223) hide show
  1. package/dist/{DateFilter-BlOpwEVq.js → DateFilter-CHDLz2EO.js} +1 -1
  2. package/dist/{NumberFilter-BPUXE4wY.js → NumberFilter-DXNQ4Uls.js} +1 -1
  3. package/dist/{PeriodFilter-B2yx329_.js → PeriodFilter-C8Qf3Jcn.js} +1 -1
  4. package/dist/{SelectFilter-CedKn1oV.js → SelectFilter-B2Ejs4Cb.js} +1 -1
  5. package/dist/{TextFilter-DkhJjRtR.js → TextFilter-CfR5_A1S.js} +1 -1
  6. package/dist/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.d.ts +116 -0
  7. package/dist/components/Amelipro/AmeliproAccordionGroup/types.d.ts +4 -0
  8. package/dist/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.d.ts +220 -0
  9. package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.d.ts +68 -0
  10. package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.d.ts +70 -0
  11. package/dist/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.d.ts +204 -0
  12. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +26 -26
  13. package/dist/components/Amelipro/AmeliproBadge/AmeliproBadge.d.ts +59 -0
  14. package/dist/components/Amelipro/AmeliproBtn/AmeliproBtn.d.ts +3 -3
  15. package/dist/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.d.ts +1 -1
  16. package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarousel.d.ts +214 -0
  17. package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/AmeliproCarouselItem.d.ts +70 -0
  18. package/dist/components/Amelipro/AmeliproCarousel/types.d.ts +7 -0
  19. package/dist/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.d.ts +125 -0
  20. package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +1 -1
  21. package/dist/components/Amelipro/AmeliproIllustratedDataTile/AmeliproIllustratedDataTile.d.ts +2 -2
  22. package/dist/components/Amelipro/AmeliproResultList/AmeliproResultList.d.ts +164 -0
  23. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +27 -27
  24. package/dist/components/Amelipro/AmeliproStateTile/AmeliproStateTile.d.ts +1 -1
  25. package/dist/components/Amelipro/AmeliproTable/AmeliproTable.d.ts +1 -1
  26. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +32 -32
  27. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +6 -6
  28. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +7 -7
  29. package/dist/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.d.ts +1 -1
  30. package/dist/components/Amelipro/AmeliproTooltips/AmeliproTooltips.d.ts +2 -2
  31. package/dist/components/ChipList/ChipList.d.ts +4 -0
  32. package/dist/components/ChipList/locales.d.ts +4 -2
  33. package/dist/components/CookiesSelection/CookiesInformation/CookiesInformation.d.ts +8 -8
  34. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +445 -8
  35. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +2 -0
  36. package/dist/components/Customs/SyTabs/SyTabs.d.ts +71 -0
  37. package/dist/components/Customs/SyTabs/config.d.ts +17 -0
  38. package/dist/components/Customs/SyTabs/types.d.ts +11 -0
  39. package/dist/components/Customs/SyTextField/SyTextField.d.ts +9 -9
  40. package/dist/components/DataList/DataList.d.ts +1 -1
  41. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +4811 -240
  42. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +52 -33
  43. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +23 -10
  44. package/dist/components/DatePicker/composables/useDateInputEditing.d.ts +1 -0
  45. package/dist/components/DatePicker/composables/useTodayButton.d.ts +1 -0
  46. package/dist/components/DialogBox/DialogBox.d.ts +219 -0
  47. package/dist/components/HeaderLoading/HeaderLoading.d.ts +27 -0
  48. package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +110 -3
  49. package/dist/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.d.ts +19 -1
  50. package/dist/components/LangBtn/LangBtn.d.ts +2 -2
  51. package/dist/components/NirField/NirField.d.ts +18 -18
  52. package/dist/components/PeriodField/PeriodField.d.ts +10766 -1620
  53. package/dist/components/PhoneField/PhoneField.d.ts +1866 -2
  54. package/dist/components/PhoneField/indicatifs.d.ts +1 -0
  55. package/dist/components/PhoneField/locales.d.ts +1 -0
  56. package/dist/components/RangeField/RangeField.d.ts +1 -1
  57. package/dist/components/RangeField/RangeSlider/RangeSlider.d.ts +1 -1
  58. package/dist/components/SubHeader/SubHeader.d.ts +8 -0
  59. package/dist/components/SubHeader/locales.d.ts +1 -0
  60. package/dist/components/SyTextArea/SyTextArea.d.ts +6 -6
  61. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +5 -4
  62. package/dist/components/Tables/SyTable/SyTable.d.ts +5 -4
  63. package/dist/components/Tables/common/SyTablePagination.d.ts +448 -7
  64. package/dist/components/Tables/common/organizeColumns/OrganizeColumns.d.ts +2 -2
  65. package/dist/components/Tables/common/types.d.ts +2 -0
  66. package/dist/components/index.d.ts +9 -0
  67. package/dist/design-system-v3.js +173 -164
  68. package/dist/design-system-v3.umd.cjs +288 -261
  69. package/dist/{main-BXPFSAB4.js → main-C66C1BkG.js} +12984 -11291
  70. package/dist/style.css +1 -1
  71. package/package.json +1 -1
  72. package/src/assets/amelipro/icons.ts +38 -11
  73. package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.mdx +20 -0
  74. package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.stories.ts +135 -0
  75. package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.vue +107 -0
  76. package/src/components/Amelipro/AmeliproAccordionGroup/__tests__/AmeliproAccordionGroup.spec.ts +37 -0
  77. package/src/components/Amelipro/AmeliproAccordionGroup/__tests__/__snapshots__/AmeliproAccordionGroup.spec.ts.snap +513 -0
  78. package/src/components/Amelipro/AmeliproAccordionGroup/types.d.ts +4 -0
  79. package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.mdx +16 -0
  80. package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.stories.ts +300 -0
  81. package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.vue +288 -0
  82. package/src/components/Amelipro/AmeliproAccordionList/__tests__/AmeliproAccordionList.spec.ts +38 -0
  83. package/src/components/Amelipro/AmeliproAccordionList/__tests__/__snapshots__/AmeliproAccordionList.spec.ts.snap +1712 -0
  84. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.mdx +19 -0
  85. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.stories.ts +68 -0
  86. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.vue +66 -0
  87. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.vue +145 -0
  88. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/__tests__/AmeliproAccordionResultTemplate.spec.ts +24 -0
  89. package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/__tests__/__snapshots__/AmeliproAccordionResultTemplate.spec.ts.snap +127 -0
  90. package/src/components/Amelipro/AmeliproAccordionResult/__tests__/AmeliproAccordionResult.spec.ts +24 -0
  91. package/src/components/Amelipro/AmeliproAccordionResult/__tests__/__snapshots__/AmeliproAccordionResult.spec.ts.snap +123 -0
  92. package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.mdx +20 -0
  93. package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.stories.ts +273 -0
  94. package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.vue +275 -0
  95. package/src/components/Amelipro/AmeliproAccordionResultList/__tests__/AmeliproAccordionResultList.spec.ts +38 -0
  96. package/src/components/Amelipro/AmeliproAccordionResultList/__tests__/__snapshots__/AmeliproAccordionResultList.spec.ts.snap +1593 -0
  97. package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.mdx +15 -0
  98. package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.stories.ts +54 -0
  99. package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.vue +76 -0
  100. package/src/components/Amelipro/AmeliproBadge/__tests__/AmeliproBadge.spec.ts +20 -0
  101. package/src/components/Amelipro/AmeliproBadge/__tests__/__snapshots__/AmeliproBadge.spec.ts.snap +19 -0
  102. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.mdx +15 -0
  103. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.stories.ts +191 -0
  104. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.vue +263 -0
  105. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/AmeliproCarouselItem.vue +93 -0
  106. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/__tests__/AmeliproCarouselItem.spec.ts +24 -0
  107. package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/__tests__/__snapshots__/AmeliproCarouselItem.spec.ts.snap +43 -0
  108. package/src/components/Amelipro/AmeliproCarousel/__tests__/AmeliproCarousel.spec.ts +40 -0
  109. package/src/components/Amelipro/AmeliproCarousel/__tests__/__snapshots__/AmeliproCarousel.spec.ts.snap +342 -0
  110. package/src/components/Amelipro/AmeliproCarousel/types.d.ts +8 -0
  111. package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.mdx +18 -0
  112. package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.stories.ts +67 -0
  113. package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.vue +233 -0
  114. package/src/components/Amelipro/AmeliproClickableTile/tests/AmeliproClickableTile.spec.ts +21 -0
  115. package/src/components/Amelipro/AmeliproClickableTile/tests/__snapshots__/AmeliproClickableTile.spec.ts.snap +140 -0
  116. package/src/components/Amelipro/AmeliproHeader/AmeliproHeader.vue +7 -1
  117. package/src/components/Amelipro/AmeliproHeader/tests/__snapshots__/AmeliproHeader.spec.ts.snap +5 -4
  118. package/src/components/Amelipro/AmeliproIcon/iconList.ts +6 -0
  119. package/src/components/Amelipro/AmeliproPageLayout/tests/__snapshots__/AmeliproPageLayout.spec.ts.snap +5 -4
  120. package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.mdx +15 -0
  121. package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.stories.ts +264 -0
  122. package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.vue +231 -0
  123. package/src/components/Amelipro/AmeliproResultList/__tests__/AmeliproResultList.spec.ts +37 -0
  124. package/src/components/Amelipro/AmeliproResultList/__tests__/__snapshots__/AmeliproResultList.spec.ts.snap +434 -0
  125. package/src/components/Amelipro/AmeliproTable/AmeliproTable.vue +6 -5
  126. package/src/components/Amelipro/AmeliproTable/__tests__/__snapshots__/AmeliproTable.spec.ts.snap +23 -26
  127. package/src/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.stories.ts +2 -2
  128. package/src/components/ChipList/Accessibilite.stories.ts +4 -0
  129. package/src/components/ChipList/ChipList.vue +185 -42
  130. package/src/components/ChipList/locales.ts +4 -2
  131. package/src/components/ChipList/tests/chipList.spec.ts +7 -4
  132. package/src/components/Customs/Selects/SelectOverview.mdx +18 -15
  133. package/src/components/Customs/Selects/SyBtnSelect/SyBtnSelect.stories.ts +10 -10
  134. package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +13 -5
  135. package/src/components/Customs/Selects/SySelect/SySelect.vue +108 -37
  136. package/src/components/Customs/SyCheckbox/SyCheckbox.mdx +3 -1
  137. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +165 -0
  138. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +28 -9
  139. package/src/components/Customs/SyTabs/Accessibilite.mdx +309 -0
  140. package/src/components/Customs/SyTabs/SyTabs.mdx +117 -0
  141. package/src/components/Customs/SyTabs/SyTabs.stories.ts +354 -0
  142. package/src/components/Customs/SyTabs/SyTabs.vue +350 -0
  143. package/src/components/Customs/SyTabs/config.ts +17 -0
  144. package/src/components/Customs/SyTabs/tests/SyTabs.spec.ts +425 -0
  145. package/src/components/Customs/SyTabs/types.ts +12 -0
  146. package/src/components/Customs/SyTextField/SyTextField.mdx +3 -0
  147. package/src/components/Customs/SyTextField/SyTextField.stories.ts +142 -1
  148. package/src/components/Customs/SyTextField/SyTextField.vue +19 -16
  149. package/src/components/DataList/DataList.vue +47 -49
  150. package/src/components/DataListGroup/DataListGroup.vue +1 -1
  151. package/src/components/DataListItem/DataListItem.vue +67 -63
  152. package/src/components/DataListItem/tests/DataListItem.spec.ts +2 -2
  153. package/src/components/DatePicker/CalendarMode/DatePicker.stories.ts +3 -3
  154. package/src/components/DatePicker/CalendarMode/DatePicker.vue +49 -13
  155. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +412 -649
  156. package/src/components/DatePicker/DatePickerValidationExample/CalendarMode.stories.ts +215 -0
  157. package/src/components/DatePicker/DatePickerValidationExample/ComplexDatePicker.stories.ts +218 -0
  158. package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.mdx +2 -0
  159. package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.stories.ts +1 -1
  160. package/src/components/DatePicker/DatePickerValidationExample/DateTextInput.stories.ts +218 -0
  161. package/src/components/DatePicker/DatePickerValidationExample/MultiMode.stories.ts +281 -0
  162. package/src/components/DatePicker/DateTextInput/DateTextInput.events.spec.ts +17 -4
  163. package/src/components/DatePicker/DateTextInput/DateTextInput.range.spec.ts +111 -18
  164. package/src/components/DatePicker/DateTextInput/DateTextInput.spec.ts +238 -6
  165. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +716 -757
  166. package/src/components/DatePicker/composables/tests/useDateInputEditing.spec.ts +4 -4
  167. package/src/components/DatePicker/composables/tests/useDisplayedDateString.spec.ts +17 -10
  168. package/src/components/DatePicker/composables/useDateInputEditing.ts +52 -22
  169. package/src/components/DatePicker/composables/useDisplayedDateString.ts +18 -4
  170. package/src/components/DatePicker/composables/useTodayButton.ts +13 -1
  171. package/src/components/DatePicker/utils/dateFormattingUtils.ts +79 -14
  172. package/src/components/DialogBox/DialogBox.stories.ts +12 -0
  173. package/src/components/DialogBox/DialogBox.vue +16 -11
  174. package/src/components/DialogBox/tests/DialogBox.spec.ts +22 -0
  175. package/src/components/HeaderLoading/Accessibilite.mdx +429 -8
  176. package/src/components/HeaderLoading/Accessibilite.stories.ts +4 -0
  177. package/src/components/HeaderLoading/HeaderLoading.vue +59 -0
  178. package/src/components/HeaderNavigationBar/HeaderNavigationBar.mdx +17 -2
  179. package/src/components/HeaderNavigationBar/HeaderNavigationBar.stories.ts +91 -2
  180. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +37 -1
  181. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +276 -21
  182. package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.spec.ts +2 -2
  183. package/src/components/NirField/NirField.mdx +3 -0
  184. package/src/components/NirField/NirField.vue +10 -1
  185. package/src/components/NirField/tests/NirField.spec.ts +81 -0
  186. package/src/components/PasswordField/PasswordField.mdx +3 -0
  187. package/src/components/PeriodField/PeriodField.mdx +2 -0
  188. package/src/components/PeriodField/PeriodField.stories.ts +195 -0
  189. package/src/components/PhoneField/Accessibilite.stories.ts +4 -0
  190. package/src/components/PhoneField/PhoneField.mdx +3 -1
  191. package/src/components/PhoneField/PhoneField.stories.ts +285 -1
  192. package/src/components/PhoneField/PhoneField.vue +228 -95
  193. package/src/components/PhoneField/indicatifs.ts +102 -102
  194. package/src/components/PhoneField/locales.ts +1 -0
  195. package/src/components/PhoneField/tests/PhoneField.spec.ts +429 -2
  196. package/src/components/SkipLink/SkipLink.vue +3 -31
  197. package/src/components/SkipLink/tests/skipLink.spec.ts +0 -21
  198. package/src/components/SubHeader/Accessibilite.stories.ts +8 -0
  199. package/src/components/SubHeader/SubHeader.mdx +1 -0
  200. package/src/components/SubHeader/SubHeader.stories.ts +179 -60
  201. package/src/components/SubHeader/SubHeader.vue +45 -15
  202. package/src/components/SubHeader/locales.ts +1 -0
  203. package/src/components/SyAlert/SyAlert.vue +6 -0
  204. package/src/components/Tables/SyServerTable/SyServerTable.mdx +3 -10
  205. package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +242 -0
  206. package/src/components/Tables/SyServerTable/SyServerTable.vue +29 -10
  207. package/src/components/Tables/SyTable/SyTable.mdx +3 -10
  208. package/src/components/Tables/SyTable/SyTable.stories.ts +242 -0
  209. package/src/components/Tables/SyTable/SyTable.vue +2 -0
  210. package/src/components/Tables/common/SyTablePagination.vue +13 -6
  211. package/src/components/Tables/common/tests/SyTablePagination.spec.ts +157 -0
  212. package/src/components/Tables/common/types.ts +2 -0
  213. package/src/components/index.ts +9 -0
  214. package/src/composables/useFilterable/useFilterable.ts +10 -0
  215. package/src/designTokens/tokens/amelipro/apColors.ts +1 -1
  216. package/src/designTokens/tokens/cnam/cnamSemantic.ts +3 -3
  217. package/src/stories/Components/Components.stories.ts +1 -1
  218. package/src/stories/GuideDuDev/FormValidationGuide.mdx +342 -0
  219. package/src/stories/Templates/Templates.stories.ts +1 -1
  220. package/src/utils/functions/ameliproColors/ameliproColors.ts +1 -1
  221. package/dist/components/DataList/locales.d.ts +0 -3
  222. package/src/components/DataList/locales.ts +0 -3
  223. package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +0 -266
@@ -1,62 +1,74 @@
1
1
  <script lang="ts" setup>
2
- import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, type ComponentPublicInstance, type Ref } from 'vue'
2
+ import {
3
+ ref,
4
+ computed,
5
+ watch,
6
+ onMounted,
7
+ onBeforeUnmount,
8
+ nextTick,
9
+ type ComponentPublicInstance,
10
+ type Ref,
11
+ } from 'vue'
12
+ import {
13
+ useDateInitialization,
14
+ type DateInput,
15
+ type DateValue,
16
+ } from '@/composables/date/useDateInitializationDayjs'
17
+ import { useAsteriskDisplay, useDateFormatValidation, useDatePickerViewMode, useDatePickerVisibility, useDateRangeValidation, useDateSelection, useDateValidation, useDisplayedDateString, useIconState, useInputBlurHandler, useManualDateValidation, useMonthButtonCustomization, useTodayButton } from '../composables'
18
+ import dayjs from 'dayjs'
3
19
  import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
4
20
  import DateTextInput from '../DateTextInput/DateTextInput.vue'
5
21
  import { VDatePicker } from 'vuetify/components'
6
22
  import { useInputHandler } from '../composables/useInputHandler'
7
23
  import { useValidation } from '@/composables/validation/useValidation'
8
24
  import { useDateFormat } from '@/composables/date/useDateFormatDayjs'
9
- import { useDateInitialization, type DateInput, type DateValue } from '@/composables/date/useDateInitializationDayjs'
10
25
  import type { DateObjectValue } from '../types'
11
26
  import { useDatePickerAccessibility } from '@/composables/date/useDatePickerAccessibility'
12
27
  import { DATE_PICKER_MESSAGES } from '../constants/messages'
13
- import { useMonthButtonCustomization } from '../composables'
14
28
  import { mdiCalendar } from '@mdi/js'
15
29
  import { useHolidayDay } from '@/composables/date/useHolidayDay'
16
- import {
17
- useTodayButton,
18
- useDatePickerViewMode,
19
- useDateSelection,
20
- useDateRangeValidation,
21
- useDateFormatValidation,
22
- useIconState,
23
- useDateValidation,
24
- useManualDateValidation,
25
- useInputBlurHandler,
26
- useDatePickerVisibility,
27
- useDisplayedDateString,
28
- useAsteriskDisplay,
29
- } from '../composables'
30
-
31
- import dayjs from 'dayjs'
32
30
  import customParseFormat from 'dayjs/plugin/customParseFormat'
33
31
 
34
- // Initialiser les plugins dayjs
35
32
  dayjs.extend(customParseFormat)
36
33
 
37
34
  const { parseDate, formatDate } = useDateFormat()
38
35
  const { initializeSelectedDates } = useDateInitialization()
39
36
  const { updateAccessibility } = useDatePickerAccessibility()
40
37
 
41
- // Variables pour suivre le mois et l'année actuellement affichés dans le CalendarMode
38
+ /**
39
+ * Utils
40
+ */
41
+ const withInternalUpdate = (fn: () => void) => {
42
+ try {
43
+ isUpdatingFromInternal.value = true
44
+ fn()
45
+ }
46
+ finally {
47
+ queueMicrotask(() => (isUpdatingFromInternal.value = false))
48
+ }
49
+ }
50
+
51
+ const unifyAfterCalendarUpdate = async () => {
52
+ if (!isDatePickerVisible.value) return
53
+ await nextTick()
54
+ customizeMonthButton()
55
+ markHolidayDays()
56
+ }
57
+
58
+ /**
59
+ * Calendar current month / year
60
+ */
42
61
  const currentMonth = ref<string | null>(null)
43
62
  const currentYear = ref<string | null>(null)
44
63
  const currentMonthName = ref<string | null>(null)
45
64
  const currentYearName = ref<string | null>(null)
46
65
 
47
66
  const onUpdateMonth = (month: string) => {
48
- // Éviter les mises à jour inutiles si le mois n'a pas changé
49
67
  if (currentMonth.value === month) return
50
-
51
68
  currentMonth.value = month
52
69
  currentMonthName.value = dayjs().month(parseInt(month, 10)).format('MMMM')
53
70
  handleMonthUpdate()
54
- nextTick(() => {
55
- if (isDatePickerVisible.value) {
56
- customizeMonthButton()
57
- markHolidayDays()
58
- }
59
- })
71
+ unifyAfterCalendarUpdate()
60
72
  }
61
73
 
62
74
  const onUpdateYear = (year: string) => {
@@ -64,132 +76,102 @@
64
76
  currentYear.value = year
65
77
  currentYearName.value = year
66
78
 
67
- // Gérer le changement d'année entre décembre et janvier
68
- if (parseInt(year) > parseInt(oldYear || '0') && parseInt(currentMonth.value || '0') === 11) {
79
+ const curMonth = parseInt(currentMonth.value ?? '0', 10)
80
+ const newYear = parseInt(year, 10)
81
+ const prevYear = parseInt(oldYear ?? '0', 10)
82
+
83
+ // Bridges Dec -> Jan and Jan -> Dec when navigating years
84
+ if (newYear > prevYear && curMonth === 11) {
69
85
  currentMonth.value = '0'
70
86
  currentMonthName.value = dayjs().month(0).format('MMMM')
71
87
  }
72
- else if (parseInt(year) < parseInt(oldYear || '0') && parseInt(currentMonth.value || '0') === 0) {
88
+ else if (newYear < prevYear && curMonth === 0) {
73
89
  currentMonth.value = '11'
74
90
  currentMonthName.value = dayjs().month(11).format('MMMM')
75
91
  }
76
92
 
77
93
  handleYearUpdate()
78
94
  handleMonthUpdate()
79
- nextTick(() => {
80
- if (isDatePickerVisible.value) {
81
- customizeMonthButton()
82
- markHolidayDays()
83
- }
84
- })
95
+ unifyAfterCalendarUpdate()
85
96
  }
86
97
 
87
- // Fonction pour gérer les dates sélectionnées depuis le DateTextInput
88
- const handleDateSelected = (value: DateValue) => {
89
- // Mettre à jour le modèle avec la nouvelle valeur
90
- updateModel(value)
91
-
92
- // Mettre à jour les dates sélectionnées
93
- if (value === null) {
94
- selectedDates.value = null
95
- }
96
- else if (Array.isArray(value)) {
97
- // Pour les plages de dates
98
- const dateObjects = value.map((dateStr) => {
99
- return parseDate(dateStr, props.dateFormatReturn || props.format)
100
- }).filter(Boolean) as Date[]
101
- selectedDates.value = dateObjects
102
- }
103
- else {
104
- // Pour une date unique
105
- const dateObject = parseDate(value, props.dateFormatReturn || props.format)
106
- selectedDates.value = dateObject
107
- }
108
-
109
- // Émettre l'événement date-selected
110
- emit('date-selected', value)
111
- }
112
-
113
- const props = withDefaults(defineProps<{
114
- modelValue?: DateInput
115
- label?: string
116
- placeholder?: string
117
- format?: string
118
- dateFormatReturn?: string
119
- isBirthDate?: boolean
120
- birthDate?: boolean // Alias pour isBirthDate pour compatibilité avec l'attribut kebab-case birth-date
121
- showWeekNumber?: boolean
122
- required?: boolean
123
- displayRange?: boolean
124
- displayIcon?: boolean
125
- displayAppendIcon?: boolean
126
- displayPrependIcon?: boolean
127
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock Axios headers
128
- customRules?: { type: string, options: any }[]
129
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock Axios headers
130
- customWarningRules?: { type: string, options: any }[]
131
- disabled?: boolean
132
- noIcon?: boolean
133
- noCalendar?: boolean
134
- isOutlined?: boolean
135
- readonly?: boolean
136
- width?: string
137
- disableErrorHandling?: boolean
138
- showSuccessMessages?: boolean
139
- bgColor?: string
140
- textFieldActivator?: boolean
141
- displayTodayButton?: boolean
142
- displayWeekendDays?: boolean
143
- displayHolidayDays?: boolean
144
- displayAsterisk?: boolean
145
- period?: {
146
- min?: string
147
- max?: string
148
- }
149
- autoClamp?: boolean
150
- }>(), {
151
- modelValue: undefined,
152
- placeholder: DATE_PICKER_MESSAGES.PLACEHOLDER_DEFAULT,
153
- format: DATE_PICKER_MESSAGES.FORMAT_DEFAULT,
154
- dateFormatReturn: '',
155
- isBirthDate: false,
156
- birthDate: false,
157
- showWeekNumber: false,
158
- required: false,
159
- displayRange: false,
160
- displayIcon: true,
161
- displayAppendIcon: false,
162
- displayPrependIcon: true,
163
- customRules: () => [],
164
- customWarningRules: () => [],
165
- disabled: false,
166
- noIcon: false,
167
- noCalendar: false,
168
- isOutlined: true,
169
- readonly: false,
170
- width: '100%',
171
- disableErrorHandling: false,
172
- showSuccessMessages: true,
173
- bgColor: 'white',
174
- textFieldActivator: false,
175
- displayTodayButton: true,
176
- displayWeekendDays: true,
177
- displayHolidayDays: true,
178
- displayAsterisk: false,
179
- period: () => ({
180
- min: '',
181
- max: '',
182
- }),
183
- autoClamp: false,
184
- label: DATE_PICKER_MESSAGES.PLACEHOLDER_DEFAULT,
185
- })
186
-
187
- // Computed properties pour period
188
- const minDate = computed(() =>
189
- props.period?.min || dayjs().subtract(10, 'year').format(props.format),
190
- )
191
- const maxDate = computed(() =>
192
- props.period?.max || dayjs().add(10, 'year').format(props.format),
98
+ /**
99
+ * Props / Emits
100
+ */
101
+ const props = withDefaults(
102
+ defineProps<{
103
+ modelValue?: DateInput
104
+ label?: string
105
+ placeholder?: string
106
+ format?: string
107
+ dateFormatReturn?: string
108
+ isBirthDate?: boolean
109
+ birthDate?: boolean
110
+ showWeekNumber?: boolean
111
+ required?: boolean
112
+ displayRange?: boolean
113
+ displayIcon?: boolean
114
+ displayAppendIcon?: boolean
115
+ displayPrependIcon?: boolean
116
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- sorry
117
+ customRules?: { type: string, options: any }[]
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- sorry
119
+ customWarningRules?: { type: string, options: any }[]
120
+ disabled?: boolean
121
+ noIcon?: boolean
122
+ noCalendar?: boolean
123
+ isOutlined?: boolean
124
+ readonly?: boolean
125
+ width?: string
126
+ disableErrorHandling?: boolean
127
+ showSuccessMessages?: boolean
128
+ bgColor?: string
129
+ textFieldActivator?: boolean
130
+ displayTodayButton?: boolean
131
+ displayWeekendDays?: boolean
132
+ displayHolidayDays?: boolean
133
+ displayAsterisk?: boolean
134
+ period?: {
135
+ min?: string
136
+ max?: string
137
+ }
138
+ autoClamp?: boolean
139
+ isValidateOnBlur?: boolean
140
+ }>(),
141
+ {
142
+ modelValue: undefined,
143
+ placeholder: DATE_PICKER_MESSAGES.PLACEHOLDER_DEFAULT,
144
+ format: DATE_PICKER_MESSAGES.FORMAT_DEFAULT,
145
+ dateFormatReturn: '',
146
+ isBirthDate: false,
147
+ birthDate: false,
148
+ showWeekNumber: false,
149
+ required: false,
150
+ displayRange: false,
151
+ displayIcon: true,
152
+ displayAppendIcon: false,
153
+ displayPrependIcon: true,
154
+ customRules: () => [],
155
+ customWarningRules: () => [],
156
+ disabled: false,
157
+ noIcon: false,
158
+ noCalendar: false,
159
+ isOutlined: true,
160
+ readonly: false,
161
+ width: '100%',
162
+ disableErrorHandling: false,
163
+ showSuccessMessages: true,
164
+ bgColor: 'white',
165
+ textFieldActivator: false,
166
+ displayTodayButton: true,
167
+ displayWeekendDays: true,
168
+ displayHolidayDays: true,
169
+ displayAsterisk: false,
170
+ period: () => ({ min: '', max: '' }),
171
+ autoClamp: false,
172
+ label: DATE_PICKER_MESSAGES.PLACEHOLDER_DEFAULT,
173
+ isValidateOnBlur: true,
174
+ },
193
175
  )
194
176
 
195
177
  const emit = defineEmits<{
@@ -201,14 +183,16 @@
201
183
  (e: 'date-selected', value: DateValue): void
202
184
  }>()
203
185
 
204
- // Utilisation du composable pour la saisie manuelle des plages de dates
205
- const selectedDates = ref<Date | (Date | null)[] | null>(
206
- initializeSelectedDates(props.modelValue as DateInput | null, props.format, props.dateFormatReturn),
207
- )
208
-
209
- // Utilisation du composable pour la validation des plages de dates
210
- const { currentRangeIsValid, getRangeValidationError } = useDateRangeValidation(selectedDates as Ref<DateObjectValue>, props.displayRange)
186
+ /**
187
+ * Derived values
188
+ */
189
+ const returnFormat = computed(() => props.dateFormatReturn || props.format)
190
+ const minDate = computed(() => props.period?.min || dayjs().subtract(10, 'year').format(props.format))
191
+ const maxDate = computed(() => props.period?.max || dayjs().add(10, 'year').format(props.format))
211
192
 
193
+ /**
194
+ * Validation + messages
195
+ */
212
196
  const isDatePickerVisible = ref(false)
213
197
  const validation = useValidation({
214
198
  showSuccessMessages: props.showSuccessMessages,
@@ -218,21 +202,33 @@
218
202
  disableErrorHandling: props.disableErrorHandling,
219
203
  })
220
204
  const { errors, warnings, successes, validateField, clearValidation } = validation
221
-
222
205
  const errorMessages = computed(() => errors.value)
223
206
  const warningMessages = computed(() => warnings.value)
224
207
  const successMessages = computed(() => successes.value)
225
- const displayFormattedDate = ref('')
226
208
 
227
- const textInputValue = ref<string>('')
209
+ const getMessageClasses = () => ({
210
+ 'dp-width': true,
211
+ 'v-messages__message--success': successMessages.value.length > 0,
212
+ 'v-messages__message--error': errorMessages.value.length > 0,
213
+ 'v-messages__message--warning': warningMessages.value.length > 0 && errorMessages.value.length < 1,
214
+ })
228
215
 
229
- // Variables pour la gestion de la saisie manuelle
216
+ /**
217
+ * Selection state
218
+ */
219
+ const selectedDates = ref<Date | (Date | null)[] | null>(
220
+ initializeSelectedDates(props.modelValue as DateInput | null, props.format, props.dateFormatReturn),
221
+ )
222
+ const { currentRangeIsValid, getRangeValidationError } = useDateRangeValidation(
223
+ selectedDates as Ref<DateObjectValue>,
224
+ props.displayRange,
225
+ )
226
+
227
+ const textInputValue = ref('')
228
+ const displayFormattedDate = ref('')
230
229
  const isManualInputActive = ref(false)
231
230
  const isFormatting = ref(false)
232
-
233
- // Variable pour éviter les mises à jour récursives
234
231
  const isUpdatingFromInternal = ref(false)
235
- // Variable pour suivre si l'utilisateur a interagi avec le champ
236
232
  const hasInteracted = ref(false)
237
233
 
238
234
  const { validateDateFormat, isDateComplete } = useDateFormatValidation({
@@ -243,8 +239,7 @@
243
239
  disableErrorHandling: props.disableErrorHandling,
244
240
  })
245
241
 
246
- // pour valider les dates
247
- const { validateDates, validateOnSubmit } = useDateValidation({
242
+ const { validateDates } = useDateValidation({
248
243
  noCalendar: props.noCalendar,
249
244
  required: props.required,
250
245
  displayRange: props.displayRange,
@@ -262,373 +257,287 @@
262
257
  successes,
263
258
  })
264
259
 
265
- // Fonction centralisée pour mettre à jour le modèle
266
260
  const updateModel = (value: DateValue) => {
267
- // Éviter les mises à jour inutiles
261
+ // Prevent redundant emits
268
262
  if (JSON.stringify(value) === JSON.stringify(props.modelValue)) return
269
-
270
- try {
271
- isUpdatingFromInternal.value = true
272
- emit('update:modelValue', value)
273
- }
274
- finally {
275
- // S'assurer que le flag est toujours réinitialisé
276
- setTimeout(() => {
277
- isUpdatingFromInternal.value = false
278
- }, 0)
279
- }
263
+ withInternalUpdate(() => emit('update:modelValue', value))
280
264
  }
281
265
 
282
- // Watcher pour mettre à jour le modèle lorsque les dates sélectionnées changent
283
- watch(selectedDates, (newValue) => {
284
- // Valider les dates
285
- validateDates()
266
+ // Keep and expose this so consumers can listen to `date-selected`
267
+ const handleDateSelected = (value: DateValue) => {
268
+ if (props.readonly) return
286
269
 
287
- // Mettre à jour le modèle si nécessaire
288
- if (newValue !== null) {
289
- updateModel(formattedDate.value)
270
+ // 1) Update v-model
271
+ updateModel(value)
290
272
 
291
- // Mettre à jour textInputValue pour le DateTextInput
292
- try {
293
- isUpdatingFromInternal.value = true
294
- if (Array.isArray(newValue)) {
295
- // Pour les plages de dates, utiliser la première date
296
- if (newValue.length > 0) {
297
- textInputValue.value = formatDate(newValue[0], props.format)
298
- }
299
- }
300
- else {
301
- // Pour une date unique
302
- textInputValue.value = formatDate(newValue, props.format)
303
- }
304
- }
305
- finally {
306
- setTimeout(() => {
307
- isUpdatingFromInternal.value = false
308
- }, 0)
309
- }
273
+ // 2) Sync internal selection
274
+ if (value === null) {
275
+ selectedDates.value = null
276
+ }
277
+ else if (Array.isArray(value)) {
278
+ const dateObjects = value
279
+ .map(dateStr => parseDate(dateStr, returnFormat.value))
280
+ .filter(Boolean) as Date[]
281
+ selectedDates.value = dateObjects
310
282
  }
311
283
  else {
312
- updateModel(null)
313
- // Réinitialiser textInputValue
314
- textInputValue.value = ''
284
+ const dateObject = parseDate(value, returnFormat.value)
285
+ selectedDates.value = dateObject
286
+ }
287
+
288
+ // 3) Re-emit upward
289
+ emit('date-selected', value)
290
+ }
291
+
292
+ // Display helpers
293
+ const displayFormattedDateComputed = computed(() => {
294
+ if (!selectedDates.value) return null
295
+ if (Array.isArray(selectedDates.value)) {
296
+ if (selectedDates.value.length >= 2)
297
+ return `${formatDate(selectedDates.value[0], props.format)} - ${formatDate(
298
+ selectedDates.value[selectedDates.value.length - 1],
299
+ props.format,
300
+ )}`
301
+ return formatDate(selectedDates.value[0], props.format)
315
302
  }
303
+ return formatDate(selectedDates.value, props.format)
316
304
  })
317
305
 
318
- const getMessageClasses = () => ({
319
- 'dp-width': true,
320
- 'v-messages__message--success': successMessages.value.length > 0,
321
- 'v-messages__message--error': errorMessages.value.length > 0,
322
- 'v-messages__message--warning': warningMessages.value.length > 0 && errorMessages.value.length < 1,
306
+ watch(displayFormattedDateComputed, (newValue) => {
307
+ if (!props.noCalendar && newValue) displayFormattedDate.value = newValue
323
308
  })
324
309
 
325
- // Déclaration des variables qui seront initialisées par le composable useDateSelection
310
+ // Range handling
326
311
  const rangeBoundaryDates = ref<[Date | null, Date | null] | null>(null)
327
- // Initialisation des variables après la déclaration de selectedDates
328
- const dateSelectionResult = useDateSelection(
329
- parseDate,
330
- selectedDates,
331
- props.format,
332
- props.displayRange,
312
+ const dateSelectionResult = useDateSelection(parseDate, selectedDates, props.format, props.displayRange)
313
+ watch(
314
+ () => dateSelectionResult.rangeBoundaryDates.value,
315
+ (newValue) => {
316
+ rangeBoundaryDates.value = newValue
317
+ },
318
+ { immediate: true },
333
319
  )
334
320
 
335
- watch(() => dateSelectionResult.rangeBoundaryDates.value, (newValue) => {
336
- rangeBoundaryDates.value = newValue
337
- }, { immediate: true })
338
-
339
- // Assignation des fonctions et variables retournées par le composable
340
- // Utiliser une fonction pour wrapper updateSelectedDates afin de maintenir la compatibilité avec le template
341
321
  const updateSelectedDates = (date: Date | null) => {
342
- // Avant de mettre à jour la date, vérifier qu'elle est valide selon nos règles personnalisées
343
322
  if (date !== null) {
344
- // Appliquer les règles personnalisées directement à la date sélectionnée
345
323
  const validationResult = validateField(date, props.customRules, props.customWarningRules)
346
-
347
- // Si la date est invalide selon nos règles, ne pas mettre à jour et afficher l'erreur
348
324
  if (validationResult.hasError) {
349
- // Mettre à jour les messages d'erreur
350
325
  errors.value = validationResult.state.errors
351
- return // Ne pas continuer la mise à jour
326
+ return
352
327
  }
353
328
  }
354
-
355
- // Si la date est valide ou null, on poursuit normalement
356
329
  dateSelectionResult.updateSelectedDates(date)
357
-
358
- // Forcer une validation immédiate après la mise à jour des dates
359
- // pour s'assurer que les messages s'affichent
360
- setTimeout(() => {
361
- validateDates(true)
362
- }, 0)
330
+ // Validate immediately to surface messages
331
+ queueMicrotask(() => validateDates(true))
363
332
  }
364
- // generateDateRange est maintenant utilisé via le composable useInputHandler
365
- // Synchroniser notre référence locale avec celle du composable
366
- watch(() => dateSelectionResult.rangeBoundaryDates.value, (newValue) => {
367
- rangeBoundaryDates.value = newValue
368
- }, { immediate: true })
369
333
 
370
- // Date(s) formatée(s) en chaîne de caractères pour la valeur de retour
334
+ // Formatted value for model
371
335
  const formattedDate = computed<DateValue>(() => {
372
336
  if (!selectedDates.value) return ''
337
+ const rf = returnFormat.value
373
338
 
374
- const returnFormat = props.dateFormatReturn || props.format
375
-
376
- // Pour les plages de dates, utiliser rangeBoundaryDates s'il est disponible
377
339
  if (props.displayRange && rangeBoundaryDates.value) {
378
340
  return [
379
- formatDate(rangeBoundaryDates.value[0], returnFormat),
380
- formatDate(rangeBoundaryDates.value[1], returnFormat),
341
+ formatDate(rangeBoundaryDates.value[0], rf),
342
+ formatDate(rangeBoundaryDates.value[1], rf),
381
343
  ] as [string, string]
382
344
  }
383
- else if (Array.isArray(selectedDates.value)) {
384
- if (selectedDates.value.length >= 2) {
345
+ if (Array.isArray(selectedDates.value)) {
346
+ if (selectedDates.value.length >= 2)
385
347
  return [
386
- formatDate(selectedDates.value[0], returnFormat),
387
- formatDate(selectedDates.value[selectedDates.value.length - 1], returnFormat),
348
+ formatDate(selectedDates.value[0], rf),
349
+ formatDate(selectedDates.value[selectedDates.value.length - 1], rf),
388
350
  ] as [string, string]
389
- }
390
351
  return ''
391
352
  }
392
-
393
- return formatDate(selectedDates.value, returnFormat)
353
+ return formatDate(selectedDates.value, rf)
394
354
  })
395
355
 
396
- watch(formattedDate, (newValue) => {
397
- if (!newValue || newValue === '') {
398
- textInputValue.value = ''
399
- }
400
- else if (typeof newValue === 'string') {
401
- // Si on a un format de retour différent, on doit convertir la date
402
- if (props.dateFormatReturn) {
403
- const date = parseDate(newValue, props.dateFormatReturn)
404
- if (date) {
405
- textInputValue.value = formatDate(date, props.format)
406
- }
356
+ watch(
357
+ formattedDate,
358
+ (newValue) => {
359
+ if (!newValue || newValue === '') {
360
+ textInputValue.value = ''
361
+ return
407
362
  }
408
- else {
409
- textInputValue.value = newValue
363
+ if (typeof newValue === 'string') {
364
+ if (props.dateFormatReturn) {
365
+ const date = parseDate(newValue, returnFormat.value)
366
+ if (date) textInputValue.value = formatDate(date, props.format)
367
+ }
368
+ else {
369
+ textInputValue.value = newValue
370
+ }
410
371
  }
372
+ },
373
+ { immediate: true },
374
+ )
375
+
376
+ watch(selectedDates, (newValue) => {
377
+ validateDates()
378
+ if (newValue !== null) {
379
+ updateModel(formattedDate.value)
380
+ withInternalUpdate(() => {
381
+ if (Array.isArray(newValue)) {
382
+ if (newValue.length > 0) textInputValue.value = formatDate(newValue[0], props.format)
383
+ }
384
+ else {
385
+ textInputValue.value = formatDate(newValue, props.format)
386
+ }
387
+ })
388
+ }
389
+ else {
390
+ updateModel(null)
391
+ textInputValue.value = ''
411
392
  }
412
- }, { immediate: true })
393
+ })
413
394
 
395
+ // Handle manual typing sync → model/selection
414
396
  watch(textInputValue, (newValue) => {
415
- // Éviter les mises à jour récursives
416
397
  if (isUpdatingFromInternal.value) return
417
-
418
- // Parse la date avec le format d'affichage
419
398
  const date = parseDate(newValue, props.format)
420
399
  if (date) {
421
- // Si on a un format de retour, formater la date dans ce format
422
- const formattedValue = props.dateFormatReturn
423
- ? formatDate(date, props.dateFormatReturn)
424
- : formatDate(date, props.format)
400
+ const formattedValue = props.dateFormatReturn ? formatDate(date, returnFormat.value) : formatDate(date, props.format)
425
401
  updateModel(formattedValue)
426
-
427
- // Mettre à jour selectedDates sans déclencher de watchers supplémentaires
428
- try {
429
- isUpdatingFromInternal.value = true
402
+ withInternalUpdate(() => {
430
403
  selectedDates.value = date
431
- // Mettre à jour l'affichage formaté
432
404
  displayFormattedDate.value = formatDate(date, props.format)
433
- }
434
- finally {
435
- setTimeout(() => {
436
- isUpdatingFromInternal.value = false
437
- }, 0)
438
- }
405
+ })
439
406
  }
440
407
  else if (newValue) {
441
- // Même si la date n'est pas valide, conserver la valeur saisie
442
- // pour éviter que la date ne disparaisse
443
408
  updateModel(newValue)
444
- // Mettre à jour l'affichage formaté pour qu'il corresponde à ce qui est saisi
445
- try {
446
- isUpdatingFromInternal.value = true
447
- displayFormattedDate.value = newValue
448
- }
449
- finally {
450
- setTimeout(() => {
451
- isUpdatingFromInternal.value = false
452
- }, 0)
453
- }
409
+ withInternalUpdate(() => (displayFormattedDate.value = newValue))
454
410
  }
455
411
  else {
456
412
  updateModel(null)
457
- // Réinitialiser l'affichage formaté
458
- try {
459
- isUpdatingFromInternal.value = true
413
+ withInternalUpdate(() => {
460
414
  displayFormattedDate.value = ''
461
415
  selectedDates.value = null
462
- }
463
- finally {
464
- setTimeout(() => {
465
- isUpdatingFromInternal.value = false
466
- }, 0)
467
- }
468
- }
469
- })
470
-
471
- // Date(s) formatée(s) en chaîne de caractères pour l'affichage
472
- const displayFormattedDateComputed = computed(() => {
473
- if (!selectedDates.value) return null
474
-
475
- if (Array.isArray(selectedDates.value)) {
476
- if (selectedDates.value.length >= 2) {
477
- return `${formatDate(selectedDates.value[0], props.format)} - ${formatDate(
478
- selectedDates.value[selectedDates.value.length - 1],
479
- props.format,
480
- )}`
481
- }
482
- return formatDate(selectedDates.value[0], props.format)
483
- }
484
-
485
- return formatDate(selectedDates.value, props.format)
486
- })
487
-
488
- watch(displayFormattedDateComputed, (newValue) => {
489
- if (!props.noCalendar && newValue) {
490
- displayFormattedDate.value = newValue
416
+ })
491
417
  }
492
418
  })
493
419
 
494
420
  /**
495
- * Met à jour l'affichage formaté de la date lorsqu'une date est sélectionnée dans le calendrier
421
+ * UI updates after picking
496
422
  */
497
423
  const updateDisplayFormattedDate = () => {
498
- // Utiliser setTimeout pour s'assurer que toutes les mises à jour sont terminées
499
- setTimeout(() => {
500
- // Mettre à jour l'affichage formaté pour qu'il corresponde à la date sélectionnée
424
+ queueMicrotask(() => {
501
425
  let formattedValue = ''
502
426
 
503
- // Gérer la fermeture du CalendarMode en fonction du mode et de l'état de sélection
504
427
  if (props.displayRange) {
505
- // Priorité à rangeBoundaryDates pour les plages
506
- if (rangeBoundaryDates.value && rangeBoundaryDates.value[0] && rangeBoundaryDates.value[1]) {
507
- // Les deux dates de la plage sont disponibles dans rangeBoundaryDates
428
+ if (rangeBoundaryDates.value?.[0] && rangeBoundaryDates.value?.[1]) {
508
429
  const startDate = formatDate(rangeBoundaryDates.value[0], props.format)
509
430
  const endDate = formatDate(rangeBoundaryDates.value[1], props.format)
510
-
511
- // Formater l'affichage de la plage
512
431
  formattedValue = `${startDate} - ${endDate}`
513
- displayFormattedDate.value = formattedValue
514
- textInputValue.value = formattedValue
515
-
516
- // Mettre à jour le modèle avec les dates formatées
432
+ displayFormattedDate.value = textInputValue.value = formattedValue
517
433
  const formattedDates = [
518
- formatDate(rangeBoundaryDates.value[0], props.dateFormatReturn || props.format),
519
- formatDate(rangeBoundaryDates.value[1], props.dateFormatReturn || props.format),
434
+ formatDate(rangeBoundaryDates.value[0], returnFormat.value),
435
+ formatDate(rangeBoundaryDates.value[1], returnFormat.value),
520
436
  ] as [string, string]
521
-
522
437
  updateModel(formattedDates)
523
438
  emit('date-selected', formattedDates)
524
-
525
- // Les deux dates de la plage sont sélectionnées, fermer le CalendarMode
526
439
  isDatePickerVisible.value = false
527
440
  emit('closed')
528
441
  }
529
- // Fallback sur selectedDates si rangeBoundaryDates n'est pas complet
530
442
  else if (Array.isArray(selectedDates.value) && selectedDates.value.length >= 2) {
531
- // Émettre l'événement date-selected avec les dates formatées
532
443
  const formattedDates = [
533
444
  formatDate(selectedDates.value[0], props.format),
534
445
  formatDate(selectedDates.value[selectedDates.value.length - 1], props.format),
535
446
  ] as [string, string]
536
-
537
447
  formattedValue = `${formattedDates[0]} - ${formattedDates[1]}`
538
- displayFormattedDate.value = formattedValue
539
- textInputValue.value = formattedValue
540
-
541
- // Mettre à jour le modèle avec les dates formatées
448
+ displayFormattedDate.value = textInputValue.value = formattedValue
542
449
  updateModel(formattedDates)
543
450
  emit('date-selected', formattedDates)
544
-
545
- // Les deux dates de la plage sont sélectionnées, fermer le CalendarMode
546
451
  isDatePickerVisible.value = false
547
452
  emit('closed')
548
453
  }
549
454
  else {
550
- // Utiliser la valeur calculée par le computed si disponible
551
455
  formattedValue = displayFormattedDateComputed.value || ''
552
- displayFormattedDate.value = formattedValue
553
- textInputValue.value = formattedValue
456
+ displayFormattedDate.value = textInputValue.value = formattedValue
554
457
  }
555
458
  }
556
459
  else {
557
- // En mode date unique
558
460
  formattedValue = displayFormattedDateComputed.value || ''
559
- displayFormattedDate.value = formattedValue
560
- textInputValue.value = formattedValue
561
-
562
- // En mode date unique, fermer le CalendarMode après sélection
461
+ displayFormattedDate.value = textInputValue.value = formattedValue
563
462
  isDatePickerVisible.value = false
564
463
  emit('closed')
565
464
  emit('date-selected', formattedDate.value)
566
465
  }
567
466
 
568
- // Valider les dates après mise à jour
569
467
  validateDates()
570
- }, 0) // setTimeout avec délai de 0ms pour s'exécuter après le cycle de rendu actuel
468
+ })
571
469
  }
572
470
 
573
- // Les variables useDateSelection sont maintenant déclarées et initialisées plus haut dans le code
471
+ /**
472
+ * Accessibility (live description during typing)
473
+ */
474
+ const accessibilityDescription = ref(DATE_PICKER_MESSAGES.ARIA_DATE_INPUT)
475
+ const getDateDescription = (dateStr: string, format: string): string => {
476
+ if (!dateStr?.trim()) return 'Aucune date saisie'
477
+ const separator = format.match(/[^DMY]/)?.[0] || '/'
478
+ const dateParts = dateStr.split(separator)
479
+ const formatParts = format.split(separator)
574
480
 
575
- // Fonction pour émettre l'événement blur (utilisée pour les tests)
576
- const emitBlurEvent = () => {
577
- emit('blur')
481
+ let description = 'Date en cours de saisie: '
482
+ for (let i = 0; i < formatParts.length; i++) {
483
+ if (i >= dateParts.length) break
484
+ const part = dateParts[i].trim()
485
+ const formatPart = formatParts[i][0]?.toUpperCase()
486
+ if (!part || part.replace(/_/g, '').length === 0) continue
487
+ if (formatPart === 'D') description += `jour ${part}, `
488
+ else if (formatPart === 'M') description += `mois ${part}, `
489
+ else if (formatPart === 'Y') description += `année ${part}, `
490
+ }
491
+ return description.endsWith(', ') ? description.slice(0, -2) : description
578
492
  }
579
493
 
580
- // Utilisation du composable pour les jours fériés
581
- const { getJoursFeries } = useHolidayDay()
494
+ watch(displayFormattedDate, (newValue) => {
495
+ if (newValue && typeof newValue === 'string') {
496
+ accessibilityDescription.value = getDateDescription(newValue.replace(/_/g, ' '), props.format)
497
+ }
498
+ else {
499
+ accessibilityDescription.value = 'Aucune date saisie'
500
+ }
501
+ })
582
502
 
583
- // Fonction pour marquer les jours fériés dans le calendrier
503
+ /**
504
+ * Holiday marking
505
+ */
506
+ const { getJoursFeries } = useHolidayDay()
584
507
  const markHolidayDays = () => {
585
- // Si l'affichage des jours fériés est désactivé, ne rien faire
586
508
  if (!props.displayHolidayDays) return
587
-
588
- // Attendre que le DOM soit mis à jour
589
509
  nextTick(() => {
590
- // Récupérer l'année et le mois courants
591
- const year = parseInt(currentYear.value || new Date().getFullYear().toString(), 10)
592
- // Utiliser currentMonth.value !== null pour vérifier si la valeur est définie, même si c'est 0
593
- const month = parseInt(currentMonth.value !== null ? currentMonth.value : new Date().getMonth().toString(), 10)
594
-
595
- // Récupérer les jours fériés pour cette année
510
+ const year = parseInt(currentYear.value || String(new Date().getFullYear()), 10)
511
+ const month = parseInt(
512
+ currentMonth.value !== null ? currentMonth.value : String(new Date().getMonth()),
513
+ 10,
514
+ )
596
515
  const joursFeries = getJoursFeries(year)
597
- // Convertir les jours fériés en objets Date
598
516
  const holidayDates = Array.from(joursFeries).map((dateStr) => {
599
517
  const [day, monthStr, yearStr] = dateStr.split('/')
600
518
  return new Date(parseInt(yearStr), parseInt(monthStr) - 1, parseInt(day))
601
519
  })
602
-
603
- // Filtrer les jours fériés pour le mois courant
604
- const monthHolidays = holidayDates.filter((holiday) => {
605
- return holiday.getMonth() === month && holiday.getFullYear() === year
606
- })
607
-
608
- // Pour chaque jour férié, trouver l'élément DOM correspondant et ajouter la classe
520
+ const monthHolidays = holidayDates.filter(h => h.getMonth() === month && h.getFullYear() === year)
609
521
  monthHolidays.forEach((holiday) => {
610
522
  const day = holiday.getDate()
611
523
  const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
612
524
  const dayElements = document.querySelectorAll(`[data-v-date="${dateStr}"]`)
613
- dayElements.forEach((element) => {
614
- element.classList.add('holiday-day')
615
- })
525
+ dayElements.forEach(el => el.classList.add('holiday-day'))
616
526
  })
617
527
  })
618
528
  }
619
529
 
530
+ /**
531
+ * Mount / unmount
532
+ */
533
+ const dateTextInputRef = ref<null | ComponentPublicInstance<typeof DateTextInput>>()
534
+ const dateCalendarTextInputRef = ref<null | ComponentPublicInstance<typeof SyTextField>>()
535
+ const datePickerRef = ref<null | ComponentPublicInstance<typeof VDatePicker>>()
536
+
620
537
  onMounted(() => {
621
538
  document.addEventListener('click', handleClickOutside)
622
-
623
- // Initialiser l'affichage formaté
624
- if (displayFormattedDateComputed.value) {
625
- displayFormattedDate.value = displayFormattedDateComputed.value
626
- }
627
-
628
- // Valider les dates au montage
539
+ if (displayFormattedDateComputed.value) displayFormattedDate.value = displayFormattedDateComputed.value
629
540
  validateDates()
630
-
631
- // Configurer l'observateur pour le bouton du mois
632
541
  setupMonthButtonObserver()
633
542
  })
634
543
 
@@ -636,10 +545,9 @@
636
545
  document.removeEventListener('click', handleClickOutside)
637
546
  })
638
547
 
639
- const dateTextInputRef = ref<null | ComponentPublicInstance<typeof DateTextInput>>()
640
- const dateCalendarTextInputRef = ref<null | ComponentPublicInstance<typeof SyTextField>>()
641
-
642
- // Initialiser le composable useInputHandler pour gérer la saisie des dates
548
+ /**
549
+ * Input handling (text field)
550
+ */
643
551
  const inputHandler = useInputHandler({
644
552
  format: props.format,
645
553
  displayRange: props.displayRange,
@@ -655,77 +563,63 @@
655
563
  isManualInputActive,
656
564
  isUpdatingFromInternal,
657
565
  clearValidation,
658
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- validation rules
659
- validateField: (value, rules, warningRules) => validateField(value, rules as any[], warningRules as any[]),
566
+ validateField: (value, rules, warningRules) =>
567
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- sorry
568
+ validateField(value, rules as any[], warningRules as any[]),
660
569
  updateModel: value => updateModel(value as DateValue),
661
570
  emitInput: value => emit('input', value),
662
571
  inputRef: dateCalendarTextInputRef as Ref<ComponentPublicInstance | null>,
663
572
  })
664
573
 
665
- /**
666
- * Gère l'entrée utilisateur dans le champ de saisie de date
667
- * Adapté pour fonctionner avec DateTextInput qui émet une valeur string au lieu d'un Event
668
- */
669
574
  const handleInput = (eventOrValue: Event | string) => {
670
- // Si c'est un événement standard, utiliser directement
575
+ if (props.readonly) return
576
+
671
577
  if (eventOrValue instanceof Event) {
672
578
  inputHandler.handleInput(eventOrValue)
673
579
  return
674
580
  }
675
581
 
676
- // Si c'est une valeur string (venant du DateTextInput)
677
582
  const inputElement = dateCalendarTextInputRef.value?.$el?.querySelector?.('input')
678
583
  if (!inputElement) return
679
584
 
680
- // Mettre à jour la valeur du modèle directement
681
585
  textInputValue.value = eventOrValue
682
586
 
683
- // Traitement spécifique pour les plages de dates
684
587
  if (props.displayRange && typeof eventOrValue === 'string') {
685
- // Vérifier si la plage est complète (contient un séparateur et deux dates)
686
588
  if (eventOrValue.includes(' - ')) {
687
- const parts = eventOrValue.split(' - ')
688
- const startDateStr = parts[0]?.trim() || ''
689
- const endDateStr = parts[1]?.trim() || ''
690
-
691
- // Si les deux dates sont présentes et valides, mettre à jour le modèle
589
+ const [startDateStr = '', endDateStr = ''] = eventOrValue.split(' - ').map(s => s.trim())
692
590
  if (startDateStr && endDateStr && !endDateStr.includes('_')) {
693
- // Convertir les dates en objets Date
694
591
  const startDate = parseDate(startDateStr, props.format)
695
592
  const endDate = parseDate(endDateStr, props.format)
696
-
697
593
  if (startDate && endDate) {
698
- // Mettre à jour les dates sélectionnées
699
594
  selectedDates.value = [startDate, endDate]
700
- // Valider la plage de dates
701
595
  validateDates()
702
596
  }
703
597
  }
704
598
  }
705
599
  }
706
600
  else {
707
- // Pour une date unique
708
601
  validateDates()
709
602
  }
710
603
  }
711
- const datePickerRef = ref<null | ComponentPublicInstance<typeof VDatePicker>>()
712
604
 
713
- // Utilisation du composable pour personnaliser les boutons du mois et de l'année
605
+ /**
606
+ * Month/year controls customization
607
+ */
714
608
  const { customizeMonthButton, setupMonthButtonObserver } = useMonthButtonCustomization(
715
609
  () => isDatePickerVisible.value,
716
610
  currentMonthName,
717
611
  currentYearName,
718
612
  )
719
613
 
720
- // Utilisation du composable pour gérer le mode d'affichage du CalendarMode
721
- const { currentViewMode, handleViewModeUpdate, handleYearUpdate, handleMonthUpdate, resetViewMode } = useDatePickerViewMode(
722
- // Fonction qui retourne la valeur actuelle de isBirthDate (combinaison de isBirthDate et birthDate)
723
- () => props.isBirthDate || props.birthDate,
724
- )
725
-
726
- // La fonction isDateComplete est maintenant importée du composable useDateFormatValidation
614
+ /**
615
+ * View mode handling
616
+ */
617
+ const { currentViewMode, handleViewModeUpdate, handleYearUpdate, handleMonthUpdate, resetViewMode }
618
+ = useDatePickerViewMode(() => props.isBirthDate || props.birthDate)
727
619
 
728
- // Fonction pour valider la saisie manuelle, similaire à validateRules dans DateTextInput
620
+ /**
621
+ * Manual input validation on blur
622
+ */
729
623
  const { validateManualInput } = useManualDateValidation({
730
624
  format: props.format,
731
625
  required: props.required,
@@ -741,6 +635,8 @@
741
635
  validateField,
742
636
  })
743
637
 
638
+ const emitBlurEvent = () => emit('blur')
639
+
744
640
  const { handleInputBlur } = useInputBlurHandler({
745
641
  format: props.format,
746
642
  dateFormatReturn: props.dateFormatReturn,
@@ -759,44 +655,9 @@
759
655
  emitBlur: emitBlurEvent,
760
656
  })
761
657
 
762
- watch(isDatePickerVisible, async (isVisible) => {
763
- if (!isVisible && props.isBirthDate) {
764
- // Réinitialiser le mode d'affichage au type birthdate
765
- resetViewMode()
766
- }
767
-
768
- if (isVisible) {
769
- // Personnaliser le bouton du mois
770
- customizeMonthButton()
771
-
772
- // Marquer les jours fériés lorsque le calendrier devient visible
773
- if (props.displayHolidayDays) {
774
- markHolidayDays()
775
- }
776
-
777
- // set the focus on the date picker
778
- await nextTick()
779
- const firstButton = datePickerRef.value?.$el?.querySelector?.('button')
780
- if (firstButton) {
781
- firstButton.focus({ preventScroll: true })
782
- }
783
- }
784
- else {
785
- // set the focus on the text input
786
- // wait for VMenu to finish DOM updates & transition
787
- setTimeout(() => {
788
- requestAnimationFrame(() => {
789
- const inputElement = dateCalendarTextInputRef.value?.$el?.querySelector?.('input')
790
- if (inputElement) {
791
- inputElement.focus({ preventScroll: true })
792
- isDatePickerVisible.value = false
793
- }
794
- })
795
- }, 0)
796
- }
797
- })
798
-
799
- // Utilisation du composable useIconState pour déterminer l'icône à afficher
658
+ /**
659
+ * Icon state
660
+ */
800
661
  const { getIcon } = useIconState({
801
662
  noCalendar: props.noCalendar,
802
663
  disableErrorHandling: props.disableErrorHandling,
@@ -805,6 +666,9 @@
805
666
  successMessages,
806
667
  })
807
668
 
669
+ /**
670
+ * Sync from external v-model
671
+ */
808
672
  const syncFromModelValue = (newValue: DateInput | undefined) => {
809
673
  if (!newValue || newValue === '') {
810
674
  selectedDates.value = null
@@ -815,45 +679,40 @@
815
679
  }
816
680
 
817
681
  selectedDates.value = initializeSelectedDates(newValue, props.format, props.dateFormatReturn)
818
-
819
682
  if (selectedDates.value) {
820
683
  const firstDate = Array.isArray(selectedDates.value)
821
684
  ? selectedDates.value[0]
822
685
  : selectedDates.value
823
-
824
686
  textInputValue.value = formatDate(firstDate, props.format)
825
687
  displayFormattedDate.value = displayFormattedDateComputed.value || ''
826
688
  }
827
-
828
689
  validateDates()
829
690
  }
830
691
 
831
- watch(() => props.modelValue, (newValue) => {
832
- if (isUpdatingFromInternal.value) {
833
- if (props.displayRange) {
834
- if (Array.isArray(newValue) && newValue.length >= 2) {
692
+ watch(
693
+ () => props.modelValue,
694
+ (newValue) => {
695
+ if (isUpdatingFromInternal.value) {
696
+ if (props.displayRange) {
697
+ if (Array.isArray(newValue) && newValue.length >= 2) {
698
+ isDatePickerVisible.value = false
699
+ emit('closed')
700
+ }
701
+ }
702
+ else {
835
703
  isDatePickerVisible.value = false
836
704
  emit('closed')
837
705
  }
706
+ return
838
707
  }
839
- else {
840
- isDatePickerVisible.value = false
841
- emit('closed')
842
- }
843
- return
844
- }
845
-
846
- try {
847
- isUpdatingFromInternal.value = true
848
- syncFromModelValue(newValue)
849
- }
850
- finally {
851
- setTimeout(() => {
852
- isUpdatingFromInternal.value = false
853
- }, 0)
854
- }
855
- }, { immediate: true })
708
+ withInternalUpdate(() => syncFromModelValue(newValue))
709
+ },
710
+ { immediate: true },
711
+ )
856
712
 
713
+ /**
714
+ * Menu / visibility & keyboard
715
+ */
857
716
  const {
858
717
  toggleDatePicker,
859
718
  openDatePicker,
@@ -875,115 +734,71 @@
875
734
  emitFocus: () => emit('focus'),
876
735
  })
877
736
 
878
- // Fonctions pour la gestion de la saisie manuelle
879
737
  const formatDateInput = (input: string, cursorPosition?: number): { formatted: string, cursorPos: number } => {
880
- // Conserver uniquement les chiffres de l'entrée
881
738
  const cleanedInput = input.replace(/[^\d]/g, '')
882
-
883
- // Déterminer le séparateur utilisé dans le format
884
739
  const separator = props.format.match(/[^DMY]/)?.[0] || '/'
885
-
886
- // Calculer la position du curseur dans l'entrée nettoyée (sans séparateurs)
887
- // Pour cela, on compte combien de chiffres il y a avant la position du curseur
888
740
  const inputBeforeCursor = input.substring(0, cursorPosition || 0)
889
741
  const digitsBeforeCursor = inputBeforeCursor.replace(/[^\d]/g, '').length
890
742
 
891
- // Construire la chaîne formatée
892
743
  let result = ''
893
744
  let digitIndex = 0
894
-
895
- // Parcourir le format pour placer les chiffres et séparateurs
896
745
  for (let i = 0; i < props.format.length && digitIndex < cleanedInput.length; i++) {
897
746
  const formatChar = props.format[i].toUpperCase()
898
-
899
747
  if (['D', 'M', 'Y'].includes(formatChar)) {
900
- // Ajouter un chiffre
901
748
  result += cleanedInput[digitIndex]
902
749
  digitIndex++
903
750
  }
904
751
  else {
905
- // Ajouter un séparateur
906
752
  result += separator
907
753
  }
908
754
  }
909
755
 
910
- // Calculer la nouvelle position du curseur
911
- // Nombre de chiffres avant le curseur + nombre de séparateurs avant ces chiffres
912
756
  let newCursorPos = digitsBeforeCursor
913
-
914
- // Ajouter les séparateurs qui apparaissent avant la position du curseur
915
757
  for (let i = 0, digitCount = 0; i < props.format.length && digitCount < digitsBeforeCursor; i++) {
916
- if (!['D', 'M', 'Y'].includes(props.format[i].toUpperCase())) {
917
- newCursorPos++
918
- }
919
- else {
920
- digitCount++
921
- }
758
+ if (!['D', 'M', 'Y'].includes(props.format[i].toUpperCase())) newCursorPos++
759
+ else digitCount++
922
760
  }
923
761
 
924
- return {
925
- formatted: result,
926
- cursorPos: Math.min(newCursorPos, result.length),
927
- }
762
+ return { formatted: result, cursorPos: Math.min(newCursorPos, result.length) }
928
763
  }
929
764
 
930
765
  const handleKeydown = (event: KeyboardEvent & { target: HTMLInputElement }) => {
931
- // Déléguer la gestion de l'ouverture du calendrier au composable
932
- if (!props.noCalendar && handleKeyboardNavigation(event)) {
933
- return
934
- }
766
+ if (props.readonly) return
767
+
768
+ if (!props.noCalendar && handleKeyboardNavigation(event)) return
935
769
 
936
- // Gérer la suppression des séparateurs
937
770
  if (event.key === 'Backspace') {
938
771
  const input = event.target
939
- if (!input.selectionStart || input.selectionStart !== input.selectionEnd) {
940
- return
941
- }
942
-
772
+ if (!input.selectionStart || input.selectionStart !== input.selectionEnd) return
943
773
  const cursorPos = input.selectionStart
944
774
  const charBeforeCursor = input.value[cursorPos - 1]
945
775
 
946
776
  if (!/\d/.test(charBeforeCursor)) {
947
- // Si le caractère avant le curseur n'est pas un chiffre, on le supprime aussi
948
- // et on supprime le chiffre avant le séparateur
949
- event.preventDefault() // Empêcher le comportement par défaut
950
-
951
- const newValue = input.value.substring(0, cursorPos - 2)
952
- + input.value.substring(cursorPos)
953
-
954
- // Mettre à jour la valeur
777
+ event.preventDefault()
778
+ const newValue = input.value.substring(0, cursorPos - 2) + input.value.substring(cursorPos)
955
779
  displayFormattedDate.value = newValue
956
-
957
- // Positionner le curseur après un court délai
958
- setTimeout(() => {
780
+ queueMicrotask(() => {
959
781
  const newCursorPos = cursorPos - 2
960
782
  input.setSelectionRange(newCursorPos, newCursorPos)
961
- }, 0)
783
+ })
962
784
  }
963
785
  }
964
786
 
965
- // Gérer les touches de direction pour éviter de se retrouver entre un séparateur et un chiffre
966
787
  if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
967
788
  const input = event.target
968
789
  const cursorPos = input.selectionStart || 0
969
-
970
- // Déterminer le séparateur utilisé dans le format
971
790
  const separator = props.format.match(/[^DMY]/)?.[0] || '/'
972
791
 
973
792
  if (event.key === 'ArrowLeft' && cursorPos > 0) {
974
793
  const charBeforeCursor = input.value[cursorPos - 1]
975
-
976
794
  if (charBeforeCursor === separator) {
977
- // Si on se déplace à gauche et qu'on rencontre un séparateur, sauter par-dessus
978
795
  event.preventDefault()
979
796
  input.setSelectionRange(cursorPos - 2, cursorPos - 2)
980
797
  }
981
798
  }
982
799
  else if (event.key === 'ArrowRight' && cursorPos < input.value.length) {
983
800
  const charAtCursor = input.value[cursorPos]
984
-
985
801
  if (charAtCursor === separator) {
986
- // Si on se déplace à droite et qu'on rencontre un séparateur, sauter par-dessus
987
802
  event.preventDefault()
988
803
  input.setSelectionRange(cursorPos + 2, cursorPos + 2)
989
804
  }
@@ -991,83 +806,34 @@
991
806
  }
992
807
  }
993
808
 
994
- // Variable pour l'accessibilité
995
- const accessibilityDescription = ref(DATE_PICKER_MESSAGES.ARIA_DATE_INPUT)
996
-
997
- // Fonction pour créer une description accessible de la date pour les lecteurs d'écran
998
- const getDateDescription = (dateStr: string, format: string): string => {
999
- // Si la chaîne est vide, retourner un message simple
1000
- if (!dateStr || !dateStr.trim()) {
1001
- return 'Aucune date saisie'
1002
- }
1003
-
1004
- // Déterminer le séparateur utilisé dans le format
1005
- const separator = format.match(/[^DMY]/)?.[0] || '/'
1006
-
1007
- // Extraire les parties de la date
1008
- const dateParts = dateStr.split(separator)
1009
- const formatParts = format.split(separator)
1010
-
1011
- // Créer une description en fonction du format
1012
- let description = 'Date en cours de saisie: '
1013
-
1014
- for (let i = 0; i < formatParts.length; i++) {
1015
- if (i >= dateParts.length) break
1016
-
1017
- const part = dateParts[i].trim()
1018
- const formatPart = formatParts[i].charAt(0).toUpperCase()
1019
-
1020
- // Ignorer les parties vides ou contenant uniquement des placeholders
1021
- if (!part || part.replace(/_/g, '').length === 0) {
1022
- continue
1023
- }
1024
-
1025
- switch (formatPart) {
1026
- case 'D':
1027
- description += `jour ${part}, `
1028
- break
1029
- case 'M':
1030
- description += `mois ${part}, `
1031
- break
1032
- case 'Y':
1033
- description += `année ${part}, `
1034
- break
1035
- }
1036
- }
1037
-
1038
- // Supprimer la virgule finale si elle existe
1039
- return description.endsWith(', ')
1040
- ? description.slice(0, -2)
1041
- : description
1042
- }
1043
-
1044
- // Mettre à jour la description accessible lorsque la valeur affichée change
1045
- watch(displayFormattedDate, (newValue) => {
1046
- if (newValue && typeof newValue === 'string') {
1047
- // Créer une version accessible pour les lecteurs d'écran (sans les caractères de placeholder)
1048
- const accessibleValue = newValue.replace(/_/g, ' ')
1049
-
1050
- // Créer un message descriptif pour le lecteur d'écran
1051
- accessibilityDescription.value = getDateDescription(accessibleValue, props.format)
1052
- }
1053
- else {
1054
- accessibilityDescription.value = 'Aucune date saisie'
1055
- }
1056
- })
1057
-
1058
- const { todayInString, selectToday } = useTodayButton(props)
809
+ /**
810
+ * Today button + labels
811
+ */
812
+ const { todayInString, selectToday, headerDate } = useTodayButton(props)
1059
813
  const { labelWithAsterisk } = useAsteriskDisplay(props)
814
+ const { displayedDateString } = useDisplayedDateString({ selectedDates, rangeBoundaryDates, todayInString })
1060
815
 
1061
- // Utilisation du composable pour l'affichage formaté des dates
1062
- const { displayedDateString } = useDisplayedDateString({
1063
- selectedDates,
1064
- rangeBoundaryDates,
1065
- todayInString,
1066
- })
1067
-
1068
- // Wrapper pour la fonction selectToday du composable
1069
816
  const handleSelectToday = () => {
1070
817
  selectToday(selectedDates)
818
+ const today = new Date()
819
+ const todayMonth = today.getMonth().toString()
820
+ const todayYear = today.getFullYear().toString()
821
+ currentMonth.value = todayMonth
822
+ currentYear.value = todayYear
823
+ currentMonthName.value = dayjs().month(parseInt(todayMonth, 10)).format('MMMM')
824
+ currentYearName.value = todayYear
825
+ }
826
+
827
+ /**
828
+ * Public API
829
+ */
830
+ const validateOnSubmit = (): boolean => {
831
+ if (props.noCalendar) {
832
+ return dateTextInputRef.value?.validateOnSubmit() || false
833
+ }
834
+ const textInputValid = dateCalendarTextInputRef.value?.validateOnSubmit() || false
835
+ validateDates(true)
836
+ return textInputValid && errors.value.length === 0
1071
837
  }
1072
838
 
1073
839
  defineExpose({
@@ -1081,8 +847,8 @@
1081
847
  updateAccessibility,
1082
848
  openDatePicker,
1083
849
  updateDisplayFormattedDate,
1084
- currentMonth, // Exposer le mois actuellement affiché
1085
- currentMonthName, // Exposer le nom du mois actuellement affiché
850
+ currentMonth,
851
+ currentMonthName,
1086
852
  toggleDatePicker,
1087
853
  validateField,
1088
854
  clearValidation,
@@ -1091,20 +857,20 @@
1091
857
  emitBlur: emitBlurEvent,
1092
858
  validateDateFormat,
1093
859
  displayFormattedDate,
860
+ // Expose for consumers
861
+ handleDateSelected,
862
+ resetViewMode,
1094
863
  })
1095
864
  </script>
1096
865
 
1097
866
  <template>
1098
- <div
1099
- class="date-picker-container"
1100
- >
1101
- <!-- Variable pour stocker la description accessible -->
867
+ <div class="date-picker-container">
868
+ <!-- Hidden live region text holder (kept for potential a11y tooling) -->
1102
869
  <span
1103
870
  v-if="false"
1104
871
  ref="accessibilityDescriptionRef"
1105
- >
1106
- {{ accessibilityDescription }}
1107
- </span>
872
+ >{{ accessibilityDescription }}</span>
873
+
1108
874
  <template v-if="props.noCalendar">
1109
875
  <DateTextInput
1110
876
  ref="dateTextInputRef"
@@ -1129,14 +895,15 @@
1129
895
  :bg-color="props.bgColor"
1130
896
  :auto-clamp="props.autoClamp"
1131
897
  :display-asterisk="props.displayAsterisk"
898
+ :is-validate-on-blur="props.isValidateOnBlur"
1132
899
  title="Date text input"
1133
900
  @focus="emit('focus')"
1134
901
  @blur="emit('blur')"
1135
902
  />
1136
903
  </template>
904
+
1137
905
  <template v-else>
1138
906
  <VMenu
1139
- v-if="!props.noCalendar"
1140
907
  v-model="isDatePickerVisible"
1141
908
  activator="parent"
1142
909
  :min-width="0"
@@ -1172,6 +939,7 @@
1172
939
  :bg-color="props.bgColor"
1173
940
  :display-range="props.displayRange"
1174
941
  :display-persistent-placeholder="true"
942
+ :is-validate-on-blur="props.isValidateOnBlur"
1175
943
  :class="[getMessageClasses(), 'label-hidden-on-focus']"
1176
944
  :append-inner-icon="getIcon"
1177
945
  :auto-clamp="props.autoClamp"
@@ -1185,8 +953,9 @@
1185
953
  @append-icon-click="openDatePickerOnIconClick"
1186
954
  />
1187
955
  </template>
956
+
1188
957
  <VDatePicker
1189
- v-if="isDatePickerVisible && !props.noCalendar"
958
+ v-if="isDatePickerVisible"
1190
959
  ref="datePickerRef"
1191
960
  v-model="selectedDates"
1192
961
  color="primary"
@@ -1202,6 +971,7 @@
1202
971
  :custom-warning-rules="props.customWarningRules"
1203
972
  :display-holiday-days="props.displayHolidayDays"
1204
973
  :display-asterisk="props.displayAsterisk"
974
+ :is-validate-on-blur="props.isValidateOnBlur"
1205
975
  @update:model-value="updateDisplayFormattedDate"
1206
976
  @update:view-mode="handleViewModeUpdate"
1207
977
  @update:month="onUpdateMonth"
@@ -1215,7 +985,7 @@
1215
985
  </template>
1216
986
  <template #header>
1217
987
  <h3 class="mx-auto my-auto ml-5 mb-4">
1218
- {{ displayedDateString }}
988
+ {{ selectedDates ? displayedDateString : headerDate }}
1219
989
  </h3>
1220
990
  </template>
1221
991
  <template
@@ -1232,9 +1002,7 @@
1232
1002
  :ripple="false"
1233
1003
  @click="handleSelectToday"
1234
1004
  >
1235
- <VIcon
1236
- class="mr-1"
1237
- >
1005
+ <VIcon class="mr-1">
1238
1006
  {{ mdiCalendar }}
1239
1007
  </VIcon>
1240
1008
  {{ DATE_PICKER_MESSAGES.BUTTON_TODAY }}
@@ -1330,12 +1098,7 @@
1330
1098
  opacity: 1;
1331
1099
  }
1332
1100
 
1333
- :deep(.v-field--dirty) {
1334
- opacity: 1 !important;
1335
-
1336
- --v-medium-emphasis-opacity: 1;
1337
- }
1338
-
1101
+ :deep(.v-field--dirty),
1339
1102
  :deep(.v-field--focused) {
1340
1103
  opacity: 1 !important;
1341
1104
 
@@ -1367,7 +1130,7 @@
1367
1130
  background-color: #afb1b1;
1368
1131
  }
1369
1132
 
1370
- /* div avant la class .v-date-picker-month__day--week-end */
1133
+ /* day before weekend */
1371
1134
  :deep(.weekend .v-date-picker-month__day:has(+ .v-date-picker-month__day--week-end) .v-btn) {
1372
1135
  background-color: #afb1b1;
1373
1136
  }