vueless 0.0.592 → 0.0.593

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 (71) hide show
  1. package/composables/useUI.ts +17 -15
  2. package/package.json +1 -1
  3. package/types.ts +2 -0
  4. package/ui.button/storybook/stories.ts +3 -1
  5. package/ui.button-link/storybook/stories.ts +3 -1
  6. package/ui.button-toggle/storybook/stories.ts +3 -1
  7. package/ui.button-toggle-item/storybook/stories.ts +3 -1
  8. package/ui.container-accordion/storybook/stories.ts +3 -1
  9. package/ui.container-card/storybook/stories.ts +3 -1
  10. package/ui.container-col/storybook/stories.ts +1 -1
  11. package/ui.container-divider/storybook/stories.ts +3 -1
  12. package/ui.container-group/storybook/stories.ts +1 -1
  13. package/ui.container-groups/storybook/stories.ts +1 -1
  14. package/ui.container-modal/storybook/stories.ts +1 -1
  15. package/ui.container-modal-confirm/storybook/stories.ts +1 -1
  16. package/ui.container-page/storybook/stories.ts +3 -1
  17. package/ui.container-row/storybook/stories.ts +3 -1
  18. package/ui.data-list/storybook/stories.ts +3 -1
  19. package/ui.data-table/storybook/stories.ts +3 -1
  20. package/ui.dropdown-badge/storybook/stories.ts +1 -1
  21. package/ui.dropdown-button/storybook/stories.ts +1 -1
  22. package/ui.dropdown-link/storybook/stories.ts +1 -1
  23. package/ui.dropdown-list/storybook/stories.ts +1 -1
  24. package/ui.dropdown-list/types.ts +1 -1
  25. package/ui.form-calendar/storybook/stories.ts +1 -1
  26. package/ui.form-checkbox/storybook/stories.ts +3 -1
  27. package/ui.form-checkbox-group/storybook/stories.ts +3 -1
  28. package/ui.form-checkbox-multi-state/storybook/stories.ts +3 -1
  29. package/ui.form-color-picker/storybook/stories.ts +3 -1
  30. package/ui.form-date-picker/storybook/stories.ts +1 -1
  31. package/ui.form-date-picker-range/storybook/stories.ts +3 -1
  32. package/ui.form-input/storybook/stories.ts +3 -1
  33. package/ui.form-input-file/storybook/stories.ts +3 -1
  34. package/ui.form-input-money/storybook/stories.ts +3 -1
  35. package/ui.form-input-number/storybook/stories.ts +3 -1
  36. package/ui.form-input-rating/storybook/stories.ts +3 -1
  37. package/ui.form-input-search/storybook/stories.ts +3 -1
  38. package/ui.form-label/ULabel.vue +1 -1
  39. package/ui.form-label/storybook/stories.ts +3 -1
  40. package/ui.form-radio/storybook/stories.ts +3 -1
  41. package/ui.form-radio-group/storybook/stories.ts +3 -1
  42. package/ui.form-select/USelect.vue +535 -682
  43. package/ui.form-select/{config.js → config.ts} +2 -1
  44. package/ui.form-select/{constants.js → constants.ts} +0 -5
  45. package/ui.form-select/storybook/Docs.mdx +2 -2
  46. package/ui.form-select/storybook/{stories.js → stories.ts} +23 -13
  47. package/ui.form-select/types.ts +134 -0
  48. package/ui.form-select/utilSelect.ts +122 -0
  49. package/ui.form-switch/storybook/stories.ts +3 -1
  50. package/ui.form-textarea/storybook/stories.ts +3 -1
  51. package/ui.image-avatar/storybook/stories.ts +3 -1
  52. package/ui.image-icon/storybook/stories.ts +3 -1
  53. package/ui.loader/storybook/stories.ts +3 -1
  54. package/ui.loader-overlay/storybook/stories.ts +1 -1
  55. package/ui.loader-progress/storybook/stories.ts +3 -1
  56. package/ui.navigation-progress/storybook/stories.ts +3 -1
  57. package/ui.navigation-tab/storybook/stories.ts +3 -1
  58. package/ui.navigation-tabs/storybook/stories.ts +3 -1
  59. package/ui.other-dot/storybook/stories.ts +3 -1
  60. package/ui.text-alert/storybook/stories.ts +3 -1
  61. package/ui.text-badge/storybook/stories.ts +3 -1
  62. package/ui.text-block/storybook/stories.ts +3 -1
  63. package/ui.text-empty/storybook/stories.ts +3 -1
  64. package/ui.text-file/storybook/stories.ts +3 -1
  65. package/ui.text-files/storybook/stories.ts +3 -1
  66. package/ui.text-header/storybook/stories.ts +3 -1
  67. package/ui.text-money/storybook/stories.ts +3 -1
  68. package/ui.text-notify/storybook/stories.ts +1 -1
  69. package/utils/storybook.ts +3 -5
  70. package/web-types.json +77 -35
  71. package/ui.form-select/utilSelect.js +0 -100
