@aziontech/webkit 0.0.1

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 (105) hide show
  1. package/package.json +46 -0
  2. package/src/core/field-auto-complete/field-auto-complete.vue +121 -0
  3. package/src/core/field-auto-complete/field-auto-complete.vue.d.ts +107 -0
  4. package/src/core/field-auto-complete/field-auto-complete.vue.d.ts.map +1 -0
  5. package/src/core/field-auto-complete/package.json +11 -0
  6. package/src/core/field-checkbox-block/field-checkbox-block.vue +106 -0
  7. package/src/core/field-checkbox-block/field-checkbox-block.vue.d.ts +111 -0
  8. package/src/core/field-checkbox-block/field-checkbox-block.vue.d.ts.map +1 -0
  9. package/src/core/field-checkbox-block/package.json +11 -0
  10. package/src/core/field-dropdown/field-dropdown.vue +240 -0
  11. package/src/core/field-dropdown/field-dropdown.vue.d.ts +203 -0
  12. package/src/core/field-dropdown/field-dropdown.vue.d.ts.map +1 -0
  13. package/src/core/field-dropdown/package.json +11 -0
  14. package/src/core/field-dropdown-icon/field-dropdown-icon.vue +129 -0
  15. package/src/core/field-dropdown-icon/field-dropdown-icon.vue.d.ts +111 -0
  16. package/src/core/field-dropdown-icon/field-dropdown-icon.vue.d.ts.map +1 -0
  17. package/src/core/field-dropdown-icon/package.json +11 -0
  18. package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue +631 -0
  19. package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue.d.ts +196 -0
  20. package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue.d.ts.map +1 -0
  21. package/src/core/field-dropdown-lazy-loader/package.json +11 -0
  22. package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue +426 -0
  23. package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue.d.ts +158 -0
  24. package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue.d.ts.map +1 -0
  25. package/src/core/field-dropdown-lazy-loader-dynamic/package.json +11 -0
  26. package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue +527 -0
  27. package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue.d.ts +203 -0
  28. package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue.d.ts.map +1 -0
  29. package/src/core/field-dropdown-lazy-loader-with-filter/package.json +11 -0
  30. package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue +439 -0
  31. package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue.d.ts +162 -0
  32. package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue.d.ts.map +1 -0
  33. package/src/core/field-dropdown-multi-select-lazy-loader/package.json +11 -0
  34. package/src/core/field-group-checkbox/field-group-checkbox.vue +102 -0
  35. package/src/core/field-group-checkbox/field-group-checkbox.vue.d.ts +87 -0
  36. package/src/core/field-group-checkbox/field-group-checkbox.vue.d.ts.map +1 -0
  37. package/src/core/field-group-checkbox/package.json +11 -0
  38. package/src/core/field-group-radio/field-group-radio.vue +149 -0
  39. package/src/core/field-group-radio/field-group-radio.vue.d.ts +99 -0
  40. package/src/core/field-group-radio/field-group-radio.vue.d.ts.map +1 -0
  41. package/src/core/field-group-radio/package.json +11 -0
  42. package/src/core/field-group-switch/field-group-switch.vue +121 -0
  43. package/src/core/field-group-switch/field-group-switch.vue.d.ts +87 -0
  44. package/src/core/field-group-switch/field-group-switch.vue.d.ts.map +1 -0
  45. package/src/core/field-group-switch/package.json +11 -0
  46. package/src/core/field-input-group/field-input-group.vue +111 -0
  47. package/src/core/field-input-group/field-input-group.vue.d.ts +87 -0
  48. package/src/core/field-input-group/field-input-group.vue.d.ts.map +1 -0
  49. package/src/core/field-input-group/package.json +11 -0
  50. package/src/core/field-multi-select/field-multi-select.vue +167 -0
  51. package/src/core/field-multi-select/field-multi-select.vue.d.ts +136 -0
  52. package/src/core/field-multi-select/field-multi-select.vue.d.ts.map +1 -0
  53. package/src/core/field-multi-select/package.json +11 -0
  54. package/src/core/field-number/field-number.vue +144 -0
  55. package/src/core/field-number/field-number.vue.d.ts +132 -0
  56. package/src/core/field-number/field-number.vue.d.ts.map +1 -0
  57. package/src/core/field-number/package.json +11 -0
  58. package/src/core/field-phone-number/field-phone-number.vue +151 -0
  59. package/src/core/field-phone-number/field-phone-number.vue.d.ts +76 -0
  60. package/src/core/field-phone-number/field-phone-number.vue.d.ts.map +1 -0
  61. package/src/core/field-phone-number/package.json +11 -0
  62. package/src/core/field-phone-number-country/field-phone-number-country.vue +60 -0
  63. package/src/core/field-phone-number-country/field-phone-number-country.vue.d.ts +18 -0
  64. package/src/core/field-phone-number-country/field-phone-number-country.vue.d.ts.map +1 -0
  65. package/src/core/field-phone-number-country/package.json +11 -0
  66. package/src/core/field-pick-list/field-pick-list.vue +285 -0
  67. package/src/core/field-pick-list/field-pick-list.vue.d.ts +43 -0
  68. package/src/core/field-pick-list/field-pick-list.vue.d.ts.map +1 -0
  69. package/src/core/field-pick-list/package.json +11 -0
  70. package/src/core/field-radio-block/field-radio-block.vue +108 -0
  71. package/src/core/field-radio-block/field-radio-block.vue.d.ts +115 -0
  72. package/src/core/field-radio-block/field-radio-block.vue.d.ts.map +1 -0
  73. package/src/core/field-radio-block/package.json +11 -0
  74. package/src/core/field-switch/field-switch.vue +41 -0
  75. package/src/core/field-switch/field-switch.vue.d.ts +33 -0
  76. package/src/core/field-switch/field-switch.vue.d.ts.map +1 -0
  77. package/src/core/field-switch/package.json +11 -0
  78. package/src/core/field-switch-block/field-switch-block.vue +123 -0
  79. package/src/core/field-switch-block/field-switch-block.vue.d.ts +123 -0
  80. package/src/core/field-switch-block/field-switch-block.vue.d.ts.map +1 -0
  81. package/src/core/field-switch-block/package.json +11 -0
  82. package/src/core/field-text/field-text.vue +128 -0
  83. package/src/core/field-text/field-text.vue.d.ts +108 -0
  84. package/src/core/field-text/field-text.vue.d.ts.map +1 -0
  85. package/src/core/field-text/package.json +11 -0
  86. package/src/core/field-text-area/field-text-area.vue +178 -0
  87. package/src/core/field-text-area/field-text-area.vue.d.ts +142 -0
  88. package/src/core/field-text-area/field-text-area.vue.d.ts.map +1 -0
  89. package/src/core/field-text-area/package.json +11 -0
  90. package/src/core/field-text-icon/field-text-icon.vue +127 -0
  91. package/src/core/field-text-icon/field-text-icon.vue.d.ts +100 -0
  92. package/src/core/field-text-icon/field-text-icon.vue.d.ts.map +1 -0
  93. package/src/core/field-text-icon/package.json +11 -0
  94. package/src/core/field-text-password/field-text-password.vue +127 -0
  95. package/src/core/field-text-password/field-text-password.vue.d.ts +108 -0
  96. package/src/core/field-text-password/field-text-password.vue.d.ts.map +1 -0
  97. package/src/core/field-text-password/package.json +11 -0
  98. package/src/core/label/label.vue +35 -0
  99. package/src/core/label/label.vue.d.ts +24 -0
  100. package/src/core/label/label.vue.d.ts.map +1 -0
  101. package/src/core/label/package.json +11 -0
  102. package/src/core/selector-block/package.json +11 -0
  103. package/src/core/selector-block/selector-block.vue +128 -0
  104. package/src/core/selector-block/selector-block.vue.d.ts +99 -0
  105. package/src/core/selector-block/selector-block.vue.d.ts.map +1 -0
