@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.
- package/dist/{DateFilter-BlOpwEVq.js → DateFilter-CHDLz2EO.js} +1 -1
- package/dist/{NumberFilter-BPUXE4wY.js → NumberFilter-DXNQ4Uls.js} +1 -1
- package/dist/{PeriodFilter-B2yx329_.js → PeriodFilter-C8Qf3Jcn.js} +1 -1
- package/dist/{SelectFilter-CedKn1oV.js → SelectFilter-B2Ejs4Cb.js} +1 -1
- package/dist/{TextFilter-DkhJjRtR.js → TextFilter-CfR5_A1S.js} +1 -1
- package/dist/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.d.ts +116 -0
- package/dist/components/Amelipro/AmeliproAccordionGroup/types.d.ts +4 -0
- package/dist/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.d.ts +220 -0
- package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.d.ts +68 -0
- package/dist/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.d.ts +70 -0
- package/dist/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.d.ts +204 -0
- package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +26 -26
- package/dist/components/Amelipro/AmeliproBadge/AmeliproBadge.d.ts +59 -0
- package/dist/components/Amelipro/AmeliproBtn/AmeliproBtn.d.ts +3 -3
- package/dist/components/Amelipro/AmeliproCaptcha/AmeliproCaptcha.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarousel.d.ts +214 -0
- package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/AmeliproCarouselItem.d.ts +70 -0
- package/dist/components/Amelipro/AmeliproCarousel/types.d.ts +7 -0
- package/dist/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.d.ts +125 -0
- package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproIllustratedDataTile/AmeliproIllustratedDataTile.d.ts +2 -2
- package/dist/components/Amelipro/AmeliproResultList/AmeliproResultList.d.ts +164 -0
- package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +27 -27
- package/dist/components/Amelipro/AmeliproStateTile/AmeliproStateTile.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproTable/AmeliproTable.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +32 -32
- package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +6 -6
- package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +7 -7
- package/dist/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.d.ts +1 -1
- package/dist/components/Amelipro/AmeliproTooltips/AmeliproTooltips.d.ts +2 -2
- package/dist/components/ChipList/ChipList.d.ts +4 -0
- package/dist/components/ChipList/locales.d.ts +4 -2
- package/dist/components/CookiesSelection/CookiesInformation/CookiesInformation.d.ts +8 -8
- package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +445 -8
- package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +2 -0
- package/dist/components/Customs/SyTabs/SyTabs.d.ts +71 -0
- package/dist/components/Customs/SyTabs/config.d.ts +17 -0
- package/dist/components/Customs/SyTabs/types.d.ts +11 -0
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +9 -9
- package/dist/components/DataList/DataList.d.ts +1 -1
- package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +4811 -240
- package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +52 -33
- package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +23 -10
- package/dist/components/DatePicker/composables/useDateInputEditing.d.ts +1 -0
- package/dist/components/DatePicker/composables/useTodayButton.d.ts +1 -0
- package/dist/components/DialogBox/DialogBox.d.ts +219 -0
- package/dist/components/HeaderLoading/HeaderLoading.d.ts +27 -0
- package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +110 -3
- package/dist/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.d.ts +19 -1
- package/dist/components/LangBtn/LangBtn.d.ts +2 -2
- package/dist/components/NirField/NirField.d.ts +18 -18
- package/dist/components/PeriodField/PeriodField.d.ts +10766 -1620
- package/dist/components/PhoneField/PhoneField.d.ts +1866 -2
- package/dist/components/PhoneField/indicatifs.d.ts +1 -0
- package/dist/components/PhoneField/locales.d.ts +1 -0
- package/dist/components/RangeField/RangeField.d.ts +1 -1
- package/dist/components/RangeField/RangeSlider/RangeSlider.d.ts +1 -1
- package/dist/components/SubHeader/SubHeader.d.ts +8 -0
- package/dist/components/SubHeader/locales.d.ts +1 -0
- package/dist/components/SyTextArea/SyTextArea.d.ts +6 -6
- package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +5 -4
- package/dist/components/Tables/SyTable/SyTable.d.ts +5 -4
- package/dist/components/Tables/common/SyTablePagination.d.ts +448 -7
- package/dist/components/Tables/common/organizeColumns/OrganizeColumns.d.ts +2 -2
- package/dist/components/Tables/common/types.d.ts +2 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/design-system-v3.js +173 -164
- package/dist/design-system-v3.umd.cjs +288 -261
- package/dist/{main-BXPFSAB4.js → main-C66C1BkG.js} +12984 -11291
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/amelipro/icons.ts +38 -11
- package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.mdx +20 -0
- package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.stories.ts +135 -0
- package/src/components/Amelipro/AmeliproAccordionGroup/AmeliproAccordionGroup.vue +107 -0
- package/src/components/Amelipro/AmeliproAccordionGroup/__tests__/AmeliproAccordionGroup.spec.ts +37 -0
- package/src/components/Amelipro/AmeliproAccordionGroup/__tests__/__snapshots__/AmeliproAccordionGroup.spec.ts.snap +513 -0
- package/src/components/Amelipro/AmeliproAccordionGroup/types.d.ts +4 -0
- package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.mdx +16 -0
- package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.stories.ts +300 -0
- package/src/components/Amelipro/AmeliproAccordionList/AmeliproAccordionList.vue +288 -0
- package/src/components/Amelipro/AmeliproAccordionList/__tests__/AmeliproAccordionList.spec.ts +38 -0
- package/src/components/Amelipro/AmeliproAccordionList/__tests__/__snapshots__/AmeliproAccordionList.spec.ts.snap +1712 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.mdx +19 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.stories.ts +68 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResult.vue +66 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/AmeliproAccordionResultTemplate.vue +145 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/__tests__/AmeliproAccordionResultTemplate.spec.ts +24 -0
- package/src/components/Amelipro/AmeliproAccordionResult/AmeliproAccordionResultTemplate/__tests__/__snapshots__/AmeliproAccordionResultTemplate.spec.ts.snap +127 -0
- package/src/components/Amelipro/AmeliproAccordionResult/__tests__/AmeliproAccordionResult.spec.ts +24 -0
- package/src/components/Amelipro/AmeliproAccordionResult/__tests__/__snapshots__/AmeliproAccordionResult.spec.ts.snap +123 -0
- package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.mdx +20 -0
- package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.stories.ts +273 -0
- package/src/components/Amelipro/AmeliproAccordionResultList/AmeliproAccordionResultList.vue +275 -0
- package/src/components/Amelipro/AmeliproAccordionResultList/__tests__/AmeliproAccordionResultList.spec.ts +38 -0
- package/src/components/Amelipro/AmeliproAccordionResultList/__tests__/__snapshots__/AmeliproAccordionResultList.spec.ts.snap +1593 -0
- package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.mdx +15 -0
- package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.stories.ts +54 -0
- package/src/components/Amelipro/AmeliproBadge/AmeliproBadge.vue +76 -0
- package/src/components/Amelipro/AmeliproBadge/__tests__/AmeliproBadge.spec.ts +20 -0
- package/src/components/Amelipro/AmeliproBadge/__tests__/__snapshots__/AmeliproBadge.spec.ts.snap +19 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.mdx +15 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.stories.ts +191 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarousel.vue +263 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/AmeliproCarouselItem.vue +93 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/__tests__/AmeliproCarouselItem.spec.ts +24 -0
- package/src/components/Amelipro/AmeliproCarousel/AmeliproCarouselItem/__tests__/__snapshots__/AmeliproCarouselItem.spec.ts.snap +43 -0
- package/src/components/Amelipro/AmeliproCarousel/__tests__/AmeliproCarousel.spec.ts +40 -0
- package/src/components/Amelipro/AmeliproCarousel/__tests__/__snapshots__/AmeliproCarousel.spec.ts.snap +342 -0
- package/src/components/Amelipro/AmeliproCarousel/types.d.ts +8 -0
- package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.mdx +18 -0
- package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.stories.ts +67 -0
- package/src/components/Amelipro/AmeliproClickableTile/AmeliproClickableTile.vue +233 -0
- package/src/components/Amelipro/AmeliproClickableTile/tests/AmeliproClickableTile.spec.ts +21 -0
- package/src/components/Amelipro/AmeliproClickableTile/tests/__snapshots__/AmeliproClickableTile.spec.ts.snap +140 -0
- package/src/components/Amelipro/AmeliproHeader/AmeliproHeader.vue +7 -1
- package/src/components/Amelipro/AmeliproHeader/tests/__snapshots__/AmeliproHeader.spec.ts.snap +5 -4
- package/src/components/Amelipro/AmeliproIcon/iconList.ts +6 -0
- package/src/components/Amelipro/AmeliproPageLayout/tests/__snapshots__/AmeliproPageLayout.spec.ts.snap +5 -4
- package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.mdx +15 -0
- package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.stories.ts +264 -0
- package/src/components/Amelipro/AmeliproResultList/AmeliproResultList.vue +231 -0
- package/src/components/Amelipro/AmeliproResultList/__tests__/AmeliproResultList.spec.ts +37 -0
- package/src/components/Amelipro/AmeliproResultList/__tests__/__snapshots__/AmeliproResultList.spec.ts.snap +434 -0
- package/src/components/Amelipro/AmeliproTable/AmeliproTable.vue +6 -5
- package/src/components/Amelipro/AmeliproTable/__tests__/__snapshots__/AmeliproTable.spec.ts.snap +23 -26
- package/src/components/Amelipro/AmeliproTileBtn/AmeliproTileBtn.stories.ts +2 -2
- package/src/components/ChipList/Accessibilite.stories.ts +4 -0
- package/src/components/ChipList/ChipList.vue +185 -42
- package/src/components/ChipList/locales.ts +4 -2
- package/src/components/ChipList/tests/chipList.spec.ts +7 -4
- package/src/components/Customs/Selects/SelectOverview.mdx +18 -15
- package/src/components/Customs/Selects/SyBtnSelect/SyBtnSelect.stories.ts +10 -10
- package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +13 -5
- package/src/components/Customs/Selects/SySelect/SySelect.vue +108 -37
- package/src/components/Customs/SyCheckbox/SyCheckbox.mdx +3 -1
- package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +165 -0
- package/src/components/Customs/SyCheckbox/SyCheckbox.vue +28 -9
- package/src/components/Customs/SyTabs/Accessibilite.mdx +309 -0
- package/src/components/Customs/SyTabs/SyTabs.mdx +117 -0
- package/src/components/Customs/SyTabs/SyTabs.stories.ts +354 -0
- package/src/components/Customs/SyTabs/SyTabs.vue +350 -0
- package/src/components/Customs/SyTabs/config.ts +17 -0
- package/src/components/Customs/SyTabs/tests/SyTabs.spec.ts +425 -0
- package/src/components/Customs/SyTabs/types.ts +12 -0
- package/src/components/Customs/SyTextField/SyTextField.mdx +3 -0
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +142 -1
- package/src/components/Customs/SyTextField/SyTextField.vue +19 -16
- package/src/components/DataList/DataList.vue +47 -49
- package/src/components/DataListGroup/DataListGroup.vue +1 -1
- package/src/components/DataListItem/DataListItem.vue +67 -63
- package/src/components/DataListItem/tests/DataListItem.spec.ts +2 -2
- package/src/components/DatePicker/CalendarMode/DatePicker.stories.ts +3 -3
- package/src/components/DatePicker/CalendarMode/DatePicker.vue +49 -13
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +412 -649
- package/src/components/DatePicker/DatePickerValidationExample/CalendarMode.stories.ts +215 -0
- package/src/components/DatePicker/DatePickerValidationExample/ComplexDatePicker.stories.ts +218 -0
- package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.mdx +2 -0
- package/src/components/DatePicker/DatePickerValidationExample/DatePickerValidation.stories.ts +1 -1
- package/src/components/DatePicker/DatePickerValidationExample/DateTextInput.stories.ts +218 -0
- package/src/components/DatePicker/DatePickerValidationExample/MultiMode.stories.ts +281 -0
- package/src/components/DatePicker/DateTextInput/DateTextInput.events.spec.ts +17 -4
- package/src/components/DatePicker/DateTextInput/DateTextInput.range.spec.ts +111 -18
- package/src/components/DatePicker/DateTextInput/DateTextInput.spec.ts +238 -6
- package/src/components/DatePicker/DateTextInput/DateTextInput.vue +716 -757
- package/src/components/DatePicker/composables/tests/useDateInputEditing.spec.ts +4 -4
- package/src/components/DatePicker/composables/tests/useDisplayedDateString.spec.ts +17 -10
- package/src/components/DatePicker/composables/useDateInputEditing.ts +52 -22
- package/src/components/DatePicker/composables/useDisplayedDateString.ts +18 -4
- package/src/components/DatePicker/composables/useTodayButton.ts +13 -1
- package/src/components/DatePicker/utils/dateFormattingUtils.ts +79 -14
- package/src/components/DialogBox/DialogBox.stories.ts +12 -0
- package/src/components/DialogBox/DialogBox.vue +16 -11
- package/src/components/DialogBox/tests/DialogBox.spec.ts +22 -0
- package/src/components/HeaderLoading/Accessibilite.mdx +429 -8
- package/src/components/HeaderLoading/Accessibilite.stories.ts +4 -0
- package/src/components/HeaderLoading/HeaderLoading.vue +59 -0
- package/src/components/HeaderNavigationBar/HeaderNavigationBar.mdx +17 -2
- package/src/components/HeaderNavigationBar/HeaderNavigationBar.stories.ts +91 -2
- package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +37 -1
- package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +276 -21
- package/src/components/HeaderNavigationBar/tests/HeaderNavigationBar.spec.ts +2 -2
- package/src/components/NirField/NirField.mdx +3 -0
- package/src/components/NirField/NirField.vue +10 -1
- package/src/components/NirField/tests/NirField.spec.ts +81 -0
- package/src/components/PasswordField/PasswordField.mdx +3 -0
- package/src/components/PeriodField/PeriodField.mdx +2 -0
- package/src/components/PeriodField/PeriodField.stories.ts +195 -0
- package/src/components/PhoneField/Accessibilite.stories.ts +4 -0
- package/src/components/PhoneField/PhoneField.mdx +3 -1
- package/src/components/PhoneField/PhoneField.stories.ts +285 -1
- package/src/components/PhoneField/PhoneField.vue +228 -95
- package/src/components/PhoneField/indicatifs.ts +102 -102
- package/src/components/PhoneField/locales.ts +1 -0
- package/src/components/PhoneField/tests/PhoneField.spec.ts +429 -2
- package/src/components/SkipLink/SkipLink.vue +3 -31
- package/src/components/SkipLink/tests/skipLink.spec.ts +0 -21
- package/src/components/SubHeader/Accessibilite.stories.ts +8 -0
- package/src/components/SubHeader/SubHeader.mdx +1 -0
- package/src/components/SubHeader/SubHeader.stories.ts +179 -60
- package/src/components/SubHeader/SubHeader.vue +45 -15
- package/src/components/SubHeader/locales.ts +1 -0
- package/src/components/SyAlert/SyAlert.vue +6 -0
- package/src/components/Tables/SyServerTable/SyServerTable.mdx +3 -10
- package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +242 -0
- package/src/components/Tables/SyServerTable/SyServerTable.vue +29 -10
- package/src/components/Tables/SyTable/SyTable.mdx +3 -10
- package/src/components/Tables/SyTable/SyTable.stories.ts +242 -0
- package/src/components/Tables/SyTable/SyTable.vue +2 -0
- package/src/components/Tables/common/SyTablePagination.vue +13 -6
- package/src/components/Tables/common/tests/SyTablePagination.spec.ts +157 -0
- package/src/components/Tables/common/types.ts +2 -0
- package/src/components/index.ts +9 -0
- package/src/composables/useFilterable/useFilterable.ts +10 -0
- package/src/designTokens/tokens/amelipro/apColors.ts +1 -1
- package/src/designTokens/tokens/cnam/cnamSemantic.ts +3 -3
- package/src/stories/Components/Components.stories.ts +1 -1
- package/src/stories/GuideDuDev/FormValidationGuide.mdx +342 -0
- package/src/stories/Templates/Templates.stories.ts +1 -1
- package/src/utils/functions/ameliproColors/ameliproColors.ts +1 -1
- package/dist/components/DataList/locales.d.ts +0 -3
- package/src/components/DataList/locales.ts +0 -3
- package/src/components/PhoneField/tests/PhoneField.additional.spec.ts +0 -266
|
@@ -1,62 +1,74 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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 (
|
|
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
|
-
|
|
80
|
-
if (isDatePickerVisible.value) {
|
|
81
|
-
customizeMonthButton()
|
|
82
|
-
markHolidayDays()
|
|
83
|
-
}
|
|
84
|
-
})
|
|
95
|
+
unifyAfterCalendarUpdate()
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
//
|
|
288
|
-
|
|
289
|
-
updateModel(formattedDate.value)
|
|
270
|
+
// 1) Update v-model
|
|
271
|
+
updateModel(value)
|
|
290
272
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
319
|
-
|
|
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
|
-
//
|
|
310
|
+
// Range handling
|
|
326
311
|
const rangeBoundaryDates = ref<[Date | null, Date | null] | null>(null)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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],
|
|
380
|
-
formatDate(rangeBoundaryDates.value[1],
|
|
341
|
+
formatDate(rangeBoundaryDates.value[0], rf),
|
|
342
|
+
formatDate(rangeBoundaryDates.value[1], rf),
|
|
381
343
|
] as [string, string]
|
|
382
344
|
}
|
|
383
|
-
|
|
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],
|
|
387
|
-
formatDate(selectedDates.value[selectedDates.value.length - 1],
|
|
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(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
421
|
+
* UI updates after picking
|
|
496
422
|
*/
|
|
497
423
|
const updateDisplayFormattedDate = () => {
|
|
498
|
-
|
|
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
|
-
|
|
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],
|
|
519
|
-
formatDate(rangeBoundaryDates.value[1],
|
|
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
|
-
}
|
|
468
|
+
})
|
|
571
469
|
}
|
|
572
470
|
|
|
573
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
-
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
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(
|
|
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
|
-
|
|
640
|
-
|
|
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
|
-
|
|
659
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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(
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
948
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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,
|
|
1085
|
-
currentMonthName,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
/*
|
|
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
|
}
|