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,1720 @@
|
|
|
1
|
+
# BaseSelect
|
|
2
|
+
|
|
3
|
+
## Template & Script
|
|
4
|
+
|
|
5
|
+
```vue
|
|
6
|
+
<template>
|
|
7
|
+
<CommonInputOuterContainer
|
|
8
|
+
ref="outerContainer"
|
|
9
|
+
:class="{
|
|
10
|
+
'bb-base-select': true,
|
|
11
|
+
'bb-base-select--active': active,
|
|
12
|
+
'bb-base-select--shown': shown,
|
|
13
|
+
'bb-base-select--loading': computedLoading,
|
|
14
|
+
'bb-base-select--disabled': disabled,
|
|
15
|
+
'bb-base-select--errors': hasErrors,
|
|
16
|
+
'bb-base-select--readonly': readonly,
|
|
17
|
+
'bb-base-select--compact': compact,
|
|
18
|
+
}"
|
|
19
|
+
@click="onOuterContainerClick"
|
|
20
|
+
>
|
|
21
|
+
<template #prepend-outer
|
|
22
|
+
><slot name="prepend-outer" :query="query"></slot
|
|
23
|
+
></template>
|
|
24
|
+
<span ref="innerContainer" class="bb-base-select__inner-wrapper">
|
|
25
|
+
<CommonInputInnerContainer
|
|
26
|
+
:append:icon="props['append:icon']"
|
|
27
|
+
:clearable="clearable && !isEmpty(modelValue)"
|
|
28
|
+
:prepend:icon="props['prepend:icon']"
|
|
29
|
+
:prevent-focus="true"
|
|
30
|
+
@click:clear="onClear"
|
|
31
|
+
>
|
|
32
|
+
<template #prepend
|
|
33
|
+
><slot name="prepend" :query="query" :focus="focusInput"></slot
|
|
34
|
+
></template>
|
|
35
|
+
<template #prefix><slot name="prefix" /></template>
|
|
36
|
+
<BbSmoothHeight tag="span">
|
|
37
|
+
<span class="bb-base-select__input-container">
|
|
38
|
+
<template
|
|
39
|
+
v-if="multiple && selectedOptions.length <= maxSelectedLabels"
|
|
40
|
+
>
|
|
41
|
+
<template v-if="comma">
|
|
42
|
+
<CommaBox
|
|
43
|
+
ref="commaBox"
|
|
44
|
+
:options="selectedOptions"
|
|
45
|
+
@option:unselected="onOptionUnselected"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
<template v-else>
|
|
49
|
+
<ChipsBox
|
|
50
|
+
ref="chipsBox"
|
|
51
|
+
:options="selectedOptions"
|
|
52
|
+
@option:unselected="onOptionUnselected"
|
|
53
|
+
/>
|
|
54
|
+
</template>
|
|
55
|
+
</template>
|
|
56
|
+
<span v-else-if="multiple" class="bb-base-select__max-reached">
|
|
57
|
+
{{
|
|
58
|
+
selectedLabelsFn
|
|
59
|
+
? selectedLabelsFn(selectedOptions.length)
|
|
60
|
+
: t('select.multipleMaxReached', selectedOptions.length)
|
|
61
|
+
}}
|
|
62
|
+
</span>
|
|
63
|
+
<input
|
|
64
|
+
:id="id"
|
|
65
|
+
:key="`${mounted}`"
|
|
66
|
+
ref="input"
|
|
67
|
+
v-model="query"
|
|
68
|
+
:aria-activedescendant="shown ? activeDescendantId : undefined"
|
|
69
|
+
:aria-autocomplete="'list'"
|
|
70
|
+
:aria-controls="renderListBox ? `${id}_listbox` : undefined"
|
|
71
|
+
:aria-describedby="ariaDescribedby"
|
|
72
|
+
:aria-expanded="shown"
|
|
73
|
+
:autocomplete="autocomplete"
|
|
74
|
+
:autofocus="autofocus"
|
|
75
|
+
:class="'bb-base-select__text-input'"
|
|
76
|
+
:disabled="disabled"
|
|
77
|
+
:inputmode="inputmode"
|
|
78
|
+
:placeholder="computedPlaceholder"
|
|
79
|
+
:readonly="readonly || !allowWriting"
|
|
80
|
+
:required="required && (!multiple || !modelValue.length)"
|
|
81
|
+
role="combobox"
|
|
82
|
+
size="1"
|
|
83
|
+
:style="{ '--characters': query.length }"
|
|
84
|
+
:type="'text'"
|
|
85
|
+
@blur.stop="onInputBlur"
|
|
86
|
+
@change.stop="onInputChange"
|
|
87
|
+
@focus.once.stop="onInputFirstFocus"
|
|
88
|
+
@focus.stop="onInputFocus"
|
|
89
|
+
@input.stop="onInputInput"
|
|
90
|
+
@keydown.stop.delete="onBackspace"
|
|
91
|
+
@keydown.stop.left="onArrowLeft"
|
|
92
|
+
@keydown.stop.prevent.down="onArrowDown"
|
|
93
|
+
@keydown.stop.prevent.enter="onEnter"
|
|
94
|
+
@keydown.stop.prevent.esc="onEscape"
|
|
95
|
+
@keydown.stop.prevent.up="onArrowUp"
|
|
96
|
+
@keydown.stop.right="onArrowRight"
|
|
97
|
+
/>
|
|
98
|
+
</span>
|
|
99
|
+
</BbSmoothHeight>
|
|
100
|
+
<slot
|
|
101
|
+
v-if="showChevron"
|
|
102
|
+
:loading="!!loading"
|
|
103
|
+
name="chevron"
|
|
104
|
+
:shown="shown"
|
|
105
|
+
>
|
|
106
|
+
<svg
|
|
107
|
+
class="bb-base-select__chevron"
|
|
108
|
+
viewBox="0 0 24 24"
|
|
109
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
110
|
+
>
|
|
111
|
+
<path
|
|
112
|
+
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
|
|
113
|
+
fill="currentColor"
|
|
114
|
+
/>
|
|
115
|
+
</svg>
|
|
116
|
+
</slot>
|
|
117
|
+
<template #append
|
|
118
|
+
><slot name="append" :query="query" :focus="focusInput"></slot
|
|
119
|
+
></template>
|
|
120
|
+
<template #suffix><slot name="suffix" /></template>
|
|
121
|
+
</CommonInputInnerContainer>
|
|
122
|
+
</span>
|
|
123
|
+
<CommonPopover
|
|
124
|
+
v-model="shown"
|
|
125
|
+
:anchor="innerContainer"
|
|
126
|
+
:offset="4"
|
|
127
|
+
:transition-duration="transitionDuration"
|
|
128
|
+
>
|
|
129
|
+
<template v-if="shown">
|
|
130
|
+
<slot name="options:prepend:outer" :focus="focusInput" />
|
|
131
|
+
</template>
|
|
132
|
+
<ListBox
|
|
133
|
+
ref="optionsContainer"
|
|
134
|
+
:compact="compact"
|
|
135
|
+
:loading="computedLoading"
|
|
136
|
+
:loading-text="loadingText"
|
|
137
|
+
:multiple="multiple"
|
|
138
|
+
:no-data-text="noDataText"
|
|
139
|
+
:open="shown"
|
|
140
|
+
:options="groupBy ? undefined : filteredOptions"
|
|
141
|
+
:groups="groupBy ? groupedOptions : undefined"
|
|
142
|
+
:header-height="headerHeight"
|
|
143
|
+
:style="optionsContainerStyles"
|
|
144
|
+
:option-height="itemHeight"
|
|
145
|
+
@focused:change="onListboxFocusedChange"
|
|
146
|
+
@option:selected="onOptionSelected"
|
|
147
|
+
@option:unselected="onOptionUnselected"
|
|
148
|
+
>
|
|
149
|
+
<template #options:prepend
|
|
150
|
+
><slot v-if="shown" name="options:prepend" :focus="focusInput"
|
|
151
|
+
/></template>
|
|
152
|
+
<template #loading><slot name="loading" :query="query" /></template>
|
|
153
|
+
<template #no-data
|
|
154
|
+
><slot name="no-data" :query="query" :focus="focusInput"
|
|
155
|
+
/></template>
|
|
156
|
+
<template #options:append
|
|
157
|
+
><slot v-if="shown" name="options:append" :focus="focusInput"
|
|
158
|
+
/></template>
|
|
159
|
+
<template #option="data"
|
|
160
|
+
><slot
|
|
161
|
+
:has-errors="hasErrors"
|
|
162
|
+
:loading="computedLoading"
|
|
163
|
+
name="option"
|
|
164
|
+
v-bind="data"
|
|
165
|
+
/></template>
|
|
166
|
+
<template #group="data"><slot name="group" v-bind="data" /></template>
|
|
167
|
+
</ListBox>
|
|
168
|
+
<template v-if="shown">
|
|
169
|
+
<slot name="options:append:outer" :focus="focusInput" />
|
|
170
|
+
</template>
|
|
171
|
+
</CommonPopover>
|
|
172
|
+
<template #append-outer
|
|
173
|
+
><slot name="append-outer" :query="query"></slot
|
|
174
|
+
></template>
|
|
175
|
+
<input v-for="input in hiddenInputs" :key="input.value" v-bind="input" />
|
|
176
|
+
</CommonInputOuterContainer>
|
|
177
|
+
</template>
|
|
178
|
+
|
|
179
|
+
<script setup lang="ts" generic="Item = any">
|
|
180
|
+
import BbSmoothHeight from '../BbSmoothHeight/BbSmoothHeight.vue';
|
|
181
|
+
import { computed, ref, nextTick } from 'vue';
|
|
182
|
+
import { hash } from '@/utilities/functions/hash';
|
|
183
|
+
import { isEmpty } from '@/utilities/functions/empty';
|
|
184
|
+
import { last } from '@/utilities/functions/last';
|
|
185
|
+
import { matchAnyKey } from '@/utilities/functions/matchAnyKey';
|
|
186
|
+
import { toRef } from 'vue';
|
|
187
|
+
import { useArray } from '@/composables/useArray';
|
|
188
|
+
import { useBaseOptions } from '@/composables/useBaseOptions';
|
|
189
|
+
import { useCoherence } from '@/composables/useCoherence';
|
|
190
|
+
import { useHashedWatcher } from '@/composables/useHashedWatcher';
|
|
191
|
+
import { useId } from '@/composables/useId';
|
|
192
|
+
import { useIndexById } from '@/composables/useIndexById';
|
|
193
|
+
import { useElementSize, useIntersectionObserver } from '@vueuse/core';
|
|
194
|
+
import { useItemsGetter } from '@/composables/useItemsGetter';
|
|
195
|
+
import { useLocale } from '@/composables/useLocale';
|
|
196
|
+
import { useMobile } from '@/composables/useMobile';
|
|
197
|
+
import { useMounted } from '@vueuse/core';
|
|
198
|
+
import { usePrefill } from '@/composables/usePrefill';
|
|
199
|
+
import { wait } from '@/utilities/functions/wait';
|
|
200
|
+
import { when } from '@/utilities/functions/when';
|
|
201
|
+
import ChipsBox from '../ChipsBox/ChipsBox.vue';
|
|
202
|
+
import CommaBox from '../CommaBox/CommaBox.vue';
|
|
203
|
+
import CommonInputInnerContainer from '../CommonInputInnerContainer/CommonInputInnerContainer.vue';
|
|
204
|
+
import CommonInputOuterContainer from '../CommonInputOuterContainer/CommonInputOuterContainer.vue';
|
|
205
|
+
import CommonPopover from '../CommonPopover/CommonPopover.vue';
|
|
206
|
+
import ListBox from '../ListBox/ListBox.vue';
|
|
207
|
+
import type {
|
|
208
|
+
BaseSelectProps,
|
|
209
|
+
BaseSelectEvents,
|
|
210
|
+
BaseSelectSlots,
|
|
211
|
+
} from './types';
|
|
212
|
+
import type { Option as BaseOption, GroupedOptions } from '@/types/Option';
|
|
213
|
+
import { useUntil } from '@/composables/useUntil';
|
|
214
|
+
import type { groupBy } from '@/utilities/functions/groupBy';
|
|
215
|
+
import { isNil } from '@/utilities/functions/isNil';
|
|
216
|
+
import { get } from '@/utilities/functions/get';
|
|
217
|
+
import { useLogger } from '@/composables/useLogger';
|
|
218
|
+
|
|
219
|
+
const props = withDefaults(defineProps<BaseSelectProps<Item>>(), {
|
|
220
|
+
allowWriting: true,
|
|
221
|
+
autocomplete: 'off',
|
|
222
|
+
depsDebounceTime: 0,
|
|
223
|
+
dependencies: () => [],
|
|
224
|
+
filterBy: () => [],
|
|
225
|
+
max: Infinity,
|
|
226
|
+
maxSelectedLabels: Infinity,
|
|
227
|
+
modelValueDebounceTime: 0,
|
|
228
|
+
prefill: 'focus',
|
|
229
|
+
queryDebounceTime: 500,
|
|
230
|
+
showChevron: true,
|
|
231
|
+
transitionDuration: 300,
|
|
232
|
+
updateOnAnimationFrame: false,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const emit = defineEmits<BaseSelectEvents>();
|
|
236
|
+
const logger = useLogger();
|
|
237
|
+
|
|
238
|
+
defineSlots<BaseSelectSlots<Item>>();
|
|
239
|
+
|
|
240
|
+
if (props.multiple && !Array.isArray(props.modelValue)) {
|
|
241
|
+
logger.throw(
|
|
242
|
+
'BaseSelect: multiple is set to "true" but modelValue is not an array.'
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const { t } = useLocale();
|
|
247
|
+
const mounted = useMounted();
|
|
248
|
+
/**
|
|
249
|
+
* START ELEMENT REFERENCES
|
|
250
|
+
*/
|
|
251
|
+
const outerContainer = ref<InstanceType<
|
|
252
|
+
typeof CommonInputOuterContainer
|
|
253
|
+
> | null>(null);
|
|
254
|
+
const innerContainer = ref<HTMLElement | null>(null);
|
|
255
|
+
const optionsContainer = ref<any | null>(null);
|
|
256
|
+
const commaBox = ref<InstanceType<typeof CommaBox> | null>(null);
|
|
257
|
+
const chipsBox = ref<InstanceType<typeof ChipsBox> | null>(null);
|
|
258
|
+
const selectedBox = computed(() =>
|
|
259
|
+
props.comma ? commaBox.value : chipsBox.value
|
|
260
|
+
);
|
|
261
|
+
const { width: containerWidth } = useElementSize(innerContainer);
|
|
262
|
+
/**
|
|
263
|
+
* END ELEMENT REFERENCES
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
const { isMobile } = useMobile();
|
|
267
|
+
const inputmode = computed(() =>
|
|
268
|
+
props.allowWriting === 'not-mobile' && isMobile.value ? 'none' : undefined
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Always provide an id so that inputs can be focussed by clicking in the label
|
|
272
|
+
const id = props.id || useId().id.value;
|
|
273
|
+
|
|
274
|
+
const { resume, pause } = useIntersectionObserver(
|
|
275
|
+
innerContainer,
|
|
276
|
+
async ([{ intersectionRatio }]) => {
|
|
277
|
+
if (intersectionRatio !== 1 && shown.value) {
|
|
278
|
+
await close();
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
{ threshold: [0, 1], immediate: false }
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const optionsContainerStyles = computed(() => ({
|
|
285
|
+
width: `${containerWidth.value ?? 0}px`,
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* START INPUT HANDLING
|
|
290
|
+
*/
|
|
291
|
+
const input = ref<HTMLElement | null>(null);
|
|
292
|
+
const activeDescendantId = ref<string>();
|
|
293
|
+
const focusInput = () => {
|
|
294
|
+
if (input.value instanceof HTMLInputElement) {
|
|
295
|
+
input.value.focus();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* When the focus goes to the input we select the current text
|
|
301
|
+
* so the user can replace or delete it.
|
|
302
|
+
* On top of that if the prefill strategy is to prefill on first focus and options
|
|
303
|
+
* weren't already retrieved by the watcher on mount, we do that
|
|
304
|
+
*/
|
|
305
|
+
const onInputFocus = async (event: FocusEvent) => {
|
|
306
|
+
emit('focus', event);
|
|
307
|
+
setActive();
|
|
308
|
+
document.addEventListener('click', onOutsideInteraction);
|
|
309
|
+
document.addEventListener('focusin', onOutsideInteraction);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const onInputFirstFocus = () => {
|
|
313
|
+
if (props.prefill === 'focus' && !isPrefilling.value && !hasPrefilled.value) {
|
|
314
|
+
prefillItems();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const onInputBlur = async (event: FocusEvent) => {
|
|
319
|
+
emit('blur', event);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const onInputChange = (event: Event) => emit('change', event);
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Search at every input by the user.
|
|
326
|
+
* When the user deletes the query unselect the current option
|
|
327
|
+
*/
|
|
328
|
+
const onInputInput = (event: Event) => {
|
|
329
|
+
emit('input', event);
|
|
330
|
+
if (query.value === '') {
|
|
331
|
+
if (!props.multiple) {
|
|
332
|
+
emit('update:modelValue', null);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
selectedBox.value?.blur();
|
|
336
|
+
if (hidden.value) {
|
|
337
|
+
open();
|
|
338
|
+
}
|
|
339
|
+
debouncedGetter(query.value, false, props.modelValue);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const onListboxFocusedChange = (id: string | undefined) => {
|
|
343
|
+
activeDescendantId.value = id;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* On click outside of the container close the panel, remove active state.
|
|
348
|
+
* In multiple selection clear query after a timeout
|
|
349
|
+
*/
|
|
350
|
+
const onOutsideInteraction = async (event: Event) => {
|
|
351
|
+
if (event.target instanceof Node) {
|
|
352
|
+
if (outerContainer.value) {
|
|
353
|
+
if (
|
|
354
|
+
!outerContainer.value.$el.contains(event.target) &&
|
|
355
|
+
!optionsContainer.value?.$el?.contains(event.target)
|
|
356
|
+
) {
|
|
357
|
+
close();
|
|
358
|
+
setInactive();
|
|
359
|
+
document.removeEventListener('click', onOutsideInteraction);
|
|
360
|
+
document.removeEventListener('focusin', onOutsideInteraction);
|
|
361
|
+
await wait(props.transitionDuration);
|
|
362
|
+
alignQueryToState();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const canProcessKeyboardBindings = computed(() => {
|
|
370
|
+
if (props.readonly) return false;
|
|
371
|
+
if (props.disabled) return false;
|
|
372
|
+
if (computedLoading.value) return false;
|
|
373
|
+
if (
|
|
374
|
+
!selectedBox.value &&
|
|
375
|
+
props.multiple &&
|
|
376
|
+
selectedOptions.value.length <= props.maxSelectedLabels
|
|
377
|
+
)
|
|
378
|
+
return false;
|
|
379
|
+
return true;
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* On arrow prevent the page from scrolling.
|
|
384
|
+
* Reset horizontal focus
|
|
385
|
+
* When the panel is open
|
|
386
|
+
* - Focus on the previous option
|
|
387
|
+
* When the panel is closed
|
|
388
|
+
* - focus on the first selected option
|
|
389
|
+
*/
|
|
390
|
+
const onArrowUp = async () => {
|
|
391
|
+
if (!canProcessKeyboardBindings.value) return;
|
|
392
|
+
// Reset horizontal focus while moving vertically
|
|
393
|
+
selectedBox.value?.blur();
|
|
394
|
+
if (shown.value) {
|
|
395
|
+
optionsContainer.value?.focusPrevious();
|
|
396
|
+
} else {
|
|
397
|
+
await open();
|
|
398
|
+
await wait(props.transitionDuration);
|
|
399
|
+
optionsContainer.value?.focusLastSelected();
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const onArrowDown = async () => {
|
|
404
|
+
if (!canProcessKeyboardBindings.value) return;
|
|
405
|
+
// Reset horizontal focus while moving vertically
|
|
406
|
+
selectedBox.value?.blur();
|
|
407
|
+
if (shown.value) {
|
|
408
|
+
optionsContainer.value?.focusNext();
|
|
409
|
+
} else {
|
|
410
|
+
await open();
|
|
411
|
+
await wait(props.transitionDuration);
|
|
412
|
+
optionsContainer.value?.focusFirstSelected();
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const onArrowLeft = (event: KeyboardEvent) => {
|
|
417
|
+
if (!canProcessKeyboardBindings.value) return;
|
|
418
|
+
if (!props.multiple || query.value) return;
|
|
419
|
+
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
// Reset vertical focus while moving horizontally
|
|
422
|
+
optionsContainer.value?.blur();
|
|
423
|
+
if (!selectedOptions.value.length) return;
|
|
424
|
+
selectedBox.value?.focusPrevious();
|
|
425
|
+
const highlightedOption = selectedBox.value?.getHighlighted();
|
|
426
|
+
if (highlightedOption) {
|
|
427
|
+
optionsContainer.value?.focusByHash(highlightedOption.valueHash);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const onArrowRight = (event: KeyboardEvent) => {
|
|
432
|
+
if (!canProcessKeyboardBindings.value) return;
|
|
433
|
+
if (!props.multiple || query.value) return;
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
// Reset vertical focus while moving horizontally
|
|
436
|
+
optionsContainer.value?.blur();
|
|
437
|
+
if (!selectedOptions.value.length) return;
|
|
438
|
+
selectedBox.value?.focusNext();
|
|
439
|
+
const highlightedOption = selectedBox.value?.getHighlighted();
|
|
440
|
+
if (highlightedOption) {
|
|
441
|
+
optionsContainer.value?.focusByHash(highlightedOption.valueHash);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const onEnter = async () => {
|
|
446
|
+
if (!canProcessKeyboardBindings.value) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!shown.value) {
|
|
450
|
+
await open();
|
|
451
|
+
await wait(props.transitionDuration);
|
|
452
|
+
if (selectedOptions.value.length) {
|
|
453
|
+
optionsContainer.value?.focusFirstSelected();
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
} else if (optionsContainer.value?.getHighlighted()) {
|
|
457
|
+
optionsContainer.value.confirmOption();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const text = query.value.trim();
|
|
461
|
+
// Do not add manual items that are already present
|
|
462
|
+
if (isItemSelected(text)) return;
|
|
463
|
+
emit('option:add', text);
|
|
464
|
+
query.value = '';
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const onEscape = async () => {
|
|
468
|
+
if (shown.value) {
|
|
469
|
+
await close();
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Delete behavior is different when query is empty
|
|
475
|
+
* If query is empty
|
|
476
|
+
* If no chip is selected - select rightmost chip
|
|
477
|
+
* If a chip is selected - deselect that option
|
|
478
|
+
* If any option remains move focus to that or reset
|
|
479
|
+
*/
|
|
480
|
+
const onBackspace = async () => {
|
|
481
|
+
if (
|
|
482
|
+
!canProcessKeyboardBindings.value ||
|
|
483
|
+
query.value ||
|
|
484
|
+
!selectedOptions.value.length
|
|
485
|
+
) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (selectedBox.value?.getHighlighted()) {
|
|
489
|
+
selectedBox.value.confirmOption();
|
|
490
|
+
}
|
|
491
|
+
await nextTick();
|
|
492
|
+
selectedBox.value?.focusPrevious();
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const query = ref<string>('');
|
|
496
|
+
|
|
497
|
+
const {
|
|
498
|
+
getter,
|
|
499
|
+
debouncedGetter,
|
|
500
|
+
items: internalItems,
|
|
501
|
+
loading,
|
|
502
|
+
} = useItemsGetter({
|
|
503
|
+
items: toRef(props, 'items'),
|
|
504
|
+
debounce: props.queryDebounceTime,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const {
|
|
508
|
+
array: innerStash,
|
|
509
|
+
add: addToStash,
|
|
510
|
+
remove: removeFromStash,
|
|
511
|
+
set: setStash,
|
|
512
|
+
} = useArray<BaseOption>();
|
|
513
|
+
|
|
514
|
+
const {
|
|
515
|
+
hasPrefilled,
|
|
516
|
+
isPrefilling,
|
|
517
|
+
prefill: prefillItems,
|
|
518
|
+
} = usePrefill({
|
|
519
|
+
trigger: props.prefill === true,
|
|
520
|
+
currentValue: props.modelValue,
|
|
521
|
+
multiple: props.multiple,
|
|
522
|
+
fn: async (isPrefill) => {
|
|
523
|
+
await getter(query.value, isPrefill, props.modelValue);
|
|
524
|
+
if (props.stash) {
|
|
525
|
+
addToStash(...options.value.map((o) => o.item));
|
|
526
|
+
}
|
|
527
|
+
alignQueryToState();
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const { data: modelValueIndexedByHash, get: isItemSelected } = useIndexById({
|
|
532
|
+
// Force model value to be an array
|
|
533
|
+
items: computed(() => [].concat(props.modelValue)),
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const { options } = useBaseOptions({
|
|
537
|
+
disabled: toRef(props, 'disabled'),
|
|
538
|
+
items: computed(() => [...internalItems.value, ...innerStash.value]),
|
|
539
|
+
itemText: props.itemText,
|
|
540
|
+
itemValue: props.itemValue,
|
|
541
|
+
max: props.max,
|
|
542
|
+
selectable: true,
|
|
543
|
+
selectedIndexedByHash: modelValueIndexedByHash,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const { data: optionsIndexedByHash } = useIndexById({
|
|
547
|
+
items: options,
|
|
548
|
+
key: 'valueHash',
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const { coherent, coherentValue } = useCoherence({
|
|
552
|
+
modelValue: toRef(props, 'modelValue'),
|
|
553
|
+
multiple: props.multiple,
|
|
554
|
+
iteratee: (item) => !!optionsIndexedByHash.value[hash(item)],
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// 6. Selected options — resolved in modelValue order so chips stay stable
|
|
558
|
+
const selectedOptions = computed<BaseOption[]>(() =>
|
|
559
|
+
([] as any[])
|
|
560
|
+
.concat(props.modelValue)
|
|
561
|
+
.map((v) => optionsIndexedByHash.value[hash(v)])
|
|
562
|
+
.filter(Boolean)
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const computedLoading = computed(() => !!(loading.value || props.loading));
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* If multiple watchers are running we don't want to run the coherence watcher
|
|
569
|
+
* multiple times we only run it the last time
|
|
570
|
+
*/
|
|
571
|
+
useHashedWatcher(
|
|
572
|
+
() => props.modelValue,
|
|
573
|
+
async () => {
|
|
574
|
+
if (!coherent.value) {
|
|
575
|
+
await getter(query.value, true, props.modelValue);
|
|
576
|
+
if (props.enforceCoherence) {
|
|
577
|
+
emit('update:modelValue', coherentValue.value);
|
|
578
|
+
selectedBox.value?.blur();
|
|
579
|
+
optionsContainer.value?.blur();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
alignQueryToState();
|
|
583
|
+
},
|
|
584
|
+
{ debounce: props.modelValueDebounceTime }
|
|
585
|
+
);
|
|
586
|
+
useHashedWatcher(
|
|
587
|
+
() => [props.dependencies, props.items],
|
|
588
|
+
async () => {
|
|
589
|
+
if (!hasPrefilled.value) return;
|
|
590
|
+
await getter(query.value, true, props.modelValue);
|
|
591
|
+
if (props.enforceCoherence && !coherent.value) {
|
|
592
|
+
emit('update:modelValue', coherentValue.value);
|
|
593
|
+
selectedBox.value?.blur();
|
|
594
|
+
optionsContainer.value?.blur();
|
|
595
|
+
}
|
|
596
|
+
alignQueryToState();
|
|
597
|
+
},
|
|
598
|
+
{ debounce: props.depsDebounceTime }
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
const alignQueryToState = () => {
|
|
602
|
+
if (props.multiple) {
|
|
603
|
+
query.value = '';
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
let option: BaseOption | undefined;
|
|
607
|
+
if (selectedOptions.value) {
|
|
608
|
+
option = last(selectedOptions.value);
|
|
609
|
+
}
|
|
610
|
+
query.value = option?.text || '';
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const computedPlaceholder = computed(() => {
|
|
614
|
+
if (selectedOptions.value.length) return '';
|
|
615
|
+
else return props.placeholder;
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const onOptionSelected = async (option: BaseOption) => {
|
|
619
|
+
if (props.multiple) {
|
|
620
|
+
emit('update:modelValue', props.modelValue.concat(option.value));
|
|
621
|
+
if (props.stash) {
|
|
622
|
+
addToStash(option.item);
|
|
623
|
+
}
|
|
624
|
+
alignQueryToState();
|
|
625
|
+
} else {
|
|
626
|
+
if (props.stash) {
|
|
627
|
+
setStash(option.item);
|
|
628
|
+
}
|
|
629
|
+
emit('update:modelValue', option.value);
|
|
630
|
+
await nextTick();
|
|
631
|
+
alignQueryToState();
|
|
632
|
+
}
|
|
633
|
+
focusInput();
|
|
634
|
+
if (!props.multiple) {
|
|
635
|
+
close();
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const onOptionUnselected = async (option: BaseOption) => {
|
|
640
|
+
removeFromStash((o) => o.valueHash !== option.valueHash);
|
|
641
|
+
if (props.multiple) {
|
|
642
|
+
const copy = { ...modelValueIndexedByHash.value };
|
|
643
|
+
delete copy[option.valueHash];
|
|
644
|
+
emit('update:modelValue', Object.values(copy));
|
|
645
|
+
} else {
|
|
646
|
+
emit('update:modelValue', null);
|
|
647
|
+
await nextTick();
|
|
648
|
+
alignQueryToState();
|
|
649
|
+
}
|
|
650
|
+
focusInput();
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Filter options to be displayed based on the current query.
|
|
655
|
+
* If no query is used or when query is set from the inside (after a selection) display all possible options
|
|
656
|
+
*/
|
|
657
|
+
const filteredOptions = computed<BaseOption<Item>[]>(() => {
|
|
658
|
+
/** Only filter when a value is being searched not when the query is fully set on a selected option text */
|
|
659
|
+
const queryMatchesASelectedOption = selectedOptions.value.some(
|
|
660
|
+
(option: BaseOption) => option.text === query.value
|
|
661
|
+
);
|
|
662
|
+
const shouldFilter =
|
|
663
|
+
(props.multiple && query.value) ||
|
|
664
|
+
(props.filterBy === 'not_stashed' && query.value) ||
|
|
665
|
+
(query.value && !queryMatchesASelectedOption && !props.multiple);
|
|
666
|
+
|
|
667
|
+
if (!shouldFilter || !props.filterBy) return options.value;
|
|
668
|
+
|
|
669
|
+
// Build the stash lookup once, outside the loop — not per item.
|
|
670
|
+
const stashedHashes =
|
|
671
|
+
props.filterBy === 'not_stashed'
|
|
672
|
+
? new Set(innerStash.value.map((o) => o.valueHash))
|
|
673
|
+
: null;
|
|
674
|
+
|
|
675
|
+
return options.value.filter((item: BaseOption) => {
|
|
676
|
+
let matchedAnything = false;
|
|
677
|
+
if (typeof props.filterBy === 'function') {
|
|
678
|
+
// Custom function is an extra matcher, not a replacement for the text
|
|
679
|
+
// match — consistent with the string/array filterBy modes.
|
|
680
|
+
matchedAnything = props.filterBy(item.value, item.item, query.value);
|
|
681
|
+
} else if (props.filterBy === 'not_stashed' && query.value) {
|
|
682
|
+
matchedAnything = !stashedHashes!.has(item.valueHash);
|
|
683
|
+
} else if (Array.isArray(props.filterBy) && props.filterBy.length) {
|
|
684
|
+
matchedAnything = matchAnyKey(
|
|
685
|
+
item.item,
|
|
686
|
+
props.filterBy,
|
|
687
|
+
query.value.trim()
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
return matchedAnything || matchAnyKey(item, ['text'], query.value.trim());
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
const defaultGroupSymbol = Symbol('default');
|
|
695
|
+
|
|
696
|
+
const getGroupData = (option: BaseOption) => {
|
|
697
|
+
if (isNil(props.groupBy)) return defaultGroupSymbol;
|
|
698
|
+
if (typeof props.groupBy === 'function') return props.groupBy(option.item);
|
|
699
|
+
return (get as any)(option.item, props.groupBy);
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
const groupedOptions = computed<GroupedOptions[]>(() => {
|
|
703
|
+
const groupIndex: Record<GroupedOptions['id'], GroupedOptions> = {};
|
|
704
|
+
for (const option of filteredOptions.value) {
|
|
705
|
+
const groupData = getGroupData(option);
|
|
706
|
+
const groupId = hash(groupData);
|
|
707
|
+
if (!groupIndex[groupId]) {
|
|
708
|
+
groupIndex[groupId] = {
|
|
709
|
+
id: groupId,
|
|
710
|
+
data: groupData ?? null,
|
|
711
|
+
options: [],
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
groupIndex[groupId].options.push(option);
|
|
715
|
+
}
|
|
716
|
+
return Object.values(groupIndex).filter((g) => g.options.length);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* We need the transitions because elements must be hidden and shown
|
|
721
|
+
* to accessibility tools when they are fully closed or fully open,
|
|
722
|
+
* while still being animated for sighted users.
|
|
723
|
+
*/
|
|
724
|
+
|
|
725
|
+
const shown = ref(false);
|
|
726
|
+
const hidden = ref(true);
|
|
727
|
+
const renderListBox = useUntil(shown);
|
|
728
|
+
|
|
729
|
+
const open = async () => {
|
|
730
|
+
resume();
|
|
731
|
+
hidden.value = false;
|
|
732
|
+
shown.value = true;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const close = async () => {
|
|
736
|
+
shown.value = false;
|
|
737
|
+
hidden.value = true;
|
|
738
|
+
activeDescendantId.value = undefined;
|
|
739
|
+
selectedBox.value?.blur();
|
|
740
|
+
pause();
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const toggle = () => {
|
|
744
|
+
if (shown.value) return close();
|
|
745
|
+
return open();
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Applies a class when the user is inside this whole component.
|
|
750
|
+
* We cannot use focus within as it doesn't work for elements that are teleported.
|
|
751
|
+
* Also we cannot use the <input/> if we physically move focus to the options so
|
|
752
|
+
* we track it manually.
|
|
753
|
+
*/
|
|
754
|
+
const active = ref(false);
|
|
755
|
+
|
|
756
|
+
const setActive = () => {
|
|
757
|
+
active.value = true;
|
|
758
|
+
};
|
|
759
|
+
const setInactive = () => {
|
|
760
|
+
active.value = false;
|
|
761
|
+
emit('inactive');
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* When clicking on the outside container seamlessly move focus to the input and open the panel
|
|
766
|
+
*/
|
|
767
|
+
const onOuterContainerClick = (event: MouseEvent) => {
|
|
768
|
+
event.preventDefault();
|
|
769
|
+
emit('click', event);
|
|
770
|
+
if (props.disabled || props.readonly) return;
|
|
771
|
+
focusInput();
|
|
772
|
+
toggle();
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* When using this component for submission we add a bunch of hidden
|
|
777
|
+
* inputs so the submitted inputs are on par with v-model
|
|
778
|
+
*/
|
|
779
|
+
/**
|
|
780
|
+
* This is used to keep the value compatible to common html expected values.
|
|
781
|
+
* Convert to string everything that's not but do not double encode strings
|
|
782
|
+
*/
|
|
783
|
+
const makeInputValue = when(
|
|
784
|
+
(item: unknown) => typeof item !== 'string',
|
|
785
|
+
JSON.stringify
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
const hiddenInputs = computed(() => {
|
|
789
|
+
return [].concat(props.modelValue).map((current) => {
|
|
790
|
+
const value = makeInputValue(current);
|
|
791
|
+
return {
|
|
792
|
+
disabled: props.disabled,
|
|
793
|
+
name: props.name,
|
|
794
|
+
type: 'hidden',
|
|
795
|
+
value: value,
|
|
796
|
+
};
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
const onClear = () => {
|
|
801
|
+
let value = null;
|
|
802
|
+
if (props.multiple) {
|
|
803
|
+
value = [];
|
|
804
|
+
}
|
|
805
|
+
emit('update:modelValue', value);
|
|
806
|
+
};
|
|
807
|
+
</script>
|
|
808
|
+
<style lang="postcss">
|
|
809
|
+
@import './index.css';
|
|
810
|
+
</style>
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
## Types
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
|
|
817
|
+
import type { IconType } from '@/types/Icon';
|
|
818
|
+
|
|
819
|
+
export type BaseSelectProps<Item = any> = {
|
|
820
|
+
/**
|
|
821
|
+
* Controls whether manual typing is allowed in the input field.
|
|
822
|
+
* Use `'not-mobile'` to disable typing on mobile while keeping it on desktop.
|
|
823
|
+
* @defaultValue `true`
|
|
824
|
+
*/
|
|
825
|
+
allowWriting?: boolean | 'not-mobile';
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Name of the icon to render at the right hand side of the input.
|
|
829
|
+
*/
|
|
830
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
831
|
+
'append:icon'?: IconType;
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Id(s) of elements describing this select for assistive technologies.
|
|
835
|
+
*/
|
|
836
|
+
ariaDescribedby?: InputHTMLAttributes['aria-describedby'];
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Browser autocomplete hint for the input field.
|
|
840
|
+
* @defaultValue `'off'`
|
|
841
|
+
*/
|
|
842
|
+
autocomplete?: InputHTMLAttributes['autocomplete'];
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Focus the input automatically on mount.
|
|
846
|
+
*/
|
|
847
|
+
autofocus?: InputHTMLAttributes['autofocus'];
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Show a clear button whenever a value is present and the control is interactive.
|
|
851
|
+
*/
|
|
852
|
+
clearable?: boolean;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Display selected values as a comma-separated string instead of individual chips.
|
|
856
|
+
* When enabled, options cannot be deselected by clicking individual chips.
|
|
857
|
+
*/
|
|
858
|
+
comma?: boolean;
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Apply the compact density variant.
|
|
862
|
+
*/
|
|
863
|
+
compact?: boolean;
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Additional dependencies that trigger item reloading when changed.
|
|
867
|
+
* @defaultValue `[]`
|
|
868
|
+
*/
|
|
869
|
+
dependencies?: any[];
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Debounce delay (ms) for dependency-triggered reloads.
|
|
873
|
+
* @defaultValue `0`
|
|
874
|
+
*/
|
|
875
|
+
depsDebounceTime?: number;
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Disables the component.
|
|
879
|
+
*/
|
|
880
|
+
disabled?: boolean;
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Reset modelValue to `null` or empty array if it no longer matches available items (enforces coherence).
|
|
884
|
+
* For example, prevents setting v-model to a user not present in the items array.
|
|
885
|
+
*/
|
|
886
|
+
enforceCoherence?: boolean;
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Controls how options are filtered during search.
|
|
890
|
+
* Options are always filtered by display text, but can also match additional properties
|
|
891
|
+
* when set to a string path, array of paths, or custom function.
|
|
892
|
+
* Set to `false` to disable filtering, or `'not_stashed'` to exclude stashed items.
|
|
893
|
+
* @defaultValue `() => []`
|
|
894
|
+
*/
|
|
895
|
+
filterBy?:
|
|
896
|
+
| string
|
|
897
|
+
| string[]
|
|
898
|
+
| false
|
|
899
|
+
| 'not_stashed'
|
|
900
|
+
| ((value: any, item: any, query: string | null) => boolean)
|
|
901
|
+
| null;
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Path or function used to group options. When set, options are organized into
|
|
905
|
+
* collapsible group headers in the dropdown.
|
|
906
|
+
*/
|
|
907
|
+
groupBy?: string | ((item: Item) => string | number | symbol);
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Height of group headers in the listbox (px). Defaults to 24px (compact) or 32px.
|
|
911
|
+
* Only applies when `groupBy` is set.
|
|
912
|
+
*/
|
|
913
|
+
headerHeight?: number;
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Apply error styling to the select.
|
|
917
|
+
*/
|
|
918
|
+
hasErrors?: boolean;
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Explicit id for the input element. Used to generate ids for listbox and options.
|
|
922
|
+
*/
|
|
923
|
+
id?: HTMLAttributes['id'];
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Array of items or function to load them asynchronously.
|
|
927
|
+
* Functions receive `(query, prefill, modelValue)` and can return a promise.
|
|
928
|
+
* Please check out {@link https://ui-components-docs.vercel.app/it/guides/fetch-data | the docs} for more information.
|
|
929
|
+
* @defaultValue `[]`
|
|
930
|
+
*/
|
|
931
|
+
items:
|
|
932
|
+
| Item[]
|
|
933
|
+
| ((query: string, prefill: boolean, modelValue: any[]) => Promise<Item[]>)
|
|
934
|
+
| ((query: string, prefill: boolean, modelValue: any[]) => Item[]);
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Height of the options in the listbox (px). Necessary for option virtualization.
|
|
938
|
+
* @defaultValue `40`
|
|
939
|
+
*/
|
|
940
|
+
itemHeight?: number;
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Path to item property for display text or function to extract it.
|
|
944
|
+
*/
|
|
945
|
+
itemText?: string | ((item: Item) => string) | undefined;
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Defines a path that returns a property of the object to use as value or a function that returns any value.
|
|
949
|
+
*/
|
|
950
|
+
itemValue?: string | ((item: Item) => string) | undefined;
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Display the loading state styles.
|
|
954
|
+
*/
|
|
955
|
+
loading?: boolean;
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Message shown while items are being loaded.
|
|
959
|
+
*/
|
|
960
|
+
loadingText?: string;
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Maximum number of selectable items (limits selection when `multiple` is true).
|
|
964
|
+
* @defaultValue `Infinity`
|
|
965
|
+
*/
|
|
966
|
+
max?: number;
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Maximum number of selected items to show as individual labels before collapsing.
|
|
970
|
+
* @defaultValue `Infinity`
|
|
971
|
+
*/
|
|
972
|
+
maxSelectedLabels?: number;
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* v-model value. Single value for single select, array for multiple select.
|
|
976
|
+
*/
|
|
977
|
+
modelValue: any;
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Debounce delay (ms) for modelValue change handling.
|
|
981
|
+
* @defaultValue `0`
|
|
982
|
+
*/
|
|
983
|
+
modelValueDebounceTime?: number;
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Enable multiple item selection. When true, modelValue must be an array.
|
|
987
|
+
*/
|
|
988
|
+
multiple?: boolean;
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Name attribute forwarded to the input element.
|
|
992
|
+
*/
|
|
993
|
+
name?: InputHTMLAttributes['name'];
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Message displayed when no items are available.
|
|
997
|
+
*/
|
|
998
|
+
noDataText?: string;
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Placeholder text when no value is selected.
|
|
1002
|
+
*/
|
|
1003
|
+
placeholder?: InputHTMLAttributes['placeholder'];
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Controls when items are pre-loaded.
|
|
1007
|
+
* `'focus'` loads on first focus, `true` loads immediately, `false` loads only on search.
|
|
1008
|
+
* @defaultValue `'focus'`
|
|
1009
|
+
*/
|
|
1010
|
+
prefill?: boolean | 'focus';
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Name of the icon to render at the left hand side of the input.
|
|
1014
|
+
*/
|
|
1015
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
1016
|
+
'prepend:icon'?: IconType;
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Debounce delay (ms) before triggering search queries after user input stops.
|
|
1020
|
+
* @defaultValue `500`
|
|
1021
|
+
*/
|
|
1022
|
+
queryDebounceTime?: number;
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Make the input read-only while keeping the dropdown accessible.
|
|
1026
|
+
*/
|
|
1027
|
+
readonly?: boolean;
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Mark the input as required for form validation.
|
|
1031
|
+
*/
|
|
1032
|
+
required?: boolean;
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Show the dropdown chevron icon.
|
|
1036
|
+
* @defaultValue `true`
|
|
1037
|
+
*/
|
|
1038
|
+
showChevron?: boolean;
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Accumulate selected items across searches instead of replacing them.
|
|
1042
|
+
* Useful when searching large databases where selections should persist.
|
|
1043
|
+
*/
|
|
1044
|
+
stash?: boolean;
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Function to generate a summary label when selected items exceed `maxSelectedLabels`.
|
|
1048
|
+
* Receives the total count and returns display text.
|
|
1049
|
+
*/
|
|
1050
|
+
selectedLabelsFn?: (count: number) => string;
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Transition duration (ms) for dropdown show/hide animations.
|
|
1054
|
+
* @defaultValue `300`
|
|
1055
|
+
*/
|
|
1056
|
+
transitionDuration?: number;
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Force dropdown repositioning on every animation frame (expensive, use sparingly).
|
|
1060
|
+
* @defaultValue `false`
|
|
1061
|
+
*/
|
|
1062
|
+
updateOnAnimationFrame?: boolean;
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
export type BaseSelectEvents = {
|
|
1066
|
+
/**
|
|
1067
|
+
* Emitted when the search input loses focus.
|
|
1068
|
+
* Forwards the original DOM `FocusEvent`.
|
|
1069
|
+
*/
|
|
1070
|
+
(e: 'blur', event: FocusEvent): void;
|
|
1071
|
+
/**
|
|
1072
|
+
* Emitted when the input fires a change event.
|
|
1073
|
+
* Forwards the original DOM `Event`.
|
|
1074
|
+
*/
|
|
1075
|
+
(e: 'change', event: Event): void;
|
|
1076
|
+
/**
|
|
1077
|
+
* Emitted when the input receives a click interaction.
|
|
1078
|
+
* Forwards the original DOM `MouseEvent`.
|
|
1079
|
+
*/
|
|
1080
|
+
(e: 'click', event: MouseEvent): void;
|
|
1081
|
+
/**
|
|
1082
|
+
* Emitted when the search input gains focus.
|
|
1083
|
+
* Forwards the original DOM `FocusEvent`.
|
|
1084
|
+
*/
|
|
1085
|
+
(e: 'focus', event: FocusEvent): void;
|
|
1086
|
+
/**
|
|
1087
|
+
* Emitted when focus/click moves outside the component after it was active.
|
|
1088
|
+
*/
|
|
1089
|
+
(e: 'inactive'): void;
|
|
1090
|
+
/**
|
|
1091
|
+
* Emitted on native input events from the search field.
|
|
1092
|
+
* Forwards the original DOM `Event`.
|
|
1093
|
+
*/
|
|
1094
|
+
(e: 'input', event: Event): void;
|
|
1095
|
+
/**
|
|
1096
|
+
* Emitted with the new selected value(s) whenever the selection changes.
|
|
1097
|
+
* Single value for single-select, array for multiple-select.
|
|
1098
|
+
*/
|
|
1099
|
+
(e: 'update:modelValue', value: any): void;
|
|
1100
|
+
/**
|
|
1101
|
+
* Emitted when the user confirms a manually typed string that does not match any option.
|
|
1102
|
+
* Useful for "add new" patterns.
|
|
1103
|
+
*/
|
|
1104
|
+
(e: 'option:add', text: string): void;
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Props exposed by slots that receive the current search query and a focus callback
|
|
1109
|
+
* (`append`, `prepend`, `no-data`).
|
|
1110
|
+
*/
|
|
1111
|
+
export type BaseSelectQueryFocusSlotProps = {
|
|
1112
|
+
/**
|
|
1113
|
+
* The current text typed in the search input.
|
|
1114
|
+
*/
|
|
1115
|
+
query: string;
|
|
1116
|
+
/**
|
|
1117
|
+
* Focuses the search input.
|
|
1118
|
+
*/
|
|
1119
|
+
focus: () => void;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Props exposed by slots that receive only the current search query
|
|
1124
|
+
* (`append-outer`, `prepend-outer`, `loading`).
|
|
1125
|
+
*/
|
|
1126
|
+
export type BaseSelectQueryOnlySlotProps = {
|
|
1127
|
+
/**
|
|
1128
|
+
* The current text typed in the search input.
|
|
1129
|
+
*/
|
|
1130
|
+
query: string;
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Props exposed by the `chevron` slot.
|
|
1135
|
+
*/
|
|
1136
|
+
export type BaseSelectChevronSlotProps = {
|
|
1137
|
+
/**
|
|
1138
|
+
* Whether the component is currently loading options.
|
|
1139
|
+
*/
|
|
1140
|
+
loading: boolean;
|
|
1141
|
+
/**
|
|
1142
|
+
* Whether the dropdown panel is currently open.
|
|
1143
|
+
*/
|
|
1144
|
+
shown: boolean;
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Props exposed by the `chip` slot for each selected item when `multiple` is true.
|
|
1149
|
+
*/
|
|
1150
|
+
export type BaseSelectChipSlotProps = {
|
|
1151
|
+
/**
|
|
1152
|
+
* Whether this chip's remove action is disabled.
|
|
1153
|
+
*/
|
|
1154
|
+
disabled: boolean | undefined;
|
|
1155
|
+
/**
|
|
1156
|
+
* Whether the select is in an error state.
|
|
1157
|
+
*/
|
|
1158
|
+
hasErrors?: boolean;
|
|
1159
|
+
/**
|
|
1160
|
+
* The zero-based index of this chip among the selected items.
|
|
1161
|
+
*/
|
|
1162
|
+
index: number;
|
|
1163
|
+
/**
|
|
1164
|
+
* The raw selected item from the `items` prop.
|
|
1165
|
+
*/
|
|
1166
|
+
item: any;
|
|
1167
|
+
/**
|
|
1168
|
+
* Whether the component is currently loading.
|
|
1169
|
+
*/
|
|
1170
|
+
loading: boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Whether the item was added manually (typed by the user).
|
|
1173
|
+
*/
|
|
1174
|
+
manual: boolean;
|
|
1175
|
+
/**
|
|
1176
|
+
* Whether this chip is in a selected state.
|
|
1177
|
+
*/
|
|
1178
|
+
selected: boolean | undefined;
|
|
1179
|
+
/**
|
|
1180
|
+
* The resolved display text for this selected item.
|
|
1181
|
+
*/
|
|
1182
|
+
text: string;
|
|
1183
|
+
/**
|
|
1184
|
+
* The resolved value for this selected item.
|
|
1185
|
+
*/
|
|
1186
|
+
value: any;
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Props exposed by the `option` slot for each item in the dropdown list.
|
|
1191
|
+
*/
|
|
1192
|
+
export type BaseSelectOptionSlotProps<Item = any> = {
|
|
1193
|
+
/**
|
|
1194
|
+
* Whether this option is disabled.
|
|
1195
|
+
*/
|
|
1196
|
+
disabled: boolean;
|
|
1197
|
+
/**
|
|
1198
|
+
* Whether this option is currently keyboard-focused.
|
|
1199
|
+
*/
|
|
1200
|
+
focused: boolean;
|
|
1201
|
+
/**
|
|
1202
|
+
* The zero-based index of this option in the list.
|
|
1203
|
+
*/
|
|
1204
|
+
index: number;
|
|
1205
|
+
/**
|
|
1206
|
+
* Whether the select is in an error state.
|
|
1207
|
+
*/
|
|
1208
|
+
hasErrors?: boolean;
|
|
1209
|
+
/**
|
|
1210
|
+
* The raw item from the `items` prop.
|
|
1211
|
+
*/
|
|
1212
|
+
item: Item;
|
|
1213
|
+
/**
|
|
1214
|
+
* Whether the component is currently loading.
|
|
1215
|
+
*/
|
|
1216
|
+
loading: boolean;
|
|
1217
|
+
/**
|
|
1218
|
+
* Whether this option is currently selected.
|
|
1219
|
+
*/
|
|
1220
|
+
selected: boolean;
|
|
1221
|
+
/**
|
|
1222
|
+
* The resolved display text for this option.
|
|
1223
|
+
*/
|
|
1224
|
+
text: string;
|
|
1225
|
+
/**
|
|
1226
|
+
* The resolved value for this option.
|
|
1227
|
+
*/
|
|
1228
|
+
value: any;
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Props exposed by the `group` slot for each group header in the dropdown list.
|
|
1233
|
+
*/
|
|
1234
|
+
export type BaseSelectGroupSlotProps<Item = any> = {
|
|
1235
|
+
/**
|
|
1236
|
+
* The display text for this group.
|
|
1237
|
+
*/
|
|
1238
|
+
text: string;
|
|
1239
|
+
/**
|
|
1240
|
+
* The raw group item from the `items` prop.
|
|
1241
|
+
*/
|
|
1242
|
+
item: Item;
|
|
1243
|
+
/**
|
|
1244
|
+
* The zero-based index of this group in the list.
|
|
1245
|
+
*/
|
|
1246
|
+
index: number;
|
|
1247
|
+
/**
|
|
1248
|
+
* The number of options in this group.
|
|
1249
|
+
*/
|
|
1250
|
+
length: number;
|
|
1251
|
+
/**
|
|
1252
|
+
* Whether this group is disabled.
|
|
1253
|
+
*/
|
|
1254
|
+
disabled: boolean;
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* Props exposed by dropdown panel boundary slots
|
|
1259
|
+
* (`options:append`, `options:append:outer`, `options:prepend`, `options:prepend:outer`).
|
|
1260
|
+
*/
|
|
1261
|
+
export type BaseSelectFocusSlotProps = {
|
|
1262
|
+
/**
|
|
1263
|
+
* Focuses the search input.
|
|
1264
|
+
*/
|
|
1265
|
+
focus: () => void;
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
export type BaseSelectSlots<Item = any> = {
|
|
1269
|
+
/**
|
|
1270
|
+
* Content rendered after the input area, at the end of the inner container.
|
|
1271
|
+
*/
|
|
1272
|
+
append?: (props: BaseSelectQueryFocusSlotProps) => any;
|
|
1273
|
+
/**
|
|
1274
|
+
* Content rendered after the entire select control, outside the input chrome.
|
|
1275
|
+
*/
|
|
1276
|
+
'append-outer'?: (props: BaseSelectQueryOnlySlotProps) => any;
|
|
1277
|
+
/**
|
|
1278
|
+
* Replaces the default dropdown chevron/arrow icon.
|
|
1279
|
+
*/
|
|
1280
|
+
chevron?: (props: BaseSelectChevronSlotProps) => any;
|
|
1281
|
+
/**
|
|
1282
|
+
* Replaces the chip rendered for each selected item when `multiple` is true.
|
|
1283
|
+
*/
|
|
1284
|
+
chip?: (props: BaseSelectChipSlotProps) => any;
|
|
1285
|
+
/**
|
|
1286
|
+
* Replaces the default loading indicator rendered inside the dropdown while options are fetching.
|
|
1287
|
+
*/
|
|
1288
|
+
loading?: (props: BaseSelectQueryOnlySlotProps) => any;
|
|
1289
|
+
/**
|
|
1290
|
+
* Replaces the default "no data" message rendered inside the dropdown when no options match.
|
|
1291
|
+
*/
|
|
1292
|
+
'no-data'?: (props: BaseSelectQueryFocusSlotProps) => any;
|
|
1293
|
+
/**
|
|
1294
|
+
* Replaces the default option row rendered for each item in the dropdown list.
|
|
1295
|
+
*/
|
|
1296
|
+
option?: (props: BaseSelectOptionSlotProps<Item>) => any;
|
|
1297
|
+
/**
|
|
1298
|
+
* Replaces the default group header row rendered above options that belong to a group.
|
|
1299
|
+
*/
|
|
1300
|
+
group?: (props: BaseSelectGroupSlotProps<Item>) => any;
|
|
1301
|
+
/**
|
|
1302
|
+
* Content rendered after the option list, inside the dropdown panel.
|
|
1303
|
+
*/
|
|
1304
|
+
'options:append'?: (props: BaseSelectFocusSlotProps) => any;
|
|
1305
|
+
/**
|
|
1306
|
+
* Content rendered after the dropdown panel, outside the scrollable options area.
|
|
1307
|
+
*/
|
|
1308
|
+
'options:append:outer'?: (props: BaseSelectFocusSlotProps) => any;
|
|
1309
|
+
/**
|
|
1310
|
+
* Content rendered before the option list, inside the dropdown panel.
|
|
1311
|
+
*/
|
|
1312
|
+
'options:prepend'?: (props: BaseSelectFocusSlotProps) => any;
|
|
1313
|
+
/**
|
|
1314
|
+
* Content rendered before the dropdown panel, outside the scrollable options area.
|
|
1315
|
+
*/
|
|
1316
|
+
'options:prepend:outer'?: (props: BaseSelectFocusSlotProps) => any;
|
|
1317
|
+
/**
|
|
1318
|
+
* Inline content rendered at the start of the input field area, before the selected value text.
|
|
1319
|
+
*/
|
|
1320
|
+
prefix?: (props: object) => any;
|
|
1321
|
+
/**
|
|
1322
|
+
* Content rendered before the input area, at the start of the inner container.
|
|
1323
|
+
*/
|
|
1324
|
+
prepend?: (props: BaseSelectQueryFocusSlotProps) => any;
|
|
1325
|
+
/**
|
|
1326
|
+
* Content rendered before the entire select control, outside the input chrome.
|
|
1327
|
+
*/
|
|
1328
|
+
'prepend-outer'?: (props: BaseSelectQueryOnlySlotProps) => any;
|
|
1329
|
+
/**
|
|
1330
|
+
* Inline content rendered at the end of the input field area, after the selected value text.
|
|
1331
|
+
*/
|
|
1332
|
+
suffix?: (props: object) => any;
|
|
1333
|
+
};
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
## Styles
|
|
1337
|
+
|
|
1338
|
+
```css
|
|
1339
|
+
.bb-base-select {
|
|
1340
|
+
/* Get the inner height based on the largest contained element this is not to be edited with arbitrary values */
|
|
1341
|
+
--bb-input-inner-h: max(
|
|
1342
|
+
calc(var(--bb-leading) + var(--bb-input-py) * 2),
|
|
1343
|
+
calc(var(--bb-input-h) - 2px),
|
|
1344
|
+
var(--bb-input-icon)
|
|
1345
|
+
);
|
|
1346
|
+
--floating-py: calc((var(--bb-input-inner-h) - var(--bb-input-icon)) / 2);
|
|
1347
|
+
--bb-arrow: 0;
|
|
1348
|
+
box-sizing: border-box;
|
|
1349
|
+
display: inline-flex;
|
|
1350
|
+
scroll-margin: 10px;
|
|
1351
|
+
width: 100%;
|
|
1352
|
+
|
|
1353
|
+
&--active {
|
|
1354
|
+
.bb-base-select__text-input {
|
|
1355
|
+
/* Approximate the length of the text as min width so the character doesn't go into a newline as often*/
|
|
1356
|
+
min-width: min(100%, calc(var(--characters) * 12px));
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
&--loading {
|
|
1360
|
+
}
|
|
1361
|
+
&--disabled {
|
|
1362
|
+
}
|
|
1363
|
+
&--errors {
|
|
1364
|
+
}
|
|
1365
|
+
&--readonly {
|
|
1366
|
+
}
|
|
1367
|
+
&--compact {
|
|
1368
|
+
--floating-py: var(--bb-input-compact-floating-py);
|
|
1369
|
+
|
|
1370
|
+
.bb-chipsbox-item {
|
|
1371
|
+
min-height: 20px;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
&--shown {
|
|
1376
|
+
.bb-base-select__chevron {
|
|
1377
|
+
transform: rotate(180deg);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
.bb-smooth-height {
|
|
1382
|
+
align-self: center;
|
|
1383
|
+
text-align: left;
|
|
1384
|
+
width: 100%;
|
|
1385
|
+
}
|
|
1386
|
+
.bb-base-select__inner-wrapper {
|
|
1387
|
+
display: block;
|
|
1388
|
+
flex: 1 1 auto;
|
|
1389
|
+
|
|
1390
|
+
.bb-common-input-inner-container {
|
|
1391
|
+
position: relative;
|
|
1392
|
+
align-items: flex-start;
|
|
1393
|
+
width: 100%;
|
|
1394
|
+
|
|
1395
|
+
> .bb-clearable-button {
|
|
1396
|
+
margin-top: var(--floating-py);
|
|
1397
|
+
}
|
|
1398
|
+
> .bb-spinner {
|
|
1399
|
+
margin-top: var(--floating-py);
|
|
1400
|
+
}
|
|
1401
|
+
> .bb-error-icon {
|
|
1402
|
+
margin-top: var(--floating-py);
|
|
1403
|
+
}
|
|
1404
|
+
> .bb-common-input-inner-container__prepend-icon,
|
|
1405
|
+
> .bb-common-input-inner-container__append-icon {
|
|
1406
|
+
margin-top: var(--floating-py);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
> .bb-common-input-inner-container__prefix,
|
|
1410
|
+
> .bb-common-input-inner-container__suffix {
|
|
1411
|
+
height: var(--bb-input-inner-h);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
:last-child:not(.bb-error-icon) {
|
|
1415
|
+
order: 1;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
.bb-listbox {
|
|
1420
|
+
--option-h: var(--option-height);
|
|
1421
|
+
display: block;
|
|
1422
|
+
|
|
1423
|
+
&.bb-listbox--open {
|
|
1424
|
+
transition: min-height 0s 0s;
|
|
1425
|
+
|
|
1426
|
+
.bb-listbox__outer-container {
|
|
1427
|
+
grid-template-rows: 1fr;
|
|
1428
|
+
opacity: 1;
|
|
1429
|
+
transition-delay: 0s, 00ms;
|
|
1430
|
+
|
|
1431
|
+
> * {
|
|
1432
|
+
opacity: 1;
|
|
1433
|
+
transition-delay: calc(var(--transition-duration) * 0.8);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
&.bb-listbox--no-data {
|
|
1438
|
+
.bb-listbox__outer-container {
|
|
1439
|
+
.bb-listbox__inner-container {
|
|
1440
|
+
.bb-listbox__no-data {
|
|
1441
|
+
display: block;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
&.bb-listbox--loading {
|
|
1447
|
+
.bb-listbox__outer-container {
|
|
1448
|
+
.bb-listbox__inner-container {
|
|
1449
|
+
.bb-listbox__loading {
|
|
1450
|
+
display: block;
|
|
1451
|
+
text-align: left;
|
|
1452
|
+
}
|
|
1453
|
+
.bb-listbox__loading {
|
|
1454
|
+
display: hidden;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
&.bb-listbox--multiple {
|
|
1461
|
+
.autocomplete-option__checkbox {
|
|
1462
|
+
display: inline-block !important;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.bb-listbox__outer-container {
|
|
1467
|
+
background-color: var(--bb-panel);
|
|
1468
|
+
border-color: var(--bb-primary);
|
|
1469
|
+
border-radius: var(--bb-radius);
|
|
1470
|
+
border-width: 0px;
|
|
1471
|
+
display: grid;
|
|
1472
|
+
grid-template-rows: 0fr;
|
|
1473
|
+
opacity: 0;
|
|
1474
|
+
transition-delay: calc(var(--transition-duration) * 0.25), 0s, 0s;
|
|
1475
|
+
transition-duration:
|
|
1476
|
+
var(--transition-duration), calc(var(--transition-duration) * 1.4),
|
|
1477
|
+
var(--transition-duration);
|
|
1478
|
+
transition-property: grid-template-rows, opacity, border;
|
|
1479
|
+
|
|
1480
|
+
> * {
|
|
1481
|
+
opacity: 0;
|
|
1482
|
+
transition-delay: 0s;
|
|
1483
|
+
transition-duration: calc(var(--transition-duration) * 2);
|
|
1484
|
+
transition-property: opacity;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.bb-listbox__inner-container {
|
|
1488
|
+
overflow: auto;
|
|
1489
|
+
text-align: left;
|
|
1490
|
+
/* Always show options up to the bottom of the screen, but not more than 6 options */
|
|
1491
|
+
max-height: min(
|
|
1492
|
+
calc(var(--option-h) * 6),
|
|
1493
|
+
calc(round(down, calc(100dvh - 130px), var(--option-h)) - 1px)
|
|
1494
|
+
);
|
|
1495
|
+
|
|
1496
|
+
.bb-listbox__loading,
|
|
1497
|
+
.bb-listbox__no-data {
|
|
1498
|
+
color: var(--bb-text);
|
|
1499
|
+
display: none;
|
|
1500
|
+
padding-bottom: var(--bb-select-option-py);
|
|
1501
|
+
padding-left: var(--bb-select-option-px);
|
|
1502
|
+
padding-right: var(--bb-select-option-px);
|
|
1503
|
+
padding-top: var(--bb-select-option-py);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
span[role='listbox'] {
|
|
1507
|
+
position: relative;
|
|
1508
|
+
width: 100%;
|
|
1509
|
+
display: block;
|
|
1510
|
+
|
|
1511
|
+
.bb-listbox__group {
|
|
1512
|
+
left: 0;
|
|
1513
|
+
position: absolute;
|
|
1514
|
+
top: 0;
|
|
1515
|
+
width: 100%;
|
|
1516
|
+
display: block;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.bb-listbox__group-header {
|
|
1520
|
+
display: flex;
|
|
1521
|
+
align-items: center;
|
|
1522
|
+
height: var(--group-header-height, 32px);
|
|
1523
|
+
padding-left: var(--bb-select-option-px);
|
|
1524
|
+
padding-right: var(--bb-select-option-px);
|
|
1525
|
+
|
|
1526
|
+
user-select: none;
|
|
1527
|
+
|
|
1528
|
+
.bb-listbox__group-header-label {
|
|
1529
|
+
display: flex;
|
|
1530
|
+
align-items: center;
|
|
1531
|
+
gap: 16px;
|
|
1532
|
+
flex: auto;
|
|
1533
|
+
text-align: left;
|
|
1534
|
+
overflow: hidden;
|
|
1535
|
+
text-overflow: ellipsis;
|
|
1536
|
+
white-space: nowrap;
|
|
1537
|
+
color: color-mix(in srgb, var(--bb-text) 50%, transparent);
|
|
1538
|
+
font-size: 0.75rem;
|
|
1539
|
+
font-weight: 600;
|
|
1540
|
+
letter-spacing: 0.025em;
|
|
1541
|
+
text-transform: uppercase;
|
|
1542
|
+
|
|
1543
|
+
&::after {
|
|
1544
|
+
content: '';
|
|
1545
|
+
display: block;
|
|
1546
|
+
width: 100%;
|
|
1547
|
+
height: 1px;
|
|
1548
|
+
background-color: var(--bb-border);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.bb-listbox__option {
|
|
1554
|
+
left: 0;
|
|
1555
|
+
position: absolute;
|
|
1556
|
+
display: block;
|
|
1557
|
+
top: 0;
|
|
1558
|
+
width: 100%;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.bb-listbox__group > .bb-listbox__option {
|
|
1562
|
+
position: static;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.autocomplete-option {
|
|
1566
|
+
--bg-opacity: 0;
|
|
1567
|
+
--bg-base-color: var(--bb-text);
|
|
1568
|
+
align-items: center;
|
|
1569
|
+
background-color: color-mix(
|
|
1570
|
+
in srgb,
|
|
1571
|
+
var(--bg-base-color) calc(100% * var(--bg-opacity)),
|
|
1572
|
+
transparent
|
|
1573
|
+
);
|
|
1574
|
+
color: var(--bb-text);
|
|
1575
|
+
cursor: pointer;
|
|
1576
|
+
display: flex;
|
|
1577
|
+
flex-grow: 1;
|
|
1578
|
+
gap: 8px;
|
|
1579
|
+
height: var(--option-h);
|
|
1580
|
+
padding-left: var(--bb-select-option-px);
|
|
1581
|
+
padding-right: var(--bb-select-option-px);
|
|
1582
|
+
transition-duration: 250ms;
|
|
1583
|
+
transition-property: color, background-color;
|
|
1584
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1585
|
+
width: 100%;
|
|
1586
|
+
|
|
1587
|
+
&:hover:not(.autocomplete-option--disabled) {
|
|
1588
|
+
--bg-opacity: 0.05;
|
|
1589
|
+
--bg-base-color: var(--bb-text);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
&--focused {
|
|
1593
|
+
--bg-opacity: 0.15;
|
|
1594
|
+
--bg-base-color: var(--bb-text);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
&--focused:hover:not(.autocomplete-option--disabled) {
|
|
1598
|
+
--bg-opacity: 0.2;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
&--selected {
|
|
1602
|
+
--bg-opacity: 0.1;
|
|
1603
|
+
--bg-base-color: var(--bb-primary) !important;
|
|
1604
|
+
color: var(--bb-primary);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
&--selected:hover:not(.autocomplete-option--disabled) {
|
|
1608
|
+
--bg-opacity: 0.15;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
&--selected.autocomplete-option--focused {
|
|
1612
|
+
--bg-opacity: 0.2;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
&--selected.autocomplete-option--focused:hover:not(
|
|
1616
|
+
.autocomplete-option--disabled
|
|
1617
|
+
) {
|
|
1618
|
+
--bg-opacity: 0.2;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
&--disabled {
|
|
1622
|
+
cursor: not-allowed;
|
|
1623
|
+
opacity: 0.5;
|
|
1624
|
+
|
|
1625
|
+
.autocomplete-option__checkbox.autocomplete-option__checkbox {
|
|
1626
|
+
background-color: var(--bb-panel-disabled);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.autocomplete-option__checkbox {
|
|
1631
|
+
pointer-events: none;
|
|
1632
|
+
display: none;
|
|
1633
|
+
width: 16px;
|
|
1634
|
+
height: 16px;
|
|
1635
|
+
background-color: var(--bb-panel);
|
|
1636
|
+
border-color: var(--bb-border);
|
|
1637
|
+
border-radius: min(6px, var(--bb-radius));
|
|
1638
|
+
border-style: solid;
|
|
1639
|
+
border-width: 1px;
|
|
1640
|
+
margin-left: -6px;
|
|
1641
|
+
transition-duration: 300ms;
|
|
1642
|
+
transition-property:
|
|
1643
|
+
color, background-color, border-color, text-decoration-color,
|
|
1644
|
+
box-shadow;
|
|
1645
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1646
|
+
|
|
1647
|
+
path {
|
|
1648
|
+
stroke: var(--bb-contrasting);
|
|
1649
|
+
stroke-dasharray: 105;
|
|
1650
|
+
stroke-dashoffset: 105;
|
|
1651
|
+
transition: stroke-dashoffset 0.3s;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.autocomplete-option__label {
|
|
1656
|
+
display: block;
|
|
1657
|
+
flex: auto;
|
|
1658
|
+
text-align: left;
|
|
1659
|
+
overflow: hidden;
|
|
1660
|
+
text-overflow: ellipsis;
|
|
1661
|
+
white-space: nowrap;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
&.autocomplete-option--selected {
|
|
1665
|
+
.autocomplete-option__checkbox {
|
|
1666
|
+
background-color: var(--bb-primary);
|
|
1667
|
+
border-color: var(--bb-primary);
|
|
1668
|
+
|
|
1669
|
+
path {
|
|
1670
|
+
stroke-dashoffset: 0;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
&__input-container {
|
|
1681
|
+
display: flex;
|
|
1682
|
+
flex-wrap: wrap;
|
|
1683
|
+
gap: 0.25rem;
|
|
1684
|
+
padding-bottom: var(--bb-input-py);
|
|
1685
|
+
padding-top: var(--bb-input-py);
|
|
1686
|
+
}
|
|
1687
|
+
&__max-reached {
|
|
1688
|
+
border-right: 1px solid var(--bb-border);
|
|
1689
|
+
color: var(--bb-input-color);
|
|
1690
|
+
font-size: var(--bb-input-font-size);
|
|
1691
|
+
padding-right: 4px;
|
|
1692
|
+
}
|
|
1693
|
+
.bb-chipsbox-item {
|
|
1694
|
+
position: relative;
|
|
1695
|
+
z-index: 1;
|
|
1696
|
+
}
|
|
1697
|
+
.bb-commabox-item {
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.bb-base-select__text-input {
|
|
1701
|
+
flex-basis: 0px !important;
|
|
1702
|
+
flex-grow: 1 !important;
|
|
1703
|
+
flex-shrink: 10000 !important;
|
|
1704
|
+
margin-bottom: 0px;
|
|
1705
|
+
margin-top: 0px;
|
|
1706
|
+
min-width: 0;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
&__chevron {
|
|
1710
|
+
background-color: inherit;
|
|
1711
|
+
color: var(--bb-icon-color);
|
|
1712
|
+
flex-shrink: 0;
|
|
1713
|
+
margin-top: var(--floating-py);
|
|
1714
|
+
order: 1;
|
|
1715
|
+
transform: rotate(0deg);
|
|
1716
|
+
transition: transform 0.2s ease-in-out;
|
|
1717
|
+
width: var(--bb-input-icon);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
```
|