@rotki/ui-library 2.12.1 → 2.13.0

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 (287) hide show
  1. package/dist/components/accordions/accordion/RuiAccordion.vue.d.ts +6 -14
  2. package/dist/components/accordions/accordion/RuiAccordion.vue.js +3 -4
  3. package/dist/components/accordions/accordion/RuiAccordion.vue.js.map +1 -1
  4. package/dist/components/accordions/accordions/RuiAccordions.vue.d.ts +10 -9
  5. package/dist/components/accordions/accordions/RuiAccordions.vue.js +27 -26
  6. package/dist/components/accordions/accordions/RuiAccordions.vue.js.map +1 -1
  7. package/dist/components/alerts/RuiAlert.vue.d.ts +7 -17
  8. package/dist/components/alerts/RuiAlert.vue2.js +8 -16
  9. package/dist/components/alerts/RuiAlert.vue2.js.map +1 -1
  10. package/dist/components/buttons/button/RuiButton.vue.d.ts +5 -7
  11. package/dist/components/buttons/button/RuiButton.vue2.js +19 -20
  12. package/dist/components/buttons/button/RuiButton.vue2.js.map +1 -1
  13. package/dist/components/buttons/button-group/RuiButtonGroup.vue.d.ts +6 -5
  14. package/dist/components/buttons/button-group/RuiButtonGroup.vue2.js +42 -41
  15. package/dist/components/buttons/button-group/RuiButtonGroup.vue2.js.map +1 -1
  16. package/dist/components/calendar/RuiCalendar.vue.d.ts +1 -6
  17. package/dist/components/calendar/RuiCalendar.vue.js +7 -8
  18. package/dist/components/calendar/RuiCalendar.vue.js.map +1 -1
  19. package/dist/components/calendar/RuiCalendarGrid.vue.js +1 -1
  20. package/dist/components/calendar/RuiCalendarGrid.vue2.js +20 -18
  21. package/dist/components/calendar/RuiCalendarGrid.vue2.js.map +1 -1
  22. package/dist/components/calendar/RuiCalendarHeader.vue.js +1 -1
  23. package/dist/components/calendar/RuiCalendarHeader.vue2.js +3 -4
  24. package/dist/components/calendar/RuiCalendarHeader.vue2.js.map +1 -1
  25. package/dist/components/calendar/RuiCalendarMenu.vue.js +1 -1
  26. package/dist/components/calendar/RuiCalendarMenu.vue2.js +48 -40
  27. package/dist/components/calendar/RuiCalendarMenu.vue2.js.map +1 -1
  28. package/dist/components/cards/RuiCard.vue.d.ts +9 -24
  29. package/dist/components/cards/RuiCard.vue2.js +7 -7
  30. package/dist/components/cards/RuiCard.vue2.js.map +1 -1
  31. package/dist/components/cards/RuiCardHeader.vue.d.ts +5 -10
  32. package/dist/components/cards/RuiCardHeader.vue2.js +1 -1
  33. package/dist/components/cards/RuiCardHeader.vue2.js.map +1 -1
  34. package/dist/components/chips/RuiChip.vue.d.ts +7 -21
  35. package/dist/components/chips/RuiChip.vue2.js +16 -21
  36. package/dist/components/chips/RuiChip.vue2.js.map +1 -1
  37. package/dist/components/color-picker/RuiColorBoard.vue.d.ts +1 -1
  38. package/dist/components/color-picker/RuiColorBoard.vue2.js +19 -19
  39. package/dist/components/color-picker/RuiColorBoard.vue2.js.map +1 -1
  40. package/dist/components/color-picker/RuiColorDisplay.vue.js +3 -5
  41. package/dist/components/color-picker/RuiColorDisplay.vue.js.map +1 -1
  42. package/dist/components/color-picker/RuiColorHue.vue2.js +17 -14
  43. package/dist/components/color-picker/RuiColorHue.vue2.js.map +1 -1
  44. package/dist/components/color-picker/RuiColorInput.vue.js +9 -7
  45. package/dist/components/color-picker/RuiColorInput.vue.js.map +1 -1
  46. package/dist/components/color-picker/RuiColorPicker.vue.d.ts +7 -9
  47. package/dist/components/color-picker/RuiColorPicker.vue.js +23 -14
  48. package/dist/components/color-picker/RuiColorPicker.vue.js.map +1 -1
  49. package/dist/components/color-picker/utils.js +22 -5
  50. package/dist/components/color-picker/utils.js.map +1 -1
  51. package/dist/components/date-time-picker/RuiDateTimePicker.vue.d.ts +2 -44
  52. package/dist/components/date-time-picker/RuiDateTimePicker.vue2.js +75 -79
  53. package/dist/components/date-time-picker/RuiDateTimePicker.vue2.js.map +1 -1
  54. package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue.js +1 -1
  55. package/dist/components/date-time-picker/dayjs-setup.d.ts +1 -0
  56. package/dist/components/date-time-picker/dayjs-setup.js +8 -0
  57. package/dist/components/date-time-picker/dayjs-setup.js.map +1 -0
  58. package/dist/components/date-time-picker/segment-config.d.ts +14 -0
  59. package/dist/components/date-time-picker/segment-config.js +23 -0
  60. package/dist/components/date-time-picker/segment-config.js.map +1 -0
  61. package/dist/components/date-time-picker/segment-utils.d.ts +5 -0
  62. package/dist/components/date-time-picker/segment-utils.js +36 -0
  63. package/dist/components/date-time-picker/segment-utils.js.map +1 -0
  64. package/dist/components/date-time-picker/use-date-time-selection.d.ts +1 -0
  65. package/dist/components/date-time-picker/use-date-time-selection.js +1 -0
  66. package/dist/components/date-time-picker/use-date-time-selection.js.map +1 -1
  67. package/dist/components/date-time-picker/use-keyboard-handler.d.ts +4 -8
  68. package/dist/components/date-time-picker/use-keyboard-handler.js +9 -54
  69. package/dist/components/date-time-picker/use-keyboard-handler.js.map +1 -1
  70. package/dist/components/date-time-picker/utils.d.ts +1 -0
  71. package/dist/components/date-time-picker/utils.js +1 -0
  72. package/dist/components/date-time-picker/utils.js.map +1 -1
  73. package/dist/components/divider/RuiDivider.vue.d.ts +1 -3
  74. package/dist/components/divider/RuiDivider.vue.js +6 -3
  75. package/dist/components/divider/RuiDivider.vue.js.map +1 -1
  76. package/dist/components/forms/auto-complete/RuiAutoComplete.vue.d.ts +10 -1138
  77. package/dist/components/forms/auto-complete/RuiAutoComplete.vue2.js +153 -160
  78. package/dist/components/forms/auto-complete/RuiAutoComplete.vue2.js.map +1 -1
  79. package/dist/components/forms/checkbox/RuiCheckbox.vue.d.ts +15 -24
  80. package/dist/components/forms/checkbox/RuiCheckbox.vue2.js +32 -32
  81. package/dist/components/forms/checkbox/RuiCheckbox.vue2.js.map +1 -1
  82. package/dist/components/forms/radio-button/radio/RuiRadio.vue.d.ts +2 -2
  83. package/dist/components/forms/radio-button/radio/RuiRadio.vue2.js +19 -22
  84. package/dist/components/forms/radio-button/radio/RuiRadio.vue2.js.map +1 -1
  85. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue.d.ts +4 -2
  86. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue2.js +13 -16
  87. package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue2.js.map +1 -1
  88. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue.d.ts +7 -13
  89. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue.js +10 -9
  90. package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue.js.map +1 -1
  91. package/dist/components/forms/select/RuiMenuSelect.vue.d.ts +10 -785
  92. package/dist/components/forms/select/RuiMenuSelect.vue2.js +60 -56
  93. package/dist/components/forms/select/RuiMenuSelect.vue2.js.map +1 -1
  94. package/dist/components/forms/select/RuiSimpleSelect.vue.d.ts +11 -10
  95. package/dist/components/forms/select/RuiSimpleSelect.vue2.js +21 -26
  96. package/dist/components/forms/select/RuiSimpleSelect.vue2.js.map +1 -1
  97. package/dist/components/forms/slider/RuiSlider.vue.d.ts +10 -26
  98. package/dist/components/forms/slider/RuiSlider.vue2.js +45 -48
  99. package/dist/components/forms/slider/RuiSlider.vue2.js.map +1 -1
  100. package/dist/components/forms/switch/RuiSwitch.vue.d.ts +12 -20
  101. package/dist/components/forms/switch/RuiSwitch.vue2.js +22 -21
  102. package/dist/components/forms/switch/RuiSwitch.vue2.js.map +1 -1
  103. package/dist/components/forms/text-area/RuiTextArea.vue.d.ts +5 -30
  104. package/dist/components/forms/text-area/RuiTextArea.vue2.js +47 -83
  105. package/dist/components/forms/text-area/RuiTextArea.vue2.js.map +1 -1
  106. package/dist/components/forms/text-field/RuiTextField.vue.d.ts +10 -30
  107. package/dist/components/forms/text-field/RuiTextField.vue2.js +36 -67
  108. package/dist/components/forms/text-field/RuiTextField.vue2.js.map +1 -1
  109. package/dist/components/helpers/RuiFormTextDetail.vue.d.ts +1 -5
  110. package/dist/components/helpers/RuiFormTextDetail.vue.js +5 -9
  111. package/dist/components/helpers/RuiFormTextDetail.vue.js.map +1 -1
  112. package/dist/components/icons/LazyIcon.vue.d.ts +1 -3
  113. package/dist/components/icons/RuiIcon.vue.d.ts +1 -4
  114. package/dist/components/icons/RuiIcon.vue2.js +11 -14
  115. package/dist/components/icons/RuiIcon.vue2.js.map +1 -1
  116. package/dist/components/index.d.ts +4 -4
  117. package/dist/components/loaders/RuiSkeletonBase.vue.d.ts +1 -3
  118. package/dist/components/loaders/RuiSkeletonBase.vue2.js +2 -2
  119. package/dist/components/loaders/RuiSkeletonBase.vue2.js.map +1 -1
  120. package/dist/components/loaders/RuiSkeletonLoader.vue.d.ts +1 -4
  121. package/dist/components/loaders/RuiSkeletonLoader.vue2.js +2 -2
  122. package/dist/components/loaders/RuiSkeletonLoader.vue2.js.map +1 -1
  123. package/dist/components/logos/RuiLogo.vue.d.ts +1 -7
  124. package/dist/components/logos/RuiLogo.vue.js +13 -13
  125. package/dist/components/logos/RuiLogo.vue.js.map +1 -1
  126. package/dist/components/overlays/badge/RuiBadge.vue.d.ts +5 -20
  127. package/dist/components/overlays/badge/RuiBadge.vue2.js +11 -13
  128. package/dist/components/overlays/badge/RuiBadge.vue2.js.map +1 -1
  129. package/dist/components/overlays/bottom-sheet/RuiBottomSheet.vue.d.ts +10 -5
  130. package/dist/components/overlays/bottom-sheet/RuiBottomSheet.vue.js +18 -7
  131. package/dist/components/overlays/bottom-sheet/RuiBottomSheet.vue.js.map +1 -1
  132. package/dist/components/overlays/dialog/RuiDialog.vue.d.ts +22 -27
  133. package/dist/components/overlays/dialog/RuiDialog.vue2.js +90 -67
  134. package/dist/components/overlays/dialog/RuiDialog.vue2.js.map +1 -1
  135. package/dist/components/overlays/menu/RuiMenu.vue.d.ts +24 -35
  136. package/dist/components/overlays/menu/RuiMenu.vue2.js +71 -78
  137. package/dist/components/overlays/menu/RuiMenu.vue2.js.map +1 -1
  138. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue.d.ts +23 -30
  139. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue2.js +78 -49
  140. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue2.js.map +1 -1
  141. package/dist/components/overlays/notification/RuiNotification.vue.d.ts +12 -12
  142. package/dist/components/overlays/notification/RuiNotification.vue.js +27 -22
  143. package/dist/components/overlays/notification/RuiNotification.vue.js.map +1 -1
  144. package/dist/components/overlays/tooltip/RuiTooltip.vue.d.ts +7 -18
  145. package/dist/components/overlays/tooltip/RuiTooltip.vue2.js +16 -9
  146. package/dist/components/overlays/tooltip/RuiTooltip.vue2.js.map +1 -1
  147. package/dist/components/progress/RuiProgress.vue.d.ts +8 -10
  148. package/dist/components/progress/RuiProgress.vue2.js +18 -24
  149. package/dist/components/progress/RuiProgress.vue2.js.map +1 -1
  150. package/dist/components/steppers/RuiFooterStepper.vue.d.ts +10 -11
  151. package/dist/components/steppers/RuiFooterStepper.vue2.js +33 -26
  152. package/dist/components/steppers/RuiFooterStepper.vue2.js.map +1 -1
  153. package/dist/components/steppers/RuiStepper.vue.d.ts +6 -15
  154. package/dist/components/steppers/RuiStepper.vue2.js +39 -36
  155. package/dist/components/steppers/RuiStepper.vue2.js.map +1 -1
  156. package/dist/components/steppers/RuiStepperCustomIcon.vue.d.ts +1 -3
  157. package/dist/components/steppers/RuiStepperCustomIcon.vue2.js +2 -2
  158. package/dist/components/steppers/RuiStepperCustomIcon.vue2.js.map +1 -1
  159. package/dist/components/steppers/RuiStepperIcon.vue.d.ts +1 -3
  160. package/dist/components/steppers/RuiStepperIcon.vue2.js +2 -2
  161. package/dist/components/steppers/RuiStepperIcon.vue2.js.map +1 -1
  162. package/dist/components/tables/RuiDataTable.vue.d.ts +29 -81
  163. package/dist/components/tables/RuiDataTable.vue2.js +196 -601
  164. package/dist/components/tables/RuiDataTable.vue2.js.map +1 -1
  165. package/dist/components/tables/RuiDataTable.vue3.js +24 -27
  166. package/dist/components/tables/RuiDataTable.vue3.js.map +1 -1
  167. package/dist/components/tables/RuiExpandButton.vue.d.ts +3 -6
  168. package/dist/components/tables/RuiExpandButton.vue2.js +4 -2
  169. package/dist/components/tables/RuiExpandButton.vue2.js.map +1 -1
  170. package/dist/components/tables/RuiTableHead.vue.d.ts +4 -7
  171. package/dist/components/tables/RuiTableHead.vue2.js +20 -20
  172. package/dist/components/tables/RuiTableHead.vue2.js.map +1 -1
  173. package/dist/components/tables/RuiTablePagination.vue.d.ts +10 -10
  174. package/dist/components/tables/RuiTablePagination.vue2.js +27 -19
  175. package/dist/components/tables/RuiTablePagination.vue2.js.map +1 -1
  176. package/dist/components/tabs/tab/RuiTab.vue.d.ts +3 -18
  177. package/dist/components/tabs/tab/RuiTab.vue2.js +84 -52
  178. package/dist/components/tabs/tab/RuiTab.vue2.js.map +1 -1
  179. package/dist/components/tabs/tab-item/RuiTabItem.vue.d.ts +3 -11
  180. package/dist/components/tabs/tab-item/RuiTabItem.vue2.js +3 -2
  181. package/dist/components/tabs/tab-item/RuiTabItem.vue2.js.map +1 -1
  182. package/dist/components/tabs/tab-items/RuiTabItems.vue.d.ts +2 -5
  183. package/dist/components/tabs/tab-items/RuiTabItems.vue2.js +18 -18
  184. package/dist/components/tabs/tab-items/RuiTabItems.vue2.js.map +1 -1
  185. package/dist/components/tabs/tabs/RuiTabs.vue.d.ts +10 -14
  186. package/dist/components/tabs/tabs/RuiTabs.vue2.js +101 -94
  187. package/dist/components/tabs/tabs/RuiTabs.vue2.js.map +1 -1
  188. package/dist/components/time-picker/RuiTimePicker.vue.d.ts +1 -4
  189. package/dist/components/time-picker/RuiTimePicker.vue2.js +88 -38
  190. package/dist/components/time-picker/RuiTimePicker.vue2.js.map +1 -1
  191. package/dist/composables/breakpoint.js +8 -13
  192. package/dist/composables/breakpoint.js.map +1 -1
  193. package/dist/composables/dropdown-menu.d.ts +7 -4
  194. package/dist/composables/dropdown-menu.js +73 -50
  195. package/dist/composables/dropdown-menu.js.map +1 -1
  196. package/dist/composables/forms/auto-complete/focus.d.ts +7 -5
  197. package/dist/composables/forms/auto-complete/focus.js +28 -17
  198. package/dist/composables/forms/auto-complete/focus.js.map +1 -1
  199. package/dist/composables/forms/auto-complete/index.d.ts +0 -9
  200. package/dist/composables/forms/auto-complete/keyboard-navigation.d.ts +8 -3
  201. package/dist/composables/forms/auto-complete/keyboard-navigation.js +11 -5
  202. package/dist/composables/forms/auto-complete/keyboard-navigation.js.map +1 -1
  203. package/dist/composables/forms/auto-complete/search.d.ts +7 -0
  204. package/dist/composables/forms/auto-complete/search.js +10 -8
  205. package/dist/composables/forms/auto-complete/search.js.map +1 -1
  206. package/dist/composables/forms/auto-complete/value.d.ts +4 -0
  207. package/dist/composables/forms/auto-complete/value.js +24 -42
  208. package/dist/composables/forms/auto-complete/value.js.map +1 -1
  209. package/dist/composables/forms/use-label-with-quote.d.ts +2 -0
  210. package/dist/composables/forms/use-label-with-quote.js +14 -0
  211. package/dist/composables/forms/use-label-with-quote.js.map +1 -0
  212. package/dist/composables/forms/use-prepend-append-width.d.ts +6 -0
  213. package/dist/composables/forms/use-prepend-append-width.js +25 -0
  214. package/dist/composables/forms/use-prepend-append-width.js.map +1 -0
  215. package/dist/composables/icons.js +1 -1
  216. package/dist/composables/icons.js.map +1 -1
  217. package/dist/composables/popper.js +63 -49
  218. package/dist/composables/popper.js.map +1 -1
  219. package/dist/composables/sticky-header.d.ts +7 -5
  220. package/dist/composables/sticky-header.js +51 -41
  221. package/dist/composables/sticky-header.js.map +1 -1
  222. package/dist/composables/tables/data-table/columns.d.ts +25 -0
  223. package/dist/composables/tables/data-table/columns.js +54 -0
  224. package/dist/composables/tables/data-table/columns.js.map +1 -0
  225. package/dist/composables/tables/data-table/expansion.d.ts +17 -0
  226. package/dist/composables/tables/data-table/expansion.js +38 -0
  227. package/dist/composables/tables/data-table/expansion.js.map +1 -0
  228. package/dist/composables/tables/data-table/grouping.d.ts +29 -0
  229. package/dist/composables/tables/data-table/grouping.js +136 -0
  230. package/dist/composables/tables/data-table/grouping.js.map +1 -0
  231. package/dist/composables/tables/data-table/pagination.d.ts +33 -0
  232. package/dist/composables/tables/data-table/pagination.js +119 -0
  233. package/dist/composables/tables/data-table/pagination.js.map +1 -0
  234. package/dist/composables/tables/data-table/selection.d.ts +29 -0
  235. package/dist/composables/tables/data-table/selection.js +164 -0
  236. package/dist/composables/tables/data-table/selection.js.map +1 -0
  237. package/dist/composables/tables/data-table/sort.d.ts +32 -0
  238. package/dist/composables/tables/data-table/sort.js +136 -0
  239. package/dist/composables/tables/data-table/sort.js.map +1 -0
  240. package/dist/composables/tables/data-table/types.d.ts +14 -0
  241. package/dist/composables/tables/data-table/types.js +11 -0
  242. package/dist/composables/tables/data-table/types.js.map +1 -0
  243. package/dist/composables/theme.js +5 -21
  244. package/dist/composables/theme.js.map +1 -1
  245. package/dist/composables/timeout-manager.d.ts +4 -2
  246. package/dist/composables/timeout-manager.js +2 -1
  247. package/dist/composables/timeout-manager.js.map +1 -1
  248. package/dist/icons/icons_1.d.ts +15 -15
  249. package/dist/icons/icons_1.js +123 -123
  250. package/dist/icons/icons_1.js.map +1 -1
  251. package/dist/icons/icons_2.d.ts +46 -46
  252. package/dist/icons/icons_2.js +301 -301
  253. package/dist/icons/icons_2.js.map +1 -1
  254. package/dist/icons/icons_3.d.ts +43 -43
  255. package/dist/icons/icons_3.js +252 -252
  256. package/dist/icons/icons_3.js.map +1 -1
  257. package/dist/icons/icons_4.d.ts +72 -2
  258. package/dist/icons/icons_4.js +368 -18
  259. package/dist/icons/icons_4.js.map +1 -1
  260. package/dist/icons/index.d.ts +1 -1
  261. package/dist/icons/index.js +1 -1
  262. package/dist/icons/index.js.map +1 -1
  263. package/dist/index.js +105 -42
  264. package/dist/index.js.map +1 -1
  265. package/dist/style.css +189 -162
  266. package/dist/theme/index.js +15 -1
  267. package/dist/theme/index.js.map +1 -1
  268. package/dist/utils/assert.d.ts +1 -1
  269. package/dist/utils/assert.js.map +1 -1
  270. package/dist/utils/form-text-detail.d.ts +9 -8
  271. package/dist/utils/form-text-detail.js +1 -2
  272. package/dist/utils/form-text-detail.js.map +1 -1
  273. package/dist/utils/helpers.d.ts +4 -32
  274. package/dist/utils/helpers.js +8 -9
  275. package/dist/utils/helpers.js.map +1 -1
  276. package/dist/utils/is-equal.d.ts +1 -1
  277. package/dist/utils/is-equal.js +5 -3
  278. package/dist/utils/is-equal.js.map +1 -1
  279. package/dist/vite-plugin/index.js +4 -4
  280. package/dist/vite-plugin/index.js.map +1 -1
  281. package/dist/vite-plugin/scanner.js +3 -3
  282. package/dist/vite-plugin/scanner.js.map +1 -1
  283. package/dist/web-types.json +390 -371
  284. package/package.json +47 -44
  285. package/dist/utils/generate-id.d.ts +0 -5
  286. package/dist/utils/generate-id.js +0 -21
  287. package/dist/utils/generate-id.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { computed, ref, watch, nextTick } from "vue";
