@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.
- package/package.json +46 -0
- package/src/core/field-auto-complete/field-auto-complete.vue +121 -0
- package/src/core/field-auto-complete/field-auto-complete.vue.d.ts +107 -0
- package/src/core/field-auto-complete/field-auto-complete.vue.d.ts.map +1 -0
- package/src/core/field-auto-complete/package.json +11 -0
- package/src/core/field-checkbox-block/field-checkbox-block.vue +106 -0
- package/src/core/field-checkbox-block/field-checkbox-block.vue.d.ts +111 -0
- package/src/core/field-checkbox-block/field-checkbox-block.vue.d.ts.map +1 -0
- package/src/core/field-checkbox-block/package.json +11 -0
- package/src/core/field-dropdown/field-dropdown.vue +240 -0
- package/src/core/field-dropdown/field-dropdown.vue.d.ts +203 -0
- package/src/core/field-dropdown/field-dropdown.vue.d.ts.map +1 -0
- package/src/core/field-dropdown/package.json +11 -0
- package/src/core/field-dropdown-icon/field-dropdown-icon.vue +129 -0
- package/src/core/field-dropdown-icon/field-dropdown-icon.vue.d.ts +111 -0
- package/src/core/field-dropdown-icon/field-dropdown-icon.vue.d.ts.map +1 -0
- package/src/core/field-dropdown-icon/package.json +11 -0
- package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue +631 -0
- package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue.d.ts +196 -0
- package/src/core/field-dropdown-lazy-loader/field-dropdown-lazy-loader.vue.d.ts.map +1 -0
- package/src/core/field-dropdown-lazy-loader/package.json +11 -0
- package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue +426 -0
- package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue.d.ts +158 -0
- package/src/core/field-dropdown-lazy-loader-dynamic/field-dropdown-lazy-loader-dynamic.vue.d.ts.map +1 -0
- package/src/core/field-dropdown-lazy-loader-dynamic/package.json +11 -0
- package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue +527 -0
- package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue.d.ts +203 -0
- package/src/core/field-dropdown-lazy-loader-with-filter/field-dropdown-lazy-loader-with-filter.vue.d.ts.map +1 -0
- package/src/core/field-dropdown-lazy-loader-with-filter/package.json +11 -0
- package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue +439 -0
- package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue.d.ts +162 -0
- package/src/core/field-dropdown-multi-select-lazy-loader/field-dropdown-multi-select-lazy-loader.vue.d.ts.map +1 -0
- package/src/core/field-dropdown-multi-select-lazy-loader/package.json +11 -0
- package/src/core/field-group-checkbox/field-group-checkbox.vue +102 -0
- package/src/core/field-group-checkbox/field-group-checkbox.vue.d.ts +87 -0
- package/src/core/field-group-checkbox/field-group-checkbox.vue.d.ts.map +1 -0
- package/src/core/field-group-checkbox/package.json +11 -0
- package/src/core/field-group-radio/field-group-radio.vue +149 -0
- package/src/core/field-group-radio/field-group-radio.vue.d.ts +99 -0
- package/src/core/field-group-radio/field-group-radio.vue.d.ts.map +1 -0
- package/src/core/field-group-radio/package.json +11 -0
- package/src/core/field-group-switch/field-group-switch.vue +121 -0
- package/src/core/field-group-switch/field-group-switch.vue.d.ts +87 -0
- package/src/core/field-group-switch/field-group-switch.vue.d.ts.map +1 -0
- package/src/core/field-group-switch/package.json +11 -0
- package/src/core/field-input-group/field-input-group.vue +111 -0
- package/src/core/field-input-group/field-input-group.vue.d.ts +87 -0
- package/src/core/field-input-group/field-input-group.vue.d.ts.map +1 -0
- package/src/core/field-input-group/package.json +11 -0
- package/src/core/field-multi-select/field-multi-select.vue +167 -0
- package/src/core/field-multi-select/field-multi-select.vue.d.ts +136 -0
- package/src/core/field-multi-select/field-multi-select.vue.d.ts.map +1 -0
- package/src/core/field-multi-select/package.json +11 -0
- package/src/core/field-number/field-number.vue +144 -0
- package/src/core/field-number/field-number.vue.d.ts +132 -0
- package/src/core/field-number/field-number.vue.d.ts.map +1 -0
- package/src/core/field-number/package.json +11 -0
- package/src/core/field-phone-number/field-phone-number.vue +151 -0
- package/src/core/field-phone-number/field-phone-number.vue.d.ts +76 -0
- package/src/core/field-phone-number/field-phone-number.vue.d.ts.map +1 -0
- package/src/core/field-phone-number/package.json +11 -0
- package/src/core/field-phone-number-country/field-phone-number-country.vue +60 -0
- package/src/core/field-phone-number-country/field-phone-number-country.vue.d.ts +18 -0
- package/src/core/field-phone-number-country/field-phone-number-country.vue.d.ts.map +1 -0
- package/src/core/field-phone-number-country/package.json +11 -0
- package/src/core/field-pick-list/field-pick-list.vue +285 -0
- package/src/core/field-pick-list/field-pick-list.vue.d.ts +43 -0
- package/src/core/field-pick-list/field-pick-list.vue.d.ts.map +1 -0
- package/src/core/field-pick-list/package.json +11 -0
- package/src/core/field-radio-block/field-radio-block.vue +108 -0
- package/src/core/field-radio-block/field-radio-block.vue.d.ts +115 -0
- package/src/core/field-radio-block/field-radio-block.vue.d.ts.map +1 -0
- package/src/core/field-radio-block/package.json +11 -0
- package/src/core/field-switch/field-switch.vue +41 -0
- package/src/core/field-switch/field-switch.vue.d.ts +33 -0
- package/src/core/field-switch/field-switch.vue.d.ts.map +1 -0
- package/src/core/field-switch/package.json +11 -0
- package/src/core/field-switch-block/field-switch-block.vue +123 -0
- package/src/core/field-switch-block/field-switch-block.vue.d.ts +123 -0
- package/src/core/field-switch-block/field-switch-block.vue.d.ts.map +1 -0
- package/src/core/field-switch-block/package.json +11 -0
- package/src/core/field-text/field-text.vue +128 -0
- package/src/core/field-text/field-text.vue.d.ts +108 -0
- package/src/core/field-text/field-text.vue.d.ts.map +1 -0
- package/src/core/field-text/package.json +11 -0
- package/src/core/field-text-area/field-text-area.vue +178 -0
- package/src/core/field-text-area/field-text-area.vue.d.ts +142 -0
- package/src/core/field-text-area/field-text-area.vue.d.ts.map +1 -0
- package/src/core/field-text-area/package.json +11 -0
- package/src/core/field-text-icon/field-text-icon.vue +127 -0
- package/src/core/field-text-icon/field-text-icon.vue.d.ts +100 -0
- package/src/core/field-text-icon/field-text-icon.vue.d.ts.map +1 -0
- package/src/core/field-text-icon/package.json +11 -0
- package/src/core/field-text-password/field-text-password.vue +127 -0
- package/src/core/field-text-password/field-text-password.vue.d.ts +108 -0
- package/src/core/field-text-password/field-text-password.vue.d.ts.map +1 -0
- package/src/core/field-text-password/package.json +11 -0
- package/src/core/label/label.vue +35 -0
- package/src/core/label/label.vue.d.ts +24 -0
- package/src/core/label/label.vue.d.ts.map +1 -0
- package/src/core/label/package.json +11 -0
- package/src/core/selector-block/package.json +11 -0
- package/src/core/selector-block/selector-block.vue +128 -0
- package/src/core/selector-block/selector-block.vue.d.ts +99 -0
- 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>
|