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.
Files changed (423) hide show
  1. package/dist/ai/BaseButton.md +448 -0
  2. package/dist/ai/BaseCheckbox.md +494 -0
  3. package/dist/ai/BaseCheckboxGroup.md +597 -0
  4. package/dist/ai/BaseColorInput.md +461 -0
  5. package/dist/ai/BaseDatePicker.md +739 -0
  6. package/dist/ai/BaseDatePickerInput.md +1517 -0
  7. package/dist/ai/BaseDialog.md +610 -0
  8. package/dist/ai/BaseInputContainer.md +570 -0
  9. package/dist/ai/BaseNumberInput.md +509 -0
  10. package/dist/ai/BaseRadio.md +405 -0
  11. package/dist/ai/BaseRadioGroup.md +535 -0
  12. package/dist/ai/BaseRating.md +489 -0
  13. package/dist/ai/BaseSelect.md +1720 -0
  14. package/dist/ai/BaseSlider.md +871 -0
  15. package/dist/ai/BaseSwitch.md +322 -0
  16. package/dist/ai/BaseSwitchGroup.md +298 -0
  17. package/dist/ai/BaseTag.md +624 -0
  18. package/dist/ai/BaseTextInput.md +392 -0
  19. package/dist/ai/BaseTextarea.md +398 -0
  20. package/dist/ai/BbAccordion.md +135 -0
  21. package/dist/ai/BbAlert.md +226 -0
  22. package/dist/ai/BbAvatar.md +200 -0
  23. package/dist/ai/BbBadge.md +185 -0
  24. package/dist/ai/BbBreadcrumbs.md +536 -0
  25. package/dist/ai/BbButton.md +687 -0
  26. package/dist/ai/BbCheckbox.md +280 -0
  27. package/dist/ai/BbCheckboxGroup.md +387 -0
  28. package/dist/ai/BbChip.md +148 -0
  29. package/dist/ai/BbCollapsible.md +119 -0
  30. package/dist/ai/BbColorInput.md +345 -0
  31. package/dist/ai/BbColorPalette.md +360 -0
  32. package/dist/ai/BbConfirm.md +160 -0
  33. package/dist/ai/BbDatePickerInput.md +414 -0
  34. package/dist/ai/BbDialog.md +135 -0
  35. package/dist/ai/BbDropdown.md +765 -0
  36. package/dist/ai/BbDropdownButton.md +629 -0
  37. package/dist/ai/BbDropzone.md +504 -0
  38. package/dist/ai/BbIcon.md +238 -0
  39. package/dist/ai/BbIntersection.md +121 -0
  40. package/dist/ai/BbNumberInput.md +372 -0
  41. package/dist/ai/BbOffCanvas.md +549 -0
  42. package/dist/ai/BbPagination.md +562 -0
  43. package/dist/ai/BbPopover.md +580 -0
  44. package/dist/ai/BbProgress.md +97 -0
  45. package/dist/ai/BbRadio.md +256 -0
  46. package/dist/ai/BbRadioGroup.md +373 -0
  47. package/dist/ai/BbRating.md +245 -0
  48. package/dist/ai/BbRatio.md +62 -0
  49. package/dist/ai/BbRows.md +307 -0
  50. package/dist/ai/BbSelect.md +562 -0
  51. package/dist/ai/BbSelectPopover.md +2010 -0
  52. package/dist/ai/BbSlider.md +274 -0
  53. package/dist/ai/BbSmoothHeight.md +167 -0
  54. package/dist/ai/BbSpinner.md +154 -0
  55. package/dist/ai/BbSwitch.md +151 -0
  56. package/dist/ai/BbSwitchGroup.md +237 -0
  57. package/dist/ai/BbTab.md +954 -0
  58. package/dist/ai/BbTable.md +1624 -0
  59. package/dist/ai/BbTag.md +315 -0
  60. package/dist/ai/BbTextInput.md +357 -0
  61. package/dist/ai/BbTextarea.md +277 -0
  62. package/dist/ai/BbToast.md +219 -0
  63. package/dist/ai/BbTooltip.md +353 -0
  64. package/dist/ai/BbTree.md +271 -0
  65. package/dist/ai/ChipsBox.md +211 -0
  66. package/dist/ai/ClearableButton.md +67 -0
  67. package/dist/ai/CommaBox.md +212 -0
  68. package/dist/ai/CommonInputInnerContainer.md +419 -0
  69. package/dist/ai/CommonInputOuterContainer.md +56 -0
  70. package/dist/ai/CommonPopover.md +446 -0
  71. package/dist/ai/ErrorIcon.md +61 -0
  72. package/dist/ai/FlatListBox.md +382 -0
  73. package/dist/ai/GroupedListBox.md +538 -0
  74. package/dist/ai/ListBox.md +234 -0
  75. package/dist/ai/OptionsContainer.md +257 -0
  76. package/dist/ai/index.md +124 -0
  77. package/dist/components/BaseButton/BaseButton.vue.d.ts +2 -163
  78. package/dist/components/BaseButton/types.d.ts +158 -0
  79. package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +4 -4
  80. package/dist/components/BaseCheckboxGroup/BaseCheckboxGroup.vue.d.ts +2 -2
  81. package/dist/components/BaseCheckboxGroup/types.d.ts +16 -9
  82. package/dist/components/BaseColorInput/BaseColorInput.vue.d.ts +12 -52
  83. package/dist/components/BaseDatePicker/BaseDatePicker.vue.d.ts +4 -76
  84. package/dist/components/BaseDatePicker/types.d.ts +100 -0
  85. package/dist/components/BaseDatePickerInput/BaseDatePickerInput.vue.d.ts +18 -315
  86. package/dist/components/BaseDatePickerInput/types.d.ts +206 -0
  87. package/dist/components/BaseDialog/BaseDialog.vue.d.ts +6 -156
  88. package/dist/components/BaseDialog/types.d.ts +180 -0
  89. package/dist/components/BaseInputContainer/BaseInputContainer.vue.d.ts +1 -107
  90. package/dist/components/BaseInputContainer/types.d.ts +126 -0
  91. package/dist/components/BaseNumberInput/BaseNumberInput.vue.d.ts +7 -170
  92. package/dist/components/BaseNumberInput/types.d.ts +191 -0
  93. package/dist/components/BaseRadio/BaseRadio.vue.d.ts +6 -119
  94. package/dist/components/BaseRadio/types.d.ts +173 -0
  95. package/dist/components/BaseRadioGroup/BaseRadioGroup.vue.d.ts +4 -274
  96. package/dist/components/BaseRadioGroup/types.d.ts +240 -0
  97. package/dist/components/BaseRating/BaseRating.vue.d.ts +5 -106
  98. package/dist/components/BaseRating/types.d.ts +144 -0
  99. package/dist/components/BaseSelect/BaseSelect.vue.d.ts +2 -363
  100. package/dist/components/BaseSelect/types.d.ts +457 -0
  101. package/dist/components/BaseSlider/BaseSlider.vue.d.ts +6 -178
  102. package/dist/components/BaseSlider/types.d.ts +201 -0
  103. package/dist/components/BaseSwitch/BaseSwitch.vue.d.ts +7 -35
  104. package/dist/components/BaseSwitch/types.d.ts +25 -0
  105. package/dist/components/BaseSwitchGroup/BaseSwitchGroup.vue.d.ts +5 -11
  106. package/dist/components/BaseSwitchGroup/types.d.ts +8 -0
  107. package/dist/components/BaseTag/BaseTag.vue.d.ts +27 -222
  108. package/dist/components/BaseTag/types.d.ts +136 -0
  109. package/dist/components/BaseTextInput/BaseTextInput.vue.d.ts +5 -141
  110. package/dist/components/BaseTextInput/types.d.ts +166 -0
  111. package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +5 -131
  112. package/dist/components/BaseTextarea/types.d.ts +151 -0
  113. package/dist/components/BbAccordion/BbAccordion.vue.d.ts +3 -51
  114. package/dist/components/BbAccordion/types.d.ts +32 -0
  115. package/dist/components/BbAlert/BbAlert.vue.d.ts +3 -50
  116. package/dist/components/BbAlert/types.d.ts +42 -0
  117. package/dist/components/BbAvatar/BbAvatar.vue.d.ts +3 -23
  118. package/dist/components/BbAvatar/types.d.ts +34 -0
  119. package/dist/components/BbBadge/BbBadge.vue.d.ts +3 -40
  120. package/dist/components/BbBadge/types.d.ts +30 -0
  121. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue.d.ts +14 -178
  122. package/dist/components/BbBreadcrumbs/types.d.ts +109 -0
  123. package/dist/components/BbButton/BbButton.vue.d.ts +4 -163
  124. package/dist/components/BbButton/types.d.ts +159 -0
  125. package/dist/components/BbCheckbox/BbCheckbox.vue.d.ts +7 -165
  126. package/dist/components/BbCheckbox/types.d.ts +130 -0
  127. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue.d.ts +7 -324
  128. package/dist/components/BbCheckboxGroup/types.d.ts +189 -0
  129. package/dist/components/BbChip/BbChip.vue.d.ts +6 -28
  130. package/dist/components/BbChip/types.d.ts +23 -0
  131. package/dist/components/BbCollapsible/BbCollapsible.vue.d.ts +3 -24
  132. package/dist/components/BbCollapsible/types.d.ts +20 -0
  133. package/dist/components/BbColorInput/BbColorInput.vue.d.ts +10 -151
  134. package/dist/components/BbColorInput/types.d.ts +131 -0
  135. package/dist/components/BbColorPalette/BbColorPalette.vue.d.ts +2 -112
  136. package/dist/components/BbColorPalette/types.d.ts +127 -0
  137. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue.d.ts +6 -212
  138. package/dist/components/BbDatePickerInput/types.d.ts +180 -0
  139. package/dist/components/BbDialog/BbDialog.vue.d.ts +2 -2
  140. package/dist/components/BbDialog/types.d.ts +1 -0
  141. package/dist/components/BbDropdown/BbDropdown.vue.d.ts +21 -247
  142. package/dist/components/BbDropdown/types.d.ts +147 -0
  143. package/dist/components/BbDropdownButton/BbDropdownButton.vue.d.ts +16 -209
  144. package/dist/components/BbDropdownButton/types.d.ts +114 -0
  145. package/dist/components/BbDropzone/BbDropzone.vue.d.ts +7 -86
  146. package/dist/components/BbDropzone/types.d.ts +67 -0
  147. package/dist/components/BbIcon/BbIcon.vue.d.ts +2 -26
  148. package/dist/components/BbIcon/types.d.ts +28 -0
  149. package/dist/components/BbIntersection/BbIntersection.vue.d.ts +3 -41
  150. package/dist/components/BbIntersection/types.d.ts +36 -0
  151. package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +44 -175
  152. package/dist/components/BbNumberInput/types.d.ts +130 -0
  153. package/dist/components/BbOffCanvas/BbOffCanvas.vue.d.ts +5 -93
  154. package/dist/components/BbOffCanvas/types.d.ts +97 -0
  155. package/dist/components/BbPagination/BbPagination.vue.d.ts +4 -87
  156. package/dist/components/BbPagination/types.d.ts +80 -0
  157. package/dist/components/BbPopover/BbPopover.vue.d.ts +9 -135
  158. package/dist/components/BbPopover/types.d.ts +99 -0
  159. package/dist/components/BbProgress/BbProgress.vue.d.ts +2 -14
  160. package/dist/components/BbProgress/types.d.ts +20 -0
  161. package/dist/components/BbRadio/BbRadio.vue.d.ts +7 -150
  162. package/dist/components/BbRadio/types.d.ts +117 -0
  163. package/dist/components/BbRadioGroup/BbRadioGroup.vue.d.ts +7 -322
  164. package/dist/components/BbRadioGroup/types.d.ts +182 -0
  165. package/dist/components/BbRating/BbRating.vue.d.ts +10 -113
  166. package/dist/components/BbRating/types.d.ts +105 -0
  167. package/dist/components/BbRatio/BbRatio.vue.d.ts +3 -18
  168. package/dist/components/BbRatio/types.d.ts +15 -0
  169. package/dist/components/BbSelect/BbSelect.vue.d.ts +7 -375
  170. package/dist/components/BbSelect/types.d.ts +351 -0
  171. package/dist/components/BbSelectPopover/BbSelectPopover.vue.d.ts +1 -1
  172. package/dist/components/BbSelectPopover/types.d.ts +351 -0
  173. package/dist/components/BbSlider/BbSlider.vue.d.ts +10 -129
  174. package/dist/components/BbSlider/types.d.ts +123 -0
  175. package/dist/components/BbSmoothHeight/BbSmoothHeight.vue.d.ts +2 -23
  176. package/dist/components/BbSmoothHeight/types.d.ts +24 -0
  177. package/dist/components/BbSpinner/BbSpinner.vue.d.ts +3 -5
  178. package/dist/components/BbSpinner/types.d.ts +8 -0
  179. package/dist/components/BbSwitch/BbSwitch.vue.d.ts +9 -65
  180. package/dist/components/BbSwitch/types.d.ts +29 -0
  181. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue.d.ts +7 -190
  182. package/dist/components/BbSwitchGroup/types.d.ts +81 -0
  183. package/dist/components/BbTab/BbTab.vue.d.ts +9 -247
  184. package/dist/components/BbTab/types.d.ts +186 -0
  185. package/dist/components/BbTag/BbTag.vue.d.ts +6 -156
  186. package/dist/components/BbTag/types.d.ts +158 -0
  187. package/dist/components/BbTextInput/BbTextInput.vue.d.ts +10 -152
  188. package/dist/components/BbTextInput/types.d.ts +137 -0
  189. package/dist/components/BbTextarea/BbTextarea.vue.d.ts +10 -142
  190. package/dist/components/BbTextarea/types.d.ts +123 -0
  191. package/dist/components/BbToast/BbToast.vue.d.ts +2 -6
  192. package/dist/components/BbToast/types.d.ts +8 -0
  193. package/dist/components/BbTooltip/BbTooltip.vue.d.ts +8 -65
  194. package/dist/components/BbTooltip/types.d.ts +55 -0
  195. package/dist/components/BbTree/BbTree.vue.d.ts +2 -65
  196. package/dist/components/BbTree/types.d.ts +69 -0
  197. package/dist/components/{ChipsBox.vue.d.ts → ChipsBox/ChipsBox.vue.d.ts} +5 -6
  198. package/dist/components/ChipsBox/types.d.ts +14 -0
  199. package/dist/components/{ClearableButton.vue.d.ts → ClearableButton/ClearableButton.vue.d.ts} +2 -0
  200. package/dist/components/ClearableButton/types.d.ts +3 -0
  201. package/dist/components/{CommaBox.vue.d.ts → CommaBox/CommaBox.vue.d.ts} +5 -6
  202. package/dist/components/CommaBox/types.d.ts +14 -0
  203. package/dist/components/CommonInputInnerContainer/CommonInputInnerContainer.vue.d.ts +25 -0
  204. package/dist/components/CommonInputInnerContainer/types.d.ts +47 -0
  205. package/dist/components/CommonInputOuterContainer/CommonInputOuterContainer.vue.d.ts +17 -0
  206. package/dist/components/CommonInputOuterContainer/types.d.ts +16 -0
  207. package/dist/components/{CommonPopover.vue.d.ts → CommonPopover/CommonPopover.vue.d.ts} +5 -30
  208. package/dist/components/CommonPopover/types.d.ts +43 -0
  209. package/dist/components/{ErrorIcon.vue.d.ts → ErrorIcon/ErrorIcon.vue.d.ts} +2 -0
  210. package/dist/components/ErrorIcon/types.d.ts +3 -0
  211. package/dist/components/FlatListBox/types.d.ts +97 -0
  212. package/dist/components/GroupedListBox/types.d.ts +118 -0
  213. package/dist/components/ListBox/ListBox.vue.d.ts +30 -0
  214. package/dist/components/ListBox/types.d.ts +133 -0
  215. package/dist/components/OptionsContainer/OptionsContainer.vue.d.ts +13 -0
  216. package/dist/components/OptionsContainer/types.d.ts +96 -0
  217. package/dist/composables/useBbConfig.d.ts +1 -1
  218. package/dist/composables/useConfirm.d.ts +1 -1
  219. package/dist/index.css +1 -1
  220. package/dist/index.d.ts +18 -18
  221. package/dist/index109.js +9 -9
  222. package/dist/index110.js +50 -49
  223. package/dist/index114.js +1 -1
  224. package/dist/index118.js +1 -1
  225. package/dist/index122.js +1 -0
  226. package/dist/index124.js +4 -4
  227. package/dist/index126.js +13 -13
  228. package/dist/index132.js +22 -19
  229. package/dist/index134.js +1 -1
  230. package/dist/index136.js +5 -5
  231. package/dist/index138.js +1 -1
  232. package/dist/index14.js +1 -1
  233. package/dist/index140.js +18 -17
  234. package/dist/index144.js +1 -1
  235. package/dist/index146.js +2 -2
  236. package/dist/index149.js +2 -2
  237. package/dist/index16.js +3 -3
  238. package/dist/index18.js +3 -3
  239. package/dist/index20.js +70 -59
  240. package/dist/index22.js +14 -14
  241. package/dist/index221.js +138 -2
  242. package/dist/index222.js +2 -138
  243. package/dist/index224.js +5 -34
  244. package/dist/index225.js +7 -32
  245. package/dist/index226.js +32 -26
  246. package/dist/index227.js +7 -0
  247. package/dist/index228.js +5 -5
  248. package/dist/index229.js +5 -8
  249. package/dist/index230.js +5 -7
  250. package/dist/index231.js +3 -2
  251. package/dist/index232.js +2 -9
  252. package/dist/index233.js +6 -13
  253. package/dist/index234.js +8 -3
  254. package/dist/index235.js +268 -2
  255. package/dist/index236.js +52 -11
  256. package/dist/index237.js +50 -6
  257. package/dist/index238.js +32 -3
  258. package/dist/index239.js +60 -3
  259. package/dist/index24.js +10 -10
  260. package/dist/index240.js +13 -2
  261. package/dist/index241.js +187 -17
  262. package/dist/index242.js +3 -12
  263. package/dist/index243.js +2 -51
  264. package/dist/index244.js +2 -18
  265. package/dist/index245.js +2 -12
  266. package/dist/index246.js +12 -16
  267. package/dist/index247.js +11 -28
  268. package/dist/index248.js +48 -15
  269. package/dist/index249.js +17 -4
  270. package/dist/index250.js +2 -2
  271. package/dist/index252.js +2 -2
  272. package/dist/index254.js +3 -135
  273. package/dist/index255.js +4 -0
  274. package/dist/index256.js +4 -107
  275. package/dist/index257.js +19 -12
  276. package/dist/index258.js +6 -2
  277. package/dist/index259.js +16 -7
  278. package/dist/index26.js +3 -3
  279. package/dist/index260.js +86 -7
  280. package/dist/index262.js +32 -0
  281. package/dist/index263.js +18 -5
  282. package/dist/index264.js +12 -5
  283. package/dist/index265.js +18 -5
  284. package/dist/index266.js +2 -5
  285. package/dist/index267.js +7 -5
  286. package/dist/index268.js +7 -5
  287. package/dist/index269.js +3 -67
  288. package/dist/index270.js +4 -33
  289. package/dist/index271.js +5 -2
  290. package/dist/index272.js +5 -2
  291. package/dist/index273.js +5 -3
  292. package/dist/index274.js +135 -4
  293. package/dist/index276.js +9 -6
  294. package/dist/index277.js +7 -11
  295. package/dist/index278.js +23 -5
  296. package/dist/index279.js +3 -5
  297. package/dist/index28.js +57 -55
  298. package/dist/index280.js +21 -266
  299. package/dist/index281.js +364 -43
  300. package/dist/index283.js +32 -31
  301. package/dist/index284.js +3 -60
  302. package/dist/index285.js +25 -4
  303. package/dist/index286.js +3 -20
  304. package/dist/index287.js +18 -5
  305. package/dist/index288.js +12 -373
  306. package/dist/index289.js +109 -0
  307. package/dist/index290.js +11 -6
  308. package/dist/index291.js +66 -15
  309. package/dist/index292.js +32 -10
  310. package/dist/index294.js +5 -8
  311. package/dist/index295.js +9 -20
  312. package/dist/index296.js +2 -8
  313. package/dist/index297.js +9 -23
  314. package/dist/index298.js +52 -24
  315. package/dist/index299.js +5 -188
  316. package/dist/index30.js +3 -3
  317. package/dist/index300.js +21 -3
  318. package/dist/index301.js +28 -3
  319. package/dist/index303.js +9 -0
  320. package/dist/index304.js +2 -7
  321. package/dist/index305.js +280 -3
  322. package/dist/index306.js +2 -2
  323. package/dist/index307.js +16 -5
  324. package/dist/index308.js +2 -7
  325. package/dist/index309.js +16 -3
  326. package/dist/index310.js +2 -3
  327. package/dist/index311.js +27 -3
  328. package/dist/index312.js +2 -2
  329. package/dist/index313.js +2 -28
  330. package/dist/index314.js +2 -17
  331. package/dist/index315.js +2 -4
  332. package/dist/index316.js +1 -1
  333. package/dist/index317.js +28 -3
  334. package/dist/index318.js +2 -280
  335. package/dist/index319.js +7 -2
  336. package/dist/index32.js +2 -2
  337. package/dist/index320.js +719 -125
  338. package/dist/index321.js +366 -2
  339. package/dist/index322.js +56 -14
  340. package/dist/index323.js +4 -2
  341. package/dist/index324.js +3 -16
  342. package/dist/index325.js +17 -2
  343. package/dist/index326.js +3 -16
  344. package/dist/index327.js +3 -2
  345. package/dist/index328.js +3 -19
  346. package/dist/index329.js +3 -2
  347. package/dist/index330.js +120 -22
  348. package/dist/index331.js +2 -2
  349. package/dist/index332.js +15 -2
  350. package/dist/index333.js +2 -2
  351. package/dist/index334.js +19 -2
  352. package/dist/index335.js +2 -2
  353. package/dist/index336.js +5 -2
  354. package/dist/index337.js +5 -3
  355. package/dist/index338.js +2 -4
  356. package/dist/index339.js +4 -719
  357. package/dist/index34.js +8 -8
  358. package/dist/index340.js +2 -366
  359. package/dist/index341.js +3 -57
  360. package/dist/index342.js +3 -6
  361. package/dist/index343.js +6 -5
  362. package/dist/index344.js +6 -34
  363. package/dist/index345.js +17 -127
  364. package/dist/index346.js +7 -396
  365. package/dist/index347.js +14 -199
  366. package/dist/index348.js +5 -259
  367. package/dist/index349.js +6 -227
  368. package/dist/index352.js +35 -2
  369. package/dist/index353.js +129 -2
  370. package/dist/index354.js +378 -114
  371. package/dist/index355.js +92 -6
  372. package/dist/index356.js +226 -17
  373. package/dist/index357.js +22 -9
  374. package/dist/index359.js +7 -5
  375. package/dist/index36.js +4 -4
  376. package/dist/index360.js +200 -7
  377. package/dist/index361.js +255 -18
  378. package/dist/index362.js +136 -0
  379. package/dist/index363.js +2 -93
  380. package/dist/index364.js +2 -441
  381. package/dist/index365.js +427 -114
  382. package/dist/index366.js +127 -46
  383. package/dist/index367.js +44 -67
  384. package/dist/index368.js +66 -516
  385. package/dist/index369.js +515 -45
  386. package/dist/index370.js +52 -0
  387. package/dist/index38.js +133 -131
  388. package/dist/index40.js +8 -8
  389. package/dist/index42.js +2 -2
  390. package/dist/index44.js +16 -15
  391. package/dist/index46.js +4 -4
  392. package/dist/index50.js +28 -25
  393. package/dist/index54.js +1 -1
  394. package/dist/index56.js +1 -1
  395. package/dist/index58.js +2 -2
  396. package/dist/index60.js +2 -2
  397. package/dist/index62.js +5 -5
  398. package/dist/index66.js +3 -1
  399. package/dist/index68.js +1 -1
  400. package/dist/index74.js +4 -4
  401. package/dist/index82.js +6 -6
  402. package/dist/index84.js +1 -1
  403. package/dist/index86.js +2 -2
  404. package/dist/index88.js +3 -3
  405. package/dist/index90.js +1 -1
  406. package/dist/index93.js +3 -3
  407. package/dist/index95.js +2 -2
  408. package/dist/index97.js +5 -5
  409. package/dist/index99.js +1 -1
  410. package/dist/utilities/functions/parseSize.d.ts +1 -1
  411. package/package.json +5 -3
  412. package/dist/components/CommonInputInnerContainer.vue.d.ts +0 -81
  413. package/dist/components/CommonInputOuterContainer.vue.d.ts +0 -41
  414. package/dist/components/FlatListBox.vue.d.ts +0 -119
  415. package/dist/components/GroupedListBox.vue.d.ts +0 -153
  416. package/dist/components/ListBox.vue.d.ts +0 -170
  417. package/dist/components/OptionsContainer.vue.d.ts +0 -172
  418. package/dist/index261.js +0 -88
  419. package/dist/index275.js +0 -25
  420. package/dist/index282.js +0 -54
  421. package/dist/index293.js +0 -5
  422. package/dist/index302.js +0 -55
  423. package/dist/index358.js +0 -17
