@cnamts/synapse 1.0.23 → 1.0.24
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/AutocompleteFilter-BWLR3U7W.js +114 -0
- package/dist/AutocompleteFilter-D9jzRzAL.cjs +1 -0
- package/dist/{DateFilter-Dc-gSGwk.js → DateFilter-BpwFexzi.js} +1 -1
- package/dist/DateFilter-DTUl8hb1.cjs +1 -0
- package/dist/{NumberFilter-vP38Wp6j.js → NumberFilter-Bz_NTdX9.js} +3 -3
- package/dist/NumberFilter-MAEojdk0.cjs +1 -0
- package/dist/PeriodFilter-CC4WgIhl.cjs +1 -0
- package/dist/{PeriodFilter-Ba1uYUnT.js → PeriodFilter-DX_wy9g-.js} +1 -1
- package/dist/SelectFilter-BR3fvl-a.cjs +1 -0
- package/dist/SelectFilter-xqiPtPgX.js +135 -0
- package/dist/{TextFilter-B84dpnoq.js → TextFilter-BBl3JFqK.js} +7 -7
- package/dist/TextFilter-CCfYFl5F.cjs +1 -0
- package/dist/apLightTheme-CFSRrjv2.cjs +1 -0
- package/dist/apLightTheme-D1P4jcD0.js +1231 -0
- package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +7022 -9616
- package/dist/components/Amelipro/AmeliproCarousel/AmeliproCarousel.d.ts +2 -2
- package/dist/components/Amelipro/AmeliproIconBtn/AmeliproIconBtn.d.ts +2 -2
- package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressCityRow/AmeliproPostalAddressCityRow.d.ts +40 -40
- package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +60 -60
- package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +7168 -9762
- package/dist/components/Amelipro/AmeliproStepper/AmeliproStepper.d.ts +2 -2
- package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +7501 -10095
- package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +21 -21
- package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +41 -41
- package/dist/components/Amelipro/StructureMenu/StructureTabs/StructureTabs.d.ts +2 -2
- package/dist/components/CookiesSelection/CookiesInformation/CookiesInformation.d.ts +20 -498
- package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +108 -146
- package/dist/components/Customs/Selects/SyInputSelect/SyInputSelect.d.ts +5 -5
- package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +12 -16
- package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +8 -8
- package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +28 -506
- package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +28 -506
- package/dist/components/Customs/SyTextField/SyTextField.d.ts +65 -85
- package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +627 -771
- package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +315 -402
- package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +112 -155
- package/dist/components/DatePicker/composables/index.d.ts +1 -0
- package/dist/components/DatePicker/composables/useDatePickerFocusTrap.d.ts +11 -0
- package/dist/components/DatePicker/composables/useDateTextField.d.ts +4 -4
- package/dist/components/DatePicker/composables/useDateValidation.d.ts +3 -3
- package/dist/components/DatePicker/composables/useInputBlurHandler.d.ts +2 -2
- package/dist/components/DatePicker/composables/useManualDateValidation.d.ts +2 -2
- package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +4 -4
- package/dist/components/HeaderToolbar/HeaderToolbar.d.ts +20 -28
- package/dist/components/LunarCalendar/useLunarCalendarValidation.d.ts +3 -3
- package/dist/components/MonthPicker/MonthPicker.d.ts +86 -122
- package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +85 -121
- package/dist/components/NirField/NirField.d.ts +206 -270
- package/dist/components/NirField/locales.d.ts +10 -10
- package/dist/components/NirField/useNirValidation.d.ts +64 -0
- package/dist/components/PasswordField/PasswordField.d.ts +8 -9
- package/dist/components/PeriodField/PeriodField.d.ts +1352 -1640
- package/dist/components/PhoneField/PhoneField.d.ts +88 -124
- package/dist/components/RangeField/RangeSlider/RangeSlider.d.ts +12 -12
- package/dist/components/SyTextArea/SyTextArea.d.ts +34 -14
- package/dist/components/SyTextArea/useDefaultValidationRules.d.ts +11 -0
- package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +9 -6
- package/dist/components/Tables/SyTable/SyTable.d.ts +9 -6
- package/dist/components/Tables/common/SyTableFilter.d.ts +2 -3
- package/dist/components/Tables/common/SyTablePagination.d.ts +17 -19
- package/dist/components/Tables/common/filters/AutocompleteFilter.d.ts +120 -0
- package/dist/components/Tables/common/filters/locales.d.ts +0 -1
- package/dist/components/Tables/common/types.d.ts +19 -3
- package/dist/components/Tables/common/useClickableTableRow.d.ts +17 -0
- package/dist/components/Tables/common/usePagination.d.ts +3 -1
- package/dist/components/Tables/common/usePinnedColumns.d.ts +31 -0
- package/dist/components/Tables/common/useTableHeaders.d.ts +2 -0
- package/dist/components/Tables/common/useTableRowCheckboxAccessibility.d.ts +5 -0
- package/dist/components/UploadWorkflow/UploadWorkflow.d.ts +6 -6
- package/dist/composables/date/useDatePickerAccessibility.d.ts +1 -1
- package/dist/composables/rules/useFieldValidation.d.ts +4 -4
- package/dist/composables/unifyValidation/useCustomValidation.d.ts +8 -0
- package/dist/composables/unifyValidation/useValidation.d.ts +102 -0
- package/dist/composables/unifyValidation/useVuetifyValidation.d.ts +18 -0
- package/dist/composables/useFormFieldErrorHandling.d.ts +2 -2
- package/dist/composables/validation/useFormValidation.d.ts +11 -2
- package/dist/composables/validation/useValidation.d.ts +15 -9
- package/dist/design-system-v3.d.ts +2 -0
- package/dist/design-system-v3.js +186 -187
- package/dist/design-system-v3.umd.cjs +1 -1066
- package/dist/{main-aLKwdMi1.js → main-BtTqyn4z.js} +16434 -15672
- package/dist/main-C1e3eoxd.cjs +1067 -0
- package/dist/main.d.ts +0 -1
- package/dist/synapse.css +1 -0
- package/dist/tooth-11-D3sLWv2n.cjs +1 -0
- package/dist/tooth-12-CXrLuH03.cjs +1 -0
- package/dist/tooth-13-BSfo5fpT.cjs +1 -0
- package/dist/tooth-14-DMzulx0h.cjs +1 -0
- package/dist/tooth-15-BKRFVi-9.cjs +1 -0
- package/dist/tooth-16-CpuxAbuM.cjs +1 -0
- package/dist/tooth-17-BPoahUdg.cjs +1 -0
- package/dist/tooth-18-DhHJz8sy.cjs +1 -0
- package/dist/tooth-21-Dgd5hn_X.cjs +1 -0
- package/dist/tooth-22-C2Tn19sB.cjs +1 -0
- package/dist/tooth-23-C9uaaSGb.cjs +1 -0
- package/dist/tooth-24-BrK9UGpf.cjs +1 -0
- package/dist/tooth-25-CE_EfGNp.cjs +1 -0
- package/dist/tooth-26-Ctv4i9Fy.cjs +1 -0
- package/dist/tooth-27-C5J7JkWM.cjs +1 -0
- package/dist/tooth-28-Z9oWqjo0.cjs +1 -0
- package/dist/tooth-31-BrYqmkTi.cjs +1 -0
- package/dist/tooth-32-BNNR0oCZ.cjs +1 -0
- package/dist/tooth-33-DuxvqO2J.cjs +1 -0
- package/dist/tooth-34-BCSCXMB6.cjs +1 -0
- package/dist/tooth-35-BLUXkX88.cjs +1 -0
- package/dist/tooth-36-IrKHYqlA.cjs +1 -0
- package/dist/tooth-37-BYqpdMwo.cjs +1 -0
- package/dist/tooth-38-B_eNXXdu.cjs +1 -0
- package/dist/tooth-41-Ddva4Ot8.cjs +1 -0
- package/dist/tooth-42-szcDqlM0.cjs +1 -0
- package/dist/tooth-43-B3ka6rQm.cjs +1 -0
- package/dist/tooth-44-CazyQucj.cjs +1 -0
- package/dist/tooth-45-B4HQtc8n.cjs +1 -0
- package/dist/tooth-46-BPM40gbG.cjs +1 -0
- package/dist/tooth-47-Dvr20dlh.cjs +1 -0
- package/dist/tooth-48-Bd8ljGsF.cjs +1 -0
- package/dist/tooth-51-OBpwCOF3.cjs +1 -0
- package/dist/tooth-52-aKxyHcmq.cjs +1 -0
- package/dist/tooth-53-vCwJjTOc.cjs +1 -0
- package/dist/tooth-54-DsWu2iFy.cjs +1 -0
- package/dist/tooth-55-BxC1X2Dn.cjs +1 -0
- package/dist/tooth-61-BbLvxMQi.cjs +1 -0
- package/dist/tooth-62-CmTkWczP.cjs +1 -0
- package/dist/tooth-63-DI7l_2qI.cjs +1 -0
- package/dist/tooth-64-B21sOsJh.cjs +1 -0
- package/dist/tooth-65-D2ZC2VEr.cjs +1 -0
- package/dist/tooth-71-D473PPO5.cjs +1 -0
- package/dist/tooth-72-Drh1wnNu.cjs +1 -0
- package/dist/tooth-73-DzlwYI23.cjs +1 -0
- package/dist/tooth-74-8aGvcZPg.cjs +1 -0
- package/dist/tooth-75-BFK7At_r.cjs +1 -0
- package/dist/tooth-81-BZmR-I0M.cjs +1 -0
- package/dist/tooth-82-euVfUUZV.cjs +1 -0
- package/dist/tooth-83-KV010j64.cjs +1 -0
- package/dist/tooth-84-BBg1RjhZ.cjs +1 -0
- package/dist/tooth-85-Cr-kc1wM.cjs +1 -0
- package/dist/vuetifyConfig.js +561 -0
- package/dist/vuetifyConfig.umd.cjs +1 -0
- package/package.json +10 -4
- package/src/assets/overrides/_btns.scss +0 -6
- package/src/assets/overrides/_icons.scss +9 -1
- package/src/assets/overrides/_typography.scss +0 -10
- package/src/components/Amelipro/AmeliproAutoCompleteField/__tests__/__snapshots__/AmeliproAutoCompleteField.spec.ts.snap +2 -2
- package/src/components/Amelipro/AmeliproHeader/AmeliproHeaderBar/AmeliproHeaderBrandSection/__tests__/__snapshots__/AmeliproHeaderBrandSection.spec.ts.snap +1 -1
- package/src/components/Amelipro/AmeliproTextArea/__tests__/__snapshots__/AmeliproTextArea.spec.ts.snap +2 -2
- package/src/components/Captcha/accessibilite/Accessibility.mdx +86 -8
- package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +12 -12
- package/src/components/ChipList/ChipList.stories.ts +0 -15
- package/src/components/ChipList/ChipList.vue +5 -1
- package/src/components/ChipList/accessibilite/Accessibility.mdx +83 -10
- package/src/components/ChipList/tests/ChipList.a11y.spec.ts +41 -0
- package/src/components/Customs/Selects/SelectBtnField/accessibilite/Accessibility.mdx +0 -9
- package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +22 -5
- package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +143 -0
- package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +14 -10
- package/src/components/Customs/Selects/SyInputSelect/SyInputSelect.stories.ts +4 -4
- package/src/components/Customs/Selects/SyInputSelect/SyInputSelect.vue +8 -9
- package/src/components/Customs/Selects/SyInputSelect/tests/SyInputSelect.spec.ts +10 -10
- package/src/components/Customs/Selects/SySelect/SySelect.vue +14 -11
- package/src/components/Customs/Selects/SySelect/tests/SySelect.spec.ts +54 -0
- package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +6 -9
- package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +10 -16
- package/src/components/Customs/SyCheckbox/SyCheckbox.vue +16 -11
- package/src/components/Customs/SyCheckbox/accessibilite/Accessibility.mdx +35 -0
- package/src/components/Customs/SyCheckbox/tests/SyCheckbox.a11y.spec.ts +134 -2
- package/src/components/Customs/SyForm/SyForm.stories.ts +31 -5
- package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +4 -7
- package/src/components/Customs/SyTextField/SyTextField.mdx +1 -1
- package/src/components/Customs/SyTextField/SyTextField.stories.ts +29 -27
- package/src/components/Customs/SyTextField/SyTextField.vue +154 -157
- package/src/components/Customs/SyTextField/tests/SyTextField.a11y.spec.ts +32 -0
- package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +120 -11
- package/src/components/DatePicker/CalendarMode/DatePicker.stories.ts +62 -58
- package/src/components/DatePicker/CalendarMode/DatePicker.vue +330 -223
- package/src/components/DatePicker/CalendarMode/accessibilite/Accessibility.mdx +82 -0
- package/src/components/DatePicker/CalendarMode/tests/DatePicker.a11y.spec.ts +141 -0
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.stories.ts +2 -56
- package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +195 -159
- package/src/components/DatePicker/ComplexDatePicker/accessibilite/Accessibility.mdx +76 -0
- package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.spec.ts +10 -10
- package/src/components/DatePicker/DatePickerValidationExample/CalendarMode.stories.ts +8 -8
- package/src/components/DatePicker/DatePickerValidationExample/ComplexDatePicker.stories.ts +106 -8
- package/src/components/DatePicker/DatePickerValidationExample/DateTextInput.stories.ts +12 -11
- package/src/components/DatePicker/DatePickerValidationExample/MultiMode.stories.ts +12 -12
- package/src/components/DatePicker/DateTextInput/DateRange.stories.ts +0 -12
- package/src/components/DatePicker/DateTextInput/DateTextInput.vue +63 -57
- package/src/components/DatePicker/DateTextInput/NoCalendar.stories.ts +3 -0
- package/src/components/DatePicker/DateTextInput/accessibilite/Accessibility.mdx +66 -0
- package/src/components/DatePicker/DateTextInput/tests/DateTextInput.spec.ts +52 -1
- package/src/components/DatePicker/composables/index.ts +1 -0
- package/src/components/DatePicker/composables/tests/useCalendarKeyboardNavigation.spec.ts +109 -65
- package/src/components/DatePicker/composables/tests/useDatePickerFocusTrap.spec.ts +138 -0
- package/src/components/DatePicker/composables/tests/useDateValidation.spec.ts +74 -18
- package/src/components/DatePicker/composables/tests/useInputBlurHandler.spec.ts +39 -0
- package/src/components/DatePicker/composables/tests/useManualDateValidation.spec.ts +91 -0
- package/src/components/DatePicker/composables/useCalendarKeyboardNavigation.ts +442 -36
- package/src/components/DatePicker/composables/useDatePickerFocusTrap.ts +92 -0
- package/src/components/DatePicker/composables/useDateTextField.ts +7 -6
- package/src/components/DatePicker/composables/useDateValidation.ts +36 -35
- package/src/components/DatePicker/composables/useInputBlurHandler.ts +3 -3
- package/src/components/DatePicker/composables/useManualDateValidation.ts +6 -2
- package/src/components/DiacriticPicker/accessibilite/Accessibility.mdx +76 -8
- package/src/components/HeaderBar/HeaderBar.stories.ts +14 -2
- package/src/components/Logo/accessibilite/Accessibility.mdx +73 -11
- package/src/components/LogoBrandSection/accessibilite/Accessibility.mdx +85 -9
- package/src/components/LunarCalendar/tests/LunarCalendar.spec.ts +3 -1
- package/src/components/LunarCalendar/useLunarCalendarValidation.ts +4 -5
- package/src/components/MonthPicker/tests/MonthPicker.spec.ts +2 -1
- package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +7 -7
- package/src/components/NirField/NirField.stories.ts +4 -0
- package/src/components/NirField/NirField.vue +64 -260
- package/src/components/NirField/accessibilite/Accessibility.mdx +2 -2
- package/src/components/NirField/locales.ts +1 -1
- package/src/components/NirField/tests/NirField.spec.ts +6 -0
- package/src/components/NirField/useNirValidation.ts +271 -0
- package/src/components/PasswordField/PasswordField.stories.ts +4 -4
- package/src/components/PasswordField/PasswordField.vue +18 -24
- package/src/components/PasswordField/tests/PasswordField.spec.ts +6 -3
- package/src/components/PeriodField/PeriodField.stories.ts +4 -4
- package/src/components/PeriodField/PeriodField.vue +57 -57
- package/src/components/PeriodField/__tests__/PeriodField.async.spec.ts +32 -0
- package/src/components/PeriodField/accessibilite/Accessibility.mdx +68 -8
- package/src/components/PeriodField/tests/PeriodField.spec.ts +28 -2
- package/src/components/PhoneField/PhoneField.vue +5 -6
- package/src/components/PhoneField/tests/PhoneField.spec.ts +1 -0
- package/src/components/RangeField/RangeField.vue +6 -0
- package/src/components/SyTextArea/SyTextArea.stories.ts +138 -2
- package/src/components/SyTextArea/SyTextArea.vue +53 -23
- package/src/components/SyTextArea/tests/SyTextArea.spec.ts +126 -3
- package/src/components/SyTextArea/useDefaultValidationRules.ts +74 -0
- package/src/components/Tables/SyServerTable/SyServerTable.mdx +25 -0
- package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +673 -1
- package/src/components/Tables/SyServerTable/SyServerTable.vue +148 -91
- package/src/components/Tables/SyServerTable/tests/SyServerTable.a11y.spec.ts +58 -0
- package/src/components/Tables/SyServerTable/tests/SyServerTable.spec.ts +122 -0
- package/src/components/Tables/SyTable/SyTable.mdx +25 -0
- package/src/components/Tables/SyTable/SyTable.stories.ts +452 -1
- package/src/components/Tables/SyTable/SyTable.vue +130 -56
- package/src/components/Tables/SyTable/tests/SyTable.a11y.spec.ts +57 -0
- package/src/components/Tables/SyTable/tests/SyTable.spec.ts +108 -0
- package/src/components/Tables/common/SyTableFilter.vue +22 -2
- package/src/components/Tables/common/TableHeader.vue +5 -1
- package/src/components/Tables/common/filters/AutocompleteFilter.vue +160 -0
- package/src/components/Tables/common/filters/NumberFilter.vue +1 -1
- package/src/components/Tables/common/filters/SelectFilter.vue +10 -11
- package/src/components/Tables/common/filters/TextFilter.vue +1 -1
- package/src/components/Tables/common/filters/getFilterComponent.ts +8 -1
- package/src/components/Tables/common/filters/locales.ts +0 -1
- package/src/components/Tables/common/filters/tests/AutocompleteFilter.a11y.spec.ts +110 -0
- package/src/components/Tables/common/filters/tests/AutocompleteFilter.spec.ts +203 -0
- package/src/components/Tables/common/filters/tests/SelectFilter.a11y.spec.ts +104 -0
- package/src/components/Tables/common/filters/tests/SelectFilter.spec.ts +152 -16
- package/src/components/Tables/common/tableFilterUtils.ts +3 -0
- package/src/components/Tables/common/tableStyles.scss +48 -4
- package/src/components/Tables/common/tests/filterByRange.spec.ts +2 -1
- package/src/components/Tables/common/types.ts +13 -4
- package/src/components/Tables/common/useClickableTableRow.ts +103 -0
- package/src/components/Tables/common/usePagination.ts +13 -0
- package/src/components/Tables/common/usePinnedColumns.ts +237 -0
- package/src/components/Tables/common/useTableHeaders.ts +3 -3
- package/src/components/Tables/common/useTableRowCheckboxAccessibility.ts +41 -0
- package/src/composables/date/tests/useDatePickerAccessibility.spec.ts +2 -6
- package/src/composables/date/useDatePickerAccessibility.ts +42 -207
- package/src/composables/rules/tests/useFieldValidation.spec.ts +120 -120
- package/src/composables/rules/useFieldValidation.ts +34 -17
- package/src/composables/unifyValidation/tests/useCustomValidation.spec.ts +601 -0
- package/src/composables/unifyValidation/tests/useValidation.spec.ts +2048 -0
- package/src/composables/unifyValidation/tests/useVuetifyValidation.spec.ts +184 -0
- package/src/composables/unifyValidation/useCustomValidation.ts +95 -0
- package/src/composables/unifyValidation/useValidation.ts +190 -0
- package/src/composables/unifyValidation/useVuetifyValidation.ts +54 -0
- package/src/composables/useFormFieldErrorHandling.ts +4 -7
- package/src/composables/validation/tests/useFormValidation.spec.ts +14 -0
- package/src/composables/validation/tests/useValidation.spec.ts +116 -21
- package/src/composables/validation/useFormValidation.ts +20 -13
- package/src/composables/validation/useValidatable.ts +8 -1
- package/src/composables/validation/useValidation.ts +135 -99
- package/src/composantsVuetify/Introduction.mdx +48 -0
- package/src/composantsVuetify/VBtn/VBtn.mdx +72 -0
- package/src/composantsVuetify/VBtn/v-btn.stories.ts +121 -0
- package/src/composantsVuetify/VTooltip/VTooltip.mdx +32 -0
- package/src/composantsVuetify/VTooltip/v-tooltip.stories.ts +95 -0
- package/src/designTokens/tokens/cnam/cnamSemantic.ts +2 -2
- package/src/main.ts +0 -2
- package/src/stories/Components/Components.stories.ts +74 -9
- package/src/stories/Demarrer/Accueil.stories.ts +3 -3
- package/src/stories/GuideDuDev/Amelipro.mdx +15 -0
- package/src/stories/GuideDuDev/Amelipro.stories.ts +209 -0
- package/src/stories/GuideDuDev/vuetifyOptions.mdx +3 -3
- package/dist/SelectFilter-BioGT6Nn.js +0 -136
- package/dist/style.css +0 -1
- package/src/components/DatePicker/Accessibilite.mdx +0 -14
|
@@ -28,20 +28,162 @@ export const useCalendarKeyboardNavigation = (options: CalendarKeyboardNavigatio
|
|
|
28
28
|
} = options
|
|
29
29
|
|
|
30
30
|
const addDays = (date: Date, amount: number) => dayjs(date).add(amount, 'day').toDate()
|
|
31
|
+
const addMonths = (date: Date, amount: number) => dayjs(date).add(amount, 'month').toDate()
|
|
32
|
+
const addYears = (date: Date, amount: number) => dayjs(date).add(amount, 'year').toDate()
|
|
31
33
|
|
|
32
34
|
const toISO = (date: Date) => dayjs(date).format('YYYY-MM-DD')
|
|
33
35
|
|
|
34
36
|
let isListenerAttached = false
|
|
35
37
|
|
|
38
|
+
const focusMonthButton = (button: HTMLButtonElement | undefined | null) => {
|
|
39
|
+
button?.focus({ preventScroll: true })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const focusYearButton = (button: HTMLButtonElement | undefined | null) => {
|
|
43
|
+
if (!button) return
|
|
44
|
+
button.scrollIntoView({ block: 'nearest', inline: 'nearest' })
|
|
45
|
+
button.focus({ preventScroll: true })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleMonthDialogNavigation = (event: KeyboardEvent): boolean => {
|
|
49
|
+
const targetBtn = (event.target as HTMLElement | null)?.closest<HTMLButtonElement>('.v-date-picker-months button')
|
|
50
|
+
if (!targetBtn) return false
|
|
51
|
+
|
|
52
|
+
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>('.v-date-picker-months button')).filter(btn => !btn.disabled)
|
|
53
|
+
if (buttons.length === 0) return false
|
|
54
|
+
|
|
55
|
+
const currentIndex = buttons.indexOf(targetBtn)
|
|
56
|
+
if (currentIndex === -1) return false
|
|
57
|
+
|
|
58
|
+
const key = event.key
|
|
59
|
+
|
|
60
|
+
// Enter/Space : click manuel sans scroll pour garantir l'activation
|
|
61
|
+
if (key === 'Enter' || key === ' ') {
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
targetBtn.click()
|
|
64
|
+
focusMonthButton(targetBtn)
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key)) return false
|
|
69
|
+
|
|
70
|
+
event.preventDefault()
|
|
71
|
+
|
|
72
|
+
const firstRowTop = buttons[0]?.offsetTop ?? 0
|
|
73
|
+
const columns = buttons.filter(btn => btn.offsetTop === firstRowTop).length || 3
|
|
74
|
+
|
|
75
|
+
const moveIndex = (delta: number) => {
|
|
76
|
+
const nextIndex = Math.min(Math.max(currentIndex + delta, 0), buttons.length - 1)
|
|
77
|
+
focusMonthButton(buttons[nextIndex])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
switch (key) {
|
|
81
|
+
case 'ArrowLeft':
|
|
82
|
+
moveIndex(-1)
|
|
83
|
+
return true
|
|
84
|
+
case 'ArrowRight':
|
|
85
|
+
moveIndex(1)
|
|
86
|
+
return true
|
|
87
|
+
case 'ArrowUp':
|
|
88
|
+
moveIndex(-columns)
|
|
89
|
+
return true
|
|
90
|
+
case 'ArrowDown':
|
|
91
|
+
moveIndex(columns)
|
|
92
|
+
return true
|
|
93
|
+
case 'Home':
|
|
94
|
+
focusMonthButton(buttons[0])
|
|
95
|
+
return true
|
|
96
|
+
case 'End':
|
|
97
|
+
focusMonthButton(buttons[buttons.length - 1])
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handleYearDialogNavigation = (event: KeyboardEvent): boolean => {
|
|
105
|
+
const targetBtn = (event.target as HTMLElement | null)?.closest<HTMLButtonElement>('.v-date-picker-years button')
|
|
106
|
+
if (!targetBtn) return false
|
|
107
|
+
|
|
108
|
+
const buttons = Array.from(document.querySelectorAll<HTMLButtonElement>('.v-date-picker-years button')).filter(btn => !btn.disabled)
|
|
109
|
+
if (buttons.length === 0) return false
|
|
110
|
+
|
|
111
|
+
const currentIndex = buttons.indexOf(targetBtn)
|
|
112
|
+
if (currentIndex === -1) return false
|
|
113
|
+
|
|
114
|
+
const key = event.key
|
|
115
|
+
|
|
116
|
+
if (key === 'Enter' || key === ' ') {
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
targetBtn.click()
|
|
119
|
+
focusYearButton(targetBtn)
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key)) return false
|
|
124
|
+
|
|
125
|
+
event.preventDefault()
|
|
126
|
+
|
|
127
|
+
const firstRowTop = buttons[0]?.offsetTop ?? 0
|
|
128
|
+
const columns = buttons.filter(btn => btn.offsetTop === firstRowTop).length || 3
|
|
129
|
+
|
|
130
|
+
const moveIndex = (delta: number) => {
|
|
131
|
+
const nextIndex = Math.min(Math.max(currentIndex + delta, 0), buttons.length - 1)
|
|
132
|
+
focusYearButton(buttons[nextIndex])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
switch (key) {
|
|
136
|
+
case 'ArrowLeft':
|
|
137
|
+
moveIndex(-1)
|
|
138
|
+
return true
|
|
139
|
+
case 'ArrowRight':
|
|
140
|
+
moveIndex(1)
|
|
141
|
+
return true
|
|
142
|
+
case 'ArrowUp':
|
|
143
|
+
moveIndex(-columns)
|
|
144
|
+
return true
|
|
145
|
+
case 'ArrowDown':
|
|
146
|
+
moveIndex(columns)
|
|
147
|
+
return true
|
|
148
|
+
case 'Home':
|
|
149
|
+
focusYearButton(buttons[0])
|
|
150
|
+
return true
|
|
151
|
+
case 'End':
|
|
152
|
+
focusYearButton(buttons[buttons.length - 1])
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
36
159
|
const getBaseDateFromEvent = (event: KeyboardEvent): { date: Date | null, fromDayCell: boolean } => {
|
|
37
160
|
const target = event.target as HTMLElement | null
|
|
161
|
+
// Chercher d'abord avec data-v-date, puis avec les classes Vuetify
|
|
38
162
|
const dayWrapper = target?.closest<HTMLElement>('[data-v-date]')
|
|
39
|
-
|
|
163
|
+
|| target?.closest<HTMLElement>('.v-date-picker-month__day')
|
|
164
|
+
|
|
165
|
+
// Essayer d'abord l'attribut data-v-date
|
|
166
|
+
let iso = dayWrapper?.getAttribute('data-v-date')
|
|
167
|
+
|
|
168
|
+
// Si pas d'attribut data-v-date, essayer de récupérer la date depuis les classes Vuetify
|
|
169
|
+
if (!iso && dayWrapper) {
|
|
170
|
+
// Vuetify utilise souvent des classes spécifiques pour identifier les dates
|
|
171
|
+
// On peut essayer de récupérer la date depuis l'élément bouton lui-même
|
|
172
|
+
const button = dayWrapper.querySelector<HTMLElement>('.v-btn')
|
|
173
|
+
if (button) {
|
|
174
|
+
// Vuetify peut stocker la date dans un attribut data ou via aria-label
|
|
175
|
+
iso = button.getAttribute('data-date')
|
|
176
|
+
|| button.getAttribute('aria-label')?.split(' ').pop()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
40
180
|
if (!iso) {
|
|
41
181
|
return { date: getCurrentDate(), fromDayCell: false }
|
|
42
182
|
}
|
|
43
183
|
|
|
44
|
-
|
|
184
|
+
// Nettoyer le format si nécessaire (enlever le texte si c'est aria-label)
|
|
185
|
+
const cleanIso = iso.replace(/^\D+/, '')
|
|
186
|
+
const parsed = dayjs(cleanIso, 'YYYY-MM-DD', true)
|
|
45
187
|
if (!parsed.isValid()) {
|
|
46
188
|
return { date: getCurrentDate(), fromDayCell: false }
|
|
47
189
|
}
|
|
@@ -49,6 +191,147 @@ export const useCalendarKeyboardNavigation = (options: CalendarKeyboardNavigatio
|
|
|
49
191
|
return { date: parsed.toDate(), fromDayCell: true }
|
|
50
192
|
}
|
|
51
193
|
|
|
194
|
+
let latestFocusToken = 0
|
|
195
|
+
|
|
196
|
+
const focusDateButton = (date: Date, attempt = 0, token?: number) => {
|
|
197
|
+
if (attempt === 0) {
|
|
198
|
+
latestFocusToken++
|
|
199
|
+
token = latestFocusToken
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Si un autre focus a été demandé entre-temps, on annule
|
|
203
|
+
if (token !== latestFocusToken) return
|
|
204
|
+
|
|
205
|
+
// Utiliser setTimeout pour la première tentative pour laisser Vue et Vuetify commencer la mise à jour du DOM
|
|
206
|
+
if (attempt === 0) {
|
|
207
|
+
setTimeout(() => focusDateButton(date, 1, token), 10)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
|
+
const rootEl = (datePickerRef.value as any)?.$el as HTMLElement | undefined
|
|
213
|
+
if (!rootEl) return
|
|
214
|
+
|
|
215
|
+
const iso = toISO(date)
|
|
216
|
+
const dayNum = date.getDate()
|
|
217
|
+
|
|
218
|
+
// Exclure les éléments qui sont dans une fenêtre en cours de disparition
|
|
219
|
+
const isActiveContext = (el: Element) => {
|
|
220
|
+
const windowItem = el.closest('.v-window-item')
|
|
221
|
+
if (!windowItem) return true
|
|
222
|
+
const classes = Array.from(windowItem.classList)
|
|
223
|
+
const isLeaving = classes.some(c => c.includes('leave-active') || c.includes('leave-to') || c === 'v-window-item--leave')
|
|
224
|
+
return !isLeaving
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const candidates: HTMLElement[] = []
|
|
228
|
+
|
|
229
|
+
// 1. Chercher par data-v-date
|
|
230
|
+
const dataDateElements = rootEl.querySelectorAll(`[data-v-date="${iso}"]`)
|
|
231
|
+
for (const el of Array.from(dataDateElements)) {
|
|
232
|
+
const btn = (el.tagName === 'BUTTON' ? el : el.querySelector('button')) as HTMLElement
|
|
233
|
+
if (btn && isActiveContext(btn)) candidates.push(btn)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 2. Chercher par texte ou aria-label si vide
|
|
237
|
+
if (candidates.length === 0) {
|
|
238
|
+
const allButtons = rootEl.querySelectorAll<HTMLElement>('.v-date-picker-month__day .v-btn')
|
|
239
|
+
for (const btn of Array.from(allButtons)) {
|
|
240
|
+
if (!isActiveContext(btn)) continue
|
|
241
|
+
const text = btn.textContent?.trim() || ''
|
|
242
|
+
const ariaLabel = btn.getAttribute('aria-label') || ''
|
|
243
|
+
if (text === dayNum.toString() || new RegExp(`\\b${dayNum}\\b`).test(ariaLabel)) {
|
|
244
|
+
candidates.push(btn)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Filtrer ceux qui ne sont pas visibles
|
|
250
|
+
const visibleCandidates = candidates.filter((btn) => {
|
|
251
|
+
// Autoriser les éléments en transition (opacity peut être 0 au tout début)
|
|
252
|
+
const windowItem = btn.closest('.v-window-item')
|
|
253
|
+
const isEntering = windowItem && Array.from(windowItem.classList).some(c => c.includes('enter-active') || c.includes('enter-to'))
|
|
254
|
+
|
|
255
|
+
if (!isEntering && btn.offsetParent === null) return false
|
|
256
|
+
|
|
257
|
+
const style = window.getComputedStyle(btn)
|
|
258
|
+
return style.display !== 'none' && style.visibility !== 'hidden'
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if (visibleCandidates.length > 0) {
|
|
262
|
+
// Préférer les non-adjacents
|
|
263
|
+
visibleCandidates.sort((a, b) => {
|
|
264
|
+
const aAdj = a.closest('.v-date-picker-month__day--adjacent') ? 1 : 0
|
|
265
|
+
const bAdj = b.closest('.v-date-picker-month__day--adjacent') ? 1 : 0
|
|
266
|
+
return aAdj - bAdj
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const bestCandidate = visibleCandidates[0]
|
|
270
|
+
if (bestCandidate) {
|
|
271
|
+
bestCandidate.focus({ preventScroll: true })
|
|
272
|
+
|
|
273
|
+
// Revérifier le focus après la durée typique d'une transition Vuetify (~350ms)
|
|
274
|
+
// car le DOM peut être re-rendu et l'élément détruit, ou le focus perdu pendant l'animation
|
|
275
|
+
if (attempt === 1) {
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
if (token === latestFocusToken && (document.activeElement !== bestCandidate || !bestCandidate.isConnected)) {
|
|
278
|
+
// Forcer un retry silencieux
|
|
279
|
+
focusDateButton(date, 2, token)
|
|
280
|
+
}
|
|
281
|
+
}, 350)
|
|
282
|
+
}
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (attempt < 15) {
|
|
288
|
+
setTimeout(() => focusDateButton(date, attempt + 1, token), 30)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const clickDateButton = (date: Date) => {
|
|
293
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock Axios headers
|
|
294
|
+
const rootEl = (datePickerRef.value as any)?.$el as HTMLElement | undefined
|
|
295
|
+
if (!rootEl) return
|
|
296
|
+
|
|
297
|
+
const iso = toISO(date)
|
|
298
|
+
|
|
299
|
+
// Essayer plusieurs sélecteurs pour trouver le bouton du jour
|
|
300
|
+
const selectors = [
|
|
301
|
+
`[data-v-date="${iso}"] > [type="button"]`, // Bouton enfant direct avec data-v-date
|
|
302
|
+
`[data-v-date="${iso}"] button`, // N'importe quel bouton dans l'élément avec data-v-date
|
|
303
|
+
`[data-v-date="${iso}"] .v-btn`, // Bouton Vuetify spécifique avec data-v-date
|
|
304
|
+
`[data-v-date="${iso}"] [role="button"]`, // Élément avec role="button" et data-v-date
|
|
305
|
+
// Sélecteurs Vuetify sans data-v-date
|
|
306
|
+
`.v-date-picker-month__day:has(.v-btn[aria-label*="${date.getDate()}"]) .v-btn`,
|
|
307
|
+
`.v-date-picker-month__day .v-btn[aria-label*="${date.getDate()}"]`,
|
|
308
|
+
]
|
|
309
|
+
|
|
310
|
+
let dayButton: HTMLButtonElement | null = null
|
|
311
|
+
for (const selector of selectors) {
|
|
312
|
+
dayButton = rootEl.querySelector<HTMLButtonElement>(selector)
|
|
313
|
+
if (dayButton) {
|
|
314
|
+
break
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Si aucun sélecteur précis ne fonctionne, chercher par aria-label complet
|
|
319
|
+
if (!dayButton) {
|
|
320
|
+
const ariaLabelPattern = new RegExp(`\\b${date.getDate()}\\b`)
|
|
321
|
+
const allButtons = rootEl.querySelectorAll<HTMLElement>('.v-date-picker-month__day .v-btn')
|
|
322
|
+
for (const button of allButtons) {
|
|
323
|
+
const ariaLabel = button.getAttribute('aria-label')
|
|
324
|
+
if (ariaLabel && ariaLabelPattern.test(ariaLabel)) {
|
|
325
|
+
dayButton = button as HTMLButtonElement
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
dayButton?.click()
|
|
332
|
+
dayButton?.focus()
|
|
333
|
+
}
|
|
334
|
+
|
|
52
335
|
const handleArrowNavigation = (event: KeyboardEvent) => {
|
|
53
336
|
if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
|
|
54
337
|
return
|
|
@@ -59,47 +342,104 @@ export const useCalendarKeyboardNavigation = (options: CalendarKeyboardNavigatio
|
|
|
59
342
|
return
|
|
60
343
|
}
|
|
61
344
|
|
|
62
|
-
|
|
345
|
+
// Laisser les flèches fonctionner nativement dans les contrôles d'entête
|
|
346
|
+
if ((event.target as HTMLElement | null)?.closest('.v-date-picker-controls')) return
|
|
347
|
+
|
|
348
|
+
// Si on n'a pas de date courante sélectionnée, on essaie de l'extraire du focus
|
|
349
|
+
const { date: current } = getBaseDateFromEvent(event)
|
|
350
|
+
|
|
351
|
+
// Si toujours aucune date n'est résolue, on abandonne
|
|
63
352
|
if (!current) return
|
|
64
353
|
|
|
65
354
|
event.preventDefault()
|
|
66
355
|
|
|
356
|
+
// Navigation normale des jours
|
|
67
357
|
let nextDate = current
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
break
|
|
82
|
-
case 'ArrowUp':
|
|
83
|
-
nextDate = addDays(current, -7)
|
|
84
|
-
break
|
|
85
|
-
case 'ArrowDown':
|
|
86
|
-
nextDate = addDays(current, 7)
|
|
87
|
-
break
|
|
88
|
-
}
|
|
358
|
+
switch (event.key) {
|
|
359
|
+
case 'ArrowLeft':
|
|
360
|
+
nextDate = addDays(current, -1)
|
|
361
|
+
break
|
|
362
|
+
case 'ArrowRight':
|
|
363
|
+
nextDate = addDays(current, 1)
|
|
364
|
+
break
|
|
365
|
+
case 'ArrowUp':
|
|
366
|
+
nextDate = addDays(current, -7)
|
|
367
|
+
break
|
|
368
|
+
case 'ArrowDown':
|
|
369
|
+
nextDate = addDays(current, 7)
|
|
370
|
+
break
|
|
89
371
|
}
|
|
90
372
|
|
|
373
|
+
// Mettre à jour l'état (ce qui peut déclencher un changement de mois dans Vuetify)
|
|
91
374
|
setCurrentDate(nextDate)
|
|
92
375
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!rootEl) return
|
|
376
|
+
// Forcer le focus sur le nouveau jour de manière résiliente
|
|
377
|
+
focusDateButton(nextDate)
|
|
378
|
+
}
|
|
97
379
|
|
|
98
|
-
|
|
99
|
-
|
|
380
|
+
const handleHomeEndPageNavigation = (event: KeyboardEvent) => {
|
|
381
|
+
if (!['Home', 'End', 'PageUp', 'PageDown'].includes(event.key)) {
|
|
382
|
+
return
|
|
383
|
+
}
|
|
100
384
|
|
|
101
|
-
|
|
102
|
-
|
|
385
|
+
// Pour la navigation de mois, toujours utiliser la date courante sélectionnée
|
|
386
|
+
// plutôt que de dépendre du DOM qui peut ne pas avoir le focus sur un jour
|
|
387
|
+
const current = getCurrentDate()
|
|
388
|
+
if (!current) return
|
|
389
|
+
|
|
390
|
+
// Respecter les combinaisons système (Ctrl/Alt/Meta), mais autoriser Shift pour année
|
|
391
|
+
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
event.preventDefault()
|
|
396
|
+
|
|
397
|
+
let nextDate = current
|
|
398
|
+
if (event.key === 'Home') {
|
|
399
|
+
nextDate = dayjs(current).startOf('month').toDate()
|
|
400
|
+
}
|
|
401
|
+
else if (event.key === 'End') {
|
|
402
|
+
nextDate = dayjs(current).endOf('month').toDate()
|
|
403
|
+
}
|
|
404
|
+
else if (event.key === 'PageUp') {
|
|
405
|
+
nextDate = event.shiftKey ? addYears(current, -1) : addMonths(current, -1)
|
|
406
|
+
// Focus sur le premier jour du mois précédent
|
|
407
|
+
nextDate = dayjs(nextDate).startOf('month').toDate()
|
|
408
|
+
}
|
|
409
|
+
else if (event.key === 'PageDown') {
|
|
410
|
+
nextDate = event.shiftKey ? addYears(current, 1) : addMonths(current, 1)
|
|
411
|
+
// Focus sur le premier jour du mois suivant
|
|
412
|
+
nextDate = dayjs(nextDate).startOf('month').toDate()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setCurrentDate(nextDate)
|
|
416
|
+
focusDateButton(nextDate)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const handleEnterSpaceNavigation = (event: KeyboardEvent) => {
|
|
420
|
+
if (!['Enter', ' '].includes(event.key)) {
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Gérer manuellement les contrôles d'entête (mois/année et flèches) car le composant natif est surchargé
|
|
425
|
+
const headerButton = (event.target as HTMLElement | null)?.closest<HTMLButtonElement>('.v-date-picker-controls button, .v-date-picker-header button')
|
|
426
|
+
if (headerButton) {
|
|
427
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
428
|
+
event.preventDefault()
|
|
429
|
+
headerButton.click()
|
|
430
|
+
// Garder le focus sur le contrôle pour éviter la lecture de toute la grille
|
|
431
|
+
headerButton.focus({ preventScroll: true })
|
|
432
|
+
setTimeout(() => headerButton.focus({ preventScroll: true }), 0)
|
|
433
|
+
}
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const { date: current, fromDayCell } = getBaseDateFromEvent(event)
|
|
438
|
+
if (!current || !fromDayCell) return
|
|
439
|
+
|
|
440
|
+
// Enter/Space : empêcher le scroll et déclencher la sélection explicite
|
|
441
|
+
event.preventDefault()
|
|
442
|
+
clickDateButton(current)
|
|
103
443
|
}
|
|
104
444
|
|
|
105
445
|
const keydownListener = (event: Event) => {
|
|
@@ -109,23 +449,89 @@ export const useCalendarKeyboardNavigation = (options: CalendarKeyboardNavigatio
|
|
|
109
449
|
// Ne pas interférer avec la saisie dans les champs de formulaire ou zones éditables
|
|
110
450
|
if (target) {
|
|
111
451
|
const tagName = target.tagName
|
|
112
|
-
if (tagName === 'INPUT' || tagName === 'TEXTAREA' ||
|
|
452
|
+
if (tagName === 'INPUT' || tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
113
453
|
return
|
|
114
454
|
}
|
|
115
455
|
}
|
|
116
456
|
|
|
457
|
+
// Ignorer les modificateurs sauf Shift pour PageUp/PageDown (année)
|
|
458
|
+
if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) {
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Gérer les dialogues mois/année ouverts
|
|
463
|
+
if (handleMonthDialogNavigation(keyboardEvent) || handleYearDialogNavigation(keyboardEvent)) {
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Gérer la navigation fléchée
|
|
117
468
|
handleArrowNavigation(keyboardEvent)
|
|
469
|
+
|
|
470
|
+
// Gérer Home/End/Page navigation
|
|
471
|
+
handleHomeEndPageNavigation(keyboardEvent)
|
|
472
|
+
|
|
473
|
+
// Gérer Enter/Espace pour sélectionner
|
|
474
|
+
handleEnterSpaceNavigation(keyboardEvent)
|
|
118
475
|
}
|
|
119
476
|
|
|
120
477
|
const attachListeners = () => {
|
|
121
478
|
if (isListenerAttached) return
|
|
122
|
-
|
|
123
|
-
|
|
479
|
+
|
|
480
|
+
// Utiliser un watcher pour attendre que le VDatePicker soit disponible
|
|
481
|
+
const tryAttach = () => {
|
|
482
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- sorry
|
|
483
|
+
const rootEl = (datePickerRef.value as any)?.$el as HTMLElement | undefined
|
|
484
|
+
|
|
485
|
+
// Chercher le conteneur parent avec tabindex="-1" (le focusTrap)
|
|
486
|
+
const containerEl = rootEl?.parentElement?.querySelector('[tabindex="-1"]') as HTMLElement | undefined
|
|
487
|
+
|| rootEl?.closest('[tabindex="-1"]') as HTMLElement | undefined
|
|
488
|
+
|
|
489
|
+
// Chercher le VDatePicker lui-même
|
|
490
|
+
const datePickerEl = rootEl?.querySelector('.v-date-picker') || rootEl
|
|
491
|
+
|
|
492
|
+
if (containerEl) {
|
|
493
|
+
// Attacher sur le conteneur du focusTrap (plus prioritaire que le document)
|
|
494
|
+
containerEl.addEventListener('keydown', keydownListener as EventListener, true)
|
|
495
|
+
isListenerAttached = true
|
|
496
|
+
}
|
|
497
|
+
else if (datePickerEl) {
|
|
498
|
+
// Attacher sur le VDatePicker directement
|
|
499
|
+
datePickerEl.addEventListener('keydown', keydownListener as EventListener, true)
|
|
500
|
+
isListenerAttached = true
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
// Fallback : attacher sur le document
|
|
504
|
+
document.addEventListener('keydown', keydownListener as EventListener, true)
|
|
505
|
+
isListenerAttached = true
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Attendre plusieurs ticks pour être sûr que le focusTrap est déjà attaché
|
|
510
|
+
setTimeout(tryAttach, 100)
|
|
124
511
|
}
|
|
125
512
|
|
|
126
513
|
const detachListeners = () => {
|
|
127
514
|
if (!isListenerAttached) return
|
|
128
|
-
|
|
515
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- sorry
|
|
516
|
+
const rootEl = (datePickerRef.value as any)?.$el as HTMLElement | undefined
|
|
517
|
+
|
|
518
|
+
// Chercher le conteneur parent avec tabindex="-1" (le focusTrap)
|
|
519
|
+
const containerEl = rootEl?.parentElement?.querySelector('[tabindex="-1"]') as HTMLElement | undefined
|
|
520
|
+
|| rootEl?.closest('[tabindex="-1"]') as HTMLElement | undefined
|
|
521
|
+
|
|
522
|
+
// Chercher le VDatePicker lui-même
|
|
523
|
+
const datePickerEl = rootEl?.querySelector('.v-date-picker') || rootEl
|
|
524
|
+
|
|
525
|
+
if (containerEl) {
|
|
526
|
+
containerEl.removeEventListener('keydown', keydownListener as EventListener, true)
|
|
527
|
+
}
|
|
528
|
+
else if (datePickerEl) {
|
|
529
|
+
datePickerEl.removeEventListener('keydown', keydownListener as EventListener, true)
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
document.removeEventListener('keydown', keydownListener as EventListener, true)
|
|
533
|
+
}
|
|
534
|
+
|
|
129
535
|
isListenerAttached = false
|
|
130
536
|
}
|
|
131
537
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type Ref } from 'vue'
|
|
2
|
+
import type { ComponentPublicInstance } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface UseDatePickerFocusTrapOptions {
|
|
5
|
+
isDatePickerVisible: Ref<boolean>
|
|
6
|
+
datePickerRef: Ref<ComponentPublicInstance | null>
|
|
7
|
+
onClose?: () => void
|
|
8
|
+
restoreFocus?: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getFocusableElements = (root: HTMLElement): HTMLElement[] => {
|
|
12
|
+
const allFocusable = Array.from(root.querySelectorAll<HTMLElement>('button,[href],input,select,textarea,[tabindex]'))
|
|
13
|
+
return allFocusable.filter(el => !el.hasAttribute('disabled') && el.getAttribute('aria-hidden') !== 'true' && el.tabIndex !== -1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useDatePickerFocusTrap(options: UseDatePickerFocusTrapOptions) {
|
|
17
|
+
const { isDatePickerVisible, datePickerRef, onClose, restoreFocus } = options
|
|
18
|
+
|
|
19
|
+
const handleMenuKeydown = (event: KeyboardEvent) => {
|
|
20
|
+
if (!isDatePickerVisible.value) return
|
|
21
|
+
|
|
22
|
+
// Ne gérer que Escape et Tab, laisser toutes les autres touches passer
|
|
23
|
+
if (event.key === 'Escape' || event.key === 'Esc') {
|
|
24
|
+
isDatePickerVisible.value = false
|
|
25
|
+
onClose?.()
|
|
26
|
+
restoreFocus?.()
|
|
27
|
+
event.preventDefault()
|
|
28
|
+
event.stopPropagation()
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Pour Tab, on gère mais on laisse les autres touches (flèches, etc.) passer complètement
|
|
33
|
+
if (event.key !== 'Tab') {
|
|
34
|
+
// Laisser toutes les autres touches passer sans aucune intervention
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (event.ctrlKey || event.altKey || event.metaKey) return // Laisser les combinaisons système
|
|
39
|
+
|
|
40
|
+
const root = (datePickerRef.value as ComponentPublicInstance | null)?.$el as HTMLElement | undefined
|
|
41
|
+
if (!root) return
|
|
42
|
+
|
|
43
|
+
// Empêcher la fermeture du menu avec Tab et garder le focus à l'intérieur
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
event.stopPropagation()
|
|
46
|
+
|
|
47
|
+
const target = event.target as HTMLElement | null
|
|
48
|
+
const todayButton = root.querySelector<HTMLElement>('.date-picker__today-button')
|
|
49
|
+
const focusables = getFocusableElements(root)
|
|
50
|
+
const firstFocusable = focusables[0]
|
|
51
|
+
if (!firstFocusable) {
|
|
52
|
+
// Aucun focusable : rester dans le menu via le bouton Aujourd'hui si présent
|
|
53
|
+
todayButton?.focus({ preventScroll: true })
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const active = document.activeElement as HTMLElement | null
|
|
58
|
+
|
|
59
|
+
// Si on appuie sur Tab (sans Shift) depuis la grille des jours, des mois ou des années,
|
|
60
|
+
// on force le focus vers le bouton Aujourd'hui (s'il existe)
|
|
61
|
+
const isFromGrid = Boolean(
|
|
62
|
+
target?.closest('.v-date-picker-months')
|
|
63
|
+
|| target?.closest('.v-date-picker-years')
|
|
64
|
+
|| target?.closest('.v-date-picker-month'),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (!event.shiftKey && isFromGrid && todayButton) {
|
|
68
|
+
todayButton.focus({ preventScroll: true })
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!event.shiftKey && active === focusables.at(-1)) {
|
|
73
|
+
firstFocusable.focus({ preventScroll: true })
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (event.shiftKey && active === focusables[0]) {
|
|
78
|
+
focusables.at(-1)?.focus({ preventScroll: true })
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Comportement par défaut : laisser Tab circuler mais au sein du menu
|
|
83
|
+
const baseActive = active ?? firstFocusable
|
|
84
|
+
const currentIndex = focusables.indexOf(baseActive)
|
|
85
|
+
const safeIndex = currentIndex === -1 ? 0 : currentIndex
|
|
86
|
+
const nextIndex = event.shiftKey ? (safeIndex - 1 + focusables.length) % focusables.length : (safeIndex + 1) % focusables.length
|
|
87
|
+
const nextFocusable = focusables[nextIndex]
|
|
88
|
+
nextFocusable?.focus({ preventScroll: true })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { handleMenuKeydown }
|
|
92
|
+
}
|
|
@@ -17,14 +17,14 @@ export interface UseDateTextFieldManualValidationOptions {
|
|
|
17
17
|
isDateComplete: (value: string) => boolean
|
|
18
18
|
parseDate: (dateStr: string, format: string) => Date | null
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
validateField: (value: unknown, rules?: any[], warningRules?: any[]) => ValidationResult
|
|
20
|
+
validateField: (value: unknown, rules?: any[], warningRules?: any[]) => Promise<ValidationResult>
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface UseDateTextFieldSubmitOptions {
|
|
24
24
|
isValidating: Ref<boolean>
|
|
25
25
|
hasInteracted: Ref<boolean>
|
|
26
26
|
inputValue: Ref<string>
|
|
27
|
-
runRules: (value: string) => boolean
|
|
27
|
+
runRules: (value: string) => Promise<boolean>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface UseDateTextFieldResetOptions {
|
|
@@ -71,15 +71,16 @@ export const useDateTextField = (options: UseDateTextFieldOptions) => {
|
|
|
71
71
|
isDateComplete: manualValidation.isDateComplete,
|
|
72
72
|
parseDate: manualValidation.parseDate,
|
|
73
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
-
validateField: manualValidation.validateField as (value: unknown, rules?: any[], warningRules?: any[]) => ValidationResult
|
|
74
|
+
validateField: manualValidation.validateField as (value: unknown, rules?: any[], warningRules?: any[]) => Promise<ValidationResult>,
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
-
const validateOnSubmit = () => {
|
|
77
|
+
const validateOnSubmit = async () => {
|
|
78
78
|
if (!submit) return true
|
|
79
79
|
const { isValidating, hasInteracted, inputValue, runRules } = submit
|
|
80
80
|
isValidating.value = true
|
|
81
81
|
hasInteracted.value = true
|
|
82
|
-
const ok = runRules(inputValue.value)
|
|
82
|
+
const ok = await runRules(inputValue.value)
|
|
83
|
+
console.log('ok from validateOnSubmit:', ok)
|
|
83
84
|
isValidating.value = false
|
|
84
85
|
return ok
|
|
85
86
|
}
|
|
@@ -99,7 +100,7 @@ export const useDateTextField = (options: UseDateTextFieldOptions) => {
|
|
|
99
100
|
const formattedStartDate = startDateValidation.clampedDate || ''
|
|
100
101
|
const formattedEndDate = endDateValidation.clampedDate || ''
|
|
101
102
|
|
|
102
|
-
return formattedEndDate ? `${formattedStartDate} - ${formattedEndDate}` : formattedStartDate
|
|
103
|
+
return formattedEndDate ? `${formattedStartDate} - ${formattedEndDate}` : `${formattedStartDate} - `
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const dateValidationResult = autoClampDate(raw, displayFormat.value)
|