@teammodlr/ui-kit 2.1.41 → 2.1.44

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 (326) hide show
  1. package/env.d.ts +1 -1
  2. package/lib/components/avatar/Avatar.vue.d.ts +1 -1
  3. package/lib/components/avatargroup/AvatarGroup.vue.d.ts +1 -1
  4. package/lib/components/badge/Badge.vue.d.ts +1 -1
  5. package/lib/components/breadcrumb/Breadcrumb.vue.d.ts +1 -1
  6. package/lib/components/breadcrumb/BreadcrumbItem.vue.d.ts +1 -1
  7. package/lib/components/button/Button.vue.d.ts +1 -1
  8. package/lib/components/buttongroup/ButtonGroup.vue.d.ts +1 -1
  9. package/lib/components/checkbox/Checkbox.vue.d.ts +4 -3
  10. package/lib/components/checkbox/style/Checkbox.css.d.ts +1 -0
  11. package/lib/components/colorpicker/ColorPicker.vue.d.ts +1 -1
  12. package/lib/components/configprovider/ConfigProvider.vue.d.ts +35 -0
  13. package/lib/components/configprovider/types.d.ts +3 -0
  14. package/lib/components/confirm/Confirm.vue.d.ts +1 -1
  15. package/lib/components/confirm/ConfirmProvider.vue.d.ts +1 -1
  16. package/lib/components/dialog/Dialog.vue.d.ts +1 -1
  17. package/lib/components/dropdown/Dropdown.vue.d.ts +1 -1
  18. package/lib/components/dropdown/DropdownHeading.vue.d.ts +1 -1
  19. package/lib/components/dropdown/DropdownItem.vue.d.ts +1 -1
  20. package/lib/components/dropdown/DropdownList.vue.d.ts +1 -1
  21. package/lib/components/dropdown/DropdownSeperator.vue.d.ts +1 -1
  22. package/lib/components/dropdown/DropdownWrapper.vue.d.ts +1 -1
  23. package/lib/components/form/Form.vue.d.ts +1 -1
  24. package/lib/components/image/Image.vue.d.ts +1 -1
  25. package/lib/components/index.d.ts +1 -0
  26. package/lib/components/input/Input.vue.d.ts +2 -2
  27. package/lib/components/link/Link.vue.d.ts +1 -1
  28. package/lib/components/messageprovider/Message.vue.d.ts +1 -1
  29. package/lib/components/messageprovider/MessageProvider.vue.d.ts +1 -1
  30. package/lib/components/modal/Modal.vue.d.ts +1 -1
  31. package/lib/components/pagination/Pagination.vue.d.ts +1 -1
  32. package/lib/components/radio/Radio.vue.d.ts +3 -3
  33. package/lib/components/radiobutton/RadioButton.vue.d.ts +1 -1
  34. package/lib/components/radiobuttongroup/RadioButtonGroup.vue.d.ts +1 -1
  35. package/lib/components/renderer/examlpes/basic.vue.d.ts +1 -1
  36. package/lib/components/renderer/renderer.d.ts +1 -1
  37. package/lib/components/select/Select.vue.d.ts +2 -2
  38. package/lib/components/skeleton/Skeleton.vue.d.ts +1 -1
  39. package/lib/components/slider/Slider.vue.d.ts +1 -1
  40. package/lib/components/switch/Switch.vue.d.ts +3 -3
  41. package/lib/components/tab/Tab.vue.d.ts +1 -1
  42. package/lib/components/tab/TabGroup.vue.d.ts +1 -1
  43. package/lib/components/table/Table.vue.d.ts +1 -1
  44. package/lib/components/table/TableBody.vue.d.ts +1 -1
  45. package/lib/components/table/TableCell.vue.d.ts +1 -1
  46. package/lib/components/table/TableHead.vue.d.ts +1 -1
  47. package/lib/components/table/TableHeadRow.vue.d.ts +1 -1
  48. package/lib/components/table/TableRow.vue.d.ts +1 -1
  49. package/lib/components/tag/Tag.vue.d.ts +1 -1
  50. package/lib/components/textarea/Textarea.vue.d.ts +2 -2
  51. package/lib/components/tooltip/Tooltip.vue.d.ts +1 -1
  52. package/lib/components/transfer/Transfer.vue.d.ts +1 -1
  53. package/lib/components/tree/Tree.vue.d.ts +1 -1
  54. package/lib/components/tree/TreeNode.vue.d.ts +1 -1
  55. package/lib/components/tree/TreeNodeToggle.vue.d.ts +1 -1
  56. package/lib/components/virtualscroll/VirtualScroll.vue.d.ts +1 -1
  57. package/lib/composables/useCachedUid.d.ts +1 -13
  58. package/lib/composables/useInput.d.ts +3 -3
  59. package/lib/index.cjs.js +10 -10
  60. package/lib/index.es.js +1347 -1338
  61. package/lib/nuxt.js +35 -35
  62. package/lib/nuxt.plugin.js +8 -8
  63. package/lib/style.css +1 -1
  64. package/lib/styles/theme.css.d.ts +1 -1
  65. package/package.json +89 -89
  66. package/src/__storybook__/argTypes.ts +53 -53
  67. package/src/__storybook__/createStory.ts +15 -15
  68. package/src/__storybook__/index.ts +2 -2
  69. package/src/call.ts +34 -34
  70. package/src/components/avatar/Avatar.vue +110 -110
  71. package/src/components/avatar/examples/basic.vue +6 -6
  72. package/src/components/avatar/examples/custom-size.vue +7 -7
  73. package/src/components/avatar/examples/index.ts +5 -5
  74. package/src/components/avatar/examples/sizes.vue +33 -33
  75. package/src/components/avatar/examples/slots.vue +8 -8
  76. package/src/components/avatar/examples/variants.vue +18 -18
  77. package/src/components/avatar/style/Avatar.css.ts +92 -92
  78. package/src/components/avatar/style/index.ts +1 -1
  79. package/src/components/avatargroup/AvatarGroup.vue +72 -72
  80. package/src/components/avatargroup/examples/basic.vue +37 -37
  81. package/src/components/avatargroup/examples/index.ts +1 -1
  82. package/src/components/avatargroup/style/AvatarGroup.css.ts +26 -26
  83. package/src/components/avatargroup/style/index.ts +1 -1
  84. package/src/components/avatargroup/symbol.ts +17 -17
  85. package/src/components/badge/Badge.vue +43 -43
  86. package/src/components/badge/examples/basic.vue +10 -10
  87. package/src/components/badge/examples/index.ts +1 -1
  88. package/src/components/badge/style/Badge.css.ts +135 -135
  89. package/src/components/badge/style/index.ts +1 -1
  90. package/src/components/breadcrumb/Breadcrumb.vue +54 -54
  91. package/src/components/breadcrumb/BreadcrumbItem.vue +72 -72
  92. package/src/components/breadcrumb/examples/basic.vue +7 -7
  93. package/src/components/breadcrumb/examples/index.ts +1 -1
  94. package/src/components/breadcrumb/style/Breadcrumb.css.ts +40 -40
  95. package/src/components/breadcrumb/style/index.ts +1 -1
  96. package/src/components/breadcrumb/symbol.ts +18 -18
  97. package/src/components/button/Button.vue +203 -203
  98. package/src/components/button/examples/basic.vue +3 -3
  99. package/src/components/button/examples/disabled.vue +3 -3
  100. package/src/components/button/examples/icon-left.vue +12 -12
  101. package/src/components/button/examples/icon.vue +9 -9
  102. package/src/components/button/examples/index.ts +9 -9
  103. package/src/components/button/examples/link.vue +3 -3
  104. package/src/components/button/examples/rounded.vue +7 -7
  105. package/src/components/button/examples/size.vue +7 -7
  106. package/src/components/button/examples/variants-secondary.vue +10 -10
  107. package/src/components/button/examples/variants.vue +10 -10
  108. package/src/components/button/style/Button.css.ts +695 -695
  109. package/src/components/button/style/index.ts +1 -1
  110. package/src/components/buttongroup/ButtonGroup.vue +36 -36
  111. package/src/components/buttongroup/examples/basic.vue +9 -9
  112. package/src/components/buttongroup/examples/icon-group.vue +21 -21
  113. package/src/components/buttongroup/examples/index.ts +2 -2
  114. package/src/components/buttongroup/style/ButtonGroup.css.ts +42 -42
  115. package/src/components/buttongroup/style/index.ts +1 -1
  116. package/src/components/buttongroup/symbol.ts +9 -9
  117. package/src/components/checkbox/Checkbox.vue +149 -145
  118. package/src/components/checkbox/examples/basic.vue +9 -9
  119. package/src/components/checkbox/examples/index.ts +2 -2
  120. package/src/components/checkbox/examples/slot.vue +14 -11
  121. package/src/components/checkbox/style/Checkbox.css.ts +178 -161
  122. package/src/components/checkbox/style/index.ts +1 -1
  123. package/src/components/colorpicker/ColorPicker.vue +333 -333
  124. package/src/components/colorpicker/examples/basic.vue +2 -2
  125. package/src/components/colorpicker/examples/index.ts +1 -1
  126. package/src/components/colorpicker/style/ColorPicker.css.ts +127 -127
  127. package/src/components/colorpicker/style/index.ts +1 -1
  128. package/src/components/colorpicker/utils.ts +206 -206
  129. package/src/components/configprovider/ConfigProvider.vue +26 -0
  130. package/src/components/configprovider/types.ts +3 -0
  131. package/src/components/confirm/Confirm.vue +65 -65
  132. package/src/components/confirm/ConfirmProvider.vue +83 -83
  133. package/src/components/confirm/examples/basic.vue +48 -48
  134. package/src/components/confirm/examples/index.ts +1 -1
  135. package/src/components/confirm/examples/wrapper.vue +9 -9
  136. package/src/components/confirm/props.ts +24 -24
  137. package/src/components/confirm/providerProps.ts +10 -10
  138. package/src/components/confirm/types.ts +33 -33
  139. package/src/components/confirm/useConfirm.ts +14 -14
  140. package/src/components/dialog/Dialog.vue +118 -118
  141. package/src/components/dialog/examples/basic.vue +16 -16
  142. package/src/components/dialog/examples/index.ts +1 -1
  143. package/src/components/dialog/style/Dialog.css.ts +100 -100
  144. package/src/components/dialog/style/index.ts +1 -1
  145. package/src/components/dropdown/Dropdown.vue +308 -308
  146. package/src/components/dropdown/DropdownHeading.vue +23 -23
  147. package/src/components/dropdown/DropdownItem.vue +151 -151
  148. package/src/components/dropdown/DropdownList.vue +20 -20
  149. package/src/components/dropdown/DropdownSeperator.vue +22 -22
  150. package/src/components/dropdown/DropdownWrapper.vue +18 -18
  151. package/src/components/dropdown/examples/basic.vue +119 -119
  152. package/src/components/dropdown/examples/context.vue +71 -71
  153. package/src/components/dropdown/examples/index.ts +3 -3
  154. package/src/components/dropdown/hooks.ts +30 -30
  155. package/src/components/dropdown/style/Dropdown.css.ts +158 -158
  156. package/src/components/dropdown/style/index.ts +1 -1
  157. package/src/components/dropdown/symbol.ts +9 -9
  158. package/src/components/form/Form.vue +153 -153
  159. package/src/components/form/examples/basic.vue +75 -75
  160. package/src/components/form/examples/index.ts +1 -1
  161. package/src/components/form/style/Form.css.ts +28 -28
  162. package/src/components/form/style/index.ts +2 -2
  163. package/src/components/form/symbol.ts +16 -16
  164. package/src/components/image/Image.vue +71 -71
  165. package/src/components/image/examples/basic.vue +5 -5
  166. package/src/components/image/examples/index.ts +1 -1
  167. package/src/components/index.ts +48 -47
  168. package/src/components/input/Input.vue +233 -233
  169. package/src/components/input/examples/basic.vue +21 -21
  170. package/src/components/input/examples/index.ts +6 -6
  171. package/src/components/input/examples/left-icon.vue +9 -9
  172. package/src/components/input/examples/prefix.vue +5 -5
  173. package/src/components/input/examples/right-icon.vue +9 -9
  174. package/src/components/input/examples/sizes.vue +9 -9
  175. package/src/components/input/examples/suffix.vue +7 -7
  176. package/src/components/input/style/Input.css.ts +294 -294
  177. package/src/components/input/style/index.ts +1 -1
  178. package/src/components/link/Link.vue +53 -53
  179. package/src/components/link/examples/basic.vue +3 -3
  180. package/src/components/link/examples/index.ts +1 -1
  181. package/src/components/messageprovider/Message.vue +135 -135
  182. package/src/components/messageprovider/MessageProvider.vue +156 -156
  183. package/src/components/messageprovider/examples/basic.vue +70 -70
  184. package/src/components/messageprovider/examples/index.ts +2 -2
  185. package/src/components/messageprovider/examples/update.vue +34 -34
  186. package/src/components/messageprovider/examples/wrapper.vue +9 -9
  187. package/src/components/messageprovider/style/Message.css.ts +111 -111
  188. package/src/components/messageprovider/style/index.ts +1 -1
  189. package/src/components/messageprovider/types.ts +56 -56
  190. package/src/components/messageprovider/useMessage.ts +14 -14
  191. package/src/components/modal/Modal.vue +310 -310
  192. package/src/components/modal/examples/basic.vue +80 -80
  193. package/src/components/modal/examples/index.ts +3 -3
  194. package/src/components/modal/examples/modal-form.vue +99 -99
  195. package/src/components/modal/examples/sidebar.vue +79 -79
  196. package/src/components/modal/style/Modal.css.ts +318 -318
  197. package/src/components/modal/style/index.ts +1 -1
  198. package/src/components/pagination/Pagination.vue +296 -296
  199. package/src/components/pagination/examples/basic.vue +17 -17
  200. package/src/components/pagination/examples/index.ts +1 -1
  201. package/src/components/pagination/style/Pagination.css.ts +93 -93
  202. package/src/components/pagination/style/index.ts +1 -1
  203. package/src/components/radio/Radio.vue +136 -136
  204. package/src/components/radio/examples/basic.vue +8 -8
  205. package/src/components/radio/examples/group.vue +15 -15
  206. package/src/components/radio/examples/index.ts +2 -2
  207. package/src/components/radio/style/Checkbox.css.ts +130 -130
  208. package/src/components/radio/style/index.ts +1 -1
  209. package/src/components/radiobutton/RadioButton.vue +131 -131
  210. package/src/components/radiobutton/style/RadioButton.css.ts +189 -189
  211. package/src/components/radiobutton/style/index.ts +1 -1
  212. package/src/components/radiobuttongroup/RadioButtonGroup.vue +74 -74
  213. package/src/components/radiobuttongroup/examples/basic.vue +16 -16
  214. package/src/components/radiobuttongroup/examples/index.ts +1 -1
  215. package/src/components/radiobuttongroup/style/RadioButtonGroup.css.ts +37 -37
  216. package/src/components/radiobuttongroup/style/index.ts +1 -1
  217. package/src/components/radiobuttongroup/symbol.ts +12 -12
  218. package/src/components/renderer/examlpes/basic.vue +23 -23
  219. package/src/components/renderer/examlpes/index.ts +1 -1
  220. package/src/components/renderer/props.ts +14 -14
  221. package/src/components/renderer/renderer.ts +16 -16
  222. package/src/components/select/Select.vue +781 -781
  223. package/src/components/select/examples/basic.vue +21 -21
  224. package/src/components/select/examples/index.ts +3 -3
  225. package/src/components/select/examples/multiple.vue +21 -21
  226. package/src/components/select/examples/searchable.vue +20 -20
  227. package/src/components/select/style/Select.css.ts +368 -368
  228. package/src/components/select/style/index.ts +1 -1
  229. package/src/components/skeleton/Skeleton.vue +55 -55
  230. package/src/components/skeleton/examples/basic.vue +3 -3
  231. package/src/components/skeleton/examples/index.ts +2 -2
  232. package/src/components/skeleton/examples/post.vue +8 -8
  233. package/src/components/skeleton/style/Skeleton.css.ts +72 -72
  234. package/src/components/skeleton/style/index.ts +2 -2
  235. package/src/components/slider/Slider.vue +76 -76
  236. package/src/components/slider/style/Slider.css.ts +50 -50
  237. package/src/components/slider/style/index.ts +1 -1
  238. package/src/components/switch/Switch.vue +132 -132
  239. package/src/components/switch/examples/basic.vue +9 -9
  240. package/src/components/switch/examples/index.ts +2 -2
  241. package/src/components/switch/examples/slot.vue +11 -11
  242. package/src/components/switch/style/Switch.css.ts +135 -135
  243. package/src/components/switch/style/index.ts +1 -1
  244. package/src/components/tab/Tab.css +113 -113
  245. package/src/components/tab/Tab.vue +98 -98
  246. package/src/components/tab/TabGroup.vue +90 -90
  247. package/src/components/tab/examples/basic.vue +19 -19
  248. package/src/components/tab/examples/index.ts +2 -2
  249. package/src/components/tab/examples/vertical.vue +19 -19
  250. package/src/components/table/Table.vue +198 -198
  251. package/src/components/table/TableBody.vue +13 -13
  252. package/src/components/table/TableCell.vue +13 -13
  253. package/src/components/table/TableHead.vue +46 -46
  254. package/src/components/table/TableHeadRow.vue +15 -15
  255. package/src/components/table/TableRow.vue +19 -19
  256. package/src/components/table/examples/basic.vue +61 -61
  257. package/src/components/table/examples/empty.vue +44 -44
  258. package/src/components/table/examples/index.ts +3 -3
  259. package/src/components/table/examples/selected.vue +68 -68
  260. package/src/components/table/style/Table.css.ts +62 -62
  261. package/src/components/table/style/index.ts +1 -1
  262. package/src/components/table/types/index.ts +6 -6
  263. package/src/components/tag/Tag.vue +72 -72
  264. package/src/components/tag/examples/basic.vue +5 -5
  265. package/src/components/tag/examples/index.ts +1 -1
  266. package/src/components/tag/style/Tag.css.ts +83 -83
  267. package/src/components/tag/style/index.ts +1 -1
  268. package/src/components/textarea/Textarea.vue +180 -180
  269. package/src/components/textarea/examples/basic.vue +8 -8
  270. package/src/components/textarea/examples/index.ts +1 -1
  271. package/src/components/textarea/style/Textarea.css.ts +191 -191
  272. package/src/components/textarea/style/index.ts +1 -1
  273. package/src/components/tooltip/Tooltip.vue +196 -196
  274. package/src/components/tooltip/examples/basic.vue +5 -5
  275. package/src/components/tooltip/examples/index.ts +2 -2
  276. package/src/components/tooltip/examples/placement.vue +9 -9
  277. package/src/components/tooltip/style/Tooltip.css.ts +90 -90
  278. package/src/components/tooltip/style/index.ts +1 -1
  279. package/src/components/transfer/Transfer.vue +145 -145
  280. package/src/components/transfer/examples/basic.vue +21 -21
  281. package/src/components/transfer/examples/index.ts +1 -1
  282. package/src/components/transfer/style/Transfer.css.ts +81 -81
  283. package/src/components/transfer/style/index.ts +1 -1
  284. package/src/components/tree/Tree.vue +138 -138
  285. package/src/components/tree/TreeNode.vue +109 -109
  286. package/src/components/tree/TreeNodeToggle.vue +48 -48
  287. package/src/components/tree/examples/basic.vue +458 -458
  288. package/src/components/tree/examples/index.ts +1 -1
  289. package/src/components/tree/props.ts +97 -97
  290. package/src/components/tree/style/Tree.css.ts +75 -75
  291. package/src/components/tree/style/index.ts +1 -1
  292. package/src/components/tree/types.ts +103 -103
  293. package/src/components/virtualscroll/VirtualScroll.css +13 -13
  294. package/src/components/virtualscroll/VirtualScroll.vue +166 -166
  295. package/src/components/virtualscroll/examples/basic.vue +31 -31
  296. package/src/components/virtualscroll/examples/index.ts +1 -1
  297. package/src/components/virtualscroll/style/VirtualScroll.css.ts +17 -17
  298. package/src/components/virtualscroll/style/index.ts +1 -1
  299. package/src/composables/element.ts +13 -13
  300. package/src/composables/index.ts +4 -4
  301. package/src/composables/useCachedUid.ts +17 -49
  302. package/src/composables/useEventListener.ts +19 -19
  303. package/src/composables/useInput.ts +151 -151
  304. package/src/composables/useInteractive.ts +19 -19
  305. package/src/composables/useIsServer.ts +7 -7
  306. package/src/composables/useMergedState.ts +18 -18
  307. package/src/composables/useMount.ts +43 -43
  308. package/src/composables/usePopper.ts +215 -215
  309. package/src/composables/useZIndex.ts +33 -33
  310. package/src/create.ts +38 -38
  311. package/src/css/components.css +16 -16
  312. package/src/css/index.css +4 -4
  313. package/src/css/transitions.css +40 -40
  314. package/src/css/variables.css +80 -80
  315. package/src/examples/cards/sidebar.vue +262 -262
  316. package/src/examples/modal/modal.vue +52 -52
  317. package/src/exports/nuxt.js +35 -35
  318. package/src/exports/nuxt.plugin.js +8 -8
  319. package/src/index.ts +5 -5
  320. package/src/install.ts +8 -8
  321. package/src/shims-vue.d.ts +6 -6
  322. package/src/styles/theme.css.ts +181 -181
  323. package/src/styles/transitions.css.ts +97 -97
  324. package/src/types/props.ts +19 -19
  325. package/src/utils/injection.ts +5 -5
  326. package/volar.d.ts +16 -16