@@ -0,0 +1,2010 @@
1
+ # BbSelectPopover
2
+
3
+ ## Template & Script
4
+
5
+ ```vue
6
+ <template>
7
+ <slot
8
+ v-if="!hasExternalActivator"
9
+ name="activator"
10
+ v-bind="{
11
+ props: {
12
+ ref: setActivatorRef,
13
+ },
14
+ shown,
15
+ disabled,
16
+ readonly,
17
+ loading: computedLoading,
18
+ query,
19
+ selectedOptions,
20
+ text: triggerInputValue,
21
+ clear: onClear,
22
+ open,
23
+ close,
24
+ toggle,
25
+ }"
26
+ />
27
+ <CommonPopover
28
+ v-model="shown"
29
+ :anchor="activatorEl"
30
+ :arrow-padding="arrowPadding"
31
+ :boundary="boundary"
32
+ class="bb-select-popover"
33
+ :class="{
34
+ 'bb-select-popover--errors': hasErrors,
35
+ 'bb-select-popover--loading': computedLoading,
36
+ }"
37
+ :flip="flip"
38
+ :placement="placement"
39
+ :offset="offset"
40
+ :padding="padding"
41
+ :transition-duration="transitionDuration"
42
+ :hide-arrow="hideArrow"
43
+ :style="panelStyles"
44
+ >
45
+ <div
46
+ v-if="computedAllowWriting"
47
+ class="bb-select-popover__search-container"
48
+ >
49
+ <input
50
+ :id="`${id}_search`"
51
+ ref="searchInput"
52
+ v-model="query"
53
+ :aria-activedescendant="activeDescendantId"
54
+ :aria-controls="renderListBox ? `${id}_listbox` : undefined"
55
+ :aria-describedby="computedAriaDescribedby"
56
+ :aria-invalid="computedAriaInvalid"
57
+ :aria-label="searchInputAriaLabel"
58
+ :autocomplete="autocomplete"
59
+ class="bb-select-popover__search-input"
60
+ :inputmode="inputmode"
61
+ :tabindex="shown ? 0 : -1"
62
+ role="searchbox"
63
+ :type="'text'"
64
+ @blur.stop="onSearchInputBlur"
65
+ @change.stop="onSearchInputChange"
66
+ @focus.stop="onSearchInputFocus"
67
+ @input.stop="onSearchInputInput"
68
+ @keydown.stop.prevent.down="onArrowDown"
69
+ @keydown.stop.prevent.enter="onEnter"
70
+ @keydown.stop.prevent.esc="onEscape"
71
+ @keydown.stop.prevent.up="onArrowUp"
72
+ />
73
+ <ErrorIcon
74
+ aria-hidden="true"
75
+ class="bb-select-popover__search-error-icon"
76
+ focusable="false"
77
+ />
78
+ <BbSpinner
79
+ aria-hidden="true"
80
+ class="bb-select-popover__search-spinner"
81
+ focusable="false"
82
+ />
83
+ <svg
84
+ class="bb-select-popover__search-icon"
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ viewBox="0 0 24 24"
87
+ aria-hidden="true"
88
+ >
89
+ <path
90
+ fill="none"
91
+ stroke="currentColor"
92
+ stroke-linecap="round"
93
+ stroke-linejoin="round"
94
+ stroke-width="2"
95
+ d="m21 21-4.343-4.343m0 0A8 8 0 1 0 5.343 5.343a8 8 0 0 0 11.314 11.314"
96
+ />
97
+ </svg>
98
+ </div>
99
+ <button
100
+ v-else
101
+ ref="popoverFocusProxy"
102
+ class="bb-select-popover__focus-proxy sr-only"
103
+ type="button"
104
+ :aria-controls="`${id}_listbox`"
105
+ :aria-describedby="computedAriaDescribedby"
106
+ :aria-invalid="computedAriaInvalid"
107
+ :aria-label="searchInputAriaLabel"
108
+ tabindex="-1"
109
+ @focus="onPopoverFocusProxyFocus"
110
+ @keydown.stop.prevent.down="onArrowDown"
111
+ @keydown.stop.prevent.up="onArrowUp"
112
+ @keydown.stop.prevent.enter="onEnter"
113
+ @keydown.stop.prevent.esc="onEscape"
114
+ >
115
+ Focus options
116
+ </button>
117
+ <div
118
+ class="bb-select-popover__options-outer"
119
+ :aria-hidden="shown ? undefined : 'true'"
120
+ :inert="!shown"
121
+ >
122
+ <slot name="options:prepend:outer" :focus="focusPopoverContent" />
123
+ </div>
124
+ <ListBox
125
+ :id="`${id}_listbox`"
126
+ ref="optionsContainer"
127
+ :compact="compact"
128
+ :loading="computedLoading"
129
+ :loading-text="loadingText"
130
+ :multiple="multiple"
131
+ :no-data-text="noDataText"
132
+ :open="shown"
133
+ :options="groupBy ? undefined : filteredOptions"
134
+ :groups="groupBy ? groupedOptions : undefined"
135
+ :header-height="headerHeight"
136
+ :option-height="itemHeight"
137
+ @focused:change="onListboxFocusedChange"
138
+ @option:selected="onOptionSelected"
139
+ @option:unselected="onOptionUnselected"
140
+ >
141
+ <template #options:prepend
142
+ ><slot name="options:prepend" :focus="focusPopoverContent"
143
+ /></template>
144
+ <template #loading><slot name="loading" :query="query" /></template>
145
+ <template #no-data
146
+ ><slot name="no-data" :query="query" :focus="focusPopoverContent"
147
+ /></template>
148
+ <template #options:append
149
+ ><slot name="options:append" :focus="focusPopoverContent"
150
+ /></template>
151
+ <template #option="data"
152
+ ><slot :loading="computedLoading" name="option" v-bind="data"
153
+ /></template>
154
+ <template #group="data"><slot name="group" v-bind="data" /></template>
155
+ </ListBox>
156
+ <div
157
+ class="bb-select-popover__options-outer"
158
+ :aria-hidden="shown ? undefined : 'true'"
159
+ :inert="!shown"
160
+ >
161
+ <slot name="options:append:outer" :focus="focusPopoverContent" />
162
+ </div>
163
+ </CommonPopover>
164
+ </template>
165
+
166
+ <script setup lang="ts" generic="Item = any">
167
+ import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
168
+ import type { CommonProps } from '@/types/CommonProps';
169
+ import type { Option as BaseOption, GroupedOptions } from '@/types/Option';
170
+ import { computed, ref, nextTick, watch, onBeforeUnmount } from 'vue';
171
+ import { extractDomContainer } from '@/utilities/functions/extractDomContainer';
172
+ import { get } from '@/utilities/functions/get';
173
+ import { hash } from '@/utilities/functions/hash';
174
+ import { isNil } from '@/utilities/functions/isNil';
175
+ import { matchAnyKey } from '@/utilities/functions/matchAnyKey';
176
+ import { parseWidthString } from '@/utilities/functions/parseWidthString';
177
+ import { toRef } from 'vue';
178
+ import { useArray } from '@/composables/useArray';
179
+ import { useBaseOptions } from '@/composables/useBaseOptions';
180
+ import { useCoherence } from '@/composables/useCoherence';
181
+ import { useElementSize, useIntersectionObserver } from '@vueuse/core';
182
+ import { useHashedWatcher } from '@/composables/useHashedWatcher';
183
+ import { useId } from '@/composables/useId';
184
+ import { useIndexById } from '@/composables/useIndexById';
185
+ import { useItemsGetter } from '@/composables/useItemsGetter';
186
+ import { useMobile } from '@/composables/useMobile';
187
+ import { usePrefill } from '@/composables/usePrefill';
188
+ import { useUntil } from '@/composables/useUntil';
189
+ import { wait } from '@/utilities/functions/wait';
190
+ import { BbSpinner } from '@/index';
191
+ import CommonPopover from '../CommonPopover/CommonPopover.vue';
192
+ import ErrorIcon from '../ErrorIcon/ErrorIcon.vue';
193
+ import ListBox from '../ListBox/ListBox.vue';
194
+ import { waitFor } from '@/utilities/functions/waitFor';
195
+
196
+ export type BbSelectPopoverProps<Item> = {
197
+ /**
198
+ * External activator element or component ref.
199
+ * When provided, the activator slot is not rendered and event listeners
200
+ * are attached programmatically to the referenced element.
201
+ */
202
+ activator?: HTMLElement | Record<string, any> | null;
203
+
204
+ /**
205
+ * Controls whether the search input is shown inside the popover panel.
206
+ * Use `'not-mobile'` to hide the search input on mobile.
207
+ * @default false
208
+ */
209
+ allowWriting?: boolean | 'not-mobile';
210
+
211
+ /**
212
+ * Browser autocomplete hint for the popover search input.
213
+ * @default 'off'
214
+ */
215
+ autocomplete?: InputHTMLAttributes['autocomplete'];
216
+
217
+ /**
218
+ * Apply the compact density variant.
219
+ */
220
+ compact?: boolean;
221
+
222
+ /**
223
+ * Space the arrow keeps from popover edges.
224
+ * Useful with rounded corners.
225
+ * @default 10
226
+ */
227
+ arrowPadding?: CommonProps['arrowPadding'];
228
+
229
+ /**
230
+ * Constrains the popover to a specific boundary element.
231
+ */
232
+ boundary?: HTMLElement | Record<string, any> | string | null;
233
+
234
+ /**
235
+ * Additional dependencies that trigger item reloading when changed.
236
+ * @default []
237
+ */
238
+ dependencies?: any[];
239
+
240
+ /**
241
+ * Debounce delay (ms) for dependency-triggered reloads.
242
+ * @default 0
243
+ */
244
+ depsDebounceTime?: number;
245
+
246
+ /**
247
+ * Disables the component.
248
+ */
249
+ disabled?: boolean;
250
+
251
+ /**
252
+ * Marks the component as being in error state.
253
+ */
254
+ hasErrors?: boolean;
255
+
256
+ /**
257
+ * Explicit invalid state for accessibility semantics.
258
+ * Falls back to `hasErrors` when not provided.
259
+ */
260
+ ariaInvalid?: boolean;
261
+
262
+ /**
263
+ * Space-separated IDREF(s) that describe this field for assistive technologies.
264
+ * Accepts either a string or array of ids.
265
+ */
266
+ ariaDescribedby?: string | string[];
267
+
268
+ /**
269
+ * Enable or disable floating flip behavior.
270
+ * @default true
271
+ */
272
+ flip?: boolean;
273
+
274
+ /**
275
+ * Whether the floating arrow is hidden.
276
+ * @default true
277
+ */
278
+ hideArrow?: boolean;
279
+
280
+ /**
281
+ * Reset modelValue to `null` or empty array if it no longer matches available items.
282
+ */
283
+ enforceCoherence?: boolean;
284
+
285
+ /**
286
+ * Controls how options are filtered during search.
287
+ * Options are always filtered by display text, but can also match additional properties
288
+ * when set to a string path, array of paths, or custom function.
289
+ * Set to `false` to disable filtering, or `'not_stashed'` to exclude stashed items.
290
+ * @default () => []
291
+ */
292
+ filterBy?:
293
+ | string
294
+ | string[]
295
+ | false
296
+ | 'not_stashed'
297
+ | ((value: any, item: any, query: string | null) => boolean)
298
+ | null;
299
+
300
+ groupBy?: string | ((item: Item) => string | number | symbol);
301
+
302
+ /**
303
+ * Height of group headers in the listbox (px). Defaults to 24px (compact) or 32px.
304
+ * Only applies when `groupBy` is set.
305
+ */
306
+ headerHeight?: number;
307
+
308
+ /**
309
+ * Explicit id for the component. Used to generate ids for listbox and options.
310
+ */
311
+ id?: HTMLAttributes['id'];
312
+
313
+ /**
314
+ * Array of items or function to load them asynchronously.
315
+ * Functions receive `(query, prefill, modelValue)` and can return a promise.
316
+ * @default []
317
+ */
318
+ items:
319
+ | Item[]
320
+ | ((query: string, prefill: boolean, modelValue: any[]) => Promise<Item[]>)
321
+ | ((query: string, prefill: boolean, modelValue: any[]) => Item[]);
322
+
323
+ /**
324
+ * Height of the options in the listbox (px).
325
+ * @default 40
326
+ */
327
+ itemHeight?: number;
328
+
329
+ /**
330
+ * Path to item property for display text or function to extract it.
331
+ */
332
+ itemText?: string | ((item: Item) => string) | undefined;
333
+
334
+ /**
335
+ * Path to item property for value or function to extract it.
336
+ */
337
+ itemValue?: string | ((item: Item) => string) | undefined;
338
+
339
+ /**
340
+ * Display the loading state styles.
341
+ */
342
+ loading?: boolean;
343
+
344
+ /**
345
+ * Message shown while items are being loaded.
346
+ */
347
+ loadingText?: string;
348
+
349
+ /**
350
+ * Maximum number of selectable items (limits selection when `multiple` is true).
351
+ * @default Infinity
352
+ */
353
+ max?: number;
354
+
355
+ /**
356
+ * v-model value. Single value for single select, array for multiple select.
357
+ */
358
+ modelValue: any;
359
+
360
+ /**
361
+ * Debounce delay (ms) for modelValue change handling.
362
+ * @default 0
363
+ */
364
+ modelValueDebounceTime?: number;
365
+
366
+ /**
367
+ * Enable multiple item selection. When true, modelValue must be an array.
368
+ */
369
+ multiple?: boolean;
370
+
371
+ /**
372
+ * Message displayed when no items are available.
373
+ */
374
+ noDataText?: string;
375
+
376
+ /**
377
+ * Distance (px) between the activator and the popover.
378
+ * @default 4
379
+ */
380
+ offset?: number;
381
+
382
+ /**
383
+ * Minimum page padding used by floating shift middleware.
384
+ * @default 0
385
+ */
386
+ padding?: CommonProps['padding'];
387
+
388
+ /**
389
+ * Floating placement of the popover.
390
+ * @default 'bottom'
391
+ */
392
+ placement?: CommonProps['placement'];
393
+
394
+ /**
395
+ * Controls when items are pre-loaded.
396
+ * `'interaction'` loads on first user interaction (first popover open),
397
+ * `true` loads immediately, `false` loads only on explicit search/open flows.
398
+ * @default 'interaction'
399
+ */
400
+ prefill?: boolean | 'interaction';
401
+
402
+ /**
403
+ * Debounce delay (ms) before triggering search queries after user input stops.
404
+ * @default 500
405
+ */
406
+ queryDebounceTime?: number;
407
+
408
+ /**
409
+ * Prevents opening the popover while keeping the activator accessible.
410
+ */
411
+ readonly?: boolean;
412
+
413
+ /**
414
+ * Accessible label used by the search input rendered inside the popover.
415
+ * @default 'Search options'
416
+ */
417
+ searchInputAriaLabel?: string;
418
+
419
+ /**
420
+ * Accumulate selected items across searches instead of replacing them.
421
+ */
422
+ stash?: boolean;
423
+
424
+ /**
425
+ * Transition duration (ms) for popover show/hide animations.
426
+ * @default 300
427
+ */
428
+ transitionDuration?: number;
429
+
430
+ /**
431
+ * Width of the popover in pixels, percentage, or `'auto'`.
432
+ */
433
+ width?: number | string;
434
+ };
435
+
436
+ export type BbSelectPopoverEvents = {
437
+ (e: 'blur', event: FocusEvent): void;
438
+ (e: 'change', event: Event): void;
439
+ (e: 'click', event: MouseEvent): void;
440
+ (e: 'focus', event: FocusEvent): void;
441
+ (e: 'inactive'): void;
442
+ (e: 'input', event: Event): void;
443
+ (e: 'update:modelValue', value: any): void;
444
+ (e: 'option:add', text: string): void;
445
+ };
446
+
447
+ export type BbSelectPopoverSlots<Item> = {
448
+ /**
449
+ * Replaces the default trigger element that opens the select popover.
450
+ * @param props - Bind these onto the activator element with `v-bind="props"` to wire up the popover reference.
451
+ * @param shown - Whether the popover is currently open.
452
+ * @param disabled - Whether the select is disabled.
453
+ * @param readonly - Whether the select is read-only.
454
+ * @param loading - Whether options are currently being fetched.
455
+ * @param query - The current search query string.
456
+ * @param clear - Clears the current selection and query.
457
+ * @param open - Opens the popover.
458
+ * @param close - Closes the popover.
459
+ * @param toggle - Toggles the popover open/closed.
460
+ */
461
+ activator?: (props: {
462
+ props: {
463
+ ref: (reference: unknown) => void;
464
+ };
465
+ shown: boolean;
466
+ disabled: boolean | undefined;
467
+ readonly: boolean | undefined;
468
+ loading: boolean;
469
+ query: string;
470
+ clear: () => void;
471
+ open: () => Promise<void>;
472
+ close: () => Promise<void>;
473
+ toggle: () => Promise<void>;
474
+ }) => any;
475
+ /**
476
+ * Content shown inside the options dropdown while items are loading.
477
+ * @param query - The current search query string.
478
+ */
479
+ loading?: (props: { query: string }) => any;
480
+ /**
481
+ * Content shown when no options match the query or the list is empty.
482
+ * @param query - The current search query string.
483
+ * @param focus - Focuses the internal search input.
484
+ */
485
+ 'no-data'?: (props: { query: string; focus: () => void }) => any;
486
+ /**
487
+ * Replaces the default rendering of each option row in the dropdown.
488
+ * @param disabled - Whether this option is disabled.
489
+ * @param focused - Whether this option currently has keyboard focus.
490
+ * @param index - Zero-based index of this option in the list.
491
+ * @param item - The raw item from the `items` prop.
492
+ * @param loading - Whether items are still being fetched.
493
+ * @param selected - Whether this option is currently selected.
494
+ * @param text - The resolved display text for this option.
495
+ * @param value - The resolved value for this option.
496
+ */
497
+ option?: (props: {
498
+ disabled: boolean;
499
+ focused: boolean;
500
+ index: number;
501
+ item: Item;
502
+ loading: boolean;
503
+ selected: boolean;
504
+ text: string;
505
+ value: any;
506
+ }) => any;
507
+ /**
508
+ * Replaces the default group header rendered above grouped options.
509
+ * @param text - The display text for this group.
510
+ * @param item - The raw group item.
511
+ * @param index - Zero-based index of the first option in this group.
512
+ * @param length - Number of options in this group.
513
+ * @param disabled - Whether all options in this group are disabled.
514
+ */
515
+ group?: (props: {
516
+ text: string;
517
+ item: Item;
518
+ index: number;
519
+ length: number;
520
+ disabled: boolean;
521
+ }) => any;
522
+ /**
523
+ * Content appended inside the options list, after the last option.
524
+ * @param focus - Focuses the internal search input.
525
+ */
526
+ 'options:append'?: (props: { focus: () => void }) => any;
527
+ /**
528
+ * Content appended outside the options list container.
529
+ * @param focus - Focuses the internal search input.
530
+ */
531
+ 'options:append:outer'?: (props: { focus: () => void }) => any;
532
+ /**
533
+ * Content prepended inside the options list, before the first option.
534
+ * @param focus - Focuses the internal search input.
535
+ */
536
+ 'options:prepend'?: (props: { focus: () => void }) => any;
537
+ /**
538
+ * Content prepended outside the options list container.
539
+ * @param focus - Focuses the internal search input.
540
+ */
541
+ 'options:prepend:outer'?: (props: { focus: () => void }) => any;
542
+ };
543
+
544
+ const props = withDefaults(defineProps<BbSelectPopoverProps<Item>>(), {
545
+ allowWriting: true,
546
+ arrowPadding: 10,
547
+ autocomplete: 'off',
548
+ depsDebounceTime: 0,
549
+ dependencies: () => [],
550
+ filterBy: () => [],
551
+ flip: true,
552
+ hideArrow: true,
553
+ max: Infinity,
554
+ modelValueDebounceTime: 0,
555
+ offset: 4,
556
+ padding: 10,
557
+ placement: 'bottom',
558
+ prefill: 'interaction',
559
+ queryDebounceTime: 500,
560
+ transitionDuration: 300,
561
+ width: '200',
562
+ });
563
+
564
+ const emit = defineEmits<BbSelectPopoverEvents>();
565
+
566
+ defineSlots<BbSelectPopoverSlots<Item>>();
567
+
568
+ if (props.multiple && !Array.isArray(props.modelValue)) {
569
+ throw new Error('Multiple is set to "true" but modelValue is not an array.');
570
+ }
571
+
572
+ /**
573
+ * START ELEMENT REFERENCES
574
+ */
575
+ const activatorEl = ref<HTMLElement>();
576
+ const optionsContainer = ref<any | null>(null);
577
+ const searchInput = ref<HTMLInputElement | null>(null);
578
+ const popoverFocusProxy = ref<HTMLButtonElement | null>(null);
579
+ const activeDescendantId = ref<string>();
580
+ const { width: activatorWidth } = useElementSize(
581
+ activatorEl,
582
+ { width: 0, height: 0 },
583
+ {
584
+ box: 'border-box',
585
+ }
586
+ );
587
+ /**
588
+ * END ELEMENT REFERENCES
589
+ */
590
+
591
+ /**
592
+ * START ACTIVATOR MANAGEMENT
593
+ */
594
+ const hasExternalActivator = computed(() => props.activator != null);
595
+
596
+ const setActivatorRef = (reference: unknown) => {
597
+ const el = extractDomContainer(reference);
598
+ if (!el) return;
599
+ activatorEl.value = el;
600
+ };
601
+
602
+ watch(
603
+ () => props.activator,
604
+ (val) => {
605
+ if (val) setActivatorRef(val);
606
+ },
607
+ { immediate: true }
608
+ );
609
+ /**
610
+ * END ACTIVATOR MANAGEMENT
611
+ */
612
+
613
+ const { isMobile } = useMobile();
614
+ const computedAllowWriting = computed(() => {
615
+ if (props.allowWriting === 'not-mobile') return !isMobile.value;
616
+ return !!props.allowWriting;
617
+ });
618
+ const inputmode = computed(() =>
619
+ props.allowWriting === 'not-mobile' && isMobile.value ? 'none' : undefined
620
+ );
621
+ const searchInputAriaLabel = computed(
622
+ () => props.searchInputAriaLabel ?? 'Search options'
623
+ );
624
+
625
+ const id = props.id || useId().id.value;
626
+ const computedAriaInvalid = computed(() => {
627
+ const isInvalid = props.ariaInvalid ?? !!props.hasErrors;
628
+ return isInvalid ? 'true' : undefined;
629
+ });
630
+ const computedAriaDescribedby = computed(() => {
631
+ const describedBy = props.ariaDescribedby;
632
+ if (Array.isArray(describedBy)) {
633
+ const normalized = describedBy
634
+ .map((value) => value.trim())
635
+ .filter(Boolean)
636
+ .join(' ');
637
+ return normalized || undefined;
638
+ }
639
+ if (typeof describedBy === 'string') {
640
+ const normalized = describedBy.trim();
641
+ return normalized || undefined;
642
+ }
643
+ return undefined;
644
+ });
645
+
646
+ const { resume, pause } = useIntersectionObserver(
647
+ activatorEl,
648
+ async ([{ intersectionRatio }]) => {
649
+ if (intersectionRatio !== 1 && shown.value) {
650
+ await close();
651
+ }
652
+ },
653
+ { threshold: [0, 1], immediate: false }
654
+ );
655
+
656
+ const normalizedWidth = computed(() => {
657
+ if (props.width === 'auto') return 'auto';
658
+ if (props.width == null || props.width === '') {
659
+ return activatorWidth.value ? `${activatorWidth.value}px` : '320px';
660
+ }
661
+
662
+ try {
663
+ const { value, unit } = parseWidthString(props.width);
664
+ if (unit === '%') {
665
+ if (!activatorWidth.value) return '320px';
666
+ return `${activatorWidth.value * (value / 100)}px`;
667
+ }
668
+ return `${value}${unit}`;
669
+ } catch {
670
+ if (typeof props.width === 'string') return props.width.trim();
671
+ return activatorWidth.value ? `${activatorWidth.value}px` : '320px';
672
+ }
673
+ });
674
+
675
+ const panelStyles = computed(() => ({
676
+ '--bb-select-popover-width': normalizedWidth.value,
677
+ }));
678
+
679
+ /**
680
+ * START FOCUS HELPERS
681
+ */
682
+ const focusActivator = () => {
683
+ activatorEl.value?.focus();
684
+ };
685
+ const focusSearchInput = () => {
686
+ if (searchInput.value instanceof HTMLInputElement) {
687
+ searchInput.value.focus();
688
+ }
689
+ };
690
+ const focusPopoverFocusProxy = () => {
691
+ if (popoverFocusProxy.value instanceof HTMLButtonElement) {
692
+ popoverFocusProxy.value.focus();
693
+ }
694
+ };
695
+ const focusPopoverContent = () => {
696
+ if (computedAllowWriting.value) {
697
+ focusSearchInput();
698
+ return;
699
+ }
700
+ focusPopoverFocusProxy();
701
+ };
702
+ /**
703
+ * END FOCUS HELPERS
704
+ */
705
+
706
+ /**
707
+ * Search input focus keeps the whole control in active state.
708
+ */
709
+ const onSearchInputFocus = (event: FocusEvent) => {
710
+ emit('focus', event);
711
+ setActive();
712
+ };
713
+
714
+ const onSearchInputBlur = (event: FocusEvent) => {
715
+ emit('blur', event);
716
+ syncActiveStateOnBlur();
717
+ };
718
+
719
+ const onSearchInputChange = (event: Event) => {
720
+ emit('change', event);
721
+ };
722
+
723
+ const onPopoverFocusProxyFocus = (_event: FocusEvent) => {
724
+ setActive();
725
+ };
726
+
727
+ /**
728
+ * Search on user typing inside the popover search field.
729
+ */
730
+ const onSearchInputInput = (event: Event) => {
731
+ emit('input', event);
732
+ debouncedGetter(query.value, false, props.modelValue);
733
+ };
734
+
735
+ const onListboxFocusedChange = (id: string | undefined) => {
736
+ activeDescendantId.value = id;
737
+ };
738
+
739
+ /**
740
+ * On click outside of the activator and popover, close the panel and remove active state.
741
+ */
742
+ const onOutsideInteraction = async (event: Event) => {
743
+ const target =
744
+ event.target instanceof Element
745
+ ? event.target
746
+ : event.target instanceof Text
747
+ ? event.target.parentElement
748
+ : null;
749
+ if (!target) return;
750
+ const insideActivator = activatorEl.value?.contains(target);
751
+ const insidePopover = !!target.closest('.bb-common-popover');
752
+ if (insideActivator || insidePopover) return;
753
+ await close();
754
+ setInactive();
755
+ };
756
+
757
+ const canProcessKeyboardBindings = computed(() => {
758
+ if (props.readonly) return false;
759
+ if (props.disabled) return false;
760
+ if (computedLoading.value) return false;
761
+ return true;
762
+ });
763
+
764
+ /**
765
+ * On first open the listbox is lazily mounted.
766
+ * Wait a few micro-cycles so imperative focus calls don't no-op.
767
+ */
768
+ const waitForOptionsContainer = async () =>
769
+ waitFor(() => !!optionsContainer.value, 100);
770
+
771
+ const focusFirstSelectedOption = () => {
772
+ const firstSelected = selectedOptions.value[0];
773
+ if (firstSelected?.valueHash) {
774
+ optionsContainer.value?.focusByHash(firstSelected.valueHash);
775
+ return;
776
+ }
777
+ optionsContainer.value?.focusFirstSelected();
778
+ };
779
+
780
+ const focusLastSelectedOption = () => {
781
+ const lastSelected = selectedOptions.value[selectedOptions.value.length - 1];
782
+ if (lastSelected?.valueHash) {
783
+ optionsContainer.value?.focusByHash(lastSelected.valueHash);
784
+ return;
785
+ }
786
+ optionsContainer.value?.focusLastSelected();
787
+ };
788
+
789
+ /**
790
+ * On arrow up:
791
+ * When the panel is open — focus on the previous option
792
+ * When the panel is closed — open and focus on the last selected option
793
+ */
794
+ const onArrowUp = async () => {
795
+ if (!canProcessKeyboardBindings.value) return;
796
+ if (shown.value) {
797
+ optionsContainer.value?.focusPrevious();
798
+ } else {
799
+ await open({ focusPopoverContent: false });
800
+ await waitForOptionsContainer();
801
+ await wait(props.transitionDuration);
802
+ focusLastSelectedOption();
803
+ }
804
+ };
805
+
806
+ const onArrowDown = async () => {
807
+ if (!canProcessKeyboardBindings.value) return;
808
+ if (shown.value) {
809
+ optionsContainer.value?.focusNext();
810
+ } else {
811
+ await open({ focusPopoverContent: false });
812
+ await waitForOptionsContainer();
813
+ await wait(props.transitionDuration);
814
+ focusFirstSelectedOption();
815
+ }
816
+ };
817
+
818
+ const onEnter = async () => {
819
+ if (!canProcessKeyboardBindings.value) {
820
+ return;
821
+ }
822
+ if (!shown.value) {
823
+ await open({ focusPopoverContent: false });
824
+ await waitForOptionsContainer();
825
+ await wait(props.transitionDuration);
826
+ if (selectedOptions.value.length) {
827
+ focusFirstSelectedOption();
828
+ } else if (computedAllowWriting.value) {
829
+ focusSearchInput();
830
+ } else {
831
+ focusPopoverFocusProxy();
832
+ }
833
+ return;
834
+ } else if (optionsContainer.value?.getHighlighted()) {
835
+ optionsContainer.value.confirmOption();
836
+ return;
837
+ }
838
+ if (!computedAllowWriting.value) return;
839
+ const text = query.value.trim();
840
+ if (isItemSelected(text)) return;
841
+ emit('option:add', text);
842
+ query.value = '';
843
+ };
844
+
845
+ const onEscape = async () => {
846
+ if (shown.value) {
847
+ await close();
848
+ focusActivator();
849
+ }
850
+ };
851
+
852
+ const query = ref<string>('');
853
+ const hasInteracted = ref(false);
854
+
855
+ const {
856
+ getter,
857
+ debouncedGetter,
858
+ items: internalItems,
859
+ loading,
860
+ } = useItemsGetter({
861
+ items: toRef(props, 'items'),
862
+ debounce: props.queryDebounceTime,
863
+ });
864
+
865
+ const {
866
+ array: innerStash,
867
+ add: addToStash,
868
+ remove: removeFromStash,
869
+ set: setStash,
870
+ } = useArray<BaseOption>();
871
+
872
+ const {
873
+ hasPrefilled,
874
+ isPrefilling,
875
+ canLoad,
876
+ prefill: prefillItems,
877
+ } = usePrefill({
878
+ trigger: computed(
879
+ () =>
880
+ props.prefill === true ||
881
+ (props.prefill === 'interaction' && hasInteracted.value)
882
+ ),
883
+ currentValue: undefined,
884
+ multiple: false,
885
+ fn: async (isPrefill) => {
886
+ await getter(query.value, isPrefill, props.modelValue);
887
+ if (props.stash) {
888
+ addToStash(...options.value.map((o) => o.item));
889
+ }
890
+ },
891
+ });
892
+
893
+ const { data: modelValueIndexedByHash, get: isItemSelected } = useIndexById({
894
+ items: computed(() => [].concat(props.modelValue)),
895
+ });
896
+
897
+ const { options } = useBaseOptions({
898
+ disabled: toRef(props, 'disabled'),
899
+ items: computed(() => [...internalItems.value, ...innerStash.value]),
900
+ itemText: props.itemText,
901
+ itemValue: props.itemValue,
902
+ max: props.max,
903
+ selectable: true,
904
+ selectedIndexedByHash: modelValueIndexedByHash,
905
+ });
906
+
907
+ const { data: optionsIndexedByHash } = useIndexById({
908
+ items: options,
909
+ key: 'valueHash',
910
+ });
911
+
912
+ const { coherent, coherentValue } = useCoherence({
913
+ modelValue: toRef(props, 'modelValue'),
914
+ multiple: props.multiple,
915
+ iteratee: (item) => !!optionsIndexedByHash.value[hash(item)],
916
+ });
917
+
918
+ const selectedOptions = computed<BaseOption[]>(() =>
919
+ ([] as any[])
920
+ .concat(props.modelValue)
921
+ .map((v) => optionsIndexedByHash.value[hash(v)])
922
+ .filter(Boolean)
923
+ );
924
+
925
+ const computedLoading = computed(() => !!(loading.value || props.loading));
926
+ const hasErrors = computed(() => !!props.hasErrors);
927
+ const triggerInputValue = computed(() => {
928
+ if (props.multiple) return '';
929
+ return selectedOptions.value[0]?.text ?? '';
930
+ });
931
+
932
+ useHashedWatcher(
933
+ () => props.modelValue,
934
+ async () => {
935
+ if (!canLoad.value) return;
936
+ if (!coherent.value) {
937
+ await getter(query.value, true, props.modelValue);
938
+ if (props.enforceCoherence) {
939
+ emit('update:modelValue', coherentValue.value);
940
+ optionsContainer.value?.blur();
941
+ }
942
+ }
943
+ },
944
+ { debounce: props.modelValueDebounceTime }
945
+ );
946
+ useHashedWatcher(
947
+ () => [props.dependencies, props.items],
948
+ async () => {
949
+ if (!canLoad.value) return;
950
+ await getter(query.value, true, props.modelValue);
951
+ if (props.enforceCoherence && !coherent.value) {
952
+ emit('update:modelValue', coherentValue.value);
953
+ optionsContainer.value?.blur();
954
+ }
955
+ },
956
+ { debounce: props.depsDebounceTime }
957
+ );
958
+
959
+ const onOptionSelected = async (option: BaseOption) => {
960
+ if (props.multiple) {
961
+ emit('update:modelValue', props.modelValue.concat(option.value));
962
+ if (props.stash) {
963
+ addToStash(option.item);
964
+ }
965
+ } else {
966
+ if (props.stash) {
967
+ setStash(option.item);
968
+ }
969
+ emit('update:modelValue', option.value);
970
+ await nextTick();
971
+ }
972
+ if (props.multiple && computedAllowWriting.value && shown.value) {
973
+ focusSearchInput();
974
+ } else {
975
+ focusActivator();
976
+ }
977
+ if (!props.multiple) {
978
+ close();
979
+ }
980
+ };
981
+
982
+ const onOptionUnselected = async (option: BaseOption) => {
983
+ removeFromStash((o) => o.valueHash !== option.valueHash);
984
+ if (props.multiple) {
985
+ const copy = { ...modelValueIndexedByHash.value };
986
+ delete copy[option.valueHash];
987
+ emit('update:modelValue', Object.values(copy));
988
+ } else {
989
+ emit('update:modelValue', null);
990
+ }
991
+ if (props.multiple && computedAllowWriting.value && shown.value) {
992
+ focusSearchInput();
993
+ } else {
994
+ focusActivator();
995
+ }
996
+ };
997
+
998
+ /**
999
+ * Filter options based on the current query.
1000
+ */
1001
+ const filteredOptions = computed<BaseOption<Item>[]>(() => {
1002
+ const queryMatchesASelectedOption = selectedOptions.value.some(
1003
+ (option: BaseOption) => option.text === query.value
1004
+ );
1005
+ const shouldFilter =
1006
+ (props.multiple && query.value) ||
1007
+ (props.filterBy === 'not_stashed' && query.value) ||
1008
+ (query.value && !queryMatchesASelectedOption && !props.multiple);
1009
+
1010
+ if (!shouldFilter || !props.filterBy) return options.value;
1011
+
1012
+ const stashedHashes =
1013
+ props.filterBy === 'not_stashed'
1014
+ ? new Set(innerStash.value.map((o) => o.valueHash))
1015
+ : null;
1016
+
1017
+ return options.value.filter((item: BaseOption) => {
1018
+ let matchedAnything = false;
1019
+ if (typeof props.filterBy === 'function') {
1020
+ matchedAnything = props.filterBy(item.value, item.item, query.value);
1021
+ } else if (props.filterBy === 'not_stashed' && query.value) {
1022
+ matchedAnything = !stashedHashes!.has(item.valueHash);
1023
+ } else if (Array.isArray(props.filterBy) && props.filterBy.length) {
1024
+ matchedAnything = matchAnyKey(
1025
+ item.item,
1026
+ props.filterBy,
1027
+ query.value.trim()
1028
+ );
1029
+ }
1030
+ return matchedAnything || matchAnyKey(item, ['text'], query.value.trim());
1031
+ });
1032
+ });
1033
+
1034
+ const defaultGroupSymbol = Symbol('default');
1035
+
1036
+ const getGroupData = (option: BaseOption) => {
1037
+ if (isNil(props.groupBy)) return defaultGroupSymbol;
1038
+ if (typeof props.groupBy === 'function') return props.groupBy(option.item);
1039
+ return (get as any)(option.item, props.groupBy);
1040
+ };
1041
+
1042
+ const groupedOptions = computed<GroupedOptions[]>(() => {
1043
+ const groupIndex: Record<GroupedOptions['id'], GroupedOptions> = {};
1044
+ for (const option of filteredOptions.value) {
1045
+ const groupData = getGroupData(option);
1046
+ const groupId = hash(groupData);
1047
+ if (!groupIndex[groupId]) {
1048
+ groupIndex[groupId] = {
1049
+ id: groupId,
1050
+ data: groupData ?? null,
1051
+ options: [],
1052
+ };
1053
+ }
1054
+ groupIndex[groupId].options.push(option);
1055
+ }
1056
+ return Object.values(groupIndex).filter((g) => g.options.length);
1057
+ });
1058
+
1059
+ /**
1060
+ * START POPOVER STATE
1061
+ */
1062
+ const shown = ref(false);
1063
+ const renderListBox = useUntil(shown);
1064
+
1065
+ const open = async ({
1066
+ focusPopoverContent = true,
1067
+ }: { focusPopoverContent?: boolean } = {}) => {
1068
+ if (props.disabled || props.readonly) return;
1069
+ if (shown.value) return;
1070
+ hasInteracted.value = true;
1071
+ resume();
1072
+ shown.value = true;
1073
+
1074
+ if (!hasPrefilled.value && !isPrefilling.value) {
1075
+ await prefillItems();
1076
+ }
1077
+
1078
+ await nextTick();
1079
+ document.addEventListener('click', onOutsideInteraction);
1080
+ document.addEventListener('focusin', onOutsideInteraction);
1081
+
1082
+ if (!focusPopoverContent) return;
1083
+
1084
+ if (computedAllowWriting.value) {
1085
+ await wait(0);
1086
+ focusSearchInput();
1087
+ } else {
1088
+ await wait(0);
1089
+ focusPopoverFocusProxy();
1090
+ }
1091
+ };
1092
+
1093
+ const close = async () => {
1094
+ if (!shown.value) return;
1095
+ shown.value = false;
1096
+ optionsContainer.value?.blur();
1097
+ activeDescendantId.value = undefined;
1098
+ pause();
1099
+ document.removeEventListener('click', onOutsideInteraction);
1100
+ document.removeEventListener('focusin', onOutsideInteraction);
1101
+ query.value = '';
1102
+ };
1103
+
1104
+ const toggle = () => {
1105
+ if (shown.value) return close();
1106
+ return open();
1107
+ };
1108
+ /**
1109
+ * END POPOVER STATE
1110
+ */
1111
+
1112
+ /**
1113
+ * START ACTIVE STATE
1114
+ * Tracks whether the user is inside this whole component.
1115
+ * We cannot use :focus-within as it doesn't work for teleported elements.
1116
+ */
1117
+ const active = ref(false);
1118
+
1119
+ const setActive = () => {
1120
+ active.value = true;
1121
+ };
1122
+ const setInactive = () => {
1123
+ if (!active.value) return;
1124
+ active.value = false;
1125
+ emit('inactive');
1126
+ };
1127
+
1128
+ const isFocusInsideComponent = (target: EventTarget | null) => {
1129
+ if (!(target instanceof Node)) return false;
1130
+ const insideActivator = !!activatorEl.value?.contains(target);
1131
+ const insidePopover = !!optionsContainer.value?.$el
1132
+ ?.closest('.bb-common-popover')
1133
+ ?.contains(target);
1134
+ return insideActivator || insidePopover;
1135
+ };
1136
+
1137
+ const syncActiveStateOnBlur = () => {
1138
+ setTimeout(() => {
1139
+ if (isFocusInsideComponent(document.activeElement)) {
1140
+ setActive();
1141
+ return;
1142
+ }
1143
+ setInactive();
1144
+ }, 0);
1145
+ };
1146
+ /**
1147
+ * END ACTIVE STATE
1148
+ */
1149
+
1150
+ const onClear = () => {
1151
+ emit('update:modelValue', props.multiple ? [] : null);
1152
+ };
1153
+
1154
+ /**
1155
+ * START ACTIVATOR LISTENERS
1156
+ * Programmatically attach accessibility attributes and event listeners
1157
+ * to the activator element, following the BbDropdown pattern.
1158
+ */
1159
+ let cleanupActivator: (() => void) | null = null;
1160
+
1161
+ watch(
1162
+ activatorEl,
1163
+ (el) => {
1164
+ if (cleanupActivator) {
1165
+ cleanupActivator();
1166
+ cleanupActivator = null;
1167
+ }
1168
+ if (!el) return;
1169
+
1170
+ el.setAttribute('role', 'combobox');
1171
+ el.setAttribute('aria-haspopup', 'listbox');
1172
+
1173
+ const handleClick = (event: MouseEvent) => {
1174
+ emit('click', event);
1175
+ if (props.disabled || props.readonly) return;
1176
+ event.preventDefault();
1177
+ toggle();
1178
+ };
1179
+
1180
+ const handleKeydown = (event: KeyboardEvent) => {
1181
+ switch (event.key) {
1182
+ case 'ArrowDown':
1183
+ event.preventDefault();
1184
+ onArrowDown();
1185
+ break;
1186
+ case 'ArrowUp':
1187
+ event.preventDefault();
1188
+ onArrowUp();
1189
+ break;
1190
+ case 'Enter':
1191
+ event.preventDefault();
1192
+ onEnter();
1193
+ break;
1194
+ case 'Escape':
1195
+ if (shown.value) {
1196
+ event.preventDefault();
1197
+ onEscape();
1198
+ }
1199
+ break;
1200
+ }
1201
+ };
1202
+
1203
+ const handleFocus = (event: FocusEvent) => {
1204
+ emit('focus', event);
1205
+ setActive();
1206
+ };
1207
+
1208
+ const handleBlur = (event: FocusEvent) => {
1209
+ emit('blur', event);
1210
+ syncActiveStateOnBlur();
1211
+ };
1212
+
1213
+ el.addEventListener('click', handleClick);
1214
+ el.addEventListener('keydown', handleKeydown);
1215
+ el.addEventListener('focus', handleFocus);
1216
+ el.addEventListener('blur', handleBlur);
1217
+
1218
+ cleanupActivator = () => {
1219
+ el.removeEventListener('click', handleClick);
1220
+ el.removeEventListener('keydown', handleKeydown);
1221
+ el.removeEventListener('focus', handleFocus);
1222
+ el.removeEventListener('blur', handleBlur);
1223
+ el.removeAttribute('role');
1224
+ el.removeAttribute('aria-haspopup');
1225
+ el.removeAttribute('aria-expanded');
1226
+ el.removeAttribute('aria-controls');
1227
+ el.removeAttribute('aria-invalid');
1228
+ el.removeAttribute('aria-describedby');
1229
+ el.removeAttribute('aria-activedescendant');
1230
+ };
1231
+ },
1232
+ { immediate: true }
1233
+ );
1234
+
1235
+ watch(
1236
+ [
1237
+ activatorEl,
1238
+ shown,
1239
+ renderListBox,
1240
+ computedAriaInvalid,
1241
+ computedAriaDescribedby,
1242
+ activeDescendantId,
1243
+ ] as const,
1244
+ ([el, isShown, hasListBox, ariaInvalid, ariaDescribedby, descendantId]) => {
1245
+ if (!el) return;
1246
+ el.setAttribute('aria-expanded', String(isShown));
1247
+ if (ariaInvalid != null) {
1248
+ el.setAttribute('aria-invalid', ariaInvalid);
1249
+ } else {
1250
+ el.removeAttribute('aria-invalid');
1251
+ }
1252
+ if (hasListBox) {
1253
+ el.setAttribute('aria-controls', `${id}_listbox`);
1254
+ } else {
1255
+ el.removeAttribute('aria-controls');
1256
+ }
1257
+ if (ariaDescribedby != null) {
1258
+ el.setAttribute('aria-describedby', ariaDescribedby);
1259
+ } else {
1260
+ el.removeAttribute('aria-describedby');
1261
+ }
1262
+ if (isShown && descendantId) {
1263
+ el.setAttribute('aria-activedescendant', descendantId);
1264
+ } else {
1265
+ el.removeAttribute('aria-activedescendant');
1266
+ }
1267
+ },
1268
+ { immediate: true }
1269
+ );
1270
+
1271
+ onBeforeUnmount(() => {
1272
+ cleanupActivator?.();
1273
+ document.removeEventListener('click', onOutsideInteraction);
1274
+ document.removeEventListener('focusin', onOutsideInteraction);
1275
+ });
1276
+ /**
1277
+ * END ACTIVATOR LISTENERS
1278
+ */
1279
+ </script>
1280
+
1281
+ <style lang="postcss">
1282
+ .bb-select-popover {
1283
+ width: var(--bb-select-popover-width, 320px);
1284
+
1285
+ .bb-select-popover__search-container {
1286
+ align-items: center;
1287
+ border-bottom: 1px solid var(--bb-border);
1288
+ display: flex;
1289
+ padding: 0.375rem 0.75rem;
1290
+ position: relative;
1291
+ }
1292
+
1293
+ .bb-select-popover__search-icon,
1294
+ .bb-select-popover__search-spinner,
1295
+ .bb-select-popover__search-error-icon {
1296
+ color: var(--bb-icon-color);
1297
+ flex-shrink: 0;
1298
+ width: var(--bb-input-icon);
1299
+ position: absolute;
1300
+ right: 0.75rem;
1301
+ top: 50%;
1302
+ transform: translateY(-50%);
1303
+ opacity: 0;
1304
+ transition:
1305
+ color 0.3s,
1306
+ opacity 0.3s;
1307
+ pointer-events: none;
1308
+ }
1309
+
1310
+ .bb-select-popover__search-input {
1311
+ background-color: transparent;
1312
+ border: 0;
1313
+ color: var(--bb-input-color);
1314
+ flex: 1 1 auto;
1315
+ font-size: var(--bb-input-font-size);
1316
+ line-height: var(--bb-leading);
1317
+ outline: none;
1318
+ padding: 0;
1319
+ padding-right: calc(var(--bb-input-icon) + 0.5rem);
1320
+ width: 100%;
1321
+ }
1322
+
1323
+ .bb-select-popover__search-icon {
1324
+ opacity: 1;
1325
+ }
1326
+
1327
+ &.bb-select-popover--errors {
1328
+ .bb-select-popover__search-error-icon {
1329
+ color: var(--bb-danger);
1330
+ opacity: 1;
1331
+ }
1332
+
1333
+ .bb-select-popover__search-icon,
1334
+ .bb-select-popover__search-spinner {
1335
+ opacity: 0;
1336
+ }
1337
+ }
1338
+
1339
+ &.bb-select-popover--loading {
1340
+ .bb-select-popover__search-spinner {
1341
+ opacity: 1;
1342
+ }
1343
+
1344
+ .bb-select-popover__search-icon,
1345
+ .bb-select-popover__search-error-icon {
1346
+ opacity: 0;
1347
+ }
1348
+ }
1349
+
1350
+ .bb-listbox {
1351
+ --option-h: var(--option-height);
1352
+ display: block;
1353
+
1354
+ &.bb-listbox--open {
1355
+ transition: min-height 0s 0s;
1356
+
1357
+ .bb-listbox__outer-container {
1358
+ grid-template-rows: 1fr;
1359
+ opacity: 1;
1360
+ transition-delay: 0s, 00ms;
1361
+
1362
+ > * {
1363
+ opacity: 1;
1364
+ transition-delay: calc(var(--transition-duration) * 0.8);
1365
+ }
1366
+ }
1367
+ }
1368
+ &.bb-listbox--no-data {
1369
+ .bb-listbox__outer-container {
1370
+ .bb-listbox__inner-container {
1371
+ .bb-listbox__no-data {
1372
+ display: block;
1373
+ }
1374
+ }
1375
+ }
1376
+ }
1377
+ &.bb-listbox--loading {
1378
+ .bb-listbox__outer-container {
1379
+ .bb-listbox__inner-container {
1380
+ .bb-listbox__loading {
1381
+ display: block;
1382
+ text-align: left;
1383
+ }
1384
+ .bb-listbox__loading {
1385
+ display: hidden;
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1390
+
1391
+ &.bb-listbox--multiple {
1392
+ .autocomplete-option__checkbox {
1393
+ display: inline-block !important;
1394
+ }
1395
+ }
1396
+
1397
+ .bb-listbox__outer-container {
1398
+ background-color: var(--bb-panel);
1399
+ border-color: var(--bb-primary);
1400
+ border-radius: var(--bb-radius);
1401
+ border-width: 0px;
1402
+ display: grid;
1403
+ grid-template-rows: 0fr;
1404
+ opacity: 0;
1405
+ transition-delay: calc(var(--transition-duration) * 0.25), 0s, 0s;
1406
+ transition-duration:
1407
+ var(--transition-duration), calc(var(--transition-duration) * 1.4),
1408
+ var(--transition-duration);
1409
+ transition-property: grid-template-rows, opacity, border;
1410
+
1411
+ > * {
1412
+ opacity: 0;
1413
+ transition-delay: 0s;
1414
+ transition-duration: calc(var(--transition-duration) * 2);
1415
+ transition-property: opacity;
1416
+ }
1417
+
1418
+ .bb-listbox__inner-container {
1419
+ overflow: auto;
1420
+ overflow-x: clip;
1421
+ text-align: left;
1422
+ /* Always show options up to the bottom of the screen, but not more than 6 options */
1423
+ max-height: min(
1424
+ calc(var(--option-h) * 6),
1425
+ calc(round(down, calc(100dvh - 130px), var(--option-h)) - 1px)
1426
+ );
1427
+
1428
+ .bb-listbox__loading,
1429
+ .bb-listbox__no-data {
1430
+ color: var(--bb-text);
1431
+ display: none;
1432
+ padding-bottom: var(--bb-select-option-py);
1433
+ padding-left: var(--bb-select-option-px);
1434
+ padding-right: var(--bb-select-option-px);
1435
+ padding-top: var(--bb-select-option-py);
1436
+ }
1437
+
1438
+ span[role='listbox'] {
1439
+ position: relative;
1440
+ width: 100%;
1441
+ display: block;
1442
+
1443
+ .bb-listbox__group {
1444
+ left: 0;
1445
+ position: absolute;
1446
+ top: 0;
1447
+ width: 100%;
1448
+ display: block;
1449
+ }
1450
+
1451
+ .bb-listbox__group-header {
1452
+ display: flex;
1453
+ align-items: center;
1454
+ height: var(--group-header-height, 32px);
1455
+ padding-left: var(--bb-select-option-px);
1456
+ padding-right: var(--bb-select-option-px);
1457
+
1458
+ user-select: none;
1459
+
1460
+ .bb-listbox__group-header-label {
1461
+ display: flex;
1462
+ align-items: center;
1463
+ gap: 16px;
1464
+ flex: auto;
1465
+ text-align: left;
1466
+ overflow: hidden;
1467
+ text-overflow: ellipsis;
1468
+ white-space: nowrap;
1469
+ color: color-mix(in srgb, var(--bb-text) 50%, transparent);
1470
+ font-size: 0.75rem;
1471
+ font-weight: 600;
1472
+ letter-spacing: 0.025em;
1473
+ text-transform: uppercase;
1474
+
1475
+ &::after {
1476
+ content: '';
1477
+ display: block;
1478
+ width: 100%;
1479
+ height: 1px;
1480
+ background-color: var(--bb-border);
1481
+ }
1482
+ }
1483
+ }
1484
+
1485
+ .bb-listbox__option {
1486
+ left: 0;
1487
+ position: absolute;
1488
+ display: block;
1489
+ top: 0;
1490
+ width: 100%;
1491
+ }
1492
+
1493
+ .bb-listbox__group > .bb-listbox__option {
1494
+ position: static;
1495
+ }
1496
+
1497
+ .autocomplete-option {
1498
+ --bg-opacity: 0;
1499
+ --bg-base-color: var(--bb-text);
1500
+ align-items: center;
1501
+ background-color: color-mix(
1502
+ in srgb,
1503
+ var(--bg-base-color) calc(100% * var(--bg-opacity)),
1504
+ transparent
1505
+ );
1506
+ color: var(--bb-text);
1507
+ cursor: pointer;
1508
+ display: flex;
1509
+ flex-grow: 1;
1510
+ gap: 8px;
1511
+ height: var(--option-h);
1512
+ padding-left: var(--bb-select-option-px);
1513
+ padding-right: var(--bb-select-option-px);
1514
+ transition-duration: 250ms;
1515
+ transition-property: color, background-color;
1516
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1517
+ width: 100%;
1518
+
1519
+ &:hover:not(.autocomplete-option--disabled) {
1520
+ --bg-opacity: 0.05;
1521
+ --bg-base-color: var(--bb-text);
1522
+ }
1523
+
1524
+ &--focused {
1525
+ --bg-opacity: 0.15;
1526
+ --bg-base-color: var(--bb-text);
1527
+ }
1528
+
1529
+ &--focused:hover:not(.autocomplete-option--disabled) {
1530
+ --bg-opacity: 0.2;
1531
+ }
1532
+
1533
+ &--selected {
1534
+ --bg-opacity: 0.1;
1535
+ --bg-base-color: var(--bb-primary) !important;
1536
+ color: var(--bb-primary);
1537
+ }
1538
+
1539
+ &--selected:hover:not(.autocomplete-option--disabled) {
1540
+ --bg-opacity: 0.15;
1541
+ }
1542
+
1543
+ &--selected.autocomplete-option--focused {
1544
+ --bg-opacity: 0.2;
1545
+ }
1546
+
1547
+ &--selected.autocomplete-option--focused:hover:not(
1548
+ .autocomplete-option--disabled
1549
+ ) {
1550
+ --bg-opacity: 0.2;
1551
+ }
1552
+
1553
+ &--disabled {
1554
+ cursor: not-allowed;
1555
+ opacity: 0.5;
1556
+
1557
+ .autocomplete-option__checkbox.autocomplete-option__checkbox {
1558
+ background-color: var(--bb-panel-disabled);
1559
+ }
1560
+ }
1561
+
1562
+ .autocomplete-option__checkbox {
1563
+ pointer-events: none;
1564
+ display: none;
1565
+ width: 16px;
1566
+ height: 16px;
1567
+ background-color: var(--bb-panel);
1568
+ border-color: var(--bb-border);
1569
+ border-radius: min(6px, var(--bb-radius));
1570
+ border-style: solid;
1571
+ border-width: 1px;
1572
+ margin-left: -6px;
1573
+ transition-duration: 300ms;
1574
+ transition-property:
1575
+ color, background-color, border-color, text-decoration-color,
1576
+ box-shadow;
1577
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1578
+
1579
+ path {
1580
+ stroke: var(--bb-contrasting);
1581
+ stroke-dasharray: 105;
1582
+ stroke-dashoffset: 105;
1583
+ transition: stroke-dashoffset 0.3s;
1584
+ }
1585
+ }
1586
+
1587
+ .autocomplete-option__label {
1588
+ display: block;
1589
+ flex: auto;
1590
+ text-align: left;
1591
+ overflow: hidden;
1592
+ text-overflow: ellipsis;
1593
+ white-space: nowrap;
1594
+ }
1595
+
1596
+ &.autocomplete-option--selected {
1597
+ .autocomplete-option__checkbox {
1598
+ background-color: var(--bb-primary);
1599
+ border-color: var(--bb-primary);
1600
+
1601
+ path {
1602
+ stroke-dashoffset: 0;
1603
+ }
1604
+ }
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+ }
1610
+ }
1611
+ }
1612
+ </style>
1613
+ ```
1614
+
1615
+ ## Types
1616
+
1617
+ ```ts
1618
+ import type { HTMLAttributes, InputHTMLAttributes } from 'vue';
1619
+ import type { CommonProps } from '@/types/CommonProps';
1620
+
1621
+ export type BbSelectPopoverProps<Item> = {
1622
+ /**
1623
+ * External activator element or component ref.
1624
+ * When provided, the activator slot is not rendered and event listeners
1625
+ * are attached programmatically to the referenced element.
1626
+ */
1627
+ activator?: HTMLElement | Record<string, any> | null;
1628
+
1629
+ /**
1630
+ * Controls whether the search input is shown inside the popover panel.
1631
+ * Use `'not-mobile'` to hide the search input on mobile.
1632
+ *
1633
+ * @defaultValue `false`
1634
+ */
1635
+ allowWriting?: boolean | 'not-mobile';
1636
+
1637
+ /**
1638
+ * Browser autocomplete hint for the popover search input.
1639
+ *
1640
+ * @defaultValue `'off'`
1641
+ */
1642
+ autocomplete?: InputHTMLAttributes['autocomplete'];
1643
+
1644
+ /**
1645
+ * Apply the compact density variant.
1646
+ */
1647
+ compact?: boolean;
1648
+
1649
+ /**
1650
+ * Space the arrow keeps from popover edges. Useful with rounded corners.
1651
+ *
1652
+ * @defaultValue `10`
1653
+ */
1654
+ arrowPadding?: CommonProps['arrowPadding'];
1655
+
1656
+ /**
1657
+ * Constrains the popover to a specific boundary element.
1658
+ */
1659
+ boundary?: HTMLElement | Record<string, any> | string | null;
1660
+
1661
+ /**
1662
+ * Additional dependencies that trigger item reloading when changed.
1663
+ *
1664
+ * @defaultValue `[]`
1665
+ */
1666
+ dependencies?: any[];
1667
+
1668
+ /**
1669
+ * Debounce delay (ms) for dependency-triggered reloads.
1670
+ *
1671
+ * @defaultValue `0`
1672
+ */
1673
+ depsDebounceTime?: number;
1674
+
1675
+ /**
1676
+ * Disables the component.
1677
+ */
1678
+ disabled?: boolean;
1679
+
1680
+ /**
1681
+ * Marks the component as being in error state.
1682
+ */
1683
+ hasErrors?: boolean;
1684
+
1685
+ /**
1686
+ * Explicit invalid state for accessibility semantics.
1687
+ * Falls back to `hasErrors` when not provided.
1688
+ */
1689
+ ariaInvalid?: boolean;
1690
+
1691
+ /**
1692
+ * Space-separated IDREF(s) that describe this field for assistive technologies.
1693
+ * Accepts either a string or array of ids.
1694
+ */
1695
+ ariaDescribedby?: string | string[];
1696
+
1697
+ /**
1698
+ * Enable or disable floating flip behavior.
1699
+ *
1700
+ * @defaultValue `true`
1701
+ */
1702
+ flip?: boolean;
1703
+
1704
+ /**
1705
+ * Whether the floating arrow is hidden.
1706
+ *
1707
+ * @defaultValue `true`
1708
+ */
1709
+ hideArrow?: boolean;
1710
+
1711
+ /**
1712
+ * Reset modelValue to `null` or empty array if it no longer matches available items.
1713
+ */
1714
+ enforceCoherence?: boolean;
1715
+
1716
+ /**
1717
+ * Controls how options are filtered during search.
1718
+ *
1719
+ * @defaultValue `() => []`
1720
+ */
1721
+ filterBy?:
1722
+ | string
1723
+ | string[]
1724
+ | false
1725
+ | 'not_stashed'
1726
+ | ((value: any, item: any, query: string | null) => boolean)
1727
+ | null;
1728
+
1729
+ /** Path to item property for grouping options. */
1730
+ groupBy?: string | ((item: Item) => string | number | symbol);
1731
+
1732
+ /**
1733
+ * Height of group headers in the listbox (px). Defaults to 24px (compact) or 32px.
1734
+ * Only applies when `groupBy` is set.
1735
+ */
1736
+ headerHeight?: number;
1737
+
1738
+ /**
1739
+ * Explicit id for the component. Used to generate ids for listbox and options.
1740
+ */
1741
+ id?: HTMLAttributes['id'];
1742
+
1743
+ /**
1744
+ * Array of items or function to load them asynchronously.
1745
+ * Functions receive `(query, prefill, modelValue)` and can return a promise.
1746
+ *
1747
+ * @defaultValue `[]`
1748
+ */
1749
+ items:
1750
+ | Item[]
1751
+ | ((query: string, prefill: boolean, modelValue: any[]) => Promise<Item[]>)
1752
+ | ((query: string, prefill: boolean, modelValue: any[]) => Item[]);
1753
+
1754
+ /**
1755
+ * Height of the options in the listbox (px).
1756
+ *
1757
+ * @defaultValue `40`
1758
+ */
1759
+ itemHeight?: number;
1760
+
1761
+ /**
1762
+ * Path to item property for display text or function to extract it.
1763
+ */
1764
+ itemText?: string | ((item: Item) => string) | undefined;
1765
+
1766
+ /**
1767
+ * Path to item property for value or function to extract it.
1768
+ */
1769
+ itemValue?: string | ((item: Item) => string) | undefined;
1770
+
1771
+ /**
1772
+ * Display the loading state styles.
1773
+ */
1774
+ loading?: boolean;
1775
+
1776
+ /**
1777
+ * Message shown while items are being loaded.
1778
+ */
1779
+ loadingText?: string;
1780
+
1781
+ /**
1782
+ * Maximum number of selectable items (limits selection when `multiple` is true).
1783
+ *
1784
+ * @defaultValue `Infinity`
1785
+ */
1786
+ max?: number;
1787
+
1788
+ /**
1789
+ * v-model value. Single value for single select, array for multiple select.
1790
+ */
1791
+ modelValue: any;
1792
+
1793
+ /**
1794
+ * Debounce delay (ms) for modelValue change handling.
1795
+ *
1796
+ * @defaultValue `0`
1797
+ */
1798
+ modelValueDebounceTime?: number;
1799
+
1800
+ /**
1801
+ * Enable multiple item selection. When true, modelValue must be an array.
1802
+ */
1803
+ multiple?: boolean;
1804
+
1805
+ /**
1806
+ * Message displayed when no items are available.
1807
+ */
1808
+ noDataText?: string;
1809
+
1810
+ /**
1811
+ * Distance (px) between the activator and the popover.
1812
+ *
1813
+ * @defaultValue `4`
1814
+ */
1815
+ offset?: number;
1816
+
1817
+ /**
1818
+ * Minimum page padding used by floating shift middleware.
1819
+ *
1820
+ * @defaultValue `0`
1821
+ */
1822
+ padding?: CommonProps['padding'];
1823
+
1824
+ /**
1825
+ * Floating placement of the popover.
1826
+ *
1827
+ * @defaultValue `'bottom'`
1828
+ */
1829
+ placement?: CommonProps['placement'];
1830
+
1831
+ /**
1832
+ * Controls when items are pre-loaded.
1833
+ * `'interaction'` loads on first user interaction, `true` loads immediately,
1834
+ * `false` loads only on explicit search/open flows.
1835
+ *
1836
+ * @defaultValue `'interaction'`
1837
+ */
1838
+ prefill?: boolean | 'interaction';
1839
+
1840
+ /**
1841
+ * Debounce delay (ms) before triggering search queries after user input stops.
1842
+ *
1843
+ * @defaultValue `500`
1844
+ */
1845
+ queryDebounceTime?: number;
1846
+
1847
+ /**
1848
+ * Prevents opening the popover while keeping the activator accessible.
1849
+ */
1850
+ readonly?: boolean;
1851
+
1852
+ /**
1853
+ * Accessible label used by the search input rendered inside the popover.
1854
+ *
1855
+ * @defaultValue `'Search options'`
1856
+ */
1857
+ searchInputAriaLabel?: string;
1858
+
1859
+ /**
1860
+ * Accumulate selected items across searches instead of replacing them.
1861
+ */
1862
+ stash?: boolean;
1863
+
1864
+ /**
1865
+ * Transition duration (ms) for popover show/hide animations.
1866
+ *
1867
+ * @defaultValue `300`
1868
+ */
1869
+ transitionDuration?: number;
1870
+
1871
+ /**
1872
+ * Width of the popover in pixels, percentage, or `'auto'`.
1873
+ */
1874
+ width?: number | string;
1875
+ };
1876
+
1877
+ export type BbSelectPopoverEvents = {
1878
+ /**
1879
+ * Emitted when the activator loses focus.
1880
+ */
1881
+ (e: 'blur', event: FocusEvent): void;
1882
+ /**
1883
+ * Emitted when the value changes via native change event.
1884
+ */
1885
+ (e: 'change', event: Event): void;
1886
+ /**
1887
+ * Emitted when the activator is clicked.
1888
+ */
1889
+ (e: 'click', event: MouseEvent): void;
1890
+ /**
1891
+ * Emitted when the activator receives focus.
1892
+ */
1893
+ (e: 'focus', event: FocusEvent): void;
1894
+ /**
1895
+ * Emitted when focus moves outside the entire component after it was active.
1896
+ */
1897
+ (e: 'inactive'): void;
1898
+ /**
1899
+ * Emitted on native input events from the search input.
1900
+ */
1901
+ (e: 'input', event: Event): void;
1902
+ /**
1903
+ * Emitted with the next selection value whenever selection changes.
1904
+ */
1905
+ (e: 'update:modelValue', value: any): void;
1906
+ /**
1907
+ * Emitted when the user types a new value and confirms it (Enter key) when `allowWriting` is enabled.
1908
+ */
1909
+ (e: 'option:add', text: string): void;
1910
+ };
1911
+
1912
+ /** Props exposed by the `activator` slot. */
1913
+ export type BbSelectPopoverActivatorSlotProps = {
1914
+ /** Bind onto the activator element with `v-bind="props"` to wire up the popover reference. */
1915
+ props: {
1916
+ ref: (reference: unknown) => void;
1917
+ };
1918
+ /** Whether the popover is currently open. */
1919
+ shown: boolean;
1920
+ /** Whether the select is disabled. */
1921
+ disabled: boolean | undefined;
1922
+ /** Whether the select is read-only. */
1923
+ readonly: boolean | undefined;
1924
+ /** Whether options are currently being fetched. */
1925
+ loading: boolean;
1926
+ /** The current search query string. */
1927
+ query: string;
1928
+ /** Clears the current selection and query. */
1929
+ clear: () => void;
1930
+ /** Opens the popover. */
1931
+ open: () => Promise<void>;
1932
+ /** Closes the popover. */
1933
+ close: () => Promise<void>;
1934
+ /** Toggles the popover open/closed. */
1935
+ toggle: () => Promise<void>;
1936
+ };
1937
+
1938
+ /** Props exposed by the `option` slot. */
1939
+ export type BbSelectPopoverOptionSlotProps<Item> = {
1940
+ /** Whether this option is disabled. */
1941
+ disabled: boolean;
1942
+ /** Whether this option currently has keyboard focus. */
1943
+ focused: boolean;
1944
+ /** Zero-based index of this option in the list. */
1945
+ index: number;
1946
+ /** The raw item from the `items` prop. */
1947
+ item: Item;
1948
+ /** Whether items are still being fetched. */
1949
+ loading: boolean;
1950
+ /** Whether this option is currently selected. */
1951
+ selected: boolean;
1952
+ /** The resolved display text for this option. */
1953
+ text: string;
1954
+ /** The resolved value for this option. */
1955
+ value: any;
1956
+ };
1957
+
1958
+ /** Props exposed by the `group` slot. */
1959
+ export type BbSelectPopoverGroupSlotProps<Item> = {
1960
+ /** The display text for this group. */
1961
+ text: string;
1962
+ /** The raw group item. */
1963
+ item: Item;
1964
+ /** Zero-based index of the first option in this group. */
1965
+ index: number;
1966
+ /** Number of options in this group. */
1967
+ length: number;
1968
+ /** Whether all options in this group are disabled. */
1969
+ disabled: boolean;
1970
+ };
1971
+
1972
+ export type BbSelectPopoverSlots<Item> = {
1973
+ /**
1974
+ * Replaces the default trigger element that opens the select popover.
1975
+ */
1976
+ activator?: (props: BbSelectPopoverActivatorSlotProps) => any;
1977
+ /**
1978
+ * Content shown inside the options dropdown while items are loading.
1979
+ */
1980
+ loading?: (props: { query: string }) => any;
1981
+ /**
1982
+ * Content shown when no options match the query or the list is empty.
1983
+ */
1984
+ 'no-data'?: (props: { query: string; focus: () => void }) => any;
1985
+ /**
1986
+ * Replaces the default rendering of each option row in the dropdown.
1987
+ */
1988
+ option?: (props: BbSelectPopoverOptionSlotProps<Item>) => any;
1989
+ /**
1990
+ * Replaces the default group header rendered above grouped options.
1991
+ */
1992
+ group?: (props: BbSelectPopoverGroupSlotProps<Item>) => any;
1993
+ /**
1994
+ * Content appended inside the options list, after the last option.
1995
+ */
1996
+ 'options:append'?: (props: { focus: () => void }) => any;
1997
+ /**
1998
+ * Content appended outside the options list container.
1999
+ */
2000
+ 'options:append:outer'?: (props: { focus: () => void }) => any;
2001
+ /**
2002
+ * Content prepended inside the options list, before the first option.
2003
+ */
2004
+ 'options:prepend'?: (props: { focus: () => void }) => any;
2005
+ /**
2006
+ * Content prepended outside the options list container.
2007
+ */
2008
+ 'options:prepend:outer'?: (props: { focus: () => void }) => any;
2009
+ };
2010
+ ```