classcard-ui 0.2.1506 → 0.2.1508-beta.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 (216) hide show
  1. package/README.md +24 -24
  2. package/dist/classcard-ui.common.js +176 -138
  3. package/dist/classcard-ui.common.js.map +1 -1
  4. package/dist/classcard-ui.css +1 -1
  5. package/dist/classcard-ui.umd.js +176 -138
  6. package/dist/classcard-ui.umd.js.map +1 -1
  7. package/dist/classcard-ui.umd.min.js +2 -2
  8. package/dist/classcard-ui.umd.min.js.map +1 -1
  9. package/package.json +84 -84
  10. package/src/App.vue +16 -16
  11. package/src/colorConfig.js +52 -52
  12. package/src/components/CAlertModal/CAlertModal.vue +181 -181
  13. package/src/components/CAlertModal/index.js +3 -3
  14. package/src/components/CAlerts/CAlerts.vue +114 -114
  15. package/src/components/CAlerts/index.js +2 -2
  16. package/src/components/CAnchorTabs/CAnchorTabs.vue +100 -100
  17. package/src/components/CAnchorTabs/index.js +2 -2
  18. package/src/components/CAnchorTag/CAnchorTag.vue +84 -84
  19. package/src/components/CAnchorTag/index.js +2 -2
  20. package/src/components/CAvatar/CAvatar.vue +231 -231
  21. package/src/components/CAvatar/index.js +2 -2
  22. package/src/components/CAvatarGroup/CAvatarGroup.vue +213 -213
  23. package/src/components/CAvatarGroup/index.js +2 -2
  24. package/src/components/CBasicTable/CBasicTable.vue +184 -184
  25. package/src/components/CBasicTable/index.js +2 -2
  26. package/src/components/CBreadcrumbs/CBreadcrumbs.vue +38 -38
  27. package/src/components/CBreadcrumbs/index.js +2 -2
  28. package/src/components/CButton/CButton.vue +239 -239
  29. package/src/components/CButton/index.js +2 -2
  30. package/src/components/CButtonGroup/CButtonGroup.vue +155 -155
  31. package/src/components/CButtonGroup/index.js +2 -2
  32. package/src/components/CButtonIcon/CButtonIcon.vue +166 -166
  33. package/src/components/CButtonIcon/index.js +2 -2
  34. package/src/components/CButtonLink/CButtonLink.vue +43 -43
  35. package/src/components/CButtonLink/index.js +2 -2
  36. package/src/components/CButtonSelect/CButtonSelect.vue +186 -186
  37. package/src/components/CButtonSelect/index.js +2 -2
  38. package/src/components/CButtonSelectBorder/CButtonSelectBorder.vue +265 -265
  39. package/src/components/CButtonSelectBorder/index.js +3 -3
  40. package/src/components/CButtonWithDropdown/CButtonWithDropdown.vue +208 -208
  41. package/src/components/CButtonWithDropdown/index.js +2 -2
  42. package/src/components/CCalendar/CCalendar.vue +443 -443
  43. package/src/components/CCalendar/index.js +3 -3
  44. package/src/components/CCard/CCard.vue +53 -53
  45. package/src/components/CCard/index.js +2 -2
  46. package/src/components/CCheckbox/CCheckbox.vue +200 -200
  47. package/src/components/CCheckbox/index.js +2 -2
  48. package/src/components/CCircularButton/CCircularButton.vue +57 -57
  49. package/src/components/CCircularButton/index.js +2 -2
  50. package/src/components/CCollapsibleSection/CCollapsibleSection.vue +121 -121
  51. package/src/components/CCollapsibleSection/index.js +2 -2
  52. package/src/components/CColorDots/CColorDots.vue +52 -52
  53. package/src/components/CColorDots/index.js +3 -3
  54. package/src/components/CConfirmActionModal/CConfirmActionModal.vue +290 -290
  55. package/src/components/CConfirmActionModal/index.js +3 -3
  56. package/src/components/CDatepicker/CDatepicker.vue +235 -235
  57. package/src/components/CDatepicker/index.js +2 -2
  58. package/src/components/CDualSelect/CDualSelect.vue +193 -193
  59. package/src/components/CDualSelect/index.js +2 -2
  60. package/src/components/CEditor/CEditor.vue +137 -137
  61. package/src/components/CEditor/index.js +6 -6
  62. package/src/components/CEditor/quill-font-setup.js +133 -133
  63. package/src/components/CFormSectionHeading/CFormSectionHeading.vue +76 -76
  64. package/src/components/CFormSectionHeading/index.js +2 -2
  65. package/src/components/CGroupedFilterDropdown/CGroupedFilterDropdown.vue +331 -331
  66. package/src/components/CGroupedFilterDropdown/index.js +2 -2
  67. package/src/components/CGroupedSelect/CGroupedSelect.vue +393 -393
  68. package/src/components/CGroupedSelect/index.js +3 -3
  69. package/src/components/CIcon/CIcon.vue +112 -112
  70. package/src/components/CIcon/index.js +2 -2
  71. package/src/components/CIconDropdown/CIconDropdown.vue +206 -206
  72. package/src/components/CIconDropdown/index.js +2 -2
  73. package/src/components/CIconSelect/CIconSelect.vue +182 -182
  74. package/src/components/CIconSelect/index.js +3 -3
  75. package/src/components/CInput/CInput.vue +227 -227
  76. package/src/components/CInput/index.js +2 -2
  77. package/src/components/CInputAddon/CInputAddon.vue +297 -297
  78. package/src/components/CInputAddon/index.js +2 -2
  79. package/src/components/CInputEmail/CInputEmail.vue +107 -107
  80. package/src/components/CInputEmail/index.js +2 -2
  81. package/src/components/CInsetTabs/CInsetTabs.vue +222 -222
  82. package/src/components/CInsetTabs/index.js +3 -3
  83. package/src/components/CLinearProgressBar/CLinearProgressBar.vue +35 -35
  84. package/src/components/CLinearProgressBar/index.js +2 -2
  85. package/src/components/CModalHeading/CModalHeading.vue +22 -22
  86. package/src/components/CModalHeading/index.js +2 -2
  87. package/src/components/CModuleHelpLinks/CModuleHelpLinks.vue +88 -88
  88. package/src/components/CModuleHelpLinks/index.js +3 -3
  89. package/src/components/CMultiselect/CMultiselect.vue +1314 -1314
  90. package/src/components/CMultiselect/index.js +2 -2
  91. package/src/components/CMultiselectr/CMultiselectr.vue +44 -44
  92. package/src/components/CMultiselectr/index.js +2 -2
  93. package/src/components/CPageHeading/CPageHeading.vue +83 -83
  94. package/src/components/CPageHeading/index.js +2 -2
  95. package/src/components/CPagination/CPagination.vue +239 -239
  96. package/src/components/CPagination/index.js +2 -2
  97. package/src/components/CPhoneNumber/CPhoneNumber.vue +213 -213
  98. package/src/components/CPhoneNumber/index.js +2 -2
  99. package/src/components/CProgress/CProgress.vue +95 -95
  100. package/src/components/CProgress/index.js +2 -2
  101. package/src/components/CRadio/CRadio.vue +198 -198
  102. package/src/components/CRadio/index.js +2 -2
  103. package/src/components/CRadioGroup/CRadioGroup.vue +96 -96
  104. package/src/components/CRadioGroup/index.js +2 -2
  105. package/src/components/CRangeSlider/CRangeSlider.vue +55 -55
  106. package/src/components/CRangeSlider/index.js +2 -2
  107. package/src/components/CReorderableStackedList/CReorderableStackedList.vue +94 -94
  108. package/src/components/CReorderableStackedList/index.js +2 -2
  109. package/src/components/CSelect/CSelect.vue +1225 -1225
  110. package/src/components/CSelect/index.js +2 -2
  111. package/src/components/CSmallTimeline/CSmallTimeline.vue +40 -40
  112. package/src/components/CSmallTimeline/index.js +2 -2
  113. package/src/components/CStackedList/CStackedList.vue +162 -162
  114. package/src/components/CStackedList/index.js +2 -2
  115. package/src/components/CStats/CStats.vue +157 -157
  116. package/src/components/CStats/index.js +2 -2
  117. package/src/components/CSwitch/CSwitch.vue +200 -200
  118. package/src/components/CSwitch/index.js +2 -2
  119. package/src/components/CTabLazy/CTabLazy.vue +83 -83
  120. package/src/components/CTabLazy/index.js +2 -2
  121. package/src/components/CTable/CTable.vue +1114 -1114
  122. package/src/components/CTable/index.js +2 -2
  123. package/src/components/CTabs/CTabs.vue +250 -250
  124. package/src/components/CTabs/index.js +2 -2
  125. package/src/components/CTag/CTag.vue +109 -109
  126. package/src/components/CTag/index.js +2 -2
  127. package/src/components/CTextarea/CTextarea.vue +118 -118
  128. package/src/components/CTextarea/index.js +2 -2
  129. package/src/components/CTimeline/CTimeline.vue +237 -237
  130. package/src/components/CTimeline/index.js +2 -2
  131. package/src/components/CTokenEditor/CTokenEditor.vue +804 -804
  132. package/src/components/CTokenEditor/index.js +3 -3
  133. package/src/components/CToolTip/CToolTip.vue +108 -108
  134. package/src/components/CToolTip/index.js +3 -3
  135. package/src/components/CUpload/CUpload.vue +332 -332
  136. package/src/components/CUpload/index.js +2 -2
  137. package/src/components/NumberAnimator.vue +112 -112
  138. package/src/components/index.js +60 -60
  139. package/src/helper.js +8 -8
  140. package/src/icons.js +855 -850
  141. package/src/main.js +22 -22
  142. package/src/stories/CAlertModal.stories.js +30 -30
  143. package/src/stories/CAlerts.stories.js +39 -39
  144. package/src/stories/CAnchorTabs.stories.js +29 -29
  145. package/src/stories/CAnchorTag.stories.js +38 -38
  146. package/src/stories/CAvatar.stories.js +38 -38
  147. package/src/stories/CAvatarGroup.stories.js +136 -136
  148. package/src/stories/CBasicTable.stories.js +316 -316
  149. package/src/stories/CBreadcrumbs.stories.js +24 -24
  150. package/src/stories/CButton.stories.js +49 -49
  151. package/src/stories/CButtonGroup.stories.js +43 -43
  152. package/src/stories/CButtonIcon.stories.js +27 -27
  153. package/src/stories/CButtonLink.stories.js +24 -24
  154. package/src/stories/CButtonSelect.stories.js +44 -44
  155. package/src/stories/CButtonSelectBorder.stories.js +56 -56
  156. package/src/stories/CButtonWithDropdown.stories.js +41 -41
  157. package/src/stories/CCalendar.stories.js +16 -16
  158. package/src/stories/CCard.stories.js +30 -30
  159. package/src/stories/CCheckbox.stories.js +38 -38
  160. package/src/stories/CCircularButton.stories.js +29 -29
  161. package/src/stories/CCollapsibleSection.stories.js +29 -29
  162. package/src/stories/CColorDots.stories.js +37 -37
  163. package/src/stories/CConfirmActionModal.stories.js +67 -67
  164. package/src/stories/CDatepicker.stories.js +31 -31
  165. package/src/stories/CDualSelect.stories.js +29 -29
  166. package/src/stories/CEditor.stories.js +36 -36
  167. package/src/stories/CFormSectionHeading.stories.js +37 -37
  168. package/src/stories/CGroupedFilterDropdown.stories.js +131 -131
  169. package/src/stories/CGroupedSelect.stories.js +103 -103
  170. package/src/stories/CIcon.stories.js +31 -31
  171. package/src/stories/CIconDropdown.stories.js +52 -52
  172. package/src/stories/CIconSelect.stories.js +45 -45
  173. package/src/stories/CInput.stories.js +50 -50
  174. package/src/stories/CInputAddon.stories.js +37 -37
  175. package/src/stories/CInputEmail.stories.js +27 -27
  176. package/src/stories/CInsetTabs.stories.js +50 -50
  177. package/src/stories/CLinearProgressBar.stories.js +22 -22
  178. package/src/stories/CModalHeading.stories.js +25 -25
  179. package/src/stories/CModuleHelpLinks.stories.js +25 -25
  180. package/src/stories/CMultiselect.stories.js +146 -146
  181. package/src/stories/CMultiselectr.stories.js +23 -23
  182. package/src/stories/CPageHeading.stories.js +32 -32
  183. package/src/stories/CPagination.stories.js +30 -30
  184. package/src/stories/CPhoneNumber.stories.js +37 -37
  185. package/src/stories/CProgress.stories.js +23 -23
  186. package/src/stories/CRadio.stories.js +44 -44
  187. package/src/stories/CRadioGroup.stories.js +51 -51
  188. package/src/stories/CRangeSlider.stories.js +23 -23
  189. package/src/stories/CReorderableStackedList.stories.js +23 -23
  190. package/src/stories/CSelect.stories.js +157 -157
  191. package/src/stories/CSmallTimeline.stories.js +26 -26
  192. package/src/stories/CStackedList.stories.js +37 -37
  193. package/src/stories/CStats.stories.js +53 -53
  194. package/src/stories/CSwitch.stories.js +28 -28
  195. package/src/stories/CTabLazy.stories.js +42 -42
  196. package/src/stories/CTable.stories.js +203 -203
  197. package/src/stories/CTabs.stories.js +36 -36
  198. package/src/stories/CTag.stories.js +37 -37
  199. package/src/stories/CTextarea.stories.js +32 -32
  200. package/src/stories/CTimeline.stories.js +26 -26
  201. package/src/stories/CTokenEditor.stories.js +136 -136
  202. package/src/stories/CToolTip.stories.js +27 -27
  203. package/src/stories/CUpload.stories.js +36 -36
  204. package/src/stories/Introduction.stories.mdx +207 -207
  205. package/src/stories/Page.vue +88 -88
  206. package/src/stories/assets/code-brackets.svg +0 -0
  207. package/src/stories/assets/colors.svg +0 -0
  208. package/src/stories/assets/comments.svg +0 -0
  209. package/src/stories/assets/direction.svg +0 -0
  210. package/src/stories/assets/flow.svg +0 -0
  211. package/src/stories/assets/plugin.svg +0 -0
  212. package/src/stories/assets/repo.svg +0 -0
  213. package/src/stories/assets/stackalt.svg +0 -0
  214. package/src/stories/header.css +26 -26
  215. package/src/stories/page.css +69 -69
  216. package/src/stories/utils.css +32 -32