@@ -1,781 +1,781 @@
1
- <template>
2
- <div ref="selectRef" tabindex="-1">
3
- <label v-if="label" :for="id" :class="style.label" :id="`${id}-label`">
4
- {{ label }}
5
- </label>
6
- <div v-if="description" :class="style.description">
7
- {{ description }}
8
- </div>
9
-
10
- <div
11
- :class="computedWrapperClasses"
12
- v-on="inputListeners"
13
- :id="id"
14
- ref="container"
15
- :aria-disabled="disabled ? 'true' : 'false'"
16
- :aria-labelledby="`${id}-label`"
17
- tabindex="0"
18
- @mousedown.prevent="onSelectClick"
19
- @focusin.stop="focusSelect">
20
- <div :class="selectComputedClasses">
21
- <template
22
- v-if="
23
- multiple &&
24
- Array.isArray(selected) &&
25
- selected.length > 0
26
- ">
27
- <MTag
28
- v-for="value in selectedMax"
29
- :key="value"
30
- :closeable="true"
31
- @close="event => removeSelected(event, value)"
32
- :size="size">
33
- {{ getSelectedLabel(value) }}
34
- </MTag>
35
- <template v-if="selectedCountNotShown > 0">
36
- <MTag :size="size">
37
- +{{ selectedCountNotShown }} more
38
- </MTag>
39
- </template>
40
- </template>
41
- <template v-else-if="!multiple && selected">
42
- {{ getSelectedLabel(selected) }}
43
- </template>
44
- <template v-else>
45
- <div :class="style.placeholder">{{ placeholder }}</div>
46
- </template>
47
- </div>
48
-
49
- <Teleport :to="'body'">
50
- <Transition
51
- :enter-active-class="scale.enterActive"
52
- :leave-active-class="scale.leaveActive"
53
- :enter-from-class="scale.enterFrom"
54
- :leave-to-class="scale.leaveTo">
55
- <div
56
- :class="style.menu"
57
- v-if="isOpen"
58
- ref="popper"
59
- :style="computedConsistentWidth"
60
- :aria-hidden="isOpen ? 'false' : 'true'">
61
- <div
62
- v-if="searchable || (multiple && selectAll)"
63
- :class="style.search">
64
- <div
65
- v-if="multiple && selectAll"
66
- :class="style.checkAll">
67
- <button
68
- role="button"
69
- type="button"
70
- :class="{
71
- [style.checkAllButton]: true,
72
- [style.checkAllButtonActive]:
73
- computedOptions.length > 0 &&
74
- computedOptions.length ===
75
- selected.length
76
- }"
77
- @click.prevent="toggleAllItems"
78
- aria-description="Select all items">
79
- <svg
80
- v-if="
81
- computedOptions.length > 0 &&
82
- computedOptions.length ===
83
- selected.length
84
- "
85
- xmlns="http://www.w3.org/2000/svg"
86
- viewBox="0 0 20 20"
87
- fill="currentColor"
88
- aria-hidden="true">
89
- <path
90
- fill-rule="evenodd"
91
- d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
92
- clip-rule="evenodd"></path>
93
- </svg>
94
- </button>
95
- </div>
96
- <input
97
- v-if="searchable"
98
- type="text"
99
- ref="searchRef"
100
- v-model="search"
101
- placeholder="Search"
102
- :class="style.searchInput" />
103
- </div>
104
- <div :class="style.options">
105
- <div
106
- v-for="(option, index) in computedOptions"
107
- :key="index"
108
- ref="optionsRef"
109
- :class="{
110
- [style.option]: true,
111
- [style.optionSelected]: option.active,
112
- [style.optionFocused]:
113
- selectedIndex === index
114
- }"
115
- @click.prevent="option.onClick"
116
- @hover="selectedIndex = index">
117
- <span :class="style.optionIcon">
118
- <svg
119
- v-if="option.active"
120
- xmlns="http://www.w3.org/2000/svg"
121
- viewBox="0 0 20 20"
122
- fill="currentColor"
123
- aria-hidden="true">
124
- <path
125
- fill-rule="evenodd"
126
- d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
127
- clip-rule="evenodd"></path>
128
- </svg>
129
- </span>
130
- <span :class="style.optionLabel">{{
131
- option.label
132
- }}</span>
133
- </div>
134
- <template v-if="computedOptions.length === 0">
135
- <div :class="style.noResults">No results</div>
136
- </template>
137
- </div>
138
- </div>
139
- </Transition>
140
- </Teleport>
141
- </div>
142
- <div v-if="inputError" :class="style.errorMessage">
143
- {{ inputError }}
144
- </div>
145
- </div>
146
- </template>
147
-
148
- <script lang="ts">
149
- import { useInput } from '../../composables/useInput';
150
- import { usePopper, type Placement } from '../../composables/usePopper';
151
- import type { Size } from '../../types/props';
152
- import { onClickOutside, useEventListener } from '@vueuse/core';
153
- import {
154
- computed,
155
- defineComponent,
156
- nextTick,
157
- ref,
158
- watch,
159
- watchEffect,
160
- type PropType
161
- } from 'vue';
162
- import MTag from '../tag/Tag.vue';
163
- import * as style from './style';
164
- import { scale } from '../../styles/transitions.css';
165
- import { useInteractive } from '../../composables/useInteractive';
166
-
167
- export default defineComponent({
168
- name: 'MSelect',
169
- emits: useInput.emits(),
170
- props: {
171
- ...useInput.props(),
172
- ...useInteractive.props(),
173
- label: {
174
- type: String
175
- },
176
- placeholder: {
177
- type: String,
178
- default: 'Select an option'
179
- },
180
- options: {
181
- type: Array as PropType<Array<any>>,
182
- required: true
183
- },
184
- searchable: {
185
- type: Boolean,
186
- default: false
187
- },
188
- multiple: {
189
- type: Boolean,
190
- default: false
191
- },
192
- size: {
193
- type: String as PropType<Size>,
194
- default: 'md'
195
- },
196
- description: {
197
- type: String,
198
- default: ''
199
- },
200
- optionValue: {
201
- type: String,
202
- default: 'value'
203
- },
204
- optionLabel: {
205
- type: String,
206
- default: 'label'
207
- },
208
- transitionName: {
209
- type: String,
210
- default: 'fade'
211
- },
212
- consistentWidth: {
213
- type: Boolean,
214
- default: true
215
- },
216
- disabled: {
217
- type: Boolean,
218
- default: false
219
- },
220
- loading: {
221
- type: Boolean,
222
- default: false
223
- },
224
- error: {
225
- type: String
226
- },
227
- allowBlank: {
228
- type: Boolean,
229
- default: false
230
- },
231
- selectAll: {
232
- type: Boolean,
233
- default: false
234
- },
235
- max: {
236
- type: Number,
237
- default: 5
238
- }
239
- },
240
- components: {
241
- MTag
242
- },
243
- setup(props, { emit, expose }) {
244
- const container = ref<HTMLElement | null>(null);
245
- const wrapper = ref<HTMLElement>();
246
- const optionsRef = ref<HTMLElement[]>();
247
- const searchRef = ref<HTMLElement>();
248
- const selectRef = ref<HTMLElement>();
249
- const selectedIndex = ref<number | undefined>(0);
250
- const open = ref<boolean>(false);
251
- const focused = ref<boolean>(false);
252
- const search = ref<string>('');
253
- const placement = ref<Placement>('bottom-start');
254
- const transfer = ref(false);
255
- const width = ref<number>(0);
256
- const keyboardNavigation = ref<boolean>(false);
257
-
258
- const { focus, blur } = useInteractive(container);
259
- // TODO: Add debounce on keyboard movement (hover flicks it back up)
260
- const {
261
- id,
262
- insideForm,
263
- inputListeners,
264
- formError,
265
- formDisabled,
266
- formLoading
267
- } = useInput(props, { focus, emit, withListeners: false });
268
-
269
- const { popper, updatePopper } = usePopper({
270
- placement,
271
- transfer,
272
- reference: container,
273
- wrapper,
274
- offset: [0, 5]
275
- });
276
-
277
- const disabled = computed(() => formDisabled || props.disabled);
278
- const loading = computed(() => formLoading || props.loading);
279
-
280
- const selected = computed<any | any[]>({
281
- get() {
282
- if (props.multiple) {
283
- if (!props.modelValue) {
284
- return [];
285
- }
286
-
287
- if (Array.isArray(props.modelValue)) {
288
- return props.modelValue;
289
- }
290
-
291
- return [props.modelValue];
292
- }
293
-
294
- return props.modelValue || null;
295
- },
296
- set(value: string | number | []) {
297
- emit('update:modelValue', value);
298
- }
299
- });
300
-
301
- const selectedMax = computed(() => {
302
- if (props.max <= 0) {
303
- return selected.value;
304
- }
305
-
306
- if (
307
- props.multiple &&
308
- Array.isArray(selected.value) &&
309
- selected.value.length > props.max
310
- ) {
311
- return selected.value.slice(0, props.max);
312
- }
313
-
314
- return selected.value;
315
- });
316
-
317
- const selectedCountNotShown = computed(() => {
318
- if (
319
- props.multiple &&
320
- Array.isArray(selected.value) &&
321
- props.max > 0
322
- ) {
323
- return selected.value.length - props.max;
324
- }
325
-
326
- return 0;
327
- });
328
-
329
- const computedConsistentWidth = computed(() => {
330
- if (props.consistentWidth) {
331
- return {
332
- width: width.value + 'px'
333
- };
334
- }
335
-
336
- return {};
337
- });
338
-
339
- const computedOptions = computed(() => {
340
- if (!props.options || props.options.length == 0) {
341
- return [];
342
- }
343
-
344
- // Filter options by search
345
- if (props.searchable && search.value) {
346
- let options = props.options
347
- .filter(option => {
348
- return option[props.optionLabel]
349
- .toLowerCase()
350
- .includes(search.value.toLowerCase());
351
- })
352
- .map(option => {
353
- if (option[props.optionValue] === undefined) {
354
- throw new Error('Option value is undefined');
355
- }
356
-
357
- if (option[props.optionLabel] === undefined) {
358
- throw new Error('Option label is undefined');
359
- }
360
-
361
- let value = option[props.optionValue];
362
- let active: boolean;
363
- if (props.multiple && Array.isArray(selected.value)) {
364
- active = selected.value.includes(value);
365
- } else {
366
- active = selected.value === value;
367
- }
368
-
369
- return {
370
- value: value,
371
- label: option[props.optionLabel],
372
- active: active,
373
- onClick: () => handleOptionClick(value)
374
- };
375
- });
376
-
377
- if (props.allowBlank) {
378
- options.unshift({
379
- value: '',
380
- label: '',
381
- active: false,
382
- onClick: () => handleOptionClick('')
383
- });
384
- }
385
-
386
- return options;
387
- }
388
-
389
- let options = props.options.map(option => {
390
- if (option[props.optionValue] === undefined) {
391
- throw new Error('Option value is undefined');
392
- }
393
-
394
- if (option[props.optionLabel] === undefined) {
395
- throw new Error('Option label is undefined');
396
- }
397
-
398
- let value = option[props.optionValue];
399
- let active: boolean;
400
- if (props.multiple && Array.isArray(selected.value)) {
401
- active = selected.value.includes(value);
402
- } else {
403
- active = selected.value === value;
404
- }
405
-
406
- return {
407
- value: value,
408
- label: option[props.optionLabel],
409
- active: active,
410
- onClick: () => handleOptionClick(value)
411
- };
412
- });
413
-
414
- if (props.allowBlank) {
415
- options.unshift({
416
- value: '',
417
- label: '',
418
- active: false,
419
- onClick: () => handleOptionClick('')
420
- });
421
- }
422
-
423
- return options;
424
- });
425
-
426
- function toggleAllItems() {
427
- if (!props.multiple) {
428
- return;
429
- }
430
-
431
- if (computedOptions.value.length === 0) {
432
- return;
433
- }
434
-
435
- if (Array.isArray(selected.value)) {
436
- if (selected.value.length === computedOptions.value.length) {
437
- selected.value = [];
438
- } else {
439
- selected.value = computedOptions.value.map(
440
- option => option.value
441
- );
442
- }
443
- } else {
444
- selected.value = [];
445
- }
446
- }
447
-
448
- function openSelect() {
449
- if (disabled.value || loading.value) {
450
- return;
451
- }
452
-
453
- if (open.value) {
454
- open.value = false;
455
- return;
456
- }
457
- open.value = true;
458
- }
459
-
460
- function getSelectedIndex(
461
- start: number | undefined,
462
- direction = 'down'
463
- ) {
464
- if (typeof start === 'undefined') {
465
- start = direction === 'down' ? -1 : 1;
466
- }
467
-
468
- if (direction === 'down') {
469
- let next = start + 1;
470
-
471
- if (next > computedOptions.value.length - 1) {
472
- next = 0;
473
- }
474
-
475
- selectedIndex.value = next;
476
- } else {
477
- let next = start - 1;
478
-
479
- if (next < 0) {
480
- next = computedOptions.value.length - 1;
481
- }
482
-
483
- selectedIndex.value = next;
484
- }
485
- }
486
-
487
- function handleOptionClick(value: string | number) {
488
- const option = computedOptions.value.find(
489
- item => item.value === value
490
- );
491
- if (!option) {
492
- return;
493
- }
494
-
495
- if (props.multiple) {
496
- if (Array.isArray(selected.value)) {
497
- const index = selected.value.indexOf(value);
498
-
499
- if (index !== -1) {
500
- selected.value.splice(index, 1);
501
- } else {
502
- selected.value.push(value);
503
- emit('update:modelValue', selected.value);
504
- }
505
- } else {
506
- selected.value = [value];
507
- }
508
-
509
- updatePopper();
510
- } else {
511
- selected.value = value;
512
- open.value = false;
513
- }
514
- }
515
-
516
- function removeSelected(event: MouseEvent, value: string | number) {
517
- event.preventDefault();
518
- event.stopPropagation();
519
- if (!props.multiple) {
520
- return;
521
- }
522
-
523
- if (Array.isArray(selected.value)) {
524
- const index = selected.value.indexOf(value);
525
-
526
- if (index !== -1) {
527
- selected.value.splice(index, 1);
528
- }
529
-
530
- updatePopper();
531
- }
532
- }
533
-
534
- useEventListener('resize', () => {
535
- open.value = false;
536
- });
537
-
538
- function handleKeyDown(e: Event) {
539
- if (computedOptions.value.length === 0) {
540
- return;
541
- }
542
-
543
- if (props.disabled) {
544
- return;
545
- }
546
-
547
- keyboardNavigation.value = true;
548
-
549
- const event: KeyboardEvent = e as KeyboardEvent;
550
-
551
- switch (event.code) {
552
- case 'Tab':
553
- if (open.value) {
554
- open.value = false;
555
- }
556
- break;
557
- case 'ArrowDown':
558
- e.preventDefault();
559
- if (!open.value) {
560
- open.value = true;
561
- return;
562
- }
563
-
564
- getSelectedIndex(selectedIndex.value, 'down');
565
- break;
566
- case 'ArrowUp':
567
- e.preventDefault();
568
- if (!open.value) {
569
- return;
570
- }
571
-
572
- getSelectedIndex(selectedIndex.value, 'up');
573
- break;
574
- case 'Enter':
575
- e.preventDefault();
576
- e.stopPropagation();
577
-
578
- if (!open.value) {
579
- open.value = true;
580
- return;
581
- }
582
-
583
- if (typeof selectedIndex.value !== 'undefined') {
584
- handleOptionClick(
585
- computedOptions.value[selectedIndex.value].value
586
- );
587
- if (!props.multiple) {
588
- open.value = false;
589
- }
590
- }
591
- break;
592
- case 'Escape':
593
- e.preventDefault();
594
- e.stopPropagation();
595
-
596
- if (!open.value) {
597
- return;
598
- }
599
-
600
- open.value = false;
601
- break;
602
- }
603
- }
604
-
605
- const transitionName = computed(() => {
606
- return `m-transition__${props.transitionName}`;
607
- });
608
-
609
- const containerComputedClasses = computed(() => {
610
- return [
611
- 'm-select__container',
612
- {
613
- 'm-form__input': insideForm
614
- }
615
- ];
616
- });
617
-
618
- const computedWrapperClasses = computed(() => {
619
- return [
620
- style.wrapper,
621
- {
622
- [style.focused]: focused.value
623
- }
624
- ];
625
- });
626
-
627
- const selectComputedClasses = computed(() => {
628
- return [
629
- style.base,
630
- {
631
- [style.selectedTags]:
632
- props.multiple &&
633
- Array.isArray(selected.value) &&
634
- selected.value.length > 0,
635
- [style.focused]: open.value,
636
- // [`m-select--${props.size}`]: props.size,
637
- 'm-select--disabled': disabled.value || loading.value,
638
- 'm-select--focused': open.value,
639
- 'm-select--multiple': props.multiple,
640
- 'm-select--error': formError.value
641
- }
642
- ];
643
- });
644
-
645
- function getSelectedLabel(value: string | number | []) {
646
- const option = props.options.find(
647
- i => i[props.optionValue] === value
648
- );
649
- if (!option) {
650
- return null;
651
- }
652
-
653
- return option[props.optionLabel];
654
- }
655
-
656
- useEventListener(selectRef, 'keydown', handleKeyDown);
657
- useEventListener(searchRef, 'keydown', handleKeyDown);
658
-
659
- onClickOutside(
660
- container,
661
- () => {
662
- open.value = false;
663
- },
664
- { ignore: [searchRef, popper] }
665
- );
666
-
667
- watchEffect(() => {
668
- if (container.value && open.value) {
669
- width.value = container.value.offsetWidth;
670
- }
671
- });
672
-
673
- watch(selectedIndex, (index: number | undefined) => {
674
- if (!keyboardNavigation.value) {
675
- return;
676
- }
677
- if (typeof index !== 'undefined' && optionsRef.value) {
678
- optionsRef.value[index].scrollIntoView({
679
- block: 'nearest',
680
- inline: 'nearest'
681
- });
682
- }
683
- });
684
-
685
- watch(open, (value: boolean) => {
686
- nextTick(() => {
687
- if (!value) {
688
- search.value = '';
689
- }
690
-
691
- if (props.searchable && searchRef.value) {
692
- searchRef.value.focus();
693
- }
694
- if (value && optionsRef.value) {
695
- const index = computedOptions.value.findIndex(
696
- i => i.value === selected.value
697
- );
698
- if (index !== -1) {
699
- optionsRef.value[index].scrollIntoView({
700
- block: 'nearest'
701
- });
702
- selectedIndex.value = index;
703
- }
704
- }
705
-
706
- keyboardNavigation.value = false;
707
- });
708
- });
709
-
710
- const inputError = computed(() => {
711
- return formError.value || props.error;
712
- });
713
-
714
- const isOpen = computed(() => {
715
- if (
716
- props.multiple &&
717
- computedOptions.value.length == 0 &&
718
- !search.value
719
- ) {
720
- return false;
721
- }
722
- return open.value;
723
- });
724
-
725
- const focusSelect = (event: FocusEvent) => {
726
- setTimeout(() => {
727
- if (selectRef.value && !open.value) {
728
- openSelect();
729
- }
730
- }, 10);
731
- };
732
-
733
- const onSelectClick = (event: MouseEvent) => {
734
- event.preventDefault();
735
- event.stopPropagation();
736
- openSelect();
737
- };
738
-
739
- expose({
740
- focus,
741
- blur
742
- });
743
-
744
- return {
745
- container,
746
- optionsRef,
747
- searchRef,
748
- selectRef,
749
- popper,
750
- focusSelect,
751
- onSelectClick,
752
-
753
- id,
754
- isOpen,
755
- open,
756
- selected,
757
- selectedMax,
758
- selectedCountNotShown,
759
- computedOptions,
760
- selectedIndex,
761
- search,
762
-
763
- containerComputedClasses,
764
- selectComputedClasses,
765
- computedWrapperClasses,
766
- computedConsistentWidth,
767
-
768
- getSelectedLabel,
769
- openSelect,
770
- removeSelected,
771
- inputError,
772
- transitionName,
773
- inputListeners,
774
- style,
775
- focused,
776
- scale,
777
- toggleAllItems
778
- };
779
- }
780
- });
781
- </script>
1
+ <template>
2
+ <div ref="selectRef" tabindex="-1">
3
+ <label v-if="label" :for="id" :class="style.label" :id="`${id}-label`">
4
+ {{ label }}
5
+ </label>
6
+ <div v-if="description" :class="style.description">
7
+ {{ description }}
8
+ </div>
9
+
10
+ <div
11
+ :class="computedWrapperClasses"
12
+ v-on="inputListeners"
13
+ :id="id"
14
+ ref="container"
15
+ :aria-disabled="disabled ? 'true' : 'false'"
16
+ :aria-labelledby="`${id}-label`"
17
+ tabindex="0"
18
+ @mousedown.prevent="onSelectClick"
19
+ @focusin.stop="focusSelect">
20
+ <div :class="selectComputedClasses">
21
+ <template
22
+ v-if="
23
+ multiple &&
24
+ Array.isArray(selected) &&
25
+ selected.length > 0
26
+ ">
27
+ <MTag
28
+ v-for="value in selectedMax"
29
+ :key="value"
30
+ :closeable="true"
31
+ @close="event => removeSelected(event, value)"
32
+ :size="size">
33
+ {{ getSelectedLabel(value) }}
34
+ </MTag>
35
+ <template v-if="selectedCountNotShown > 0">
36
+ <MTag :size="size">
37
+ +{{ selectedCountNotShown }} more
38
+ </MTag>
39
+ </template>
40
+ </template>
41
+ <template v-else-if="!multiple && selected">
42
+ {{ getSelectedLabel(selected) }}
43
+ </template>
44
+ <template v-else>
45
+ <div :class="style.placeholder">{{ placeholder }}</div>
46
+ </template>
47
+ </div>
48
+
49
+ <Teleport :to="'body'">
50
+ <Transition
51
+ :enter-active-class="scale.enterActive"
52
+ :leave-active-class="scale.leaveActive"
53
+ :enter-from-class="scale.enterFrom"
54
+ :leave-to-class="scale.leaveTo">
55
+ <div
56
+ :class="style.menu"
57
+ v-if="isOpen"
58
+ ref="popper"
59
+ :style="computedConsistentWidth"
60
+ :aria-hidden="isOpen ? 'false' : 'true'">
61
+ <div
62
+ v-if="searchable || (multiple && selectAll)"
63
+ :class="style.search">
64
+ <div
65
+ v-if="multiple && selectAll"
66
+ :class="style.checkAll">
67
+ <button
68
+ role="button"
69
+ type="button"
70
+ :class="{
71
+ [style.checkAllButton]: true,
72
+ [style.checkAllButtonActive]:
73
+ computedOptions.length > 0 &&
74
+ computedOptions.length ===
75
+ selected.length
76
+ }"
77
+ @click.prevent="toggleAllItems"
78
+ aria-description="Select all items">
79
+ <svg
80
+ v-if="
81
+ computedOptions.length > 0 &&
82
+ computedOptions.length ===
83
+ selected.length
84
+ "
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ viewBox="0 0 20 20"
87
+ fill="currentColor"
88
+ aria-hidden="true">
89
+ <path
90
+ fill-rule="evenodd"
91
+ d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
92
+ clip-rule="evenodd"></path>
93
+ </svg>
94
+ </button>
95
+ </div>
96
+ <input
97
+ v-if="searchable"
98
+ type="text"
99
+ ref="searchRef"
100
+ v-model="search"
101
+ placeholder="Search"
102
+ :class="style.searchInput" />
103
+ </div>
104
+ <div :class="style.options">
105
+ <div
106
+ v-for="(option, index) in computedOptions"
107
+ :key="index"
108
+ ref="optionsRef"
109
+ :class="{
110
+ [style.option]: true,
111
+ [style.optionSelected]: option.active,
112
+ [style.optionFocused]:
113
+ selectedIndex === index
114
+ }"
115
+ @click.prevent="option.onClick"
116
+ @hover="selectedIndex = index">
117
+ <span :class="style.optionIcon">
118
+ <svg
119
+ v-if="option.active"
120
+ xmlns="http://www.w3.org/2000/svg"
121
+ viewBox="0 0 20 20"
122
+ fill="currentColor"
123
+ aria-hidden="true">
124
+ <path
125
+ fill-rule="evenodd"
126
+ d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
127
+ clip-rule="evenodd"></path>
128
+ </svg>
129
+ </span>
130
+ <span :class="style.optionLabel">{{
131
+ option.label
132
+ }}</span>
133
+ </div>
134
+ <template v-if="computedOptions.length === 0">
135
+ <div :class="style.noResults">No results</div>
136
+ </template>
137
+ </div>
138
+ </div>
139
+ </Transition>
140
+ </Teleport>
141
+ </div>
142
+ <div v-if="inputError" :class="style.errorMessage">
143
+ {{ inputError }}
144
+ </div>
145
+ </div>
146
+ </template>
147
+
148
+ <script lang="ts">
149
+ import { useInput } from '../../composables/useInput';
150
+ import { usePopper, type Placement } from '../../composables/usePopper';
151
+ import type { Size } from '../../types/props';
152
+ import { onClickOutside, useEventListener } from '@vueuse/core';
153
+ import {
154
+ computed,
155
+ defineComponent,
156
+ nextTick,
157
+ ref,
158
+ watch,
159
+ watchEffect,
160
+ type PropType
161
+ } from 'vue';
162
+ import MTag from '../tag/Tag.vue';
163
+ import * as style from './style';
164
+ import { scale } from '../../styles/transitions.css';
165
+ import { useInteractive } from '../../composables/useInteractive';
166
+
167
+ export default defineComponent({
168
+ name: 'MSelect',
169
+ emits: useInput.emits(),
170
+ props: {
171
+ ...useInput.props(),
172
+ ...useInteractive.props(),
173
+ label: {
174
+ type: String
175
+ },
176
+ placeholder: {
177
+ type: String,
178
+ default: 'Select an option'
179
+ },
180
+ options: {
181
+ type: Array as PropType<Array<any>>,
182
+ required: true
183
+ },
184
+ searchable: {
185
+ type: Boolean,
186
+ default: false
187
+ },
188
+ multiple: {
189
+ type: Boolean,
190
+ default: false
191
+ },
192
+ size: {
193
+ type: String as PropType<Size>,
194
+ default: 'md'
195
+ },
196
+ description: {
197
+ type: String,
198
+ default: ''
199
+ },
200
+ optionValue: {
201
+ type: String,
202
+ default: 'value'
203
+ },
204
+ optionLabel: {
205
+ type: String,
206
+ default: 'label'
207
+ },
208
+ transitionName: {
209
+ type: String,
210
+ default: 'fade'
211
+ },
212
+ consistentWidth: {
213
+ type: Boolean,
214
+ default: true
215
+ },
216
+ disabled: {
217
+ type: Boolean,
218
+ default: false
219
+ },
220
+ loading: {
221
+ type: Boolean,
222
+ default: false
223
+ },
224
+ error: {
225
+ type: String
226
+ },
227
+ allowBlank: {
228
+ type: Boolean,
229
+ default: false
230
+ },
231
+ selectAll: {
232
+ type: Boolean,
233
+ default: false
234
+ },
235
+ max: {
236
+ type: Number,
237
+ default: 5
238
+ }
239
+ },
240
+ components: {
241
+ MTag
242
+ },
243
+ setup(props, { emit, expose }) {
244
+ const container = ref<HTMLElement | null>(null);
245
+ const wrapper = ref<HTMLElement>();
246
+ const optionsRef = ref<HTMLElement[]>();
247
+ const searchRef = ref<HTMLElement>();
248
+ const selectRef = ref<HTMLElement>();
249
+ const selectedIndex = ref<number | undefined>(0);
250
+ const open = ref<boolean>(false);
251
+ const focused = ref<boolean>(false);
252
+ const search = ref<string>('');
253
+ const placement = ref<Placement>('bottom-start');
254
+ const transfer = ref(false);
255
+ const width = ref<number>(0);
256
+ const keyboardNavigation = ref<boolean>(false);
257
+
258
+ const { focus, blur } = useInteractive(container);
259
+ // TODO: Add debounce on keyboard movement (hover flicks it back up)
260
+ const {
261
+ id,
262
+ insideForm,
263
+ inputListeners,
264
+ formError,
265
+ formDisabled,
266
+ formLoading
267
+ } = useInput(props, { focus, emit, withListeners: false });
268
+
269
+ const { popper, updatePopper } = usePopper({
270
+ placement,
271
+ transfer,
272
+ reference: container,
273
+ wrapper,
274
+ offset: [0, 5]
275
+ });
276
+
277
+ const disabled = computed(() => formDisabled || props.disabled);
278
+ const loading = computed(() => formLoading || props.loading);
279
+
280
+ const selected = computed<any | any[]>({
281
+ get() {
282
+ if (props.multiple) {
283
+ if (!props.modelValue) {
284
+ return [];
285
+ }
286
+
287
+ if (Array.isArray(props.modelValue)) {
288
+ return props.modelValue;
289
+ }
290
+
291
+ return [props.modelValue];
292
+ }
293
+
294
+ return props.modelValue || null;
295
+ },
296
+ set(value: string | number | []) {
297
+ emit('update:modelValue', value);
298
+ }
299
+ });
300
+
301
+ const selectedMax = computed(() => {
302
+ if (props.max <= 0) {
303
+ return selected.value;
304
+ }
305
+
306
+ if (
307
+ props.multiple &&
308
+ Array.isArray(selected.value) &&
309
+ selected.value.length > props.max
310
+ ) {
311
+ return selected.value.slice(0, props.max);
312
+ }
313
+
314
+ return selected.value;
315
+ });
316
+
317
+ const selectedCountNotShown = computed(() => {
318
+ if (
319
+ props.multiple &&
320
+ Array.isArray(selected.value) &&
321
+ props.max > 0
322
+ ) {
323
+ return selected.value.length - props.max;
324
+ }
325
+
326
+ return 0;
327
+ });
328
+
329
+ const computedConsistentWidth = computed(() => {
330
+ if (props.consistentWidth) {
331
+ return {
332
+ width: width.value + 'px'
333
+ };
334
+ }
335
+
336
+ return {};
337
+ });
338
+
339
+ const computedOptions = computed(() => {
340
+ if (!props.options || props.options.length == 0) {
341
+ return [];
342
+ }
343
+
344
+ // Filter options by search
345
+ if (props.searchable && search.value) {
346
+ let options = props.options
347
+ .filter(option => {
348
+ return option[props.optionLabel]
349
+ .toLowerCase()
350
+ .includes(search.value.toLowerCase());
351
+ })
352
+ .map(option => {
353
+ if (option[props.optionValue] === undefined) {
354
+ throw new Error('Option value is undefined');
355
+ }
356
+
357
+ if (option[props.optionLabel] === undefined) {
358
+ throw new Error('Option label is undefined');
359
+ }
360
+
361
+ let value = option[props.optionValue];
362
+ let active: boolean;
363
+ if (props.multiple && Array.isArray(selected.value)) {
364
+ active = selected.value.includes(value);
365
+ } else {
366
+ active = selected.value === value;
367
+ }
368
+
369
+ return {
370
+ value: value,
371
+ label: option[props.optionLabel],
372
+ active: active,
373
+ onClick: () => handleOptionClick(value)
374
+ };
375
+ });
376
+
377
+ if (props.allowBlank) {
378
+ options.unshift({
379
+ value: '',
380
+ label: '',
381
+ active: false,
382
+ onClick: () => handleOptionClick('')
383
+ });
384
+ }
385
+
386
+ return options;
387
+ }
388
+
389
+ let options = props.options.map(option => {
390
+ if (option[props.optionValue] === undefined) {
391
+ throw new Error('Option value is undefined');
392
+ }
393
+
394
+ if (option[props.optionLabel] === undefined) {
395
+ throw new Error('Option label is undefined');
396
+ }
397
+
398
+ let value = option[props.optionValue];
399
+ let active: boolean;
400
+ if (props.multiple && Array.isArray(selected.value)) {
401
+ active = selected.value.includes(value);
402
+ } else {
403
+ active = selected.value === value;
404
+ }
405
+
406
+ return {
407
+ value: value,
408
+ label: option[props.optionLabel],
409
+ active: active,
410
+ onClick: () => handleOptionClick(value)
411
+ };
412
+ });
413
+
414
+ if (props.allowBlank) {
415
+ options.unshift({
416
+ value: '',
417
+ label: '',
418
+ active: false,
419
+ onClick: () => handleOptionClick('')
420
+ });
421
+ }
422
+
423
+ return options;
424
+ });
425
+
426
+ function toggleAllItems() {
427
+ if (!props.multiple) {
428
+ return;
429
+ }
430
+
431
+ if (computedOptions.value.length === 0) {
432
+ return;
433
+ }
434
+
435
+ if (Array.isArray(selected.value)) {
436
+ if (selected.value.length === computedOptions.value.length) {
437
+ selected.value = [];
438
+ } else {
439
+ selected.value = computedOptions.value.map(
440
+ option => option.value
441
+ );
442
+ }
443
+ } else {
444
+ selected.value = [];
445
+ }
446
+ }
447
+
448
+ function openSelect() {
449
+ if (disabled.value || loading.value) {
450
+ return;
451
+ }
452
+
453
+ if (open.value) {
454
+ open.value = false;
455
+ return;
456
+ }
457
+ open.value = true;
458
+ }
459
+
460
+ function getSelectedIndex(
461
+ start: number | undefined,
462
+ direction = 'down'
463
+ ) {
464
+ if (typeof start === 'undefined') {
465
+ start = direction === 'down' ? -1 : 1;
466
+ }
467
+
468
+ if (direction === 'down') {
469
+ let next = start + 1;
470
+
471
+ if (next > computedOptions.value.length - 1) {
472
+ next = 0;
473
+ }
474
+
475
+ selectedIndex.value = next;
476
+ } else {
477
+ let next = start - 1;
478
+
479
+ if (next < 0) {
480
+ next = computedOptions.value.length - 1;
481
+ }
482
+
483
+ selectedIndex.value = next;
484
+ }
485
+ }
486
+
487
+ function handleOptionClick(value: string | number) {
488
+ const option = computedOptions.value.find(
489
+ item => item.value === value
490
+ );
491
+ if (!option) {
492
+ return;
493
+ }
494
+
495
+ if (props.multiple) {
496
+ if (Array.isArray(selected.value)) {
497
+ const index = selected.value.indexOf(value);
498
+
499
+ if (index !== -1) {
500
+ selected.value.splice(index, 1);
501
+ } else {
502
+ selected.value.push(value);
503
+ emit('update:modelValue', selected.value);
504
+ }
505
+ } else {
506
+ selected.value = [value];
507
+ }
508
+
509
+ updatePopper();
510
+ } else {
511
+ selected.value = value;
512
+ open.value = false;
513
+ }
514
+ }
515
+
516
+ function removeSelected(event: MouseEvent, value: string | number) {
517
+ event.preventDefault();
518
+ event.stopPropagation();
519
+ if (!props.multiple) {
520
+ return;
521
+ }
522
+
523
+ if (Array.isArray(selected.value)) {
524
+ const index = selected.value.indexOf(value);
525
+
526
+ if (index !== -1) {
527
+ selected.value.splice(index, 1);
528
+ }
529
+
530
+ updatePopper();
531
+ }
532
+ }
533
+
534
+ useEventListener('resize', () => {
535
+ open.value = false;
536
+ });
537
+
538
+ function handleKeyDown(e: Event) {
539
+ if (computedOptions.value.length === 0) {
540
+ return;
541
+ }
542
+
543
+ if (props.disabled) {
544
+ return;
545
+ }
546
+
547
+ keyboardNavigation.value = true;
548
+
549
+ const event: KeyboardEvent = e as KeyboardEvent;
550
+
551
+ switch (event.code) {
552
+ case 'Tab':
553
+ if (open.value) {
554
+ open.value = false;
555
+ }
556
+ break;
557
+ case 'ArrowDown':
558
+ e.preventDefault();
559
+ if (!open.value) {
560
+ open.value = true;
561
+ return;
562
+ }
563
+
564
+ getSelectedIndex(selectedIndex.value, 'down');
565
+ break;
566
+ case 'ArrowUp':
567
+ e.preventDefault();
568
+ if (!open.value) {
569
+ return;
570
+ }
571
+
572
+ getSelectedIndex(selectedIndex.value, 'up');
573
+ break;
574
+ case 'Enter':
575
+ e.preventDefault();
576
+ e.stopPropagation();
577
+
578
+ if (!open.value) {
579
+ open.value = true;
580
+ return;
581
+ }
582
+
583
+ if (typeof selectedIndex.value !== 'undefined') {
584
+ handleOptionClick(
585
+ computedOptions.value[selectedIndex.value].value
586
+ );
587
+ if (!props.multiple) {
588
+ open.value = false;
589
+ }
590
+ }
591
+ break;
592
+ case 'Escape':
593
+ e.preventDefault();
594
+ e.stopPropagation();
595
+
596
+ if (!open.value) {
597
+ return;
598
+ }
599
+
600
+ open.value = false;
601
+ break;
602
+ }
603
+ }
604
+
605
+ const transitionName = computed(() => {
606
+ return `m-transition__${props.transitionName}`;
607
+ });
608
+
609
+ const containerComputedClasses = computed(() => {
610
+ return [
611
+ 'm-select__container',
612
+ {
613
+ 'm-form__input': insideForm
614
+ }
615
+ ];
616
+ });
617
+
618
+ const computedWrapperClasses = computed(() => {
619
+ return [
620
+ style.wrapper,
621
+ {
622
+ [style.focused]: focused.value
623
+ }
624
+ ];
625
+ });
626
+
627
+ const selectComputedClasses = computed(() => {
628
+ return [
629
+ style.base,
630
+ {
631
+ [style.selectedTags]:
632
+ props.multiple &&
633
+ Array.isArray(selected.value) &&
634
+ selected.value.length > 0,
635
+ [style.focused]: open.value,
636
+ // [`m-select--${props.size}`]: props.size,
637
+ 'm-select--disabled': disabled.value || loading.value,
638
+ 'm-select--focused': open.value,
639
+ 'm-select--multiple': props.multiple,
640
+ 'm-select--error': formError.value
641
+ }
642
+ ];
643
+ });
644
+
645
+ function getSelectedLabel(value: string | number | []) {
646
+ const option = props.options.find(
647
+ i => i[props.optionValue] === value
648
+ );
649
+ if (!option) {
650
+ return null;
651
+ }
652
+
653
+ return option[props.optionLabel];
654
+ }
655
+
656
+ useEventListener(selectRef, 'keydown', handleKeyDown);
657
+ useEventListener(searchRef, 'keydown', handleKeyDown);
658
+
659
+ onClickOutside(
660
+ container,
661
+ () => {
662
+ open.value = false;
663
+ },
664
+ { ignore: [searchRef, popper] }
665
+ );
666
+
667
+ watchEffect(() => {
668
+ if (container.value && open.value) {
669
+ width.value = container.value.offsetWidth;
670
+ }
671
+ });
672
+
673
+ watch(selectedIndex, (index: number | undefined) => {
674
+ if (!keyboardNavigation.value) {
675
+ return;
676
+ }
677
+ if (typeof index !== 'undefined' && optionsRef.value) {
678
+ optionsRef.value[index].scrollIntoView({
679
+ block: 'nearest',
680
+ inline: 'nearest'
681
+ });
682
+ }
683
+ });
684
+
685
+ watch(open, (value: boolean) => {
686
+ nextTick(() => {
687
+ if (!value) {
688
+ search.value = '';
689
+ }
690
+
691
+ if (props.searchable && searchRef.value) {
692
+ searchRef.value.focus();
693
+ }
694
+ if (value && optionsRef.value) {
695
+ const index = computedOptions.value.findIndex(
696
+ i => i.value === selected.value
697
+ );
698
+ if (index !== -1) {
699
+ optionsRef.value[index].scrollIntoView({
700
+ block: 'nearest'
701
+ });
702
+ selectedIndex.value = index;
703
+ }
704
+ }
705
+
706
+ keyboardNavigation.value = false;
707
+ });
708
+ });
709
+
710
+ const inputError = computed(() => {
711
+ return formError.value || props.error;
712
+ });
713
+
714
+ const isOpen = computed(() => {
715
+ if (
716
+ props.multiple &&
717
+ computedOptions.value.length == 0 &&
718
+ !search.value
719
+ ) {
720
+ return false;
721
+ }
722
+ return open.value;
723
+ });
724
+
725
+ const focusSelect = (event: FocusEvent) => {
726
+ setTimeout(() => {
727
+ if (selectRef.value && !open.value) {
728
+ openSelect();
729
+ }
730
+ }, 10);
731
+ };
732
+
733
+ const onSelectClick = (event: MouseEvent) => {
734
+ event.preventDefault();
735
+ event.stopPropagation();
736
+ openSelect();
737
+ };
738
+
739
+ expose({
740
+ focus,
741
+ blur
742
+ });
743
+
744
+ return {
745
+ container,
746
+ optionsRef,
747
+ searchRef,
748
+ selectRef,
749
+ popper,
750
+ focusSelect,
751
+ onSelectClick,
752
+
753
+ id,
754
+ isOpen,
755
+ open,
756
+ selected,
757
+ selectedMax,
758
+ selectedCountNotShown,
759
+ computedOptions,
760
+ selectedIndex,
761
+ search,
762
+
763
+ containerComputedClasses,
764
+ selectComputedClasses,
765
+ computedWrapperClasses,
766
+ computedConsistentWidth,
767
+
768
+ getSelectedLabel,
769
+ openSelect,
770
+ removeSelected,
771
+ inputError,
772
+ transitionName,
773
+ inputListeners,
774
+ style,
775
+ focused,
776
+ scale,
777
+ toggleAllItems
778
+ };
779
+ }
780
+ });
781
+ </script>