design-system-next 2.8.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/design-system-next.js +2090 -2102
- package/dist/design-system-next.js.gz +0 -0
- package/dist/package.json.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/list/ladderized-list/use-ladderized-list.ts +11 -1
- package/src/components/select/select-multiple/select-multiple.ts +0 -3
- package/src/components/select/select-multiple/use-select-multiple.ts +81 -150
|
Binary file
|
package/dist/package.json.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, onBeforeMount, ref, toRefs } from 'vue';
|
|
1
|
+
import { computed, onBeforeMount, ref, toRefs, watch } from 'vue';
|
|
2
2
|
import { useVModel } from '@vueuse/core';
|
|
3
3
|
|
|
4
4
|
import { LadderizedListPropTypes, LadderizedListEmitTypes } from './ladderized-list';
|
|
@@ -146,6 +146,16 @@ export const useLadderizedList = (
|
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
+
// Watch for modelValue changes and reset selectedListItem if cleared
|
|
150
|
+
watch(
|
|
151
|
+
() => props.modelValue,
|
|
152
|
+
(newVal) => {
|
|
153
|
+
if (!newVal || (Array.isArray(newVal) && newVal.length === 0)) {
|
|
154
|
+
selectedListItem.value = [];
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
149
159
|
onBeforeMount(() => {
|
|
150
160
|
activeList.value = menuList.value;
|
|
151
161
|
initializeMenuList();
|
|
@@ -124,9 +124,6 @@ export const multiSelectEmitTypes = {
|
|
|
124
124
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
125
125
|
'update:modelValue': (_value: unknown) => true,
|
|
126
126
|
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
128
|
-
'infinite-scroll-trigger': (_triggered: boolean) => true,
|
|
129
|
-
|
|
130
127
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
131
128
|
'search-string': (_search: string | number) => true,
|
|
132
129
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, toRefs, computed, ComputedRef, onMounted, watch } from 'vue';
|
|
2
|
-
import { onClickOutside,
|
|
2
|
+
import { onClickOutside, useVModel, useDebounceFn } from '@vueuse/core';
|
|
3
3
|
|
|
4
4
|
import classNames from 'classnames';
|
|
5
5
|
|
|
@@ -28,46 +28,46 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
28
28
|
};
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
// Popper Variables
|
|
32
31
|
const multiSelectRef = ref<HTMLDivElement | null>(null);
|
|
33
32
|
const multiSelectPopperState = ref<boolean>(false);
|
|
34
33
|
const isMultiSelectPopperDisabled = computed(() => disabled.value);
|
|
35
34
|
|
|
36
|
-
// Multi-Select Variables
|
|
37
35
|
const multiSelectModel = useVModel(props, 'modelValue', emit);
|
|
38
|
-
const multiSelectedListItems = ref<MenuListType[]>();
|
|
36
|
+
const multiSelectedListItems = ref<MenuListType[]>([]);
|
|
39
37
|
const multiSelectMenuList = ref<MenuListType[]>([]);
|
|
40
38
|
const hasUserSelected = ref(false);
|
|
41
39
|
|
|
42
|
-
// Input Text Variables
|
|
43
40
|
const inputText = ref<string | number>('');
|
|
44
41
|
const inputTextBackup = ref<string | number>('');
|
|
45
42
|
const isSearching = ref<boolean>(false);
|
|
46
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Opens the multi-select dropdown menu and resets the searching state.
|
|
46
|
+
*/
|
|
47
47
|
const handleMenuToggle = () => {
|
|
48
48
|
multiSelectPopperState.value = true;
|
|
49
|
-
|
|
50
49
|
isSearching.value = false;
|
|
51
50
|
};
|
|
52
51
|
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Returns the normalized value of the model as an array for internal use.
|
|
54
|
+
*/
|
|
54
55
|
const normalizedValue = computed(() => {
|
|
55
|
-
// If already an array, use it
|
|
56
56
|
if (Array.isArray(multiSelectModel.value)) {
|
|
57
57
|
return multiSelectModel.value;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// If not an array but has a value, make it a single-item array
|
|
61
60
|
if (multiSelectModel.value !== undefined && multiSelectModel.value !== null) {
|
|
62
61
|
return [multiSelectModel.value];
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// Default empty array
|
|
66
64
|
return [];
|
|
67
65
|
});
|
|
68
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Processes the menuList prop and normalizes it into MenuListType[] for the dropdown.
|
|
69
|
+
*/
|
|
69
70
|
const processMenuList = () => {
|
|
70
|
-
// Handle empty array or non-array values
|
|
71
71
|
if (!menuList.value || !Array.isArray(menuList.value) || menuList.value.length === 0) {
|
|
72
72
|
multiSelectMenuList.value = [];
|
|
73
73
|
|
|
@@ -76,7 +76,6 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
76
76
|
|
|
77
77
|
const firstItem = menuList.value[0];
|
|
78
78
|
|
|
79
|
-
// Handle array of strings
|
|
80
79
|
if (typeof firstItem === 'string') {
|
|
81
80
|
multiSelectMenuList.value = (menuList.value as string[]).map((item) => ({
|
|
82
81
|
text: item,
|
|
@@ -86,43 +85,47 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
// Handle array of numbers
|
|
90
88
|
if (typeof firstItem === 'number') {
|
|
91
|
-
multiSelectMenuList.value = (menuList.value as number
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
multiSelectMenuList.value = (menuList.value as Array<number | string | Record<string, unknown>>)
|
|
90
|
+
.filter((item): item is number => typeof item === 'number')
|
|
91
|
+
.map((item) => ({
|
|
92
|
+
text: item.toString(),
|
|
93
|
+
value: item,
|
|
94
|
+
}));
|
|
95
95
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// Handle array of objects with dynamic attributes
|
|
100
99
|
if (typeof firstItem === 'object' && firstItem !== null) {
|
|
101
|
-
// Check if it's already in MenuListType format
|
|
102
100
|
if ('text' in firstItem && 'value' in firstItem) {
|
|
103
101
|
multiSelectMenuList.value = menuList.value as MenuListType[];
|
|
102
|
+
|
|
104
103
|
return;
|
|
105
104
|
}
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
multiSelectMenuList.value = (menuList.value as Record<string, unknown>[]).map((item) => {
|
|
108
|
-
// Ensure displayText is a string
|
|
109
107
|
const displayText = item[textField.value] !== undefined ? String(item[textField.value]) : 'Unnamed';
|
|
110
|
-
|
|
108
|
+
|
|
111
109
|
let itemValue = valueField.value && item[valueField.value] !== undefined ? item[valueField.value] : item;
|
|
112
|
-
|
|
110
|
+
|
|
113
111
|
if (itemValue === undefined) itemValue = '';
|
|
112
|
+
|
|
114
113
|
return {
|
|
115
114
|
text: displayText,
|
|
116
115
|
value: typeof itemValue === 'object' ? JSON.stringify(itemValue) : String(itemValue),
|
|
117
|
-
_originalObject: item,
|
|
116
|
+
_originalObject: item,
|
|
118
117
|
};
|
|
119
118
|
});
|
|
119
|
+
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
multiSelectMenuList.value = menuList.value as MenuListType[];
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Returns the filtered menu list based on the search input, or the full list if local search is disabled.
|
|
128
|
+
*/
|
|
126
129
|
const filteredMultiSelectMenuList = computed(() => {
|
|
127
130
|
if (disabledLocalSearch.value) {
|
|
128
131
|
return multiSelectMenuList.value;
|
|
@@ -135,102 +138,59 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
135
138
|
return multiSelectMenuList.value.filter((item) => item.text?.toString().toLowerCase().includes(query));
|
|
136
139
|
});
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Search handler: always emit search-string, but only filter locally if local search is enabled
|
|
141
|
+
/**
|
|
142
|
+
* Handles the search input and emits the search-string event (debounced).
|
|
143
|
+
*/
|
|
143
144
|
const handleSearch = () => {
|
|
144
145
|
isSearching.value = true;
|
|
145
146
|
|
|
146
147
|
debouncedEmitSearch();
|
|
147
148
|
};
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Debounced function to emit the search-string event for remote search.
|
|
152
|
+
*/
|
|
149
153
|
const debouncedEmitSearch = useDebounceFn(() => {
|
|
150
154
|
emit('search-string', inputText.value);
|
|
151
155
|
}, 300);
|
|
152
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Handles closing the dropdown when clicking outside, restoring input text if searching.
|
|
159
|
+
*/
|
|
153
160
|
onClickOutside(multiSelectRef, () => {
|
|
154
161
|
multiSelectPopperState.value = false;
|
|
155
|
-
// If user was searching, restore inputText from backup
|
|
156
162
|
if (isSearching.value) {
|
|
157
163
|
inputText.value = inputTextBackup.value;
|
|
158
164
|
}
|
|
159
165
|
isSearching.value = false;
|
|
160
166
|
});
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
},
|
|
167
|
-
{ distance: 10 },
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Handle multi-selected item for simple list component
|
|
168
|
+
/**
|
|
169
|
+
* Handles selection changes from the dropdown and updates the model value.
|
|
170
|
+
* Converts stringified objects back to objects if needed.
|
|
171
|
+
*/
|
|
171
172
|
const handleMultiSelectedItem = (multiSelectedItems: MenuListType[]) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
lastValue = lastClicked._originalObject ?? lastClicked.value;
|
|
180
|
-
} else if (typeof lastClicked.value === 'number') {
|
|
181
|
-
lastValue = lastClicked.value;
|
|
182
|
-
} else if (
|
|
183
|
-
typeof lastClicked.value === 'string' &&
|
|
184
|
-
!isNaN(Number(lastClicked.value)) &&
|
|
185
|
-
lastClicked.value.trim() !== '' &&
|
|
186
|
-
/^-?\d+(\.\d+)?$/.test(lastClicked.value)
|
|
187
|
-
) {
|
|
188
|
-
lastValue = Number(lastClicked.value);
|
|
189
|
-
} else {
|
|
190
|
-
lastValue = lastClicked.value;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Always normalize current selection to an array
|
|
194
|
-
let current: (string | number | Record<string, unknown>)[] = [];
|
|
195
|
-
if (Array.isArray(multiSelectModel.value)) {
|
|
196
|
-
current = [...multiSelectModel.value];
|
|
197
|
-
} else if (
|
|
198
|
-
multiSelectModel.value !== undefined &&
|
|
199
|
-
multiSelectModel.value !== null &&
|
|
200
|
-
multiSelectModel.value !== ''
|
|
201
|
-
) {
|
|
202
|
-
current = [multiSelectModel.value];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Find if already selected (deep compare for objects, strict for primitives)
|
|
206
|
-
const isSelected = current.some((sel) => {
|
|
207
|
-
if (typeof sel === 'object' && typeof lastValue === 'object') {
|
|
208
|
-
return JSON.stringify(sel) === JSON.stringify(lastValue);
|
|
173
|
+
const selectedValues = multiSelectedItems.map((item) => {
|
|
174
|
+
if (typeof item.value === 'string' && item.value.startsWith('{') && item.value.endsWith('}')) {
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(item.value);
|
|
177
|
+
} catch {
|
|
178
|
+
return item.value;
|
|
179
|
+
}
|
|
209
180
|
}
|
|
210
|
-
return
|
|
181
|
+
return item.value;
|
|
211
182
|
});
|
|
212
183
|
|
|
213
|
-
if (isSelected) {
|
|
214
|
-
// Remove from selection
|
|
215
|
-
current = current.filter((sel) => {
|
|
216
|
-
if (typeof sel === 'object' && typeof lastValue === 'object') {
|
|
217
|
-
return JSON.stringify(sel) !== JSON.stringify(lastValue);
|
|
218
|
-
}
|
|
219
|
-
return sel !== lastValue;
|
|
220
|
-
});
|
|
221
|
-
} else {
|
|
222
|
-
// Add to selection
|
|
223
|
-
current.push(lastValue);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
184
|
hasUserSelected.value = true;
|
|
227
|
-
multiSelectModel.value =
|
|
185
|
+
multiSelectModel.value = selectedValues;
|
|
228
186
|
multiSelectPopperState.value = true;
|
|
229
|
-
// Clone inputText to backup after selection
|
|
230
187
|
inputTextBackup.value = inputText.value;
|
|
231
188
|
};
|
|
232
189
|
|
|
233
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Updates the selected items in the dropdown based on the current model value.
|
|
192
|
+
* Handles stringified objects and updates the input text accordingly.
|
|
193
|
+
*/
|
|
234
194
|
const updateMultiSelectedItemsFromValue = () => {
|
|
235
195
|
if (!multiSelectMenuList.value.length) return;
|
|
236
196
|
|
|
@@ -238,70 +198,41 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
238
198
|
|
|
239
199
|
if (!values || !values.length) {
|
|
240
200
|
multiSelectedListItems.value = [];
|
|
241
|
-
|
|
242
|
-
// Always clear inputText and backup if nothing is selected
|
|
243
201
|
inputText.value = '';
|
|
244
202
|
inputTextBackup.value = '';
|
|
245
203
|
|
|
246
204
|
return;
|
|
247
205
|
}
|
|
248
206
|
|
|
249
|
-
// Store both original values and string versions for flexible matching
|
|
250
|
-
const valueData = values.map((val) => {
|
|
251
|
-
if (val === undefined || val === null) return { original: '', string: '' };
|
|
252
|
-
|
|
253
|
-
// For objects, use JSON string representation
|
|
254
|
-
if (typeof val === 'object') {
|
|
255
|
-
return {
|
|
256
|
-
original: val,
|
|
257
|
-
string: JSON.stringify(val),
|
|
258
|
-
isObject: true,
|
|
259
|
-
id: 'id' in val ? val.id : undefined,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// For numbers and strings, keep original and string versions
|
|
264
|
-
return {
|
|
265
|
-
original: val,
|
|
266
|
-
string: val.toString(),
|
|
267
|
-
isObject: false,
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Extract just string values for comparison
|
|
272
|
-
const valueStrings = valueData.map((v) => v.string);
|
|
273
|
-
|
|
274
207
|
multiSelectedListItems.value = multiSelectMenuList.value.filter((item) => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (v.string === itemJson) return true;
|
|
208
|
+
return values.some((val) => {
|
|
209
|
+
let itemVal = item.value;
|
|
210
|
+
let valToCompare = val;
|
|
211
|
+
|
|
212
|
+
if (typeof itemVal === 'string' && itemVal.startsWith('{') && itemVal.endsWith('}')) {
|
|
213
|
+
try {
|
|
214
|
+
itemVal = JSON.parse(itemVal);
|
|
215
|
+
} catch {
|
|
216
|
+
// ignore
|
|
217
|
+
}
|
|
218
|
+
}
|
|
287
219
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
220
|
+
if (typeof valToCompare === 'string' && valToCompare.startsWith('{') && valToCompare.endsWith('}')) {
|
|
221
|
+
try {
|
|
222
|
+
valToCompare = JSON.parse(valToCompare);
|
|
223
|
+
} catch {
|
|
224
|
+
// ignore
|
|
291
225
|
}
|
|
292
|
-
|
|
293
|
-
});
|
|
294
|
-
}
|
|
226
|
+
}
|
|
295
227
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return
|
|
301
|
-
}
|
|
228
|
+
if (typeof itemVal === 'object' && typeof valToCompare === 'object') {
|
|
229
|
+
return JSON.stringify(itemVal) === JSON.stringify(valToCompare);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return itemVal == valToCompare;
|
|
233
|
+
});
|
|
302
234
|
});
|
|
303
235
|
|
|
304
|
-
// Only update inputText if not searching
|
|
305
236
|
if (!isSearching.value) {
|
|
306
237
|
if (multiSelectedListItems.value.length > 3) {
|
|
307
238
|
inputText.value = `${multiSelectedListItems.value.length} items selected`;
|
|
@@ -309,19 +240,20 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
309
240
|
inputText.value = multiSelectedListItems.value.map((item) => item.text).join(', ');
|
|
310
241
|
}
|
|
311
242
|
|
|
312
|
-
// Only use displayText.value if user hasn't selected anything yet
|
|
313
243
|
if (displayText.value && !hasUserSelected.value && (!inputText.value || inputText.value === '')) {
|
|
314
244
|
inputText.value = displayText.value;
|
|
315
245
|
inputTextBackup.value = displayText.value;
|
|
316
|
-
} else {
|
|
317
|
-
// Always update backup to match inputText if not searching
|
|
246
|
+
} else if (hasUserSelected.value) {
|
|
318
247
|
inputTextBackup.value = inputText.value;
|
|
319
248
|
}
|
|
320
249
|
}
|
|
321
250
|
};
|
|
322
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Clears the selection and input text, and closes the dropdown.
|
|
254
|
+
*/
|
|
323
255
|
const handleClear = () => {
|
|
324
|
-
emit('update:modelValue',
|
|
256
|
+
emit('update:modelValue', []);
|
|
325
257
|
|
|
326
258
|
inputText.value = '';
|
|
327
259
|
|
|
@@ -339,7 +271,6 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
339
271
|
onMounted(() => {
|
|
340
272
|
processMenuList();
|
|
341
273
|
|
|
342
|
-
// Set initial multi-selected items based on model value
|
|
343
274
|
if (normalizedValue.value.length > 0) {
|
|
344
275
|
updateMultiSelectedItemsFromValue();
|
|
345
276
|
} else if (displayText.value) {
|