@eturnity/eturnity_reusable_components 7.33.0-EPDM-11313.0 → 7.33.0-EPDM-11205.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 (330) hide show
  1. package/.eslintrc.js +184 -184
  2. package/.prettierrc +9 -9
  3. package/.storybook/main.js +8 -8
  4. package/.storybook/preview.js +46 -46
  5. package/README.md +29 -29
  6. package/babel.config.js +5 -5
  7. package/package.json +50 -50
  8. package/postcss.config.js +6 -6
  9. package/src/App.vue +110 -110
  10. package/src/assets/icons/arrow_down.svg +3 -3
  11. package/src/assets/icons/arrow_up_red.svg +3 -3
  12. package/src/assets/icons/black_spinner.svg +35 -35
  13. package/src/assets/icons/delete_icon.svg +11 -11
  14. package/src/assets/icons/delete_icon_gray.svg +11 -11
  15. package/src/assets/icons/drag_icon.svg +8 -8
  16. package/src/assets/icons/external_icon.svg +6 -6
  17. package/src/assets/icons/language_icon.svg +6 -6
  18. package/src/assets/icons/pdf_icon.svg +6 -6
  19. package/src/assets/icons/plus_button.svg +4 -4
  20. package/src/assets/icons/search_icon_black.svg +3 -3
  21. package/src/assets/icons/subposition_icon.svg +3 -3
  22. package/src/assets/icons/subposition_marker.svg +3 -3
  23. package/src/assets/icons/warning_icon.svg +3 -3
  24. package/src/assets/svgIcons/2d_active.svg +7 -7
  25. package/src/assets/svgIcons/2d_inactive.svg +8 -8
  26. package/src/assets/svgIcons/3d_active.svg +7 -7
  27. package/src/assets/svgIcons/3d_inactive.svg +8 -8
  28. package/src/assets/svgIcons/_readme.md +7 -7
  29. package/src/assets/svgIcons/accept.svg +5 -5
  30. package/src/assets/svgIcons/activate_panels_active.svg +22 -22
  31. package/src/assets/svgIcons/activate_panels_inactive.svg +20 -20
  32. package/src/assets/svgIcons/add_icon-1.svg +3 -3
  33. package/src/assets/svgIcons/add_icon.svg +4 -4
  34. package/src/assets/svgIcons/address_book.svg +3 -3
  35. package/src/assets/svgIcons/after_sale_as_a_service.svg +6 -6
  36. package/src/assets/svgIcons/all_good.svg +3 -3
  37. package/src/assets/svgIcons/angle_active.svg +5 -5
  38. package/src/assets/svgIcons/angle_inactive.svg +4 -4
  39. package/src/assets/svgIcons/area_active.svg +11 -11
  40. package/src/assets/svgIcons/area_inactive.svg +26 -26
  41. package/src/assets/svgIcons/areas_tool.svg +14 -14
  42. package/src/assets/svgIcons/arrow_down.svg +3 -3
  43. package/src/assets/svgIcons/arrow_left.svg +4 -4
  44. package/src/assets/svgIcons/arrow_right.svg +4 -4
  45. package/src/assets/svgIcons/arrow_up.svg +3 -3
  46. package/src/assets/svgIcons/attachment.svg +3 -3
  47. package/src/assets/svgIcons/base_layer.svg +3 -3
  48. package/src/assets/svgIcons/battery.svg +3 -3
  49. package/src/assets/svgIcons/bell.svg +3 -3
  50. package/src/assets/svgIcons/bexio.svg +4 -4
  51. package/src/assets/svgIcons/bold.svg +3 -3
  52. package/src/assets/svgIcons/bom.svg +3 -3
  53. package/src/assets/svgIcons/bom_generation.svg +10 -10
  54. package/src/assets/svgIcons/bookmaker.svg +3 -3
  55. package/src/assets/svgIcons/bubble.svg +3 -3
  56. package/src/assets/svgIcons/bug.svg +5 -5
  57. package/src/assets/svgIcons/buildings.svg +55 -0
  58. package/src/assets/svgIcons/bullet_list.svg +8 -8
  59. package/src/assets/svgIcons/calendar.svg +7 -7
  60. package/src/assets/svgIcons/calendar_icon.svg +7 -7
  61. package/src/assets/svgIcons/call.svg +3 -3
  62. package/src/assets/svgIcons/camera.svg +3 -3
  63. package/src/assets/svgIcons/car.svg +3 -3
  64. package/src/assets/svgIcons/cart.svg +3 -3
  65. package/src/assets/svgIcons/charger_icon_white.svg +44 -44
  66. package/src/assets/svgIcons/checkbox.svg +3 -3
  67. package/src/assets/svgIcons/clear_formatting.svg +7 -7
  68. package/src/assets/svgIcons/clickable_info.svg +4 -4
  69. package/src/assets/svgIcons/clip.svg +3 -3
  70. package/src/assets/svgIcons/clock.svg +17 -17
  71. package/src/assets/svgIcons/clock_full.svg +3 -3
  72. package/src/assets/svgIcons/close_for_modals,_tool_tips.svg +4 -4
  73. package/src/assets/svgIcons/co_branding.svg +5 -5
  74. package/src/assets/svgIcons/collapse.svg +4 -4
  75. package/src/assets/svgIcons/collections.svg +3 -3
  76. package/src/assets/svgIcons/component_library.svg +7 -7
  77. package/src/assets/svgIcons/consumption_tariffs.svg +43 -43
  78. package/src/assets/svgIcons/context_menu-1.svg +6 -6
  79. package/src/assets/svgIcons/context_menu-2.svg +5 -5
  80. package/src/assets/svgIcons/context_menu.svg +5 -5
  81. package/src/assets/svgIcons/context_menu_tabs.svg +5 -5
  82. package/src/assets/svgIcons/cross.svg +4 -4
  83. package/src/assets/svgIcons/current_variant.svg +4 -4
  84. package/src/assets/svgIcons/dashboard.svg +3 -3
  85. package/src/assets/svgIcons/data_transfer.svg +3 -3
  86. package/src/assets/svgIcons/deadline.svg +4 -4
  87. package/src/assets/svgIcons/deal_flow.svg +5 -5
  88. package/src/assets/svgIcons/delete.svg +4 -4
  89. package/src/assets/svgIcons/delete_area_active.svg +16 -16
  90. package/src/assets/svgIcons/delete_area_inactive.svg +15 -15
  91. package/src/assets/svgIcons/direction_active-1.svg +12 -12
  92. package/src/assets/svgIcons/direction_active.svg +5 -5
  93. package/src/assets/svgIcons/direction_arrow.svg +4 -4
  94. package/src/assets/svgIcons/direction_inactive.svg +4 -4
  95. package/src/assets/svgIcons/dislike.svg +3 -3
  96. package/src/assets/svgIcons/distance_tool.svg +8 -8
  97. package/src/assets/svgIcons/distances_active.svg +9 -9
  98. package/src/assets/svgIcons/distances_inactive.svg +8 -8
  99. package/src/assets/svgIcons/distort_tool.svg +10 -10
  100. package/src/assets/svgIcons/distort_tool2.svg +16 -16
  101. package/src/assets/svgIcons/document.svg +3 -3
  102. package/src/assets/svgIcons/documents.svg +4 -4
  103. package/src/assets/svgIcons/downarrow.svg +3 -3
  104. package/src/assets/svgIcons/download.svg +4 -4
  105. package/src/assets/svgIcons/drag_icon.svg +8 -8
  106. package/src/assets/svgIcons/draggable_corner.svg +5 -5
  107. package/src/assets/svgIcons/draw_tool.svg +3 -3
  108. package/src/assets/svgIcons/duplicate-1.svg +8 -8
  109. package/src/assets/svgIcons/duplicate-2.svg +5 -5
  110. package/src/assets/svgIcons/duplicate.svg +4 -4
  111. package/src/assets/svgIcons/e-mobility_configurator.svg +6 -6
  112. package/src/assets/svgIcons/e_signature.svg +5 -5
  113. package/src/assets/svgIcons/edit_button.svg +3 -3
  114. package/src/assets/svgIcons/electricity_tariff.svg +3 -3
  115. package/src/assets/svgIcons/email.svg +3 -3
  116. package/src/assets/svgIcons/ems-1.svg +3 -3
  117. package/src/assets/svgIcons/ems.svg +3 -3
  118. package/src/assets/svgIcons/end_of_the_list.svg +5 -5
  119. package/src/assets/svgIcons/erase.svg +4 -4
  120. package/src/assets/svgIcons/external_icon.svg +5 -5
  121. package/src/assets/svgIcons/fav_icon.svg +4 -4
  122. package/src/assets/svgIcons/finance.svg +3 -3
  123. package/src/assets/svgIcons/financing_for_pv-1.svg +5 -5
  124. package/src/assets/svgIcons/financing_for_pv-2.svg +3 -3
  125. package/src/assets/svgIcons/financing_for_pv.svg +6 -6
  126. package/src/assets/svgIcons/finish-1.svg +4 -4
  127. package/src/assets/svgIcons/finish.svg +3 -3
  128. package/src/assets/svgIcons/flatten.svg +11 -11
  129. package/src/assets/svgIcons/flatten_roof.svg +20 -20
  130. package/src/assets/svgIcons/folder.svg +3 -3
  131. package/src/assets/svgIcons/free_technology.svg +5 -5
  132. package/src/assets/svgIcons/handle.svg +5 -5
  133. package/src/assets/svgIcons/heat_calc.svg +7 -7
  134. package/src/assets/svgIcons/height_equalize.svg +3 -3
  135. package/src/assets/svgIcons/height_snap.svg +3 -3
  136. package/src/assets/svgIcons/house.svg +3 -3
  137. package/src/assets/svgIcons/house_3d-1.svg +7 -7
  138. package/src/assets/svgIcons/house_3d.svg +7 -7
  139. package/src/assets/svgIcons/inclination.svg +2 -2
  140. package/src/assets/svgIcons/info.svg +3 -3
  141. package/src/assets/svgIcons/initial_situation.svg +3 -3
  142. package/src/assets/svgIcons/integrations.svg +3 -3
  143. package/src/assets/svgIcons/intro-tour-1.svg +3 -3
  144. package/src/assets/svgIcons/intro-tour.svg +3 -3
  145. package/src/assets/svgIcons/inverter-1.svg +5 -5
  146. package/src/assets/svgIcons/inverter.svg +3 -3
  147. package/src/assets/svgIcons/italic.svg +3 -3
  148. package/src/assets/svgIcons/key.svg +3 -3
  149. package/src/assets/svgIcons/lake.svg +29 -0
  150. package/src/assets/svgIcons/layers_close.svg +4 -4
  151. package/src/assets/svgIcons/layers_open.svg +4 -4
  152. package/src/assets/svgIcons/lead_marketplace.svg +6 -6
  153. package/src/assets/svgIcons/lead_provider.svg +4 -4
  154. package/src/assets/svgIcons/length_2d.svg +2 -2
  155. package/src/assets/svgIcons/length_3d.svg +4 -4
  156. package/src/assets/svgIcons/length_calculator.svg +2 -2
  157. package/src/assets/svgIcons/length_in_2d_active.svg +12 -12
  158. package/src/assets/svgIcons/length_in_2d_inctive.svg +13 -13
  159. package/src/assets/svgIcons/light_bulb.svg +3 -3
  160. package/src/assets/svgIcons/like.svg +3 -3
  161. package/src/assets/svgIcons/line_graph.svg +3 -3
  162. package/src/assets/svgIcons/local_subsidies.svg +18 -18
  163. package/src/assets/svgIcons/location.svg +3 -3
  164. package/src/assets/svgIcons/lock.svg +3 -3
  165. package/src/assets/svgIcons/logout.svg +3 -3
  166. package/src/assets/svgIcons/loop.svg +3 -3
  167. package/src/assets/svgIcons/low-vegetation.svg +37 -0
  168. package/src/assets/svgIcons/lunch.svg +4 -4
  169. package/src/assets/svgIcons/magic_tool.svg +6 -6
  170. package/src/assets/svgIcons/map_icon.svg +5 -5
  171. package/src/assets/svgIcons/map_settings.svg +3 -3
  172. package/src/assets/svgIcons/margin_tool.svg +4 -4
  173. package/src/assets/svgIcons/meeting.svg +6 -6
  174. package/src/assets/svgIcons/move_copy.svg +4 -4
  175. package/src/assets/svgIcons/new_area_inactive.svg +11 -11
  176. package/src/assets/svgIcons/next.svg +4 -4
  177. package/src/assets/svgIcons/normal-tg.svg +30 -0
  178. package/src/assets/svgIcons/normal-vegetation.svg +53 -0
  179. package/src/assets/svgIcons/not_equal_to.svg +3 -3
  180. package/src/assets/svgIcons/numbered_list.svg +6 -6
  181. package/src/assets/svgIcons/obstacle_tool.svg +9 -9
  182. package/src/assets/svgIcons/obstacle_tool_origin.svg +3 -3
  183. package/src/assets/svgIcons/offset_tool.svg +8 -8
  184. package/src/assets/svgIcons/open-tg.svg +21 -0
  185. package/src/assets/svgIcons/outline_tool.svg +11 -11
  186. package/src/assets/svgIcons/pan_tool.svg +12 -12
  187. package/src/assets/svgIcons/panels_tool.svg +8 -8
  188. package/src/assets/svgIcons/pen_tool.svg +4 -4
  189. package/src/assets/svgIcons/picker_tool.svg +4 -4
  190. package/src/assets/svgIcons/picture.svg +3 -3
  191. package/src/assets/svgIcons/pin.svg +5 -5
  192. package/src/assets/svgIcons/presentation.svg +3 -3
  193. package/src/assets/svgIcons/previous.svg +4 -4
  194. package/src/assets/svgIcons/profile-1.svg +4 -4
  195. package/src/assets/svgIcons/profile.svg +4 -4
  196. package/src/assets/svgIcons/profitability.svg +3 -3
  197. package/src/assets/svgIcons/project_analysis.svg +4 -4
  198. package/src/assets/svgIcons/project_settings.svg +4 -4
  199. package/src/assets/svgIcons/protected-tg.svg +47 -0
  200. package/src/assets/svgIcons/pv.svg +3 -3
  201. package/src/assets/svgIcons/quotations.svg +6 -6
  202. package/src/assets/svgIcons/redo.svg +6 -6
  203. package/src/assets/svgIcons/resizer.svg +5 -5
  204. package/src/assets/svgIcons/roof_layer.svg +3 -3
  205. package/src/assets/svgIcons/rotate_tool.svg +3 -3
  206. package/src/assets/svgIcons/rotate_view.svg +5 -5
  207. package/src/assets/svgIcons/ruler_tool.svg +3 -3
  208. package/src/assets/svgIcons/run_simulation.svg +3 -3
  209. package/src/assets/svgIcons/save.svg +3 -3
  210. package/src/assets/svgIcons/scaling_tool.svg +8 -8
  211. package/src/assets/svgIcons/sea.svg +34 -0
  212. package/src/assets/svgIcons/search.svg +3 -3
  213. package/src/assets/svgIcons/security.svg +3 -3
  214. package/src/assets/svgIcons/settings.svg +3 -3
  215. package/src/assets/svgIcons/show_in_a_new_tab.svg +12 -12
  216. package/src/assets/svgIcons/smartphone.svg +4 -4
  217. package/src/assets/svgIcons/solar_calc.svg +13 -13
  218. package/src/assets/svgIcons/sorting.svg +4 -4
  219. package/src/assets/svgIcons/split.svg +12 -12
  220. package/src/assets/svgIcons/start_of_the_list.svg +5 -5
  221. package/src/assets/svgIcons/strikethrough.svg +4 -4
  222. package/src/assets/svgIcons/subscriptions.svg +3 -3
  223. package/src/assets/svgIcons/subsidies-1.svg +5 -5
  224. package/src/assets/svgIcons/subsidies-2.svg +3 -3
  225. package/src/assets/svgIcons/subsidies.svg +3 -3
  226. package/src/assets/svgIcons/subtract_icon.svg +3 -3
  227. package/src/assets/svgIcons/suitcase.svg +3 -3
  228. package/src/assets/svgIcons/summer.svg +3 -3
  229. package/src/assets/svgIcons/template_icon_not_clickable.svg +6 -6
  230. package/src/assets/svgIcons/transfer.svg +4 -4
  231. package/src/assets/svgIcons/trim_tool.svg +4 -4
  232. package/src/assets/svgIcons/truck.svg +3 -3
  233. package/src/assets/svgIcons/underlined.svg +3 -3
  234. package/src/assets/svgIcons/undo.svg +6 -6
  235. package/src/assets/svgIcons/uparrow.svg +3 -3
  236. package/src/assets/svgIcons/update.svg +3 -3
  237. package/src/assets/svgIcons/upload_avatar-1.svg +12 -12
  238. package/src/assets/svgIcons/upload_avatar.svg +5 -5
  239. package/src/assets/svgIcons/upload_image.svg +8 -8
  240. package/src/assets/svgIcons/upload_image_tool.svg +7 -7
  241. package/src/assets/svgIcons/variants.svg +6 -6
  242. package/src/assets/svgIcons/vertical_tool.svg +3 -3
  243. package/src/assets/svgIcons/virtual_storage.svg +4 -4
  244. package/src/assets/svgIcons/warning.svg +4 -4
  245. package/src/assets/svgIcons/way.svg +5 -5
  246. package/src/assets/svgIcons/wifi.svg +3 -3
  247. package/src/assets/svgIcons/winter.svg +3 -3
  248. package/src/assets/svgIcons/workflow_template.svg +11 -11
  249. package/src/assets/theme.js +41 -41
  250. package/src/components/addNewButton/AddNewButton.stories.js +24 -24
  251. package/src/components/addNewButton/index.vue +61 -61
  252. package/src/components/banner/actionBanner/index.vue +64 -64
  253. package/src/components/banner/banner/banner.stories.js +31 -31
  254. package/src/components/banner/banner/index.vue +188 -188
  255. package/src/components/banner/infoBanner/index.vue +57 -57
  256. package/src/components/buttons/buttonIcon/index.vue +145 -145
  257. package/src/components/buttons/closeButton/CloseButton.stories.js +29 -29
  258. package/src/components/buttons/closeButton/index.vue +61 -61
  259. package/src/components/buttons/mainButton/index.vue +140 -144
  260. package/src/components/card/index.vue +96 -96
  261. package/src/components/collapsableInfoText/index.vue +127 -127
  262. package/src/components/deleteIcon/DeleteIcon.stories.js +29 -29
  263. package/src/components/deleteIcon/index.vue +78 -78
  264. package/src/components/draggableInputHandle/index.vue +46 -46
  265. package/src/components/dropdown/Dropdown.stories.js +53 -53
  266. package/src/components/dropdown/index.vue +138 -138
  267. package/src/components/errorMessage/index.vue +64 -64
  268. package/src/components/filter/filterSettings.vue +639 -639
  269. package/src/components/filter/index.vue +154 -154
  270. package/src/components/filter/parentDropdown.vue +91 -91
  271. package/src/components/icon/Icons.stories.js +41 -41
  272. package/src/components/icon/iconCache.js +23 -23
  273. package/src/components/icon/iconCollection.vue +68 -68
  274. package/src/components/icon/index.vue +140 -140
  275. package/src/components/iconWrapper/index.vue +179 -179
  276. package/src/components/infoCard/index.vue +40 -40
  277. package/src/components/infoText/index.vue +170 -170
  278. package/src/components/inputs/checkbox/Checkbox.stories.js +57 -57
  279. package/src/components/inputs/checkbox/index.vue +224 -224
  280. package/src/components/inputs/inputNumber/InputNumber.stories.js +150 -150
  281. package/src/components/inputs/inputNumber/index.vue +789 -789
  282. package/src/components/inputs/inputNumberQuestion/index.vue +218 -218
  283. package/src/components/inputs/inputText/InputText.stories.js +75 -75
  284. package/src/components/inputs/inputText/index.vue +376 -376
  285. package/src/components/inputs/radioButton/RadioButton.stories.js +58 -58
  286. package/src/components/inputs/radioButton/index.vue +273 -273
  287. package/src/components/inputs/searchInput/SearchInput.stories.js +40 -40
  288. package/src/components/inputs/searchInput/index.vue +151 -151
  289. package/src/components/inputs/select/index.vue +908 -908
  290. package/src/components/inputs/select/option/index.vue +148 -148
  291. package/src/components/inputs/select/select.stories.js +58 -58
  292. package/src/components/inputs/slider/index.vue +126 -126
  293. package/src/components/inputs/switchField/index.vue +254 -254
  294. package/src/components/inputs/textAreaInput/TextAreaInput.stories.js +127 -127
  295. package/src/components/inputs/textAreaInput/index.vue +198 -198
  296. package/src/components/inputs/toggle/Toggle.stories.js +77 -77
  297. package/src/components/inputs/toggle/index.vue +285 -285
  298. package/src/components/label/index.vue +99 -99
  299. package/src/components/markerItem/index.vue +88 -88
  300. package/src/components/modals/actionModal/index.vue +64 -64
  301. package/src/components/modals/infoModal/index.vue +52 -52
  302. package/src/components/modals/modal/index.vue +188 -188
  303. package/src/components/modals/modal/modal.stories.js +31 -31
  304. package/src/components/navigationTabs/index.vue +114 -114
  305. package/src/components/pageSubtitle/index.vue +67 -67
  306. package/src/components/pageTitle/index.vue +68 -68
  307. package/src/components/pagination/index.vue +148 -148
  308. package/src/components/progressBar/index.vue +125 -125
  309. package/src/components/projectMarker/index.vue +300 -300
  310. package/src/components/rangeSlider/Slider.vue +573 -573
  311. package/src/components/rangeSlider/index.vue +517 -517
  312. package/src/components/rangeSlider/utils/dom.js +49 -49
  313. package/src/components/rangeSlider/utils/fns.js +26 -26
  314. package/src/components/selectedOptions/index.vue +145 -145
  315. package/src/components/sideMenu/index.vue +270 -270
  316. package/src/components/spinner/index.vue +68 -68
  317. package/src/components/tableDropdown/index.vue +638 -638
  318. package/src/components/tables/mainTable/exampleNested.vue +328 -328
  319. package/src/components/tables/mainTable/index.vue +510 -510
  320. package/src/components/tables/viewTable/index.vue +195 -195
  321. package/src/components/tabsHeader/index.vue +83 -83
  322. package/src/components/threeDots/index.vue +413 -413
  323. package/src/components/videoThumbnail/index.vue +103 -103
  324. package/src/components/videoThumbnail/videoThumbnail.stories.js +33 -33
  325. package/src/helpers/currencyMapping.js +28 -28
  326. package/src/helpers/numberConverter.js +103 -103
  327. package/src/helpers/translateLang.js +128 -128
  328. package/src/main.js +6 -6
  329. package/src/mixins/inputValidations.js +97 -97
  330. package/src/utils/index.js +12 -12