1
+ import { computed, shallowRef, ref, watch, nextTick } from "vue";
2
2
  import { get, set } from "@vueuse/shared";
3
3
  import { useVirtualList, useArrayMap, watchOnce } from "@vueuse/core";
4
4
  function useDropdownOptionProperty({
@@ -9,8 +9,7 @@ function useDropdownOptionProperty({
9
9
  if (textAttr) {
10
10
  if (typeof textAttr === "function")
11
11
  return textAttr(item);
12
- else
13
- return item[textAttr]?.toString();
12
+ else return item[textAttr]?.toString();
14
13
  }
15
14
  return item?.toString();
16
15
  }
@@ -24,13 +23,23 @@ function useDropdownOptionProperty({
24
23
  getText
25
24
  };
26
25
  }
26
+ function toSelectedArray(val) {
27
+ if (Array.isArray(val))
28
+ return val;
29
+ if (val !== null && val !== void 0)
30
+ return [val];
31
+ return [];
32
+ }
27
33
  function useDropdownMenu({
28
34
  appendWidth,
29
35
  autoFocus,
30
36
  autoSelectFirst,
31
37
  dense,
32
- disabled = ref(false),
38
+ disabled = shallowRef(false),
39
+ getIdentifier: externalGetIdentifier,
40
+ getText: externalGetText,
33
41
  hideSelected,
42
+ isOpen: externalIsOpen,
34
43
  itemHeight = 48,
35
44
  keyAttr,
36
45
  menuRef,
@@ -41,25 +50,36 @@ function useDropdownMenu({
41
50
  textAttr,
42
51
  value
43
52
  }) {
53
+ const { getIdentifier: defaultGetIdentifier, getText: defaultGetText } = useDropdownOptionProperty({
54
+ keyAttr,
55
+ textAttr
56
+ });
57
+ const getIdentifier = externalGetIdentifier ?? defaultGetIdentifier;
58
+ const getText = externalGetText ?? defaultGetText;
59
+ const activeIdentifiers = computed(() => {
60
+ const val = get(value);
61
+ const selected = toSelectedArray(val);
62
+ const identifiers = /* @__PURE__ */ new Set();
63
+ for (const item of selected) {
64
+ identifiers.add(getIdentifier(item));
65
+ }
66
+ return identifiers;
67
+ });
68
+ function isActiveItem(item) {
69
+ return get(activeIdentifiers).has(getIdentifier(item));
70
+ }
44
71
  const options = computed(() => {
45
72
  const options2 = get(allOptions);
46
73
  if (!hideSelected)
47
74
  return options2;
48
75
  return options2.filter((item) => !isActiveItem(item));
49
76
  });
50
- const { getIdentifier, getText } = useDropdownOptionProperty({
51
- keyAttr,
52
- textAttr
77
+ const { containerProps, list, scrollTo, wrapperProps } = useVirtualList(options, {
78
+ itemHeight,
79
+ overscan
53
80
  });
54
- const { containerProps, list, scrollTo, wrapperProps } = useVirtualList(
55
- options,
56
- {
57
- itemHeight,
58
- overscan
59
- }
60
- );
61
81
  const renderedData = useArrayMap(list, ({ data, index: _index }) => ({ _index, item: data }));
62
- const isOpen = ref(false);
82
+ const isOpen = externalIsOpen ?? shallowRef(false);
63
83
  const valueKey = computed(() => {
64
84
  const selected = get(value);
65
85
  if (!keyAttr || !selected)
@@ -67,63 +87,67 @@ function useDropdownMenu({
67
87
  return Array.isArray(selected) ? selected.map((item) => item[keyAttr]) : selected[keyAttr];
68
88
  });
69
89
  const highlightedIndex = ref(get(autoSelectFirst) ? 0 : -1);
90
+ const optionWidthBounds = computed(() => {
91
+ let min = 0;
92
+ let max = 0;
93
+ for (const option of get(options)) {
94
+ const length = getText(option)?.toString()?.length ?? 0;
95
+ if (min === 0 && max === 0) {
96
+ min = length;
97
+ max = length;
98
+ } else if (length < min) {
99
+ min = length;
100
+ } else if (length > max) {
101
+ max = length;
102
+ }
103
+ }
104
+ return { min, max };
105
+ });
70
106
  const menuWidth = computed(() => {
71
- const widths = { max: 0, min: 0 };
107
+ const { min, max } = get(optionWidthBounds);
72
108
  const maxWidth = 30;
73
109
  const paddingX = 1.5;
74
110
  const fontMultiplier = get(dense) ? 12 : 13;
75
- get(options).forEach((option) => {
76
- const length = getText(option)?.toString()?.length ?? 0;
77
- if (widths.min === 0 && widths.max === 0) {
78
- widths.min = length;
79
- widths.max = length;
80
- } else if (length < widths.min) {
81
- widths.min = length;
82
- } else if (length > widths.max) {
83
- widths.max = length;
84
- }
85
- });
86
- const difference = widths.max - widths.min;
111
+ const difference = max - min;
87
112
  function computeValue(width) {
88
113
  const additionalWidths = (prependWidth ? prependWidth + 0.5 : 0) + (appendWidth ? appendWidth + 0.5 : 0);
89
114
  return `${Math.min(width * fontMultiplier / 16 + paddingX + additionalWidths, maxWidth)}rem`;
90
115
  }
91
116
  if (difference <= 5)
92
- return computeValue(widths.max);
93
- return computeValue(widths.min + difference / 2);
117
+ return computeValue(max);
118
+ return computeValue(min + difference / 2);
94
119
  });
95
120
  function toggle(state = false) {
96
121
  set(isOpen, state);
97
122
  }
98
123
  function itemIndexInValue(item) {
99
124
  const val = get(value);
100
- const selected = Array.isArray(val) ? val : val ? [val] : [];
125
+ const selected = toSelectedArray(val);
101
126
  if (selected.length === 0)
102
127
  return -1;
103
- return selected.findIndex((selectedItem) => {
104
- if (keyAttr)
105
- return selectedItem[keyAttr] === item[keyAttr];
106
- return selectedItem === item;
107
- });
108
- }
109
- function isActiveItem(item) {
110
- return itemIndexInValue(item) !== -1;
128
+ const itemId = getIdentifier(item);
129
+ return selected.findIndex((selectedItem) => getIdentifier(selectedItem) === itemId);
111
130
  }
112
131
  async function adjustScrollByHighlightedIndex(smooth = false) {
113
132
  const index = get(highlightedIndex);
114
133
  if (index > -1) {
115
134
  await nextTick(() => {
116
- const container = get(menuRef)?.parentElement;
117
- if (container) {
118
- const highlightedElem = get(menuRef).getElementsByClassName("highlighted")[0];
135
+ const menuEl = get(menuRef);
136
+ const container = menuEl?.parentElement;
137
+ if (container && menuEl) {
138
+ const highlightedElem = menuEl.getElementsByClassName("highlighted")[0];
119
139
  if (highlightedElem) {
120
- highlightedElem.scrollIntoView?.({ behavior: smooth ? "smooth" : "auto", block: "nearest" });
121
- if (get(autoFocus) && "focus" in highlightedElem && typeof highlightedElem.focus === "function")
140
+ highlightedElem.scrollIntoView?.({
141
+ behavior: smooth ? "smooth" : "auto",
142
+ block: "nearest"
143
+ });
144
+ if (get(autoFocus) && "focus" in highlightedElem && typeof highlightedElem.focus === "function") {
122
145
  highlightedElem?.focus();
146
+ }
123
147
  } else {
124
148
  scrollTo(index);
125
149
  if (get(autoFocus)) {
126
- const elem = get(menuRef).getElementsByClassName("highlighted")[0];
150
+ const elem = menuEl.getElementsByClassName("highlighted")[0];
127
151
  if (elem && "focus" in elem && typeof elem.focus === "function")
128
152
  elem.focus();
129
153
  }
@@ -136,7 +160,7 @@ function useDropdownMenu({
136
160
  await nextTick(() => {
137
161
  if (open) {
138
162
  const val = get(value);
139
- if (Array.isArray(val) && val.length > 0 || !!val) {
163
+ if (Array.isArray(val) && val.length > 0 || val !== null && val !== void 0) {
140
164
  const index = get(options).findIndex(isActiveItem);
141
165
  if (index > -1)
142
166
  set(highlightedIndex, index);
@@ -180,8 +204,7 @@ function useDropdownMenu({
180
204
  set(highlightedIndex, 0);
181
205
  else if (position < 0)
182
206
  set(highlightedIndex, total - 1);
183
- else
184
- set(highlightedIndex, position);
207
+ else set(highlightedIndex, position);
185
208
  };
186
209
  const applyHighlighted = () => {
187
210
  if (!setValue || !get(isOpen))
@@ -189,8 +212,8 @@ function useDropdownMenu({
189
212
  const highlightedIndexVal = get(highlightedIndex);
190
213
  if (highlightedIndexVal === -1)
191
214
  return;
192
- const entry = get(options).find((_data, index) => highlightedIndexVal === index);
193
- if (entry)
215
+ const entry = get(options)[highlightedIndexVal];
216
+ if (entry !== void 0)
194
217
  setValue(entry);
195
218
  };
196
219
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-menu.js","sources":["../../src/composables/dropdown-menu.ts"],"sourcesContent":["import type { Ref } from 'vue';\n\nexport type KeyOfType<T, V> = T extends string | number | boolean | symbol | null | undefined\n ? never\n : V extends object\n ? keyof T\n : keyof {\n [P in keyof T as T[P] extends V\n ? P\n : V extends T[P]\n ? P\n : never]: any\n };\n\nexport interface DropdownItemAttr<TValue, TItem> {\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem | ((item: TItem) => string);\n}\n\nexport interface DropdownOptions<TValue, TItem> {\n options: Ref<TItem[]>;\n dense?: Ref<boolean>;\n disabled?: Ref<boolean>;\n value: Ref<TItem | TItem[] | undefined>;\n menuRef: Ref<HTMLElement>;\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem | ((item: TItem) => string);\n appendWidth?: number;\n prependWidth?: number;\n itemHeight?: number;\n overscan?: number;\n autoSelectFirst?: boolean;\n autoFocus?: boolean;\n setValue?: (val: TItem) => void;\n hideSelected?: boolean;\n}\n\nexport function useDropdownOptionProperty<TValue, TItem>({\n keyAttr,\n textAttr,\n}: DropdownItemAttr<TValue, TItem>) {\n function getText(item: TItem): string | undefined {\n if (textAttr) {\n if (typeof textAttr === 'function')\n return textAttr(item);\n\n else\n return item[textAttr]?.toString();\n }\n\n return item?.toString();\n }\n\n function getIdentifier(item: TItem): TItem | TItem[KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>] {\n if (keyAttr)\n return item[keyAttr];\n\n return item;\n }\n\n return {\n getIdentifier,\n getText,\n };\n}\n\nexport function useDropdownMenu<TValue, TItem>({\n appendWidth,\n autoFocus,\n autoSelectFirst,\n dense,\n disabled = ref(false),\n hideSelected,\n itemHeight = 48,\n keyAttr,\n menuRef,\n options: allOptions,\n overscan = 5,\n prependWidth,\n setValue,\n textAttr,\n value,\n}: DropdownOptions<TValue, TItem>) {\n const options = computed(() => {\n const options = get(allOptions);\n if (!hideSelected)\n return options;\n\n return options.filter(item => !isActiveItem(item));\n });\n\n const { getIdentifier, getText } = useDropdownOptionProperty({\n keyAttr,\n textAttr,\n });\n\n const { containerProps, list, scrollTo, wrapperProps } = useVirtualList<TItem>(\n options,\n {\n itemHeight,\n overscan,\n },\n );\n\n const renderedData = useArrayMap(list, ({ data, index: _index }) => ({ _index, item: data }));\n\n const isOpen = ref(false);\n\n const valueKey = computed(() => {\n const selected = get(value);\n if (!keyAttr || !selected)\n return selected;\n return Array.isArray(selected) ? selected.map(item => item[keyAttr]) : selected[keyAttr];\n });\n\n const highlightedIndex: Ref<number> = ref(get(autoSelectFirst) ? 0 : -1);\n\n const menuWidth = computed(() => {\n const widths = { max: 0, min: 0 };\n const maxWidth = 30;\n const paddingX = 1.5;\n const fontMultiplier = get(dense) ? 12 : 13;\n\n get(options).forEach((option) => {\n const length = getText(option)?.toString()?.length ?? 0;\n if (widths.min === 0 && widths.max === 0) {\n widths.min = length;\n widths.max = length;\n }\n else if (length < widths.min) {\n widths.min = length;\n }\n else if (length > widths.max) {\n widths.max = length;\n }\n });\n\n const difference = widths.max - widths.min;\n\n function computeValue(width: number) {\n const additionalWidths = (prependWidth ? prependWidth + 0.5 : 0) + (appendWidth ? appendWidth + 0.5 : 0);\n return `${Math.min((width * fontMultiplier) / 16 + paddingX + additionalWidths, maxWidth)}rem`;\n }\n\n if (difference <= 5)\n return computeValue(widths.max);\n\n return computeValue(widths.min + difference / 2);\n });\n\n function toggle(state: boolean = false) {\n set(isOpen, state);\n }\n\n function itemIndexInValue(item: TItem): number {\n const val = get(value);\n const selected: TItem[] = Array.isArray(val) ? val : (val ? [val] : []);\n\n if (selected.length === 0)\n return -1;\n\n return selected.findIndex((selectedItem) => {\n if (keyAttr)\n return selectedItem[keyAttr] === item[keyAttr];\n return selectedItem === item;\n });\n }\n\n function isActiveItem(item: TItem): boolean {\n return itemIndexInValue(item) !== -1;\n }\n\n async function adjustScrollByHighlightedIndex(smooth: boolean = false) {\n const index = get(highlightedIndex);\n if (index > -1) {\n await nextTick(() => {\n const container = get(menuRef)?.parentElement;\n if (container) {\n const highlightedElem = get(menuRef).getElementsByClassName('highlighted')[0];\n\n if (highlightedElem) {\n highlightedElem.scrollIntoView?.({ behavior: smooth ? 'smooth' : 'auto', block: 'nearest' });\n if (get(autoFocus) && 'focus' in highlightedElem && typeof highlightedElem.focus === 'function')\n highlightedElem?.focus();\n }\n else {\n scrollTo(index);\n if (get(autoFocus)) {\n const elem = get(menuRef).getElementsByClassName('highlighted')[0];\n if (elem && 'focus' in elem && typeof elem.focus === 'function')\n elem.focus();\n }\n }\n }\n });\n }\n }\n\n async function updateOpen(open: boolean) {\n await nextTick(() => {\n if (open) {\n const val = get(value);\n\n // set highlighted index to active item\n if ((Array.isArray(val) && val.length > 0) || !!val) {\n const index = get(options).findIndex(isActiveItem);\n if (index > -1)\n set(highlightedIndex, index);\n }\n\n watchOnce(list, async () => {\n await adjustScrollByHighlightedIndex();\n });\n }\n });\n }\n\n watch([isOpen, disabled], async ([open, _]) => {\n await updateOpen(open);\n });\n\n watch(highlightedIndex, async (curr, prev) => {\n if (curr !== prev)\n await adjustScrollByHighlightedIndex(true);\n });\n\n watch(options, async () => {\n if (get(highlightedIndex) !== -1) {\n if (get(autoSelectFirst)) {\n set(highlightedIndex, 0);\n await adjustScrollByHighlightedIndex();\n }\n else {\n set(highlightedIndex, -1);\n scrollTo(0);\n }\n }\n else {\n scrollTo(0);\n }\n });\n\n const moveHighlight = (up: boolean) => {\n if (!get(isOpen)) {\n toggle(true);\n return;\n }\n\n let position = get(highlightedIndex);\n const move = up ? -1 : 1;\n\n position += move;\n\n const total = get(options).length;\n\n if (position >= total)\n set(highlightedIndex, 0);\n else if (position < 0)\n set(highlightedIndex, total - 1);\n else\n set(highlightedIndex, position);\n };\n\n const applyHighlighted = () => {\n if (!setValue || !get(isOpen))\n return;\n\n const highlightedIndexVal = get(highlightedIndex);\n if (highlightedIndexVal === -1)\n return;\n\n const entry = get(options).find((_data, index) => highlightedIndexVal === index);\n if (entry)\n setValue(entry);\n };\n\n return {\n applyHighlighted,\n containerProps,\n getIdentifier,\n getText,\n highlightedIndex,\n isActiveItem,\n isOpen,\n itemIndexInValue,\n menuWidth,\n moveHighlight,\n optionsWithSelectedHidden: options,\n renderedData,\n toggle,\n valueKey,\n wrapperProps,\n };\n}\n"],"names":["options"],"mappings":"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"}
1
+ {"version":3,"file":"dropdown-menu.js","sources":["../../src/composables/dropdown-menu.ts"],"sourcesContent":["import type { Ref, TemplateRef } from 'vue';\n\nexport type KeyOfType<T, V> = T extends string | number | boolean | symbol | null | undefined\n ? never\n : V extends object\n ? keyof T\n : keyof {\n [P in keyof T as T[P] extends V ? P : V extends T[P] ? P : never]: any;\n };\n\nexport interface DropdownItemAttr<TValue, TItem> {\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem | ((item: TItem) => string);\n}\n\nexport interface DropdownOptions<TValue, TItem> {\n options: Ref<TItem[]>;\n dense?: Ref<boolean>;\n disabled?: Ref<boolean>;\n value: Ref<TItem | TItem[] | undefined>;\n menuRef: Ref<HTMLElement> | TemplateRef<HTMLElement>;\n keyAttr?: KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>;\n textAttr?: keyof TItem | ((item: TItem) => string);\n appendWidth?: number;\n prependWidth?: number;\n itemHeight?: number;\n overscan?: number;\n autoSelectFirst?: boolean;\n autoFocus?: boolean;\n setValue?: (val: TItem) => void;\n hideSelected?: boolean;\n isOpen?: Ref<boolean>;\n getText?: (item: TItem) => string | undefined;\n getIdentifier?: (item: TItem) => any;\n}\n\nexport function useDropdownOptionProperty<TValue, TItem>({\n keyAttr,\n textAttr,\n}: DropdownItemAttr<TValue, TItem>) {\n function getText(item: TItem): string | undefined {\n if (textAttr) {\n if (typeof textAttr === 'function')\n return textAttr(item);\n else return item[textAttr]?.toString();\n }\n\n return item?.toString();\n }\n\n function getIdentifier(\n item: TItem,\n ): TItem | TItem[KeyOfType<TItem, TValue extends Array<infer U> ? U : TValue>] {\n if (keyAttr)\n return item[keyAttr];\n\n return item;\n }\n\n return {\n getIdentifier,\n getText,\n };\n}\n\nfunction toSelectedArray<TItem>(val: TItem | TItem[] | undefined): TItem[] {\n if (Array.isArray(val))\n return val;\n if (val !== null && val !== undefined)\n return [val];\n return [];\n}\n\nexport function useDropdownMenu<TValue, TItem>({\n appendWidth,\n autoFocus,\n autoSelectFirst,\n dense,\n disabled = shallowRef(false),\n getIdentifier: externalGetIdentifier,\n getText: externalGetText,\n hideSelected,\n isOpen: externalIsOpen,\n itemHeight = 48,\n keyAttr,\n menuRef,\n options: allOptions,\n overscan = 5,\n prependWidth,\n setValue,\n textAttr,\n value,\n}: DropdownOptions<TValue, TItem>) {\n const { getIdentifier: defaultGetIdentifier, getText: defaultGetText } = useDropdownOptionProperty({\n keyAttr,\n textAttr,\n });\n\n const getIdentifier = externalGetIdentifier ?? defaultGetIdentifier;\n const getText = externalGetText ?? defaultGetText;\n\n const activeIdentifiers = computed<Set<any>>(() => {\n const val = get(value);\n const selected: TItem[] = toSelectedArray(val);\n const identifiers = new Set<any>();\n for (const item of selected) {\n identifiers.add(getIdentifier(item));\n }\n return identifiers;\n });\n\n function isActiveItem(item: TItem): boolean {\n return get(activeIdentifiers).has(getIdentifier(item));\n }\n\n const options = computed<TItem[]>(() => {\n const options = get(allOptions);\n if (!hideSelected)\n return options;\n\n return options.filter(item => !isActiveItem(item));\n });\n\n const { containerProps, list, scrollTo, wrapperProps } = useVirtualList<TItem>(options, {\n itemHeight,\n overscan,\n });\n\n const renderedData = useArrayMap(list, ({ data, index: _index }) => ({ _index, item: data }));\n\n const isOpen = externalIsOpen ?? shallowRef<boolean>(false);\n\n const valueKey = computed(() => {\n const selected = get(value);\n if (!keyAttr || !selected)\n return selected;\n return Array.isArray(selected) ? selected.map(item => item[keyAttr]) : selected[keyAttr];\n });\n\n const highlightedIndex: Ref<number> = ref(get(autoSelectFirst) ? 0 : -1);\n\n const optionWidthBounds = computed<{ min: number; max: number }>(() => {\n let min = 0;\n let max = 0;\n for (const option of get(options)) {\n const length = getText(option)?.toString()?.length ?? 0;\n if (min === 0 && max === 0) {\n min = length;\n max = length;\n }\n else if (length < min) {\n min = length;\n }\n else if (length > max) {\n max = length;\n }\n }\n return { min, max };\n });\n\n const menuWidth = computed<string>(() => {\n const { min, max } = get(optionWidthBounds);\n const maxWidth = 30;\n const paddingX = 1.5;\n const fontMultiplier = get(dense) ? 12 : 13;\n const difference = max - min;\n\n function computeValue(width: number): string {\n const additionalWidths =\n (prependWidth ? prependWidth + 0.5 : 0) + (appendWidth ? appendWidth + 0.5 : 0);\n return `${Math.min((width * fontMultiplier) / 16 + paddingX + additionalWidths, maxWidth)}rem`;\n }\n\n if (difference <= 5)\n return computeValue(max);\n\n return computeValue(min + difference / 2);\n });\n\n function toggle(state: boolean = false): void {\n set(isOpen, state);\n }\n\n function itemIndexInValue(item: TItem): number {\n const val = get(value);\n const selected: TItem[] = toSelectedArray(val);\n\n if (selected.length === 0)\n return -1;\n\n const itemId = getIdentifier(item);\n return selected.findIndex(selectedItem => getIdentifier(selectedItem) === itemId);\n }\n\n async function adjustScrollByHighlightedIndex(smooth: boolean = false): Promise<void> {\n const index = get(highlightedIndex);\n if (index > -1) {\n await nextTick(() => {\n const menuEl = get(menuRef);\n const container = menuEl?.parentElement;\n if (container && menuEl) {\n const highlightedElem = menuEl.getElementsByClassName('highlighted')[0];\n\n if (highlightedElem) {\n highlightedElem.scrollIntoView?.({\n behavior: smooth ? 'smooth' : 'auto',\n block: 'nearest',\n });\n if (\n get(autoFocus) &&\n 'focus' in highlightedElem &&\n typeof highlightedElem.focus === 'function'\n ) {\n highlightedElem?.focus();\n }\n }\n else {\n scrollTo(index);\n if (get(autoFocus)) {\n const elem = menuEl.getElementsByClassName('highlighted')[0];\n if (elem && 'focus' in elem && typeof elem.focus === 'function')\n elem.focus();\n }\n }\n }\n });\n }\n }\n\n async function updateOpen(open: boolean): Promise<void> {\n await nextTick(() => {\n if (open) {\n const val = get(value);\n\n // set highlighted index to active item\n if ((Array.isArray(val) && val.length > 0) || (val !== null && val !== undefined)) {\n const index = get(options).findIndex(isActiveItem);\n if (index > -1)\n set(highlightedIndex, index);\n }\n\n watchOnce(list, async () => {\n await adjustScrollByHighlightedIndex();\n });\n }\n });\n }\n\n watch([isOpen, disabled], async ([open, _]) => {\n await updateOpen(open);\n });\n\n watch(highlightedIndex, async (curr, prev) => {\n if (curr !== prev)\n await adjustScrollByHighlightedIndex(true);\n });\n\n watch(options, async () => {\n if (get(highlightedIndex) !== -1) {\n if (get(autoSelectFirst)) {\n set(highlightedIndex, 0);\n await adjustScrollByHighlightedIndex();\n }\n else {\n set(highlightedIndex, -1);\n scrollTo(0);\n }\n }\n else {\n scrollTo(0);\n }\n });\n\n const moveHighlight = (up: boolean): void => {\n if (!get(isOpen)) {\n toggle(true);\n return;\n }\n\n let position = get(highlightedIndex);\n const move = up ? -1 : 1;\n\n position += move;\n\n const total = get(options).length;\n\n if (position >= total)\n set(highlightedIndex, 0);\n else if (position < 0)\n set(highlightedIndex, total - 1);\n else set(highlightedIndex, position);\n };\n\n const applyHighlighted = (): void => {\n if (!setValue || !get(isOpen))\n return;\n\n const highlightedIndexVal = get(highlightedIndex);\n if (highlightedIndexVal === -1)\n return;\n\n const entry = get(options)[highlightedIndexVal];\n if (entry !== undefined)\n setValue(entry);\n };\n\n return {\n applyHighlighted,\n containerProps,\n getIdentifier,\n getText,\n highlightedIndex,\n isActiveItem,\n isOpen,\n itemIndexInValue,\n menuWidth,\n moveHighlight,\n optionsWithSelectedHidden: options,\n renderedData,\n toggle,\n valueKey,\n wrapperProps,\n };\n}\n"],"names":["options"],"mappings":"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"}
@@ -1,20 +1,22 @@
1
- import type { ComputedRef, MaybeRef, Ref } from 'vue';
1
+ import type { ComputedRef, MaybeRef, Ref, TemplateRef } from 'vue';
2
2
  export interface UseAutoCompleteFocusOptions {
3
+ /** Whether custom (free-text) values are allowed. */
3
4
  customValue: MaybeRef<boolean>;
5
+ /** Whether to apply the current value as the search text when focused. */
4
6
  shouldApplyValueAsSearch: MaybeRef<boolean>;
7
+ /** Whether the autocomplete is disabled. */
5
8
  disabled: MaybeRef<boolean>;
6
9
  }
7
- export interface UseAutoCompleteFocusDeps<TItem> {
10
+ export interface UseAutoCompleteFocusDeps {
8
11
  activatorFocused: Ref<boolean>;
9
12
  activatorFocusedWithin: Ref<boolean>;
10
13
  focusedValueIndex: Ref<number>;
11
- filteredOptions: Ref<TItem[]>;
12
14
  internalSearch: Ref<string>;
13
15
  isOpen: Ref<boolean>;
14
16
  justOpened: Ref<boolean>;
15
17
  menuWrapperFocusedWithin: Ref<boolean>;
16
18
  searchInputFocused: Ref<boolean>;
17
- textInput: Ref<HTMLInputElement | undefined>;
19
+ textInput: Ref<HTMLInputElement | undefined> | TemplateRef<HTMLInputElement>;
18
20
  setSearchAsValue: () => void;
19
21
  updateInternalSearch: (value?: string) => void;
20
22
  }
@@ -25,4 +27,4 @@ export interface UseAutoCompleteFocusReturn {
25
27
  onInputFocused: () => void;
26
28
  setInputFocus: () => Promise<void>;
27
29
  }
28
- export declare function useAutoCompleteFocus<TItem>(options: UseAutoCompleteFocusOptions, deps: UseAutoCompleteFocusDeps<TItem>): UseAutoCompleteFocusReturn;
30
+ export declare function useAutoCompleteFocus(options: UseAutoCompleteFocusOptions, deps: UseAutoCompleteFocusDeps): UseAutoCompleteFocusReturn;
@@ -1,4 +1,4 @@
1
- import { computed, nextTick } from "vue";
1
+ import { computed, watch, nextTick } from "vue";
2
2
  import { get, set } from "@vueuse/shared";
3
3
  import { watchDebounced } from "@vueuse/core";
4
4
  function useAutoCompleteFocus(options, deps) {
@@ -39,24 +39,35 @@ function useAutoCompleteFocus(options, deps) {
39
39
  set(deps.justOpened, true);
40
40
  }
41
41
  }
42
- watchDebounced(anyFocused, (focused) => {
43
- if (!focused) {
44
- set(deps.isOpen, false);
45
- const customValue = get(options.customValue);
46
- const filteredOptions = get(deps.filteredOptions);
47
- const internalSearch = get(deps.internalSearch);
48
- if (customValue && filteredOptions.length === 0 && internalSearch) {
49
- deps.setSearchAsValue();
50
- }
51
- const shouldApply = get(options.shouldApplyValueAsSearch);
52
- if (!shouldApply) {
53
- deps.updateInternalSearch();
54
- }
42
+ let pendingCustomSearch = "";
43
+ watch(anyFocused, (focused) => {
44
+ if (!focused && get(options.customValue)) {
45
+ pendingCustomSearch = get(deps.internalSearch);
55
46
  }
56
- }, {
57
- debounce: 200,
58
- maxWait: 400
59
47
  });
48
+ watchDebounced(
49
+ anyFocused,
50
+ (focused) => {
51
+ if (!focused) {
52
+ set(deps.isOpen, false);
53
+ const customValue = get(options.customValue);
54
+ const searchToUse = pendingCustomSearch;
55
+ pendingCustomSearch = "";
56
+ if (customValue && searchToUse) {
57
+ deps.updateInternalSearch(searchToUse);
58
+ deps.setSearchAsValue();
59
+ }
60
+ const shouldApply = get(options.shouldApplyValueAsSearch);
61
+ if (!shouldApply) {
62
+ deps.updateInternalSearch();
63
+ }
64
+ }
65
+ },
66
+ {
67
+ debounce: 200,
68
+ maxWait: 400
69
+ }
70
+ );
60
71
  return {
61
72
  anyFocused,
62
73
  inputClass,
@@ -1 +1 @@
1
- {"version":3,"file":"focus.js","sources":["../../../../src/composables/forms/auto-complete/focus.ts"],"sourcesContent":["import type { ComputedRef, MaybeRef, Ref } from 'vue';\n\nexport interface UseAutoCompleteFocusOptions {\n customValue: MaybeRef<boolean>;\n shouldApplyValueAsSearch: MaybeRef<boolean>;\n disabled: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteFocusDeps<TItem> {\n activatorFocused: Ref<boolean>;\n activatorFocusedWithin: Ref<boolean>;\n focusedValueIndex: Ref<number>;\n filteredOptions: Ref<TItem[]>;\n internalSearch: Ref<string>;\n isOpen: Ref<boolean>;\n justOpened: Ref<boolean>;\n menuWrapperFocusedWithin: Ref<boolean>;\n searchInputFocused: Ref<boolean>;\n textInput: Ref<HTMLInputElement | undefined>;\n setSearchAsValue: () => void;\n updateInternalSearch: (value?: string) => void;\n}\n\nexport interface UseAutoCompleteFocusReturn {\n anyFocused: ComputedRef<boolean>;\n inputClass: ComputedRef<string>;\n onActivatorFocused: () => Promise<void>;\n onInputFocused: () => void;\n setInputFocus: () => Promise<void>;\n}\n\nexport function useAutoCompleteFocus<TItem>(\n options: UseAutoCompleteFocusOptions,\n deps: UseAutoCompleteFocusDeps<TItem>,\n): UseAutoCompleteFocusReturn {\n const anyFocused = computed<boolean>(() =>\n get(deps.activatorFocusedWithin) || get(deps.menuWrapperFocusedWithin),\n );\n\n const inputClass = computed<string>(() => {\n const isFocused = get(anyFocused);\n const isDisabled = get(options.disabled);\n const shouldApply = get(options.shouldApplyValueAsSearch);\n const hasSearch = get(deps.internalSearch);\n\n if ((!isFocused || isDisabled) && !shouldApply)\n return 'w-0 h-0';\n if (hasSearch)\n return 'flex-1 min-w-[4rem]';\n return 'flex-1 min-w-0';\n });\n\n async function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(deps.searchInputFocused, true);\n });\n }\n\n async function onActivatorFocused(): Promise<void> {\n await nextTick(() => {\n if (!get(deps.activatorFocused)) {\n set(deps.searchInputFocused, true);\n }\n });\n }\n\n function onInputFocused(): void {\n set(deps.focusedValueIndex, -1);\n\n const shouldApply = get(options.shouldApplyValueAsSearch);\n if (shouldApply) {\n const textInput = get(deps.textInput);\n textInput?.select();\n }\n\n if (!get(deps.isOpen)) {\n set(deps.justOpened, true);\n }\n }\n\n // Close menu if the activator is not focused anymore\n // Using debounced to avoid the menu closing momentarily while focus switches.\n watchDebounced(anyFocused, (focused) => {\n if (!focused) {\n set(deps.isOpen, false);\n\n const customValue = get(options.customValue);\n const filteredOptions = get(deps.filteredOptions);\n const internalSearch = get(deps.internalSearch);\n\n if (customValue && filteredOptions.length === 0 && internalSearch) {\n deps.setSearchAsValue();\n }\n\n const shouldApply = get(options.shouldApplyValueAsSearch);\n if (!shouldApply) {\n deps.updateInternalSearch();\n }\n }\n }, {\n debounce: 200,\n maxWait: 400,\n });\n\n return {\n anyFocused,\n inputClass,\n onActivatorFocused,\n onInputFocused,\n setInputFocus,\n };\n}\n"],"names":[],"mappings":";;;AA+BO,SAAS,qBACd,SACA,MAC4B;AAC5B,QAAM,aAAa;AAAA,IAAkB,MACnC,IAAI,KAAK,sBAAsB,KAAK,IAAI,KAAK,wBAAwB;AAAA,EAAA;AAGvE,QAAM,aAAa,SAAiB,MAAM;AACxC,UAAM,YAAY,IAAI,UAAU;AAChC,UAAM,aAAa,IAAI,QAAQ,QAAQ;AACvC,UAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,UAAM,YAAY,IAAI,KAAK,cAAc;AAEzC,SAAK,CAAC,aAAa,eAAe,CAAC;AACjC,aAAO;AACT,QAAI;AACF,aAAO;AACT,WAAO;AAAA,EACT,CAAC;AAED,iBAAe,gBAA+B;AAC5C,UAAM,SAAS,MAAM;AACnB,UAAI,KAAK,oBAAoB,IAAI;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,iBAAe,qBAAoC;AACjD,UAAM,SAAS,MAAM;AACnB,UAAI,CAAC,IAAI,KAAK,gBAAgB,GAAG;AAC/B,YAAI,KAAK,oBAAoB,IAAI;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,iBAAuB;AAC9B,QAAI,KAAK,mBAAmB,EAAE;AAE9B,UAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,QAAI,aAAa;AACf,YAAM,YAAY,IAAI,KAAK,SAAS;AACpC,iBAAW,OAAA;AAAA,IACb;AAEA,QAAI,CAAC,IAAI,KAAK,MAAM,GAAG;AACrB,UAAI,KAAK,YAAY,IAAI;AAAA,IAC3B;AAAA,EACF;AAIA,iBAAe,YAAY,CAAC,YAAY;AACtC,QAAI,CAAC,SAAS;AACZ,UAAI,KAAK,QAAQ,KAAK;AAEtB,YAAM,cAAc,IAAI,QAAQ,WAAW;AAC3C,YAAM,kBAAkB,IAAI,KAAK,eAAe;AAChD,YAAM,iBAAiB,IAAI,KAAK,cAAc;AAE9C,UAAI,eAAe,gBAAgB,WAAW,KAAK,gBAAgB;AACjE,aAAK,iBAAA;AAAA,MACP;AAEA,YAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,UAAI,CAAC,aAAa;AAChB,aAAK,qBAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF,GAAG;AAAA,IACD,UAAU;AAAA,IACV,SAAS;AAAA,EAAA,CACV;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"focus.js","sources":["../../../../src/composables/forms/auto-complete/focus.ts"],"sourcesContent":["import type { ComputedRef, MaybeRef, Ref, TemplateRef } from 'vue';\n\nexport interface UseAutoCompleteFocusOptions {\n /** Whether custom (free-text) values are allowed. */\n customValue: MaybeRef<boolean>;\n /** Whether to apply the current value as the search text when focused. */\n shouldApplyValueAsSearch: MaybeRef<boolean>;\n /** Whether the autocomplete is disabled. */\n disabled: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteFocusDeps {\n activatorFocused: Ref<boolean>;\n activatorFocusedWithin: Ref<boolean>;\n focusedValueIndex: Ref<number>;\n internalSearch: Ref<string>;\n isOpen: Ref<boolean>;\n justOpened: Ref<boolean>;\n menuWrapperFocusedWithin: Ref<boolean>;\n searchInputFocused: Ref<boolean>;\n textInput: Ref<HTMLInputElement | undefined> | TemplateRef<HTMLInputElement>;\n setSearchAsValue: () => void;\n updateInternalSearch: (value?: string) => void;\n}\n\nexport interface UseAutoCompleteFocusReturn {\n anyFocused: ComputedRef<boolean>;\n inputClass: ComputedRef<string>;\n onActivatorFocused: () => Promise<void>;\n onInputFocused: () => void;\n setInputFocus: () => Promise<void>;\n}\n\nexport function useAutoCompleteFocus(\n options: UseAutoCompleteFocusOptions,\n deps: UseAutoCompleteFocusDeps,\n): UseAutoCompleteFocusReturn {\n const anyFocused = computed<boolean>(\n () => get(deps.activatorFocusedWithin) || get(deps.menuWrapperFocusedWithin),\n );\n\n const inputClass = computed<string>(() => {\n const isFocused = get(anyFocused);\n const isDisabled = get(options.disabled);\n const shouldApply = get(options.shouldApplyValueAsSearch);\n const hasSearch = get(deps.internalSearch);\n\n if ((!isFocused || isDisabled) && !shouldApply)\n return 'w-0 h-0';\n if (hasSearch)\n return 'flex-1 min-w-[4rem]';\n return 'flex-1 min-w-0';\n });\n\n async function setInputFocus(): Promise<void> {\n await nextTick(() => {\n set(deps.searchInputFocused, true);\n });\n }\n\n async function onActivatorFocused(): Promise<void> {\n await nextTick(() => {\n if (!get(deps.activatorFocused)) {\n set(deps.searchInputFocused, true);\n }\n });\n }\n\n function onInputFocused(): void {\n set(deps.focusedValueIndex, -1);\n\n const shouldApply = get(options.shouldApplyValueAsSearch);\n if (shouldApply) {\n const textInput = get(deps.textInput);\n textInput?.select();\n }\n\n if (!get(deps.isOpen)) {\n set(deps.justOpened, true);\n }\n }\n\n // Capture the search value immediately when focus is lost, before the debounced\n // handler fires. Other watchers (value.ts) may clear internalSearch when isOpen\n // changes, so we need to preserve it for the custom value blur flow.\n let pendingCustomSearch = '';\n\n watch(anyFocused, (focused) => {\n if (!focused && get(options.customValue)) {\n pendingCustomSearch = get(deps.internalSearch);\n }\n });\n\n // Close menu if the activator is not focused anymore\n // Using debounced to avoid the menu closing momentarily while focus switches.\n watchDebounced(\n anyFocused,\n (focused) => {\n if (!focused) {\n set(deps.isOpen, false);\n\n const customValue = get(options.customValue);\n const searchToUse = pendingCustomSearch;\n pendingCustomSearch = '';\n\n if (customValue && searchToUse) {\n deps.updateInternalSearch(searchToUse);\n deps.setSearchAsValue();\n }\n\n const shouldApply = get(options.shouldApplyValueAsSearch);\n if (!shouldApply) {\n deps.updateInternalSearch();\n }\n }\n },\n {\n debounce: 200,\n maxWait: 400,\n },\n );\n\n return {\n anyFocused,\n inputClass,\n onActivatorFocused,\n onInputFocused,\n setInputFocus,\n };\n}\n"],"names":[],"mappings":";;;AAiCO,SAAS,qBACd,SACA,MAC4B;AAC5B,QAAM,aAAa;AAAA,IACjB,MAAM,IAAI,KAAK,sBAAsB,KAAK,IAAI,KAAK,wBAAwB;AAAA,EAAA;AAG7E,QAAM,aAAa,SAAiB,MAAM;AACxC,UAAM,YAAY,IAAI,UAAU;AAChC,UAAM,aAAa,IAAI,QAAQ,QAAQ;AACvC,UAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,UAAM,YAAY,IAAI,KAAK,cAAc;AAEzC,SAAK,CAAC,aAAa,eAAe,CAAC;AACjC,aAAO;AACT,QAAI;AACF,aAAO;AACT,WAAO;AAAA,EACT,CAAC;AAED,iBAAe,gBAA+B;AAC5C,UAAM,SAAS,MAAM;AACnB,UAAI,KAAK,oBAAoB,IAAI;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,iBAAe,qBAAoC;AACjD,UAAM,SAAS,MAAM;AACnB,UAAI,CAAC,IAAI,KAAK,gBAAgB,GAAG;AAC/B,YAAI,KAAK,oBAAoB,IAAI;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,iBAAuB;AAC9B,QAAI,KAAK,mBAAmB,EAAE;AAE9B,UAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,QAAI,aAAa;AACf,YAAM,YAAY,IAAI,KAAK,SAAS;AACpC,iBAAW,OAAA;AAAA,IACb;AAEA,QAAI,CAAC,IAAI,KAAK,MAAM,GAAG;AACrB,UAAI,KAAK,YAAY,IAAI;AAAA,IAC3B;AAAA,EACF;AAKA,MAAI,sBAAsB;AAE1B,QAAM,YAAY,CAAC,YAAY;AAC7B,QAAI,CAAC,WAAW,IAAI,QAAQ,WAAW,GAAG;AACxC,4BAAsB,IAAI,KAAK,cAAc;AAAA,IAC/C;AAAA,EACF,CAAC;AAID;AAAA,IACE;AAAA,IACA,CAAC,YAAY;AACX,UAAI,CAAC,SAAS;AACZ,YAAI,KAAK,QAAQ,KAAK;AAEtB,cAAM,cAAc,IAAI,QAAQ,WAAW;AAC3C,cAAM,cAAc;AACpB,8BAAsB;AAEtB,YAAI,eAAe,aAAa;AAC9B,eAAK,qBAAqB,WAAW;AACrC,eAAK,iBAAA;AAAA,QACP;AAEA,cAAM,cAAc,IAAI,QAAQ,wBAAwB;AACxD,YAAI,CAAC,aAAa;AAChB,eAAK,qBAAA;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA,EACX;AAGF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1,12 +1,3 @@
1
- export declare function useAutoCompleteProps<T extends object>(props: {
2
- keyProp?: Extract<keyof T, string>;
3
- textProp?: Extract<keyof T, string>;
4
- itemDisabledProp?: Extract<keyof T, string>;
5
- }): {
6
- getItemKey: (item: T) => string | undefined;
7
- getTextValue: (item: T) => string | undefined;
8
- isItemDisabled: (item: T) => boolean;
9
- };
10
1
  export * from './focus.js';
11
2
  export * from './keyboard-navigation.js';
12
3
  export * from './search.js';
@@ -1,20 +1,25 @@
1
- import type { MaybeRef, Ref } from 'vue';
1
+ import type { MaybeRef, Ref, TemplateRef } from 'vue';
2
2
  export interface UseAutoCompleteKeyboardNavigationOptions {
3
+ /** Whether multiple items can be selected. */
3
4
  multiple: MaybeRef<boolean>;
5
+ /** Whether custom (free-text) values are allowed. */
4
6
  customValue: MaybeRef<boolean>;
7
+ /** Whether selected values are displayed as chips. */
8
+ chips: MaybeRef<boolean>;
5
9
  }
6
10
  export interface UseAutoCompleteKeyboardNavigationDeps<TItem> {
7
11
  internalSearch: Ref<string>;
8
12
  value: Ref<TItem[]>;
9
13
  filteredOptions: Ref<TItem[]>;
10
- options: Ref<TItem[]>;
11
14
  isOpen: Ref<boolean>;
12
15
  highlightedIndex: Ref<number>;
13
16
  searchInputFocused: Ref<boolean>;
14
17
  applyHighlighted: () => void;
15
18
  clear: () => void;
19
+ removeValue: (item: TItem) => void;
16
20
  setSearchAsValue: () => void;
17
- activator: Ref<HTMLElement | undefined>;
21
+ getText: (item: TItem) => string | undefined;
22
+ activator: Ref<HTMLElement | undefined> | TemplateRef<HTMLElement>;
18
23
  }
19
24
  export interface UseAutoCompleteKeyboardNavigationReturn {
20
25
  focusedValueIndex: Ref<number>;
@@ -42,8 +42,7 @@ function useAutoCompleteKeyboardNavigation(options, deps) {
42
42
  if (typeof highlightedOption === "string") {
43
43
  hasExactMatch = highlightedOption === internalSearch;
44
44
  } else if (highlightedOption && typeof highlightedOption === "object") {
45
- const label = highlightedOption.label || highlightedOption.value || "";
46
- hasExactMatch = label === internalSearch;
45
+ hasExactMatch = (deps.getText(highlightedOption) ?? "") === internalSearch;
47
46
  }
48
47
  }
49
48
  if (!hasExactMatch) {
@@ -89,10 +88,17 @@ function useAutoCompleteKeyboardNavigation(options, deps) {
89
88
  const total = value.length;
90
89
  const multiple = get(options.multiple);
91
90
  if (!internalSearch && total > 0) {
92
- if (multiple)
93
- set(focusedValueIndex, total - 1);
94
- else
91
+ if (multiple) {
92
+ if (get(options.chips)) {
93
+ set(focusedValueIndex, total - 1);
94
+ } else {
95
+ const lastItem = value[total - 1];
96
+ if (lastItem !== void 0)
97
+ deps.removeValue(lastItem);
98
+ }
99
+ } else {
95
100
  deps.clear();
101
+ }
96
102
  }
97
103
  }
98
104
  watch(deps.value, () => {
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-navigation.js","sources":["../../../../src/composables/forms/auto-complete/keyboard-navigation.ts"],"sourcesContent":["import type { MaybeRef, Ref } from 'vue';\nimport { get, set } from '@vueuse/shared';\n\nexport interface UseAutoCompleteKeyboardNavigationOptions {\n multiple: MaybeRef<boolean>;\n customValue: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationDeps<TItem> {\n internalSearch: Ref<string>;\n value: Ref<TItem[]>;\n filteredOptions: Ref<TItem[]>;\n options: Ref<TItem[]>;\n isOpen: Ref<boolean>;\n highlightedIndex: Ref<number>;\n searchInputFocused: Ref<boolean>;\n applyHighlighted: () => void;\n clear: () => void;\n setSearchAsValue: () => void;\n activator: Ref<HTMLElement | undefined>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationReturn {\n focusedValueIndex: Ref<number>;\n moveSelectedValueHighlight: (event: KeyboardEvent, next: boolean) => void;\n onEnter: (event: KeyboardEvent) => void;\n onTab: (event: KeyboardEvent) => void;\n onInputDeletePressed: () => void;\n setValueFocus: (index: number) => void;\n}\n\nexport function useAutoCompleteKeyboardNavigation<TItem>(\n options: UseAutoCompleteKeyboardNavigationOptions,\n deps: UseAutoCompleteKeyboardNavigationDeps<TItem>,\n): UseAutoCompleteKeyboardNavigationReturn {\n const focusedValueIndex = ref<number>(-1);\n const chipElementsCache = ref<Map<number, HTMLElement>>(new Map());\n\n function setValueFocus(index: number): void {\n set(focusedValueIndex, index);\n }\n\n function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean): void {\n const multiple = get(options.multiple);\n const internalSearchValue = get(deps.internalSearch);\n\n if (!multiple || internalSearchValue.length > 0)\n return;\n\n event.preventDefault();\n const total = get(deps.value).length;\n\n let current = get(focusedValueIndex);\n\n if (current === -1) {\n set(focusedValueIndex, next ? 0 : total - 1);\n return;\n }\n\n const move = next ? 1 : -1;\n current += move;\n\n if (current < 0 || current >= total) {\n set(focusedValueIndex, -1);\n set(deps.searchInputFocused, true);\n }\n else {\n set(focusedValueIndex, current);\n }\n }\n\n function onEnter(event: KeyboardEvent): void {\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const isOpen = get(deps.isOpen);\n const customValue = get(options.customValue);\n const multiple = get(options.multiple);\n const value = get(deps.value);\n const internalSearch = get(deps.internalSearch);\n\n // Check if we should use custom value\n if (customValue && internalSearch && isOpen) {\n // Check if any option exactly matches the search term\n let hasExactMatch = false;\n\n if (highlightedIndex > -1 && highlightedIndex < filteredOptions.length) {\n const highlightedOption = filteredOptions[highlightedIndex];\n if (typeof highlightedOption === 'string') {\n hasExactMatch = highlightedOption === internalSearch;\n }\n else if (highlightedOption && typeof highlightedOption === 'object') {\n const label = (highlightedOption as any).label || (highlightedOption as any).value || '';\n hasExactMatch = label === internalSearch;\n }\n }\n\n // If no exact match with highlighted option, use custom value\n if (!hasExactMatch) {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n return;\n }\n }\n\n if (filteredOptions.length > 0 && highlightedIndex > -1 && isOpen) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n else if (filteredOptions.length === 0 && customValue && internalSearch) {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n }\n else if (!isOpen && value.length === 0) {\n set(deps.isOpen, true);\n event.preventDefault();\n }\n else {\n const activator = get(deps.activator);\n const form = activator?.closest('form');\n if (form) {\n form.dispatchEvent(new Event('submit'));\n }\n }\n }\n\n function onTab(event: KeyboardEvent): void {\n const isOpen = get(deps.isOpen);\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const multiple = get(options.multiple);\n\n if (isOpen && filteredOptions.length > 0 && highlightedIndex > -1 && !multiple) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n }\n\n function onInputDeletePressed(): void {\n const internalSearch = get(deps.internalSearch);\n const value = get(deps.value);\n const total = value.length;\n const multiple = get(options.multiple);\n\n if (!internalSearch && total > 0) {\n if (multiple)\n set(focusedValueIndex, total - 1);\n else\n deps.clear();\n }\n }\n\n // Watch value changes to reset focused index and clear cache\n watch(deps.value, () => {\n setValueFocus(-1);\n get(chipElementsCache).clear();\n });\n\n // Watch focused value index to handle chip focus\n watch(focusedValueIndex, async (index) => {\n const multiple = get(options.multiple);\n if (index === -1 || !multiple)\n return;\n\n await nextTick(() => {\n const cache = get(chipElementsCache);\n let activeChip = cache.get(index);\n\n if (!activeChip) {\n const activator = get(deps.activator);\n const element = activator?.querySelector(`[data-index=\"${index}\"]`) as HTMLElement | null;\n if (element) {\n activeChip = element;\n cache.set(index, element);\n }\n }\n\n if (activeChip && 'focus' in activeChip && typeof activeChip.focus === 'function')\n activeChip.focus();\n });\n });\n\n return {\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onTab,\n setValueFocus,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keyboard-navigation.js","sources":["../../../../src/composables/forms/auto-complete/keyboard-navigation.ts"],"sourcesContent":["import type { MaybeRef, Ref, TemplateRef } from 'vue';\nimport { get, set } from '@vueuse/shared';\n\nexport interface UseAutoCompleteKeyboardNavigationOptions {\n /** Whether multiple items can be selected. */\n multiple: MaybeRef<boolean>;\n /** Whether custom (free-text) values are allowed. */\n customValue: MaybeRef<boolean>;\n /** Whether selected values are displayed as chips. */\n chips: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationDeps<TItem> {\n internalSearch: Ref<string>;\n value: Ref<TItem[]>;\n filteredOptions: Ref<TItem[]>;\n isOpen: Ref<boolean>;\n highlightedIndex: Ref<number>;\n searchInputFocused: Ref<boolean>;\n applyHighlighted: () => void;\n clear: () => void;\n removeValue: (item: TItem) => void;\n setSearchAsValue: () => void;\n getText: (item: TItem) => string | undefined;\n activator: Ref<HTMLElement | undefined> | TemplateRef<HTMLElement>;\n}\n\nexport interface UseAutoCompleteKeyboardNavigationReturn {\n focusedValueIndex: Ref<number>;\n moveSelectedValueHighlight: (event: KeyboardEvent, next: boolean) => void;\n onEnter: (event: KeyboardEvent) => void;\n onTab: (event: KeyboardEvent) => void;\n onInputDeletePressed: () => void;\n setValueFocus: (index: number) => void;\n}\n\nexport function useAutoCompleteKeyboardNavigation<TItem>(\n options: UseAutoCompleteKeyboardNavigationOptions,\n deps: UseAutoCompleteKeyboardNavigationDeps<TItem>,\n): UseAutoCompleteKeyboardNavigationReturn {\n const focusedValueIndex = ref<number>(-1);\n const chipElementsCache = ref<Map<number, HTMLElement>>(new Map());\n\n function setValueFocus(index: number): void {\n set(focusedValueIndex, index);\n }\n\n function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean): void {\n const multiple = get(options.multiple);\n const internalSearchValue = get(deps.internalSearch);\n\n if (!multiple || internalSearchValue.length > 0)\n return;\n\n event.preventDefault();\n const total = get(deps.value).length;\n\n let current = get(focusedValueIndex);\n\n if (current === -1) {\n set(focusedValueIndex, next ? 0 : total - 1);\n return;\n }\n\n const move = next ? 1 : -1;\n current += move;\n\n if (current < 0 || current >= total) {\n set(focusedValueIndex, -1);\n set(deps.searchInputFocused, true);\n }\n else {\n set(focusedValueIndex, current);\n }\n }\n\n function onEnter(event: KeyboardEvent): void {\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const isOpen = get(deps.isOpen);\n const customValue = get(options.customValue);\n const multiple = get(options.multiple);\n const value = get(deps.value);\n const internalSearch = get(deps.internalSearch);\n\n // Check if we should use custom value\n if (customValue && internalSearch && isOpen) {\n // Check if any option exactly matches the search term\n let hasExactMatch = false;\n\n if (highlightedIndex > -1 && highlightedIndex < filteredOptions.length) {\n const highlightedOption = filteredOptions[highlightedIndex];\n if (typeof highlightedOption === 'string') {\n hasExactMatch = highlightedOption === internalSearch;\n }\n else if (highlightedOption && typeof highlightedOption === 'object') {\n hasExactMatch = (deps.getText(highlightedOption) ?? '') === internalSearch;\n }\n }\n\n // If no exact match with highlighted option, use custom value\n if (!hasExactMatch) {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n return;\n }\n }\n\n if (filteredOptions.length > 0 && highlightedIndex > -1 && isOpen) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n else if (filteredOptions.length === 0 && customValue && internalSearch) {\n deps.setSearchAsValue();\n if (!multiple)\n set(deps.isOpen, false);\n event.preventDefault();\n }\n else if (!isOpen && value.length === 0) {\n set(deps.isOpen, true);\n event.preventDefault();\n }\n else {\n const activator = get(deps.activator);\n const form = activator?.closest('form');\n if (form) {\n form.dispatchEvent(new Event('submit'));\n }\n }\n }\n\n function onTab(event: KeyboardEvent): void {\n const isOpen = get(deps.isOpen);\n const filteredOptions = get(deps.filteredOptions);\n const highlightedIndex = get(deps.highlightedIndex);\n const multiple = get(options.multiple);\n\n if (isOpen && filteredOptions.length > 0 && highlightedIndex > -1 && !multiple) {\n deps.applyHighlighted();\n event.preventDefault();\n }\n }\n\n function onInputDeletePressed(): void {\n const internalSearch = get(deps.internalSearch);\n const value = get(deps.value);\n const total = value.length;\n const multiple = get(options.multiple);\n\n if (!internalSearch && total > 0) {\n if (multiple) {\n if (get(options.chips)) {\n set(focusedValueIndex, total - 1);\n }\n else {\n const lastItem = value[total - 1];\n if (lastItem !== undefined)\n deps.removeValue(lastItem);\n }\n }\n else {\n deps.clear();\n }\n }\n }\n\n // Watch value changes to reset focused index and clear cache\n watch(deps.value, () => {\n setValueFocus(-1);\n get(chipElementsCache).clear();\n });\n\n // Watch focused value index to handle chip focus\n watch(focusedValueIndex, async (index) => {\n const multiple = get(options.multiple);\n if (index === -1 || !multiple)\n return;\n\n await nextTick(() => {\n const cache = get(chipElementsCache);\n let activeChip = cache.get(index);\n\n if (!activeChip) {\n const activator = get(deps.activator);\n const element = activator?.querySelector(`[data-index=\"${index}\"]`) as HTMLElement | null;\n if (element) {\n activeChip = element;\n cache.set(index, element);\n }\n }\n\n if (activeChip && 'focus' in activeChip && typeof activeChip.focus === 'function')\n activeChip.focus();\n });\n });\n\n return {\n focusedValueIndex,\n moveSelectedValueHighlight,\n onEnter,\n onInputDeletePressed,\n onTab,\n setValueFocus,\n };\n}\n"],"names":[],"mappings":";;AAoCO,SAAS,kCACd,SACA,MACyC;AACzC,QAAM,oBAAoB,IAAY,EAAE;AACxC,QAAM,oBAAoB,IAA8B,oBAAI,KAAK;AAEjE,WAAS,cAAc,OAAqB;AAC1C,QAAI,mBAAmB,KAAK;AAAA,EAC9B;AAEA,WAAS,2BAA2B,OAAsB,MAAqB;AAC7E,UAAM,WAAW,IAAI,QAAQ,QAAQ;AACrC,UAAM,sBAAsB,IAAI,KAAK,cAAc;AAEnD,QAAI,CAAC,YAAY,oBAAoB,SAAS;AAC5C;AAEF,UAAM,eAAA;AACN,UAAM,QAAQ,IAAI,KAAK,KAAK,EAAE;AAE9B,QAAI,UAAU,IAAI,iBAAiB;AAEnC,QAAI,YAAY,IAAI;AAClB,UAAI,mBAAmB,OAAO,IAAI,QAAQ,CAAC;AAC3C;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,IAAI;AACxB,eAAW;AAEX,QAAI,UAAU,KAAK,WAAW,OAAO;AACnC,UAAI,mBAAmB,EAAE;AACzB,UAAI,KAAK,oBAAoB,IAAI;AAAA,IACnC,OACK;AACH,UAAI,mBAAmB,OAAO;AAAA,IAChC;AAAA,EACF;AAEA,WAAS,QAAQ,OAA4B;AAC3C,UAAM,kBAAkB,IAAI,KAAK,eAAe;AAChD,UAAM,mBAAmB,IAAI,KAAK,gBAAgB;AAClD,UAAM,SAAS,IAAI,KAAK,MAAM;AAC9B,UAAM,cAAc,IAAI,QAAQ,WAAW;AAC3C,UAAM,WAAW,IAAI,QAAQ,QAAQ;AACrC,UAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,UAAM,iBAAiB,IAAI,KAAK,cAAc;AAG9C,QAAI,eAAe,kBAAkB,QAAQ;AAE3C,UAAI,gBAAgB;AAEpB,UAAI,mBAAmB,MAAM,mBAAmB,gBAAgB,QAAQ;AACtE,cAAM,oBAAoB,gBAAgB,gBAAgB;AAC1D,YAAI,OAAO,sBAAsB,UAAU;AACzC,0BAAgB,sBAAsB;AAAA,QACxC,WACS,qBAAqB,OAAO,sBAAsB,UAAU;AACnE,2BAAiB,KAAK,QAAQ,iBAAiB,KAAK,QAAQ;AAAA,QAC9D;AAAA,MACF;AAGA,UAAI,CAAC,eAAe;AAClB,aAAK,iBAAA;AACL,YAAI,CAAC;AACH,cAAI,KAAK,QAAQ,KAAK;AACxB,cAAM,eAAA;AACN;AAAA,MACF;AAAA,IACF;AAEA,QAAI,gBAAgB,SAAS,KAAK,mBAAmB,MAAM,QAAQ;AACjE,WAAK,iBAAA;AACL,YAAM,eAAA;AAAA,IACR,WACS,gBAAgB,WAAW,KAAK,eAAe,gBAAgB;AACtE,WAAK,iBAAA;AACL,UAAI,CAAC;AACH,YAAI,KAAK,QAAQ,KAAK;AACxB,YAAM,eAAA;AAAA,IACR,WACS,CAAC,UAAU,MAAM,WAAW,GAAG;AACtC,UAAI,KAAK,QAAQ,IAAI;AACrB,YAAM,eAAA;AAAA,IACR,OACK;AACH,YAAM,YAAY,IAAI,KAAK,SAAS;AACpC,YAAM,OAAO,WAAW,QAAQ,MAAM;AACtC,UAAI,MAAM;AACR,aAAK,cAAc,IAAI,MAAM,QAAQ,CAAC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAEA,WAAS,MAAM,OAA4B;AACzC,UAAM,SAAS,IAAI,KAAK,MAAM;AAC9B,UAAM,kBAAkB,IAAI,KAAK,eAAe;AAChD,UAAM,mBAAmB,IAAI,KAAK,gBAAgB;AAClD,UAAM,WAAW,IAAI,QAAQ,QAAQ;AAErC,QAAI,UAAU,gBAAgB,SAAS,KAAK,mBAAmB,MAAM,CAAC,UAAU;AAC9E,WAAK,iBAAA;AACL,YAAM,eAAA;AAAA,IACR;AAAA,EACF;AAEA,WAAS,uBAA6B;AACpC,UAAM,iBAAiB,IAAI,KAAK,cAAc;AAC9C,UAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,UAAM,QAAQ,MAAM;AACpB,UAAM,WAAW,IAAI,QAAQ,QAAQ;AAErC,QAAI,CAAC,kBAAkB,QAAQ,GAAG;AAChC,UAAI,UAAU;AACZ,YAAI,IAAI,QAAQ,KAAK,GAAG;AACtB,cAAI,mBAAmB,QAAQ,CAAC;AAAA,QAClC,OACK;AACH,gBAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,cAAI,aAAa;AACf,iBAAK,YAAY,QAAQ;AAAA,QAC7B;AAAA,MACF,OACK;AACH,aAAK,MAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAGA,QAAM,KAAK,OAAO,MAAM;AACtB,kBAAc,EAAE;AAChB,QAAI,iBAAiB,EAAE,MAAA;AAAA,EACzB,CAAC;AAGD,QAAM,mBAAmB,OAAO,UAAU;AACxC,UAAM,WAAW,IAAI,QAAQ,QAAQ;AACrC,QAAI,UAAU,MAAM,CAAC;AACnB;AAEF,UAAM,SAAS,MAAM;AACnB,YAAM,QAAQ,IAAI,iBAAiB;AACnC,UAAI,aAAa,MAAM,IAAI,KAAK;AAEhC,UAAI,CAAC,YAAY;AACf,cAAM,YAAY,IAAI,KAAK,SAAS;AACpC,cAAM,UAAU,WAAW,cAAc,gBAAgB,KAAK,IAAI;AAClE,YAAI,SAAS;AACX,uBAAa;AACb,gBAAM,IAAI,OAAO,OAAO;AAAA,QAC1B;AAAA,MACF;AAEA,UAAI,cAAc,WAAW,cAAc,OAAO,WAAW,UAAU;AACrE,mBAAW,MAAA;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1,11 +1,18 @@
1
1
  import type { MaybeRef, Ref } from 'vue';
2
2
  export interface UseAutoCompleteSearchOptions<TItem> {
3
+ /** The property used as the unique identifier for each item. */
3
4
  keyAttr?: MaybeRef<keyof TItem | undefined>;
5
+ /** The property used to display text for each item. */
4
6
  textAttr?: MaybeRef<keyof TItem | undefined>;
7
+ /** Whether to disable client-side filtering of options. */
5
8
  noFilter?: MaybeRef<boolean>;
9
+ /** Custom filter function to match items against the search query. */
6
10
  filter?: MaybeRef<((item: TItem, queryText: string) => boolean) | undefined>;
11
+ /** Whether custom (free-text) values are allowed. */
7
12
  customValue?: MaybeRef<boolean>;
13
+ /** Whether to hide the custom value option from the dropdown. */
8
14
  hideCustomValue?: MaybeRef<boolean>;
15
+ /** Whether to return the full item object instead of just the key. */
9
16
  returnObject?: MaybeRef<boolean>;
10
17
  }
11
18
  export interface UseAutoCompleteSearchReturn<TItem> {
@@ -1,16 +1,18 @@
1
1
  import { get, set } from "@vueuse/shared";
2
2
  import { getTextToken } from "../../../utils/helpers.js";
3
- import { ref, computed, watch, onUnmounted } from "vue";
3
+ import { shallowRef, computed, watch, onUnmounted } from "vue";
4
4
  import { refDebounced } from "@vueuse/core";
5
5
  function useAutoCompleteSearch(options, searchModel, opts) {
6
- const internalSearch = ref("");
6
+ const internalSearch = shallowRef("");
7
7
  const debouncedInternalSearch = refDebounced(internalSearch, 50);
8
- const justOpened = ref(false);
8
+ const justOpened = shallowRef(false);
9
+ const MAX_CACHE_SIZE = 500;
9
10
  const tokenCache = /* @__PURE__ */ new Map();
10
11
  const getCachedTextToken = (text) => {
11
- if (tokenCache.has(text)) {
12
+ if (tokenCache.has(text))
12
13
  return tokenCache.get(text);
13
- }
14
+ if (tokenCache.size >= MAX_CACHE_SIZE)
15
+ tokenCache.clear();
14
16
  const token = getTextToken(text);
15
17
  tokenCache.set(text, token);
16
18
  return token;
@@ -28,10 +30,10 @@ function useAutoCompleteSearch(options, searchModel, opts) {
28
30
  const usedFilter = filter || ((item) => {
29
31
  if (!item)
30
32
  return false;
31
- const keywords = [keyAttr ? getCachedTextToken(item[keyAttr]) : item.toString()];
33
+ const keywords = [getCachedTextToken(keyAttr ? String(item[keyAttr]) : item.toString())];
32
34
  if (textAttr && typeof item === "object")
33
- keywords.push(getCachedTextToken(item[textAttr]));
34
- return keywords.some((keyword) => getCachedTextToken(keyword).includes(searchToken));
35
+ keywords.push(getCachedTextToken(String(item[textAttr])));
36
+ return keywords.some((keyword) => keyword.includes(searchToken));
35
37
  });
36
38
  const filtered = optionsValue.filter((item) => usedFilter(item, search));
37
39
  const customValue = get(opts.customValue);
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sources":["../../../../src/composables/forms/auto-complete/search.ts"],"sourcesContent":["import type { MaybeRef, Ref } from 'vue';\nimport { get, set } from '@vueuse/shared';\nimport { getTextToken } from '@/utils/helpers';\n\nexport interface UseAutoCompleteSearchOptions<TItem> {\n keyAttr?: MaybeRef<keyof TItem | undefined>;\n textAttr?: MaybeRef<keyof TItem | undefined>;\n noFilter?: MaybeRef<boolean>;\n filter?: MaybeRef<((item: TItem, queryText: string) => boolean) | undefined>;\n customValue?: MaybeRef<boolean>;\n hideCustomValue?: MaybeRef<boolean>;\n returnObject?: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteSearchReturn<TItem> {\n internalSearch: Ref<string>;\n debouncedInternalSearch: Readonly<Ref<string>>;\n filteredOptions: Readonly<Ref<TItem[]>>;\n justOpened: Ref<boolean>;\n updateInternalSearch: (value?: string) => void;\n textValueToProperValue: (val: any, returnObject?: boolean) => TItem;\n}\n\nexport function useAutoCompleteSearch<TItem>(\n options: MaybeRef<TItem[]>,\n searchModel: Ref<string>,\n opts: UseAutoCompleteSearchOptions<TItem>,\n): UseAutoCompleteSearchReturn<TItem> {\n const internalSearch = ref<string>('');\n const debouncedInternalSearch = refDebounced(internalSearch, 50);\n const justOpened = ref<boolean>(false);\n\n // Memoize token computation to avoid repeated string processing\n const tokenCache = new Map<string, string>();\n const getCachedTextToken = (text: string): string => {\n if (tokenCache.has(text)) {\n return tokenCache.get(text)!;\n }\n const token = getTextToken(text);\n tokenCache.set(text, token);\n return token;\n };\n\n const filteredOptions = computed<TItem[]>(() => {\n const search = get(debouncedInternalSearch);\n const optionsValue = get(options);\n\n const noFilter = get(opts.noFilter);\n if (noFilter || !search || get(justOpened))\n return optionsValue;\n\n const keyAttr = get(opts.keyAttr);\n const textAttr = get(opts.textAttr);\n const filter = get(opts.filter);\n\n // Cache the search token once\n const searchToken = getCachedTextToken(search);\n\n const usedFilter = filter || ((item) => {\n if (!item)\n return false;\n\n const keywords: string[] = [keyAttr ? getCachedTextToken((item as any)[keyAttr]) : item.toString()];\n\n if (textAttr && typeof item === 'object')\n keywords.push(getCachedTextToken((item as any)[textAttr]));\n\n return keywords.some(keyword => getCachedTextToken(keyword).includes(searchToken));\n });\n\n const filtered = optionsValue.filter(item => usedFilter(item, search));\n\n const customValue = get(opts.customValue);\n const hideCustomValue = get(opts.hideCustomValue);\n if (!customValue || hideCustomValue || !search) {\n return filtered;\n }\n\n // Use a more efficient check for custom value inclusion\n const isCustomValueIncluded = filtered.some((item) => {\n if (!item) {\n return false;\n }\n\n const val = textAttr ? (item as any)[textAttr] : item.toString();\n return val === search;\n });\n\n if (isCustomValueIncluded) {\n return filtered;\n }\n\n return [textValueToProperValue(search, {\n keyAttr,\n returnObject: get(opts.returnObject),\n textAttr,\n }), ...filtered];\n });\n\n function textValueToProperValue(val: any, options: { keyAttr?: keyof TItem; textAttr?: keyof TItem; returnObject?: boolean }): TItem {\n const { keyAttr, returnObject, textAttr } = options;\n if (!keyAttr || returnObject)\n return val;\n\n return {\n [keyAttr]: val,\n ...(\n textAttr\n ? { [textAttr]: val }\n : {}\n ),\n } as TItem;\n }\n\n function updateInternalSearch(value: string = ''): void {\n set(searchModel, value);\n set(internalSearch, value);\n }\n\n watch(searchModel, (search) => {\n set(internalSearch, search);\n }, { immediate: true });\n\n // Clear token cache when component unmounts to prevent memory leaks\n onUnmounted(() => {\n tokenCache.clear();\n });\n\n return {\n debouncedInternalSearch,\n filteredOptions,\n internalSearch,\n justOpened,\n textValueToProperValue: (val: any, returnObjectOverride: boolean = false) => textValueToProperValue(val, {\n keyAttr: get(opts.keyAttr),\n returnObject: returnObjectOverride || get(opts.returnObject),\n textAttr: get(opts.textAttr),\n }),\n updateInternalSearch,\n };\n}\n"],"names":["options"],"mappings":";;;;AAuBO,SAAS,sBACd,SACA,aACA,MACoC;AACpC,QAAM,iBAAiB,IAAY,EAAE;AACrC,QAAM,0BAA0B,aAAa,gBAAgB,EAAE;AAC/D,QAAM,aAAa,IAAa,KAAK;AAGrC,QAAM,iCAAiB,IAAA;AACvB,QAAM,qBAAqB,CAAC,SAAyB;AACnD,QAAI,WAAW,IAAI,IAAI,GAAG;AACxB,aAAO,WAAW,IAAI,IAAI;AAAA,IAC5B;AACA,UAAM,QAAQ,aAAa,IAAI;AAC/B,eAAW,IAAI,MAAM,KAAK;AAC1B,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,SAAkB,MAAM;AAC9C,UAAM,SAAS,IAAI,uBAAuB;AAC1C,UAAM,eAAe,IAAI,OAAO;AAEhC,UAAM,WAAW,IAAI,KAAK,QAAQ;AAClC,QAAI,YAAY,CAAC,UAAU,IAAI,UAAU;AACvC,aAAO;AAET,UAAM,UAAU,IAAI,KAAK,OAAO;AAChC,UAAM,WAAW,IAAI,KAAK,QAAQ;AAClC,UAAM,SAAS,IAAI,KAAK,MAAM;AAG9B,UAAM,cAAc,mBAAmB,MAAM;AAE7C,UAAM,aAAa,WAAW,CAAC,SAAS;AACtC,UAAI,CAAC;AACH,eAAO;AAET,YAAM,WAAqB,CAAC,UAAU,mBAAoB,KAAa,OAAO,CAAC,IAAI,KAAK,UAAU;AAElG,UAAI,YAAY,OAAO,SAAS;AAC9B,iBAAS,KAAK,mBAAoB,KAAa,QAAQ,CAAC,CAAC;AAE3D,aAAO,SAAS,KAAK,CAAA,YAAW,mBAAmB,OAAO,EAAE,SAAS,WAAW,CAAC;AAAA,IACnF;AAEA,UAAM,WAAW,aAAa,OAAO,UAAQ,WAAW,MAAM,MAAM,CAAC;AAErE,UAAM,cAAc,IAAI,KAAK,WAAW;AACxC,UAAM,kBAAkB,IAAI,KAAK,eAAe;AAChD,QAAI,CAAC,eAAe,mBAAmB,CAAC,QAAQ;AAC9C,aAAO;AAAA,IACT;AAGA,UAAM,wBAAwB,SAAS,KAAK,CAAC,SAAS;AACpD,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,MACT;AAEA,YAAM,MAAM,WAAY,KAAa,QAAQ,IAAI,KAAK,SAAA;AACtD,aAAO,QAAQ;AAAA,IACjB,CAAC;AAED,QAAI,uBAAuB;AACzB,aAAO;AAAA,IACT;AAEA,WAAO,CAAC,uBAAuB,QAAQ;AAAA,MACrC;AAAA,MACA,cAAc,IAAI,KAAK,YAAY;AAAA,MACnC;AAAA,IAAA,CACD,GAAG,GAAG,QAAQ;AAAA,EACjB,CAAC;AAED,WAAS,uBAAuB,KAAUA,UAA2F;AACnI,UAAM,EAAE,SAAS,cAAc,SAAA,IAAaA;AAC5C,QAAI,CAAC,WAAW;AACd,aAAO;AAET,WAAO;AAAA,MACL,CAAC,OAAO,GAAG;AAAA,MACX,GACE,WACI,EAAE,CAAC,QAAQ,GAAG,IAAA,IACd,CAAA;AAAA,IAAC;AAAA,EAGX;AAEA,WAAS,qBAAqB,QAAgB,IAAU;AACtD,QAAI,aAAa,KAAK;AACtB,QAAI,gBAAgB,KAAK;AAAA,EAC3B;AAEA,QAAM,aAAa,CAAC,WAAW;AAC7B,QAAI,gBAAgB,MAAM;AAAA,EAC5B,GAAG,EAAE,WAAW,MAAM;AAGtB,cAAY,MAAM;AAChB,eAAW,MAAA;AAAA,EACb,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,wBAAwB,CAAC,KAAU,uBAAgC,UAAU,uBAAuB,KAAK;AAAA,MACvG,SAAS,IAAI,KAAK,OAAO;AAAA,MACzB,cAAc,wBAAwB,IAAI,KAAK,YAAY;AAAA,MAC3D,UAAU,IAAI,KAAK,QAAQ;AAAA,IAAA,CAC5B;AAAA,IACD;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"search.js","sources":["../../../../src/composables/forms/auto-complete/search.ts"],"sourcesContent":["import type { MaybeRef, Ref } from 'vue';\nimport { get, set } from '@vueuse/shared';\nimport { getTextToken } from '@/utils/helpers';\n\nexport interface UseAutoCompleteSearchOptions<TItem> {\n /** The property used as the unique identifier for each item. */\n keyAttr?: MaybeRef<keyof TItem | undefined>;\n /** The property used to display text for each item. */\n textAttr?: MaybeRef<keyof TItem | undefined>;\n /** Whether to disable client-side filtering of options. */\n noFilter?: MaybeRef<boolean>;\n /** Custom filter function to match items against the search query. */\n filter?: MaybeRef<((item: TItem, queryText: string) => boolean) | undefined>;\n /** Whether custom (free-text) values are allowed. */\n customValue?: MaybeRef<boolean>;\n /** Whether to hide the custom value option from the dropdown. */\n hideCustomValue?: MaybeRef<boolean>;\n /** Whether to return the full item object instead of just the key. */\n returnObject?: MaybeRef<boolean>;\n}\n\nexport interface UseAutoCompleteSearchReturn<TItem> {\n internalSearch: Ref<string>;\n debouncedInternalSearch: Readonly<Ref<string>>;\n filteredOptions: Readonly<Ref<TItem[]>>;\n justOpened: Ref<boolean>;\n updateInternalSearch: (value?: string) => void;\n textValueToProperValue: (val: any, returnObject?: boolean) => TItem;\n}\n\nexport function useAutoCompleteSearch<TItem>(\n options: MaybeRef<TItem[]>,\n searchModel: Ref<string>,\n opts: UseAutoCompleteSearchOptions<TItem>,\n): UseAutoCompleteSearchReturn<TItem> {\n const internalSearch = shallowRef<string>('');\n const debouncedInternalSearch = refDebounced(internalSearch, 50);\n const justOpened = shallowRef<boolean>(false);\n\n // Memoize token computation to avoid repeated string processing\n const MAX_CACHE_SIZE = 500;\n const tokenCache = new Map<string, string>();\n const getCachedTextToken = (text: string): string => {\n if (tokenCache.has(text))\n return tokenCache.get(text)!;\n\n if (tokenCache.size >= MAX_CACHE_SIZE)\n tokenCache.clear();\n\n const token = getTextToken(text);\n tokenCache.set(text, token);\n return token;\n };\n\n const filteredOptions = computed<TItem[]>(() => {\n const search = get(debouncedInternalSearch);\n const optionsValue = get(options);\n\n const noFilter = get(opts.noFilter);\n if (noFilter || !search || get(justOpened))\n return optionsValue;\n\n const keyAttr = get(opts.keyAttr);\n const textAttr = get(opts.textAttr);\n const filter = get(opts.filter);\n\n // Cache the search token once\n const searchToken = getCachedTextToken(search);\n\n const usedFilter = filter || ((item) => {\n if (!item)\n return false;\n\n const keywords: string[] = [getCachedTextToken(keyAttr ? String((item as any)[keyAttr]) : item.toString())];\n\n if (textAttr && typeof item === 'object')\n keywords.push(getCachedTextToken(String((item as any)[textAttr])));\n\n return keywords.some(keyword => keyword.includes(searchToken));\n });\n\n const filtered = optionsValue.filter(item => usedFilter(item, search));\n\n const customValue = get(opts.customValue);\n const hideCustomValue = get(opts.hideCustomValue);\n if (!customValue || hideCustomValue || !search) {\n return filtered;\n }\n\n // Use a more efficient check for custom value inclusion\n const isCustomValueIncluded = filtered.some((item) => {\n if (!item) {\n return false;\n }\n\n const val = textAttr ? (item as any)[textAttr] : item.toString();\n return val === search;\n });\n\n if (isCustomValueIncluded) {\n return filtered;\n }\n\n return [textValueToProperValue(search, {\n keyAttr,\n returnObject: get(opts.returnObject),\n textAttr,\n }), ...filtered];\n });\n\n function textValueToProperValue(val: any, options: { keyAttr?: keyof TItem; textAttr?: keyof TItem; returnObject?: boolean }): TItem {\n const { keyAttr, returnObject, textAttr } = options;\n if (!keyAttr || returnObject)\n return val;\n\n return {\n [keyAttr]: val,\n ...(\n textAttr\n ? { [textAttr]: val }\n : {}\n ),\n } as TItem;\n }\n\n function updateInternalSearch(value: string = ''): void {\n set(searchModel, value);\n set(internalSearch, value);\n }\n\n watch(searchModel, (search) => {\n set(internalSearch, search);\n }, { immediate: true });\n\n // Clear token cache when component unmounts to prevent memory leaks\n onUnmounted(() => {\n tokenCache.clear();\n });\n\n return {\n debouncedInternalSearch,\n filteredOptions,\n internalSearch,\n justOpened,\n textValueToProperValue: (val: any, returnObjectOverride: boolean = false) => textValueToProperValue(val, {\n keyAttr: get(opts.keyAttr),\n returnObject: returnObjectOverride || get(opts.returnObject),\n textAttr: get(opts.textAttr),\n }),\n updateInternalSearch,\n };\n}\n"],"names":["options"],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  import type { ComputedRef, MaybeRef, Ref } from 'vue';
2
2
  export interface UseAutoCompleteValueOptions<TItem> {
3
+ /** The property used as the unique identifier for each item. */
3
4
  keyAttr?: MaybeRef<keyof TItem | undefined>;
5
+ /** Whether to return the full item object instead of just the key. */
4
6
  returnObject?: MaybeRef<boolean>;
7
+ /** Whether custom (free-text) values are allowed. */
5
8
  customValue?: MaybeRef<boolean>;
6
9
  }
7
10
  export interface UseAutoCompleteValueReturn<TItem> {
@@ -14,6 +17,7 @@ export interface UseAutoCompleteValueDeps<TItem> {
14
17
  textValueToProperValue: (val: any, returnObject?: boolean) => TItem;
15
18
  shouldApplyValueAsSearch: MaybeRef<boolean>;
16
19
  isOpen: MaybeRef<boolean>;
20
+ multiple: MaybeRef<boolean>;
17
21
  updateInternalSearch: (value?: string) => void;
18
22
  }
19
23
  export declare function useAutoCompleteValue<TValue, TItem>(modelValue: Ref<TValue | undefined>, options: MaybeRef<TItem[]>, opts: UseAutoCompleteValueOptions<TItem>, deps: UseAutoCompleteValueDeps<TItem>): UseAutoCompleteValueReturn<TItem>;