@@ -1,316 +1,4 @@
1
- <template>
2
- <ULabel
3
- ref="labelComponentRef"
4
- :for="elementId"
5
- :size="size"
6
- :label="label"
7
- :error="error"
8
- :description="description"
9
- :align="labelAlign"
10
- :disabled="disabled"
11
- centred
12
- v-bind="selectLabelAttrs"
13
- :data-test="dataTest"
14
- :tabindex="-1"
15
- >
16
- <div
17
- ref="wrapperRef"
18
- :tabindex="searchable || disabled ? -1 : 0"
19
- role="combobox"
20
- :aria-owns="'listbox-' + elementId"
21
- v-bind="wrapperAttrs"
22
- @focus="activate"
23
- @blur="deactivate"
24
- @keydown.self.down.prevent="dropdownListRef.pointerForward"
25
- @keydown.self.up.prevent="dropdownListRef.pointerBackward"
26
- @keydown.enter.tab.stop.self="dropdownListRef.addPointerElement"
27
- @keyup.esc="deactivate"
28
- >
29
- <!-- @slot Use it to add something right. -->
30
- <slot name="right" />
31
-
32
- <div v-if="hasSlotContent($slots['right-icon']) || rightIcon" v-bind="rightIconWrapperAttrs">
33
- <!--
34
- @slot Use it to add icon right.
35
- @binding {string} icon-name
36
- @binding {string} icon-size
37
- -->
38
- <slot name="right-icon" :icon-name="rightIcon" :icon-size="iconSize">
39
- <UIcon
40
- v-if="rightIcon"
41
- :name="rightIcon"
42
- :size="iconSize"
43
- internal
44
- v-bind="rightIconAttrs"
45
- />
46
- </slot>
47
- </div>
48
-
49
- <div
50
- v-if="hasSlotContent($slots['after-caret']) && !(multiple && localValue.length)"
51
- v-bind="afterCaretAttrs"
52
- :tabindex="-1"
53
- >
54
- <!--
55
- @slot Use it to add something after caret.
56
- @binding {object} scope-props
57
- -->
58
- <slot :scope-props="props" name="after-caret" />
59
- </div>
60
-
61
- <div
62
- v-show="!multiple || (!isLocalValue && multiple)"
63
- v-bind="toggleAttrs"
64
- :tabindex="-1"
65
- @mousedown.prevent.stop="toggle"
66
- >
67
- <!--
68
- @slot Use it to add something instead of the toggle icon.
69
- @binding {string} icon-name
70
- @binding {string} icon-size
71
- @binding {boolean} opened
72
- -->
73
- <slot
74
- name="toggle"
75
- :icon-name="config.defaults.dropdownIcon"
76
- :icon-size="iconSize"
77
- :opened="isOpen"
78
- >
79
- <UIcon
80
- internal
81
- interactive
82
- color="gray"
83
- :size="iconSize"
84
- :name="config.defaults.dropdownIcon"
85
- v-bind="toggleIconAttrs"
86
- :tabindex="-1"
87
- />
88
- </slot>
89
- </div>
90
-
91
- <div
92
- v-if="isLocalValue && !clearable && !disabled && !multiple"
93
- v-bind="clearAttrs"
94
- @mousedown="removeElement"
95
- >
96
- <!--
97
- @slot Use it to add something instead of the clear icon.
98
- @binding {string} icon-name
99
- @binding {string} icon-size
100
- -->
101
- <slot name="clear" :icon-name="config.defaults.clearIcon" :icon-size="iconSize">
102
- <UIcon
103
- internal
104
- interactive
105
- color="gray"
106
- :size="iconSize"
107
- :name="config.defaults.clearIcon"
108
- v-bind="clearIconAttrs"
109
- />
110
- </slot>
111
- </div>
112
-
113
- <div
114
- v-if="hasSlotContent($slots['before-caret']) && !(multiple && localValue.length)"
115
- v-bind="beforeCaretAttrs"
116
- >
117
- <!--
118
- @slot Use it to add something before caret.
119
- @binding {object} scope-props
120
- -->
121
- <slot :scope-props="props" name="before-caret" />
122
- </div>
123
-
124
- <div ref="innerWrapperRef" v-bind="innerWrapperAttrs">
125
- <span
126
- v-if="hasSlotContent($slots['left-icon']) || leftIcon"
127
- ref="leftSlotWrapperRef"
128
- v-bind="leftIconWrapperAttrs"
129
- >
130
- <!--
131
- @slot Use it to add icon left.
132
- @binding {string} icon-name
133
- @binding {string} icon-size
134
- -->
135
- <slot name="left-icon" :icon-name="leftIcon" :icon-size="iconSize">
136
- <UIcon
137
- v-if="leftIcon"
138
- :name="leftIcon"
139
- :size="iconSize"
140
- internal
141
- v-bind="leftIconAttrs"
142
- />
143
- </slot>
144
- </span>
145
-
146
- <span v-if="hasSlotContent($slots['left'])" ref="leftSlotWrapperRef">
147
- <!-- @slot Use it to add something left. -->
148
- <slot name="left" />
149
- </span>
150
-
151
- <div v-if="multiple && localValue.length" v-bind="selectedLabelsAttrs">
152
- <span v-for="item in localValue" :key="item[valueKey]" v-bind="selectedLabelAttrs">
153
- <!--
154
- @slot Use it to customise selected value label.
155
- @binding {string} selected-label
156
- -->
157
- <slot
158
- name="selected-label"
159
- :selected-label="getOptionLabel(item)"
160
- :value="item[valueKey]"
161
- :raw-option="item"
162
- >
163
- {{ getOptionLabel(item) }}
164
- </slot>
165
-
166
- <!--
167
- @slot Use it to add something after selected value label.
168
- @binding {object} scope-props
169
- -->
170
- <slot :scope-props="props" name="selected-label-after" />
171
-
172
- <div
173
- v-if="!disabled"
174
- v-bind="clearMultipleAttrs"
175
- @mousedown.prevent.capture
176
- @click.prevent.capture
177
- @mousedown="removeElement(item)"
178
- >
179
- <!--
180
- @slot Use it to add something instead of the clear icon (when multiple prop enabled).
181
- @binding {string} icon-name
182
- @binding {string} icon-size
183
- -->
184
- <slot
185
- name="clear-multiple"
186
- :icon-name="config.defaults.clearMultipleIcon"
187
- :icon-size="iconSize"
188
- >
189
- <UIcon
190
- internal
191
- interactive
192
- color="gray"
193
- :size="iconSize"
194
- :name="config.defaults.clearMultipleIcon"
195
- v-bind="clearMultipleIconAttrs"
196
- />
197
- </slot>
198
- </div>
199
- </span>
200
- </div>
201
-
202
- <div v-bind="searchAttrs">
203
- <input
204
- v-show="searchable || !localValue || multiple || !isOpen"
205
- :id="elementId"
206
- ref="searchInputRef"
207
- v-model="search"
208
- type="text"
209
- autocomplete="off"
210
- :spellcheck="false"
211
- :placeholder="inputPlaceholder"
212
- :disabled="disabled"
213
- :aria-controls="'listbox-' + elementId"
214
- v-bind="searchInputAttrs"
215
- @focus="activate"
216
- @blur.prevent="deactivate"
217
- @keyup.esc="deactivate"
218
- @keydown.down.prevent="dropdownListRef.pointerForward"
219
- @keydown.up.prevent="dropdownListRef.pointerBackward"
220
- @keydown.enter.prevent.stop.self="dropdownListRef.addPointerElement"
221
- />
222
- </div>
223
-
224
- <span
225
- v-if="isSelectedValueLabelVisible"
226
- v-bind="selectedLabelAttrs"
227
- @mousedown.prevent="toggle"
228
- >
229
- <!--
230
- @slot Use it to add selected value label.
231
- @binding {string} selected-label
232
- @binding {string} value
233
- -->
234
- <slot name="selected-label" :selected-label="selectedLabel" :value="localValue[valueKey]">
235
- {{ selectedLabel }}
236
- </slot>
237
-
238
- <!--
239
- @slot Use it to add something after selected value label.
240
- @binding {object} scope-props
241
- -->
242
- <slot :scope-props="props" name="selected-label-after" />
243
- </span>
244
-
245
- <div
246
- v-if="isLocalValue && !clearable && !disabled && multiple"
247
- v-bind="clearMultipleTextAttrs"
248
- @mousedown.prevent.capture="removeElement(localValue)"
249
- @click.prevent.capture
250
- v-text="currentLocale.clear"
251
- />
252
- </div>
253
-
254
- <UDropdownList
255
- v-if="isOpen"
256
- ref="dropdownListRef"
257
- v-model="dropdownValue"
258
- :options="filteredOptions"
259
- :disabled="disabled"
260
- :size="size"
261
- :visible-options="visibleOptions"
262
- :value-key="valueKey"
263
- :label-key="labelKey"
264
- :add-option="addOption"
265
- tabindex="-1"
266
- v-bind="dropdownListAttrs"
267
- @add="onAddOption"
268
- @focus="activate"
269
- @mousedown.prevent.capture
270
- @click.prevent.capture
271
- >
272
- <template #before-option="{ option, index }">
273
- <!--
274
- @slot Use it to add something before option.
275
- @binding {object} option
276
- @binding {number} index
277
- -->
278
- <slot name="before-option" :option="option" :index="index" />
279
- </template>
280
-
281
- <template #option="{ option, index }">
282
- <!--
283
- @slot Use it to customise the option.
284
- @binding {object} option
285
- @binding {number} index
286
- -->
287
- <slot name="option" :option="option" :index="index" />
288
- </template>
289
-
290
- <template #after-option="{ option, index }">
291
- <!--
292
- @slot Use it to add something after option.
293
- @binding {object} option
294
- @binding {number} index
295
- -->
296
- <slot name="after-option" :option="option" :index="index" />
297
- </template>
298
-
299
- <template #empty>
300
- <template v-if="isEmpty">
301
- {{ currentLocale.listIsEmpty }}
302
- </template>
303
-
304
- <template v-else>
305
- {{ currentLocale.noDataToShow }}
306
- </template>
307
- </template>
308
- </UDropdownList>
309
- </div>
310
- </ULabel>
311
- </template>
312
-
313
- <script setup>
1
+ <script setup lang="ts">
314
2
  import { ref, computed, nextTick, watch, useSlots, onMounted, useId } from "vue";
