classcard-ui 0.2.1485 → 0.2.1486

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