design-system-next 2.11.22 → 2.12.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 (82) hide show
  1. package/dist/design-system-next.es.js +6328 -6212
  2. package/dist/design-system-next.es.js.gz +0 -0
  3. package/dist/design-system-next.umd.js +12 -12
  4. package/dist/design-system-next.umd.js.gz +0 -0
  5. package/dist/main.css +1 -1
  6. package/dist/main.css.gz +0 -0
  7. package/dist/package.json.d.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/assets/scripts/border-radius.ts +15 -15
  10. package/src/assets/scripts/colors.ts +134 -134
  11. package/src/assets/scripts/max-width.ts +11 -11
  12. package/src/assets/scripts/spacing.ts +23 -23
  13. package/src/assets/scripts/utilities.ts +15 -15
  14. package/src/components/accordion/accordion.ts +43 -43
  15. package/src/components/accordion/use-accordion.ts +43 -43
  16. package/src/components/avatar/avatar.ts +64 -64
  17. package/src/components/badge/badge.ts +43 -43
  18. package/src/components/banner/banner.ts +20 -20
  19. package/src/components/button/button.ts +72 -72
  20. package/src/components/button/button.vue +15 -15
  21. package/src/components/card/card.ts +52 -52
  22. package/src/components/checkbox/checkbox.ts +45 -45
  23. package/src/components/chips/chips.ts +95 -95
  24. package/src/components/collapsible/collapsible.ts +21 -21
  25. package/src/components/collapsible/collapsible.vue +27 -27
  26. package/src/components/date-picker/__tests__/date-picker.test.ts +112 -112
  27. package/src/components/date-picker/date-picker.ts +157 -157
  28. package/src/components/date-picker/date-picker.vue +60 -53
  29. package/src/components/date-picker/use-date-picker.ts +6 -0
  30. package/src/components/dropdown/__tests__/dropdown-fixes.spec.ts +106 -106
  31. package/src/components/dropdown/__tests__/dropdown-value-types.spec.ts +213 -213
  32. package/src/components/dropdown/fix-multi-number.ts +92 -92
  33. package/src/components/dropdown/use-dropdown.ts +488 -488
  34. package/src/components/empty-state/empty-state.ts +50 -50
  35. package/src/components/file-upload/file-upload.ts +87 -87
  36. package/src/components/floating-action/floating-action.ts +12 -12
  37. package/src/components/input/input-contact-number/input-contact-number.ts +83 -83
  38. package/src/components/input/input-email/input-email.vue +17 -17
  39. package/src/components/input/input-password/use-input-password.ts +19 -19
  40. package/src/components/input/input-search/input-search.vue +13 -13
  41. package/src/components/input/input-url/input-url.vue +20 -20
  42. package/src/components/input/input-username/input-username.vue +17 -17
  43. package/src/components/input/input.vue +72 -72
  44. package/src/components/list/ladderized-list/ladderized-list.ts +39 -39
  45. package/src/components/logo/logo.ts +43 -43
  46. package/src/components/logo/logo.vue +14 -14
  47. package/src/components/logo/use-logo.ts +41 -41
  48. package/src/components/modal/modal.ts +45 -45
  49. package/src/components/progress-bar/progress-bar.ts +39 -39
  50. package/src/components/radio/radio.ts +42 -42
  51. package/src/components/select/select.ts +144 -144
  52. package/src/components/sidepanel/sidepanel.vue +55 -55
  53. package/src/components/sidepanel/stacking-sidepanel/stacking-sidepanel.ts +16 -16
  54. package/src/components/sidepanel/stacking-sidepanel/stacking-sidepanel.vue +39 -39
  55. package/src/components/slider/slider.ts +38 -38
  56. package/src/components/snackbar/snack/snack.ts +71 -71
  57. package/src/components/snackbar/use-snackbar.ts +34 -34
  58. package/src/components/status/status.ts +19 -19
  59. package/src/components/status/status.vue +13 -13
  60. package/src/components/stepper/step/step.ts +47 -47
  61. package/src/components/stepper/stepper.ts +47 -47
  62. package/src/components/stepper/stepper.vue +34 -34
  63. package/src/components/switch/switch.ts +42 -42
  64. package/src/components/table/table-actions/table-actions.ts +42 -42
  65. package/src/components/table/table-actions/table-actions.vue +40 -40
  66. package/src/components/table/table-chips-title/table-chips-title.ts +27 -27
  67. package/src/components/table/table-chips-title/table-chips-title.vue +32 -32
  68. package/src/components/table/table-chips-title/use-table-chips-title.ts +22 -22
  69. package/src/components/table/table-lozenge-title/table-lozenge-title.ts +23 -23
  70. package/src/components/table/table-lozenge-title/table-lozenge-title.vue +26 -26
  71. package/src/components/table/table-lozenge-title/use-table-lozenge-title.ts +21 -21
  72. package/src/components/table/table-pagination/table-pagination.ts +63 -63
  73. package/src/components/table/table-pagination/table-pagination.vue +72 -72
  74. package/src/components/table/table.ts +173 -173
  75. package/src/components/tabs/tabs.ts +43 -43
  76. package/src/components/textarea/textarea.ts +72 -72
  77. package/src/components/textarea/textarea.vue +45 -45
  78. package/src/components/time-picker/time-picker.ts +69 -69
  79. package/src/components/tooltip/use-tooltip.ts +13 -13
  80. package/src/examples/dropdown-number-multi-select.vue +76 -76
  81. package/src/stores/useSnackbarStore.ts +44 -44
  82. package/src/vite-env.d.ts +6 -0
