bitboss-ui 2.1.113 → 2.1.115
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/ai/BaseButton.md +448 -0
- package/dist/ai/BaseCheckbox.md +494 -0
- package/dist/ai/BaseCheckboxGroup.md +597 -0
- package/dist/ai/BaseColorInput.md +461 -0
- package/dist/ai/BaseDatePicker.md +739 -0
- package/dist/ai/BaseDatePickerInput.md +1517 -0
- package/dist/ai/BaseDialog.md +610 -0
- package/dist/ai/BaseInputContainer.md +570 -0
- package/dist/ai/BaseNumberInput.md +509 -0
- package/dist/ai/BaseRadio.md +405 -0
- package/dist/ai/BaseRadioGroup.md +535 -0
- package/dist/ai/BaseRating.md +489 -0
- package/dist/ai/BaseSelect.md +1720 -0
- package/dist/ai/BaseSlider.md +871 -0
- package/dist/ai/BaseSwitch.md +322 -0
- package/dist/ai/BaseSwitchGroup.md +298 -0
- package/dist/ai/BaseTag.md +624 -0
- package/dist/ai/BaseTextInput.md +392 -0
- package/dist/ai/BaseTextarea.md +398 -0
- package/dist/ai/BbAccordion.md +135 -0
- package/dist/ai/BbAlert.md +226 -0
- package/dist/ai/BbAvatar.md +200 -0
- package/dist/ai/BbBadge.md +185 -0
- package/dist/ai/BbBreadcrumbs.md +536 -0
- package/dist/ai/BbButton.md +687 -0
- package/dist/ai/BbCheckbox.md +280 -0
- package/dist/ai/BbCheckboxGroup.md +387 -0
- package/dist/ai/BbChip.md +148 -0
- package/dist/ai/BbCollapsible.md +119 -0
- package/dist/ai/BbColorInput.md +345 -0
- package/dist/ai/BbColorPalette.md +360 -0
- package/dist/ai/BbConfirm.md +160 -0
- package/dist/ai/BbDatePickerInput.md +414 -0
- package/dist/ai/BbDialog.md +135 -0
- package/dist/ai/BbDropdown.md +765 -0
- package/dist/ai/BbDropdownButton.md +629 -0
- package/dist/ai/BbDropzone.md +504 -0
- package/dist/ai/BbIcon.md +238 -0
- package/dist/ai/BbIntersection.md +121 -0
- package/dist/ai/BbNumberInput.md +372 -0
- package/dist/ai/BbOffCanvas.md +549 -0
- package/dist/ai/BbPagination.md +562 -0
- package/dist/ai/BbPopover.md +580 -0
- package/dist/ai/BbProgress.md +97 -0
- package/dist/ai/BbRadio.md +256 -0
- package/dist/ai/BbRadioGroup.md +373 -0
- package/dist/ai/BbRating.md +245 -0
- package/dist/ai/BbRatio.md +62 -0
- package/dist/ai/BbRows.md +307 -0
- package/dist/ai/BbSelect.md +562 -0
- package/dist/ai/BbSelectPopover.md +2010 -0
- package/dist/ai/BbSlider.md +274 -0
- package/dist/ai/BbSmoothHeight.md +167 -0
- package/dist/ai/BbSpinner.md +154 -0
- package/dist/ai/BbSwitch.md +151 -0
- package/dist/ai/BbSwitchGroup.md +237 -0
- package/dist/ai/BbTab.md +954 -0
- package/dist/ai/BbTable.md +1624 -0
- package/dist/ai/BbTag.md +315 -0
- package/dist/ai/BbTextInput.md +357 -0
- package/dist/ai/BbTextarea.md +277 -0
- package/dist/ai/BbToast.md +219 -0
- package/dist/ai/BbTooltip.md +353 -0
- package/dist/ai/BbTree.md +271 -0
- package/dist/ai/ChipsBox.md +211 -0
- package/dist/ai/ClearableButton.md +67 -0
- package/dist/ai/CommaBox.md +212 -0
- package/dist/ai/CommonInputInnerContainer.md +419 -0
- package/dist/ai/CommonInputOuterContainer.md +56 -0
- package/dist/ai/CommonPopover.md +446 -0
- package/dist/ai/ErrorIcon.md +61 -0
- package/dist/ai/FlatListBox.md +382 -0
- package/dist/ai/GroupedListBox.md +538 -0
- package/dist/ai/ListBox.md +234 -0
- package/dist/ai/OptionsContainer.md +257 -0
- package/dist/ai/index.md +124 -0
- package/dist/components/BaseButton/BaseButton.vue.d.ts +2 -163
- package/dist/components/BaseButton/types.d.ts +158 -0
- package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +4 -4
- package/dist/components/BaseCheckboxGroup/BaseCheckboxGroup.vue.d.ts +2 -2
- package/dist/components/BaseCheckboxGroup/types.d.ts +16 -9
- package/dist/components/BaseColorInput/BaseColorInput.vue.d.ts +12 -52
- package/dist/components/BaseDatePicker/BaseDatePicker.vue.d.ts +4 -76
- package/dist/components/BaseDatePicker/types.d.ts +100 -0
- package/dist/components/BaseDatePickerInput/BaseDatePickerInput.vue.d.ts +18 -315
- package/dist/components/BaseDatePickerInput/types.d.ts +206 -0
- package/dist/components/BaseDialog/BaseDialog.vue.d.ts +6 -156
- package/dist/components/BaseDialog/types.d.ts +180 -0
- package/dist/components/BaseInputContainer/BaseInputContainer.vue.d.ts +1 -107
- package/dist/components/BaseInputContainer/types.d.ts +126 -0
- package/dist/components/BaseNumberInput/BaseNumberInput.vue.d.ts +7 -170
- package/dist/components/BaseNumberInput/types.d.ts +191 -0
- package/dist/components/BaseRadio/BaseRadio.vue.d.ts +6 -119
- package/dist/components/BaseRadio/types.d.ts +173 -0
- package/dist/components/BaseRadioGroup/BaseRadioGroup.vue.d.ts +4 -274
- package/dist/components/BaseRadioGroup/types.d.ts +240 -0
- package/dist/components/BaseRating/BaseRating.vue.d.ts +5 -106
- package/dist/components/BaseRating/types.d.ts +144 -0
- package/dist/components/BaseSelect/BaseSelect.vue.d.ts +2 -363
- package/dist/components/BaseSelect/types.d.ts +457 -0
- package/dist/components/BaseSlider/BaseSlider.vue.d.ts +6 -178
- package/dist/components/BaseSlider/types.d.ts +201 -0
- package/dist/components/BaseSwitch/BaseSwitch.vue.d.ts +7 -35
- package/dist/components/BaseSwitch/types.d.ts +25 -0
- package/dist/components/BaseSwitchGroup/BaseSwitchGroup.vue.d.ts +5 -11
- package/dist/components/BaseSwitchGroup/types.d.ts +8 -0
- package/dist/components/BaseTag/BaseTag.vue.d.ts +27 -222
- package/dist/components/BaseTag/types.d.ts +136 -0
- package/dist/components/BaseTextInput/BaseTextInput.vue.d.ts +5 -141
- package/dist/components/BaseTextInput/types.d.ts +166 -0
- package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +5 -131
- package/dist/components/BaseTextarea/types.d.ts +151 -0
- package/dist/components/BbAccordion/BbAccordion.vue.d.ts +3 -51
- package/dist/components/BbAccordion/types.d.ts +32 -0
- package/dist/components/BbAlert/BbAlert.vue.d.ts +3 -50
- package/dist/components/BbAlert/types.d.ts +42 -0
- package/dist/components/BbAvatar/BbAvatar.vue.d.ts +3 -23
- package/dist/components/BbAvatar/types.d.ts +34 -0
- package/dist/components/BbBadge/BbBadge.vue.d.ts +3 -40
- package/dist/components/BbBadge/types.d.ts +30 -0
- package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.d.ts +14 -178
- package/dist/components/BbBreadcrumbs/types.d.ts +109 -0
- package/dist/components/BbButton/BbButton.vue.d.ts +4 -163
- package/dist/components/BbButton/types.d.ts +159 -0
- package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +7 -165
- package/dist/components/BbCheckbox/types.d.ts +130 -0
- package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +7 -324
- package/dist/components/BbCheckboxGroup/types.d.ts +189 -0
- package/dist/components/BbChip/BbChip.vue.d.ts +6 -28
- package/dist/components/BbChip/types.d.ts +23 -0
- package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +3 -24
- package/dist/components/BbCollapsible/types.d.ts +20 -0
- package/dist/components/BbColorInput/BbColorInput.vue.d.ts +10 -151
- package/dist/components/BbColorInput/types.d.ts +131 -0
- package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +2 -112
- package/dist/components/BbColorPalette/types.d.ts +127 -0
- package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +6 -212
- package/dist/components/BbDatePickerInput/types.d.ts +180 -0
- package/dist/components/BbDialog/BbDialog.vue.d.ts +2 -2
- package/dist/components/BbDialog/types.d.ts +1 -0
- package/dist/components/BbDropdown/BbDropdown.vue.d.ts +21 -247
- package/dist/components/BbDropdown/types.d.ts +147 -0
- package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +16 -209
- package/dist/components/BbDropdownButton/types.d.ts +114 -0
- package/dist/components/BbDropzone/BbDropzone.vue.d.ts +7 -86
- package/dist/components/BbDropzone/types.d.ts +67 -0
- package/dist/components/BbIcon/BbIcon.vue.d.ts +2 -26
- package/dist/components/BbIcon/types.d.ts +28 -0
- package/dist/components/BbIntersection/BbIntersection.vue.d.ts +3 -41
- package/dist/components/BbIntersection/types.d.ts +36 -0
- package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +44 -175
- package/dist/components/BbNumberInput/types.d.ts +130 -0
- package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +5 -93
- package/dist/components/BbOffCanvas/types.d.ts +97 -0
- package/dist/components/BbPagination/BbPagination.vue.d.ts +4 -87
- package/dist/components/BbPagination/types.d.ts +80 -0
- package/dist/components/BbPopover/BbPopover.vue.d.ts +9 -135
- package/dist/components/BbPopover/types.d.ts +99 -0
- package/dist/components/BbProgress/BbProgress.vue.d.ts +2 -14
- package/dist/components/BbProgress/types.d.ts +20 -0
- package/dist/components/BbRadio/BbRadio.vue.d.ts +7 -150
- package/dist/components/BbRadio/types.d.ts +117 -0
- package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +7 -322
- package/dist/components/BbRadioGroup/types.d.ts +182 -0
- package/dist/components/BbRating/BbRating.vue.d.ts +10 -113
- package/dist/components/BbRating/types.d.ts +105 -0
- package/dist/components/BbRatio/BbRatio.vue.d.ts +3 -18
- package/dist/components/BbRatio/types.d.ts +15 -0
- package/dist/components/BbSelect/BbSelect.vue.d.ts +7 -375
- package/dist/components/BbSelect/types.d.ts +351 -0
- package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +1 -1
- package/dist/components/BbSelectPopover/types.d.ts +351 -0
- package/dist/components/BbSlider/BbSlider.vue.d.ts +10 -129
- package/dist/components/BbSlider/types.d.ts +123 -0
- package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +2 -23
- package/dist/components/BbSmoothHeight/types.d.ts +24 -0
- package/dist/components/BbSpinner/BbSpinner.vue.d.ts +3 -5
- package/dist/components/BbSpinner/types.d.ts +8 -0
- package/dist/components/BbSwitch/BbSwitch.vue.d.ts +9 -65
- package/dist/components/BbSwitch/types.d.ts +29 -0
- package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +7 -190
- package/dist/components/BbSwitchGroup/types.d.ts +81 -0
- package/dist/components/BbTab/BbTab.vue.d.ts +9 -247
- package/dist/components/BbTab/types.d.ts +186 -0
- package/dist/components/BbTag/BbTag.vue.d.ts +6 -156
- package/dist/components/BbTag/types.d.ts +158 -0
- package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -152
- package/dist/components/BbTextInput/types.d.ts +137 -0
- package/dist/components/BbTextarea/BbTextarea.vue.d.ts +10 -142
- package/dist/components/BbTextarea/types.d.ts +123 -0
- package/dist/components/BbToast/BbToast.vue.d.ts +2 -6
- package/dist/components/BbToast/types.d.ts +8 -0
- package/dist/components/BbTooltip/BbTooltip.vue.d.ts +8 -65
- package/dist/components/BbTooltip/types.d.ts +55 -0
- package/dist/components/BbTree/BbTree.vue.d.ts +2 -65
- package/dist/components/BbTree/types.d.ts +69 -0
- package/dist/components/{ChipsBox.vue.d.ts → ChipsBox/ChipsBox.vue.d.ts} +5 -6
- package/dist/components/ChipsBox/types.d.ts +14 -0
- package/dist/components/{ClearableButton.vue.d.ts → ClearableButton/ClearableButton.vue.d.ts} +2 -0
- package/dist/components/ClearableButton/types.d.ts +3 -0
- package/dist/components/{CommaBox.vue.d.ts → CommaBox/CommaBox.vue.d.ts} +5 -6
- package/dist/components/CommaBox/types.d.ts +14 -0
- package/dist/components/CommonInputInnerContainer/CommonInputInnerContainer.vue.d.ts +25 -0
- package/dist/components/CommonInputInnerContainer/types.d.ts +47 -0
- package/dist/components/CommonInputOuterContainer/CommonInputOuterContainer.vue.d.ts +17 -0
- package/dist/components/CommonInputOuterContainer/types.d.ts +16 -0
- package/dist/components/{CommonPopover.vue.d.ts → CommonPopover/CommonPopover.vue.d.ts} +5 -30
- package/dist/components/CommonPopover/types.d.ts +43 -0
- package/dist/components/{ErrorIcon.vue.d.ts → ErrorIcon/ErrorIcon.vue.d.ts} +2 -0
- package/dist/components/ErrorIcon/types.d.ts +3 -0
- package/dist/components/FlatListBox/types.d.ts +97 -0
- package/dist/components/GroupedListBox/types.d.ts +118 -0
- package/dist/components/ListBox/ListBox.vue.d.ts +30 -0
- package/dist/components/ListBox/types.d.ts +133 -0
- package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +13 -0
- package/dist/components/OptionsContainer/types.d.ts +96 -0
- package/dist/composables/useBbConfig.d.ts +1 -1
- package/dist/composables/useConfirm.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +18 -18
- package/dist/index109.js +9 -9
- package/dist/index110.js +50 -49
- package/dist/index114.js +1 -1
- package/dist/index118.js +1 -1
- package/dist/index122.js +1 -0
- package/dist/index124.js +4 -4
- package/dist/index126.js +13 -13
- package/dist/index132.js +22 -19
- package/dist/index134.js +1 -1
- package/dist/index136.js +5 -5
- package/dist/index138.js +1 -1
- package/dist/index14.js +1 -1
- package/dist/index140.js +18 -17
- package/dist/index144.js +1 -1
- package/dist/index146.js +2 -2
- package/dist/index149.js +2 -2
- package/dist/index16.js +3 -3
- package/dist/index18.js +3 -3
- package/dist/index20.js +70 -59
- package/dist/index22.js +14 -14
- package/dist/index221.js +138 -2
- package/dist/index222.js +2 -138
- package/dist/index224.js +5 -34
- package/dist/index225.js +7 -32
- package/dist/index226.js +32 -26
- package/dist/index227.js +7 -0
- package/dist/index228.js +5 -5
- package/dist/index229.js +5 -8
- package/dist/index230.js +5 -7
- package/dist/index231.js +3 -2
- package/dist/index232.js +2 -9
- package/dist/index233.js +6 -13
- package/dist/index234.js +8 -3
- package/dist/index235.js +268 -2
- package/dist/index236.js +52 -11
- package/dist/index237.js +50 -6
- package/dist/index238.js +32 -3
- package/dist/index239.js +60 -3
- package/dist/index24.js +10 -10
- package/dist/index240.js +13 -2
- package/dist/index241.js +187 -17
- package/dist/index242.js +3 -12
- package/dist/index243.js +2 -51
- package/dist/index244.js +2 -18
- package/dist/index245.js +2 -12
- package/dist/index246.js +12 -16
- package/dist/index247.js +11 -28
- package/dist/index248.js +48 -15
- package/dist/index249.js +17 -4
- package/dist/index250.js +2 -2
- package/dist/index252.js +2 -2
- package/dist/index254.js +3 -135
- package/dist/index255.js +4 -0
- package/dist/index256.js +4 -107
- package/dist/index257.js +19 -12
- package/dist/index258.js +6 -2
- package/dist/index259.js +16 -7
- package/dist/index26.js +3 -3
- package/dist/index260.js +86 -7
- package/dist/index262.js +32 -0
- package/dist/index263.js +18 -5
- package/dist/index264.js +12 -5
- package/dist/index265.js +18 -5
- package/dist/index266.js +2 -5
- package/dist/index267.js +7 -5
- package/dist/index268.js +7 -5
- package/dist/index269.js +3 -67
- package/dist/index270.js +4 -33
- package/dist/index271.js +5 -2
- package/dist/index272.js +5 -2
- package/dist/index273.js +5 -3
- package/dist/index274.js +135 -4
- package/dist/index276.js +9 -6
- package/dist/index277.js +7 -11
- package/dist/index278.js +23 -5
- package/dist/index279.js +3 -5
- package/dist/index28.js +57 -55
- package/dist/index280.js +21 -266
- package/dist/index281.js +364 -43
- package/dist/index283.js +32 -31
- package/dist/index284.js +3 -60
- package/dist/index285.js +25 -4
- package/dist/index286.js +3 -20
- package/dist/index287.js +18 -5
- package/dist/index288.js +12 -373
- package/dist/index289.js +109 -0
- package/dist/index290.js +11 -6
- package/dist/index291.js +66 -15
- package/dist/index292.js +32 -10
- package/dist/index294.js +5 -8
- package/dist/index295.js +9 -20
- package/dist/index296.js +2 -8
- package/dist/index297.js +9 -23
- package/dist/index298.js +52 -24
- package/dist/index299.js +5 -188
- package/dist/index30.js +3 -3
- package/dist/index300.js +21 -3
- package/dist/index301.js +28 -3
- package/dist/index303.js +9 -0
- package/dist/index304.js +2 -7
- package/dist/index305.js +280 -3
- package/dist/index306.js +2 -2
- package/dist/index307.js +16 -5
- package/dist/index308.js +2 -7
- package/dist/index309.js +16 -3
- package/dist/index310.js +2 -3
- package/dist/index311.js +27 -3
- package/dist/index312.js +2 -2
- package/dist/index313.js +2 -28
- package/dist/index314.js +2 -17
- package/dist/index315.js +2 -4
- package/dist/index316.js +1 -1
- package/dist/index317.js +28 -3
- package/dist/index318.js +2 -280
- package/dist/index319.js +7 -2
- package/dist/index32.js +2 -2
- package/dist/index320.js +719 -125
- package/dist/index321.js +366 -2
- package/dist/index322.js +56 -14
- package/dist/index323.js +4 -2
- package/dist/index324.js +3 -16
- package/dist/index325.js +17 -2
- package/dist/index326.js +3 -16
- package/dist/index327.js +3 -2
- package/dist/index328.js +3 -19
- package/dist/index329.js +3 -2
- package/dist/index330.js +120 -22
- package/dist/index331.js +2 -2
- package/dist/index332.js +15 -2
- package/dist/index333.js +2 -2
- package/dist/index334.js +19 -2
- package/dist/index335.js +2 -2
- package/dist/index336.js +5 -2
- package/dist/index337.js +5 -3
- package/dist/index338.js +2 -4
- package/dist/index339.js +4 -719
- package/dist/index34.js +8 -8
- package/dist/index340.js +2 -366
- package/dist/index341.js +3 -57
- package/dist/index342.js +3 -6
- package/dist/index343.js +6 -5
- package/dist/index344.js +6 -34
- package/dist/index345.js +17 -127
- package/dist/index346.js +7 -396
- package/dist/index347.js +14 -199
- package/dist/index348.js +5 -259
- package/dist/index349.js +6 -227
- package/dist/index352.js +35 -2
- package/dist/index353.js +129 -2
- package/dist/index354.js +378 -114
- package/dist/index355.js +92 -6
- package/dist/index356.js +226 -17
- package/dist/index357.js +22 -9
- package/dist/index359.js +7 -5
- package/dist/index36.js +4 -4
- package/dist/index360.js +200 -7
- package/dist/index361.js +255 -18
- package/dist/index362.js +136 -0
- package/dist/index363.js +2 -93
- package/dist/index364.js +2 -441
- package/dist/index365.js +427 -114
- package/dist/index366.js +127 -46
- package/dist/index367.js +44 -67
- package/dist/index368.js +66 -516
- package/dist/index369.js +515 -45
- package/dist/index370.js +52 -0
- package/dist/index38.js +133 -131
- package/dist/index40.js +8 -8
- package/dist/index42.js +2 -2
- package/dist/index44.js +16 -15
- package/dist/index46.js +4 -4
- package/dist/index50.js +28 -25
- package/dist/index54.js +1 -1
- package/dist/index56.js +1 -1
- package/dist/index58.js +2 -2
- package/dist/index60.js +2 -2
- package/dist/index62.js +5 -5
- package/dist/index66.js +3 -1
- package/dist/index68.js +1 -1
- package/dist/index74.js +4 -4
- package/dist/index82.js +6 -6
- package/dist/index84.js +1 -1
- package/dist/index86.js +2 -2
- package/dist/index88.js +3 -3
- package/dist/index90.js +1 -1
- package/dist/index93.js +3 -3
- package/dist/index95.js +2 -2
- package/dist/index97.js +5 -5
- package/dist/index99.js +1 -1
- package/dist/utilities/functions/parseSize.d.ts +1 -1
- package/package.json +5 -3
- package/dist/components/CommonInputInnerContainer.vue.d.ts +0 -81
- package/dist/components/CommonInputOuterContainer.vue.d.ts +0 -41
- package/dist/components/FlatListBox.vue.d.ts +0 -119
- package/dist/components/GroupedListBox.vue.d.ts +0 -153
- package/dist/components/ListBox.vue.d.ts +0 -170
- package/dist/components/OptionsContainer.vue.d.ts +0 -172
- package/dist/index261.js +0 -88
- package/dist/index275.js +0 -25
- package/dist/index282.js +0 -54
- package/dist/index293.js +0 -5
- package/dist/index302.js +0 -55
- package/dist/index358.js +0 -17
|
@@ -0,0 +1,1517 @@
|
|
|
1
|
+
# BaseDatePickerInput
|
|
2
|
+
|
|
3
|
+
## Template & Script
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<template>
|
|
7
|
+
<CommonInputOuterContainer
|
|
8
|
+
ref="outerContainer"
|
|
9
|
+
:class="classes"
|
|
10
|
+
@focusin="onFocusIn"
|
|
11
|
+
>
|
|
12
|
+
<template #prepend-outer><slot name="prepend-outer"></slot></template>
|
|
13
|
+
|
|
14
|
+
<div class="bb-base-date-picker-input__inner-wrapper">
|
|
15
|
+
<CommonInputInnerContainer
|
|
16
|
+
ref="innerContainer"
|
|
17
|
+
:append:icon="props['append:icon']"
|
|
18
|
+
:clearable="props.clearable && !isEmpty(modelValue)"
|
|
19
|
+
:prepend:icon="props['prepend:icon']"
|
|
20
|
+
@click:clear="onClickClear"
|
|
21
|
+
>
|
|
22
|
+
<template #prepend><slot name="prepend" /></template>
|
|
23
|
+
<template #prefix><slot name="prefix" /></template>
|
|
24
|
+
|
|
25
|
+
<div
|
|
26
|
+
class="bb-base-date-picker-input__fields"
|
|
27
|
+
:class="{
|
|
28
|
+
'bb-base-date-picker-input__fields--empty': showPlaceholder,
|
|
29
|
+
'bb-base-date-picker-input__fields--range': range,
|
|
30
|
+
}"
|
|
31
|
+
>
|
|
32
|
+
<span
|
|
33
|
+
v-if="showPlaceholder"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
class="bb-base-date-picker-input__placeholder"
|
|
36
|
+
@click="focusFirstField"
|
|
37
|
+
>
|
|
38
|
+
{{ placeholderText }}
|
|
39
|
+
</span>
|
|
40
|
+
<template
|
|
41
|
+
v-for="(descriptor, index) in fieldOrder"
|
|
42
|
+
:key="descriptorKey(descriptor)"
|
|
43
|
+
>
|
|
44
|
+
<input
|
|
45
|
+
:id="getFieldId(descriptor)"
|
|
46
|
+
:ref="
|
|
47
|
+
(el) =>
|
|
48
|
+
(fieldRefs[descriptorKey(descriptor)] =
|
|
49
|
+
el as HTMLInputElement)
|
|
50
|
+
"
|
|
51
|
+
:aria-describedby="descriptionId"
|
|
52
|
+
:aria-invalid="props.hasErrors ? 'true' : undefined"
|
|
53
|
+
:aria-label="getFieldLabel(descriptor)"
|
|
54
|
+
:autocomplete="autocomplete"
|
|
55
|
+
:autofocus="autofocus && index === 0"
|
|
56
|
+
:class="[
|
|
57
|
+
'bb-base-date-picker-input__field',
|
|
58
|
+
`bb-base-date-picker-input__field--${descriptor.field}`,
|
|
59
|
+
`bb-base-date-picker-input__field--${descriptor.segment}`,
|
|
60
|
+
]"
|
|
61
|
+
:disabled="disabled"
|
|
62
|
+
:inputmode="inputmode"
|
|
63
|
+
:maxlength="descriptor.field === 'year' ? 4 : 2"
|
|
64
|
+
:name="getFieldName(descriptor)"
|
|
65
|
+
:readonly="isInputReadonly"
|
|
66
|
+
:required="required"
|
|
67
|
+
:tabindex="index % 3 === 0 ? 0 : -1"
|
|
68
|
+
type="text"
|
|
69
|
+
:value="fields[descriptor.segment][descriptor.field]"
|
|
70
|
+
@blur="onFieldBlur(descriptor)"
|
|
71
|
+
@focus="onFieldFocus(descriptor)"
|
|
72
|
+
@input="onFieldInput(descriptor, $event)"
|
|
73
|
+
@keydown="onFieldKeydown(descriptor, $event)"
|
|
74
|
+
@paste="onFieldPaste(descriptor, $event)"
|
|
75
|
+
/>
|
|
76
|
+
<span
|
|
77
|
+
v-if="index < fieldOrder.length - 1"
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
class="bb-base-date-picker-input__separator"
|
|
80
|
+
:class="{
|
|
81
|
+
'bb-base-date-picker-input__separator--range':
|
|
82
|
+
fieldOrder[index].segment !== fieldOrder[index + 1].segment,
|
|
83
|
+
}"
|
|
84
|
+
@click="focusField(fieldOrder[index + 1], { select: true })"
|
|
85
|
+
>{{
|
|
86
|
+
fieldOrder[index].segment !== fieldOrder[index + 1].segment
|
|
87
|
+
? '–'
|
|
88
|
+
: '/'
|
|
89
|
+
}}</span
|
|
90
|
+
>
|
|
91
|
+
</template>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<button
|
|
95
|
+
v-if="!isPopoverHidden"
|
|
96
|
+
:aria-label="accessibleButtonLabel"
|
|
97
|
+
:aria-controls="calendarId"
|
|
98
|
+
:aria-expanded="shown"
|
|
99
|
+
aria-haspopup="dialog"
|
|
100
|
+
class="bb-base-date-picker-input__calendar-btn"
|
|
101
|
+
:disabled="disabled || readonly"
|
|
102
|
+
type="button"
|
|
103
|
+
@click.stop="open"
|
|
104
|
+
>
|
|
105
|
+
<svg
|
|
106
|
+
fill="none"
|
|
107
|
+
viewBox="0 0 16 16"
|
|
108
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
109
|
+
>
|
|
110
|
+
<path
|
|
111
|
+
d="M12.6667 2.66667H3.33333C2.59695 2.66667 2 3.26363 2 4.00001V13.3333C2 14.0697 2.59695 14.6667 3.33333 14.6667H12.6667C13.403 14.6667 14 14.0697 14 13.3333V4.00001C14 3.26363 13.403 2.66667 12.6667 2.66667Z"
|
|
112
|
+
stroke="currentColor"
|
|
113
|
+
stroke-linecap="round"
|
|
114
|
+
stroke-linejoin="round"
|
|
115
|
+
/>
|
|
116
|
+
<path
|
|
117
|
+
d="M10.6667 1.33333V3.99999"
|
|
118
|
+
stroke="currentColor"
|
|
119
|
+
stroke-linecap="round"
|
|
120
|
+
stroke-linejoin="round"
|
|
121
|
+
/>
|
|
122
|
+
<path
|
|
123
|
+
d="M5.33333 1.33333V3.99999"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
stroke-linecap="round"
|
|
126
|
+
stroke-linejoin="round"
|
|
127
|
+
/>
|
|
128
|
+
<path
|
|
129
|
+
d="M2 6.66667H14"
|
|
130
|
+
stroke="currentColor"
|
|
131
|
+
stroke-linecap="round"
|
|
132
|
+
stroke-linejoin="round"
|
|
133
|
+
/>
|
|
134
|
+
</svg>
|
|
135
|
+
</button>
|
|
136
|
+
<span
|
|
137
|
+
:id="descriptionId"
|
|
138
|
+
class="bb-base-date-picker-input__accessible-description sr-only"
|
|
139
|
+
>{{ t('baseDatePickerInput.dateFormat') }}
|
|
140
|
+
{{
|
|
141
|
+
props.range
|
|
142
|
+
? t('baseDatePickerInput.dateFormatRange')
|
|
143
|
+
: t('baseDatePickerInput.dateFormatSingle')
|
|
144
|
+
}}</span
|
|
145
|
+
>
|
|
146
|
+
<template #append><slot name="append" /></template>
|
|
147
|
+
<template #suffix><slot name="suffix" /></template>
|
|
148
|
+
</CommonInputInnerContainer>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<CommonPopover
|
|
152
|
+
v-model="shown"
|
|
153
|
+
:anchor="innerContainer"
|
|
154
|
+
dialog
|
|
155
|
+
:offset="4"
|
|
156
|
+
:transition-duration="transitionDuration"
|
|
157
|
+
>
|
|
158
|
+
<div
|
|
159
|
+
:id="calendarId"
|
|
160
|
+
ref="calendar"
|
|
161
|
+
class="bb-base-date-picker-input__calendar"
|
|
162
|
+
:class="{
|
|
163
|
+
'bb-base-date-picker-input__calendar--shown': shown,
|
|
164
|
+
}"
|
|
165
|
+
:inert="!shown"
|
|
166
|
+
>
|
|
167
|
+
<BaseDatePicker
|
|
168
|
+
:id="id"
|
|
169
|
+
v-bind="calendarEventListeners"
|
|
170
|
+
ref="baseDatePicker"
|
|
171
|
+
:disabled="disabled"
|
|
172
|
+
:first-day-of-week="firstDayOfWeek"
|
|
173
|
+
:floating="floating"
|
|
174
|
+
:max="max"
|
|
175
|
+
:min="min"
|
|
176
|
+
:model-value="modelValue"
|
|
177
|
+
:range="range"
|
|
178
|
+
:readonly="readonly"
|
|
179
|
+
:selectable="selectable"
|
|
180
|
+
/>
|
|
181
|
+
</div>
|
|
182
|
+
</CommonPopover>
|
|
183
|
+
<template #append-outer><slot name="append-outer"></slot></template>
|
|
184
|
+
</CommonInputOuterContainer>
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<script setup lang="ts">
|
|
188
|
+
import { _config } from '@/composables/useBbConfig';
|
|
189
|
+
import { computed, onBeforeUnmount, ref, watch, watchEffect } from 'vue';
|
|
190
|
+
import { Dayjs, default as dayjs } from 'dayjs';
|
|
191
|
+
import { default as customParseFormat } from 'dayjs/plugin/customParseFormat';
|
|
192
|
+
import { default as en } from 'dayjs/locale/en';
|
|
193
|
+
import { default as isSameOrAfter } from 'dayjs/plugin/isSameOrAfter';
|
|
194
|
+
import { default as isSameOrBefore } from 'dayjs/plugin/isSameOrBefore';
|
|
195
|
+
import { default as it } from 'dayjs/locale/it';
|
|
196
|
+
import { isEmpty } from '@/utilities/functions/empty';
|
|
197
|
+
import { useId } from '@/composables/useId';
|
|
198
|
+
import { useLocale } from '@/composables/useLocale';
|
|
199
|
+
import { useLogger } from '@/composables/useLogger';
|
|
200
|
+
import { useMobile } from '@/composables/useMobile';
|
|
201
|
+
import BaseDatePicker from '../BaseDatePicker/BaseDatePicker.vue';
|
|
202
|
+
import CommonInputInnerContainer from '../CommonInputInnerContainer/CommonInputInnerContainer.vue';
|
|
203
|
+
import CommonInputOuterContainer from '../CommonInputOuterContainer/CommonInputOuterContainer.vue';
|
|
204
|
+
import CommonPopover from '../CommonPopover/CommonPopover.vue';
|
|
205
|
+
import type {
|
|
206
|
+
BaseDatePickerInputProps,
|
|
207
|
+
BaseDatePickerInputEvents,
|
|
208
|
+
BaseDatePickerInputSlots,
|
|
209
|
+
DatePickerInputError,
|
|
210
|
+
} from './types';
|
|
211
|
+
|
|
212
|
+
const locales = { it, en };
|
|
213
|
+
|
|
214
|
+
dayjs.extend(customParseFormat);
|
|
215
|
+
dayjs.extend(isSameOrAfter);
|
|
216
|
+
dayjs.extend(isSameOrBefore);
|
|
217
|
+
watchEffect(() => {
|
|
218
|
+
dayjs.locale(locales[_config.locale] ?? en);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const props = withDefaults(defineProps<BaseDatePickerInputProps>(), {
|
|
222
|
+
allowWriting: true,
|
|
223
|
+
autocomplete: 'off',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const emit = defineEmits<BaseDatePickerInputEvents>();
|
|
227
|
+
|
|
228
|
+
defineSlots<BaseDatePickerInputSlots>();
|
|
229
|
+
|
|
230
|
+
const { t } = useLocale();
|
|
231
|
+
const logger = useLogger();
|
|
232
|
+
|
|
233
|
+
const BOUNDARY_DATE_FORMAT = 'YYYY-MM-DD';
|
|
234
|
+
|
|
235
|
+
const calendar = ref<HTMLElement | null>(null);
|
|
236
|
+
const outerContainer = ref<InstanceType<
|
|
237
|
+
typeof CommonInputOuterContainer
|
|
238
|
+
> | null>(null);
|
|
239
|
+
const innerContainer = ref<InstanceType<
|
|
240
|
+
typeof CommonInputInnerContainer
|
|
241
|
+
> | null>(null);
|
|
242
|
+
const baseDatePicker = ref<InstanceType<typeof BaseDatePicker> | null>(null);
|
|
243
|
+
const id = props.id || `bdpi-${useId().id.value}`;
|
|
244
|
+
const descriptionId = `bdpi-description_${id}`;
|
|
245
|
+
const calendarId = `bdpi-calendar_${id}`;
|
|
246
|
+
|
|
247
|
+
const { isMobile } = useMobile();
|
|
248
|
+
const isWritingAllowed = computed(() => {
|
|
249
|
+
if (props.allowWriting === 'not-mobile') return !isMobile.value;
|
|
250
|
+
return !!props.allowWriting;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const isPopoverHidden = computed(() => {
|
|
254
|
+
if (props.hidePopover === 'not-mobile') return !isMobile.value;
|
|
255
|
+
return !!props.hidePopover;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const isInputReadonly = computed(
|
|
259
|
+
() => !!props.readonly || !isWritingAllowed.value
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const inputmode = computed(() => (isWritingAllowed.value ? 'numeric' : 'none'));
|
|
263
|
+
|
|
264
|
+
const classes = computed(() => ({
|
|
265
|
+
'bb-base-date-picker-input': true,
|
|
266
|
+
'bb-base-date-picker-input--active': active.value,
|
|
267
|
+
'bb-base-date-picker-input--errors': props.hasErrors,
|
|
268
|
+
'bb-base-date-picker-input--loading': props.loading,
|
|
269
|
+
'bb-base-date-picker-input--readonly': props.readonly,
|
|
270
|
+
'bb-base-date-picker-input--disabled': props.disabled,
|
|
271
|
+
'bb-base-date-picker-input--compact': props.compact,
|
|
272
|
+
'bb-base-date-picker-input--range': props.range,
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
if (props.min) {
|
|
276
|
+
if (!dayjs(props.min, BOUNDARY_DATE_FORMAT, true).isValid()) {
|
|
277
|
+
logger.throw('BaseDatePickerInput: min must be in the format YYYY-MM-DD');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (props.max) {
|
|
282
|
+
if (!dayjs(props.max, BOUNDARY_DATE_FORMAT, true).isValid()) {
|
|
283
|
+
logger.throw('BaseDatePickerInput: max must be in the format YYYY-MM-DD');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Validate that at least one interaction method is available on each platform.
|
|
288
|
+
// Desktop: writing is on when allowWriting is true or 'not-mobile'; popover is on when hidePopover is not true.
|
|
289
|
+
// Mobile: writing is on when allowWriting is true; popover is on when hidePopover is not 'not-mobile' and not true.
|
|
290
|
+
const desktopHasWriting = props.allowWriting !== false;
|
|
291
|
+
const desktopHasPopover = props.hidePopover !== true;
|
|
292
|
+
const mobileHasWriting = props.allowWriting === true;
|
|
293
|
+
const mobileHasPopover =
|
|
294
|
+
props.hidePopover !== true && props.hidePopover !== 'not-mobile';
|
|
295
|
+
|
|
296
|
+
if (!desktopHasWriting && !desktopHasPopover) {
|
|
297
|
+
logger.throw(
|
|
298
|
+
'BaseDatePickerInput: desktop users would have no interaction method — allowWriting:false and hidePopover:true cannot be combined.'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (!mobileHasWriting && !mobileHasPopover) {
|
|
302
|
+
logger.throw(
|
|
303
|
+
'BaseDatePickerInput: mobile users would have no interaction method — combine allowWriting:"not-mobile" with hidePopover:"not-mobile" to allow the calendar in mobile while still hiding the virtual keyboard.'
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const toOuterValue = (value: Dayjs): string => {
|
|
308
|
+
if (props.floating) {
|
|
309
|
+
return value.format(BOUNDARY_DATE_FORMAT);
|
|
310
|
+
}
|
|
311
|
+
return value.toISOString();
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const toInnerValue = (value: string): Dayjs => {
|
|
315
|
+
if (props.floating) {
|
|
316
|
+
return dayjs(value, BOUNDARY_DATE_FORMAT, true);
|
|
317
|
+
}
|
|
318
|
+
return dayjs(value);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Applies a class when the user is inside this whole component.
|
|
323
|
+
* We cannot use focus within as it doesn't work for elements that are teleported.
|
|
324
|
+
* Also we cannot use the <input/> if we physically move focus to the calendar so
|
|
325
|
+
* we track it manually.
|
|
326
|
+
*/
|
|
327
|
+
const active = ref(false);
|
|
328
|
+
|
|
329
|
+
const onFocusIn = () => {
|
|
330
|
+
if (active.value) return;
|
|
331
|
+
emit('active');
|
|
332
|
+
active.value = true;
|
|
333
|
+
document.addEventListener('focusin', onDocumentInteraction);
|
|
334
|
+
document.addEventListener('click', onDocumentInteraction);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const onDocumentInteraction = (event: Event) => {
|
|
338
|
+
const target = event.target;
|
|
339
|
+
if (!(target instanceof HTMLElement)) return;
|
|
340
|
+
if (!outerContainer.value?.$el) return;
|
|
341
|
+
if (outerContainer.value.$el.contains(target)) return;
|
|
342
|
+
if (calendar.value?.contains(target)) return;
|
|
343
|
+
|
|
344
|
+
// Restore fields to match modelValue when focus leaves the component
|
|
345
|
+
// This ensures coherence if user left fields in incomplete state
|
|
346
|
+
restoreFieldsFromModelValue();
|
|
347
|
+
|
|
348
|
+
active.value = false;
|
|
349
|
+
emit('inactive');
|
|
350
|
+
document.removeEventListener('focusin', onDocumentInteraction);
|
|
351
|
+
document.removeEventListener('click', onDocumentInteraction);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* We need the transitions because elements must be hidden and shown
|
|
356
|
+
* to accessibility tools when they are fully closed or fully open,
|
|
357
|
+
* while still being animated for sighted users.
|
|
358
|
+
*/
|
|
359
|
+
|
|
360
|
+
// Option panel is open
|
|
361
|
+
const shown = ref(false);
|
|
362
|
+
|
|
363
|
+
const onClickDocument = (event: Event) => {
|
|
364
|
+
const target = event.target;
|
|
365
|
+
if (!target) return;
|
|
366
|
+
if (target instanceof HTMLDialogElement) {
|
|
367
|
+
shown.value = false;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const open = () => {
|
|
372
|
+
if (props.disabled || props.readonly || isPopoverHidden.value) return;
|
|
373
|
+
shown.value = true;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
watch(shown, (value) => {
|
|
377
|
+
if (value) {
|
|
378
|
+
document.addEventListener('click', onClickDocument, { passive: true });
|
|
379
|
+
} else {
|
|
380
|
+
baseDatePicker.value?.resetStatus();
|
|
381
|
+
document.removeEventListener('click', onClickDocument);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const satisfiesSelection = (value: typeof props.modelValue) => {
|
|
386
|
+
if (props.range) {
|
|
387
|
+
return Array.isArray(value) && value.length === 2;
|
|
388
|
+
} else {
|
|
389
|
+
return !!value;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* These events are just propagated
|
|
395
|
+
*/
|
|
396
|
+
const calendarEventListeners = {
|
|
397
|
+
'onUpdate:modelValue': (value: string | string[] | null) => {
|
|
398
|
+
emit('update:modelValue', value);
|
|
399
|
+
if (satisfiesSelection(value)) {
|
|
400
|
+
shown.value = false;
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const accessibleButtonLabel = computed(() => {
|
|
406
|
+
if (props.range) {
|
|
407
|
+
const values = Array.isArray(props.modelValue) ? props.modelValue : [];
|
|
408
|
+
const [startRaw, endRaw] = values;
|
|
409
|
+
if (!startRaw || !endRaw) return t('baseDatePickerInput.emptyRange').value;
|
|
410
|
+
|
|
411
|
+
const start = toInnerValue(startRaw);
|
|
412
|
+
const end = toInnerValue(endRaw);
|
|
413
|
+
if (!start.isValid() || !end.isValid())
|
|
414
|
+
return t('baseDatePickerInput.emptyRange').value;
|
|
415
|
+
|
|
416
|
+
return t(
|
|
417
|
+
'baseDatePickerInput.changeSelectionRange',
|
|
418
|
+
start.format('D MMMM YYYY'),
|
|
419
|
+
end.format('D MMMM YYYY')
|
|
420
|
+
).value;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (typeof props.modelValue === 'string') {
|
|
424
|
+
const parsed = toInnerValue(props.modelValue);
|
|
425
|
+
if (!parsed.isValid()) return t('baseDatePickerInput.emptySelection').value;
|
|
426
|
+
|
|
427
|
+
return t(
|
|
428
|
+
'baseDatePickerInput.changeSelection',
|
|
429
|
+
parsed.format('D MMMM YYYY')
|
|
430
|
+
).value;
|
|
431
|
+
}
|
|
432
|
+
return t('baseDatePickerInput.emptySelection').value;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const isDateDisabled = (date: Dayjs) => {
|
|
436
|
+
if (!date.isValid()) return true;
|
|
437
|
+
|
|
438
|
+
let passing = true;
|
|
439
|
+
if (props.min) {
|
|
440
|
+
const min = dayjs(props.min, BOUNDARY_DATE_FORMAT, true);
|
|
441
|
+
passing &&= date.isSameOrAfter(min);
|
|
442
|
+
}
|
|
443
|
+
if (props.max) {
|
|
444
|
+
const max = dayjs(props.max, BOUNDARY_DATE_FORMAT, true);
|
|
445
|
+
passing &&= date.isSameOrBefore(max);
|
|
446
|
+
}
|
|
447
|
+
if (typeof props.selectable === 'function') {
|
|
448
|
+
passing &&= props.selectable(toOuterValue(date));
|
|
449
|
+
}
|
|
450
|
+
return !passing;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const onClickClear = () => {
|
|
454
|
+
if (props.range) {
|
|
455
|
+
emit('update:modelValue', []);
|
|
456
|
+
} else {
|
|
457
|
+
emit('update:modelValue', null);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// ==========================================================================
|
|
462
|
+
// Segmented field system — used for both single and range modes
|
|
463
|
+
// ==========================================================================
|
|
464
|
+
|
|
465
|
+
type DateFieldKey = 'day' | 'month' | 'year';
|
|
466
|
+
type Segment = 'start' | 'end';
|
|
467
|
+
type FieldDescriptor = { segment: Segment; field: DateFieldKey };
|
|
468
|
+
|
|
469
|
+
const descriptorKey = (d: FieldDescriptor) => `${d.segment}.${d.field}`;
|
|
470
|
+
|
|
471
|
+
const dateFieldOrder = computed<DateFieldKey[]>(() => {
|
|
472
|
+
// For Italian and most European locales, use DD/MM/YYYY
|
|
473
|
+
// For English and US locales, use MM/DD/YYYY
|
|
474
|
+
const locale = _config.locale;
|
|
475
|
+
if (locale === 'en') return ['month', 'day', 'year'];
|
|
476
|
+
return ['day', 'month', 'year'];
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const fieldOrder = computed<FieldDescriptor[]>(() => {
|
|
480
|
+
const order = dateFieldOrder.value;
|
|
481
|
+
const start: FieldDescriptor[] = order.map((field) => ({
|
|
482
|
+
segment: 'start',
|
|
483
|
+
field,
|
|
484
|
+
}));
|
|
485
|
+
if (!props.range) return start;
|
|
486
|
+
const end: FieldDescriptor[] = order.map((field) => ({
|
|
487
|
+
segment: 'end',
|
|
488
|
+
field,
|
|
489
|
+
}));
|
|
490
|
+
return [...start, ...end];
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const emptyFieldGroup = (): Record<DateFieldKey, string> => ({
|
|
494
|
+
day: '',
|
|
495
|
+
month: '',
|
|
496
|
+
year: '',
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const fields = ref<Record<Segment, Record<DateFieldKey, string>>>({
|
|
500
|
+
start: emptyFieldGroup(),
|
|
501
|
+
end: emptyFieldGroup(),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const fieldRefs: Record<string, HTMLInputElement | null> = {};
|
|
505
|
+
|
|
506
|
+
const isOwnField = (el: Element) => {
|
|
507
|
+
for (const key in fieldRefs) {
|
|
508
|
+
if (fieldRefs[key] === el) return true;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const getFieldId = (d: FieldDescriptor): string => {
|
|
514
|
+
if (!props.range) return `${id}-${d.field}`;
|
|
515
|
+
return `${id}-${d.segment}-${d.field}`;
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const getFieldName = (d: FieldDescriptor): string | undefined => {
|
|
519
|
+
if (!props.name) return undefined;
|
|
520
|
+
if (!props.range) return `${props.name}-${d.field}`;
|
|
521
|
+
return `${props.name}-${d.segment}-${d.field}`;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const getFieldLabel = (descriptor: FieldDescriptor): string => {
|
|
525
|
+
const base: Record<DateFieldKey, string> = {
|
|
526
|
+
day: 'Day',
|
|
527
|
+
month: 'Month',
|
|
528
|
+
year: 'Year',
|
|
529
|
+
};
|
|
530
|
+
if (!props.range) return base[descriptor.field];
|
|
531
|
+
const prefix = descriptor.segment === 'start' ? 'Start' : 'End';
|
|
532
|
+
return `${prefix} ${base[descriptor.field].toLowerCase()}`;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const placeholderText = computed(() => {
|
|
536
|
+
if (props.placeholder) return props.placeholder;
|
|
537
|
+
if (props.range) return 'DD/MM/YYYY – DD/MM/YYYY';
|
|
538
|
+
return 'DD/MM/YYYY';
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const showPlaceholder = computed(() => {
|
|
542
|
+
const s = fields.value.start;
|
|
543
|
+
const startEmpty = !s.day && !s.month && !s.year;
|
|
544
|
+
if (!props.range) return startEmpty;
|
|
545
|
+
const e = fields.value.end;
|
|
546
|
+
const endEmpty = !e.day && !e.month && !e.year;
|
|
547
|
+
return startEmpty && endEmpty;
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const parseValueToFieldGroup = (
|
|
551
|
+
value: string | null | undefined
|
|
552
|
+
): Record<DateFieldKey, string> => {
|
|
553
|
+
if (!value) return emptyFieldGroup();
|
|
554
|
+
const parsed = toInnerValue(value);
|
|
555
|
+
if (!parsed.isValid()) return emptyFieldGroup();
|
|
556
|
+
return {
|
|
557
|
+
day: parsed.format('DD'),
|
|
558
|
+
month: parsed.format('MM'),
|
|
559
|
+
year: parsed.format('YYYY'),
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const parseModelValueToFields = (value: typeof props.modelValue) => {
|
|
564
|
+
if (!value) {
|
|
565
|
+
fields.value = { start: emptyFieldGroup(), end: emptyFieldGroup() };
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (props.range) {
|
|
569
|
+
const arr = Array.isArray(value) ? value : [];
|
|
570
|
+
const [startRaw, endRaw] = arr;
|
|
571
|
+
fields.value = {
|
|
572
|
+
start: parseValueToFieldGroup(startRaw),
|
|
573
|
+
end: parseValueToFieldGroup(endRaw),
|
|
574
|
+
};
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (Array.isArray(value)) {
|
|
578
|
+
fields.value = { start: emptyFieldGroup(), end: emptyFieldGroup() };
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
fields.value = {
|
|
582
|
+
start: parseValueToFieldGroup(value),
|
|
583
|
+
end: emptyFieldGroup(),
|
|
584
|
+
};
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Restores the field inputs to match the current modelValue.
|
|
589
|
+
* Called when focus leaves the component to ensure display coherence.
|
|
590
|
+
*/
|
|
591
|
+
const restoreFieldsFromModelValue = () => {
|
|
592
|
+
parseModelValueToFields(props.modelValue);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// Initialize fields from modelValue
|
|
596
|
+
parseModelValueToFields(props.modelValue);
|
|
597
|
+
|
|
598
|
+
// Watch for external modelValue changes
|
|
599
|
+
watch(
|
|
600
|
+
() => props.modelValue,
|
|
601
|
+
(value) => {
|
|
602
|
+
// Always clear fields when modelValue is cleared from outside
|
|
603
|
+
if (value === null || (Array.isArray(value) && value.length === 0)) {
|
|
604
|
+
parseModelValueToFields(value);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Only update if not currently editing (to avoid overwriting user input)
|
|
609
|
+
const activeEl = document.activeElement;
|
|
610
|
+
if (activeEl && isOwnField(activeEl)) return;
|
|
611
|
+
parseModelValueToFields(value);
|
|
612
|
+
}
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const focusField = (
|
|
616
|
+
descriptor: FieldDescriptor,
|
|
617
|
+
options: {
|
|
618
|
+
select?: boolean;
|
|
619
|
+
caretToEnd?: boolean;
|
|
620
|
+
caretToStart?: boolean;
|
|
621
|
+
} = {}
|
|
622
|
+
) => {
|
|
623
|
+
const input = fieldRefs[descriptorKey(descriptor)];
|
|
624
|
+
if (!input) return;
|
|
625
|
+
input.focus();
|
|
626
|
+
if (options.select) input.select();
|
|
627
|
+
else if (options.caretToEnd)
|
|
628
|
+
input.setSelectionRange(input.value.length, input.value.length);
|
|
629
|
+
else if (options.caretToStart) input.setSelectionRange(0, 0);
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
const focusFirstField = () => {
|
|
633
|
+
focusField(fieldOrder.value[0]);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const findDescriptorIndex = (descriptor: FieldDescriptor) =>
|
|
637
|
+
fieldOrder.value.findIndex(
|
|
638
|
+
(d) => d.segment === descriptor.segment && d.field === descriptor.field
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
const advanceFromField = (descriptor: FieldDescriptor) => {
|
|
642
|
+
const idx = findDescriptorIndex(descriptor);
|
|
643
|
+
if (idx < fieldOrder.value.length - 1) {
|
|
644
|
+
focusField(fieldOrder.value[idx + 1], { select: true });
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const retreatFromField = (descriptor: FieldDescriptor) => {
|
|
649
|
+
const idx = findDescriptorIndex(descriptor);
|
|
650
|
+
if (idx > 0) focusField(fieldOrder.value[idx - 1], { caretToEnd: true });
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
type SegmentStatus = 'empty' | 'partial' | 'complete';
|
|
654
|
+
|
|
655
|
+
const segmentStatus = (segment: Segment): SegmentStatus => {
|
|
656
|
+
const g = fields.value[segment];
|
|
657
|
+
const filledCount = (g.day ? 1 : 0) + (g.month ? 1 : 0) + (g.year ? 1 : 0);
|
|
658
|
+
if (filledCount === 0) return 'empty';
|
|
659
|
+
// Year must be 4 digits to disambiguate; day/month can be 1 or 2 (they'll be
|
|
660
|
+
// padded by parseSegmentAndCorrect before emission).
|
|
661
|
+
if (g.day && g.month && g.year && g.year.length === 4) return 'complete';
|
|
662
|
+
return 'partial';
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const parseSegmentAndCorrect = (segment: Segment): Dayjs | null => {
|
|
666
|
+
const g = fields.value[segment];
|
|
667
|
+
if (!g.day || !g.month || !g.year) return null;
|
|
668
|
+
|
|
669
|
+
const dayNum = parseInt(g.day, 10);
|
|
670
|
+
const monthNum = parseInt(g.month, 10);
|
|
671
|
+
const yearNum = parseInt(g.year, 10);
|
|
672
|
+
|
|
673
|
+
if (
|
|
674
|
+
dayNum < 1 ||
|
|
675
|
+
dayNum > 31 ||
|
|
676
|
+
monthNum < 1 ||
|
|
677
|
+
monthNum > 12 ||
|
|
678
|
+
yearNum < 1000 ||
|
|
679
|
+
yearNum > 9999
|
|
680
|
+
) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const year = g.year.padStart(4, '0');
|
|
685
|
+
const month = g.month.padStart(2, '0');
|
|
686
|
+
const day = g.day.padStart(2, '0');
|
|
687
|
+
const dateString = `${year}-${month}-${day}`;
|
|
688
|
+
let parsed = dayjs(dateString, BOUNDARY_DATE_FORMAT, true);
|
|
689
|
+
|
|
690
|
+
// If date is invalid (e.g., Feb 31), snap to the last valid day of that month
|
|
691
|
+
if (!parsed.isValid()) {
|
|
692
|
+
const lastDayOfMonth = dayjs(
|
|
693
|
+
`${year}-${month}-01`,
|
|
694
|
+
BOUNDARY_DATE_FORMAT,
|
|
695
|
+
true
|
|
696
|
+
)
|
|
697
|
+
.endOf('month')
|
|
698
|
+
.date();
|
|
699
|
+
const correctedDay = Math.min(dayNum, lastDayOfMonth);
|
|
700
|
+
const correctedDateString = `${year}-${month}-${String(correctedDay).padStart(2, '0')}`;
|
|
701
|
+
parsed = dayjs(correctedDateString, BOUNDARY_DATE_FORMAT, true);
|
|
702
|
+
fields.value[segment].day = String(correctedDay).padStart(2, '0');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return parsed.isValid() ? parsed : null;
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Converts a Dayjs into the three display field strings (DD, MM, YYYY) used by the inputs.
|
|
710
|
+
*/
|
|
711
|
+
const dayjsToFieldGroup = (d: Dayjs): Record<DateFieldKey, string> => ({
|
|
712
|
+
day: d.format('DD'),
|
|
713
|
+
month: d.format('MM'),
|
|
714
|
+
year: d.format('YYYY'),
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Attempts to snap a disabled date to the nearest valid boundary.
|
|
719
|
+
*
|
|
720
|
+
* - If the date is below `min`, returns `min` (provided `min` itself passes all constraints).
|
|
721
|
+
* - If the date is above `max`, returns `max` (same caveat).
|
|
722
|
+
* - If the date is disabled only by `selectable` (i.e. within min/max), there is no
|
|
723
|
+
* deterministic "closest" value, so `null` is returned and the caller should remove it.
|
|
724
|
+
*/
|
|
725
|
+
const normalizeDisabledDate = (date: Dayjs): Dayjs | null => {
|
|
726
|
+
let candidate = date;
|
|
727
|
+
if (props.min) {
|
|
728
|
+
const min = dayjs(props.min, BOUNDARY_DATE_FORMAT, true);
|
|
729
|
+
if (candidate.isBefore(min, 'day')) candidate = min;
|
|
730
|
+
}
|
|
731
|
+
if (props.max) {
|
|
732
|
+
const max = dayjs(props.max, BOUNDARY_DATE_FORMAT, true);
|
|
733
|
+
if (candidate.isAfter(max, 'day')) candidate = max;
|
|
734
|
+
}
|
|
735
|
+
// No boundary clamping happened → date is disabled by `selectable` only
|
|
736
|
+
if (candidate.isSame(date, 'day')) return null;
|
|
737
|
+
// The boundary itself might be blocked by `selectable`
|
|
738
|
+
if (isDateDisabled(candidate)) return null;
|
|
739
|
+
return candidate;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const emitError = (error: DatePickerInputError) => {
|
|
743
|
+
logger.warn(
|
|
744
|
+
`[${error.code}] original: ${JSON.stringify(error.original)} → normalized: ${JSON.stringify(error.normalized)}`
|
|
745
|
+
);
|
|
746
|
+
emit('error', error);
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
const combineFieldsToModelValue = (): void => {
|
|
750
|
+
const startStatus = segmentStatus('start');
|
|
751
|
+
|
|
752
|
+
if (!props.range) {
|
|
753
|
+
if (startStatus === 'empty') {
|
|
754
|
+
emit('update:modelValue', null);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (startStatus === 'partial') return;
|
|
758
|
+
|
|
759
|
+
const parsed = parseSegmentAndCorrect('start');
|
|
760
|
+
if (!parsed) return;
|
|
761
|
+
if (isDateDisabled(parsed)) {
|
|
762
|
+
const original = toOuterValue(parsed);
|
|
763
|
+
const candidate = normalizeDisabledDate(parsed);
|
|
764
|
+
if (candidate) {
|
|
765
|
+
const normalized = toOuterValue(candidate);
|
|
766
|
+
fields.value.start = dayjsToFieldGroup(candidate);
|
|
767
|
+
emit('update:modelValue', normalized);
|
|
768
|
+
emitError({ code: 'disabled_date', original, normalized });
|
|
769
|
+
} else {
|
|
770
|
+
fields.value.start = emptyFieldGroup();
|
|
771
|
+
emit('update:modelValue', null);
|
|
772
|
+
emitError({ code: 'disabled_date', original, normalized: null });
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
emit('update:modelValue', toOuterValue(parsed));
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// ── Range mode ──────────────────────────────────────────────────────────────
|
|
781
|
+
|
|
782
|
+
const endStatus = segmentStatus('end');
|
|
783
|
+
|
|
784
|
+
if (startStatus === 'empty' && endStatus === 'empty') {
|
|
785
|
+
emit('update:modelValue', null);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (startStatus === 'partial' || endStatus === 'partial') return;
|
|
789
|
+
// end complete but start empty is ambiguous — defer until user fills start
|
|
790
|
+
if (startStatus === 'empty') return;
|
|
791
|
+
|
|
792
|
+
const startParsed = parseSegmentAndCorrect('start');
|
|
793
|
+
if (!startParsed) return;
|
|
794
|
+
const endParsed =
|
|
795
|
+
endStatus !== 'empty' ? parseSegmentAndCorrect('end') : null;
|
|
796
|
+
|
|
797
|
+
// Original outer values captured before any normalization
|
|
798
|
+
const startOriginal = toOuterValue(startParsed);
|
|
799
|
+
const endOriginal = endParsed ? toOuterValue(endParsed) : null;
|
|
800
|
+
|
|
801
|
+
// Normalize start if disabled ──────────────────────────────────────────────
|
|
802
|
+
let effectiveStart = startParsed;
|
|
803
|
+
let startNormalized = false;
|
|
804
|
+
|
|
805
|
+
if (isDateDisabled(startParsed)) {
|
|
806
|
+
const candidate = normalizeDisabledDate(startParsed);
|
|
807
|
+
const original: string[] = endOriginal
|
|
808
|
+
? [startOriginal, endOriginal]
|
|
809
|
+
: [startOriginal];
|
|
810
|
+
if (!candidate) {
|
|
811
|
+
fields.value.start = emptyFieldGroup();
|
|
812
|
+
fields.value.end = emptyFieldGroup();
|
|
813
|
+
emit('update:modelValue', null);
|
|
814
|
+
emitError({ code: 'disabled_range_start', original, normalized: null });
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
effectiveStart = candidate;
|
|
818
|
+
startNormalized = true;
|
|
819
|
+
fields.value.start = dayjsToFieldGroup(candidate);
|
|
820
|
+
if (!endParsed) {
|
|
821
|
+
const normalized = [toOuterValue(candidate)];
|
|
822
|
+
emit('update:modelValue', normalized);
|
|
823
|
+
emitError({ code: 'disabled_range_start', original, normalized });
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (!endParsed) {
|
|
829
|
+
emit('update:modelValue', [toOuterValue(effectiveStart)]);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Normalize end if disabled ────────────────────────────────────────────────
|
|
834
|
+
let effectiveEnd = endParsed;
|
|
835
|
+
let endNormalized = false;
|
|
836
|
+
|
|
837
|
+
if (isDateDisabled(endParsed)) {
|
|
838
|
+
const candidate = normalizeDisabledDate(endParsed);
|
|
839
|
+
const original: [string, string] = [startOriginal, endOriginal!];
|
|
840
|
+
if (!candidate) {
|
|
841
|
+
const normalized: [string] = [toOuterValue(effectiveStart)];
|
|
842
|
+
fields.value.end = emptyFieldGroup();
|
|
843
|
+
emit('update:modelValue', normalized);
|
|
844
|
+
if (startNormalized)
|
|
845
|
+
emitError({
|
|
846
|
+
code: 'disabled_range_start',
|
|
847
|
+
original,
|
|
848
|
+
normalized,
|
|
849
|
+
});
|
|
850
|
+
emitError({ code: 'disabled_range_end', original, normalized });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
effectiveEnd = candidate;
|
|
854
|
+
endNormalized = true;
|
|
855
|
+
fields.value.end = dayjsToFieldGroup(candidate);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Ordering check ───────────────────────────────────────────────────────────
|
|
859
|
+
if (effectiveEnd.isBefore(effectiveStart, 'day')) {
|
|
860
|
+
const original: [string, string] = [
|
|
861
|
+
toOuterValue(effectiveStart),
|
|
862
|
+
toOuterValue(effectiveEnd),
|
|
863
|
+
];
|
|
864
|
+
const normalized: [string, string] = [
|
|
865
|
+
toOuterValue(effectiveEnd),
|
|
866
|
+
toOuterValue(effectiveStart),
|
|
867
|
+
];
|
|
868
|
+
const savedStart = { ...fields.value.start };
|
|
869
|
+
fields.value.start = { ...fields.value.end };
|
|
870
|
+
fields.value.end = savedStart;
|
|
871
|
+
emit('update:modelValue', normalized);
|
|
872
|
+
if (startNormalized)
|
|
873
|
+
emitError({
|
|
874
|
+
code: 'disabled_range_start',
|
|
875
|
+
original: [startOriginal, endOriginal!],
|
|
876
|
+
normalized,
|
|
877
|
+
});
|
|
878
|
+
if (endNormalized)
|
|
879
|
+
emitError({
|
|
880
|
+
code: 'disabled_range_end',
|
|
881
|
+
original: [startOriginal, endOriginal!],
|
|
882
|
+
normalized,
|
|
883
|
+
});
|
|
884
|
+
emitError({ code: 'end_before_start', original, normalized });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// All valid ────────────────────────────────────────────────────────────────
|
|
889
|
+
const finalValue: [string, string] = [
|
|
890
|
+
toOuterValue(effectiveStart),
|
|
891
|
+
toOuterValue(effectiveEnd),
|
|
892
|
+
];
|
|
893
|
+
emit('update:modelValue', finalValue);
|
|
894
|
+
if (startNormalized)
|
|
895
|
+
emitError({
|
|
896
|
+
code: 'disabled_range_start',
|
|
897
|
+
original: endOriginal ? [startOriginal, endOriginal] : [startOriginal],
|
|
898
|
+
normalized: finalValue,
|
|
899
|
+
});
|
|
900
|
+
if (endNormalized)
|
|
901
|
+
emitError({
|
|
902
|
+
code: 'disabled_range_end',
|
|
903
|
+
original: [startOriginal, endOriginal!],
|
|
904
|
+
normalized: finalValue,
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
const onFieldInput = (descriptor: FieldDescriptor, event: Event) => {
|
|
909
|
+
const { segment, field } = descriptor;
|
|
910
|
+
const target = event.target as HTMLInputElement;
|
|
911
|
+
let value = target.value.replace(/\D/g, ''); // Only keep digits
|
|
912
|
+
|
|
913
|
+
const previous = fields.value[segment][field];
|
|
914
|
+
|
|
915
|
+
// Clamp values based on field type
|
|
916
|
+
if (field === 'day') {
|
|
917
|
+
if (value.length > 0) {
|
|
918
|
+
const num = parseInt(value, 10);
|
|
919
|
+
// Reject "00" or values over 31
|
|
920
|
+
if (value === '00' || num > 31) {
|
|
921
|
+
value = previous;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} else if (field === 'month') {
|
|
925
|
+
if (value.length > 0) {
|
|
926
|
+
const num = parseInt(value, 10);
|
|
927
|
+
// Reject "00" or values over 12
|
|
928
|
+
if (value === '00' || num > 12) {
|
|
929
|
+
value = previous;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
} else if (field === 'year') {
|
|
933
|
+
value = value.slice(0, 4);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
fields.value[segment][field] = value;
|
|
937
|
+
target.value = value;
|
|
938
|
+
|
|
939
|
+
// Smart auto-advance: if field is complete OR can't accept another digit
|
|
940
|
+
const maxLength = field === 'year' ? 4 : 2;
|
|
941
|
+
let shouldAdvance = value.length === maxLength;
|
|
942
|
+
|
|
943
|
+
// For single-digit day/month, check if another digit is possible
|
|
944
|
+
if (!shouldAdvance && value.length === 1) {
|
|
945
|
+
const num = parseInt(value, 10);
|
|
946
|
+
if (field === 'day' && num >= 4) {
|
|
947
|
+
// Days 4-9 can only be 04-09 (no valid 40+)
|
|
948
|
+
value = value.padStart(2, '0');
|
|
949
|
+
fields.value[segment][field] = value;
|
|
950
|
+
target.value = value;
|
|
951
|
+
shouldAdvance = true;
|
|
952
|
+
} else if (field === 'month' && num >= 2) {
|
|
953
|
+
// Months 2-9 can only be 02-09 (no valid 20+)
|
|
954
|
+
value = value.padStart(2, '0');
|
|
955
|
+
fields.value[segment][field] = value;
|
|
956
|
+
target.value = value;
|
|
957
|
+
shouldAdvance = true;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (shouldAdvance) advanceFromField(descriptor);
|
|
962
|
+
|
|
963
|
+
combineFieldsToModelValue();
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
const onFieldKeydown = (descriptor: FieldDescriptor, event: KeyboardEvent) => {
|
|
967
|
+
const target = event.target as HTMLInputElement;
|
|
968
|
+
|
|
969
|
+
// Any of `/`, `-`, ` ` advances to the next field, padding a single digit first
|
|
970
|
+
if (event.key === '/' || event.key === '-' || event.key === ' ') {
|
|
971
|
+
event.preventDefault();
|
|
972
|
+
const current = fields.value[descriptor.segment][descriptor.field];
|
|
973
|
+
if (descriptor.field !== 'year' && current.length === 1) {
|
|
974
|
+
if (current === '0')
|
|
975
|
+
fields.value[descriptor.segment][descriptor.field] = '';
|
|
976
|
+
else
|
|
977
|
+
fields.value[descriptor.segment][descriptor.field] = current.padStart(
|
|
978
|
+
2,
|
|
979
|
+
'0'
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
advanceFromField(descriptor);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Backspace at start of field: go to previous field (don't swallow the delete
|
|
987
|
+
// otherwise — let the browser remove a digit normally).
|
|
988
|
+
if (
|
|
989
|
+
event.key === 'Backspace' &&
|
|
990
|
+
target.selectionStart === 0 &&
|
|
991
|
+
target.selectionEnd === 0
|
|
992
|
+
) {
|
|
993
|
+
const idx = findDescriptorIndex(descriptor);
|
|
994
|
+
if (idx > 0) {
|
|
995
|
+
event.preventDefault();
|
|
996
|
+
retreatFromField(descriptor);
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Arrow left at start: go to previous field
|
|
1002
|
+
if (event.key === 'ArrowLeft' && target.selectionStart === 0) {
|
|
1003
|
+
const idx = findDescriptorIndex(descriptor);
|
|
1004
|
+
if (idx > 0) {
|
|
1005
|
+
event.preventDefault();
|
|
1006
|
+
retreatFromField(descriptor);
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Arrow right at end: go to next field
|
|
1012
|
+
if (
|
|
1013
|
+
event.key === 'ArrowRight' &&
|
|
1014
|
+
target.selectionStart === target.value.length
|
|
1015
|
+
) {
|
|
1016
|
+
const idx = findDescriptorIndex(descriptor);
|
|
1017
|
+
if (idx < fieldOrder.value.length - 1) {
|
|
1018
|
+
event.preventDefault();
|
|
1019
|
+
focusField(fieldOrder.value[idx + 1], { caretToStart: true });
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const onFieldFocus = (descriptor: FieldDescriptor) => {
|
|
1026
|
+
// If all fields are empty and focusing a non-first field, redirect to first
|
|
1027
|
+
const first = fieldOrder.value[0];
|
|
1028
|
+
const allEmpty = fieldOrder.value.every(
|
|
1029
|
+
(d) => !fields.value[d.segment][d.field]
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
if (
|
|
1033
|
+
allEmpty &&
|
|
1034
|
+
(descriptor.segment !== first.segment || descriptor.field !== first.field)
|
|
1035
|
+
) {
|
|
1036
|
+
requestAnimationFrame(() => focusField(first));
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Select all on focus for easy replacement
|
|
1041
|
+
const input = fieldRefs[descriptorKey(descriptor)];
|
|
1042
|
+
if (input && input.value) {
|
|
1043
|
+
requestAnimationFrame(() => input.select());
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const onFieldBlur = (descriptor: FieldDescriptor) => {
|
|
1048
|
+
const { segment, field } = descriptor;
|
|
1049
|
+
// Pad values when blurring, but clear if it's just "0"
|
|
1050
|
+
if (field === 'day' || field === 'month') {
|
|
1051
|
+
const val = fields.value[segment][field];
|
|
1052
|
+
if (val && val.length === 1) {
|
|
1053
|
+
if (val === '0') fields.value[segment][field] = '';
|
|
1054
|
+
else fields.value[segment][field] = val.padStart(2, '0');
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
combineFieldsToModelValue();
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Paste handling: distribute digits across the remaining fields starting from the
|
|
1062
|
+
* current one. This makes pasting strings like "01/02/2024" or
|
|
1063
|
+
* "01/02/2024 - 03/04/2025" feel natural regardless of the original separators.
|
|
1064
|
+
*/
|
|
1065
|
+
const onFieldPaste = (descriptor: FieldDescriptor, event: ClipboardEvent) => {
|
|
1066
|
+
const text = event.clipboardData?.getData('text') ?? '';
|
|
1067
|
+
const digits = text.replace(/\D/g, '');
|
|
1068
|
+
if (digits.length < 2) return; // let the native handler process single digits
|
|
1069
|
+
|
|
1070
|
+
event.preventDefault();
|
|
1071
|
+
|
|
1072
|
+
const startIndex = findDescriptorIndex(descriptor);
|
|
1073
|
+
let cursor = 0;
|
|
1074
|
+
let lastFilled: FieldDescriptor | null = null;
|
|
1075
|
+
|
|
1076
|
+
for (
|
|
1077
|
+
let i = startIndex;
|
|
1078
|
+
i < fieldOrder.value.length && cursor < digits.length;
|
|
1079
|
+
i++
|
|
1080
|
+
) {
|
|
1081
|
+
const d = fieldOrder.value[i];
|
|
1082
|
+
const maxLen = d.field === 'year' ? 4 : 2;
|
|
1083
|
+
const slice = digits.slice(cursor, cursor + maxLen);
|
|
1084
|
+
if (!slice) break;
|
|
1085
|
+
fields.value[d.segment][d.field] = slice;
|
|
1086
|
+
lastFilled = d;
|
|
1087
|
+
cursor += maxLen;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (lastFilled) {
|
|
1091
|
+
const target = lastFilled;
|
|
1092
|
+
requestAnimationFrame(() => focusField(target, { caretToEnd: true }));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
combineFieldsToModelValue();
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
onBeforeUnmount(() => {
|
|
1099
|
+
document.removeEventListener('focusin', onDocumentInteraction);
|
|
1100
|
+
document.removeEventListener('click', onDocumentInteraction);
|
|
1101
|
+
document.removeEventListener('click', onClickDocument);
|
|
1102
|
+
});
|
|
1103
|
+
</script>
|
|
1104
|
+
|
|
1105
|
+
<style lang="postcss">
|
|
1106
|
+
@import './index.css';
|
|
1107
|
+
</style>
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
## Types
|
|
1111
|
+
|
|
1112
|
+
```ts
|
|
1113
|
+
import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
|
|
1114
|
+
import type { IconType } from '@/types/Icon';
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Props for the low-level date-picker input that couples a segmented text field with a popover calendar.
|
|
1118
|
+
* Accepts ISO strings (or `YYYY-MM-DD` when `floating` is true) and can operate in single or range mode.
|
|
1119
|
+
* In both modes users type directly into segmented day / month / year inputs with auto-advance between them.
|
|
1120
|
+
*/
|
|
1121
|
+
export type BaseDatePickerInputProps = {
|
|
1122
|
+
/**
|
|
1123
|
+
* Controls whether manual typing is allowed inside the text fields.
|
|
1124
|
+
* Use `'not-mobile'` to disable typing on mobile devices while keeping it on desktop.
|
|
1125
|
+
* Applies to both single and range modes.
|
|
1126
|
+
* @defaultValue `true`
|
|
1127
|
+
*/
|
|
1128
|
+
allowWriting?: boolean | 'not-mobile';
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Name of the icon to render at the right hand side of the input.
|
|
1132
|
+
*/
|
|
1133
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
1134
|
+
'append:icon'?: IconType;
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Browser autocomplete hint for the input.
|
|
1138
|
+
* @defaultValue `'off'`
|
|
1139
|
+
*/
|
|
1140
|
+
autocomplete?: InputHTMLAttributes['autocomplete'];
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Focus the first text field automatically when the component mounts.
|
|
1144
|
+
*/
|
|
1145
|
+
autofocus?: InputHTMLAttributes['autofocus'];
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Displays a clear button whenever a value is present and the control is interactive.
|
|
1149
|
+
*/
|
|
1150
|
+
clearable?: boolean;
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Applies the compact density styles to the control.
|
|
1154
|
+
*/
|
|
1155
|
+
compact?: boolean;
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Disables the input, calendar trigger, and popover.
|
|
1159
|
+
*/
|
|
1160
|
+
disabled?: boolean;
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Sets the first day of the week for the calendar (0 = Sunday, 6 = Saturday).
|
|
1164
|
+
*/
|
|
1165
|
+
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* Emit dates formatted as `YYYY-MM-DD` instead of ISO strings.
|
|
1169
|
+
*/
|
|
1170
|
+
floating?: boolean;
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Marks the control as invalid, applying error styles and setting `aria-invalid`.
|
|
1174
|
+
*/
|
|
1175
|
+
hasErrors?: boolean;
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Explicit id for the input element. Falls back to an auto-generated `bdpi-<uid>` value.
|
|
1179
|
+
*/
|
|
1180
|
+
id?: HTMLAttributes['id'];
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Shows loading visuals on the input.
|
|
1184
|
+
*/
|
|
1185
|
+
loading?: boolean;
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Maximum selectable date in `YYYY-MM-DD` format.
|
|
1189
|
+
* Values outside the pattern throw during setup to surface configuration mistakes.
|
|
1190
|
+
*/
|
|
1191
|
+
max?: string;
|
|
1192
|
+
|
|
1193
|
+
/**
|
|
1194
|
+
* Minimum selectable date in `YYYY-MM-DD` format.
|
|
1195
|
+
* Values outside the pattern throw during setup to surface configuration mistakes.
|
|
1196
|
+
*/
|
|
1197
|
+
min?: string;
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* v-model value. Use `null` for empty, a single string for single selection,
|
|
1201
|
+
* or a two-element string array (`[start, end]`) when `range` is true.
|
|
1202
|
+
*/
|
|
1203
|
+
modelValue: string | string[] | null;
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Name attribute forwarded to the native inputs. In single mode fields are named
|
|
1207
|
+
* `${name}-day`, `${name}-month`, `${name}-year`; in range mode they are prefixed
|
|
1208
|
+
* with the segment (`${name}-start-day`, `${name}-end-day`, …).
|
|
1209
|
+
*/
|
|
1210
|
+
name?: InputHTMLAttributes['name'];
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Placeholder text displayed when no value is present. Defaults to a format hint
|
|
1214
|
+
* (`DD/MM/YYYY` or `DD/MM/YYYY – DD/MM/YYYY` for range mode).
|
|
1215
|
+
*/
|
|
1216
|
+
placeholder?: InputHTMLAttributes['placeholder'];
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Icon name rendered before the input field.
|
|
1220
|
+
*/
|
|
1221
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
1222
|
+
'prepend:icon'?: IconType;
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Enables range selection. When active, v-model expects a two-element array `[start, end]`.
|
|
1226
|
+
* Users can still type into the start and end segments as long as `allowWriting` is truthy.
|
|
1227
|
+
*/
|
|
1228
|
+
range?: boolean;
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Makes the text fields read-only and prevents opening the calendar.
|
|
1232
|
+
*/
|
|
1233
|
+
readonly?: boolean;
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Marks the inputs as required to the browser.
|
|
1237
|
+
*/
|
|
1238
|
+
required?: boolean;
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Predicate invoked for each candidate date. Return `false` to disable selection for that value.
|
|
1242
|
+
*/
|
|
1243
|
+
selectable?: (date: string) => boolean;
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Hides the calendar trigger button and prevents the calendar popover from opening.
|
|
1247
|
+
* Use `'not-mobile'` to hide the popover on desktop while keeping it visible on mobile,
|
|
1248
|
+
* complementing `allowWriting: 'not-mobile'` (desktop = keyboard-first, mobile = calendar-first).
|
|
1249
|
+
* An error is thrown if both writing and the popover would be unavailable on the same platform.
|
|
1250
|
+
*/
|
|
1251
|
+
hidePopover?: boolean | 'not-mobile';
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Transition duration, in milliseconds, for the popover appearance.
|
|
1255
|
+
*/
|
|
1256
|
+
transitionDuration?: number;
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* Structured payload carried by every `error` event.
|
|
1261
|
+
*
|
|
1262
|
+
* | `code` | Trigger | `original` | `normalized` |
|
|
1263
|
+
* |------------------------|------------------------------------|------------------------------|-------------------------------------------------------|
|
|
1264
|
+
* | `disabled_date` | Typed single date is disabled | The disabled date | Snapped to `min`/`max`, or `null` if not normalizable |
|
|
1265
|
+
* | `disabled_range_start` | Typed range start is disabled | `[start]` or `[start, end]` | Snapped range, or `null` if start is not normalizable |
|
|
1266
|
+
* | `disabled_range_end` | Typed range end is disabled | `[start, end]` | Snapped range (length 1 or 2), or `null` |
|
|
1267
|
+
* | `end_before_start` | Typed end is chronologically first | `[start, end]` (wrong order) | `[end, start]` — always the swapped pair |
|
|
1268
|
+
*/
|
|
1269
|
+
export type DatePickerInputError =
|
|
1270
|
+
| {
|
|
1271
|
+
code: 'disabled_date';
|
|
1272
|
+
/** The value the user typed before normalization. */
|
|
1273
|
+
original: string;
|
|
1274
|
+
/** Snapped to the nearest `min`/`max` boundary, or `null` if disabled by `selectable`. */
|
|
1275
|
+
normalized: string | null;
|
|
1276
|
+
}
|
|
1277
|
+
| {
|
|
1278
|
+
code: 'disabled_range_start';
|
|
1279
|
+
/** The range value before normalization (may be `[start]` or `[start, end]`). */
|
|
1280
|
+
original: string[];
|
|
1281
|
+
/** Snapped range, or `null` if start could not be normalized. */
|
|
1282
|
+
normalized: string[] | null;
|
|
1283
|
+
}
|
|
1284
|
+
| {
|
|
1285
|
+
code: 'disabled_range_end';
|
|
1286
|
+
/** The full range before normalization. */
|
|
1287
|
+
original: [string, string];
|
|
1288
|
+
/** Range with end snapped, or `[start]` if end could not be normalized. */
|
|
1289
|
+
normalized: string[] | null;
|
|
1290
|
+
}
|
|
1291
|
+
| {
|
|
1292
|
+
code: 'end_before_start';
|
|
1293
|
+
/** `[start, end]` in the order the user typed them. */
|
|
1294
|
+
original: [string, string];
|
|
1295
|
+
/** `[end, start]` — the chronologically corrected pair. */
|
|
1296
|
+
normalized: [string, string];
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
export type BaseDatePickerInputEvents = {
|
|
1300
|
+
/**
|
|
1301
|
+
* Emitted when the composite control receives focus (first focusin after being inactive).
|
|
1302
|
+
*/
|
|
1303
|
+
(e: 'active'): void;
|
|
1304
|
+
/**
|
|
1305
|
+
* Emitted alongside every value normalization or removal when the typed date is invalid or out of range.
|
|
1306
|
+
* See `DatePickerInputError` for the full code map and payload shapes.
|
|
1307
|
+
*/
|
|
1308
|
+
(e: 'error', error: DatePickerInputError): void;
|
|
1309
|
+
/**
|
|
1310
|
+
* Emitted when a native text field inside the control gains focus.
|
|
1311
|
+
* Forwards the original DOM `FocusEvent`.
|
|
1312
|
+
*/
|
|
1313
|
+
(e: 'focus', event: FocusEvent): void;
|
|
1314
|
+
/**
|
|
1315
|
+
* Emitted when focus/click moves outside the entire composite control.
|
|
1316
|
+
*/
|
|
1317
|
+
(e: 'inactive'): void;
|
|
1318
|
+
/**
|
|
1319
|
+
* Emitted whenever the selected date value changes.
|
|
1320
|
+
* Payload is `null` for cleared values, a single string for single-date mode,
|
|
1321
|
+
* or a two-element array for range mode.
|
|
1322
|
+
*/
|
|
1323
|
+
(e: 'update:modelValue', value: BaseDatePickerInputProps['modelValue']): void;
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
export type BaseDatePickerInputSlots = {
|
|
1327
|
+
/**
|
|
1328
|
+
* Content rendered before the entire input control, outside the input chrome.
|
|
1329
|
+
*/
|
|
1330
|
+
'prepend-outer'?: (props: object) => any;
|
|
1331
|
+
/**
|
|
1332
|
+
* Content rendered before the date fields, at the start of the inner container.
|
|
1333
|
+
*/
|
|
1334
|
+
prepend?: (props: object) => any;
|
|
1335
|
+
/**
|
|
1336
|
+
* Inline content rendered at the start of the input field area, before the date fields.
|
|
1337
|
+
*/
|
|
1338
|
+
prefix?: (props: object) => any;
|
|
1339
|
+
/**
|
|
1340
|
+
* Content rendered after the date fields, at the end of the inner container (before the calendar toggle button).
|
|
1341
|
+
*/
|
|
1342
|
+
append?: (props: object) => any;
|
|
1343
|
+
/**
|
|
1344
|
+
* Content rendered after the entire input control, outside the input chrome.
|
|
1345
|
+
*/
|
|
1346
|
+
'append-outer'?: (props: object) => any;
|
|
1347
|
+
/**
|
|
1348
|
+
* Inline content rendered at the end of the input field area, after the date fields.
|
|
1349
|
+
*/
|
|
1350
|
+
suffix?: (props: object) => any;
|
|
1351
|
+
};
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
## Styles
|
|
1355
|
+
|
|
1356
|
+
```css
|
|
1357
|
+
.bb-base-date-picker-input {
|
|
1358
|
+
--bb-arrow: 0;
|
|
1359
|
+
|
|
1360
|
+
&--disabled {
|
|
1361
|
+
}
|
|
1362
|
+
&--errors {
|
|
1363
|
+
}
|
|
1364
|
+
&--loading {
|
|
1365
|
+
}
|
|
1366
|
+
&--readonly {
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.bb-base-date-picker-input__inner-wrapper {
|
|
1370
|
+
display: block;
|
|
1371
|
+
flex: 1 1 auto;
|
|
1372
|
+
}
|
|
1373
|
+
.bb-common-input-inner-container {
|
|
1374
|
+
position: relative;
|
|
1375
|
+
display: flex;
|
|
1376
|
+
> :last-child {
|
|
1377
|
+
order: 2;
|
|
1378
|
+
}
|
|
1379
|
+
> input {
|
|
1380
|
+
}
|
|
1381
|
+
.bb-base-date-picker-input__fields {
|
|
1382
|
+
display: flex;
|
|
1383
|
+
align-items: center;
|
|
1384
|
+
gap: 0;
|
|
1385
|
+
flex: 1;
|
|
1386
|
+
position: relative;
|
|
1387
|
+
|
|
1388
|
+
&--empty {
|
|
1389
|
+
.bb-base-date-picker-input__separator {
|
|
1390
|
+
opacity: 0;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
.bb-base-date-picker-input__placeholder {
|
|
1395
|
+
position: absolute;
|
|
1396
|
+
top: 0;
|
|
1397
|
+
left: 0;
|
|
1398
|
+
right: 0;
|
|
1399
|
+
bottom: 0;
|
|
1400
|
+
display: flex;
|
|
1401
|
+
align-items: center;
|
|
1402
|
+
justify-content: flex-start;
|
|
1403
|
+
color: var(--bb-text-tertiary, #9ca3af);
|
|
1404
|
+
background: inherit;
|
|
1405
|
+
pointer-events: none;
|
|
1406
|
+
user-select: none;
|
|
1407
|
+
z-index: 1;
|
|
1408
|
+
overflow: hidden;
|
|
1409
|
+
white-space: nowrap;
|
|
1410
|
+
text-overflow: ellipsis;
|
|
1411
|
+
}
|
|
1412
|
+
.bb-base-date-picker-input__field {
|
|
1413
|
+
flex: 0 0 auto;
|
|
1414
|
+
min-width: 0;
|
|
1415
|
+
text-align: left;
|
|
1416
|
+
padding: 0;
|
|
1417
|
+
border: none;
|
|
1418
|
+
background: transparent;
|
|
1419
|
+
color: inherit;
|
|
1420
|
+
font: inherit;
|
|
1421
|
+
outline: none;
|
|
1422
|
+
|
|
1423
|
+
&--day,
|
|
1424
|
+
&--month {
|
|
1425
|
+
width: 2ch;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
&--day {
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
&--month {
|
|
1432
|
+
text-align: center;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
&--year {
|
|
1436
|
+
width: 4ch;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
&::placeholder {
|
|
1440
|
+
color: transparent;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
&:disabled {
|
|
1444
|
+
cursor: not-allowed;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
&[readonly] {
|
|
1448
|
+
cursor: default;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
.bb-base-date-picker-input__separator {
|
|
1452
|
+
color: currentColor;
|
|
1453
|
+
user-select: none;
|
|
1454
|
+
flex-shrink: 0;
|
|
1455
|
+
opacity: 0.5;
|
|
1456
|
+
cursor: text;
|
|
1457
|
+
|
|
1458
|
+
&--range {
|
|
1459
|
+
padding: 0 0.375rem;
|
|
1460
|
+
opacity: 0.6;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
.bb-base-date-picker-input__calendar-btn {
|
|
1464
|
+
background-color: inherit;
|
|
1465
|
+
border-radius: var(--bb-radius);
|
|
1466
|
+
margin-right: -0.25rem;
|
|
1467
|
+
isolation: isolate;
|
|
1468
|
+
z-index: 1;
|
|
1469
|
+
order: 1;
|
|
1470
|
+
outline-style: none;
|
|
1471
|
+
padding: clamp(
|
|
1472
|
+
var(--bb-input-compact-py),
|
|
1473
|
+
calc((var(--bb-input-h) - var(--bb-input-icon)) / 4),
|
|
1474
|
+
4px
|
|
1475
|
+
);
|
|
1476
|
+
transition-duration: 150ms;
|
|
1477
|
+
transition-property: color, background-color;
|
|
1478
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1479
|
+
|
|
1480
|
+
&:focus:not(:disabled),
|
|
1481
|
+
&:hover:not(:disabled) {
|
|
1482
|
+
background-color: var(--bb-primary);
|
|
1483
|
+
|
|
1484
|
+
svg {
|
|
1485
|
+
color: var(--bb-contrasting);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
&:disabled {
|
|
1489
|
+
display: none;
|
|
1490
|
+
}
|
|
1491
|
+
svg {
|
|
1492
|
+
color: var(--bb-primary);
|
|
1493
|
+
transition-duration: 150ms;
|
|
1494
|
+
transition-property: color, background-color;
|
|
1495
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1496
|
+
width: var(--bb-input-icon);
|
|
1497
|
+
}
|
|
1498
|
+
> span {
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
.bb-common-input-inner-container__prepend-icon,
|
|
1503
|
+
.bb-common-input-inner-container__append-icon {
|
|
1504
|
+
}
|
|
1505
|
+
.bb-spinner {
|
|
1506
|
+
}
|
|
1507
|
+
.bb-error-icon {
|
|
1508
|
+
}
|
|
1509
|
+
.bb-common-input-inner-container__prefix,
|
|
1510
|
+
.bb-common-input-inner-container__suffix {
|
|
1511
|
+
height: var(--bb-input-h);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
&__accessible-description {
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
```
|