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