@@ -0,0 +1,631 @@
1
+ <template>
2
+ <LabelBlock
3
+ v-if="props.label"
4
+ :for="props.name"
5
+ :label="props.label"
6
+ :isRequired="$attrs.required"
7
+ :data-testid="customTestId.label"
8
+ />
9
+ <Dropdown
10
+ appendTo="self"
11
+ :id="name"
12
+ :name="props.name"
13
+ :loading="loading"
14
+ v-model="inputValue"
15
+ :options="data"
16
+ :optionLabel="props.optionLabel"
17
+ :optionDisabled="props.optionDisabled"
18
+ :optionValue="props.optionValue"
19
+ :optionGroupLabel="props.optionGroupLabel"
20
+ :optionGroupChildren="props.optionGroupChildren"
21
+ :placeholder="props.placeholder"
22
+ :showClear="props.enableClearOption"
23
+ @change="emitChange"
24
+ @blur="emitBlur"
25
+ :class="errorMessage ? 'p-invalid' : ''"
26
+ v-bind="$attrs"
27
+ class="w-full"
28
+ :pt="{
29
+ clearIcon: {
30
+ 'data-testid': customTestId.clearIcon
31
+ },
32
+ filterInput: {
33
+ class: 'w-full',
34
+ 'data-testid': customTestId.filterInput
35
+ },
36
+ trigger: {
37
+ 'data-testid': customTestId.trigger
38
+ },
39
+ loadingIcon: {
40
+ 'data-testid': customTestId.loadingIcon
41
+ }
42
+ }"
43
+ :data-testid="customTestId.dropdown"
44
+ :virtualScrollerOptions="VIRTUAL_SCROLLER_CONFIG"
45
+ >
46
+ <template
47
+ v-if="enableCustomLabel"
48
+ #value="slotProps"
49
+ >
50
+ <span
51
+ class="flex align-items-center gap-2 max-w-full"
52
+ :data-testid="customTestId.value"
53
+ >
54
+ <i
55
+ v-if="showIcon"
56
+ :class="`pi ${iconSelected} ${applyIconColor(iconSelected)}`"
57
+ ></i>
58
+ <span
59
+ class="truncate max-w-full"
60
+ :title="getLabelBySelectedValue(slotProps.value)"
61
+ >
62
+ {{ getLabelBySelectedValue(slotProps.value) }}
63
+ </span>
64
+ </span>
65
+ </template>
66
+ <template #option="slotProps">
67
+ <div class="flex align-items-center gap-2">
68
+ <i
69
+ v-if="slotProps.option.icon"
70
+ :class="`pi ${slotProps.option.icon} ${applyIconColor(slotProps.option.icon)}`"
71
+ ></i>
72
+ <span
73
+ v-else-if="!slotProps.option.icon && showIcon"
74
+ class="w-4"
75
+ ></span>
76
+ <div>{{ slotProps.option.name }}</div>
77
+ </div>
78
+ </template>
79
+
80
+ <template #header>
81
+ <div class="p-2 flex">
82
+ <div class="p-inputgroup">
83
+ <InputText
84
+ type="text"
85
+ v-model="search"
86
+ placeholder="Search"
87
+ class="w-full rounded-r-none"
88
+ ref="focusSearch"
89
+ :data-testid="customTestId.search"
90
+ />
91
+ <span
92
+ class="p-inputgroup-addon"
93
+ @click="searchFilter"
94
+ >
95
+ <i class="pi pi-search"></i>
96
+ </span>
97
+ </div>
98
+ </div>
99
+ </template>
100
+
101
+ <template #footer>
102
+ <slot name="footer" />
103
+ </template>
104
+ </Dropdown>
105
+
106
+ <small
107
+ v-if="errorMessage"
108
+ :data-testid="customTestId.error"
109
+ class="p-error text-xs font-normal leading-tight"
110
+ >
111
+ {{ errorMessage }}
112
+ </small>
113
+ <small
114
+ class="text-xs text-color-secondary font-normal leading-5"
115
+ :data-testid="customTestId.description"
116
+ v-if="props.description || hasDescriptionSlot"
117
+ >
118
+ <slot name="description">
119
+ {{ props.description }}
120
+ </slot>
121
+ </small>
122
+ </template>
123
+
124
+ <script setup>
125
+ import Dropdown from 'primevue/dropdown'
126
+ import InputText from 'primevue/inputtext'
127
+ import { useField } from 'vee-validate'
128
+ import { computed, toRef, useSlots, useAttrs, ref, onMounted, watchEffect, watch } from 'vue'
129
+ import { watchDebounced } from '@vueuse/core'
130
+ import LabelBlock from '../label'
131
+
132
+ const props = defineProps({
133
+ value: {
134
+ type: [String, Number],
135
+ default: ''
136
+ },
137
+ name: {
138
+ type: String,
139
+ required: true
140
+ },
141
+ label: {
142
+ type: String,
143
+ default: ''
144
+ },
145
+ placeholder: {
146
+ type: String,
147
+ default: ''
148
+ },
149
+ description: {
150
+ type: String,
151
+ default: ''
152
+ },
153
+ optionLabel: {
154
+ type: String,
155
+ default: ''
156
+ },
157
+ optionValue: {
158
+ type: String,
159
+ default: ''
160
+ },
161
+ moreOptions: {
162
+ type: Array,
163
+ default: null
164
+ },
165
+ optionDisabled: {
166
+ type: [String, Function],
167
+ default: ''
168
+ },
169
+ service: {
170
+ type: Function,
171
+ required: true
172
+ },
173
+ loadService: {
174
+ type: Function,
175
+ required: true
176
+ },
177
+ enableWorkaroundLabelToDisabledOptions: {
178
+ type: Boolean,
179
+ default: false
180
+ },
181
+ enableClearOption: {
182
+ type: Boolean,
183
+ default: false
184
+ },
185
+ disableEmitFirstRender: {
186
+ type: Boolean,
187
+ default: false
188
+ },
189
+ optionGroupLabel: {
190
+ type: String,
191
+ default: ''
192
+ },
193
+ optionGroupChildren: {
194
+ type: String,
195
+ default: ''
196
+ },
197
+ defaultPosition: {
198
+ type: Number,
199
+ default: 0
200
+ },
201
+ showIcon: {
202
+ type: Boolean,
203
+ default: false
204
+ },
205
+ iconColor: {
206
+ type: Object,
207
+ default: () => ({})
208
+ }
209
+ })
210
+
211
+ const emit = defineEmits(['onBlur', 'onChange', 'onSelectOption', 'onAccessDenied'])
212
+
213
+ const PAGE_INCREMENT = 1
214
+ const PAGE_SIZE = 100
215
+ const INITIAL_PAGE = 1
216
+ const SEARCH_DEBOUNCE = 500
217
+ const SEARCH_MAX_WAIT = 1000
218
+ const NUMBER_OF_CHARACTERS_MIN_FOR_SEARCH = 3
219
+ const NUMBER_OF_CHARACTERS_TO_RESET_SEARCH = 0
220
+ const PERMISSION_DENIED = 'You do not have permission'
221
+ const hasNoPermission = ref(false)
222
+ const iconSelected = ref('')
223
+ const name = toRef(props, 'name')
224
+ const slots = useSlots()
225
+ const data = ref([])
226
+ const loading = ref(false)
227
+ const totalCount = ref(0)
228
+ const page = ref(INITIAL_PAGE)
229
+ const search = ref('')
230
+ const focusSearch = ref(null)
231
+ const disableEmitInit = ref(props.disableEmitFirstRender)
232
+ const alreadyLoadedData = ref(false)
233
+
234
+ onMounted(async () => {
235
+ await fetchData()
236
+ })
237
+
238
+ const hasDescriptionSlot = !!slots.description
239
+ const { value: inputValue, errorMessage } = useField(name, undefined, {
240
+ initialValue: props.value
241
+ })
242
+
243
+ const handleLazyLoad = async (event) => {
244
+ const { last } = event
245
+ const numberOfPage = Math.ceil(totalCount.value / PAGE_SIZE)
246
+ const goRequest = last >= data.value?.length
247
+
248
+ if (page.value < numberOfPage && goRequest && !loading.value) {
249
+ page.value += PAGE_INCREMENT
250
+ await fetchData(page.value)
251
+ }
252
+ }
253
+
254
+ const emitBlur = () => {
255
+ emit('onBlur')
256
+ }
257
+
258
+ const emitChange = () => {
259
+ let selectedOption = null
260
+
261
+ // Check if data is grouped
262
+ const isGroupedData =
263
+ data.value.length > 0 &&
264
+ data.value.some((item) => item[props.optionGroupLabel] && item[props.optionGroupChildren])
265
+
266
+ if (isGroupedData) {
267
+ // Search for the selected option within groups
268
+ for (const group of data.value) {
269
+ const groupItems = group[props.optionGroupChildren] || []
270
+ selectedOption = groupItems.find((option) => option[props.optionValue] === inputValue.value)
271
+ if (selectedOption) break
272
+ }
273
+ } else {
274
+ // Search in flat data (existing behavior)
275
+ selectedOption = data.value.find((option) => option[props.optionValue] === inputValue.value)
276
+ }
277
+
278
+ emit('onChange', inputValue.value)
279
+
280
+ if (inputValue.value === null) {
281
+ emit('onClear')
282
+ }
283
+
284
+ if (selectedOption) {
285
+ emit('onSelectOption', selectedOption)
286
+ }
287
+ }
288
+
289
+ const preventValueSetWithoutPermission = () => {
290
+ data.value = [
291
+ {
292
+ [props.optionValue]: props.value,
293
+ name: props.value
294
+ }
295
+ ]
296
+ }
297
+
298
+ const applyIconColor = (icon) => {
299
+ return props.iconColor[icon]
300
+ }
301
+
302
+ /**
303
+ * Workaround to resolve the issue described in https://github.com/primefaces/primevue/issues/4431
304
+ * This should be remove from this field component as soon as the
305
+ * primevue team fixes the issue.
306
+ * When we select a disabled value, the label is not showing
307
+ * @param {*} selectedValue The selected value in the Dropdown component.
308
+ * @returns {string | null} The selected value if it corresponds to a disabled option, or null otherwise.
309
+ */
310
+ const getLabelBySelectedValue = (selectedValue) => {
311
+ if (!selectedValue) return null
312
+
313
+ let selectedOption = null
314
+
315
+ if (props.options) {
316
+ selectedOption = props.options.find((option) => option.value === selectedValue)
317
+ return selectedOption?.label
318
+ }
319
+
320
+ if (data.value?.length) {
321
+ const isGroupedData = data.value.some(
322
+ (item) => item[props.optionGroupLabel] && item[props.optionGroupChildren]
323
+ )
324
+
325
+ if (isGroupedData) {
326
+ for (const group of data.value) {
327
+ const groupItems = group[props.optionGroupChildren] || []
328
+ selectedOption = groupItems.find((option) => option[props.optionValue] === selectedValue)
329
+ if (selectedOption) break
330
+ }
331
+ } else {
332
+ selectedOption = data.value.find((option) => option[props.optionValue] === selectedValue)
333
+ }
334
+ }
335
+
336
+ if (selectedOption) {
337
+ Promise.resolve().then(() => {
338
+ iconSelected.value = selectedOption.icon
339
+ })
340
+ return selectedOption[props.optionLabel] || selectedOption.name
341
+ }
342
+
343
+ return null
344
+ }
345
+
346
+ const fetchData = async (currentPage = 1) => {
347
+ try {
348
+ loading.value = true
349
+
350
+ if (currentPage === INITIAL_PAGE) {
351
+ data.value = []
352
+ }
353
+
354
+ const response = await props.service({
355
+ pageSize: PAGE_SIZE,
356
+ page: currentPage,
357
+ search: search.value,
358
+ ordering: 'name'
359
+ })
360
+
361
+ totalCount.value = response.count
362
+
363
+ // Check if response is grouped data (has label and items structure)
364
+ const isGroupedData =
365
+ Array.isArray(response.body) &&
366
+ response.body.some((item) => item.label && Array.isArray(item.items))
367
+
368
+ let results
369
+
370
+ if (isGroupedData) {
371
+ // Process grouped data
372
+ results = response.body.map((group) => ({
373
+ [props.optionGroupLabel]: group.label,
374
+ [props.optionGroupChildren]:
375
+ group.items?.map((item) => ({
376
+ [props.optionLabel]: item.name,
377
+ [props.optionValue]: item.id,
378
+ ...props?.moreOptions?.reduce(
379
+ (additionalFields, option) => ({
380
+ ...additionalFields,
381
+ [option]: item[option]
382
+ }),
383
+ {}
384
+ )
385
+ })) || []
386
+ }))
387
+ } else {
388
+ // Process flat data (existing behavior)
389
+ results = response.body?.map((item) => {
390
+ return {
391
+ [props.optionLabel]: item.name,
392
+ [props.optionValue]: item.id,
393
+ ...props?.moreOptions?.reduce(
394
+ (additionalFields, option) => ({
395
+ ...additionalFields,
396
+ [option]: item[option]
397
+ }),
398
+ {}
399
+ )
400
+ }
401
+ })
402
+ }
403
+
404
+ if (currentPage === INITIAL_PAGE) {
405
+ data.value = results ? results : []
406
+ } else {
407
+ if (isGroupedData) {
408
+ // For grouped data, merge groups and their items
409
+ const mergedGroups = []
410
+
411
+ results.forEach((newGroup) => {
412
+ const existingGroupIndex = data.value.findIndex(
413
+ (existingGroup) =>
414
+ existingGroup[props.optionGroupLabel] === newGroup[props.optionGroupLabel]
415
+ )
416
+
417
+ if (existingGroupIndex >= 0) {
418
+ // Merge items into existing group
419
+ const existingItems = data.value[existingGroupIndex][props.optionGroupChildren] || []
420
+ const newItems = newGroup[props.optionGroupChildren] || []
421
+
422
+ const uniqueNewItems = newItems.filter(
423
+ (newItem) =>
424
+ !existingItems.some(
425
+ (existingItem) => existingItem[props.optionValue] === newItem[props.optionValue]
426
+ )
427
+ )
428
+
429
+ data.value[existingGroupIndex][props.optionGroupChildren] = [
430
+ ...existingItems,
431
+ ...uniqueNewItems
432
+ ]
433
+ } else {
434
+ // Add new group
435
+ mergedGroups.push(newGroup)
436
+ }
437
+ })
438
+
439
+ data.value = [...data.value, ...mergedGroups]
440
+ } else {
441
+ // For flat data (existing behavior)
442
+ const uniqueResults = results.filter(
443
+ (newItem) =>
444
+ !data.value.some(
445
+ (existingItem) => existingItem[props.optionValue] === newItem[props.optionValue]
446
+ )
447
+ )
448
+
449
+ data.value = [...data.value, ...uniqueResults]
450
+ }
451
+ }
452
+
453
+ if (
454
+ currentPage === INITIAL_PAGE &&
455
+ props.value &&
456
+ search.value === '' &&
457
+ !alreadyLoadedData.value
458
+ ) {
459
+ await checkValueInList(props.value)
460
+ }
461
+ } catch (error) {
462
+ //Here we check if the error was caused by a lack of permission. If that's not the case, we add the ID to avoid blocking the user's experience.
463
+ if (typeof error === 'string' && error?.includes(PERMISSION_DENIED)) {
464
+ hasNoPermission.value = true
465
+ preventValueSetWithoutPermission()
466
+ }
467
+ emit('onAccessDenied')
468
+ } finally {
469
+ loading.value = false
470
+ }
471
+ }
472
+
473
+ const loadSelectedValue = async (id) => {
474
+ if (!id) return
475
+ try {
476
+ loading.value = true
477
+ const results = await props.loadService({ id })
478
+ if (!results) return
479
+ const newOption = {
480
+ [props.optionLabel]: results.name,
481
+ [props.optionValue]: results.id,
482
+ ...props?.moreOptions?.reduce(
483
+ (additionalFields, option) => ({
484
+ ...additionalFields,
485
+ [option]: results[option]
486
+ }),
487
+ {}
488
+ )
489
+ }
490
+
491
+ // Check if data is grouped
492
+ const isGroupedData =
493
+ data.value.length > 0 &&
494
+ data.value.some((item) => item[props.optionGroupLabel] && item[props.optionGroupChildren])
495
+
496
+ let optionExists = false
497
+
498
+ if (isGroupedData) {
499
+ // Check if option exists in any group
500
+ optionExists = data.value.some((group) =>
501
+ group[props.optionGroupChildren]?.some(
502
+ (item) => item[props.optionValue] === newOption[props.optionValue]
503
+ )
504
+ )
505
+
506
+ if (!optionExists) {
507
+ // Add to first group or create a new group
508
+ if (data.value.length > 0) {
509
+ data.value[props.defaultPosition][props.optionGroupChildren] = [
510
+ newOption,
511
+ ...data.value[props.defaultPosition][props.optionGroupChildren]
512
+ ]
513
+ } else {
514
+ // Create a default group if no groups exist
515
+ data.value = [
516
+ {
517
+ [props.optionGroupLabel]: 'Options',
518
+ [props.optionGroupChildren]: [newOption]
519
+ }
520
+ ]
521
+ }
522
+ }
523
+ } else {
524
+ // Flat data (existing behavior)
525
+ optionExists = data.value.some(
526
+ (item) => item[props.optionValue] === newOption[props.optionValue]
527
+ )
528
+
529
+ if (!optionExists) {
530
+ data.value = [newOption, ...data.value]
531
+ }
532
+ }
533
+
534
+ if (disableEmitInit.value) {
535
+ disableEmitInit.value = false
536
+ return
537
+ }
538
+ emitChange()
539
+ } finally {
540
+ loading.value = false
541
+ }
542
+ }
543
+
544
+ const searchFilter = () => {
545
+ page.value = INITIAL_PAGE
546
+ fetchData()
547
+ }
548
+
549
+ const enableCustomLabel = computed(() => {
550
+ return props.enableWorkaroundLabelToDisabledOptions && !!inputValue.value
551
+ })
552
+ /**
553
+ * end of primevue workaround
554
+ */
555
+
556
+ const attrs = useAttrs()
557
+
558
+ const customTestId = computed(() => {
559
+ const id = attrs['data-testid'] || 'field-dropdown'
560
+
561
+ return {
562
+ label: `${id}__label`,
563
+ dropdown: `${id}__dropdown`,
564
+ value: `${id}__value`,
565
+ clearIcon: `${id}__clear-icon`,
566
+ description: `${id}__description`,
567
+ error: `${id}__error-message`,
568
+ filterInput: `${id}__dropdown-filter-input`,
569
+ trigger: `${id}__dropdown-trigger`,
570
+ loadingIcon: `${id}__loading-icon`,
571
+ search: `${id}__dropdown-search`
572
+ }
573
+ })
574
+
575
+ watch(
576
+ () => props.value,
577
+ (newValue) => {
578
+ if (hasNoPermission.value) {
579
+ preventValueSetWithoutPermission()
580
+ }
581
+ checkValueInList(newValue)
582
+ }
583
+ )
584
+
585
+ watchDebounced(
586
+ search,
587
+ () => {
588
+ if (
589
+ search.value.length >= NUMBER_OF_CHARACTERS_MIN_FOR_SEARCH ||
590
+ search.value.length === NUMBER_OF_CHARACTERS_TO_RESET_SEARCH
591
+ ) {
592
+ searchFilter()
593
+ }
594
+ },
595
+ { debounce: SEARCH_DEBOUNCE, maxWait: SEARCH_MAX_WAIT }
596
+ )
597
+
598
+ watchEffect(() => {
599
+ if (focusSearch.value) {
600
+ focusSearch.value.$el.focus()
601
+ }
602
+ })
603
+
604
+ const VIRTUAL_SCROLLER_CONFIG = {
605
+ lazy: true,
606
+ onLazyLoad: handleLazyLoad,
607
+ itemSize: 38,
608
+ showLoader: true,
609
+ loading
610
+ }
611
+
612
+ const checkValueInList = (value) => {
613
+ const existitemInList = data.value?.some((item) => item[props.optionValue] === value)
614
+
615
+ if (!existitemInList) {
616
+ loadSelectedValue(value)
617
+ alreadyLoadedData.value = true
618
+ }
619
+ }
620
+
621
+ const refreshData = async () => {
622
+ page.value = INITIAL_PAGE
623
+ search.value = ''
624
+ await fetchData()
625
+ }
626
+
627
+ // Expose refresh function to parent components
628
+ defineExpose({
629
+ refreshData
630
+ })
631
+ </script>