design-system-next 2.7.43 → 2.7.44

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.
@@ -0,0 +1,105 @@
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+ import type { MenuListType } from '../list/list';
3
+
4
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
5
+
6
+ const GROUPED_ITEMS_BY_TYPES = ['A-Z', 'Z-A'] as const;
7
+
8
+ const PLACEMENTS_TYPES = [
9
+ 'auto',
10
+ 'auto-start',
11
+ 'auto-end',
12
+ 'top',
13
+ 'top-start',
14
+ 'top-end',
15
+ 'right',
16
+ 'right-start',
17
+ 'right-end',
18
+ 'bottom',
19
+ 'bottom-start',
20
+ 'bottom-end',
21
+ 'left',
22
+ 'left-start',
23
+ 'left-end',
24
+ ] as const;
25
+
26
+ const POPPER_STRATEGY_TYPES = ['fixed', 'absolute'] as const;
27
+
28
+ export const selectPropTypes = {
29
+ id: {
30
+ type: String,
31
+ required: true,
32
+ },
33
+ modelValue: {
34
+ type: [String, Number, Object, Array] as PropType<
35
+ string | number | Record<string, unknown> | (string | number | Record<string, unknown>)[]
36
+ >,
37
+ default: () => [],
38
+ },
39
+ menuList: {
40
+ type: Array as PropType<MenuListType[] | string[] | Record<string, unknown>[]>,
41
+ required: true,
42
+ default: [],
43
+ },
44
+ groupItemsBy: {
45
+ type: String as PropType<(typeof GROUPED_ITEMS_BY_TYPES)[number]>,
46
+ validator: (value: (typeof GROUPED_ITEMS_BY_TYPES)[number] | undefined) => {
47
+ return value === undefined || GROUPED_ITEMS_BY_TYPES.includes(value);
48
+ },
49
+ },
50
+ textField: {
51
+ type: String,
52
+ default: 'text',
53
+ description: 'Field name to use for display text when using dynamic object arrays',
54
+ },
55
+ valueField: {
56
+ type: String,
57
+ default: 'value',
58
+ description: 'Field name to use for value when using dynamic object arrays',
59
+ },
60
+ placeholder: {
61
+ type: String,
62
+ },
63
+ searchString: {
64
+ type: String,
65
+ default: '',
66
+ },
67
+ placement: {
68
+ type: String as PropType<(typeof PLACEMENTS_TYPES)[number]>,
69
+ validator: (value: (typeof PLACEMENTS_TYPES)[number]) => PLACEMENTS_TYPES.includes(value),
70
+ default: 'bottom',
71
+ },
72
+ popperStrategy: {
73
+ type: String,
74
+ validator: (value: 'fixed' | 'absolute') => POPPER_STRATEGY_TYPES.includes(value),
75
+ default: 'absolute',
76
+ },
77
+ popperWidth: {
78
+ type: String,
79
+ default: '100%',
80
+ },
81
+ width: {
82
+ type: String,
83
+ default: '100%',
84
+ },
85
+ wrapperPosition: {
86
+ type: String,
87
+ default: 'relative',
88
+ },
89
+ disabled: {
90
+ type: Boolean,
91
+ default: false,
92
+ },
93
+ readonly: {
94
+ type: Boolean,
95
+ default: false,
96
+ },
97
+ };
98
+
99
+ export const selectEmitTypes = {
100
+ 'infinite-scroll-trigger': Boolean,
101
+ 'update:modelValue': (_value: unknown) => true, // Accept any type of value
102
+ };
103
+
104
+ export type SelectPropTypes = ExtractPropTypes<typeof selectPropTypes>;
105
+ export type SelectEmitTypes = typeof selectEmitTypes;
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <Menu
3
+ :shown="selectPopperState"
4
+ aria-id="select-wrapper"
5
+ distance="4"
6
+ :placement="props.placement"
7
+ :triggers="[]"
8
+ :popper-hide-triggers="[]"
9
+ :auto-hide="false"
10
+ :disabled="isSelectPopperDisabled"
11
+ :container="`#${props.id}`"
12
+ :strategy="
13
+ props.popperStrategy === 'fixed' || props.popperStrategy === 'absolute' ? props.popperStrategy : 'absolute'
14
+ "
15
+ :delay="0"
16
+ :style="{
17
+ position: props.wrapperPosition,
18
+ width: props.width,
19
+ }"
20
+ >
21
+ <div @click="selectPopperState = true">
22
+ {{ selectedListItems }}
23
+
24
+ <spr-input
25
+ v-model="inputText"
26
+ label="Select Numbers"
27
+ :placeholder="props.placeholder"
28
+ :readonly="props.readonly"
29
+ :disabled="props.disabled"
30
+ autocomplete="off"
31
+ />
32
+ </div>
33
+
34
+ <div
35
+ :id="props.id"
36
+ :style="{
37
+ width: props.popperWidth,
38
+ }"
39
+ ></div>
40
+
41
+ <template #popper>
42
+ <div
43
+ ref="selectRef"
44
+ class="spr-grid spr-max-h-[300px] spr-gap-0.5 spr-overflow-y-auto spr-overflow-x-hidden spr-p-2"
45
+ >
46
+ <template v-if="selectMenuList.length > 0">
47
+ <spr-list
48
+ v-model="selectedListItems"
49
+ :menu-list="selectMenuList"
50
+ :group-items-by="props.groupItemsBy"
51
+ :pre-selected-items="selectValue"
52
+ @update:model-value="handleSelectedItem"
53
+ />
54
+ </template>
55
+ <template v-else>
56
+ <div class="spr-flex spr-items-center spr-justify-center spr-p-2 spr-text-center">
57
+ <span class="spr-body-sm-regular spr-m-0">No results found</span>
58
+ </div>
59
+ </template>
60
+ </div>
61
+ </template>
62
+ </Menu>
63
+ </template>
64
+
65
+ <script lang="ts" setup>
66
+ import { Menu } from 'floating-vue';
67
+
68
+ import 'floating-vue/dist/style.css';
69
+
70
+ import SprInput from '../input/input.vue';
71
+ import SprList from '../list/list.vue';
72
+
73
+ import { selectPropTypes, selectEmitTypes } from './select';
74
+
75
+ import { useSelect } from './use-select';
76
+
77
+ const props = defineProps(selectPropTypes);
78
+ const emit = defineEmits(selectEmitTypes);
79
+
80
+ const {
81
+ selectPopperState,
82
+ selectRef,
83
+ selectMenuList,
84
+ isSelectPopperDisabled,
85
+ selectedListItems,
86
+ handleSelectedItem,
87
+ selectValue,
88
+ inputText,
89
+ } = useSelect(props, emit);
90
+ </script>
@@ -0,0 +1,445 @@
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 { SelectPropTypes, SelectEmitTypes } from './select';
6
+ import type { MenuListType } from '../list/list';
7
+
8
+ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitTypes>['emit']) => {
9
+ const { menuList, searchString, disabled, textField, valueField } = toRefs(props);
10
+
11
+ const selectRef = ref<HTMLDivElement | null>(null);
12
+ const selectPopperState = ref<boolean>(false);
13
+ const isSelectPopperDisabled = computed(() => disabled.value);
14
+
15
+ const selectModel = useVModel(props, 'modelValue', emit);
16
+ const inputText = ref('');
17
+ const selectedListItems = ref<MenuListType[]>([]);
18
+ const selectMenuList = ref<MenuListType[]>([]);
19
+
20
+ // Normalized value for internal use - always an array
21
+ const normalizedValue = computed(() => {
22
+ // If already an array, use it
23
+ if (Array.isArray(selectModel.value)) {
24
+ return selectModel.value;
25
+ }
26
+
27
+ // If not an array but has a value, make it a single-item array
28
+ if (selectModel.value !== undefined && selectModel.value !== null) {
29
+ return [selectModel.value];
30
+ }
31
+
32
+ // Default empty array
33
+ return [];
34
+ });
35
+
36
+ // Compatibility layer for pre-selected items (List component expects string[] format)
37
+ const compatPreSelectedItems = computed(() => {
38
+ return selectModel.value !== undefined && selectModel.value !== null
39
+ ? [
40
+ typeof selectModel.value === 'object'
41
+ ? selectModel.value
42
+ : typeof selectModel.value === 'number'
43
+ ? selectModel.value
44
+ : selectModel.value.toString(),
45
+ ]
46
+ : [];
47
+ });
48
+
49
+ const processMenuList = () => {
50
+ // Handle empty array or non-array values
51
+ if (!menuList.value || !Array.isArray(menuList.value) || menuList.value.length === 0) {
52
+ selectMenuList.value = [];
53
+ return;
54
+ }
55
+
56
+ const firstItem = menuList.value[0];
57
+
58
+ // Handle array of strings
59
+ if (typeof firstItem === 'string') {
60
+ selectMenuList.value = (menuList.value as string[]).map((item) => ({
61
+ text: item,
62
+ value: item,
63
+ }));
64
+
65
+ return;
66
+ }
67
+
68
+ // Handle array of numbers
69
+ if (typeof firstItem === 'number') {
70
+ selectMenuList.value = (menuList.value as number[]).map((item) => ({
71
+ text: item.toString(),
72
+ value: item, // Keep the value as a number instead of converting to string
73
+ }));
74
+ return;
75
+ }
76
+
77
+ // Handle array of objects with dynamic attributes
78
+ if (typeof firstItem === 'object' && firstItem !== null) {
79
+ // Check if it's already in MenuListType format
80
+ if ('text' in firstItem && 'value' in firstItem) {
81
+ selectMenuList.value = menuList.value as MenuListType[];
82
+
83
+ return;
84
+ }
85
+
86
+ // Transform to MenuListType format using textField and valueField
87
+ selectMenuList.value = (menuList.value as Record<string, unknown>[]).map((item) => {
88
+ const displayText = item[textField.value] || 'Unnamed';
89
+ // Use the specified value field if available, otherwise use the entire object
90
+ const itemValue = valueField.value && item[valueField.value] !== undefined ? item[valueField.value] : item;
91
+
92
+ return {
93
+ text: displayText,
94
+ value: typeof itemValue === 'object' ? JSON.stringify(itemValue) : itemValue.toString(),
95
+ _originalObject: item, // Store the original object for reference
96
+ };
97
+ });
98
+
99
+ return;
100
+ }
101
+
102
+ // Default fallback
103
+ selectMenuList.value = menuList.value as MenuListType[];
104
+ };
105
+
106
+ watch(menuList, () => {
107
+ processMenuList();
108
+ });
109
+
110
+ const handleSearch = () => {
111
+ if (menuList.value && menuList.value.length === 0) {
112
+ return;
113
+ }
114
+
115
+ if (!multiSelect.value) {
116
+ singleSelectSearch();
117
+ } else {
118
+ // Process menu list for searching
119
+ processMenuList();
120
+
121
+ // Handle multi-select search - filter based on search string
122
+ if (searchString.value.trim() !== '') {
123
+ selectMenuList.value = getFilteredMenuList(selectMenuList.value);
124
+ }
125
+ }
126
+ };
127
+
128
+ const singleSelectSearch = () => {
129
+ if (props.ladderized) {
130
+ ladderizedSearch();
131
+ } else {
132
+ basicSearch();
133
+ }
134
+ };
135
+
136
+ const basicSearch = () => {
137
+ // Process menu list first
138
+ processMenuList();
139
+
140
+ // Then filter based on search string
141
+ if (searchString.value.trim() !== '') {
142
+ selectMenuList.value = getFilteredMenuList(selectMenuList.value);
143
+ }
144
+ };
145
+
146
+ const ladderizedSearch = () => {
147
+ //revert to initial list if search string is empty or selectModel is not empty
148
+ if (searchString.value === '' || normalizedValue.value.length > 0) {
149
+ selectMenuList.value = [...menuList.value] as MenuListType[];
150
+ return;
151
+ }
152
+
153
+ const menuListSubLevels = getAllSublevelItems(menuList.value as MenuListType[]);
154
+
155
+ const filteredMenuList = getFilteredMenuList(menuList.value as MenuListType[]);
156
+ const filteredMenuListSubLevels = getFilteredMenuList(menuListSubLevels);
157
+
158
+ if (filteredMenuList.length > 0) {
159
+ //if there is a match at the top level of the menuList
160
+ selectMenuList.value = getAllSublevelItems(filteredMenuList);
161
+ } else if (filteredMenuListSubLevels.length > 0) {
162
+ //if there is a match at the 2nd level (sublevel of a menuList item) of the menuList
163
+ selectMenuList.value = filteredMenuListSubLevels;
164
+ } else {
165
+ selectMenuList.value = [];
166
+ }
167
+ };
168
+
169
+ // compile sublevel items from menuList to a single array
170
+ // and add text and value of the parent item to all sublevel items as subtext and subvalue
171
+ const getAllSublevelItems = (menuList: MenuListType[]) => {
172
+ return menuList.reduce<MenuListType[]>((currentValue, currentItem) => {
173
+ if (currentItem.sublevel) {
174
+ const mappedSublevel = currentItem.sublevel.map((sublevelItem: MenuListType) => ({
175
+ ...sublevelItem, //text and value of a sublevel item
176
+ subtext: currentItem.text, // text of parent of a sublevel item
177
+ subvalue: typeof currentItem.value === 'string' ? currentItem.value : String(currentItem.value), // value of parent of a sublevel item as string
178
+ }));
179
+
180
+ return [...currentValue, ...mappedSublevel];
181
+ }
182
+
183
+ return currentValue;
184
+ }, [] as MenuListType[]);
185
+ };
186
+
187
+ // filter list based on search string and menuList/sublevel texts
188
+ const getFilteredMenuList = (list: MenuListType[]) => {
189
+ return list.filter((item: MenuListType) => {
190
+ const searchTerm = searchString.value.toLowerCase().trim();
191
+ return item.text.toLowerCase().includes(searchTerm);
192
+ });
193
+ };
194
+
195
+ watch(searchString, () => {
196
+ handleSearch();
197
+ });
198
+
199
+ onClickOutside(selectRef, () => {
200
+ selectPopperState.value = false;
201
+ });
202
+
203
+ useInfiniteScroll(
204
+ selectRef,
205
+ () => {
206
+ emit('infinite-scroll-trigger', true);
207
+ },
208
+ { distance: 10 },
209
+ );
210
+
211
+ // Handle selected item for simple list component
212
+ const handleSelectedItem = (selectedItems: MenuListType[]) => {
213
+ if (!props.ladderized) {
214
+ // Determine the type of value to emit based on the original data type and multiSelect
215
+ if (multiSelect.value) {
216
+ // For multi-select, always return an array
217
+ const values = selectedItems.map((item) => {
218
+ // If we stored the original object, use it
219
+ if ('_originalObject' in item) {
220
+ return item._originalObject;
221
+ }
222
+
223
+ // For simple types, handle value type conversion properly
224
+ const val = item.value;
225
+
226
+ // If it's already a number, keep it as a number
227
+ if (typeof val === 'number') {
228
+ return val;
229
+ }
230
+
231
+ // For strings that look like numbers, convert them
232
+ if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
233
+ // Only convert if it looks like a proper number format
234
+ if (/^-?\d+(\.\d+)?$/.test(val)) {
235
+ return Number(val);
236
+ }
237
+ }
238
+
239
+ // Return the original value for all other cases
240
+ return val;
241
+ });
242
+
243
+ selectModel.value = values as (string | number | Record<string, unknown>)[];
244
+ } else {
245
+ // For single-select
246
+ if (selectedItems.length === 0) {
247
+ selectModel.value = props.multiSelect ? [] : '';
248
+ return;
249
+ }
250
+
251
+ const item = selectedItems[0];
252
+
253
+ // If we stored the original object, use it
254
+ if ('_originalObject' in item) {
255
+ selectModel.value = item._originalObject as Record<string, unknown>;
256
+ } else {
257
+ // For simple types, return the value (try to convert number strings to numbers)
258
+ const val = item.value;
259
+ if (typeof val === 'string' && !isNaN(Number(val)) && val.trim() !== '') {
260
+ selectModel.value = Number(val);
261
+ } else {
262
+ selectModel.value = val;
263
+ }
264
+ }
265
+ }
266
+ } else if (props.ladderized) {
267
+ if (props.searchString !== '') {
268
+ // generate select value if ladderized with search string
269
+ const subvalue = selectedItems[0]?.subvalue;
270
+ const value = selectedItems[0]?.value;
271
+ if (subvalue !== undefined && value !== undefined) {
272
+ selectModel.value = [subvalue, value] as [string, string | number];
273
+ }
274
+ } else {
275
+ // For regular ladderized select selection without search
276
+ if (selectedItems.length > 0) {
277
+ const item = selectedItems[0];
278
+ // Use the value directly for ladderized items
279
+ if (item && item.value) {
280
+ selectModel.value = item.value;
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ // Always close select for single selection, regardless of value type
287
+ if (!multiSelect.value) {
288
+ setTimeout(() => {
289
+ selectPopperState.value = false;
290
+ }, 10);
291
+ }
292
+ };
293
+
294
+ // Handle selected item for ladderized list component
295
+ const handleSelectedLadderizedItem = (selectedItems: string[]) => {
296
+ // Update the model value with the selected ladderized items
297
+ if (selectedItems.length > 0) {
298
+ selectModel.value = selectedItems;
299
+ }
300
+
301
+ // If item is from last sublevel, close the select
302
+ if (checkIfItemFromLastSublevel(selectedItems)) {
303
+ selectPopperState.value = false;
304
+ }
305
+ };
306
+
307
+ const checkIfItemFromLastSublevel = (selectedItems: string[]) => {
308
+ let selectedItemsObject = selectMenuList.value;
309
+
310
+ // Traverse to the last item in the selectedItems array
311
+ selectedItems.forEach((selectedItem) => {
312
+ selectedItemsObject = selectedItemsObject.find((item) => selectedItem === item.value)?.sublevel ?? [];
313
+ });
314
+
315
+ // If there is a sublevel, return false
316
+ if (selectedItemsObject.length > 0) {
317
+ return false;
318
+ }
319
+
320
+ return true;
321
+ };
322
+
323
+ // Update selected items when model value changes externally
324
+ const updateSelectedItemsFromValue = () => {
325
+ if (!selectMenuList.value.length) return;
326
+
327
+ const values = normalizedValue.value;
328
+ if (!values || !values.length) {
329
+ selectedListItems.value = [];
330
+ return;
331
+ }
332
+
333
+ // Store both original values and string versions for flexible matching
334
+ const valueData = values.map((val) => {
335
+ if (val === undefined || val === null) return { original: '', string: '' };
336
+
337
+ // For objects, use JSON string representation
338
+ if (typeof val === 'object') {
339
+ return {
340
+ original: val,
341
+ string: JSON.stringify(val),
342
+ isObject: true,
343
+ id: 'id' in val ? val.id : undefined,
344
+ };
345
+ }
346
+
347
+ // For numbers and strings, keep original and string versions
348
+ return {
349
+ original: val,
350
+ string: val.toString(),
351
+ isObject: false,
352
+ };
353
+ });
354
+
355
+ // Extract just string values for comparison
356
+ const valueStrings = valueData.map((v) => v.string);
357
+
358
+ if (props.ladderized) {
359
+ // Special handling for ladderized selects
360
+ if (Array.isArray(selectModel.value) && selectModel.value.length === 2) {
361
+ // Handle [subvalue, value] format used in ladderized selects with search
362
+ const subvalue = selectModel.value[0]?.toString() || '';
363
+ const value = selectModel.value[1]?.toString() || '';
364
+
365
+ selectedListItems.value = selectMenuList.value.filter((item) => {
366
+ return item.value === value && (!item.subvalue || item.subvalue === subvalue);
367
+ });
368
+ } else {
369
+ // Regular ladderized select value
370
+ selectedListItems.value = selectMenuList.value.filter((item) => {
371
+ // Convert both to strings for comparison or check direct equality for numbers
372
+ if (typeof item.value === 'number') {
373
+ return valueData.some((v) => v.original === item.value || v.string === String(item.value));
374
+ } else {
375
+ return valueStrings.includes(String(item.value));
376
+ }
377
+ });
378
+ }
379
+ } else {
380
+ // Regular Select value
381
+ selectedListItems.value = selectMenuList.value.filter((item) => {
382
+ // Handle objects with _originalObject property
383
+ if ('_originalObject' in item && item._originalObject) {
384
+ return valueData.some((v) => {
385
+ // If both are objects, compare by JSON string or by ID
386
+ if (v.isObject && typeof v.original === 'object') {
387
+ const originalObj = item._originalObject as Record<string, unknown>;
388
+
389
+ // First try direct equality comparison
390
+ if (v.original === originalObj) return true;
391
+
392
+ // Try JSON string comparison
393
+ const itemJson = JSON.stringify(originalObj);
394
+ if (v.string === itemJson) return true;
395
+
396
+ // Try ID-based comparison if both have ID fields
397
+ if (v.id !== undefined && 'id' in originalObj) {
398
+ return v.id === originalObj.id;
399
+ }
400
+ }
401
+ return false;
402
+ });
403
+ }
404
+
405
+ // Handle both numeric and string values correctly
406
+ if (typeof item.value === 'number') {
407
+ return valueData.some((v) => v.original === item.value || v.string === String(item.value));
408
+ } else {
409
+ return valueStrings.includes(String(item.value));
410
+ }
411
+ });
412
+ }
413
+ };
414
+
415
+ watch(selectModel, () => {
416
+ updateSelectedItemsFromValue();
417
+ });
418
+
419
+ watch(selectMenuList, () => {
420
+ updateSelectedItemsFromValue();
421
+ });
422
+
423
+ onMounted(() => {
424
+ processMenuList();
425
+
426
+ // Set initial selected items based on model value
427
+ if (normalizedValue.value.length > 0) {
428
+ updateSelectedItemsFromValue();
429
+ }
430
+ });
431
+
432
+ return {
433
+ selectPopperState,
434
+ selectRef,
435
+ inputText,
436
+ selectMenuList,
437
+ isSelectPopperDisabled,
438
+ selectedListItems,
439
+ handleSelectedItem,
440
+ handleSelectedLadderizedItem,
441
+ selectModel: compatPreSelectedItems, // Use compatible format for lists
442
+ removeCurrentLevelInBackLabel,
443
+ isLadderizedSearch,
444
+ };
445
+ };
@@ -0,0 +1,71 @@
1
+ // Example usage of select with primitive number values in multi-select mode
2
+
3
+ <template>
4
+ <div>
5
+ <h2>Multiple Number Values Demo</h2>
6
+ <p>Selected values: {{ displaySelection }}</p>
7
+
8
+ <div class="select-container">
9
+ <spr-select
10
+ id="number-multi-select"
11
+ v-model="selectedNumbers"
12
+ :menu-list="numberOptions"
13
+ multi-select
14
+ @update:model-value="handleSelectedItems"
15
+ >
16
+ <spr-input v-model="displayText" label="Select Numbers" readonly placeholder="Select numbers..." />
17
+ </spr-select>
18
+ </div>
19
+
20
+ <div class="mt-4">
21
+ <h3>Current selection type:</h3>
22
+ <pre>{{ typeof selectedNumbers[0] }}</pre>
23
+
24
+ <h3>Current selection:</h3>
25
+ {{ selectedNumbers }}
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import { ref, computed } from 'vue';
32
+ import SprInput from '@/components/input/input.vue';
33
+ import SprSelect from '@/components/select/select.vue';
34
+
35
+ // Define number options - raw number values
36
+ const numberOptions = [
37
+ { text: 'One', value: 1 },
38
+ { text: 'Two', value: 2 },
39
+ { text: 'Three', value: 3 },
40
+ { text: 'Four', value: 4 },
41
+ { text: 'Five', value: 5 },
42
+ ];
43
+
44
+ // Track selected numbers
45
+ const selectedNumbers = ref([]);
46
+ const displayText = ref('');
47
+
48
+ // Display the selection summary
49
+ const displaySelection = computed(() => {
50
+ if (selectedNumbers.value.length === 0) {
51
+ return 'None';
52
+ }
53
+ return selectedNumbers.value.join(', ');
54
+ });
55
+
56
+ // Handle selected items and update display text
57
+ const handleSelectedItems = (items) => {
58
+ // For multi-select, update display text to show selected items
59
+ const selectedTexts = items.map((itemValue) => {
60
+ // Find corresponding text for each selected value
61
+ const option = numberOptions.find((opt) => opt.value === itemValue);
62
+ return option ? option.text : itemValue;
63
+ });
64
+
65
+ // Update the input display text
66
+ displayText.value = selectedTexts.join(', ');
67
+
68
+ console.log('Selected values:', items);
69
+ console.log('Type of first value:', typeof items[0]);
70
+ };
71
+ </script>