@@ -1,1225 +1,1225 @@
1
- <template>
2
- <div>
3
- <div
4
- :class="`flex w-full items-center ${
5
- label ? 'justify-between' : 'justify-end'
6
- }`"
7
- >
8
- <div class="flex items-center" v-if="label">
9
- <!-- label of select field -->
10
- <label class="block text-sm font-medium text-gray-900">
11
- {{ label }}
12
- </label>
13
- <!-- asterisk sign to render if field is required -->
14
- <p v-if="isRequired" class="ml-1 text-red-600">*</p>
15
- </div>
16
- <button
17
- v-if="actionBtn"
18
- :id="getActionIDFn(actionBtn)"
19
- class="block text-sm font-medium text-indigo-800 hover:underline"
20
- @click="actionBtnEvent($event)"
21
- >
22
- {{ actionBtn }}
23
- </button>
24
- <span v-if="hint" class="text-sm text-gray-500">{{ hint }}</span>
25
- </div>
26
- <div :class="['relative', inputContainerClass]" ref="inputContainer">
27
- <div :class="label || actionBtn ? 'mt-1' : ''">
28
- <input
29
- ref="c-select-input"
30
- type="text"
31
- v-model="selectSearch"
32
- @click="handleInputClick"
33
- @focus="handleInputFocus"
34
- @input="search()"
35
- @keydown="handleKeydown"
36
- @keydown.enter.prevent="handleKeydown"
37
- aria-haspopup="listbox"
38
- aria-expanded="true"
39
- aria-labelledby="listbox-label"
40
- class="h-9 w-full cursor-pointer border py-2 pr-10 text-left text-sm"
41
- :class="[
42
- classes,
43
- !isValidate && 'border-red-300',
44
- inputClasses,
45
- customBorderRadius,
46
- toggleDropdown ? 'pl-10' : 'pl-3',
47
- ]"
48
- :disabled="isDisabled"
49
- autocomplete="off"
50
- :name="searchInputName"
51
- data-lpignore="true"
52
- data-1p-ignore
53
- data-form-type="other"
54
- @blur="handleElementBlur"
55
- :id="id"
56
- :style="inputStyle"
57
- />
58
- <button
59
- class="absolute top-2.5 right-10 z-100 cursor-pointer"
60
- :id="id + '_close_button'"
61
- v-if="showCloseButton && (value == null || value.length == 0)"
62
- @click.stop="handleCrossClick"
63
- >
64
- <c-icon
65
- name="close"
66
- type="outline-v2"
67
- class="h-4 w-4 text-gray-400"
68
- />
69
- </button>
70
- <div
71
- v-if="!toggleDropdown"
72
- class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
73
- >
74
- <div
75
- :class="[
76
- 'flex min-w-0 flex-1 items-center gap-2',
77
- selectedOptionStyles,
78
- ]"
79
- >
80
- <div
81
- class="flex shrink-0"
82
- v-if="!addCheckBox && showImage && !selectSearch && value"
83
- >
84
- <c-avatar
85
- v-if="value.photo"
86
- size="extraextraextrasmall"
87
- :image="value.photo"
88
- :rounded="true"
89
- ></c-avatar>
90
- <c-avatar
91
- v-else
92
- size="extraextraextrasmall"
93
- :nameInitials="value.initials"
94
- :rounded="true"
95
- :isDynamicallyColored="coloredAvatars"
96
- ></c-avatar>
97
- </div>
98
- <div
99
- class="flex shrink-0"
100
- v-if="
101
- addCheckBox &&
102
- showImage &&
103
- !selectSearch &&
104
- selectedValuesArray &&
105
- selectedValuesArray.length &&
106
- selectedValuesArray[0].id
107
- "
108
- >
109
- <c-avatar
110
- v-if="selectedValuesArray[0].photo"
111
- size="extraextraextrasmall"
112
- :image="selectedValuesArray[0].photo"
113
- :rounded="true"
114
- ></c-avatar>
115
- <c-avatar
116
- v-else
117
- size="extraextraextrasmall"
118
- :nameInitials="selectedValuesArray[0].initials"
119
- :rounded="true"
120
- :isDynamicallyColored="coloredAvatars"
121
- ></c-avatar>
122
- </div>
123
- <c-icon
124
- v-if="icon && !showImage && !selectSearch"
125
- :class="icon.class"
126
- :name="icon.name"
127
- :type="icon.type"
128
- :viewBox="icon.viewBox"
129
- >
130
- </c-icon>
131
- <div
132
- v-if="addCheckBox"
133
- class="flex flex-1 items-center overflow-hidden text-sm"
134
- >
135
- <p
136
- class="block truncate"
137
- v-if="
138
- selectedValuesArray.length > 0 &&
139
- selectedValuesArray[0].id &&
140
- (!selectSearch || selectSearch == '')
141
- "
142
- >
143
- {{ selectedValuesArray[0][renderOptionName] }}
144
- </p>
145
- <p v-else>
146
- {{ !selectSearch || selectSearch == "" ? placeholder : null }}
147
- </p>
148
- <p
149
- v-if="
150
- selectedValuesArray.length > 1 &&
151
- (!selectSearch || selectSearch == '') &&
152
- selectedValuesArray[0].showExtra
153
- "
154
- class="ml-1 block"
155
- >
156
- +{{ selectedValuesArray.length - 1 }}
157
- </p>
158
- </div>
159
- <div v-else class="flex min-w-0 flex-1 items-center gap-3">
160
- <p
161
- :class="[
162
- 'min-w-0 truncate text-sm text-left',
163
- showImage ? 'ml-3' : '',
164
- selectedValueClass,
165
- ]"
166
- v-bind:style="{
167
- ...(shouldShowCustomFonts
168
- ? { fontFamily: selectedValue }
169
- : {}),
170
- }"
171
- >
172
- {{
173
- hasSelectedValue
174
- ? selectedValue
175
- : !selectSearch || selectSearch == ""
176
- ? placeholder
177
- : null
178
- }}
179
- </p>
180
- <c-tag
181
- v-if="value && value.badge"
182
- :label="value.badge.label"
183
- :color="value.badge.color"
184
- class="shrink-0"
185
- ></c-tag>
186
- </div>
187
- </div>
188
- </div>
189
- <div
190
- v-if="toggleDropdown"
191
- class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
192
- >
193
- <div class="flex items-center gap-2">
194
- <c-icon
195
- name="search"
196
- type="outline"
197
- class="h-5 w-5 text-gray-400"
198
- ></c-icon>
199
- <p v-if="!selectSearch" class="text-sm text-gray-500">Search</p>
200
- </div>
201
- </div>
202
- <div class="pointer-events-none absolute top-2 right-3 flex">
203
- <div
204
- v-if="type == 'tertiary' ? showFocus : true"
205
- class="pointer-events-none right-0 flex items-center"
206
- >
207
- <c-icon
208
- name="chevron-down"
209
- type="solid"
210
- :class="[
211
- 'h-5 w-5 transition-transform duration-300',
212
- toggleDropdown ? 'rotate-180' : '',
213
- dropdownSelectorIconClass,
214
- ]"
215
- ></c-icon>
216
- </div>
217
- </div>
218
- </div>
219
- <transition
220
- enter-active-class="transition ease-out duration-100"
221
- enter-class="transform opacity-0 scale-95"
222
- enter-to-class="transform opacity-100 scale-100"
223
- leave-active-class="transition ease-in duration-75"
224
- leave-class="transform opacity-100 scale-100"
225
- leave-to-class="transform opacity-0 scale-95"
226
- >
227
- <div
228
- v-if="toggleDropdown && !isDisabled"
229
- :class="`${getDropdownPosition()} ${
230
- useSticky ? 'sticky sm:absolute' : 'absolute'
231
- } z-10 ${
232
- isFooter && shouldOpenAbove
233
- ? 'mb-12 max-h-60'
234
- : !isFooter && shouldOpenAbove
235
- ? 'mb-1 max-h-60'
236
- : 'mt-1 max-h-80 pb-20'
237
- } ${customWidth ? customWidth : 'w-full'}`"
238
- >
239
- <ul
240
- tabindex="-1"
241
- role="listbox"
242
- aria-labelledby="listbox-label"
243
- ref="optionsList"
244
- :class="`max-h-60 overflow-auto overscroll-contain bg-white py-1 text-sm ring-1 ring-gray-900 ring-opacity-5 focus:outline-none ${getDropdownPosition2()} ${getDropdownShadow()} ${
245
- isFooter ||
246
- (showCreateOption &&
247
- showCreateOptionAfterSearch &&
248
- selectSearch &&
249
- selectSearch.trim())
250
- ? 'rounded-t-md'
251
- : 'rounded-md'
252
- }`"
253
- @mousedown="handlePreventBlur"
254
- >
255
- <li
256
- v-if="addAction"
257
- @mousedown="actionEvent($event)"
258
- class="relative flex min-h-[36px] cursor-pointer select-none py-2 pl-3 pr-9 text-indigo-500 hover:bg-indigo-100 hover:text-indigo-700"
259
- >
260
- <c-icon
261
- type="outline"
262
- class="mr-1 h-5 w-5 text-indigo-400 group-hover:text-indigo-500"
263
- name="plus"
264
- ></c-icon>
265
- {{ addAction.label }}
266
- </li>
267
- <li v-if="headerSwitch" class="min-h-[36px] p-2 hover:bg-gray-100">
268
- <c-switch
269
- :label="headerSwitch.headerText"
270
- class="text-sm"
271
- direction="left"
272
- size="small"
273
- :value="headerSwitch.headerSwitchValue ? 1 : 0"
274
- @returnToggleValue="handleHeaderSwitch"
275
- :disabled="headerSwitch.headerSwitchDisable"
276
- ></c-switch>
277
- </li>
278
- <hr v-if="headerSwitch" class="my-1" />
279
- <!-- Select All Option -->
280
- <template
281
- v-if="
282
- (selectCheckboxes || addCheckBox) &&
283
- (organizedOptions.groups.length > 0 ||
284
- organizedOptions.selectedValues.length > 0 ||
285
- organizedOptions.unselectedValues.length > 0)
286
- "
287
- >
288
- <li
289
- @mousedown="handleSelect($event, allOption)"
290
- class="relative flex min-h-[36px] cursor-pointer select-none items-center gap-3 py-2 pl-3 pr-9 text-gray-700 hover:bg-gray-100"
291
- >
292
- <input
293
- type="checkbox"
294
- name="select-all"
295
- class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
296
- :checked="selectAll"
297
- />
298
-
299
- <div class="flex items-center gap-2">
300
- <c-icon
301
- v-if="allOption && allOption.icon"
302
- :class="allOption.icon.class"
303
- :name="allOption.icon.name"
304
- :type="allOption.icon.type"
305
- :viewBox="allOption.icon.viewBox"
306
- ></c-icon>
307
- <span class="list-options block break-words font-normal">
308
- Select all
309
- </span>
310
- </div>
311
- </li>
312
- <hr class="my-1" />
313
- </template>
314
- <!-- Groups Section -->
315
- <li
316
- v-for="(option, index) in organizedOptions.groups"
317
- :key="`group-${option.id || index}`"
318
- id="listbox-option-group"
319
- role="option"
320
- :ref="`option-group-${String(index)}`"
321
- @mousedown="handleSelect($event, option)"
322
- :class="option.isDisabled ? 'pointer-events-none' : ''"
323
- >
324
- <span
325
- class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
326
- :class="`group ${
327
- option.isDisabled ? 'custom-disabled-state' : ''
328
- } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
329
- >
330
- <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
331
- <input
332
- type="checkbox"
333
- name="group"
334
- class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
335
- :checked="isChecked(option)"
336
- />
337
- </div>
338
- <div class="flex min-w-0 flex-1 items-start gap-2">
339
- <div v-if="option.showImage" class="shrink-0 pt-0.5">
340
- <c-avatar
341
- v-if="option.photo"
342
- size="extraextraextrasmall"
343
- :image="option.photo"
344
- :rounded="true"
345
- ></c-avatar>
346
- <c-avatar
347
- v-else
348
- size="extraextraextrasmall"
349
- :nameInitials="option.initials"
350
- :rounded="true"
351
- :isDynamicallyColored="coloredAvatars"
352
- ></c-avatar>
353
- </div>
354
- <div
355
- class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
356
- v-if="option.showIcon && option.icon"
357
- >
358
- <c-icon
359
- :class="option.icon.class"
360
- :name="option.icon.name"
361
- :type="option.icon.type"
362
- :viewBox="option.icon.viewBox"
363
- >
364
- </c-icon>
365
- </div>
366
- <span
367
- :class="[
368
- option.photo || option.initials ? '' : 'text-left',
369
- addCheckBox ? 'overflow-hidden' : '',
370
- ]"
371
- class="list-options min-w-0 flex-1 break-words font-normal"
372
- v-bind:style="{
373
- ...(shouldShowCustomFonts
374
- ? { fontFamily: option[renderOptionName] }
375
- : {}),
376
- }"
377
- >{{ option[renderOptionName] }}
378
- </span>
379
- </div>
380
- <c-icon
381
- v-if="!addCheckBox && isOptionSelected(option)"
382
- name="check-outline-v2"
383
- type="outline-v2"
384
- class="h-4 w-4 shrink-0 self-center text-indigo-700"
385
- />
386
- </span>
387
- </li>
388
- <hr
389
- v-if="
390
- organizedOptions.groups.length > 0 &&
391
- (organizedOptions.selectedValues.length > 0 ||
392
- organizedOptions.unselectedValues.length > 0)
393
- "
394
- class="my-1"
395
- />
396
- <!-- Selected values section (non-groups) -->
397
- <li
398
- v-for="(option, index) in organizedOptions.selectedValues"
399
- :key="`selected-value-${option.id || index}`"
400
- :id="`listbox-option-selected-value-${String(index)}`"
401
- role="option"
402
- :ref="`option-selected-value-${String(index)}`"
403
- @mousedown="handleSelect($event, option)"
404
- :class="option.isDisabled ? 'pointer-events-none' : ''"
405
- >
406
- <span
407
- class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
408
- :class="`group ${
409
- option.isDisabled ? 'custom-disabled-state' : ''
410
- } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
411
- >
412
- <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
413
- <input
414
- type="checkbox"
415
- name="value"
416
- class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
417
- :checked="preservedOrder ? isChecked(option) : true"
418
- />
419
- </div>
420
- <div class="flex min-w-0 flex-1 items-start gap-2">
421
- <div v-if="option.showImage" class="shrink-0 pt-0.5">
422
- <c-avatar
423
- v-if="option.photo"
424
- size="extraextraextrasmall"
425
- :image="option.photo"
426
- :rounded="true"
427
- ></c-avatar>
428
- <c-avatar
429
- v-else
430
- size="extraextraextrasmall"
431
- :nameInitials="option.initials"
432
- :rounded="true"
433
- :isDynamicallyColored="coloredAvatars"
434
- ></c-avatar>
435
- </div>
436
- <div
437
- class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
438
- v-if="option.showIcon && option.icon"
439
- >
440
- <c-icon
441
- :class="option.icon.class"
442
- :name="option.icon.name"
443
- :type="option.icon.type"
444
- :viewBox="option.icon.viewBox"
445
- >
446
- </c-icon>
447
- </div>
448
- <span
449
- :class="[
450
- option.photo || option.initials ? '' : 'text-left',
451
- addCheckBox ? 'overflow-hidden' : '',
452
- ]"
453
- class="list-options min-w-0 flex-1 break-words font-normal"
454
- v-bind:style="{
455
- ...(shouldShowCustomFonts
456
- ? { fontFamily: option[renderOptionName] }
457
- : {}),
458
- }"
459
- >{{ option[renderOptionName] }}
460
- </span>
461
- </div>
462
- </span>
463
- <hr v-if="option.isBorder" class="my-1" />
464
- </li>
465
- <hr
466
- v-if="
467
- organizedOptions.selectedValues.length > 0 &&
468
- organizedOptions.unselectedValues.length > 0
469
- "
470
- class="my-1"
471
- />
472
- <!-- Unselected values section -->
473
- <li
474
- v-for="(option, index) in organizedOptions.unselectedValues"
475
- :key="`unselected-value-${option.id || index}`"
476
- :id="`listbox-option-unselected-value-${String(index)}`"
477
- role="option"
478
- :ref="`option-unselected-value-${String(index)}`"
479
- @mousedown="handleSelect($event, option)"
480
- :class="option.isDisabled ? 'pointer-events-none' : ''"
481
- >
482
- <span
483
- class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
484
- :class="`group ${
485
- option.isDisabled ? 'custom-disabled-state' : ''
486
- } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
487
- >
488
- <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
489
- <input
490
- type="checkbox"
491
- name="value"
492
- class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
493
- :checked="selectAll || isChecked(option)"
494
- />
495
- </div>
496
- <div class="flex min-w-0 flex-1 items-start gap-2">
497
- <div v-if="option.showImage" class="shrink-0 pt-0.5">
498
- <c-avatar
499
- v-if="option.photo"
500
- size="extraextraextrasmall"
501
- :image="option.photo"
502
- :rounded="true"
503
- ></c-avatar>
504
- <c-avatar
505
- v-else
506
- size="extraextraextrasmall"
507
- :nameInitials="option.initials"
508
- :rounded="true"
509
- :isDynamicallyColored="coloredAvatars"
510
- ></c-avatar>
511
- </div>
512
- <div
513
- class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
514
- v-if="option.showIcon && option.icon"
515
- >
516
- <c-icon
517
- :class="option.icon.class"
518
- :name="option.icon.name"
519
- :type="option.icon.type"
520
- :viewBox="option.icon.viewBox"
521
- >
522
- </c-icon>
523
- </div>
524
- <div
525
- class="list-options flex min-w-0 flex-1 items-center gap-2 font-normal"
526
- >
527
- <span
528
- :class="[
529
- option.photo || option.initials ? '' : 'text-left',
530
- addCheckBox ? 'overflow-hidden' : '',
531
- ]"
532
- class="min-w-0 break-words"
533
- v-bind:style="{
534
- ...(shouldShowCustomFonts
535
- ? { fontFamily: option[renderOptionName] }
536
- : {}),
537
- }"
538
- >{{ option[renderOptionName] }}</span
539
- >
540
- <c-tag
541
- v-if="option.badge"
542
- :label="option.badge.label"
543
- :color="option.badge.color"
544
- class="shrink-0"
545
- ></c-tag>
546
- </div>
547
- </div>
548
- <c-icon
549
- v-if="!addCheckBox && isOptionSelected(option)"
550
- name="check-outline-v2"
551
- type="outline-v2"
552
- class="h-4 w-4 shrink-0 self-center text-indigo-700"
553
- />
554
- </span>
555
- <hr v-if="option.isBorder" class="my-1" />
556
- </li>
557
- <li
558
- v-if="
559
- organizedOptions.groups.length === 0 &&
560
- organizedOptions.selectedValues.length === 0 &&
561
- organizedOptions.unselectedValues.length === 0 &&
562
- selectSearch &&
563
- selectSearch.length > 0
564
- "
565
- class="px-3 py-2 text-sm text-gray-500"
566
- >
567
- No options found, try searching something else.
568
- </li>
569
- </ul>
570
- <div
571
- v-if="isFooter"
572
- class="group cursor-pointer rounded-b-md bg-gray-50 p-3 ring-1 ring-gray-900 ring-opacity-5"
573
- @mousedown="handleFooter"
574
- >
575
- <p class="text-sm text-indigo-700">
576
- {{ footerText }}
577
- </p>
578
- </div>
579
- <div
580
- v-if="
581
- showCreateOption &&
582
- showCreateOptionAfterSearch &&
583
- selectSearch &&
584
- selectSearch.trim()
585
- "
586
- class="sticky bottom-0 z-10 flex min-h-[44px] items-start gap-3 rounded-b-md bg-white px-3 py-2 shadow-lg ring-1 ring-gray-900 ring-opacity-5"
587
- >
588
- <span class="word-break flex-1 text-sm text-gray-700">{{
589
- selectSearch
590
- }}</span>
591
-
592
- <button
593
- type="button"
594
- :id="id + '_create_button'"
595
- @mousedown="handleCreateOption($event)"
596
- class="flex items-center gap-1 text-sm text-indigo-700"
597
- >
598
- <c-icon
599
- type="solid"
600
- name="plus-circle-solid-v2"
601
- class="h-5 w-5 text-indigo-600"
602
- viewBox="0 0 24 24"
603
- ></c-icon>
604
- Create
605
- </button>
606
- </div>
607
- </div>
608
- </transition>
609
- </div>
610
- <p
611
- v-if="helpText && isValidate == true"
612
- class="mt-2 text-left text-sm text-gray-500"
613
- >
614
- {{ helpText }}
615
- </p>
616
- <!-- validation error message -->
617
- <p
618
- v-if="!isValidate && errorMessage"
619
- class="mt-2 text-left text-sm text-red-600"
620
- >
621
- {{ errorMessage }}
622
- </p>
623
- </div>
624
- </template>
625
- <script>
626
- import { isNumber, isEmpty } from "lodash-es";
627
- import CIcon from "../CIcon/CIcon.vue";
628
- import { getActionID } from "../../helper";
629
- import CTag from "../CTag/CTag.vue";
630
- import CAvatar from "../CAvatar/CAvatar.vue";
631
- export default {
632
- name: "CSelect",
633
- components: {
634
- CIcon,
635
- CTag,
636
- CAvatar,
637
- },
638
- props: {
639
- // id of selectpicker
640
- id: {
641
- type: String,
642
- },
643
- // label of selectpicker
644
- label: {
645
- type: String,
646
- },
647
- // action button of selectpicker
648
- actionBtn: {
649
- type: String,
650
- default: null,
651
- },
652
- // placeholder in selectpicker
653
- placeholder: {
654
- type: String,
655
- },
656
- // list to render in dropdown
657
- options: {
658
- type: Array,
659
- required: true,
660
- },
661
- // show the avatars with different colors
662
- coloredAvatars: {
663
- type: Boolean,
664
- },
665
- // text below dropdown to describe more about the field
666
- helpText: {
667
- type: String,
668
- },
669
- // implement a fixed btn at the bottom of dropdown
670
- isFooter: {
671
- type: Boolean,
672
- },
673
- // style input field
674
- inputStyle: {
675
- type: String,
676
- },
677
- // footer btn text
678
- footerText: {
679
- type: String,
680
- },
681
- // text adjacent to label of dropdown to provide hint about field
682
- hint: { type: String },
683
- // to show if image is present along with dropdown option
684
- showImage: { type: Boolean },
685
- // whether the select field is mandatory or not
686
- isRequired: {
687
- type: Boolean,
688
- },
689
- // validation is passed or not
690
- isValidate: { type: Boolean, default: true },
691
- // will truncate the text in input field
692
- selectedOptionStyles: {
693
- type: String,
694
- },
695
- // validation error message
696
- errorMessage: {
697
- type: String,
698
- },
699
- // perform action on first option in dropdown
700
- addAction: { type: Object },
701
- // icons in dropdown list
702
- icon: { type: Object },
703
- // value to set as default option in dropdown
704
- value: { type: [Object, String, Array], default: null },
705
- // type of dropdown - gray,tertiary, blue or default
706
- type: {
707
- type: String,
708
- },
709
- // name of the field in array to that contains text to render in dropdown
710
- renderOptionName: {
711
- type: String,
712
- default: "option",
713
- },
714
- // whether to disable the selectpicker
715
- isDisabled: {
716
- type: Boolean,
717
- default: false,
718
- },
719
- inputClasses: {
720
- type: String,
721
- default: "",
722
- },
723
- optionClasses: {
724
- type: String,
725
- default: "",
726
- },
727
- // customised width for the dropdown
728
- customWidth: {
729
- type: String,
730
- default: null,
731
- },
732
- customBorderRadius: {
733
- type: String,
734
- default: "rounded-md",
735
- },
736
- addCheckBox: {
737
- type: Boolean,
738
- default: false,
739
- },
740
- selectAll: {
741
- type: Boolean,
742
- default: false,
743
- },
744
- selectCheckboxes: {
745
- type: Boolean,
746
- default: false,
747
- },
748
- headerSwitch: {
749
- type: Object,
750
- default: null,
751
- },
752
- useSticky: {
753
- type: Boolean,
754
- default: false,
755
- },
756
- showCloseButton: {
757
- type: Boolean,
758
- default: false,
759
- },
760
- showCreateOption: {
761
- type: Boolean,
762
- default: false,
763
- },
764
- shouldShowCustomFonts: {
765
- type: Boolean,
766
- default: false,
767
- },
768
- customFontNames: {
769
- type: Array,
770
- default: () => [],
771
- },
772
- inputContainerClass: {
773
- type: String,
774
- default: "",
775
- },
776
- },
777
- data() {
778
- return {
779
- toggleDropdown: false,
780
- selectedValue: null,
781
- showFocus: false,
782
- selectSearch: null,
783
- renderOptions: this.options,
784
- selectedValuesArray: [],
785
- isToggle: 0,
786
- shouldOpenAbove: false,
787
- // Frozen order while dropdown is open - prevents UI shift on selection
788
- preservedOrder: null,
789
- };
790
- },
791
- computed: {
792
- // Non-credential-like, per-instance name so browser password managers don't
793
- // classify the search box as a username/email field and autofill it.
794
- searchInputName() {
795
- return `c-select-search-${this.id || this._uid}`;
796
- },
797
- classes() {
798
- return {
799
- "text-gray-900 border border-gray-300 hover:bg-gray-200 bg-gray-100 focus:ring-2 focus:border-gray-200 focus:ring-gray-200 focus:ring-offset-2 shadow-sm":
800
- this.type == "gray",
801
- "bg-white border border-gray-300 hover:bg-indigo-100 text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-200":
802
- this.type == "tertiary",
803
- "bg-white text-indigo-700 border-none focus:outline-none":
804
- this.type == "indigo",
805
- "border border-gray-300 bg-white shadow-sm focus:outline-none focus:border-gray-300 focus:ring-indigo-600 focus:ring-2 focus:ring-offset-2":
806
- this.type == null,
807
- };
808
- },
809
- allOption() {
810
- if (
811
- this.options &&
812
- this.options.length > 0 &&
813
- this.options[0] &&
814
- this.options[0].id === "all"
815
- ) {
816
- return this.options[0];
817
- }
818
- return null;
819
- },
820
- organizedOptions() {
821
- if (!this.addCheckBox) {
822
- const filteredOptions = this.renderOptions.filter(
823
- (option) => option.id !== "all"
824
- );
825
- return {
826
- groups: [],
827
- selectedValues: [],
828
- unselectedValues: filteredOptions,
829
- };
830
- }
831
-
832
- // While dropdown is open and not searching, preserve order to prevent UI shift on selection
833
- if (
834
- this.preservedOrder &&
835
- (!this.selectSearch || this.selectSearch.trim() === "")
836
- ) {
837
- return this.preservedOrder;
838
- }
839
-
840
- return this.computeOrganizedOptions();
841
- },
842
- showCreateOptionAfterSearch() {
843
- if (!this.showCreateOption) return false;
844
-
845
- const term = (this.selectSearch || "").trim().toLowerCase();
846
- if (!term) return false;
847
-
848
- const hasExactMatch = this.options.some((option) => {
849
- const label = option[this.renderOptionName];
850
- return label && String(label).toLowerCase() === term;
851
- });
852
-
853
- return !hasExactMatch;
854
- },
855
- dropdownSelectorIconClass() {
856
- return this.type === "indigo" ? "text-indigo-600" : "text-gray-400";
857
- },
858
- hasSelectedValue() {
859
- const val = this.selectedValue;
860
- if (val === null || val === undefined || val === "") {
861
- return false;
862
- }
863
- if (typeof val === "object") {
864
- return !isEmpty(val);
865
- }
866
- return true;
867
- },
868
- selectedValueClass() {
869
- if (this.hasSelectedValue) {
870
- if (this.isDisabled) {
871
- return "text-gray-500";
872
- }
873
-
874
- if (this.type === "indigo") {
875
- return "text-indigo-600";
876
- }
877
- return "text-gray-700";
878
- }
879
- return "text-gray-500";
880
- },
881
- },
882
- methods: {
883
- isEmpty,
884
- getActionIDFn(name) {
885
- return getActionID(name, this.id);
886
- },
887
- checkAvailableSpace() {
888
- if (!this.$refs.inputContainer) return false;
889
-
890
- const container = this.$refs.inputContainer;
891
- const rect = container.getBoundingClientRect();
892
- const viewportHeight = window.innerHeight;
893
- const bottomSpace = viewportHeight - rect.bottom;
894
- const componentHeight = 320;
895
-
896
- // Check if bottom space is less than 80% the component height if there is no footer
897
- return (
898
- bottomSpace <
899
- (this.isFooter ? componentHeight : (componentHeight * 4) / 5)
900
- );
901
- },
902
- getDropdownPosition() {
903
- this.shouldOpenAbove = this.checkAvailableSpace();
904
- return this.shouldOpenAbove ? "bottom-full" : "top-full";
905
- },
906
- getDropdownPosition2() {
907
- return this.shouldOpenAbove ? "bottom-full" : "";
908
- },
909
- getDropdownShadow() {
910
- if (this.shouldOpenAbove) {
911
- return "shadow-[0_-10px_15px_-3px_rgba(0,0,0,0.1),0_-4px_6px_-4px_rgba(0,0,0,0.1)]";
912
- } else {
913
- return "shadow-lg";
914
- }
915
- },
916
- handleCrossClick(event) {
917
- event.preventDefault();
918
- this.$emit("hide-single-select-dropdown");
919
- },
920
- handleInputClick() {
921
- this.toggleDropdown = !this.toggleDropdown;
922
- if (!this.toggleDropdown) {
923
- this.selectSearch = "";
924
- this.$nextTick(() => {
925
- if (this.$refs["c-select-input"]) {
926
- this.$refs["c-select-input"].focus();
927
- }
928
- });
929
- }
930
- },
931
- handleInputFocus() {
932
- if (this.type == "tertiary") {
933
- this.showFocus = true;
934
- }
935
- },
936
- handleSelect(event, option) {
937
- this.selectSearch = null;
938
- this.selectedValue = option[this.renderOptionName];
939
- if (this.addCheckBox) {
940
- // Freeze current display order BEFORE updating selection to prevent UI shift
941
- const currentDisplay = this.organizedOptions;
942
- this.preservedOrder = {
943
- groups: [...currentDisplay.groups],
944
- selectedValues: [...currentDisplay.selectedValues],
945
- unselectedValues: [...currentDisplay.unselectedValues],
946
- };
947
- if (this.getIndex(option) == -1) {
948
- this.selectedValuesArray = [...this.selectedValuesArray, option];
949
- } else {
950
- this.selectedValuesArray = this.selectedValuesArray.filter(
951
- (item) => option.id !== item.id
952
- );
953
- }
954
- this.$emit("onChangeChecked", this.selectedValuesArray, option);
955
- }
956
-
957
- this.$emit("onChangeValue", option);
958
- if (!this.addCheckBox) {
959
- this.toggleDropdown = false;
960
- }
961
- this.type === "tertiary" ? (this.showFocus = false) : "";
962
- this.renderOptions = this.options;
963
- },
964
- handleFooter() {
965
- this.$emit("handleFooter");
966
- },
967
- actionEvent(event) {
968
- this.$emit("listAction", event);
969
- },
970
- actionBtnEvent(event) {
971
- this.$emit("listAction", event);
972
- },
973
- handleElementBlur() {
974
- this.toggleDropdown = false;
975
- if (this.addCheckBox) {
976
- this.$emit("onClickOutside", this.selectedValuesArray);
977
- }
978
- this.selectSearch = null;
979
- this.renderOptions = this.options;
980
- },
981
- handleHeaderSwitch(value) {
982
- this.$emit("handleHeaderSwitch", value);
983
- },
984
- //this prevents the blur to be called when the user clicks on the scrollbar
985
- handlePreventBlur(event) {
986
- event.preventDefault();
987
- },
988
- search() {
989
- this.selectedValue = null;
990
- // if the search term is empty space, then set the render options to empty array
991
- if (
992
- this.selectSearch !== null &&
993
- this.selectSearch.length > 0 &&
994
- this.selectSearch.trim() === ""
995
- ) {
996
- this.renderOptions = [];
997
- this.$emit("search-term", this.selectSearch);
998
- return;
999
- }
1000
- if (
1001
- !this.selectSearch ||
1002
- this.selectSearch == "" ||
1003
- this.selectSearch.trim() == ""
1004
- ) {
1005
- this.renderOptions = this.options;
1006
- this.$emit("search-term", this.selectSearch);
1007
- return;
1008
- }
1009
- let options = [...this.options];
1010
- this.renderOptions = options.filter((option) => {
1011
- isNumber(option[this.renderOptionName])
1012
- ? (option[this.renderOptionName] =
1013
- option[this.renderOptionName].toString())
1014
- : "";
1015
- const optionName = String(
1016
- option[this.renderOptionName] || ""
1017
- ).toLowerCase();
1018
- const searchTerm = this.selectSearch.trim().toLowerCase();
1019
- return optionName.includes(searchTerm);
1020
- });
1021
-
1022
- if (this.renderOptions.length === 0) {
1023
- this.$emit("search-term", this.selectSearch.trim());
1024
- }
1025
- },
1026
- async scrollOptionIntoView(refName) {
1027
- await this.$nextTick();
1028
- const list = this.$refs.optionsList;
1029
- const optionEl = (this.$refs[refName] || [])[0];
1030
- if (!list || !optionEl) return;
1031
-
1032
- const top = optionEl.offsetTop;
1033
- const bottom = top + optionEl.offsetHeight;
1034
- const viewTop = list.scrollTop;
1035
- const viewBottom = viewTop + list.clientHeight;
1036
-
1037
- const target =
1038
- top < viewTop
1039
- ? top
1040
- : bottom > viewBottom
1041
- ? bottom - list.clientHeight
1042
- : viewTop;
1043
-
1044
- if (target !== viewTop) {
1045
- list.scrollTo({ top: target, behavior: "smooth" });
1046
- }
1047
- },
1048
- isOptionSelected(option) {
1049
- /*
1050
- * Compares value to option by id when both exist,
1051
- * otherwise by renderOptionName / string value.
1052
- */
1053
- if (option == null) {
1054
- return false;
1055
- }
1056
-
1057
- const v = this.value;
1058
- if (v === null) {
1059
- return false;
1060
- }
1061
-
1062
- if (typeof v === "object" && v !== null) {
1063
- if (v.id !== undefined && option.id !== undefined) {
1064
- return v.id === option.id;
1065
- }
1066
-
1067
- const vLabel = v[this.renderOptionName];
1068
- if (vLabel !== undefined && vLabel !== null) {
1069
- return String(vLabel) === String(option[this.renderOptionName]);
1070
- }
1071
- }
1072
-
1073
- return String(v) === String(option[this.renderOptionName]);
1074
- },
1075
- getIndex(option) {
1076
- return this.selectedValuesArray.findIndex((item) => item.id == option.id);
1077
- },
1078
- isChecked(option) {
1079
- return this.selectedValuesArray.findIndex(
1080
- (item) => item.id == option.id
1081
- ) == -1
1082
- ? false
1083
- : true;
1084
- },
1085
- handleCreateOption(event) {
1086
- event.preventDefault();
1087
- this.toggleDropdown = false;
1088
- this.$emit("handleCreateOption", {
1089
- event,
1090
- searchText: this.selectSearch.trim(),
1091
- });
1092
- this.selectSearch = null;
1093
- this.renderOptions = this.options;
1094
-
1095
- const cSelectInputRef = this.$refs["c-select-input"];
1096
- this.$nextTick(() => {
1097
- cSelectInputRef && cSelectInputRef.blur();
1098
- });
1099
- },
1100
- handleKeydown(event) {
1101
- if (event.key === "Enter") {
1102
- event.preventDefault();
1103
- event.stopPropagation();
1104
-
1105
- if (
1106
- this.showCreateOption &&
1107
- this.showCreateOptionAfterSearch &&
1108
- this.selectSearch &&
1109
- this.selectSearch.trim()
1110
- ) {
1111
- this.handleCreateOption(event);
1112
- }
1113
- }
1114
- },
1115
- async handleLoadFont() {
1116
- const customFontNames = this.customFontNames;
1117
- const googleParam = customFontNames
1118
- .map((f) => `family=${f.replace(/ /g, "+")}`)
1119
- .join("&");
1120
-
1121
- const link = document.createElement("link");
1122
- link.rel = "stylesheet";
1123
- link.href = `https://fonts.googleapis.com/css2?${googleParam}&display=swap`;
1124
- link.setAttribute("data-fonts", customFontNames.join(","));
1125
- document.head.appendChild(link);
1126
-
1127
- await document.fonts.ready;
1128
- },
1129
- isGroup(option) {
1130
- return option.type && option.type.includes("group");
1131
- },
1132
- computeOrganizedOptions() {
1133
- const selectedIds = new Set(
1134
- this.selectedValuesArray.map((item) => item.id)
1135
- );
1136
-
1137
- const groups = [];
1138
- const selectedValues = [];
1139
- const unselectedValues = [];
1140
-
1141
- // Filter out the "all" option from renderOptions
1142
- this.renderOptions
1143
- .filter((option) => option.id !== "all")
1144
- .forEach((option) => {
1145
- if (this.isGroup(option)) {
1146
- // Groups stay in their own section regardless of selection
1147
- groups.push(option);
1148
- } else {
1149
- // Values are separated by selection state
1150
- if (selectedIds.has(option.id)) {
1151
- selectedValues.push(option);
1152
- } else {
1153
- unselectedValues.push(option);
1154
- }
1155
- }
1156
- });
1157
-
1158
- // Sort by renderOptionName (guard against undefined a/b or missing renderOptionName)
1159
- const getSortKey = (item) =>
1160
- String((item && item[this.renderOptionName]) || "").toLowerCase();
1161
-
1162
- selectedValues.sort((a, b) => getSortKey(a).localeCompare(getSortKey(b)));
1163
- unselectedValues.sort((a, b) =>
1164
- getSortKey(a).localeCompare(getSortKey(b))
1165
- );
1166
-
1167
- return {
1168
- groups,
1169
- selectedValues,
1170
- unselectedValues,
1171
- };
1172
- },
1173
- },
1174
- watch: {
1175
- value() {
1176
- this.selectedValue =
1177
- this.value !== null && this.value[this.renderOptionName]
1178
- ? this.value[this.renderOptionName]
1179
- : this.value;
1180
- if (this.addCheckBox) {
1181
- this.selectedValuesArray = this.value ? this.value : [];
1182
- }
1183
- },
1184
- options() {
1185
- this.renderOptions = this.options;
1186
- },
1187
- toggleDropdown(newValue) {
1188
- if (newValue) {
1189
- this.checkAvailableSpace();
1190
-
1191
- this.preservedOrder = null;
1192
-
1193
- const index = this.renderOptions.findIndex(
1194
- (option) => option[this.renderOptionName] === this.selectedValue
1195
- );
1196
- if (index !== -1) {
1197
- const refName = `option-${String(index)}`;
1198
- this.scrollOptionIntoView(refName);
1199
- }
1200
- } else {
1201
- this.preservedOrder = null;
1202
- }
1203
- },
1204
- },
1205
- mounted() {
1206
- this.selectedValue =
1207
- this.value !== null && this.value[this.renderOptionName]
1208
- ? this.value[this.renderOptionName]
1209
- : this.value;
1210
- if (this.addCheckBox) {
1211
- this.selectedValuesArray = this.value ? this.value : [];
1212
- }
1213
-
1214
- if (this.shouldShowCustomFonts && this.customFontNames.length > 0) {
1215
- this.handleLoadFont();
1216
- }
1217
- },
1218
- };
1219
- </script>
1220
-
1221
- <style scoped>
1222
- .word-break {
1223
- word-break: break-word;
1224
- }
1225
- </style>
1
+ <template>
2
+ <div>
3
+ <div
4
+ :class="`flex w-full items-center ${
5
+ label ? 'justify-between' : 'justify-end'
6
+ }`"
7
+ >
8
+ <div class="flex items-center" v-if="label">
9
+ <!-- label of select field -->
10
+ <label class="block text-sm font-medium text-gray-900">
11
+ {{ label }}
12
+ </label>
13
+ <!-- asterisk sign to render if field is required -->
14
+ <p v-if="isRequired" class="ml-1 text-red-600">*</p>
15
+ </div>
16
+ <button
17
+ v-if="actionBtn"
18
+ :id="getActionIDFn(actionBtn)"
19
+ class="block text-sm font-medium text-indigo-800 hover:underline"
20
+ @click="actionBtnEvent($event)"
21
+ >
22
+ {{ actionBtn }}
23
+ </button>
24
+ <span v-if="hint" class="text-sm text-gray-500">{{ hint }}</span>
25
+ </div>
26
+ <div :class="['relative', inputContainerClass]" ref="inputContainer">
27
+ <div :class="label || actionBtn ? 'mt-1' : ''">
28
+ <input
29
+ ref="c-select-input"
30
+ type="text"
31
+ v-model="selectSearch"
32
+ @click="handleInputClick"
33
+ @focus="handleInputFocus"
34
+ @input="search()"
35
+ @keydown="handleKeydown"
36
+ @keydown.enter.prevent="handleKeydown"
37
+ aria-haspopup="listbox"
38
+ aria-expanded="true"
39
+ aria-labelledby="listbox-label"
40
+ class="h-9 w-full cursor-pointer border py-2 pr-10 text-left text-sm"
41
+ :class="[
42
+ classes,
43
+ !isValidate && 'border-red-300',
44
+ inputClasses,
45
+ customBorderRadius,
46
+ toggleDropdown ? 'pl-10' : 'pl-3',
47
+ ]"
48
+ :disabled="isDisabled"
49
+ autocomplete="off"
50
+ :name="searchInputName"
51
+ data-lpignore="true"
52
+ data-1p-ignore
53
+ data-form-type="other"
54
+ @blur="handleElementBlur"
55
+ :id="id"
56
+ :style="inputStyle"
57
+ />
58
+ <button
59
+ class="absolute top-2.5 right-10 z-100 cursor-pointer"
60
+ :id="id + '_close_button'"
61
+ v-if="showCloseButton && (value == null || value.length == 0)"
62
+ @click.stop="handleCrossClick"
63
+ >
64
+ <c-icon
65
+ name="close"
66
+ type="outline-v2"
67
+ class="h-4 w-4 text-gray-400"
68
+ />
69
+ </button>
70
+ <div
71
+ v-if="!toggleDropdown"
72
+ class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
73
+ >
74
+ <div
75
+ :class="[
76
+ 'flex min-w-0 flex-1 items-center gap-2',
77
+ selectedOptionStyles,
78
+ ]"
79
+ >
80
+ <div
81
+ class="flex shrink-0"
82
+ v-if="!addCheckBox && showImage && !selectSearch && value"
83
+ >
84
+ <c-avatar
85
+ v-if="value.photo"
86
+ size="extraextraextrasmall"
87
+ :image="value.photo"
88
+ :rounded="true"
89
+ ></c-avatar>
90
+ <c-avatar
91
+ v-else
92
+ size="extraextraextrasmall"
93
+ :nameInitials="value.initials"
94
+ :rounded="true"
95
+ :isDynamicallyColored="coloredAvatars"
96
+ ></c-avatar>
97
+ </div>
98
+ <div
99
+ class="flex shrink-0"
100
+ v-if="
101
+ addCheckBox &&
102
+ showImage &&
103
+ !selectSearch &&
104
+ selectedValuesArray &&
105
+ selectedValuesArray.length &&
106
+ selectedValuesArray[0].id
107
+ "
108
+ >
109
+ <c-avatar
110
+ v-if="selectedValuesArray[0].photo"
111
+ size="extraextraextrasmall"
112
+ :image="selectedValuesArray[0].photo"
113
+ :rounded="true"
114
+ ></c-avatar>
115
+ <c-avatar
116
+ v-else
117
+ size="extraextraextrasmall"
118
+ :nameInitials="selectedValuesArray[0].initials"
119
+ :rounded="true"
120
+ :isDynamicallyColored="coloredAvatars"
121
+ ></c-avatar>
122
+ </div>
123
+ <c-icon
124
+ v-if="icon && !showImage && !selectSearch"
125
+ :class="icon.class"
126
+ :name="icon.name"
127
+ :type="icon.type"
128
+ :viewBox="icon.viewBox"
129
+ >
130
+ </c-icon>
131
+ <div
132
+ v-if="addCheckBox"
133
+ class="flex flex-1 items-center overflow-hidden text-sm"
134
+ >
135
+ <p
136
+ class="block truncate"
137
+ v-if="
138
+ selectedValuesArray.length > 0 &&
139
+ selectedValuesArray[0].id &&
140
+ (!selectSearch || selectSearch == '')
141
+ "
142
+ >
143
+ {{ selectedValuesArray[0][renderOptionName] }}
144
+ </p>
145
+ <p v-else>
146
+ {{ !selectSearch || selectSearch == "" ? placeholder : null }}
147
+ </p>
148
+ <p
149
+ v-if="
150
+ selectedValuesArray.length > 1 &&
151
+ (!selectSearch || selectSearch == '') &&
152
+ selectedValuesArray[0].showExtra
153
+ "
154
+ class="ml-1 block"
155
+ >
156
+ +{{ selectedValuesArray.length - 1 }}
157
+ </p>
158
+ </div>
159
+ <div v-else class="flex min-w-0 flex-1 items-center gap-3">
160
+ <p
161
+ :class="[
162
+ 'min-w-0 truncate text-sm text-left',
163
+ showImage ? 'ml-3' : '',
164
+ selectedValueClass,
165
+ ]"
166
+ v-bind:style="{
167
+ ...(shouldShowCustomFonts
168
+ ? { fontFamily: selectedValue }
169
+ : {}),
170
+ }"
171
+ >
172
+ {{
173
+ hasSelectedValue
174
+ ? selectedValue
175
+ : !selectSearch || selectSearch == ""
176
+ ? placeholder
177
+ : null
178
+ }}
179
+ </p>
180
+ <c-tag
181
+ v-if="value && value.badge"
182
+ :label="value.badge.label"
183
+ :color="value.badge.color"
184
+ class="shrink-0"
185
+ ></c-tag>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ <div
190
+ v-if="toggleDropdown"
191
+ class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
192
+ >
193
+ <div class="flex items-center gap-2">
194
+ <c-icon
195
+ name="search"
196
+ type="outline"
197
+ class="h-5 w-5 text-gray-400"
198
+ ></c-icon>
199
+ <p v-if="!selectSearch" class="text-sm text-gray-500">Search</p>
200
+ </div>
201
+ </div>
202
+ <div class="pointer-events-none absolute top-2 right-3 flex">
203
+ <div
204
+ v-if="type == 'tertiary' ? showFocus : true"
205
+ class="pointer-events-none right-0 flex items-center"
206
+ >
207
+ <c-icon
208
+ name="chevron-down"
209
+ type="solid"
210
+ :class="[
211
+ 'h-5 w-5 transition-transform duration-300',
212
+ toggleDropdown ? 'rotate-180' : '',
213
+ dropdownSelectorIconClass,
214
+ ]"
215
+ ></c-icon>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ <transition
220
+ enter-active-class="transition ease-out duration-100"
221
+ enter-class="transform opacity-0 scale-95"
222
+ enter-to-class="transform opacity-100 scale-100"
223
+ leave-active-class="transition ease-in duration-75"
224
+ leave-class="transform opacity-100 scale-100"
225
+ leave-to-class="transform opacity-0 scale-95"
226
+ >
227
+ <div
228
+ v-if="toggleDropdown && !isDisabled"
229
+ :class="`${getDropdownPosition()} ${
230
+ useSticky ? 'sticky sm:absolute' : 'absolute'
231
+ } z-10 ${
232
+ isFooter && shouldOpenAbove
233
+ ? 'mb-12 max-h-60'
234
+ : !isFooter && shouldOpenAbove
235
+ ? 'mb-1 max-h-60'
236
+ : 'mt-1 max-h-80 pb-20'
237
+ } ${customWidth ? customWidth : 'w-full'}`"
238
+ >
239
+ <ul
240
+ tabindex="-1"
241
+ role="listbox"
242
+ aria-labelledby="listbox-label"
243
+ ref="optionsList"
244
+ :class="`max-h-60 overflow-auto overscroll-contain bg-white py-1 text-sm ring-1 ring-gray-900 ring-opacity-5 focus:outline-none ${getDropdownPosition2()} ${getDropdownShadow()} ${
245
+ isFooter ||
246
+ (showCreateOption &&
247
+ showCreateOptionAfterSearch &&
248
+ selectSearch &&
249
+ selectSearch.trim())
250
+ ? 'rounded-t-md'
251
+ : 'rounded-md'
252
+ }`"
253
+ @mousedown="handlePreventBlur"
254
+ >
255
+ <li
256
+ v-if="addAction"
257
+ @mousedown="actionEvent($event)"
258
+ class="relative flex min-h-[36px] cursor-pointer select-none py-2 pl-3 pr-9 text-indigo-500 hover:bg-indigo-100 hover:text-indigo-700"
259
+ >
260
+ <c-icon
261
+ type="outline"
262
+ class="mr-1 h-5 w-5 text-indigo-400 group-hover:text-indigo-500"
263
+ name="plus"
264
+ ></c-icon>
265
+ {{ addAction.label }}
266
+ </li>
267
+ <li v-if="headerSwitch" class="min-h-[36px] p-2 hover:bg-gray-100">
268
+ <c-switch
269
+ :label="headerSwitch.headerText"
270
+ class="text-sm"
271
+ direction="left"
272
+ size="small"
273
+ :value="headerSwitch.headerSwitchValue ? 1 : 0"
274
+ @returnToggleValue="handleHeaderSwitch"
275
+ :disabled="headerSwitch.headerSwitchDisable"
276
+ ></c-switch>
277
+ </li>
278
+ <hr v-if="headerSwitch" class="my-1" />
279
+ <!-- Select All Option -->
280
+ <template
281
+ v-if="
282
+ (selectCheckboxes || addCheckBox) &&
283
+ (organizedOptions.groups.length > 0 ||
284
+ organizedOptions.selectedValues.length > 0 ||
285
+ organizedOptions.unselectedValues.length > 0)
286
+ "
287
+ >
288
+ <li
289
+ @mousedown="handleSelect($event, allOption)"
290
+ class="relative flex min-h-[36px] cursor-pointer select-none items-center gap-3 py-2 pl-3 pr-9 text-gray-700 hover:bg-gray-100"
291
+ >
292
+ <input
293
+ type="checkbox"
294
+ name="select-all"
295
+ class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
296
+ :checked="selectAll"
297
+ />
298
+
299
+ <div class="flex items-center gap-2">
300
+ <c-icon
301
+ v-if="allOption && allOption.icon"
302
+ :class="allOption.icon.class"
303
+ :name="allOption.icon.name"
304
+ :type="allOption.icon.type"
305
+ :viewBox="allOption.icon.viewBox"
306
+ ></c-icon>
307
+ <span class="list-options block break-words font-normal">
308
+ Select all
309
+ </span>
310
+ </div>
311
+ </li>
312
+ <hr class="my-1" />
313
+ </template>
314
+ <!-- Groups Section -->
315
+ <li
316
+ v-for="(option, index) in organizedOptions.groups"
317
+ :key="`group-${option.id || index}`"
318
+ id="listbox-option-group"
319
+ role="option"
320
+ :ref="`option-group-${String(index)}`"
321
+ @mousedown="handleSelect($event, option)"
322
+ :class="option.isDisabled ? 'pointer-events-none' : ''"
323
+ >
324
+ <span
325
+ class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
326
+ :class="`group ${
327
+ option.isDisabled ? 'custom-disabled-state' : ''
328
+ } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
329
+ >
330
+ <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
331
+ <input
332
+ type="checkbox"
333
+ name="group"
334
+ class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
335
+ :checked="isChecked(option)"
336
+ />
337
+ </div>
338
+ <div class="flex min-w-0 flex-1 items-start gap-2">
339
+ <div v-if="option.showImage" class="shrink-0 pt-0.5">
340
+ <c-avatar
341
+ v-if="option.photo"
342
+ size="extraextraextrasmall"
343
+ :image="option.photo"
344
+ :rounded="true"
345
+ ></c-avatar>
346
+ <c-avatar
347
+ v-else
348
+ size="extraextraextrasmall"
349
+ :nameInitials="option.initials"
350
+ :rounded="true"
351
+ :isDynamicallyColored="coloredAvatars"
352
+ ></c-avatar>
353
+ </div>
354
+ <div
355
+ class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
356
+ v-if="option.showIcon && option.icon"
357
+ >
358
+ <c-icon
359
+ :class="option.icon.class"
360
+ :name="option.icon.name"
361
+ :type="option.icon.type"
362
+ :viewBox="option.icon.viewBox"
363
+ >
364
+ </c-icon>
365
+ </div>
366
+ <span
367
+ :class="[
368
+ option.photo || option.initials ? '' : 'text-left',
369
+ addCheckBox ? 'overflow-hidden' : '',
370
+ ]"
371
+ class="list-options min-w-0 flex-1 break-words font-normal"
372
+ v-bind:style="{
373
+ ...(shouldShowCustomFonts
374
+ ? { fontFamily: option[renderOptionName] }
375
+ : {}),
376
+ }"
377
+ >{{ option[renderOptionName] }}
378
+ </span>
379
+ </div>
380
+ <c-icon
381
+ v-if="!addCheckBox && isOptionSelected(option)"
382
+ name="check-outline-v2"
383
+ type="outline-v2"
384
+ class="h-4 w-4 shrink-0 self-center text-indigo-700"
385
+ />
386
+ </span>
387
+ </li>
388
+ <hr
389
+ v-if="
390
+ organizedOptions.groups.length > 0 &&
391
+ (organizedOptions.selectedValues.length > 0 ||
392
+ organizedOptions.unselectedValues.length > 0)
393
+ "
394
+ class="my-1"
395
+ />
396
+ <!-- Selected values section (non-groups) -->
397
+ <li
398
+ v-for="(option, index) in organizedOptions.selectedValues"
399
+ :key="`selected-value-${option.id || index}`"
400
+ :id="`listbox-option-selected-value-${String(index)}`"
401
+ role="option"
402
+ :ref="`option-selected-value-${String(index)}`"
403
+ @mousedown="handleSelect($event, option)"
404
+ :class="option.isDisabled ? 'pointer-events-none' : ''"
405
+ >
406
+ <span
407
+ class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
408
+ :class="`group ${
409
+ option.isDisabled ? 'custom-disabled-state' : ''
410
+ } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
411
+ >
412
+ <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
413
+ <input
414
+ type="checkbox"
415
+ name="value"
416
+ class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
417
+ :checked="preservedOrder ? isChecked(option) : true"
418
+ />
419
+ </div>
420
+ <div class="flex min-w-0 flex-1 items-start gap-2">
421
+ <div v-if="option.showImage" class="shrink-0 pt-0.5">
422
+ <c-avatar
423
+ v-if="option.photo"
424
+ size="extraextraextrasmall"
425
+ :image="option.photo"
426
+ :rounded="true"
427
+ ></c-avatar>
428
+ <c-avatar
429
+ v-else
430
+ size="extraextraextrasmall"
431
+ :nameInitials="option.initials"
432
+ :rounded="true"
433
+ :isDynamicallyColored="coloredAvatars"
434
+ ></c-avatar>
435
+ </div>
436
+ <div
437
+ class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
438
+ v-if="option.showIcon && option.icon"
439
+ >
440
+ <c-icon
441
+ :class="option.icon.class"
442
+ :name="option.icon.name"
443
+ :type="option.icon.type"
444
+ :viewBox="option.icon.viewBox"
445
+ >
446
+ </c-icon>
447
+ </div>
448
+ <span
449
+ :class="[
450
+ option.photo || option.initials ? '' : 'text-left',
451
+ addCheckBox ? 'overflow-hidden' : '',
452
+ ]"
453
+ class="list-options min-w-0 flex-1 break-words font-normal"
454
+ v-bind:style="{
455
+ ...(shouldShowCustomFonts
456
+ ? { fontFamily: option[renderOptionName] }
457
+ : {}),
458
+ }"
459
+ >{{ option[renderOptionName] }}
460
+ </span>
461
+ </div>
462
+ </span>
463
+ <hr v-if="option.isBorder" class="my-1" />
464
+ </li>
465
+ <hr
466
+ v-if="
467
+ organizedOptions.selectedValues.length > 0 &&
468
+ organizedOptions.unselectedValues.length > 0
469
+ "
470
+ class="my-1"
471
+ />
472
+ <!-- Unselected values section -->
473
+ <li
474
+ v-for="(option, index) in organizedOptions.unselectedValues"
475
+ :key="`unselected-value-${option.id || index}`"
476
+ :id="`listbox-option-unselected-value-${String(index)}`"
477
+ role="option"
478
+ :ref="`option-unselected-value-${String(index)}`"
479
+ @mousedown="handleSelect($event, option)"
480
+ :class="option.isDisabled ? 'pointer-events-none' : ''"
481
+ >
482
+ <span
483
+ class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
484
+ :class="`group ${
485
+ option.isDisabled ? 'custom-disabled-state' : ''
486
+ } ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
487
+ >
488
+ <div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
489
+ <input
490
+ type="checkbox"
491
+ name="value"
492
+ class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
493
+ :checked="selectAll || isChecked(option)"
494
+ />
495
+ </div>
496
+ <div class="flex min-w-0 flex-1 items-start gap-2">
497
+ <div v-if="option.showImage" class="shrink-0 pt-0.5">
498
+ <c-avatar
499
+ v-if="option.photo"
500
+ size="extraextraextrasmall"
501
+ :image="option.photo"
502
+ :rounded="true"
503
+ ></c-avatar>
504
+ <c-avatar
505
+ v-else
506
+ size="extraextraextrasmall"
507
+ :nameInitials="option.initials"
508
+ :rounded="true"
509
+ :isDynamicallyColored="coloredAvatars"
510
+ ></c-avatar>
511
+ </div>
512
+ <div
513
+ class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
514
+ v-if="option.showIcon && option.icon"
515
+ >
516
+ <c-icon
517
+ :class="option.icon.class"
518
+ :name="option.icon.name"
519
+ :type="option.icon.type"
520
+ :viewBox="option.icon.viewBox"
521
+ >
522
+ </c-icon>
523
+ </div>
524
+ <div
525
+ class="list-options flex min-w-0 flex-1 items-center gap-2 font-normal"
526
+ >
527
+ <span
528
+ :class="[
529
+ option.photo || option.initials ? '' : 'text-left',
530
+ addCheckBox ? 'overflow-hidden' : '',
531
+ ]"
532
+ class="min-w-0 break-words"
533
+ v-bind:style="{
534
+ ...(shouldShowCustomFonts
535
+ ? { fontFamily: option[renderOptionName] }
536
+ : {}),
537
+ }"
538
+ >{{ option[renderOptionName] }}</span
539
+ >
540
+ <c-tag
541
+ v-if="option.badge"
542
+ :label="option.badge.label"
543
+ :color="option.badge.color"
544
+ class="shrink-0"
545
+ ></c-tag>
546
+ </div>
547
+ </div>
548
+ <c-icon
549
+ v-if="!addCheckBox && isOptionSelected(option)"
550
+ name="check-outline-v2"
551
+ type="outline-v2"
552
+ class="h-4 w-4 shrink-0 self-center text-indigo-700"
553
+ />
554
+ </span>
555
+ <hr v-if="option.isBorder" class="my-1" />
556
+ </li>
557
+ <li
558
+ v-if="
559
+ organizedOptions.groups.length === 0 &&
560
+ organizedOptions.selectedValues.length === 0 &&
561
+ organizedOptions.unselectedValues.length === 0 &&
562
+ selectSearch &&
563
+ selectSearch.length > 0
564
+ "
565
+ class="px-3 py-2 text-sm text-gray-500"
566
+ >
567
+ No options found, try searching something else.
568
+ </li>
569
+ </ul>
570
+ <div
571
+ v-if="isFooter"
572
+ class="group cursor-pointer rounded-b-md bg-gray-50 p-3 ring-1 ring-gray-900 ring-opacity-5"
573
+ @mousedown="handleFooter"
574
+ >
575
+ <p class="text-sm text-indigo-700">
576
+ {{ footerText }}
577
+ </p>
578
+ </div>
579
+ <div
580
+ v-if="
581
+ showCreateOption &&
582
+ showCreateOptionAfterSearch &&
583
+ selectSearch &&
584
+ selectSearch.trim()
585
+ "
586
+ class="sticky bottom-0 z-10 flex min-h-[44px] items-start gap-3 rounded-b-md bg-white px-3 py-2 shadow-lg ring-1 ring-gray-900 ring-opacity-5"
587
+ >
588
+ <span class="word-break flex-1 text-sm text-gray-700">{{
589
+ selectSearch
590
+ }}</span>
591
+
592
+ <button
593
+ type="button"
594
+ :id="id + '_create_button'"
595
+ @mousedown="handleCreateOption($event)"
596
+ class="flex items-center gap-1 text-sm text-indigo-700"
597
+ >
598
+ <c-icon
599
+ type="solid"
600
+ name="plus-circle-solid-v2"
601
+ class="h-5 w-5 text-indigo-600"
602
+ viewBox="0 0 24 24"
603
+ ></c-icon>
604
+ Create
605
+ </button>
606
+ </div>
607
+ </div>
608
+ </transition>
609
+ </div>
610
+ <p
611
+ v-if="helpText && isValidate == true"
612
+ class="mt-2 text-left text-sm text-gray-500"
613
+ >
614
+ {{ helpText }}
615
+ </p>
616
+ <!-- validation error message -->
617
+ <p
618
+ v-if="!isValidate && errorMessage"
619
+ class="mt-2 text-left text-sm text-red-600"
620
+ >
621
+ {{ errorMessage }}
622
+ </p>
623
+ </div>
624
+ </template>
625
+ <script>
626
+ import { isNumber, isEmpty } from "lodash-es";
627
+ import CIcon from "../CIcon/CIcon.vue";
628
+ import { getActionID } from "../../helper";
629
+ import CTag from "../CTag/CTag.vue";
630
+ import CAvatar from "../CAvatar/CAvatar.vue";
631
+ export default {
632
+ name: "CSelect",
633
+ components: {
634
+ CIcon,
635
+ CTag,
636
+ CAvatar,
637
+ },
638
+ props: {
639
+ // id of selectpicker
640
+ id: {
641
+ type: String,
642
+ },
643
+ // label of selectpicker
644
+ label: {
645
+ type: String,
646
+ },
647
+ // action button of selectpicker
648
+ actionBtn: {
649
+ type: String,
650
+ default: null,
651
+ },
652
+ // placeholder in selectpicker
653
+ placeholder: {
654
+ type: String,
655
+ },
656
+ // list to render in dropdown
657
+ options: {
658
+ type: Array,
659
+ required: true,
660
+ },
661
+ // show the avatars with different colors
662
+ coloredAvatars: {
663
+ type: Boolean,
664
+ },
665
+ // text below dropdown to describe more about the field
666
+ helpText: {
667
+ type: String,
668
+ },
669
+ // implement a fixed btn at the bottom of dropdown
670
+ isFooter: {
671
+ type: Boolean,
672
+ },
673
+ // style input field
674
+ inputStyle: {
675
+ type: String,
676
+ },
677
+ // footer btn text
678
+ footerText: {
679
+ type: String,
680
+ },
681
+ // text adjacent to label of dropdown to provide hint about field
682
+ hint: { type: String },
683
+ // to show if image is present along with dropdown option
684
+ showImage: { type: Boolean },
685
+ // whether the select field is mandatory or not
686
+ isRequired: {
687
+ type: Boolean,
688
+ },
689
+ // validation is passed or not
690
+ isValidate: { type: Boolean, default: true },
691
+ // will truncate the text in input field
692
+ selectedOptionStyles: {
693
+ type: String,
694
+ },
695
+ // validation error message
696
+ errorMessage: {
697
+ type: String,
698
+ },
699
+ // perform action on first option in dropdown
700
+ addAction: { type: Object },
701
+ // icons in dropdown list
702
+ icon: { type: Object },
703
+ // value to set as default option in dropdown
704
+ value: { type: [Object, String, Array], default: null },
705
+ // type of dropdown - gray,tertiary, blue or default
706
+ type: {
707
+ type: String,
708
+ },
709
+ // name of the field in array to that contains text to render in dropdown
710
+ renderOptionName: {
711
+ type: String,
712
+ default: "option",
713
+ },
714
+ // whether to disable the selectpicker
715
+ isDisabled: {
716
+ type: Boolean,
717
+ default: false,
718
+ },
719
+ inputClasses: {
720
+ type: String,
721
+ default: "",
722
+ },
723
+ optionClasses: {
724
+ type: String,
725
+ default: "",
726
+ },
727
+ // customised width for the dropdown
728
+ customWidth: {
729
+ type: String,
730
+ default: null,
731
+ },
732
+ customBorderRadius: {
733
+ type: String,
734
+ default: "rounded-md",
735
+ },
736
+ addCheckBox: {
737
+ type: Boolean,
738
+ default: false,
739
+ },
740
+ selectAll: {
741
+ type: Boolean,
742
+ default: false,
743
+ },
744
+ selectCheckboxes: {
745
+ type: Boolean,
746
+ default: false,
747
+ },
748
+ headerSwitch: {
749
+ type: Object,
750
+ default: null,
751
+ },
752
+ useSticky: {
753
+ type: Boolean,
754
+ default: false,
755
+ },
756
+ showCloseButton: {
757
+ type: Boolean,
758
+ default: false,
759
+ },
760
+ showCreateOption: {
761
+ type: Boolean,
762
+ default: false,
763
+ },
764
+ shouldShowCustomFonts: {
765
+ type: Boolean,
766
+ default: false,
767
+ },
768
+ customFontNames: {
769
+ type: Array,
770
+ default: () => [],
771
+ },
772
+ inputContainerClass: {
773
+ type: String,
774
+ default: "",
775
+ },
776
+ },
777
+ data() {
778
+ return {
779
+ toggleDropdown: false,
780
+ selectedValue: null,
781
+ showFocus: false,
782
+ selectSearch: null,
783
+ renderOptions: this.options,
784
+ selectedValuesArray: [],
785
+ isToggle: 0,
786
+ shouldOpenAbove: false,
787
+ // Frozen order while dropdown is open - prevents UI shift on selection
788
+ preservedOrder: null,
789
+ };
790
+ },
791
+ computed: {
792
+ // Non-credential-like, per-instance name so browser password managers don't
793
+ // classify the search box as a username/email field and autofill it.
794
+ searchInputName() {
795
+ return `c-select-search-${this.id || this._uid}`;
796
+ },
797
+ classes() {
798
+ return {
799
+ "text-gray-900 border border-gray-300 hover:bg-gray-200 bg-gray-100 focus:ring-2 focus:border-gray-200 focus:ring-gray-200 focus:ring-offset-2 shadow-sm":
800
+ this.type == "gray",
801
+ "bg-white border border-gray-300 hover:bg-indigo-100 text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-200":
802
+ this.type == "tertiary",
803
+ "bg-white text-indigo-700 border-none focus:outline-none":
804
+ this.type == "indigo",
805
+ "border border-gray-300 bg-white shadow-sm focus:outline-none focus:border-gray-300 focus:ring-indigo-600 focus:ring-2 focus:ring-offset-2":
806
+ this.type == null,
807
+ };
808
+ },
809
+ allOption() {
810
+ if (
811
+ this.options &&
812
+ this.options.length > 0 &&
813
+ this.options[0] &&
814
+ this.options[0].id === "all"
815
+ ) {
816
+ return this.options[0];
817
+ }
818
+ return null;
819
+ },
820
+ organizedOptions() {
821
+ if (!this.addCheckBox) {
822
+ const filteredOptions = this.renderOptions.filter(
823
+ (option) => option.id !== "all"
824
+ );
825
+ return {
826
+ groups: [],
827
+ selectedValues: [],
828
+ unselectedValues: filteredOptions,
829
+ };
830
+ }
831
+
832
+ // While dropdown is open and not searching, preserve order to prevent UI shift on selection
833
+ if (
834
+ this.preservedOrder &&
835
+ (!this.selectSearch || this.selectSearch.trim() === "")
836
+ ) {
837
+ return this.preservedOrder;
838
+ }
839
+
840
+ return this.computeOrganizedOptions();
841
+ },
842
+ showCreateOptionAfterSearch() {
843
+ if (!this.showCreateOption) return false;
844
+
845
+ const term = (this.selectSearch || "").trim().toLowerCase();
846
+ if (!term) return false;
847
+
848
+ const hasExactMatch = this.options.some((option) => {
849
+ const label = option[this.renderOptionName];
850
+ return label && String(label).toLowerCase() === term;
851
+ });
852
+
853
+ return !hasExactMatch;
854
+ },
855
+ dropdownSelectorIconClass() {
856
+ return this.type === "indigo" ? "text-indigo-600" : "text-gray-400";
857
+ },
858
+ hasSelectedValue() {
859
+ const val = this.selectedValue;
860
+ if (val === null || val === undefined || val === "") {
861
+ return false;
862
+ }
863
+ if (typeof val === "object") {
864
+ return !isEmpty(val);
865
+ }
866
+ return true;
867
+ },
868
+ selectedValueClass() {
869
+ if (this.hasSelectedValue) {
870
+ if (this.isDisabled) {
871
+ return "text-gray-500";
872
+ }
873
+
874
+ if (this.type === "indigo") {
875
+ return "text-indigo-600";
876
+ }
877
+ return "text-gray-700";
878
+ }
879
+ return "text-gray-500";
880
+ },
881
+ },
882
+ methods: {
883
+ isEmpty,
884
+ getActionIDFn(name) {
885
+ return getActionID(name, this.id);
886
+ },
887
+ checkAvailableSpace() {
888
+ if (!this.$refs.inputContainer) return false;
889
+
890
+ const container = this.$refs.inputContainer;
891
+ const rect = container.getBoundingClientRect();
892
+ const viewportHeight = window.innerHeight;
893
+ const bottomSpace = viewportHeight - rect.bottom;
894
+ const componentHeight = 320;
895
+
896
+ // Check if bottom space is less than 80% the component height if there is no footer
897
+ return (
898
+ bottomSpace <
899
+ (this.isFooter ? componentHeight : (componentHeight * 4) / 5)
900
+ );
901
+ },
902
+ getDropdownPosition() {
903
+ this.shouldOpenAbove = this.checkAvailableSpace();
904
+ return this.shouldOpenAbove ? "bottom-full" : "top-full";
905
+ },
906
+ getDropdownPosition2() {
907
+ return this.shouldOpenAbove ? "bottom-full" : "";
908
+ },
909
+ getDropdownShadow() {
910
+ if (this.shouldOpenAbove) {
911
+ return "shadow-[0_-10px_15px_-3px_rgba(0,0,0,0.1),0_-4px_6px_-4px_rgba(0,0,0,0.1)]";
912
+ } else {
913
+ return "shadow-lg";
914
+ }
915
+ },
916
+ handleCrossClick(event) {
917
+ event.preventDefault();
918
+ this.$emit("hide-single-select-dropdown");
919
+ },
920
+ handleInputClick() {
921
+ this.toggleDropdown = !this.toggleDropdown;
922
+ if (!this.toggleDropdown) {
923
+ this.selectSearch = "";
924
+ this.$nextTick(() => {
925
+ if (this.$refs["c-select-input"]) {
926
+ this.$refs["c-select-input"].focus();
927
+ }
928
+ });
929
+ }
930
+ },
931
+ handleInputFocus() {
932
+ if (this.type == "tertiary") {
933
+ this.showFocus = true;
934
+ }
935
+ },
936
+ handleSelect(event, option) {
937
+ this.selectSearch = null;
938
+ this.selectedValue = option[this.renderOptionName];
939
+ if (this.addCheckBox) {
940
+ // Freeze current display order BEFORE updating selection to prevent UI shift
941
+ const currentDisplay = this.organizedOptions;
942
+ this.preservedOrder = {
943
+ groups: [...currentDisplay.groups],
944
+ selectedValues: [...currentDisplay.selectedValues],
945
+ unselectedValues: [...currentDisplay.unselectedValues],
946
+ };
947
+ if (this.getIndex(option) == -1) {
948
+ this.selectedValuesArray = [...this.selectedValuesArray, option];
949
+ } else {
950
+ this.selectedValuesArray = this.selectedValuesArray.filter(
951
+ (item) => option.id !== item.id
952
+ );
953
+ }
954
+ this.$emit("onChangeChecked", this.selectedValuesArray, option);
955
+ }
956
+
957
+ this.$emit("onChangeValue", option);
958
+ if (!this.addCheckBox) {
959
+ this.toggleDropdown = false;
960
+ }
961
+ this.type === "tertiary" ? (this.showFocus = false) : "";
962
+ this.renderOptions = this.options;
963
+ },
964
+ handleFooter() {
965
+ this.$emit("handleFooter");
966
+ },
967
+ actionEvent(event) {
968
+ this.$emit("listAction", event);
969
+ },
970
+ actionBtnEvent(event) {
971
+ this.$emit("listAction", event);
972
+ },
973
+ handleElementBlur() {
974
+ this.toggleDropdown = false;
975
+ if (this.addCheckBox) {
976
+ this.$emit("onClickOutside", this.selectedValuesArray);
977
+ }
978
+ this.selectSearch = null;
979
+ this.renderOptions = this.options;
980
+ },
981
+ handleHeaderSwitch(value) {
982
+ this.$emit("handleHeaderSwitch", value);
983
+ },
984
+ //this prevents the blur to be called when the user clicks on the scrollbar
985
+ handlePreventBlur(event) {
986
+ event.preventDefault();
987
+ },
988
+ search() {
989
+ this.selectedValue = null;
990
+ // if the search term is empty space, then set the render options to empty array
991
+ if (
992
+ this.selectSearch !== null &&
993
+ this.selectSearch.length > 0 &&
994
+ this.selectSearch.trim() === ""
995
+ ) {
996
+ this.renderOptions = [];
997
+ this.$emit("search-term", this.selectSearch);
998
+ return;
999
+ }
1000
+ if (
1001
+ !this.selectSearch ||
1002
+ this.selectSearch == "" ||
1003
+ this.selectSearch.trim() == ""
1004
+ ) {
1005
+ this.renderOptions = this.options;
1006
+ this.$emit("search-term", this.selectSearch);
1007
+ return;
1008
+ }
1009
+ let options = [...this.options];
1010
+ this.renderOptions = options.filter((option) => {
1011
+ isNumber(option[this.renderOptionName])
1012
+ ? (option[this.renderOptionName] =
1013
+ option[this.renderOptionName].toString())
1014
+ : "";
1015
+ const optionName = String(
1016
+ option[this.renderOptionName] || ""
1017
+ ).toLowerCase();
1018
+ const searchTerm = this.selectSearch.trim().toLowerCase();
1019
+ return optionName.includes(searchTerm);
1020
+ });
1021
+
1022
+ if (this.renderOptions.length === 0) {
1023
+ this.$emit("search-term", this.selectSearch.trim());
1024
+ }
1025
+ },
1026
+ async scrollOptionIntoView(refName) {
1027
+ await this.$nextTick();
1028
+ const list = this.$refs.optionsList;
1029
+ const optionEl = (this.$refs[refName] || [])[0];
1030
+ if (!list || !optionEl) return;
1031
+
1032
+ const top = optionEl.offsetTop;
1033
+ const bottom = top + optionEl.offsetHeight;
1034
+ const viewTop = list.scrollTop;
1035
+ const viewBottom = viewTop + list.clientHeight;
1036
+
1037
+ const target =
1038
+ top < viewTop
1039
+ ? top
1040
+ : bottom > viewBottom
1041
+ ? bottom - list.clientHeight
1042
+ : viewTop;
1043
+
1044
+ if (target !== viewTop) {
1045
+ list.scrollTo({ top: target, behavior: "smooth" });
1046
+ }
1047
+ },
1048
+ isOptionSelected(option) {
1049
+ /*
1050
+ * Compares value to option by id when both exist,
1051
+ * otherwise by renderOptionName / string value.
1052
+ */
1053
+ if (option == null) {
1054
+ return false;
1055
+ }
1056
+
1057
+ const v = this.value;
1058
+ if (v === null) {
1059
+ return false;
1060
+ }
1061
+
1062
+ if (typeof v === "object" && v !== null) {
1063
+ if (v.id !== undefined && option.id !== undefined) {
1064
+ return v.id === option.id;
1065
+ }
1066
+
1067
+ const vLabel = v[this.renderOptionName];
1068
+ if (vLabel !== undefined && vLabel !== null) {
1069
+ return String(vLabel) === String(option[this.renderOptionName]);
1070
+ }
1071
+ }
1072
+
1073
+ return String(v) === String(option[this.renderOptionName]);
1074
+ },
1075
+ getIndex(option) {
1076
+ return this.selectedValuesArray.findIndex((item) => item.id == option.id);
1077
+ },
1078
+ isChecked(option) {
1079
+ return this.selectedValuesArray.findIndex(
1080
+ (item) => item.id == option.id
1081
+ ) == -1
1082
+ ? false
1083
+ : true;
1084
+ },
1085
+ handleCreateOption(event) {
1086
+ event.preventDefault();
1087
+ this.toggleDropdown = false;
1088
+ this.$emit("handleCreateOption", {
1089
+ event,
1090
+ searchText: this.selectSearch.trim(),
1091
+ });
1092
+ this.selectSearch = null;
1093
+ this.renderOptions = this.options;
1094
+
1095
+ const cSelectInputRef = this.$refs["c-select-input"];
1096
+ this.$nextTick(() => {
1097
+ cSelectInputRef && cSelectInputRef.blur();
1098
+ });
1099
+ },
1100
+ handleKeydown(event) {
1101
+ if (event.key === "Enter") {
1102
+ event.preventDefault();
1103
+ event.stopPropagation();
1104
+
1105
+ if (
1106
+ this.showCreateOption &&
1107
+ this.showCreateOptionAfterSearch &&
1108
+ this.selectSearch &&
1109
+ this.selectSearch.trim()
1110
+ ) {
1111
+ this.handleCreateOption(event);
1112
+ }
1113
+ }
1114
+ },
1115
+ async handleLoadFont() {
1116
+ const customFontNames = this.customFontNames;
1117
+ const googleParam = customFontNames
1118
+ .map((f) => `family=${f.replace(/ /g, "+")}`)
1119
+ .join("&");
1120
+
1121
+ const link = document.createElement("link");
1122
+ link.rel = "stylesheet";
1123
+ link.href = `https://fonts.googleapis.com/css2?${googleParam}&display=swap`;
1124
+ link.setAttribute("data-fonts", customFontNames.join(","));
1125
+ document.head.appendChild(link);
1126
+
1127
+ await document.fonts.ready;
1128
+ },
1129
+ isGroup(option) {
1130
+ return option.type && option.type.includes("group");
1131
+ },
1132
+ computeOrganizedOptions() {
1133
+ const selectedIds = new Set(
1134
+ this.selectedValuesArray.map((item) => item.id)
1135
+ );
1136
+
1137
+ const groups = [];
1138
+ const selectedValues = [];
1139
+ const unselectedValues = [];
1140
+
1141
+ // Filter out the "all" option from renderOptions
1142
+ this.renderOptions
1143
+ .filter((option) => option.id !== "all")
1144
+ .forEach((option) => {
1145
+ if (this.isGroup(option)) {
1146
+ // Groups stay in their own section regardless of selection
1147
+ groups.push(option);
1148
+ } else {
1149
+ // Values are separated by selection state
1150
+ if (selectedIds.has(option.id)) {
1151
+ selectedValues.push(option);
1152
+ } else {
1153
+ unselectedValues.push(option);
1154
+ }
1155
+ }
1156
+ });
1157
+
1158
+ // Sort by renderOptionName (guard against undefined a/b or missing renderOptionName)
1159
+ const getSortKey = (item) =>
1160
+ String((item && item[this.renderOptionName]) || "").toLowerCase();
1161
+
1162
+ selectedValues.sort((a, b) => getSortKey(a).localeCompare(getSortKey(b)));
1163
+ unselectedValues.sort((a, b) =>
1164
+ getSortKey(a).localeCompare(getSortKey(b))
1165
+ );
1166
+
1167
+ return {
1168
+ groups,
1169
+ selectedValues,
1170
+ unselectedValues,
1171
+ };
1172
+ },
1173
+ },
1174
+ watch: {
1175
+ value() {
1176
+ this.selectedValue =
1177
+ this.value !== null && this.value[this.renderOptionName]
1178
+ ? this.value[this.renderOptionName]
1179
+ : this.value;
1180
+ if (this.addCheckBox) {
1181
+ this.selectedValuesArray = this.value ? this.value : [];
1182
+ }
1183
+ },
1184
+ options() {
1185
+ this.renderOptions = this.options;
1186
+ },
1187
+ toggleDropdown(newValue) {
1188
+ if (newValue) {
1189
+ this.checkAvailableSpace();
1190
+
1191
+ this.preservedOrder = null;
1192
+
1193
+ const index = this.renderOptions.findIndex(
1194
+ (option) => option[this.renderOptionName] === this.selectedValue
1195
+ );
1196
+ if (index !== -1) {
1197
+ const refName = `option-${String(index)}`;
1198
+ this.scrollOptionIntoView(refName);
1199
+ }
1200
+ } else {
1201
+ this.preservedOrder = null;
1202
+ }
1203
+ },
1204
+ },
1205
+ mounted() {
1206
+ this.selectedValue =
1207
+ this.value !== null && this.value[this.renderOptionName]
1208
+ ? this.value[this.renderOptionName]
1209
+ : this.value;
1210
+ if (this.addCheckBox) {
1211
+ this.selectedValuesArray = this.value ? this.value : [];
1212
+ }
1213
+
1214
+ if (this.shouldShowCustomFonts && this.customFontNames.length > 0) {
1215
+ this.handleLoadFont();
1216
+ }
1217
+ },
1218
+ };
1219
+ </script>
1220
+
1221
+ <style scoped>
1222
+ .word-break {
1223
+ word-break: break-word;
1224
+ }
1225
+ </style>