@@ -1,488 +1,488 @@
1
- import { ref, toRefs, computed, onMounted, watch } from 'vue';
2
- import { onClickOutside, useInfiniteScroll, useVModel } from '@vueuse/core';
3
-
4
- import type { SetupContext } from 'vue';
5
- import type { DropdownPropTypes, DropdownEmitTypes } from './dropdown';
6
- import type { MenuListType } from '../list/list';
7
-
8
- export const useDropdown = (props: DropdownPropTypes, emit: SetupContext<DropdownEmitTypes>['emit']) => {
9
- const { menuList, searchString, disabled, multiSelect, removeCurrentLevelInBackLabel, ladderized, textField, valueField } = toRefs(props);
10
-
11
- // Dropdown component ref variables
12
- const dropdownValue = useVModel(props, 'modelValue', emit); // v-model value of dropdown component
13
- const dropdownRef = ref<HTMLDivElement | null>(null);
14
-
15
- // List component ref variables
16
- const selectedListItems = ref<MenuListType[]>([]); // v-model value of the list component
17
- const dropdownMenuList = ref<MenuListType[]>([]); // menu list for the list component
18
-
19
- // Normalized value for internal use - always an array
20
- const normalizedValue = computed(() => {
21
- // If already an array, use it
22
- if (Array.isArray(dropdownValue.value)) {
23
- return dropdownValue.value;
24
- }
25
- // If not an array but has a value, make it a single-item array
26
- if (dropdownValue.value !== undefined && dropdownValue.value !== null) {
27
- return [dropdownValue.value];
28
- }
29
- // Default empty array
30
- return [];
31
- });
32
-
33
- // Compatibility layer for pre-selected items (List component expects string[] format)
34
- const compatPreSelectedItems = computed(() => {
35
- // For ladderized dropdown with search, handle the special format
36
- if (props.ladderized && Array.isArray(dropdownValue.value) && dropdownValue.value.length === 2) {
37
- // We return only the second value from the [subvalue, value] format which is the actual selected value
38
- return [dropdownValue.value[1]?.toString() || ''];
39
- }
40
-
41
- // For regular arrays (multi-select)
42
- if (Array.isArray(dropdownValue.value)) {
43
- return dropdownValue.value.map(item => {
44
- if (item === undefined || item === null) return '';
45
-
46
- // For numbers, preserve the original number value instead of converting to string
47
- if (typeof item === 'number') return item;
48
-
49
- // For objects, pass the original object reference
50
- // This is key for proper multi-select of objects
51
- if (typeof item === 'object') return item;
52
-
53
- // For strings, pass as is
54
- return item.toString();
55
- });
56
- }
57
-
58
- // For single values (single-select)
59
- return dropdownValue.value !== undefined && dropdownValue.value !== null
60
- ? [typeof dropdownValue.value === 'object' ?
61
- // Pass object reference directly instead of stringifying
62
- dropdownValue.value :
63
- // For numbers, preserve the original number value
64
- typeof dropdownValue.value === 'number' ?
65
- dropdownValue.value :
66
- dropdownValue.value.toString()]
67
- : [];
68
- });
69
-
70
- // Popper state
71
- const dropdownPopperState = ref<boolean>(false);
72
- const isDropdownPopperDisabled = computed(() => disabled.value);
73
-
74
- const isLadderizedSearch = computed(
75
- () => ladderized.value && searchString.value !== '' && normalizedValue.value.length === 0,
76
- );
77
-
78
- const processMenuList = () => {
79
- // Handle empty array or non-array values
80
- if (!menuList.value || !Array.isArray(menuList.value) || menuList.value.length === 0) {
81
- dropdownMenuList.value = [];
82
- return;
83
- }
84
-
85
- // If ladderized is true and menu list items already conform to MenuListType, don't transform
86
- if (ladderized.value) {
87
- // Verify the items have the required structure for ladderized lists
88
- const allValid = menuList.value.every(item =>
89
- typeof item === 'object' && item !== null && 'text' in item && 'value' in item
90
- );
91
-
92
- if (allValid) {
93
- dropdownMenuList.value = menuList.value as MenuListType[];
94
- } else {
95
- console.warn('Ladderized dropdown requires menu items in {text, value} format');
96
- dropdownMenuList.value = [];
97
- }
98
- return;
99
- }
100
-
101
- const firstItem = menuList.value[0];
102
-
103
- // Handle array of strings
104
- if (typeof firstItem === 'string') {
105
- dropdownMenuList.value = (menuList.value as string[]).map(item => ({
106
- text: item,
107
- value: item
108
- }));
109
- return;
110
- }
111
-
112
- // Handle array of numbers
113
- if (typeof firstItem === 'number') {
114
- dropdownMenuList.value = (menuList.value as number[]).map(item => ({
115
- text: item.toString(),
116
- value: item // Keep the value as a number instead of converting to string
117
- }));
118
- return;
119
- }
120
-
121
- // Handle array of objects with dynamic attributes
122
- if (typeof firstItem === 'object' && firstItem !== null) {
123
- // Check if it's already in MenuListType format
124
- if ('text' in firstItem && 'value' in firstItem) {
125
- dropdownMenuList.value = menuList.value as MenuListType[];
126
- return;
127
- }
128
-
129
- // Transform to MenuListType format using textField and valueField
130
- dropdownMenuList.value = (menuList.value as Record<string, unknown>[]).map(item => {
131
- const displayText = item[textField.value] || 'Unnamed';
132
- // Use the specified value field if available, otherwise use the entire object
133
- const itemValue = valueField.value && item[valueField.value] !== undefined
134
- ? item[valueField.value]
135
- : item;
136
-
137
- return {
138
- text: displayText,
139
- value: typeof itemValue === 'object' ? JSON.stringify(itemValue) : itemValue.toString(),
140
- _originalObject: item // Store the original object for reference
141
- };
142
- });
143
- return;
144
- }
145
-
146
- // Default fallback
147
- dropdownMenuList.value = menuList.value as MenuListType[];
148
- };
149
-
150
- watch(menuList, () => {
151
- processMenuList();
152
- });
153
-
154
- const handleSearch = () => {
155
- if (menuList.value && menuList.value.length === 0) {
156
- return;
157
- }
158
-
159
- if (!multiSelect.value) {
160
- singleSelectSearch();
161
- } else {
162
- // Process menu list for searching
163
- processMenuList();
164
-
165
- // Handle multi-select search - filter based on search string
166
- if (searchString.value.trim() !== '') {
167
- dropdownMenuList.value = getFilteredMenuList(dropdownMenuList.value);
168
- }
169
- }
170
- };
171
-
172
- const singleSelectSearch = () => {
173
- if (props.ladderized) {
174
- ladderizedSearch();
175
- } else {
176
- basicSearch();
177
- }
178
- };
179
-
180
- const basicSearch = () => {
181
- // Process menu list first
182
- processMenuList();
183
-
184
- // Then filter based on search string
185
- if (searchString.value.trim() !== '') {
186
- dropdownMenuList.value = getFilteredMenuList(dropdownMenuList.value);
187
- }
188
- };
189
-
190
- const ladderizedSearch = () => {
191
- //revert to initial list if search string is empty or dropdownValue is not empty
192
- if (searchString.value === '' || normalizedValue.value.length > 0) {
193
- dropdownMenuList.value = [...menuList.value] as MenuListType[];
194
- return;
195
- }
196
-
197
- const menuListSubLevels = getAllSublevelItems(menuList.value as MenuListType[]);
198
-
199
- const filteredMenuList = getFilteredMenuList(menuList.value as MenuListType[]);
200
- const filteredMenuListSubLevels = getFilteredMenuList(menuListSubLevels);
201
-
202
- if (filteredMenuList.length > 0) {
203
- //if there is a match at the top level of the menuList
204
- dropdownMenuList.value = getAllSublevelItems(filteredMenuList);
205
- } else if (filteredMenuListSubLevels.length > 0) {
206
- //if there is a match at the 2nd level (sublevel of a menuList item) of the menuList
207
- dropdownMenuList.value = filteredMenuListSubLevels;
208
- } else {
209
- dropdownMenuList.value = [];
210
- }
211
- };
212
-
213
- // compile sublevel items from menuList to a single array
214
- // and add text and value of the parent item to all sublevel items as subtext and subvalue
215
- const getAllSublevelItems = (menuList: MenuListType[]) => {
216
- return menuList.reduce<MenuListType[]>((currentValue, currentItem) => {
217
- if (currentItem.sublevel) {
218
- const mappedSublevel = currentItem.sublevel.map((sublevelItem: MenuListType) => ({
219
- ...sublevelItem, //text and value of a sublevel item
220
- subtext: currentItem.text, // text of parent of a sublevel item
221
- subvalue: typeof currentItem.value === 'string' ? currentItem.value : String(currentItem.value), // value of parent of a sublevel item as string
222
- }));
223
-
224
- return [...currentValue, ...mappedSublevel];
225
- }
226
-
227
- return currentValue;
228
- }, [] as MenuListType[]);
229
- };
230
-
231
- // filter list based on search string and menuList/sublevel texts
232
- const getFilteredMenuList = (list: MenuListType[]) => {
233
- return list.filter((item: MenuListType) => {
234
- const searchTerm = searchString.value.toLowerCase().trim();
235
- return item.text.toLowerCase().includes(searchTerm);
236
- });
237
- };
238
-
239
- watch(searchString, () => {
240
- handleSearch();
241
- });
242
-
243
- onClickOutside(dropdownRef, () => {
244
- dropdownPopperState.value = false;
245
- });
246
-
247
- useInfiniteScroll(
248
- dropdownRef,
249
- () => {
250
- emit('infinite-scroll-trigger', true);
251
- },
252
- { distance: 10 },
253
- );
254
-
255
- // Handle selected item for simple list component
256
- const handleSelectedItem = (selectedItems: MenuListType[]) => {
257
- if (!props.ladderized) {
258
- // Determine the type of value to emit based on the original data type and multiSelect
259
- if (multiSelect.value) {
260
- // For multi-select, always return an array
261
- const values = selectedItems.map(item => {
262
- // If we stored the original object, use it
263
- if ('_originalObject' in item) {
264
- return item._originalObject;
265
- }
266
-
267
- // For simple types, handle value type conversion properly
268
- const val = item.value;
269
-
270
- // If it's already a number, keep it as a number
271
- if (typeof val === 'number') {
272
- return val;
273
- }
274
-
275
- // For strings that look like numbers, convert them
276
- if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
277
- // Only convert if it looks like a proper number format
278
- if (/^-?\d+(\.\d+)?$/.test(val)) {
279
- return Number(val);
280
- }
281
- }
282
-
283
- // Return the original value for all other cases
284
- return val;
285
- });
286
-
287
- dropdownValue.value = values as (string | number | Record<string, unknown>)[];
288
- } else {
289
- // For single-select
290
- if (selectedItems.length === 0) {
291
- dropdownValue.value = props.multiSelect ? [] : '';
292
- return;
293
- }
294
-
295
- const item = selectedItems[0];
296
-
297
- // If we stored the original object, use it
298
- if ('_originalObject' in item) {
299
- dropdownValue.value = item._originalObject as Record<string, unknown>;
300
- } else {
301
- // For simple types, return the value (try to convert number strings to numbers)
302
- const val = item.value;
303
- if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
304
- dropdownValue.value = Number(val);
305
- } else {
306
- dropdownValue.value = val;
307
- }
308
- }
309
- }
310
- } else if (props.ladderized) {
311
- if (props.searchString !== '') {
312
- // generate dropdown value if ladderized with search string
313
- const subvalue = selectedItems[0]?.subvalue;
314
- const value = selectedItems[0]?.value;
315
- if (subvalue !== undefined && value !== undefined) {
316
- dropdownValue.value = [subvalue, value] as [string, string | number];
317
- }
318
- } else {
319
- // For regular ladderized dropdown selection without search
320
- if (selectedItems.length > 0) {
321
- const item = selectedItems[0];
322
- // Use the value directly for ladderized items
323
- if (item && item.value) {
324
- dropdownValue.value = item.value;
325
- }
326
- }
327
- }
328
- }
329
-
330
- // Always close dropdown for single selection, regardless of value type
331
- if (!multiSelect.value) {
332
- setTimeout(() => {
333
- dropdownPopperState.value = false;
334
- }, 10);
335
- }
336
- };
337
-
338
- // Handle selected item for ladderized list component
339
- const handleSelectedLadderizedItem = (selectedItems: string[]) => {
340
- // Update the model value with the selected ladderized items
341
- if (selectedItems.length > 0) {
342
- dropdownValue.value = selectedItems;
343
- }
344
-
345
- // If item is from last sublevel, close the dropdown
346
- if (checkIfItemFromLastSublevel(selectedItems)) {
347
- dropdownPopperState.value = false;
348
- }
349
- };
350
-
351
- const checkIfItemFromLastSublevel = (selectedItems: string[]) => {
352
- let selectedItemsObject = dropdownMenuList.value;
353
-
354
- // Traverse to the last item in the selectedItems array
355
- selectedItems.forEach((selectedItem) => {
356
- selectedItemsObject = selectedItemsObject.find((item) => selectedItem === item.value)?.sublevel ?? [];
357
- });
358
-
359
- // If there is a sublevel, return false
360
- if (selectedItemsObject.length > 0) {
361
- return false;
362
- }
363
-
364
- return true;
365
- };
366
-
367
- // Update selected items when model value changes externally
368
- const updateSelectedItemsFromValue = () => {
369
- if (!dropdownMenuList.value.length) return;
370
-
371
- const values = normalizedValue.value;
372
- if (!values || !values.length) {
373
- selectedListItems.value = [];
374
- return;
375
- }
376
-
377
- // Store both original values and string versions for flexible matching
378
- const valueData = values.map(val => {
379
- if (val === undefined || val === null) return { original: '', string: '' };
380
-
381
- // For objects, use JSON string representation
382
- if (typeof val === 'object') {
383
- return {
384
- original: val,
385
- string: JSON.stringify(val),
386
- isObject: true,
387
- id: 'id' in val ? val.id : undefined
388
- };
389
- }
390
-
391
- // For numbers and strings, keep original and string versions
392
- return {
393
- original: val,
394
- string: val.toString(),
395
- isObject: false
396
- };
397
- });
398
-
399
- // Extract just string values for comparison
400
- const valueStrings = valueData.map(v => v.string);
401
-
402
- if (props.ladderized) {
403
- // Special handling for ladderized dropdowns
404
- if (Array.isArray(dropdownValue.value) && dropdownValue.value.length === 2) {
405
- // Handle [subvalue, value] format used in ladderized dropdowns with search
406
- const subvalue = dropdownValue.value[0]?.toString() || '';
407
- const value = dropdownValue.value[1]?.toString() || '';
408
-
409
- selectedListItems.value = dropdownMenuList.value.filter(item => {
410
- return item.value === value && (!item.subvalue || item.subvalue === subvalue);
411
- });
412
- } else {
413
- // Regular ladderized dropdown value
414
- selectedListItems.value = dropdownMenuList.value.filter(item => {
415
- // Convert both to strings for comparison or check direct equality for numbers
416
- if (typeof item.value === 'number') {
417
- return valueData.some(v => v.original === item.value || v.string === String(item.value));
418
- } else {
419
- return valueStrings.includes(String(item.value));
420
- }
421
- });
422
- }
423
- } else {
424
- // Regular dropdown value
425
- selectedListItems.value = dropdownMenuList.value.filter(item => {
426
- // Handle objects with _originalObject property
427
- if ('_originalObject' in item && item._originalObject) {
428
- return valueData.some(v => {
429
- // If both are objects, compare by JSON string or by ID
430
- if (v.isObject && typeof v.original === 'object') {
431
- const originalObj = item._originalObject as Record<string, unknown>;
432
-
433
- // First try direct equality comparison
434
- if (v.original === originalObj) return true;
435
-
436
- // Try JSON string comparison
437
- const itemJson = JSON.stringify(originalObj);
438
- if (v.string === itemJson) return true;
439
-
440
- // Try ID-based comparison if both have ID fields
441
- if (v.id !== undefined && 'id' in originalObj) {
442
- return v.id === originalObj.id;
443
- }
444
- }
445
- return false;
446
- });
447
- }
448
-
449
- // Handle both numeric and string values correctly
450
- if (typeof item.value === 'number') {
451
- return valueData.some(v => v.original === item.value || v.string === String(item.value));
452
- } else {
453
- return valueStrings.includes(String(item.value));
454
- }
455
- });
456
- }
457
- };
458
-
459
- watch(dropdownValue, () => {
460
- updateSelectedItemsFromValue();
461
- });
462
-
463
- watch(dropdownMenuList, () => {
464
- updateSelectedItemsFromValue();
465
- });
466
-
467
- onMounted(() => {
468
- processMenuList();
469
-
470
- // Set initial selected items based on model value
471
- if (normalizedValue.value.length > 0) {
472
- updateSelectedItemsFromValue();
473
- }
474
- });
475
-
476
- return {
477
- dropdownPopperState,
478
- dropdownRef,
479
- dropdownMenuList,
480
- isDropdownPopperDisabled,
481
- selectedListItems,
482
- handleSelectedItem,
483
- handleSelectedLadderizedItem,
484
- dropdownValue: compatPreSelectedItems, // Use compatible format for lists
485
- removeCurrentLevelInBackLabel,
486
- isLadderizedSearch,
487
- };
488
- };
1
+ import { ref, toRefs, computed, onMounted, watch } from 'vue';
2
+ import { onClickOutside, useInfiniteScroll, useVModel } from '@vueuse/core';
3
+
4
+ import type { SetupContext } from 'vue';
5
+ import type { DropdownPropTypes, DropdownEmitTypes } from './dropdown';
6
+ import type { MenuListType } from '../list/list';
7
+
8
+ export const useDropdown = (props: DropdownPropTypes, emit: SetupContext<DropdownEmitTypes>['emit']) => {
9
+ const { menuList, searchString, disabled, multiSelect, removeCurrentLevelInBackLabel, ladderized, textField, valueField } = toRefs(props);
10
+
11
+ // Dropdown component ref variables
12
+ const dropdownValue = useVModel(props, 'modelValue', emit); // v-model value of dropdown component
13
+ const dropdownRef = ref<HTMLDivElement | null>(null);
14
+
15
+ // List component ref variables
16
+ const selectedListItems = ref<MenuListType[]>([]); // v-model value of the list component
17
+ const dropdownMenuList = ref<MenuListType[]>([]); // menu list for the list component
18
+
19
+ // Normalized value for internal use - always an array
20
+ const normalizedValue = computed(() => {
21
+ // If already an array, use it
22
+ if (Array.isArray(dropdownValue.value)) {
23
+ return dropdownValue.value;
24
+ }
25
+ // If not an array but has a value, make it a single-item array
26
+ if (dropdownValue.value !== undefined && dropdownValue.value !== null) {
27
+ return [dropdownValue.value];
28
+ }
29
+ // Default empty array
30
+ return [];
31
+ });
32
+
33
+ // Compatibility layer for pre-selected items (List component expects string[] format)
34
+ const compatPreSelectedItems = computed(() => {
35
+ // For ladderized dropdown with search, handle the special format
36
+ if (props.ladderized && Array.isArray(dropdownValue.value) && dropdownValue.value.length === 2) {
37
+ // We return only the second value from the [subvalue, value] format which is the actual selected value
38
+ return [dropdownValue.value[1]?.toString() || ''];
39
+ }
40
+
41
+ // For regular arrays (multi-select)
42
+ if (Array.isArray(dropdownValue.value)) {
43
+ return dropdownValue.value.map(item => {
44
+ if (item === undefined || item === null) return '';
45
+
46
+ // For numbers, preserve the original number value instead of converting to string
47
+ if (typeof item === 'number') return item;
48
+
49
+ // For objects, pass the original object reference
50
+ // This is key for proper multi-select of objects
51
+ if (typeof item === 'object') return item;
52
+
53
+ // For strings, pass as is
54
+ return item.toString();
55
+ });
56
+ }
57
+
58
+ // For single values (single-select)
59
+ return dropdownValue.value !== undefined && dropdownValue.value !== null
60
+ ? [typeof dropdownValue.value === 'object' ?
61
+ // Pass object reference directly instead of stringifying
62
+ dropdownValue.value :
63
+ // For numbers, preserve the original number value
64
+ typeof dropdownValue.value === 'number' ?
65
+ dropdownValue.value :
66
+ dropdownValue.value.toString()]
67
+ : [];
68
+ });
69
+
70
+ // Popper state
71
+ const dropdownPopperState = ref<boolean>(false);
72
+ const isDropdownPopperDisabled = computed(() => disabled.value);
73
+
74
+ const isLadderizedSearch = computed(
75
+ () => ladderized.value && searchString.value !== '' && normalizedValue.value.length === 0,
76
+ );
77
+
78
+ const processMenuList = () => {
79
+ // Handle empty array or non-array values
80
+ if (!menuList.value || !Array.isArray(menuList.value) || menuList.value.length === 0) {
81
+ dropdownMenuList.value = [];
82
+ return;
83
+ }
84
+
85
+ // If ladderized is true and menu list items already conform to MenuListType, don't transform
86
+ if (ladderized.value) {
87
+ // Verify the items have the required structure for ladderized lists
88
+ const allValid = menuList.value.every(item =>
89
+ typeof item === 'object' && item !== null && 'text' in item && 'value' in item
90
+ );
91
+
92
+ if (allValid) {
93
+ dropdownMenuList.value = menuList.value as MenuListType[];
94
+ } else {
95
+ console.warn('Ladderized dropdown requires menu items in {text, value} format');
96
+ dropdownMenuList.value = [];
97
+ }
98
+ return;
99
+ }
100
+
101
+ const firstItem = menuList.value[0];
102
+
103
+ // Handle array of strings
104
+ if (typeof firstItem === 'string') {
105
+ dropdownMenuList.value = (menuList.value as string[]).map(item => ({
106
+ text: item,
107
+ value: item
108
+ }));
109
+ return;
110
+ }
111
+
112
+ // Handle array of numbers
113
+ if (typeof firstItem === 'number') {
114
+ dropdownMenuList.value = (menuList.value as number[]).map(item => ({
115
+ text: item.toString(),
116
+ value: item // Keep the value as a number instead of converting to string
117
+ }));
118
+ return;
119
+ }
120
+
121
+ // Handle array of objects with dynamic attributes
122
+ if (typeof firstItem === 'object' && firstItem !== null) {
123
+ // Check if it's already in MenuListType format
124
+ if ('text' in firstItem && 'value' in firstItem) {
125
+ dropdownMenuList.value = menuList.value as MenuListType[];
126
+ return;
127
+ }
128
+
129
+ // Transform to MenuListType format using textField and valueField
130
+ dropdownMenuList.value = (menuList.value as Record<string, unknown>[]).map(item => {
131
+ const displayText = item[textField.value] || 'Unnamed';
132
+ // Use the specified value field if available, otherwise use the entire object
133
+ const itemValue = valueField.value && item[valueField.value] !== undefined
134
+ ? item[valueField.value]
135
+ : item;
136
+
137
+ return {
138
+ text: displayText,
139
+ value: typeof itemValue === 'object' ? JSON.stringify(itemValue) : itemValue.toString(),
140
+ _originalObject: item // Store the original object for reference
141
+ };
142
+ });
143
+ return;
144
+ }
145
+
146
+ // Default fallback
147
+ dropdownMenuList.value = menuList.value as MenuListType[];
148
+ };
149
+
150
+ watch(menuList, () => {
151
+ processMenuList();
152
+ });
153
+
154
+ const handleSearch = () => {
155
+ if (menuList.value && menuList.value.length === 0) {
156
+ return;
157
+ }
158
+
159
+ if (!multiSelect.value) {
160
+ singleSelectSearch();
161
+ } else {
162
+ // Process menu list for searching
163
+ processMenuList();
164
+
165
+ // Handle multi-select search - filter based on search string
166
+ if (searchString.value.trim() !== '') {
167
+ dropdownMenuList.value = getFilteredMenuList(dropdownMenuList.value);
168
+ }
169
+ }
170
+ };
171
+
172
+ const singleSelectSearch = () => {
173
+ if (props.ladderized) {
174
+ ladderizedSearch();
175
+ } else {
176
+ basicSearch();
177
+ }
178
+ };
179
+
180
+ const basicSearch = () => {
181
+ // Process menu list first
182
+ processMenuList();
183
+
184
+ // Then filter based on search string
185
+ if (searchString.value.trim() !== '') {
186
+ dropdownMenuList.value = getFilteredMenuList(dropdownMenuList.value);
187
+ }
188
+ };
189
+
190
+ const ladderizedSearch = () => {
191
+ //revert to initial list if search string is empty or dropdownValue is not empty
192
+ if (searchString.value === '' || normalizedValue.value.length > 0) {
193
+ dropdownMenuList.value = [...menuList.value] as MenuListType[];
194
+ return;
195
+ }
196
+
197
+ const menuListSubLevels = getAllSublevelItems(menuList.value as MenuListType[]);
198
+
199
+ const filteredMenuList = getFilteredMenuList(menuList.value as MenuListType[]);
200
+ const filteredMenuListSubLevels = getFilteredMenuList(menuListSubLevels);
201
+
202
+ if (filteredMenuList.length > 0) {
203
+ //if there is a match at the top level of the menuList
204
+ dropdownMenuList.value = getAllSublevelItems(filteredMenuList);
205
+ } else if (filteredMenuListSubLevels.length > 0) {
206
+ //if there is a match at the 2nd level (sublevel of a menuList item) of the menuList
207
+ dropdownMenuList.value = filteredMenuListSubLevels;
208
+ } else {
209
+ dropdownMenuList.value = [];
210
+ }
211
+ };
212
+
213
+ // compile sublevel items from menuList to a single array
214
+ // and add text and value of the parent item to all sublevel items as subtext and subvalue
215
+ const getAllSublevelItems = (menuList: MenuListType[]) => {
216
+ return menuList.reduce<MenuListType[]>((currentValue, currentItem) => {
217
+ if (currentItem.sublevel) {
218
+ const mappedSublevel = currentItem.sublevel.map((sublevelItem: MenuListType) => ({
219
+ ...sublevelItem, //text and value of a sublevel item
220
+ subtext: currentItem.text, // text of parent of a sublevel item
221
+ subvalue: typeof currentItem.value === 'string' ? currentItem.value : String(currentItem.value), // value of parent of a sublevel item as string
222
+ }));
223
+
224
+ return [...currentValue, ...mappedSublevel];
225
+ }
226
+
227
+ return currentValue;
228
+ }, [] as MenuListType[]);
229
+ };
230
+
231
+ // filter list based on search string and menuList/sublevel texts
232
+ const getFilteredMenuList = (list: MenuListType[]) => {
233
+ return list.filter((item: MenuListType) => {
234
+ const searchTerm = searchString.value.toLowerCase().trim();
235
+ return item.text.toLowerCase().includes(searchTerm);
236
+ });
237
+ };
238
+
239
+ watch(searchString, () => {
240
+ handleSearch();
241
+ });
242
+
243
+ onClickOutside(dropdownRef, () => {
244
+ dropdownPopperState.value = false;
245
+ });
246
+
247
+ useInfiniteScroll(
248
+ dropdownRef,
249
+ () => {
250
+ emit('infinite-scroll-trigger', true);
251
+ },
252
+ { distance: 10 },
253
+ );
254
+
255
+ // Handle selected item for simple list component
256
+ const handleSelectedItem = (selectedItems: MenuListType[]) => {
257
+ if (!props.ladderized) {
258
+ // Determine the type of value to emit based on the original data type and multiSelect
259
+ if (multiSelect.value) {
260
+ // For multi-select, always return an array
261
+ const values = selectedItems.map(item => {
262
+ // If we stored the original object, use it
263
+ if ('_originalObject' in item) {
264
+ return item._originalObject;
265
+ }
266
+
267
+ // For simple types, handle value type conversion properly
268
+ const val = item.value;
269
+
270
+ // If it's already a number, keep it as a number
271
+ if (typeof val === 'number') {
272
+ return val;
273
+ }
274
+
275
+ // For strings that look like numbers, convert them
276
+ if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
277
+ // Only convert if it looks like a proper number format
278
+ if (/^-?\d+(\.\d+)?$/.test(val)) {
279
+ return Number(val);
280
+ }
281
+ }
282
+
283
+ // Return the original value for all other cases
284
+ return val;
285
+ });
286
+
287
+ dropdownValue.value = values as (string | number | Record<string, unknown>)[];
288
+ } else {
289
+ // For single-select
290
+ if (selectedItems.length === 0) {
291
+ dropdownValue.value = props.multiSelect ? [] : '';
292
+ return;
293
+ }
294
+
295
+ const item = selectedItems[0];
296
+
297
+ // If we stored the original object, use it
298
+ if ('_originalObject' in item) {
299
+ dropdownValue.value = item._originalObject as Record<string, unknown>;
300
+ } else {
301
+ // For simple types, return the value (try to convert number strings to numbers)
302
+ const val = item.value;
303
+ if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
304
+ dropdownValue.value = Number(val);
305
+ } else {
306
+ dropdownValue.value = val;
307
+ }
308
+ }
309
+ }
310
+ } else if (props.ladderized) {
311
+ if (props.searchString !== '') {
312
+ // generate dropdown value if ladderized with search string
313
+ const subvalue = selectedItems[0]?.subvalue;
314
+ const value = selectedItems[0]?.value;
315
+ if (subvalue !== undefined && value !== undefined) {
316
+ dropdownValue.value = [subvalue, value] as [string, string | number];
317
+ }
318
+ } else {
319
+ // For regular ladderized dropdown selection without search
320
+ if (selectedItems.length > 0) {
321
+ const item = selectedItems[0];
322
+ // Use the value directly for ladderized items
323
+ if (item && item.value) {
324
+ dropdownValue.value = item.value;
325
+ }
326
+ }
327
+ }
328
+ }
329
+
330
+ // Always close dropdown for single selection, regardless of value type
331
+ if (!multiSelect.value) {
332
+ setTimeout(() => {
333
+ dropdownPopperState.value = false;
334
+ }, 10);
335
+ }
336
+ };
337
+
338
+ // Handle selected item for ladderized list component
339
+ const handleSelectedLadderizedItem = (selectedItems: string[]) => {
340
+ // Update the model value with the selected ladderized items
341
+ if (selectedItems.length > 0) {
342
+ dropdownValue.value = selectedItems;
343
+ }
344
+
345
+ // If item is from last sublevel, close the dropdown
346
+ if (checkIfItemFromLastSublevel(selectedItems)) {
347
+ dropdownPopperState.value = false;
348
+ }
349
+ };
350
+
351
+ const checkIfItemFromLastSublevel = (selectedItems: string[]) => {
352
+ let selectedItemsObject = dropdownMenuList.value;
353
+
354
+ // Traverse to the last item in the selectedItems array
355
+ selectedItems.forEach((selectedItem) => {
356
+ selectedItemsObject = selectedItemsObject.find((item) => selectedItem === item.value)?.sublevel ?? [];
357
+ });
358
+
359
+ // If there is a sublevel, return false
360
+ if (selectedItemsObject.length > 0) {
361
+ return false;
362
+ }
363
+
364
+ return true;
365
+ };
366
+
367
+ // Update selected items when model value changes externally
368
+ const updateSelectedItemsFromValue = () => {
369
+ if (!dropdownMenuList.value.length) return;
370
+
371
+ const values = normalizedValue.value;
372
+ if (!values || !values.length) {
373
+ selectedListItems.value = [];
374
+ return;
375
+ }
376
+
377
+ // Store both original values and string versions for flexible matching
378
+ const valueData = values.map(val => {
379
+ if (val === undefined || val === null) return { original: '', string: '' };
380
+
381
+ // For objects, use JSON string representation
382
+ if (typeof val === 'object') {
383
+ return {
384
+ original: val,
385
+ string: JSON.stringify(val),
386
+ isObject: true,
387
+ id: 'id' in val ? val.id : undefined
388
+ };
389
+ }
390
+
391
+ // For numbers and strings, keep original and string versions
392
+ return {
393
+ original: val,
394
+ string: val.toString(),
395
+ isObject: false
396
+ };
397
+ });
398
+
399
+ // Extract just string values for comparison
400
+ const valueStrings = valueData.map(v => v.string);
401
+
402
+ if (props.ladderized) {
403
+ // Special handling for ladderized dropdowns
404
+ if (Array.isArray(dropdownValue.value) && dropdownValue.value.length === 2) {
405
+ // Handle [subvalue, value] format used in ladderized dropdowns with search
406
+ const subvalue = dropdownValue.value[0]?.toString() || '';
407
+ const value = dropdownValue.value[1]?.toString() || '';
408
+
409
+ selectedListItems.value = dropdownMenuList.value.filter(item => {
410
+ return item.value === value && (!item.subvalue || item.subvalue === subvalue);
411
+ });
412
+ } else {
413
+ // Regular ladderized dropdown value
414
+ selectedListItems.value = dropdownMenuList.value.filter(item => {
415
+ // Convert both to strings for comparison or check direct equality for numbers
416
+ if (typeof item.value === 'number') {
417
+ return valueData.some(v => v.original === item.value || v.string === String(item.value));
418
+ } else {
419
+ return valueStrings.includes(String(item.value));
420
+ }
421
+ });
422
+ }
423
+ } else {
424
+ // Regular dropdown value
425
+ selectedListItems.value = dropdownMenuList.value.filter(item => {
426
+ // Handle objects with _originalObject property
427
+ if ('_originalObject' in item && item._originalObject) {
428
+ return valueData.some(v => {
429
+ // If both are objects, compare by JSON string or by ID
430
+ if (v.isObject && typeof v.original === 'object') {
431
+ const originalObj = item._originalObject as Record<string, unknown>;
432
+
433
+ // First try direct equality comparison
434
+ if (v.original === originalObj) return true;
435
+
436
+ // Try JSON string comparison
437
+ const itemJson = JSON.stringify(originalObj);
438
+ if (v.string === itemJson) return true;
439
+
440
+ // Try ID-based comparison if both have ID fields
441
+ if (v.id !== undefined && 'id' in originalObj) {
442
+ return v.id === originalObj.id;
443
+ }
444
+ }
445
+ return false;
446
+ });
447
+ }
448
+
449
+ // Handle both numeric and string values correctly
450
+ if (typeof item.value === 'number') {
451
+ return valueData.some(v => v.original === item.value || v.string === String(item.value));
452
+ } else {
453
+ return valueStrings.includes(String(item.value));
454
+ }
455
+ });
456
+ }
457
+ };
458
+
459
+ watch(dropdownValue, () => {
460
+ updateSelectedItemsFromValue();
461
+ });
462
+
463
+ watch(dropdownMenuList, () => {
464
+ updateSelectedItemsFromValue();
465
+ });
466
+
467
+ onMounted(() => {
468
+ processMenuList();
469
+
470
+ // Set initial selected items based on model value
471
+ if (normalizedValue.value.length > 0) {
472
+ updateSelectedItemsFromValue();
473
+ }
474
+ });
475
+
476
+ return {
477
+ dropdownPopperState,
478
+ dropdownRef,
479
+ dropdownMenuList,
480
+ isDropdownPopperDisabled,
481
+ selectedListItems,
482
+ handleSelectedItem,
483
+ handleSelectedLadderizedItem,
484
+ dropdownValue: compatPreSelectedItems, // Use compatible format for lists
485
+ removeCurrentLevelInBackLabel,
486
+ isLadderizedSearch,
487
+ };
488
+ };