@@ -1,908 +1,908 @@
1
- <template>
2
- <Container
3
- :no-relative="noRelative"
4
- :select-width="selectWidth"
5
- @mouseenter="mouseEnterHandler"
6
- @mouseleave="mouseLeaveHandler"
7
- >
8
- <InputWrapper
9
- :align-items="alignItems"
10
- :has-label="!!label && label.length > 0"
11
- :no-relative="noRelative"
12
- >
13
- <LabelWrapper
14
- v-if="label"
15
- :data-id="labelDataId"
16
- :info-text-message="!!infoTextMessage || !!$slots.infoText"
17
- >
18
- <InputLabel
19
- :font-color="
20
- labelFontColor || colorMode == 'dark' ? 'white' : 'eturnityGrey'
21
- "
22
- :font-size="fontSize"
23
- >{{ label }}
24
- <OptionalLabel v-if="labelOptional">
25
- ({{ $gettext('Optional') }})</OptionalLabel
26
- >
27
- </InputLabel>
28
- <InfoText
29
- v-if="infoTextMessage || !!$slots.infoText"
30
- :align-arrow="infoTextAlignArrow"
31
- :max-width="infoTextWidth"
32
- :size="infoTextSize"
33
- :text="infoTextMessage"
34
- :width="infoTextWidth"
35
- >
36
- <slot name="infoText"></slot>
37
- </InfoText>
38
- </LabelWrapper>
39
- <SelectButtonWrapper :disabled="disabled">
40
- <SelectButton
41
- ref="select"
42
- :bg-color="
43
- buttonBgColor || colorMode == 'dark' ? 'transparentBlack1' : 'white'
44
- "
45
- class="select-button"
46
- :data-id="dataId"
47
- :disabled="disabled"
48
- :font-color="
49
- buttonFontColor || colorMode == 'dark' ? 'white' : 'black'
50
- "
51
- :font-size="fontSize"
52
- :has-error="hasError"
53
- :has-no-padding="isSearchBarVisible || !hasSelectButtonPadding"
54
- :height="height"
55
- :no-relative="noRelative"
56
- :padding-left="paddingLeft"
57
- :select-height="selectHeight"
58
- :select-min-height="selectMinHeight"
59
- :select-width="selectWidth"
60
- :show-border="showBorder"
61
- :show-disabled-background="showDisabledBackground"
62
- :table-padding-left="tablePaddingLeft"
63
- @click="toggleDropdown"
64
- @keydown="onKeyDown"
65
- >
66
- <DraggableInputHandle
67
- v-if="isDraggable && !isSearchBarVisible"
68
- :height="selectHeight"
69
- />
70
- <InputText
71
- v-if="isSearchBarVisible"
72
- ref="searchInput"
73
- background-color="transparent"
74
- :font-color="
75
- buttonFontColor || colorMode == 'dark' ? 'white' : 'black'
76
- "
77
- :font-size="fontSize"
78
- input-height="34px"
79
- :input-width="computedWidth"
80
- :no-border="true"
81
- tabindex="0"
82
- :value="textSearch"
83
- @click.stop
84
- @input-change="searchChange"
85
- @keydown.stop="onKeyDown"
86
- />
87
- <Selector
88
- v-else
89
- :padding-left="paddingLeft"
90
- :select-width="selectWidth"
91
- :show-border="showBorder"
92
- >
93
- <slot name="selector" :selected-value="selectedValue"></slot>
94
- </Selector>
95
- <Caret class="caret_dropdown" @click.stop="toggleCaretDropdown">
96
- <Icon
97
- v-if="isDropdownOpen"
98
- :color="
99
- caretColor || colorMode == 'dark'
100
- ? 'white'
101
- : 'transparentBlack1'
102
- "
103
- name="arrow_up"
104
- size="12px"
105
- />
106
- <Icon
107
- v-else
108
- :color="
109
- caretColor || colorMode == 'dark'
110
- ? 'white'
111
- : 'transparentBlack1'
112
- "
113
- name="arrow_down"
114
- size="12px"
115
- />
116
- </Caret>
117
- </SelectButton>
118
- <DropdownWrapper ref="dropdownWrapperRef" :no-relative="noRelative">
119
- <Teleport to="#portal-target">
120
- <SelectDropdown
121
- v-show="isSelectDropdownShown"
122
- ref="dropdown"
123
- :bg-color="
124
- dropdownBgColor || colorMode == 'dark' ? 'black' : 'white'
125
- "
126
- :dropdown-position="dropdownPosition"
127
- :font-color="
128
- dropdownFontColor || colorMode == 'dark' ? 'white' : 'black'
129
- "
130
- :font-size="fontSize"
131
- :hovered-bg-color="
132
- colorMode == 'dark' ? '#000000' : dropdownBgColor
133
- "
134
- :hovered-index="hoveredIndex"
135
- :hovered-value="hoveredValue"
136
- :is-active="isActive"
137
- :min-width="minWidth"
138
- :no-relative="noRelative"
139
- :option-width="getOptionWidth"
140
- :selected-value="selectedValue"
141
- @mouseleave="optionLeave"
142
- @option-hovered="optionHovered"
143
- @option-selected="optionSelected"
144
- >
145
- <slot name="dropdown"></slot>
146
- </SelectDropdown>
147
- </Teleport>
148
- </DropdownWrapper>
149
- </SelectButtonWrapper>
150
- </InputWrapper>
151
- </Container>
152
- </template>
153
-
154
- <script>
155
- //How to use it
156
- // <Select
157
- // hoverDropdown="true"
158
- // selectWidth="100%"
159
- // minWidth="220px"
160
- // optionWidth="50%"
161
- // label="that is a label"
162
- // alignItems="vertical"
163
- // label-data-id="test-label-data-id"
164
- // data-id="test-data-id"
165
- // :hasSelectButtonPadding="false"
166
- // >
167
- // <template #selector="{selectedValue}">
168
- // value selected: {{selectedValue}}
169
- // </template>
170
- // <template #dropdown>
171
- // <Option value="1">value one</Option>
172
- // <Option value="2">value two</Option>
173
- // <Option value="3">value three</Option>
174
- // <Option value="4">value four</Option>
175
- // </template>
176
- // </Select>
177
-
178
- import { Teleport } from 'vue'
179
- import styled from 'vue3-styled-components'
180
- import InfoText from '../../infoText'
181
- import Icon from '../../icon'
182
- import InputText from '../inputText'
183
- import DraggableInputHandle from '../../draggableInputHandle'
184
- import { debounce } from '../../../utils'
185
-
186
- const CARET_WIDTH = '30px'
187
- const BORDER_WIDTH = '1px'
188
-
189
- const Caret = styled.div`
190
- display: flex;
191
- align-items: center;
192
- justify-content: center;
193
- width: ${CARET_WIDTH};
194
- min-width: ${CARET_WIDTH};
195
- height: 100%;
196
- align-items: center;
197
- cursor: pointer;
198
- margin-left: auto;
199
- `
200
-
201
- const selectorProps = {
202
- selectWidth: String,
203
- paddingLeft: String,
204
- showBorder: Boolean,
205
- }
206
- const Selector = styled('div', selectorProps)`
207
- ${(props) =>
208
- props.selectWidth === '100%'
209
- ? 'width: 100%;'
210
- : `width: calc(${props.selectWidth} -
211
- (
212
- ${CARET_WIDTH} +
213
- ${props.paddingLeft}
214
- ${props.showBorder ? `+ (${BORDER_WIDTH} * 2)` : ''}
215
- )
216
- );
217
- white-space: nowrap;
218
- text-overflow: ellipsis;
219
- overflow: hidden;`}
220
- `
221
-
222
- const labelAttrs = { fontSize: String, fontColor: String }
223
- const InputLabel = styled('div', labelAttrs)`
224
- color: ${(props) =>
225
- props.theme.colors[props.fontColor]
226
- ? props.theme.colors[props.fontColor]
227
- : props.fontColor};
228
- font-size: ${(props) => props.fontSize};
229
- font-weight: 700;
230
- `
231
- const OptionalLabel = styled.span`
232
- font-weight: 300;
233
- `
234
- const inputProps = {
235
- selectWidth: String,
236
- optionWidth: String,
237
- noRelative: Boolean,
238
- }
239
- const Container = styled('div', inputProps)`
240
- width: ${(props) => props.selectWidth};
241
- position: ${(props) => (props.noRelative ? 'static' : 'relative')};
242
- display: inline-block;
243
- `
244
-
245
- const LabelWrapperAttrs = { infoTextMessage: Boolean }
246
- const LabelWrapper = styled('div', LabelWrapperAttrs)`
247
- display: inline-grid;
248
- grid-template-columns: ${(props) =>
249
- props.infoTextMessage ? 'auto auto' : 'auto'};
250
- grid-gap: 12px;
251
- align-items: center;
252
- justify-content: start;
253
- `
254
-
255
- const SelectButtonWrapperAttrs = {
256
- disabled: Boolean,
257
- }
258
- const SelectButtonWrapper = styled('div', SelectButtonWrapperAttrs)`
259
- ${(props) => (props.disabled ? 'cursor: not-allowed' : 'cursor: pointer')};
260
- `
261
-
262
- const selectButtonAttrs = {
263
- bgColor: String,
264
- fontColor: String,
265
- hasError: Boolean,
266
- disabled: Boolean,
267
- selectHeight: String,
268
- selectWidth: String,
269
- height: String,
270
- selectMinHeight: String,
271
- hasNoPadding: Boolean,
272
- showBorder: Boolean,
273
- paddingLeft: String,
274
- noRelative: Boolean,
275
- tablePaddingLeft: String,
276
- showDisabledBackground: Boolean,
277
- }
278
- const SelectButton = styled('div', selectButtonAttrs)`
279
- position: ${(props) => (props.noRelative ? 'static' : 'relative')};
280
- box-sizing: border-box;
281
- border-radius: 4px;
282
- max-width: ${(props) => (props.selectWidth ? props.selectWidth : '100%')};
283
- ${(props) =>
284
- props.isSearchBarVisible
285
- ? ''
286
- : `padding-left: ${
287
- props.hasNoPadding
288
- ? '0'
289
- : props.tablePaddingLeft
290
- ? props.tablePaddingLeft
291
- : props.paddingLeft
292
- }`};
293
- text-align: left;
294
- min-height: ${(props) =>
295
- props.selectHeight
296
- ? props.selectHeight
297
- : props.selectMinHeight
298
- ? props.selectMinHeight
299
- : props.height
300
- ? props.height
301
- : '36px'};
302
- display: flex;
303
- align-items: center;
304
- height: ${(props) => props.selectHeight};
305
- ${({ showBorder, theme, hasError }) =>
306
- showBorder &&
307
- `
308
- border: ${BORDER_WIDTH} solid ${
309
- hasError ? theme.colors.red : theme.colors.grey4
310
- }
311
- `}
312
- background-color:${(props) =>
313
- props.disabled && props.showDisabledBackground
314
- ? props.theme.colors.grey5
315
- : props.theme.colors[props.bgColor]
316
- ? props.theme.colors[props.bgColor]
317
- : props.bgColor};
318
- color: ${(props) =>
319
- props.theme.colors[props.fontColor]
320
- ? props.theme.colors[props.fontColor]
321
- : props.fontColor};
322
- ${(props) => (props.disabled ? 'pointer-events: none' : '')};
323
- overflow: hidden;
324
- & > .handle {
325
- border-right: ${(props) =>
326
- props.hasError ? props.theme.colors.red : props.theme.colors.grey4}
327
- 1px solid;
328
- }
329
- `
330
- const selectDropdownAttrs = {
331
- hoveredBgColor: String,
332
- bgColor: String,
333
- fontColor: String,
334
- optionWidth: String,
335
- hoveredIndex: Number,
336
- fontSize: String,
337
- dropdownPosition: Object,
338
- hoveredValue: Number | String,
339
- selectedValue: Number | String,
340
- noRelative: Boolean,
341
- minWidth: String,
342
- }
343
- const SelectDropdown = styled('div', selectDropdownAttrs)`
344
- box-sizing: border-box;
345
- z-index: ${(props) => (props.isActive ? '2' : '99999')};
346
- position: absolute;
347
- top: ${(props) =>
348
- props.noRelative ? 'auto' : props.dropdownPosition?.top + 'px'};
349
- left: ${(props) => props.dropdownPosition?.left}px;
350
- border: ${BORDER_WIDTH} solid ${(props) => props.theme.colors.grey4};
351
- border-radius: 4px;
352
- display: flex;
353
- flex-direction: column;
354
- align-items: flex-start;
355
- padding: 0px;
356
- box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
357
- width: ${(props) => (props.optionWidth ? props.optionWidth : '100%')};
358
- min-width: ${(props) =>
359
- props.minWidth
360
- ? props.minWidth
361
- : props.optionWidth
362
- ? props.optionWidth
363
- : '100%'};
364
- background-color: ${(props) =>
365
- props.theme.colors[props.bgColor]
366
- ? props.theme.colors[props.bgColor]
367
- : props.bgColor};
368
- color: ${(props) =>
369
- props.theme.colors[props.fontColor]
370
- ? props.theme.colors[props.fontColor]
371
- : props.fontColor};
372
- max-height: 300px;
373
- overflow-y: auto;
374
- & > div[data-value='${(props) => props.hoveredValue}'] {
375
- background-color: ${(props) =>
376
- props.theme.colors[props.hoveredBgColor]
377
- ? props.theme.colors[props.hoveredBgColor]
378
- : props.hoveredBgColor};
379
- }
380
- font-size: ${(props) => props.fontSize};
381
- `
382
- SelectDropdown.emits = ['option-hovered', 'option-selected']
383
- const DropdownAttrs = { noRelative: Boolean }
384
- const DropdownWrapper = styled('div', DropdownAttrs)`
385
- position: ${(props) => (props.noRelative ? 'static' : 'relative')};
386
- `
387
- const inputAttrs = {
388
- alignItems: String,
389
- hasLabel: Boolean,
390
- noRelative: Boolean,
391
- }
392
- const InputWrapper = styled('div', inputAttrs)`
393
- position: ${(props) => (props.noRelative ? 'static' : 'relative')};
394
- display: grid;
395
- width: 100%;
396
- min-width: ${(props) => (props.minWidth ? props.minWidth : '150px')};
397
- align-items: center;
398
- gap: 8px;
399
- grid-template-columns: ${(props) =>
400
- props.alignItems === 'vertical' || !props.hasLabel ? '1fr' : 'auto 1fr'};
401
- `
402
-
403
- const DROPDOWN_HEIGHT_OFFSET = 4
404
- const DROPDOWN_TOP_OFFSET = 21
405
- const MIN_OPTION_LENGTH = 5
406
-
407
- const DROPDOWN_MENU_POSITIONS = {
408
- Automatic: 'automatic',
409
- Bottom: 'bottom',
410
- }
411
-
412
- export default {
413
- name: 'RCselect',
414
-
415
- components: {
416
- SelectButton,
417
- SelectButtonWrapper,
418
- SelectDropdown,
419
- Container,
420
- InputLabel,
421
- LabelWrapper,
422
- OptionalLabel,
423
- InfoText,
424
- InputWrapper,
425
- DropdownWrapper,
426
- Icon,
427
- Caret,
428
- Selector,
429
- InputText,
430
- Teleport,
431
- DraggableInputHandle,
432
- },
433
-
434
- props: {
435
- value: {
436
- required: false,
437
- default: null,
438
- },
439
- fontSize: {
440
- required: false,
441
- default: '13px',
442
- },
443
- noRelative: {
444
- required: false,
445
- default: false,
446
- },
447
- label: {
448
- required: false,
449
- },
450
- labelOptional: {
451
- required: false,
452
- default: false,
453
- },
454
- labelDataId: {
455
- required: false,
456
- default: '',
457
- },
458
- infoTextMessage: {
459
- required: false,
460
- },
461
- selectWidth: {
462
- type: String,
463
- required: false,
464
- default: '100%',
465
- },
466
- minWidth: {
467
- required: false,
468
- },
469
- maxWidth: {
470
- required: false,
471
- },
472
- selectHeight: {
473
- type: String,
474
- required: false,
475
- default: null,
476
- },
477
- height: {
478
- required: false,
479
- default: null,
480
- },
481
- selectMinHeight: {
482
- required: false,
483
- default: '36px',
484
- },
485
- optionWidth: {
486
- required: false,
487
- default: null,
488
- },
489
- hoverDropdown: {
490
- required: false,
491
- default: false,
492
- },
493
- dropdownAutoClose: {
494
- required: false,
495
- default: false,
496
- },
497
- alignItems: {
498
- required: false,
499
- default: 'horizontal',
500
- },
501
- buttonBgColor: {
502
- required: false,
503
- },
504
- buttonFontColor: {
505
- required: false,
506
- },
507
- dropdownBgColor: {
508
- required: false,
509
- default: 'grey5',
510
- },
511
- dropdownFontColor: {
512
- required: false,
513
- },
514
- dropDownArrowVisible: {
515
- required: false,
516
- default: true,
517
- },
518
- caretColor: {
519
- required: false,
520
- },
521
- labelFontColor: {
522
- required: false,
523
- },
524
- colorMode: {
525
- required: false,
526
- default: 'light',
527
- },
528
- isSearchable: {
529
- required: false,
530
- default: true,
531
- },
532
- hasError: {
533
- required: false,
534
- default: false,
535
- },
536
- disabled: {
537
- required: false,
538
- default: false,
539
- },
540
- isAutoSearch: {
541
- required: false,
542
- default: true,
543
- },
544
- showBorder: {
545
- required: false,
546
- default: true,
547
- },
548
- infoTextSize: {
549
- required: false,
550
- default: '14px',
551
- },
552
- dataId: {
553
- type: String,
554
- default: '',
555
- },
556
- hasSelectButtonPadding: {
557
- required: false,
558
- type: Boolean,
559
- default: true,
560
- },
561
- isDraggable: {
562
- type: Boolean,
563
- default: false,
564
- },
565
- leftPadding: {
566
- type: String,
567
- default: '15px',
568
- },
569
- tablePaddingLeft: {
570
- required: false,
571
- },
572
- showDisabledBackground: {
573
- required: false,
574
- default: true,
575
- },
576
- minOptionLength: {
577
- type: Number,
578
- default: MIN_OPTION_LENGTH,
579
- },
580
- dropdownMenuPosition: {
581
- type: String,
582
- default: DROPDOWN_MENU_POSITIONS.Automatic, // options: ['automatic', bottom]
583
- },
584
- infoTextWidth: {
585
- type: String,
586
- required: false,
587
- },
588
- infoTextAlignArrow: {
589
- type: String,
590
- required: false,
591
- },
592
- },
593
-
594
- data() {
595
- return {
596
- selectedValue: null,
597
- paddingLeft: this.isDraggable ? '30px' : this.leftPadding,
598
- isDropdownOpen: false,
599
- isActive: false,
600
- textSearch: '',
601
- hoveredIndex: 0,
602
- isClickOutsideActive: false,
603
- dropdownPosition: {
604
- left: null,
605
- top: null,
606
- },
607
- dropdownWidth: null,
608
- hoveredValue: null,
609
- }
610
- },
611
- computed: {
612
- optionLength() {
613
- if (this.isDropdownOpen) {
614
- return this.$refs.dropdown.$el.childElementCount > 1
615
- ? this.$refs.dropdown.$el.childElementCount
616
- : this.$refs.dropdown.$el.children[0].childElementCount
617
- }
618
-
619
- return 0
620
- },
621
- isSearchBarVisible() {
622
- return (
623
- this.isSearchable &&
624
- this.optionLength >= this.minOptionLength &&
625
- this.isDropdownOpen
626
- )
627
- },
628
- computedWidth() {
629
- function removePX(item) {
630
- return Number(item.replace('px', ''))
631
- }
632
-
633
- return this.selectWidth === '100%'
634
- ? '100%'
635
- : removePX(this.selectWidth) - removePX(CARET_WIDTH) + 'px'
636
- },
637
- getOptionWidth() {
638
- if (this.optionWidth) return this.optionWidth
639
-
640
- return this.dropdownWidth
641
- },
642
- isSelectDropdownShown() {
643
- return (
644
- this.isDropdownOpen &&
645
- this.dropdownPosition.left !== null &&
646
- (!this.isSearchable || this.isSearchable)
647
- )
648
- },
649
- isMobileDevice() {
650
- const userAgent =
651
- navigator.userAgent || navigator.vendor || window.opera
652
- const touchCapable =
653
- 'ontouchstart' in window ||
654
- navigator.maxTouchPoints > 0 ||
655
- navigator.msMaxTouchPoints > 0
656
-
657
- return (
658
- /Android/i.test(userAgent) ||
659
- /iPad|iPhone|iPod/.test(userAgent) ||
660
- (/Macintosh/.test(userAgent) && touchCapable) ||
661
- /windows phone/i.test(userAgent)
662
- )
663
- },
664
- },
665
- watch: {
666
- value(val) {
667
- this.selectedValue = val
668
- },
669
- async isDropdownOpen(val) {
670
- if (val) {
671
- this.$emit('on-dropdown-open')
672
- setTimeout(() => {
673
- this.isClickOutsideActive = true
674
- }, 10)
675
- await this.$nextTick()
676
- this.handleSetDropdownOffet()
677
- } else {
678
- this.dropdownPosition.left = null
679
- setTimeout(() => {
680
- this.isClickOutsideActive = false
681
- }, 10)
682
- }
683
- if (val && this.isSearchable) {
684
- this.$nextTick(() => {
685
- if (this.$refs.searchInput && !this.isMobileDevice) {
686
- this.$refs.searchInput.$el.querySelector('input').focus()
687
- }
688
- })
689
- }
690
- },
691
- },
692
- mounted() {
693
- this.observeDropdownHeight()
694
- this.observeSelectWidth()
695
- window.addEventListener('resize', this.handleSetDropdownOffet)
696
- },
697
- beforeMount() {
698
- this.selectedValue = this.value
699
- document.addEventListener('click', this.clickOutside)
700
- this.getDropdownPosition()
701
- window.removeEventListener('resize', this.handleSetDropdownOffet)
702
- if (this.dropdownResizeObserver) this.dropdownResizeObserver.disconnect()
703
- if (this.selectResizeObserver) this.selectResizeObserver.disconnect()
704
- },
705
- unmounted() {
706
- document.removeEventListener('click', this.clickOutside)
707
- },
708
- methods: {
709
- focus() {
710
- this.isActive = true
711
- },
712
- blur(e) {
713
- this.isActive = false
714
- this.$emit('blur', e)
715
- },
716
- toggleDropdown() {
717
- this.isDropdownOpen = !this.isDropdownOpen
718
- },
719
- toggleCaretDropdown() {
720
- this.isDropdownOpen = !this.isDropdownOpen
721
- },
722
- closeDropdown() {
723
- this.blur()
724
- this.clearSearch()
725
- this.isDropdownOpen = false
726
- },
727
- clearSearch() {
728
- this.textSearch = ''
729
- this.searchChange('')
730
- },
731
- optionSelected(e) {
732
- this.selectedValue = e
733
- this.closeDropdown()
734
- this.blur()
735
- this.$emit('input-change', e)
736
- },
737
- optionHovered: debounce(function (e) {
738
- this.hoveredValue = e
739
- }, 300),
740
- mouseEnterHandler() {
741
- if (this.hoverDropdown) {
742
- this.focus()
743
- this.isDropdownOpen = true
744
- }
745
- },
746
- mouseLeaveHandler() {
747
- if (this.hoverDropdown) {
748
- this.blur()
749
- }
750
- },
751
- optionLeave() {
752
- if (this.dropdownAutoClose) {
753
- this.isDropdownOpen = false
754
- }
755
- },
756
- searchChange(value) {
757
- this.textSearch = value
758
- this.$emit('search-change', value)
759
- const dropdownChildren = [...this.$refs.dropdown.$el.children]
760
- dropdownChildren.forEach((el) => {
761
- if (!el.textContent.toLowerCase().includes(value.toLowerCase())) {
762
- el.style.display = 'none'
763
-
764
- return
765
- }
766
-
767
- el.style.display = 'inherit'
768
- })
769
- },
770
- clickOutside(event) {
771
- const dropdownRef = this.$refs.dropdown
772
- // we need to prevent closing on selecting an option, because in the case of
773
- // a disabled option, we don't want to close the dropdown
774
- if (!this.isClickOutsideActive) return
775
- if (
776
- this.$refs.select.$el == event.target ||
777
- this.$refs.select.$el.contains(event.target) ||
778
- event.target.id === 'more-button' ||
779
- event.target.parentNode === dropdownRef.$el
780
- ) {
781
- return
782
- } else {
783
- this.closeDropdown()
784
- }
785
- },
786
- onKeyDown(e) {
787
- if (e.key == 'ArrowDown') {
788
- this.onArrowPress(1)
789
- } else if (e.key == 'ArrowUp') {
790
- this.onArrowPress(-1)
791
- } else if (e.key == 'Enter') {
792
- const optionHoveredComponent = [...this.$refs.dropdown.$el.children][
793
- (this.hoveredIndex - 1 + this.optionLength) % this.optionLength
794
- ]
795
- this.optionSelected(optionHoveredComponent.$el.dataset.value)
796
- }
797
- },
798
- // If some part of the dropdown menu is outside viewport of the bottom of the screen,
799
- // we need to offset it and display it at the top of the select dropdown instead
800
- async getDropdownPosition() {
801
- if (
802
- !this.$refs.dropdownWrapperRef ||
803
- !this.$refs.select ||
804
- !this.$refs.dropdown
805
- ) {
806
- return
807
- }
808
- await this.$nextTick()
809
- const isDisplayedAtBottom = await this.generateDropdownPosition()
810
- // If the dropdown menu is going to be displayed at the bottom,
811
- // we need reverify its position after a dom update (nextTick)
812
- await this.$nextTick()
813
- if (isDisplayedAtBottom) this.generateDropdownPosition()
814
- },
815
- async generateDropdownPosition() {
816
- const isDropdownNotCompletelyVisible =
817
- await this.isBottomOfDropdownOutOfViewport()
818
- const dropdownWrapperEl = this.$refs.dropdownWrapperRef.$el
819
- const selectButtonHeight = this.$refs.select.$el.clientHeight
820
- const dropdownHeight = this.$refs.dropdown.$el.clientHeight
821
- const dropdownWrapperRelativeHeight =
822
- dropdownWrapperEl.getBoundingClientRect().top +
823
- window.scrollY +
824
- DROPDOWN_HEIGHT_OFFSET
825
-
826
- const top =
827
- isDropdownNotCompletelyVisible ||
828
- (!isDropdownNotCompletelyVisible &&
829
- this.dropdownMenuPosition === DROPDOWN_MENU_POSITIONS.Bottom)
830
- ? dropdownWrapperRelativeHeight
831
- : dropdownWrapperRelativeHeight -
832
- dropdownHeight -
833
- selectButtonHeight -
834
- DROPDOWN_TOP_OFFSET
835
- const left = this.dropdownPosition.left
836
- ? this.dropdownPosition.left
837
- : dropdownWrapperEl.getBoundingClientRect().left + window.scrollX
838
-
839
- this.dropdownPosition = { left: Math.floor(left), top: Math.floor(top) }
840
-
841
- return isDropdownNotCompletelyVisible
842
- },
843
- async isBottomOfDropdownOutOfViewport() {
844
- if (
845
- !this.$refs.dropdown ||
846
- this.dropdownMenuPosition === DROPDOWN_MENU_POSITIONS.Bottom
847
- ) {
848
- return false
849
- }
850
-
851
- await this.$nextTick()
852
- const rect = this.$refs.dropdown.$el.getBoundingClientRect()
853
- const windowHeight =
854
- window.innerHeight || document.documentElement.clientHeight
855
-
856
- if (windowHeight <= 650) return true
857
-
858
- // using Math.floor because the offsets may contain decimals we are not going to consider here
859
- return Math.floor(rect.top) + Math.floor(rect.height) <= windowHeight
860
- },
861
- observeDropdownHeight() {
862
- if (!this.$refs.dropdown) return
863
- this.dropdownResizeObserver = new ResizeObserver(() => {
864
- this.$nextTick(() => this.getDropdownPosition())
865
- })
866
- this.dropdownResizeObserver.observe(this.$refs.dropdown.$el)
867
- },
868
- handleSetDropdownOffet() {
869
- if (!this.$refs.select) return
870
- this.dropdownPosition.left = Math.floor(
871
- this.$refs.select.$el.getBoundingClientRect().left
872
- )
873
- this.getDropdownWidth()
874
- },
875
- observeSelectWidth() {
876
- if (!this.$refs.select) return
877
- this.selectResizeObserver = new ResizeObserver(() =>
878
- // eslint-disable-next-line vue/valid-next-tick
879
- this.$nextTick(() => this.getDropdownWidth())
880
- )
881
- this.selectResizeObserver.observe(this.$refs.dropdown.$el)
882
- },
883
- async getDropdownWidth() {
884
- if (!this.$refs.select) return
885
- await this.$nextTick()
886
- this.dropdownWidth = `${this.$refs.select.$el.clientWidth}px`
887
- },
888
- onArrowPress(dir) {
889
- let newHoveredElem
890
- const currentHoveredElem = this.$refs.dropdown.$el.querySelector(
891
- `[data-value="${this.hoveredValue}"]`
892
- )
893
- if (currentHoveredElem) {
894
- if (dir > 0) {
895
- newHoveredElem = currentHoveredElem.nextElementSibling
896
- } else {
897
- newHoveredElem = currentHoveredElem.previousElementSibling
898
- }
899
- if (newHoveredElem) {
900
- this.hoveredValue = newHoveredElem.getAttribute('data-value')
901
- const topPos = newHoveredElem.offsetTop
902
- this.$refs.dropdown.$el.scrollTop = topPos
903
- }
904
- }
905
- },
906
- },
907
- }
908
- </script>
1
+ <template>
2
+ <Container
3
+ :no-relative="noRelative"
4
+ :select-width="selectWidth"
5
+ @mouseenter="mouseEnterHandler"
6
+ @mouseleave="mouseLeaveHandler"
7
+ >
8
+ <InputWrapper
9
+ :align-items="alignItems"
10
+ :has-label="!!label && label.length > 0"
11
+ :no-relative="noRelative"
12
+ >
13
+ <LabelWrapper
14
+ v-if="label"
15
+ :data-id="labelDataId"
16
+ :info-text-message="!!infoTextMessage || !!$slots.infoText"
17
+ >
18
+ <InputLabel
19
+ :font-color="
20
+ labelFontColor || colorMode == 'dark' ? 'white' : 'eturnityGrey'
21
+ "
22
+ :font-size="fontSize"
23
+ >{{ label }}
24
+ <OptionalLabel v-if="labelOptional">
25
+ ({{ $gettext('Optional') }})</OptionalLabel
26
+ >
27
+ </InputLabel>
28
+ <InfoText
29
+ v-if="infoTextMessage || !!$slots.infoText"
30
+ :align-arrow="infoTextAlignArrow"
31
+ :max-width="infoTextWidth"
32
+ :size="infoTextSize"
33
+ :text="infoTextMessage"
34
+ :width="infoTextWidth"
35
+ >
36
+ <slot name="infoText"></slot>
37
+ </InfoText>
38
+ </LabelWrapper>
39
+ <SelectButtonWrapper :disabled="disabled">
40
+ <SelectButton
41
+ ref="select"
42
+ :bg-color="
43
+ buttonBgColor || colorMode == 'dark' ? 'transparentBlack1' : 'white'
44
+ "
45
+ class="select-button"
46
+ :data-id="dataId"
47
+ :disabled="disabled"
48
+ :font-color="
49
+ buttonFontColor || colorMode == 'dark' ? 'white' : 'black'
50
+ "
51
+ :font-size="fontSize"
52
+ :has-error="hasError"
53
+ :has-no-padding="isSearchBarVisible || !hasSelectButtonPadding"
54
+ :height="height"
55
+ :no-relative="noRelative"
56
+ :padding-left="paddingLeft"
57
+ :select-height="selectHeight"
58
+ :select-min-height="selectMinHeight"
59
+ :select-width="selectWidth"
60
+ :show-border="showBorder"
61
+ :show-disabled-background="showDisabledBackground"
62
+ :table-padding-left="tablePaddingLeft"
63
+ @click="toggleDropdown"
64
+ @keydown="onKeyDown"
65
+ >
66
+ <DraggableInputHandle
67
+ v-if="isDraggable && !isSearchBarVisible"
68
+ :height="selectHeight"
69
+ />
70
+ <InputText
71
+ v-if="isSearchBarVisible"
72
+ ref="searchInput"
73
+ background-color="transparent"
74
+ :font-color="
75
+ buttonFontColor || colorMode == 'dark' ? 'white' : 'black'
76
+ "
77
+ :font-size="fontSize"
78
+ input-height="34px"
79
+ :input-width="computedWidth"
80
+ :no-border="true"
81
+ tabindex="0"
82
+ :value="textSearch"
83
+ @click.stop
84
+ @input-change="searchChange"
85
+ @keydown.stop="onKeyDown"
86
+ />
87
+ <Selector
88
+ v-else
89
+ :padding-left="paddingLeft"
90
+ :select-width="selectWidth"
91
+ :show-border="showBorder"
92
+ >
93
+ <slot name="selector" :selected-value="selectedValue"></slot>
94
+ </Selector>
95
+ <Caret class="caret_dropdown" @click.stop="toggleCaretDropdown">
96
+ <Icon
97
+ v-if="isDropdownOpen"
98
+ :color="
99
+ caretColor || colorMode == 'dark'
100
+ ? 'white'
101
+ : 'transparentBlack1'
102
+ "
103
+ name="arrow_up"
104
+ size="12px"
105
+ />
106
+ <Icon
107
+ v-else
108
+ :color="
109
+ caretColor || colorMode == 'dark'
110
+ ? 'white'
111
+ : 'transparentBlack1'
112
+ "
113
+ name="arrow_down"
114
+ size="12px"
115
+ />
116
+ </Caret>
117
+ </SelectButton>
118
+ <DropdownWrapper ref="dropdownWrapperRef" :no-relative="noRelative">
119
+ <Teleport to="#portal-target">
120
+ <SelectDropdown
121
+ v-show="isSelectDropdownShown"
122
+ ref="dropdown"
123
+ :bg-color="
124
+ dropdownBgColor || colorMode == 'dark' ? 'black' : 'white'
125
+ "
126
+ :dropdown-position="dropdownPosition"
127
+ :font-color="
128
+ dropdownFontColor || colorMode == 'dark' ? 'white' : 'black'
129
+ "
130
+ :font-size="fontSize"
131
+ :hovered-bg-color="
132
+ colorMode == 'dark' ? '#000000' : dropdownBgColor
133
+ "
134
+ :hovered-index="hoveredIndex"
135
+ :hovered-value="hoveredValue"
136
+ :is-active="isActive"
137
+ :min-width="minWidth"
138
+ :no-relative="noRelative"
139
+ :option-width="getOptionWidth"
140
+ :selected-value="selectedValue"
141
+ @mouseleave="optionLeave"
142
+ @option-hovered="optionHovered"
143
+ @option-selected="optionSelected"
144
+ >
145
+ <slot name="dropdown"></slot>
146
+ </SelectDropdown>
147
+ </Teleport>
148
+ </DropdownWrapper>
149
+ </SelectButtonWrapper>
150
+ </InputWrapper>
151
+ </Container>
152
+ </template>
153
+
154
+ <script>
155
+ //How to use it
156
+ // <Select
157
+ // hoverDropdown="true"
158
+ // selectWidth="100%"
159
+ // minWidth="220px"
160
+ // optionWidth="50%"
161
+ // label="that is a label"
162
+ // alignItems="vertical"
163
+ // label-data-id="test-label-data-id"
164
+ // data-id="test-data-id"
165
+ // :hasSelectButtonPadding="false"
166
+ // >
167
+ // <template #selector="{selectedValue}">
168
+ // value selected: {{selectedValue}}
169
+ // </template>
170
+ // <template #dropdown>
171
+ // <Option value="1">value one</Option>
172
+ // <Option value="2">value two</Option>
173
+ // <Option value="3">value three</Option>
174
+ // <Option value="4">value four</Option>
175
+ // </template>
176
+ // </Select>
177
+
178
+ import { Teleport } from 'vue'
179
+ import styled from 'vue3-styled-components'
180
+ import InfoText from '../../infoText'
181
+ import Icon from '../../icon'
182
+ import InputText from '../inputText'
183
+ import DraggableInputHandle from '../../draggableInputHandle'
184
+ import { debounce } from '../../../utils'
185
+
186
+ const CARET_WIDTH = '30px'
187
+ const BORDER_WIDTH = '1px'
188
+
189
+ const Caret = styled.div`
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ width: ${CARET_WIDTH};
194
+ min-width: ${CARET_WIDTH};
195
+ height: 100%;
196
+ align-items: center;
197
+ cursor: pointer;
198
+ margin-left: auto;
199
+ `
200
+
201
+ const selectorProps = {
202
+ selectWidth: String,
203
+ paddingLeft: String,
204
+ showBorder: Boolean,
205
+ }
206
+ const Selector = styled('div', selectorProps)`
207
+ ${(props) =>
208
+ props.selectWidth === '100%'
209
+ ? 'width: 100%;'
210
+ : `width: calc(${props.selectWidth} -
211
+ (
212
+ ${CARET_WIDTH} +
213
+ ${props.paddingLeft}
214
+ ${props.showBorder ? `+ (${BORDER_WIDTH} * 2)` : ''}
215
+ )
216
+ );
217
+ white-space: nowrap;
218
+ text-overflow: ellipsis;
219
+ overflow: hidden;`}
220
+ `
221
+
222
+ const labelAttrs = { fontSize: String, fontColor: String }
223
+ const InputLabel = styled('div', labelAttrs)`
224
+ color: ${(props) =>
225
+ props.theme.colors[props.fontColor]
226
+ ? props.theme.colors[props.fontColor]
227
+ : props.fontColor};
228
+ font-size: ${(props) => props.fontSize};
229
+ font-weight: 700;
230
+ `
231
+ const OptionalLabel = styled.span`
232
+ font-weight: 300;
233
+ `
234
+ const inputProps = {
235
+ selectWidth: String,
236
+ optionWidth: String,
237
+ noRelative: Boolean,
238
+ }
239
+ const Container = styled('div', inputProps)`
240
+ width: ${(props) => props.selectWidth};
241
+ position: ${(props) => (props.noRelative ? 'static' : 'relative')};
242
+ display: inline-block;
243
+ `
244
+
245
+ const LabelWrapperAttrs = { infoTextMessage: Boolean }
246
+ const LabelWrapper = styled('div', LabelWrapperAttrs)`
247
+ display: inline-grid;
248
+ grid-template-columns: ${(props) =>
249
+ props.infoTextMessage ? 'auto auto' : 'auto'};
250
+ grid-gap: 12px;
251
+ align-items: center;
252
+ justify-content: start;
253
+ `
254
+
255
+ const SelectButtonWrapperAttrs = {
256
+ disabled: Boolean,
257
+ }
258
+ const SelectButtonWrapper = styled('div', SelectButtonWrapperAttrs)`
259
+ ${(props) => (props.disabled ? 'cursor: not-allowed' : 'cursor: pointer')};
260
+ `
261
+
262
+ const selectButtonAttrs = {
263
+ bgColor: String,
264
+ fontColor: String,
265
+ hasError: Boolean,
266
+ disabled: Boolean,
267
+ selectHeight: String,
268
+ selectWidth: String,
269
+ height: String,
270
+ selectMinHeight: String,
271
+ hasNoPadding: Boolean,
272
+ showBorder: Boolean,
273
+ paddingLeft: String,
274
+ noRelative: Boolean,
275
+ tablePaddingLeft: String,
276
+ showDisabledBackground: Boolean,
277
+ }
278
+ const SelectButton = styled('div', selectButtonAttrs)`
279
+ position: ${(props) => (props.noRelative ? 'static' : 'relative')};
280
+ box-sizing: border-box;
281
+ border-radius: 4px;
282
+ max-width: ${(props) => (props.selectWidth ? props.selectWidth : '100%')};
283
+ ${(props) =>
284
+ props.isSearchBarVisible
285
+ ? ''
286
+ : `padding-left: ${
287
+ props.hasNoPadding
288
+ ? '0'
289
+ : props.tablePaddingLeft
290
+ ? props.tablePaddingLeft
291
+ : props.paddingLeft
292
+ }`};
293
+ text-align: left;
294
+ min-height: ${(props) =>
295
+ props.selectHeight
296
+ ? props.selectHeight
297
+ : props.selectMinHeight
298
+ ? props.selectMinHeight
299
+ : props.height
300
+ ? props.height
301
+ : '36px'};
302
+ display: flex;
303
+ align-items: center;
304
+ height: ${(props) => props.selectHeight};
305
+ ${({ showBorder, theme, hasError }) =>
306
+ showBorder &&
307
+ `
308
+ border: ${BORDER_WIDTH} solid ${
309
+ hasError ? theme.colors.red : theme.colors.grey4
310
+ }
311
+ `}
312
+ background-color:${(props) =>
313
+ props.disabled && props.showDisabledBackground
314
+ ? props.theme.colors.grey5
315
+ : props.theme.colors[props.bgColor]
316
+ ? props.theme.colors[props.bgColor]
317
+ : props.bgColor};
318
+ color: ${(props) =>
319
+ props.theme.colors[props.fontColor]
320
+ ? props.theme.colors[props.fontColor]
321
+ : props.fontColor};
322
+ ${(props) => (props.disabled ? 'pointer-events: none' : '')};
323
+ overflow: hidden;
324
+ & > .handle {
325
+ border-right: ${(props) =>
326
+ props.hasError ? props.theme.colors.red : props.theme.colors.grey4}
327
+ 1px solid;
328
+ }
329
+ `
330
+ const selectDropdownAttrs = {
331
+ hoveredBgColor: String,
332
+ bgColor: String,
333
+ fontColor: String,
334
+ optionWidth: String,
335
+ hoveredIndex: Number,
336
+ fontSize: String,
337
+ dropdownPosition: Object,
338
+ hoveredValue: Number | String,
339
+ selectedValue: Number | String,
340
+ noRelative: Boolean,
341
+ minWidth: String,
342
+ }
343
+ const SelectDropdown = styled('div', selectDropdownAttrs)`
344
+ box-sizing: border-box;
345
+ z-index: ${(props) => (props.isActive ? '2' : '99999')};
346
+ position: absolute;
347
+ top: ${(props) =>
348
+ props.noRelative ? 'auto' : props.dropdownPosition?.top + 'px'};
349
+ left: ${(props) => props.dropdownPosition?.left}px;
350
+ border: ${BORDER_WIDTH} solid ${(props) => props.theme.colors.grey4};
351
+ border-radius: 4px;
352
+ display: flex;
353
+ flex-direction: column;
354
+ align-items: flex-start;
355
+ padding: 0px;
356
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
357
+ width: ${(props) => (props.optionWidth ? props.optionWidth : '100%')};
358
+ min-width: ${(props) =>
359
+ props.minWidth
360
+ ? props.minWidth
361
+ : props.optionWidth
362
+ ? props.optionWidth
363
+ : '100%'};
364
+ background-color: ${(props) =>
365
+ props.theme.colors[props.bgColor]
366
+ ? props.theme.colors[props.bgColor]
367
+ : props.bgColor};
368
+ color: ${(props) =>
369
+ props.theme.colors[props.fontColor]
370
+ ? props.theme.colors[props.fontColor]
371
+ : props.fontColor};
372
+ max-height: 300px;
373
+ overflow-y: auto;
374
+ & > div[data-value='${(props) => props.hoveredValue}'] {
375
+ background-color: ${(props) =>
376
+ props.theme.colors[props.hoveredBgColor]
377
+ ? props.theme.colors[props.hoveredBgColor]
378
+ : props.hoveredBgColor};
379
+ }
380
+ font-size: ${(props) => props.fontSize};
381
+ `
382
+ SelectDropdown.emits = ['option-hovered', 'option-selected']
383
+ const DropdownAttrs = { noRelative: Boolean }
384
+ const DropdownWrapper = styled('div', DropdownAttrs)`
385
+ position: ${(props) => (props.noRelative ? 'static' : 'relative')};
386
+ `
387
+ const inputAttrs = {
388
+ alignItems: String,
389
+ hasLabel: Boolean,
390
+ noRelative: Boolean,
391
+ }
392
+ const InputWrapper = styled('div', inputAttrs)`
393
+ position: ${(props) => (props.noRelative ? 'static' : 'relative')};
394
+ display: grid;
395
+ width: 100%;
396
+ min-width: ${(props) => (props.minWidth ? props.minWidth : '150px')};
397
+ align-items: center;
398
+ gap: 8px;
399
+ grid-template-columns: ${(props) =>
400
+ props.alignItems === 'vertical' || !props.hasLabel ? '1fr' : 'auto 1fr'};
401
+ `
402
+
403
+ const DROPDOWN_HEIGHT_OFFSET = 4
404
+ const DROPDOWN_TOP_OFFSET = 21
405
+ const MIN_OPTION_LENGTH = 5
406
+
407
+ const DROPDOWN_MENU_POSITIONS = {
408
+ Automatic: 'automatic',
409
+ Bottom: 'bottom',
410
+ }
411
+
412
+ export default {
413
+ name: 'RCselect',
414
+
415
+ components: {
416
+ SelectButton,
417
+ SelectButtonWrapper,
418
+ SelectDropdown,
419
+ Container,
420
+ InputLabel,
421
+ LabelWrapper,
422
+ OptionalLabel,
423
+ InfoText,
424
+ InputWrapper,
425
+ DropdownWrapper,
426
+ Icon,
427
+ Caret,
428
+ Selector,
429
+ InputText,
430
+ Teleport,
431
+ DraggableInputHandle,
432
+ },
433
+
434
+ props: {
435
+ value: {
436
+ required: false,
437
+ default: null,
438
+ },
439
+ fontSize: {
440
+ required: false,
441
+ default: '13px',
442
+ },
443
+ noRelative: {
444
+ required: false,
445
+ default: false,
446
+ },
447
+ label: {
448
+ required: false,
449
+ },
450
+ labelOptional: {
451
+ required: false,
452
+ default: false,
453
+ },
454
+ labelDataId: {
455
+ required: false,
456
+ default: '',
457
+ },
458
+ infoTextMessage: {
459
+ required: false,
460
+ },
461
+ selectWidth: {
462
+ type: String,
463
+ required: false,
464
+ default: '100%',
465
+ },
466
+ minWidth: {
467
+ required: false,
468
+ },
469
+ maxWidth: {
470
+ required: false,
471
+ },
472
+ selectHeight: {
473
+ type: String,
474
+ required: false,
475
+ default: null,
476
+ },
477
+ height: {
478
+ required: false,
479
+ default: null,
480
+ },
481
+ selectMinHeight: {
482
+ required: false,
483
+ default: '36px',
484
+ },
485
+ optionWidth: {
486
+ required: false,
487
+ default: null,
488
+ },
489
+ hoverDropdown: {
490
+ required: false,
491
+ default: false,
492
+ },
493
+ dropdownAutoClose: {
494
+ required: false,
495
+ default: false,
496
+ },
497
+ alignItems: {
498
+ required: false,
499
+ default: 'horizontal',
500
+ },
501
+ buttonBgColor: {
502
+ required: false,
503
+ },
504
+ buttonFontColor: {
505
+ required: false,
506
+ },
507
+ dropdownBgColor: {
508
+ required: false,
509
+ default: 'grey5',
510
+ },
511
+ dropdownFontColor: {
512
+ required: false,
513
+ },
514
+ dropDownArrowVisible: {
515
+ required: false,
516
+ default: true,
517
+ },
518
+ caretColor: {
519
+ required: false,
520
+ },
521
+ labelFontColor: {
522
+ required: false,
523
+ },
524
+ colorMode: {
525
+ required: false,
526
+ default: 'light',
527
+ },
528
+ isSearchable: {
529
+ required: false,
530
+ default: true,
531
+ },
532
+ hasError: {
533
+ required: false,
534
+ default: false,
535
+ },
536
+ disabled: {
537
+ required: false,
538
+ default: false,
539
+ },
540
+ isAutoSearch: {
541
+ required: false,
542
+ default: true,
543
+ },
544
+ showBorder: {
545
+ required: false,
546
+ default: true,
547
+ },
548
+ infoTextSize: {
549
+ required: false,
550
+ default: '14px',
551
+ },
552
+ dataId: {
553
+ type: String,
554
+ default: '',
555
+ },
556
+ hasSelectButtonPadding: {
557
+ required: false,
558
+ type: Boolean,
559
+ default: true,
560
+ },
561
+ isDraggable: {
562
+ type: Boolean,
563
+ default: false,
564
+ },
565
+ leftPadding: {
566
+ type: String,
567
+ default: '15px',
568
+ },
569
+ tablePaddingLeft: {
570
+ required: false,
571
+ },
572
+ showDisabledBackground: {
573
+ required: false,
574
+ default: true,
575
+ },
576
+ minOptionLength: {
577
+ type: Number,
578
+ default: MIN_OPTION_LENGTH,
579
+ },
580
+ dropdownMenuPosition: {
581
+ type: String,
582
+ default: DROPDOWN_MENU_POSITIONS.Automatic, // options: ['automatic', bottom]
583
+ },
584
+ infoTextWidth: {
585
+ type: String,
586
+ required: false,
587
+ },
588
+ infoTextAlignArrow: {
589
+ type: String,
590
+ required: false,
591
+ },
592
+ },
593
+
594
+ data() {
595
+ return {
596
+ selectedValue: null,
597
+ paddingLeft: this.isDraggable ? '30px' : this.leftPadding,
598
+ isDropdownOpen: false,
599
+ isActive: false,
600
+ textSearch: '',
601
+ hoveredIndex: 0,
602
+ isClickOutsideActive: false,
603
+ dropdownPosition: {
604
+ left: null,
605
+ top: null,
606
+ },
607
+ dropdownWidth: null,
608
+ hoveredValue: null,
609
+ }
610
+ },
611
+ computed: {
612
+ optionLength() {
613
+ if (this.isDropdownOpen) {
614
+ return this.$refs.dropdown.$el.childElementCount > 1
615
+ ? this.$refs.dropdown.$el.childElementCount
616
+ : this.$refs.dropdown.$el.children[0].childElementCount
617
+ }
618
+
619
+ return 0
620
+ },
621
+ isSearchBarVisible() {
622
+ return (
623
+ this.isSearchable &&
624
+ this.optionLength >= this.minOptionLength &&
625
+ this.isDropdownOpen
626
+ )
627
+ },
628
+ computedWidth() {
629
+ function removePX(item) {
630
+ return Number(item.replace('px', ''))
631
+ }
632
+
633
+ return this.selectWidth === '100%'
634
+ ? '100%'
635
+ : removePX(this.selectWidth) - removePX(CARET_WIDTH) + 'px'
636
+ },
637
+ getOptionWidth() {
638
+ if (this.optionWidth) return this.optionWidth
639
+
640
+ return this.dropdownWidth
641
+ },
642
+ isSelectDropdownShown() {
643
+ return (
644
+ this.isDropdownOpen &&
645
+ this.dropdownPosition.left !== null &&
646
+ (!this.isSearchable || this.isSearchable)
647
+ )
648
+ },
649
+ isMobileDevice() {
650
+ const userAgent =
651
+ navigator.userAgent || navigator.vendor || window.opera
652
+ const touchCapable =
653
+ 'ontouchstart' in window ||
654
+ navigator.maxTouchPoints > 0 ||
655
+ navigator.msMaxTouchPoints > 0
656
+
657
+ return (
658
+ /Android/i.test(userAgent) ||
659
+ /iPad|iPhone|iPod/.test(userAgent) ||
660
+ (/Macintosh/.test(userAgent) && touchCapable) ||
661
+ /windows phone/i.test(userAgent)
662
+ )
663
+ },
664
+ },
665
+ watch: {
666
+ value(val) {
667
+ this.selectedValue = val
668
+ },
669
+ async isDropdownOpen(val) {
670
+ if (val) {
671
+ this.$emit('on-dropdown-open')
672
+ setTimeout(() => {
673
+ this.isClickOutsideActive = true
674
+ }, 10)
675
+ await this.$nextTick()
676
+ this.handleSetDropdownOffet()
677
+ } else {
678
+ this.dropdownPosition.left = null
679
+ setTimeout(() => {
680
+ this.isClickOutsideActive = false
681
+ }, 10)
682
+ }
683
+ if (val && this.isSearchable) {
684
+ this.$nextTick(() => {
685
+ if (this.$refs.searchInput && !this.isMobileDevice) {
686
+ this.$refs.searchInput.$el.querySelector('input').focus()
687
+ }
688
+ })
689
+ }
690
+ },
691
+ },
692
+ mounted() {
693
+ this.observeDropdownHeight()
694
+ this.observeSelectWidth()
695
+ window.addEventListener('resize', this.handleSetDropdownOffet)
696
+ },
697
+ beforeMount() {
698
+ this.selectedValue = this.value
699
+ document.addEventListener('click', this.clickOutside)
700
+ this.getDropdownPosition()
701
+ window.removeEventListener('resize', this.handleSetDropdownOffet)
702
+ if (this.dropdownResizeObserver) this.dropdownResizeObserver.disconnect()
703
+ if (this.selectResizeObserver) this.selectResizeObserver.disconnect()
704
+ },
705
+ unmounted() {
706
+ document.removeEventListener('click', this.clickOutside)
707
+ },
708
+ methods: {
709
+ focus() {
710
+ this.isActive = true
711
+ },
712
+ blur(e) {
713
+ this.isActive = false
714
+ this.$emit('blur', e)
715
+ },
716
+ toggleDropdown() {
717
+ this.isDropdownOpen = !this.isDropdownOpen
718
+ },
719
+ toggleCaretDropdown() {
720
+ this.isDropdownOpen = !this.isDropdownOpen
721
+ },
722
+ closeDropdown() {
723
+ this.blur()
724
+ this.clearSearch()
725
+ this.isDropdownOpen = false
726
+ },
727
+ clearSearch() {
728
+ this.textSearch = ''
729
+ this.searchChange('')
730
+ },
731
+ optionSelected(e) {
732
+ this.selectedValue = e
733
+ this.closeDropdown()
734
+ this.blur()
735
+ this.$emit('input-change', e)
736
+ },
737
+ optionHovered: debounce(function (e) {
738
+ this.hoveredValue = e
739
+ }, 300),
740
+ mouseEnterHandler() {
741
+ if (this.hoverDropdown) {
742
+ this.focus()
743
+ this.isDropdownOpen = true
744
+ }
745
+ },
746
+ mouseLeaveHandler() {
747
+ if (this.hoverDropdown) {
748
+ this.blur()
749
+ }
750
+ },
751
+ optionLeave() {
752
+ if (this.dropdownAutoClose) {
753
+ this.isDropdownOpen = false
754
+ }
755
+ },
756
+ searchChange(value) {
757
+ this.textSearch = value
758
+ this.$emit('search-change', value)
759
+ const dropdownChildren = [...this.$refs.dropdown.$el.children]
760
+ dropdownChildren.forEach((el) => {
761
+ if (!el.textContent.toLowerCase().includes(value.toLowerCase())) {
762
+ el.style.display = 'none'
763
+
764
+ return
765
+ }
766
+
767
+ el.style.display = 'inherit'
768
+ })
769
+ },
770
+ clickOutside(event) {
771
+ const dropdownRef = this.$refs.dropdown
772
+ // we need to prevent closing on selecting an option, because in the case of
773
+ // a disabled option, we don't want to close the dropdown
774
+ if (!this.isClickOutsideActive) return
775
+ if (
776
+ this.$refs.select.$el == event.target ||
777
+ this.$refs.select.$el.contains(event.target) ||
778
+ event.target.id === 'more-button' ||
779
+ event.target.parentNode === dropdownRef.$el
780
+ ) {
781
+ return
782
+ } else {
783
+ this.closeDropdown()
784
+ }
785
+ },
786
+ onKeyDown(e) {
787
+ if (e.key == 'ArrowDown') {
788
+ this.onArrowPress(1)
789
+ } else if (e.key == 'ArrowUp') {
790
+ this.onArrowPress(-1)
791
+ } else if (e.key == 'Enter') {
792
+ const optionHoveredComponent = [...this.$refs.dropdown.$el.children][
793
+ (this.hoveredIndex - 1 + this.optionLength) % this.optionLength
794
+ ]
795
+ this.optionSelected(optionHoveredComponent.$el.dataset.value)
796
+ }
797
+ },
798
+ // If some part of the dropdown menu is outside viewport of the bottom of the screen,
799
+ // we need to offset it and display it at the top of the select dropdown instead
800
+ async getDropdownPosition() {
801
+ if (
802
+ !this.$refs.dropdownWrapperRef ||
803
+ !this.$refs.select ||
804
+ !this.$refs.dropdown
805
+ ) {
806
+ return
807
+ }
808
+ await this.$nextTick()
809
+ const isDisplayedAtBottom = await this.generateDropdownPosition()
810
+ // If the dropdown menu is going to be displayed at the bottom,
811
+ // we need reverify its position after a dom update (nextTick)
812
+ await this.$nextTick()
813
+ if (isDisplayedAtBottom) this.generateDropdownPosition()
814
+ },
815
+ async generateDropdownPosition() {
816
+ const isDropdownNotCompletelyVisible =
817
+ await this.isBottomOfDropdownOutOfViewport()
818
+ const dropdownWrapperEl = this.$refs.dropdownWrapperRef.$el
819
+ const selectButtonHeight = this.$refs.select.$el.clientHeight
820
+ const dropdownHeight = this.$refs.dropdown.$el.clientHeight
821
+ const dropdownWrapperRelativeHeight =
822
+ dropdownWrapperEl.getBoundingClientRect().top +
823
+ window.scrollY +
824
+ DROPDOWN_HEIGHT_OFFSET
825
+
826
+ const top =
827
+ isDropdownNotCompletelyVisible ||
828
+ (!isDropdownNotCompletelyVisible &&
829
+ this.dropdownMenuPosition === DROPDOWN_MENU_POSITIONS.Bottom)
830
+ ? dropdownWrapperRelativeHeight
831
+ : dropdownWrapperRelativeHeight -
832
+ dropdownHeight -
833
+ selectButtonHeight -
834
+ DROPDOWN_TOP_OFFSET
835
+ const left = this.dropdownPosition.left
836
+ ? this.dropdownPosition.left
837
+ : dropdownWrapperEl.getBoundingClientRect().left + window.scrollX
838
+
839
+ this.dropdownPosition = { left: Math.floor(left), top: Math.floor(top) }
840
+
841
+ return isDropdownNotCompletelyVisible
842
+ },
843
+ async isBottomOfDropdownOutOfViewport() {
844
+ if (
845
+ !this.$refs.dropdown ||
846
+ this.dropdownMenuPosition === DROPDOWN_MENU_POSITIONS.Bottom
847
+ ) {
848
+ return false
849
+ }
850
+
851
+ await this.$nextTick()
852
+ const rect = this.$refs.dropdown.$el.getBoundingClientRect()
853
+ const windowHeight =
854
+ window.innerHeight || document.documentElement.clientHeight
855
+
856
+ if (windowHeight <= 650) return true
857
+
858
+ // using Math.floor because the offsets may contain decimals we are not going to consider here
859
+ return Math.floor(rect.top) + Math.floor(rect.height) <= windowHeight
860
+ },
861
+ observeDropdownHeight() {
862
+ if (!this.$refs.dropdown) return
863
+ this.dropdownResizeObserver = new ResizeObserver(() => {
864
+ this.$nextTick(() => this.getDropdownPosition())
865
+ })
866
+ this.dropdownResizeObserver.observe(this.$refs.dropdown.$el)
867
+ },
868
+ handleSetDropdownOffet() {
869
+ if (!this.$refs.select) return
870
+ this.dropdownPosition.left = Math.floor(
871
+ this.$refs.select.$el.getBoundingClientRect().left
872
+ )
873
+ this.getDropdownWidth()
874
+ },
875
+ observeSelectWidth() {
876
+ if (!this.$refs.select) return
877
+ this.selectResizeObserver = new ResizeObserver(() =>
878
+ // eslint-disable-next-line vue/valid-next-tick
879
+ this.$nextTick(() => this.getDropdownWidth())
880
+ )
881
+ this.selectResizeObserver.observe(this.$refs.dropdown.$el)
882
+ },
883
+ async getDropdownWidth() {
884
+ if (!this.$refs.select) return
885
+ await this.$nextTick()
886
+ this.dropdownWidth = `${this.$refs.select.$el.clientWidth}px`
887
+ },
888
+ onArrowPress(dir) {
889
+ let newHoveredElem
890
+ const currentHoveredElem = this.$refs.dropdown.$el.querySelector(
891
+ `[data-value="${this.hoveredValue}"]`
892
+ )
893
+ if (currentHoveredElem) {
894
+ if (dir > 0) {
895
+ newHoveredElem = currentHoveredElem.nextElementSibling
896
+ } else {
897
+ newHoveredElem = currentHoveredElem.previousElementSibling
898
+ }
899
+ if (newHoveredElem) {
900
+ this.hoveredValue = newHoveredElem.getAttribute('data-value')
901
+ const topPos = newHoveredElem.offsetTop
902
+ this.$refs.dropdown.$el.scrollTop = topPos
903
+ }
904
+ }
905
+ },
906
+ },
907
+ }
908
+ </script>