315
3
  import { merge } from "lodash-es";
316
4
 
@@ -323,247 +11,61 @@ import { createDebounce, hasSlotContent } from "../utils/helper.ts";
323
11
  import { getDefaults } from "../utils/ui.ts";
324
12
  import { isMac } from "../utils/platform.ts";
325
13
 
326
- import SelectService from "./utilSelect.js";
327
- import defaultConfig from "./config.js";
328
- import { USelect, DIRECTION, KEY_CODES } from "./constants.js";
14
+ import {
15
+ filterOptions,
16
+ filterGroups,
17
+ removeSelectedValues,
18
+ getCurrentOption,
19
+ } from "./utilSelect.ts";
20
+ import defaultConfig from "./config.ts";
21
+ import { USelect, DIRECTION, KEYS } from "./constants.ts";
329
22
 
330
23
  import { useLocale } from "../composables/useLocale.ts";
331
24
 
332
- defineOptions({ inheritAttrs: false });
25
+ import type { Option, Config as UDropdownListConfig } from "../ui.dropdown-list/types.ts";
26
+ import type { Props, Config, IconSize } from "./types.ts";
27
+ import type { ComponentExposed, KeyAttrsWithConfig } from "../types.ts";
333
28
 
334
- const props = defineProps({
335
- /**
336
- * Select value.
337
- */
338
- modelValue: {
339
- type: [String, Number, Array],
340
- default: "",
341
- },
342
-
343
- /**
344
- * Select options.
345
- */
346
- options: {
347
- type: Array,
348
- default: () => [],
349
- },
350
-
351
- /**
352
- * Select label.
353
- */
354
- label: {
355
- type: String,
356
- default: "",
357
- },
358
-
359
- /**
360
- * Label placement.
361
- * @values top, topInside, topWithDesc, left, right
362
- */
363
- labelAlign: {
364
- type: String,
365
- default: getDefaults(defaultConfig, USelect).labelAlign,
366
- },
367
-
368
- /**
369
- * Select placeholder.
370
- */
371
- placeholder: {
372
- type: String,
373
- default: "",
374
- },
375
-
376
- /**
377
- * Select description.
378
- */
379
- description: {
380
- type: String,
381
- default: "",
382
- },
383
-
384
- /**
385
- * Select error message.
386
- */
387
- error: {
388
- type: String,
389
- default: "",
390
- },
391
-
392
- /**
393
- * Select size.
394
- * @values sm, md, lg
395
- */
396
- size: {
397
- type: String,
398
- default: getDefaults(defaultConfig, USelect).size,
399
- },
400
-
401
- /**
402
- * Left icon name.
403
- */
404
- leftIcon: {
405
- type: String,
406
- default: "",
407
- },
408
-
409
- /**
410
- * Right icon name.
411
- */
412
- rightIcon: {
413
- type: String,
414
- default: "",
415
- },
416
-
417
- /**
418
- * Select open direction.
419
- * @values auto, top, bottom
420
- */
421
- openDirection: {
422
- type: String,
423
- default: getDefaults(defaultConfig, USelect).openDirection,
424
- },
425
-
426
- /**
427
- * Label key in the item object of options.
428
- */
429
- labelKey: {
430
- type: String,
431
- default: getDefaults(defaultConfig, USelect).labelKey,
432
- },
433
-
434
- /**
435
- * Value key in the item object of options.
436
- */
437
- valueKey: {
438
- type: String,
439
- default: getDefaults(defaultConfig, USelect).valueKey,
440
- },
441
-
442
- /**
443
- * Set a name of the property containing the group label.
444
- */
445
- groupLabelKey: {
446
- type: String,
447
- default: "label",
448
- },
449
-
450
- /**
451
- * Set a name of the property containing the group values.
452
- */
453
- groupValueKey: {
454
- type: String,
455
- default: "",
456
- },
457
-
458
- /**
459
- * Number of options displayed in the dropdown.
460
- */
461
- optionsLimit: {
462
- type: Number,
463
- default: getDefaults(defaultConfig, USelect).optionsLimit,
464
- },
465
-
466
- /**
467
- * Amount of options you can see without scroll.
468
- */
469
- visibleOptions: {
470
- type: Number,
471
- default: getDefaults(defaultConfig, USelect).visibleOptions,
472
- },
473
-
474
- /**
475
- * Allow clearing selected value.
476
- */
477
- clearable: {
478
- type: Boolean,
479
- default: getDefaults(defaultConfig, USelect).clearable,
480
- },
481
-
482
- /**
483
- * Allows multiple selection.
484
- */
485
- multiple: {
486
- type: Boolean,
487
- default: getDefaults(defaultConfig, USelect).multiple,
488
- },
489
-
490
- /**
491
- * Allows to search value in a list.
492
- */
493
- searchable: {
494
- type: Boolean,
495
- default: getDefaults(defaultConfig, USelect).searchable,
496
- },
497
-
498
- /**
499
- * Disable the select.
500
- */
501
- disabled: {
502
- type: Boolean,
503
- default: getDefaults(defaultConfig, USelect).disabled,
504
- },
505
-
506
- /**
507
- * Show "Add new option" button in the list.
508
- */
509
- addOption: {
510
- type: Boolean,
511
- default: getDefaults(defaultConfig, USelect).addOption,
512
- },
513
-
514
- /**
515
- * Unique element id.
516
- */
517
- id: {
518
- type: String,
519
- default: "",
520
- },
521
-
522
- /**
523
- * Component config object.
524
- */
525
- config: {
526
- type: Object,
527
- default: () => ({}),
528
- },
29
+ defineOptions({ inheritAttrs: false });
529
30
 
530
- /**
531
- * Data-test attribute for automated testing.
532
- */
533
- dataTest: {
534
- type: String,
535
- default: "",
536
- },
31
+ const props = withDefaults(defineProps<Props>(), {
32
+ ...getDefaults<Props, Config>(defaultConfig, USelect),
33
+ options: () => [],
34
+ modelValue: "",
35
+ label: "",
36
+ placeholder: "",
537
37
  });
538
38
 
539
39
  const emit = defineEmits([
540
40
  /**
541
41
  * Triggers when a dropdown list is opened.
542
- * @property {string} propsId
42
+ * @property {string} elementId
543
43
  */
544
44
  "open",
545
45
 
546
46
  /**
547
47
  * Triggers when a dropdown list is closed.
548
- * @property {string} propsId
48
+ * @property {string} elementId
549
49
  */
550
50
  "close",
551
51
 
552
52
  /**
553
53
  * Triggers when the search value is changed.
54
+ * @property {string} query
554
55
  */
555
56
  "searchChange",
556
57
 
557
58
  /**
558
59
  * Triggers when option is removed.
559
60
  * @property {string} option
560
- * @property {string} propsId
561
61
  */
562
62
  "remove",
563
63
 
564
64
  /**
565
65
  * Triggers when option is selected.
66
+ * @property {string} value
566
67
  * @property {number} value
68
+ * @property {Option} value
567
69
  */
568
70
  "update:modelValue",
569
71
 
@@ -585,12 +87,17 @@ const isOpen = ref(false);
585
87
  const preferredOpenDirection = ref(DIRECTION.bottom);
586
88
  const search = ref("");
587
89
 
588
- const dropdownListRef = ref(null);
589
- const wrapperRef = ref(null);
590
- const searchInputRef = ref(null);
591
- const labelComponentRef = ref(null);
592
- const leftSlotWrapperRef = ref(null);
593
- const innerWrapperRef = ref(null);
90
+ const dropdownListRef = ref<ComponentExposed<typeof UDropdownList> | null>(null);
91
+ const wrapperRef = ref<HTMLDivElement | null>(null);
92
+ const searchInputRef = ref<HTMLInputElement | null>(null);
93
+ const labelComponentRef = ref<ComponentExposed<typeof ULabel> | null>(null);
94
+ const leftSlotWrapperRef = ref<HTMLSpanElement | null>(null);
95
+ const innerWrapperRef = ref<HTMLDivElement | null>(null);
96
+
97
+ const elementId = props.id || useId();
98
+
99
+ const i18nGlobal = tm(USelect);
100
+ const currentLocale = computed(() => merge(defaultConfig.i18n, i18nGlobal, props.config.i18n));
594
101
 
595
102
  const isTop = computed(() => {
596
103
  if (props.openDirection === DIRECTION.top) return true;
@@ -599,11 +106,6 @@ const isTop = computed(() => {
599
106
  return preferredOpenDirection.value === DIRECTION.top;
600
107
  });
601
108
 
602
- const elementId = props.id || useId();
603
-
604
- const i18nGlobal = tm(USelect);
605
- const currentLocale = computed(() => merge(defaultConfig.i18n, i18nGlobal, props.config.i18n));
606
-
607
109
  const iconSize = computed(() => {
608
110
  const sizes = {
609
111
  sm: "xs",
@@ -611,22 +113,24 @@ const iconSize = computed(() => {
611
113
  lg: "md",
612
114
  };
613
115
 
614
- return sizes[props.size];
116
+ return sizes[props.size] as IconSize;
615
117
  });
616
118
 
617
119
  const inputPlaceholder = computed(() => {
618
120
  const message = currentLocale.value.addMore;
619
121
 
620
- return props.multiple && localValue.value.length ? message : props.placeholder;
122
+ return props.multiple && localValue.value?.length ? message : props.placeholder;
621
123
  });
622
124
 
623
125
  const dropdownValue = computed({
624
126
  get: () => props.modelValue,
625
127
  set: (newValue) => {
626
- let value = newValue;
128
+ let value;
627
129
 
628
130
  if (props.multiple) {
629
131
  value = Array.isArray(props.modelValue) ? [...props.modelValue, newValue] : [newValue];
132
+ } else {
133
+ value = newValue;
630
134
  }
631
135
 
632
136
  emit("update:modelValue", value);
@@ -642,55 +146,76 @@ const isSelectedValueLabelVisible = computed(() => {
642
146
  const filteredOptions = computed(() => {
643
147
  const normalizedSearch = search.value.toLowerCase().trim() || "";
644
148
 
149
+ let selectedValues: (string | number)[] = [];
150
+
151
+ if (Array.isArray(props.modelValue)) {
152
+ selectedValues = props.modelValue.map((value) => {
153
+ if (typeof value === "object") {
154
+ return value[props.valueKey] as string | number;
155
+ }
156
+
157
+ return value;
158
+ });
159
+ } else if (props.modelValue) {
160
+ selectedValues =
161
+ typeof props.modelValue === "object"
162
+ ? [props.modelValue[props.valueKey]]
163
+ : [props.modelValue];
164
+ }
165
+
645
166
  let options = props.multiple
646
- ? SelectService.removeSelectedValues(
647
- props.options,
648
- props.groupValueKey,
649
- props.valueKey,
650
- props.modelValue,
651
- )
167
+ ? removeSelectedValues(props.options, selectedValues, props.valueKey, props.groupValueKey)
652
168
  : [...props.options];
653
169
 
654
170
  options = props.groupValueKey
655
- ? filterAndFlat(options, normalizedSearch, props.labelKey)
656
- : SelectService.filterOptions(options, normalizedSearch, props.labelKey);
171
+ ? filterGroups(
172
+ options,
173
+ normalizedSearch,
174
+ props.labelKey,
175
+ props.groupValueKey,
176
+ props.groupLabelKey,
177
+ )
178
+ : filterOptions(options, normalizedSearch, props.labelKey);
657
179
 
658
180
  return options.slice(0, props.optionsLimit || options.length);
659
181
  });
660
182
 
661
183
  const localValue = computed(() => {
662
184
  if (!props.multiple) {
663
- return SelectService.getCurrentOption(
664
- props.modelValue,
665
- props.options,
666
- props.groupValueKey,
667
- props.valueKey,
668
- );
185
+ const [singleValue] = Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue];
186
+
187
+ return getCurrentOption(props.options, singleValue, props.valueKey, props.groupValueKey);
669
188
  }
670
189
 
671
- return props.modelValue
672
- ? props.modelValue.map((item) =>
673
- SelectService.getCurrentOption(item, props.options, props.groupValueKey, props.valueKey),
190
+ return props.modelValue && Array.isArray(props.modelValue)
191
+ ? props.modelValue.map((value) =>
192
+ getCurrentOption(props.options, value, props.valueKey, props.groupValueKey),
674
193
  )
675
194
  : [];
676
195
  });
677
196
 
678
197
  const isLocalValue = computed(() => {
679
- if (props.multiple) return Boolean(localValue.value.length);
198
+ const value = localValue.value;
680
199
 
681
- return typeof localValue.value !== "number"
682
- ? Boolean(localValue.value)
683
- : Boolean(String(localValue.value));
200
+ if (Array.isArray(value)) {
201
+ return !!value.length;
202
+ }
203
+
204
+ if (typeof value === "object") {
205
+ return !!Object.keys(value).length;
206
+ }
207
+
208
+ return !!String(value);
684
209
  });
685
210
 
686
211
  const selectedLabel = computed(() => {
687
- return isLocalValue.value ? getOptionLabel(localValue.value) : "";
212
+ return isLocalValue.value ? getOptionLabel(localValue.value as Option) : "";
688
213
  });
689
214
 
690
215
  const isEmpty = computed(() => {
691
216
  return (
692
217
  (filteredOptions.value.length === 0 && search) ||
693
- (props.multiple && localValue.value.length === props.options.length)
218
+ (props.multiple && localValue.value?.length === props.options.length)
694
219
  );
695
220
  });
696
221
 
@@ -707,16 +232,16 @@ if (props.addOption) {
707
232
 
708
233
  onMounted(setLabelPosition);
709
234
 
710
- function getOptionLabel(option) {
235
+ function getOptionLabel(option: Option) {
711
236
  if (!option) return "";
712
237
 
713
238
  return option[props.labelKey] || "";
714
239
  }
715
240
 
716
- function onKeydownAddOption(event) {
241
+ function onKeydownAddOption(event: KeyboardEvent) {
717
242
  if (!isOpen.value) return;
718
243
 
719
- const isEnter = event.keyCode === KEY_CODES.enter;
244
+ const isEnter = event.key === KEYS.enter;
720
245
  const isCtrl = event.ctrlKey;
721
246
  const isMeta = event.metaKey;
722
247
 
@@ -735,18 +260,6 @@ function onAddOption() {
735
260
  emit("add");
736
261
  }
737
262
 
738
- function filterAndFlat(options, search, label) {
739
- const filteredGroups = SelectService.filterGroups(
740
- options,
741
- search,
742
- label,
743
- props.groupValueKey,
744
- props.groupLabelKey,
745
- );
746
-
747
- return SelectService.flattenOptions(filteredGroups, props.groupValueKey, props.groupLabelKey);
748
- }
749
-
750
263
  function toggle() {
751
264
  isOpen.value ? deactivate() : activate();
752
265
  }
@@ -763,7 +276,7 @@ function deactivate() {
763
276
  }
764
277
 
765
278
  function activate() {
766
- if (props.isOpen || props.disabled) return;
279
+ if (isOpen.value || props.disabled) return;
767
280
 
768
281
  adjustPosition();
769
282
 
@@ -775,15 +288,17 @@ function activate() {
775
288
  nextTick(() => searchInputRef.value && searchInputRef.value.focus());
776
289
  }
777
290
 
778
- if (wrapperRef.value !== undefined && !props.searchable) wrapperRef.value.focus();
291
+ if (wrapperRef.value && !props.searchable) {
292
+ wrapperRef.value.focus();
293
+ }
779
294
 
780
295
  emit("open", elementId);
781
296
  }
782
297
 
783
298
  function adjustPosition() {
784
- if (typeof window === "undefined" || !dropdownListRef.value) return;
299
+ if (typeof window === "undefined" || !dropdownListRef.value || !wrapperRef.value) return;
785
300
 
786
- const dropdownHeight = dropdownListRef.value.wrapperRef.getBoundingClientRect().height;
301
+ const dropdownHeight = dropdownListRef.value.wrapperRef?.getBoundingClientRect().height || 0;
787
302
  const spaceAbove = wrapperRef.value.getBoundingClientRect().top;
788
303
  const spaceBelow = window.innerHeight - wrapperRef.value.getBoundingClientRect().bottom;
789
304
  const hasEnoughSpaceBelow = spaceBelow > dropdownHeight;
@@ -795,128 +310,466 @@ function adjustPosition() {
795
310
  }
796
311
  }
797
312
 
798
- function removeElement(option, shouldClose = true) {
313
+ function onMouseDownClearItem(event: MouseEvent, option: Option) {
314
+ if (props.disabled) return;
315
+
316
+ const value = Array.isArray(props.modelValue)
317
+ ? [...props.modelValue].filter((item) => {
318
+ if (typeof item === "object") {
319
+ return item[props.valueKey] !== option[props.valueKey];
320
+ }
321
+
322
+ return item !== option[props.valueKey];
323
+ })
324
+ : [];
325
+
326
+ emit("update:modelValue", value);
327
+ emit("change", { value, options: props.options });
328
+ emit("remove", option);
329
+ }
330
+
331
+ function onMouseDownClear() {
799
332
  if (props.disabled) return;
800
333
 
801
- if (props.clearable && !props.multiple) {
334
+ if (!props.clearable && !props.multiple) {
802
335
  deactivate();
803
336
 
804
337
  return;
805
338
  }
806
339
 
807
- let value = "";
340
+ const value = props.multiple ? [] : "";
341
+
342
+ emit("update:modelValue", value);
343
+ emit("change", { value, options: props.options });
344
+ emit("remove", props.options);
345
+ }
346
+
347
+ function setLabelPosition() {
348
+ if (
349
+ props.labelAlign === "top" ||
350
+ !hasSlotContent(slots["left"]) ||
351
+ (!hasSlotContent(slots["left-icon"]) && !props.leftIcon)
352
+ ) {
353
+ return;
354
+ }
355
+
356
+ if (!leftSlotWrapperRef.value || !innerWrapperRef.value || !labelComponentRef.value) {
357
+ return;
358
+ }
359
+
360
+ const leftSlotWidth = leftSlotWrapperRef.value.getBoundingClientRect().width;
361
+ const innerWrapperPaddingLeft = parseInt(
362
+ window.getComputedStyle(innerWrapperRef.value).paddingLeft,
363
+ );
364
+
365
+ const nestedLabel = labelComponentRef.value.labelElement;
366
+
367
+ if (props.multiple && Array.isArray(localValue.value) && localValue.value.length >= 1) {
368
+ if (nestedLabel) {
369
+ nestedLabel.style.left = `${leftSlotWidth - innerWrapperPaddingLeft}px`;
370
+ }
371
+
372
+ leftSlotWrapperRef.value.classList.remove("group-[]/placement-inside:-mt-4");
373
+ } else {
374
+ if (nestedLabel) {
375
+ nestedLabel.style.left = `${leftSlotWidth + innerWrapperPaddingLeft}px`;
376
+ }
377
+ }
378
+ }
379
+
380
+ defineExpose({
381
+ /**
382
+ * A reference to the dropdown list element for direct DOM manipulation.
383
+ * @property {HTMLElement}
384
+ */
385
+ dropdownListRef,
386
+
387
+ /**
388
+ * A reference to the wrapper element for direct DOM manipulation.
389
+ * @property {HTMLElement}
390
+ */
391
+ wrapperRef,
392
+
393
+ /**
394
+ * A reference to the search input element for direct DOM manipulation.
395
+ * @property {HTMLElement}
396
+ */
397
+ searchInputRef,
398
+
399
+ /**
400
+ * A reference to the label component for direct DOM manipulation.
401
+ * @property {HTMLElement}
402
+ */
403
+ labelComponentRef,
404
+
405
+ /**
406
+ * A reference to the left slot wrapper element for direct DOM manipulation.
407
+ * @property {HTMLElement}
408
+ */
409
+ leftSlotWrapperRef,
410
+
411
+ /**
412
+ * A reference to the inner wrapper element for direct DOM manipulation.
413
+ * @property {HTMLElement}
414
+ */
415
+ innerWrapperRef,
416
+ });
417
+
418
+ /**
419
+ * Get element / nested component attributes for each config token ✨
420
+ * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
421
+ */
422
+ const mutatedProps = computed(() => ({
423
+ error: Boolean(props.error),
424
+ label: Boolean(props.label),
425
+ /* component state, not a props */
426
+ selected: Boolean(selectedLabel.value),
427
+ opened: isOpen.value,
428
+ openedTop: isTop.value,
429
+ }));
430
+
431
+ const {
432
+ config,
433
+ selectLabelAttrs,
434
+ wrapperAttrs,
435
+ innerWrapperAttrs,
436
+ leftIconWrapperAttrs,
437
+ rightIconWrapperAttrs,
438
+ leftIconAttrs,
439
+ rightIconAttrs,
440
+ beforeCaretAttrs,
441
+ afterCaretAttrs,
442
+ toggleAttrs,
443
+ clearAttrs,
444
+ clearMultipleTextAttrs,
445
+ clearMultipleAttrs,
446
+ searchAttrs,
447
+ searchInputAttrs,
448
+ selectedLabelsAttrs,
449
+ selectedLabelAttrs,
450
+ dropdownListAttrs,
451
+ toggleIconAttrs,
452
+ clearIconAttrs,
453
+ clearMultipleIconAttrs,
454
+ } = useUI(defaultConfig, mutatedProps);
455
+ </script>
456
+
457
+ <template>
458
+ <ULabel
459
+ ref="labelComponentRef"
460
+ :for="elementId"
461
+ :size="size"
462
+ :label="label"
463
+ :error="error"
464
+ :description="description"
465
+ :align="labelAlign"
466
+ :disabled="disabled"
467
+ centred
468
+ v-bind="selectLabelAttrs"
469
+ :data-test="dataTest"
470
+ :tabindex="-1"
471
+ >
472
+ <div
473
+ ref="wrapperRef"
474
+ :tabindex="searchable || disabled ? -1 : 0"
475
+ role="combobox"
476
+ :aria-owns="'listbox-' + elementId"
477
+ v-bind="wrapperAttrs"
478
+ @focus="activate"
479
+ @blur="deactivate"
480
+ @keydown.self.down.prevent="dropdownListRef?.pointerForward"
481
+ @keydown.self.up.prevent="dropdownListRef?.pointerBackward"
482
+ @keydown.enter.tab.stop.self="dropdownListRef?.addPointerElement"
483
+ @keyup.esc="deactivate"
484
+ >
485
+ <!-- @slot Use it to add something right. -->
486
+ <slot name="right" />
487
+
488
+ <div v-if="hasSlotContent($slots['right-icon']) || rightIcon" v-bind="rightIconWrapperAttrs">
489
+ <!--
490
+ @slot Use it to add icon right.
491
+ @binding {string} icon-name
492
+ @binding {string} icon-size
493
+ -->
494
+ <slot name="right-icon" :icon-name="rightIcon" :icon-size="iconSize">
495
+ <UIcon
496
+ v-if="rightIcon"
497
+ :name="rightIcon"
498
+ :size="iconSize"
499
+ internal
500
+ v-bind="rightIconAttrs"
501
+ />
502
+ </slot>
503
+ </div>
504
+
505
+ <div
506
+ v-if="hasSlotContent($slots['after-caret']) && !(multiple && localValue?.length)"
507
+ v-bind="afterCaretAttrs"
508
+ :tabindex="-1"
509
+ >
510
+ <!--
511
+ @slot Use it to add something after caret.
512
+ @binding {object} scope-props
513
+ -->
514
+ <slot :scope-props="props" name="after-caret" />
515
+ </div>
516
+
517
+ <div
518
+ v-show="!multiple || (!isLocalValue && multiple)"
519
+ v-bind="toggleAttrs"
520
+ :tabindex="-1"
521
+ @mousedown.prevent.stop="toggle"
522
+ >
523
+ <!--
524
+ @slot Use it to add something instead of the toggle icon.
525
+ @binding {string} icon-name
526
+ @binding {string} icon-size
527
+ @binding {boolean} opened
528
+ -->
529
+ <slot
530
+ name="toggle"
531
+ :icon-name="config.defaults.dropdownIcon"
532
+ :icon-size="iconSize"
533
+ :opened="isOpen"
534
+ >
535
+ <UIcon
536
+ internal
537
+ interactive
538
+ color="gray"
539
+ :size="iconSize"
540
+ :name="config.defaults.dropdownIcon"
541
+ v-bind="toggleIconAttrs"
542
+ :tabindex="-1"
543
+ />
544
+ </slot>
545
+ </div>
546
+
547
+ <div
548
+ v-if="isLocalValue && clearable && !disabled && !multiple"
549
+ v-bind="clearAttrs"
550
+ @mousedown="onMouseDownClear"
551
+ >
552
+ <!--
553
+ @slot Use it to add something instead of the clear icon.
554
+ @binding {string} icon-name
555
+ @binding {string} icon-size
556
+ -->
557
+ <slot name="clear" :icon-name="config.defaults.clearIcon" :icon-size="iconSize">
558
+ <UIcon
559
+ internal
560
+ interactive
561
+ color="gray"
562
+ :size="iconSize"
563
+ :name="config.defaults.clearIcon"
564
+ v-bind="clearIconAttrs"
565
+ />
566
+ </slot>
567
+ </div>
568
+
569
+ <div
570
+ v-if="hasSlotContent($slots['before-caret']) && !(multiple && localValue?.length)"
571
+ v-bind="beforeCaretAttrs"
572
+ >
573
+ <!--
574
+ @slot Use it to add something before caret.
575
+ @binding {object} scope-props
576
+ -->
577
+ <slot :scope-props="props" name="before-caret" />
578
+ </div>
808
579
 
809
- if (props.multiple) {
810
- value = !Array.isArray(option)
811
- ? [...props.modelValue].filter((item) => item !== option[props.valueKey])
812
- : [];
813
- }
580
+ <div ref="innerWrapperRef" v-bind="innerWrapperAttrs">
581
+ <span
582
+ v-if="hasSlotContent($slots['left-icon']) || leftIcon"
583
+ ref="leftSlotWrapperRef"
584
+ v-bind="leftIconWrapperAttrs"
585
+ >
586
+ <!--
587
+ @slot Use it to add icon left.
588
+ @binding {string} icon-name
589
+ @binding {string} icon-size
590
+ -->
591
+ <slot name="left-icon" :icon-name="leftIcon" :icon-size="iconSize">
592
+ <UIcon
593
+ v-if="leftIcon"
594
+ :name="leftIcon"
595
+ :size="iconSize"
596
+ internal
597
+ v-bind="leftIconAttrs"
598
+ />
599
+ </slot>
600
+ </span>
814
601
 
815
- emit("update:modelValue", value);
816
- emit("change", { value, options: props.options });
817
- emit("remove", option, elementId);
602
+ <span v-if="hasSlotContent($slots['left'])" ref="leftSlotWrapperRef">
603
+ <!-- @slot Use it to add something left. -->
604
+ <slot name="left" />
605
+ </span>
818
606
 
819
- if (shouldClose) {
820
- deactivate();
821
- }
822
- }
607
+ <div v-if="multiple && localValue?.length" v-bind="selectedLabelsAttrs">
608
+ <span
609
+ v-for="item in localValue as Option[]"
610
+ :key="String(item[valueKey])"
611
+ v-bind="selectedLabelAttrs"
612
+ >
613
+ <!--
614
+ @slot Use it to customise selected value label.
615
+ @binding {string} selected-label
616
+ -->
617
+ <slot
618
+ name="selected-label"
619
+ :selected-label="getOptionLabel(item)"
620
+ :value="item[valueKey]"
621
+ :raw-option="item"
622
+ >
623
+ {{ getOptionLabel(item) }}
624
+ </slot>
823
625
 
824
- function setLabelPosition() {
825
- if (
826
- props.labelAlign === "top" ||
827
- !hasSlotContent(slots["left"]) ||
828
- (!hasSlotContent(slots["left-icon"]) && !props.leftIcon)
829
- ) {
830
- return;
831
- }
626
+ <!--
627
+ @slot Use it to add something after selected value label.
628
+ @binding {object} scope-props
629
+ -->
630
+ <slot :scope-props="props" name="selected-label-after" />
832
631
 
833
- const leftSlotWidth = leftSlotWrapperRef.value.getBoundingClientRect().width;
834
- const innerWrapperPaddingLeft = parseInt(
835
- window.getComputedStyle(innerWrapperRef.value).paddingLeft,
836
- );
632
+ <div
633
+ v-if="!disabled"
634
+ v-bind="clearMultipleAttrs"
635
+ @mousedown.prevent.capture
636
+ @click.prevent.capture
637
+ @mousedown="onMouseDownClearItem($event, item)"
638
+ >
639
+ <!--
640
+ @slot Use it to add something instead of the clear icon (when multiple prop enabled).
641
+ @binding {string} icon-name
642
+ @binding {string} icon-size
643
+ -->
644
+ <slot
645
+ name="clear-multiple"
646
+ :icon-name="config.defaults.clearMultipleIcon"
647
+ :icon-size="iconSize"
648
+ >
649
+ <UIcon
650
+ internal
651
+ interactive
652
+ color="gray"
653
+ :size="iconSize"
654
+ :name="config.defaults.clearMultipleIcon"
655
+ v-bind="clearMultipleIconAttrs"
656
+ />
657
+ </slot>
658
+ </div>
659
+ </span>
660
+ </div>
837
661
 
838
- if (props.multiple && localValue.value.length >= 1) {
839
- labelComponentRef.value.labelElement.style.left = `${leftSlotWidth - innerWrapperPaddingLeft}px`;
840
- leftSlotWrapperRef.value.classList.remove("group-[]/placement-inside:-mt-4");
841
- } else {
842
- labelComponentRef.value.labelElement.style.left = `${leftSlotWidth + innerWrapperPaddingLeft}px`;
843
- }
844
- }
662
+ <div v-bind="searchAttrs">
663
+ <input
664
+ v-show="searchable || !localValue || multiple || !isOpen"
665
+ :id="elementId"
666
+ ref="searchInputRef"
667
+ v-model="search"
668
+ type="text"
669
+ autocomplete="off"
670
+ :spellcheck="false"
671
+ :placeholder="inputPlaceholder"
672
+ :disabled="disabled"
673
+ :aria-controls="'listbox-' + elementId"
674
+ v-bind="searchInputAttrs"
675
+ @focus="activate"
676
+ @blur.prevent="deactivate"
677
+ @keyup.esc="deactivate"
678
+ @keydown.down.prevent="dropdownListRef?.pointerForward"
679
+ @keydown.up.prevent="dropdownListRef?.pointerBackward"
680
+ @keydown.enter.prevent.stop.self="dropdownListRef?.addPointerElement"
681
+ />
682
+ </div>
845
683
 
846
- defineExpose({
847
- /**
848
- * A reference to the dropdown list element for direct DOM manipulation.
849
- * @property {HTMLElement}
850
- */
851
- dropdownListRef,
684
+ <span
685
+ v-if="isSelectedValueLabelVisible"
686
+ v-bind="selectedLabelAttrs"
687
+ @mousedown.prevent="toggle"
688
+ >
689
+ <!--
690
+ @slot Use it to add selected value label.
691
+ @binding {string} selected-label
692
+ @binding {string} value
693
+ -->
694
+ <slot
695
+ name="selected-label"
696
+ :selected-label="selectedLabel"
697
+ :value="(localValue as Option)[valueKey]"
698
+ >
699
+ {{ selectedLabel }}
700
+ </slot>
852
701
 
853
- /**
854
- * A reference to the wrapper element for direct DOM manipulation.
855
- * @property {HTMLElement}
856
- */
857
- wrapperRef,
702
+ <!--
703
+ @slot Use it to add something after selected value label.
704
+ @binding {object} scope-props
705
+ -->
706
+ <slot :scope-props="props" name="selected-label-after" />
707
+ </span>
858
708
 
859
- /**
860
- * A reference to the search input element for direct DOM manipulation.
861
- * @property {HTMLElement}
862
- */
863
- searchInputRef,
709
+ <div
710
+ v-if="isLocalValue && clearable && !disabled && multiple"
711
+ v-bind="clearMultipleTextAttrs"
712
+ @mousedown.prevent.capture="onMouseDownClear"
713
+ @click.prevent.capture
714
+ v-text="currentLocale.clear"
715
+ />
716
+ </div>
864
717
 
865
- /**
866
- * A reference to the label component for direct DOM manipulation.
867
- * @property {HTMLElement}
868
- */
869
- labelComponentRef,
718
+ <UDropdownList
719
+ v-if="isOpen"
720
+ ref="dropdownListRef"
721
+ v-model="dropdownValue as string | number"
722
+ :options="filteredOptions"
723
+ :disabled="disabled"
724
+ :size="size"
725
+ :visible-options="visibleOptions"
726
+ :value-key="valueKey"
727
+ :label-key="labelKey"
728
+ :add-option="addOption"
729
+ tabindex="-1"
730
+ v-bind="dropdownListAttrs as KeyAttrsWithConfig<UDropdownListConfig>"
731
+ @add="onAddOption"
732
+ @focus="activate"
733
+ @mousedown.prevent.capture
734
+ @click.prevent.capture
735
+ >
736
+ <template #before-option="{ option, index }">
737
+ <!--
738
+ @slot Use it to add something before option.
739
+ @binding {object} option
740
+ @binding {number} index
741
+ -->
742
+ <slot name="before-option" :option="option" :index="index" />
743
+ </template>
870
744
 
871
- /**
872
- * A reference to the left slot wrapper element for direct DOM manipulation.
873
- * @property {HTMLElement}
874
- */
875
- leftSlotWrapperRef,
745
+ <template #option="{ option, index }">
746
+ <!--
747
+ @slot Use it to customise the option.
748
+ @binding {object} option
749
+ @binding {number} index
750
+ -->
751
+ <slot name="option" :option="option" :index="index" />
752
+ </template>
876
753
 
877
- /**
878
- * A reference to the inner wrapper element for direct DOM manipulation.
879
- * @property {HTMLElement}
880
- */
881
- innerWrapperRef,
882
- });
754
+ <template #after-option="{ option, index }">
755
+ <!--
756
+ @slot Use it to add something after option.
757
+ @binding {object} option
758
+ @binding {number} index
759
+ -->
760
+ <slot name="after-option" :option="option" :index="index" />
761
+ </template>
883
762
 
884
- /**
885
- * Get element / nested component attributes for each config token ✨
886
- * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
887
- */
888
- const mutatedProps = computed(() => ({
889
- error: Boolean(props.error),
890
- label: Boolean(props.label),
891
- /* component state, not a props */
892
- selected: Boolean(selectedLabel.value),
893
- opened: isOpen.value,
894
- openedTop: isTop.value,
895
- }));
763
+ <template #empty>
764
+ <template v-if="isEmpty">
765
+ {{ currentLocale.listIsEmpty }}
766
+ </template>
896
767
 
897
- // eslint-disable-next-line vue/no-dupe-keys
898
- const {
899
- config,
900
- selectLabelAttrs,
901
- wrapperAttrs,
902
- innerWrapperAttrs,
903
- leftIconWrapperAttrs,
904
- rightIconWrapperAttrs,
905
- leftIconAttrs,
906
- rightIconAttrs,
907
- beforeCaretAttrs,
908
- afterCaretAttrs,
909
- toggleAttrs,
910
- clearAttrs,
911
- clearMultipleTextAttrs,
912
- clearMultipleAttrs,
913
- searchAttrs,
914
- searchInputAttrs,
915
- selectedLabelsAttrs,
916
- selectedLabelAttrs,
917
- dropdownListAttrs,
918
- toggleIconAttrs,
919
- clearIconAttrs,
920
- clearMultipleIconAttrs,
921
- } = useUI(defaultConfig, mutatedProps);
922
- </script>
768
+ <template v-else>
769
+ {{ currentLocale.noDataToShow }}
770
+ </template>
771
+ </template>
772
+ </UDropdownList>
773
+ </div>
774
+ </ULabel>
775
+ </template>