design-system-next 2.19.0 → 2.19.4
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.es.js +6099 -6014
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +13 -13
- package/dist/design-system-next.umd.js.gz +0 -0
- package/dist/main.css +1 -1
- package/dist/main.css.gz +0 -0
- package/dist/package.json.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/chips/chips.vue +4 -1
- package/src/components/date-picker/{reusable-calendar/reusable-calendar.ts → date-calendar-picker/date-calendar-picker.ts} +4 -4
- package/src/components/date-picker/{reusable-calendar/reusable-calendar.vue → date-calendar-picker/date-calendar-picker.vue} +10 -10
- package/src/components/date-picker/{reusable-calendar/use-reusable-calendar.ts → date-calendar-picker/use-date-calendar-picker.ts} +4 -4
- package/src/components/list/ladderized-list/use-ladderized-list.ts +19 -8
- package/src/components/list/list.ts +9 -0
- package/src/components/list/list.vue +70 -18
- package/src/components/list/use-list.ts +20 -5
- package/src/components/select/select-ladderized/select-ladderized.ts +5 -1
- package/src/components/select/select-ladderized/select-ladderized.vue +3 -1
- package/src/components/select/select-ladderized/use-select-ladderized.ts +17 -4
- package/src/components/select/select-multiple/select-multiple.ts +16 -0
- package/src/components/select/select-multiple/select-multiple.vue +3 -0
- package/src/components/select/select-multiple/use-select-multiple.ts +87 -54
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
ref="
|
|
4
|
-
:class="
|
|
3
|
+
ref="dateCalendarPickerRef"
|
|
4
|
+
:class="dateCalendarPickerClasses"
|
|
5
5
|
>
|
|
6
6
|
<div
|
|
7
7
|
:class="[
|
|
@@ -132,13 +132,13 @@ import DatePickerCalendarTab from '../tabs/DatePickerCalendarTab.vue';
|
|
|
132
132
|
import DatePickerMonthTab from '../tabs/DatePickerMonthTab.vue';
|
|
133
133
|
import DatePickerYearTab from '../tabs/DatePickerYearTab.vue';
|
|
134
134
|
|
|
135
|
-
import {
|
|
136
|
-
import {
|
|
135
|
+
import { useDateCalendarPicker } from './use-date-calendar-picker';
|
|
136
|
+
import { dateCalendarPickerEmitTypes, dateCalendarPickerPropTypes } from './date-calendar-picker';
|
|
137
137
|
|
|
138
|
-
const props = defineProps(
|
|
139
|
-
const emit = defineEmits(
|
|
138
|
+
const props = defineProps(dateCalendarPickerPropTypes);
|
|
139
|
+
const emit = defineEmits(dateCalendarPickerEmitTypes);
|
|
140
140
|
|
|
141
|
-
const
|
|
141
|
+
const dateCalendarPickerRef = ref<HTMLElement | null>(null);
|
|
142
142
|
|
|
143
143
|
// Use the composable for all logic
|
|
144
144
|
const {
|
|
@@ -176,12 +176,12 @@ const {
|
|
|
176
176
|
handleMonthTabMonthUpdateWrapper,
|
|
177
177
|
handleYearTabYearUpdateWrapper,
|
|
178
178
|
handleYearTabCurrentPageUpdateWrapper,
|
|
179
|
-
} =
|
|
179
|
+
} = useDateCalendarPicker(props, emit);
|
|
180
180
|
|
|
181
181
|
// Compute CSS classes using classNames utility
|
|
182
|
-
const
|
|
182
|
+
const dateCalendarPickerClasses = computed(() => {
|
|
183
183
|
return classNames(
|
|
184
|
-
'
|
|
184
|
+
'date-calendar-picker-container spr-bg-white spr-rounded-lg spr-shadow-lg spr-border spr-border-solid spr-border-mushroom-200 min-w-[320px]',
|
|
185
185
|
{
|
|
186
186
|
'spr-disabled': props.disabled,
|
|
187
187
|
'spr-readonly': props.readonly,
|
|
@@ -3,11 +3,11 @@ import { toRefs } from 'vue';
|
|
|
3
3
|
import dayjs from 'dayjs';
|
|
4
4
|
|
|
5
5
|
import type { SetupContext } from 'vue';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DateCalendarPickerEmitTypes, DateCalendarPickerPropTypes } from './date-calendar-picker';
|
|
7
7
|
|
|
8
|
-
export const
|
|
9
|
-
props:
|
|
10
|
-
emit: SetupContext<
|
|
8
|
+
export const useDateCalendarPicker = (
|
|
9
|
+
props: DateCalendarPickerPropTypes,
|
|
10
|
+
emit: SetupContext<DateCalendarPickerEmitTypes>['emit']
|
|
11
11
|
) => {
|
|
12
12
|
// Extract reactive props
|
|
13
13
|
const {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { onBeforeMount, ref, toRefs, watch } from 'vue';
|
|
2
|
-
import { useVModel } from '@vueuse/core';
|
|
3
|
-
|
|
2
|
+
import { useVModel, watchDeep } from '@vueuse/core';
|
|
4
3
|
import { LadderizedListPropTypes, LadderizedListEmitTypes } from './ladderized-list';
|
|
5
|
-
|
|
6
4
|
import type { SetupContext } from 'vue';
|
|
7
5
|
import type { MenuListType } from '../list';
|
|
8
6
|
|
|
@@ -21,6 +19,8 @@ export const useLadderizedList = (
|
|
|
21
19
|
const activeList = ref<MenuListType[]>(menuList.value); // List of items to display in the active level
|
|
22
20
|
const searchText = ref('');
|
|
23
21
|
|
|
22
|
+
const modelValueIsCustom = ref(false);
|
|
23
|
+
|
|
24
24
|
// Recursive filter function for ladderized options
|
|
25
25
|
const filterOptionsRecursive = (items: MenuListType[], search: string): MenuListType[] => {
|
|
26
26
|
if (!search) return items;
|
|
@@ -86,6 +86,12 @@ export const useLadderizedList = (
|
|
|
86
86
|
const handleSelectedListItem = (item: MenuListType) => {
|
|
87
87
|
transitionName.value = 'slide-left';
|
|
88
88
|
|
|
89
|
+
if (modelValueIsCustom.value) {
|
|
90
|
+
console.log("Custom");
|
|
91
|
+
ladderizedListOutput.value.shift();
|
|
92
|
+
modelValueIsCustom.value = false;
|
|
93
|
+
};
|
|
94
|
+
|
|
89
95
|
// If searching, reconstruct full path as array of option objects
|
|
90
96
|
if (searchText.value) {
|
|
91
97
|
const path = findOptionPath(menuList.value, String(item.value));
|
|
@@ -111,7 +117,7 @@ export const useLadderizedList = (
|
|
|
111
117
|
replaceItemInOutput(item);
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
if (item.sublevel && item.sublevel.length > 0) updateLevel(item);
|
|
120
|
+
if (item.sublevel && item.sublevel.length > 0) updateLevel(item); // FIXME: This causes activeLevel one less than expected. Currently not critical since most forms are 2 levels deep.
|
|
115
121
|
emit('update:modelValue', ladderizedListOutput.value);
|
|
116
122
|
};
|
|
117
123
|
|
|
@@ -224,9 +230,14 @@ export const useLadderizedList = (
|
|
|
224
230
|
|
|
225
231
|
prevList.value = activeList.value;
|
|
226
232
|
activeList.value = item.sublevel ?? prevList.value;
|
|
227
|
-
activeLevel.value += item.sublevel ? 1 : 0;
|
|
233
|
+
activeLevel.value += item.sublevel ? 1 : 0; // FIXME: This causes activeLevel one less than expected. Currently not critical since most forms are 2 levels deep.
|
|
228
234
|
} else {
|
|
229
|
-
// If no item found,
|
|
235
|
+
// If no item found, rest the values to initial state
|
|
236
|
+
selectedListItem.value = [];
|
|
237
|
+
prevList.value = [];
|
|
238
|
+
activeList.value = menuList.value;
|
|
239
|
+
activeLevel.value = 0;
|
|
240
|
+
modelValueIsCustom.value = true;
|
|
230
241
|
return;
|
|
231
242
|
}
|
|
232
243
|
});
|
|
@@ -241,8 +252,8 @@ export const useLadderizedList = (
|
|
|
241
252
|
};
|
|
242
253
|
|
|
243
254
|
// Watch for modelValue changes and reset selectedListItem if cleared
|
|
244
|
-
|
|
245
|
-
|
|
255
|
+
watchDeep(
|
|
256
|
+
ladderizedListOutput,
|
|
246
257
|
(newVal) => {
|
|
247
258
|
if (!newVal || (Array.isArray(newVal) && newVal.length === 0)) {
|
|
248
259
|
selectedListItem.value = [];
|
|
@@ -36,6 +36,10 @@ export const listPropTypes = {
|
|
|
36
36
|
type: String,
|
|
37
37
|
default: '',
|
|
38
38
|
},
|
|
39
|
+
supportingDisplayText: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: '',
|
|
42
|
+
},
|
|
39
43
|
menuList: {
|
|
40
44
|
type: Array as PropType<MenuListType[]>,
|
|
41
45
|
required: true,
|
|
@@ -99,11 +103,16 @@ export const listPropTypes = {
|
|
|
99
103
|
type: String,
|
|
100
104
|
default: '',
|
|
101
105
|
},
|
|
106
|
+
disabledUnselectedItems: {
|
|
107
|
+
type: Boolean,
|
|
108
|
+
default: false,
|
|
109
|
+
},
|
|
102
110
|
};
|
|
103
111
|
|
|
104
112
|
export const listEmitTypes = {
|
|
105
113
|
'update:modelValue': (value: MenuListType[]) => value,
|
|
106
114
|
'update:searchValue': (value: string) => typeof value === 'string',
|
|
115
|
+
'get-single-selected-item': (item: MenuListType) => item,
|
|
107
116
|
};
|
|
108
117
|
|
|
109
118
|
export type ListPropTypes = ExtractPropTypes<typeof listPropTypes>;
|
|
@@ -20,8 +20,14 @@
|
|
|
20
20
|
:placeholder="props.searchableMenuPlaceholder"
|
|
21
21
|
autocomplete="off"
|
|
22
22
|
/>
|
|
23
|
-
<span
|
|
24
|
-
|
|
23
|
+
<span
|
|
24
|
+
v-if="props.supportingDisplayText || props.displayListItemSelected"
|
|
25
|
+
class="spr-label-sm-medium spr-text-color-base spr-block"
|
|
26
|
+
>
|
|
27
|
+
<template v-if="props.supportingDisplayText">
|
|
28
|
+
{{ props.supportingDisplayText }}
|
|
29
|
+
</template>
|
|
30
|
+
<template v-else> {{ selectedItems.length }} Selected</template>
|
|
25
31
|
</span>
|
|
26
32
|
</div>
|
|
27
33
|
</template>
|
|
@@ -47,7 +53,11 @@
|
|
|
47
53
|
>
|
|
48
54
|
<template v-if="props.lozenge">
|
|
49
55
|
<div class="spr-flex spr-items-center spr-gap-1">
|
|
50
|
-
<spr-checkbox
|
|
56
|
+
<spr-checkbox
|
|
57
|
+
v-if="props.multiSelect"
|
|
58
|
+
:disabled="item.disabled || (props.disabledUnselectedItems && !isItemSelected(item))"
|
|
59
|
+
:checked="isItemSelected(item)"
|
|
60
|
+
/>
|
|
51
61
|
<spr-lozenge
|
|
52
62
|
v-if="props.lozenge"
|
|
53
63
|
:label="item.text || (item.lozengeProps?.label as string)"
|
|
@@ -61,27 +71,46 @@
|
|
|
61
71
|
</template>
|
|
62
72
|
<template v-else>
|
|
63
73
|
<div class="spr-flex spr-items-center spr-gap-1">
|
|
64
|
-
<spr-checkbox
|
|
74
|
+
<spr-checkbox
|
|
75
|
+
v-if="props.multiSelect"
|
|
76
|
+
:disabled="item.disabled || (props.disabledUnselectedItems && !isItemSelected(item))"
|
|
77
|
+
:checked="isItemSelected(item)"
|
|
78
|
+
/>
|
|
65
79
|
<div :class="[item.textColor, 'spr-flex spr-flex-row spr-items-center spr-gap-size-spacing-3xs']">
|
|
66
|
-
<span
|
|
80
|
+
<span
|
|
81
|
+
v-if="props.itemIcon || item.icon"
|
|
82
|
+
:class="[
|
|
83
|
+
item.iconColor,
|
|
84
|
+
'spr-mt-[2px]',
|
|
85
|
+
{
|
|
86
|
+
'spr-text-color-disabled':
|
|
87
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
88
|
+
},
|
|
89
|
+
]"
|
|
90
|
+
>
|
|
67
91
|
<icon :icon="(props.itemIcon || item.icon) as string" width="20px" height="20px" />
|
|
68
92
|
</span>
|
|
69
93
|
<div
|
|
70
94
|
:class="[
|
|
71
95
|
'spr-flex spr-flex-auto spr-flex-col spr-justify-start',
|
|
72
|
-
{
|
|
96
|
+
{
|
|
97
|
+
'spr-text-color-disabled':
|
|
98
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
99
|
+
},
|
|
73
100
|
]"
|
|
74
101
|
>
|
|
75
|
-
<span class="spr-text-left spr-text-xs"
|
|
102
|
+
<span class="spr-break-words spr-text-left spr-text-xs">
|
|
76
103
|
{{ item.text }}
|
|
77
104
|
</span>
|
|
78
105
|
<span
|
|
79
106
|
v-if="item.subtext"
|
|
80
107
|
:class="[
|
|
81
|
-
'spr-body-xs-regular spr-text-color-base spr-text-left',
|
|
82
|
-
{
|
|
108
|
+
'spr-body-xs-regular spr-text-color-base spr-break-words spr-text-left',
|
|
109
|
+
{
|
|
110
|
+
'spr-text-color-disabled':
|
|
111
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
112
|
+
},
|
|
83
113
|
]"
|
|
84
|
-
style="word-break: break-word"
|
|
85
114
|
>
|
|
86
115
|
{{ item.subtext }}
|
|
87
116
|
</span>
|
|
@@ -140,7 +169,11 @@
|
|
|
140
169
|
>
|
|
141
170
|
<template v-if="props.lozenge">
|
|
142
171
|
<div class="spr-flex spr-items-center spr-gap-1">
|
|
143
|
-
<spr-checkbox
|
|
172
|
+
<spr-checkbox
|
|
173
|
+
v-if="props.multiSelect"
|
|
174
|
+
:disabled="item.disabled || (props.disabledUnselectedItems && !isItemSelected(item))"
|
|
175
|
+
:checked="isItemSelected(item)"
|
|
176
|
+
/>
|
|
144
177
|
<spr-lozenge
|
|
145
178
|
v-if="props.lozenge"
|
|
146
179
|
:label="item.text || (item.lozengeProps?.label as string)"
|
|
@@ -154,27 +187,46 @@
|
|
|
154
187
|
</template>
|
|
155
188
|
<template v-else>
|
|
156
189
|
<div class="spr-flex spr-items-center spr-gap-1">
|
|
157
|
-
<spr-checkbox
|
|
190
|
+
<spr-checkbox
|
|
191
|
+
v-if="props.multiSelect"
|
|
192
|
+
:disabled="item.disabled || (props.disabledUnselectedItems && !isItemSelected(item))"
|
|
193
|
+
:checked="isItemSelected(item)"
|
|
194
|
+
/>
|
|
158
195
|
<div :class="[item.textColor, 'spr-flex spr-flex-row spr-items-center spr-gap-size-spacing-3xs']">
|
|
159
|
-
<span
|
|
196
|
+
<span
|
|
197
|
+
v-if="props.itemIcon || item.icon"
|
|
198
|
+
:class="[
|
|
199
|
+
item.iconColor,
|
|
200
|
+
'spr-mt-[2px]',
|
|
201
|
+
{
|
|
202
|
+
'spr-text-color-disabled':
|
|
203
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
204
|
+
},
|
|
205
|
+
]"
|
|
206
|
+
>
|
|
160
207
|
<icon :icon="(props.itemIcon || item.icon) as string" width="20px" height="20px" />
|
|
161
208
|
</span>
|
|
162
209
|
<div
|
|
163
210
|
:class="[
|
|
164
211
|
'spr-flex spr-flex-auto spr-flex-col spr-justify-start',
|
|
165
|
-
{
|
|
212
|
+
{
|
|
213
|
+
'spr-text-color-disabled':
|
|
214
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
215
|
+
},
|
|
166
216
|
]"
|
|
167
217
|
>
|
|
168
|
-
<span class="spr-text-left spr-text-xs"
|
|
218
|
+
<span class="spr-break-words spr-text-left spr-text-xs">
|
|
169
219
|
{{ item.text }}
|
|
170
220
|
</span>
|
|
171
221
|
<span
|
|
172
222
|
v-if="item.subtext"
|
|
173
223
|
:class="[
|
|
174
|
-
'spr-body-xs-regular spr-text-color-base spr-text-left',
|
|
175
|
-
{
|
|
224
|
+
'spr-body-xs-regular spr-text-color-base spr-break-words spr-text-left',
|
|
225
|
+
{
|
|
226
|
+
'spr-text-color-disabled':
|
|
227
|
+
item.disabled || (props.disabledUnselectedItems && !isItemSelected(item)),
|
|
228
|
+
},
|
|
176
229
|
]"
|
|
177
|
-
style="word-break: break-word"
|
|
178
230
|
>
|
|
179
231
|
{{ item.subtext }}
|
|
180
232
|
</span>
|
|
@@ -14,8 +14,16 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
14
14
|
const selectedItems = useVModel(props, 'modelValue', emit);
|
|
15
15
|
const searchString = useVModel(props, 'searchValue', emit);
|
|
16
16
|
|
|
17
|
-
const {
|
|
18
|
-
|
|
17
|
+
const {
|
|
18
|
+
menuList,
|
|
19
|
+
menuLevel,
|
|
20
|
+
groupItemsBy,
|
|
21
|
+
multiSelect,
|
|
22
|
+
preSelectedItems,
|
|
23
|
+
disabledLocalSearch,
|
|
24
|
+
noCheck,
|
|
25
|
+
disabledUnselectedItems,
|
|
26
|
+
} = toRefs(props);
|
|
19
27
|
|
|
20
28
|
const listClasses: ComputedRef<ListClasses> = computed(() => {
|
|
21
29
|
const listItemClasses = classNames(
|
|
@@ -161,6 +169,7 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
161
169
|
|
|
162
170
|
if (previousSelected) {
|
|
163
171
|
handleSelectedItem(item);
|
|
172
|
+
|
|
164
173
|
return true;
|
|
165
174
|
}
|
|
166
175
|
|
|
@@ -341,10 +350,10 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
341
350
|
};
|
|
342
351
|
|
|
343
352
|
const getListItemClasses = (item: MenuListType) => ({
|
|
344
|
-
[listClasses.value.listItemClasses]: !item.disabled,
|
|
353
|
+
[listClasses.value.listItemClasses]: !item.disabled && !(disabledUnselectedItems.value && !isItemSelected(item)),
|
|
345
354
|
'spr-background-color-single-active': isItemSelected(item) && !item.disabled && !noCheck.value,
|
|
346
|
-
'
|
|
347
|
-
item.disabled,
|
|
355
|
+
'spr-cursor-not-allowed spr-flex spr-items-center spr-gap-1.5 spr-rounded-lg':
|
|
356
|
+
item.disabled || (disabledUnselectedItems.value && !isItemSelected(item)),
|
|
348
357
|
'spr-p-size-spacing-3xs': !props.lozenge,
|
|
349
358
|
'spr-py-size-spacing-3xs spr-px-size-spacing-4xs': props.lozenge,
|
|
350
359
|
});
|
|
@@ -420,6 +429,8 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
420
429
|
const handleSelectedItem = (item: MenuListType) => {
|
|
421
430
|
if (item.disabled) return;
|
|
422
431
|
|
|
432
|
+
if (disabledUnselectedItems.value && !isItemSelected(item)) return;
|
|
433
|
+
|
|
423
434
|
if (multiSelect.value) {
|
|
424
435
|
// For multi-select, check if item is already selected
|
|
425
436
|
const index = selectedItems.value.findIndex((selectedItem: MenuListType) => {
|
|
@@ -488,8 +499,11 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
488
499
|
} else {
|
|
489
500
|
// For single-select, simply replace the selection
|
|
490
501
|
selectedItems.value = [item];
|
|
502
|
+
|
|
491
503
|
if (item.onClickFn) item.onClickFn();
|
|
492
504
|
}
|
|
505
|
+
|
|
506
|
+
emit('get-single-selected-item', item);
|
|
493
507
|
};
|
|
494
508
|
// #endregion - Helper Methods
|
|
495
509
|
|
|
@@ -567,6 +581,7 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
567
581
|
|
|
568
582
|
onMounted(() => {
|
|
569
583
|
searchString.value = searchText.value;
|
|
584
|
+
|
|
570
585
|
setMenuList();
|
|
571
586
|
setPreSelectedItems();
|
|
572
587
|
});
|
|
@@ -144,12 +144,16 @@ export const selectLadderizedPropTypes = {
|
|
|
144
144
|
type: Boolean,
|
|
145
145
|
default: false,
|
|
146
146
|
},
|
|
147
|
+
writableInputText: {
|
|
148
|
+
type: Boolean,
|
|
149
|
+
default: false,
|
|
150
|
+
}
|
|
147
151
|
};
|
|
148
152
|
|
|
149
153
|
export const selectLadderizedEmitTypes = {
|
|
150
154
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
151
155
|
'update:modelValue': (_value: unknown) => true,
|
|
152
|
-
'popper-state':
|
|
156
|
+
'popper-state': () => true,
|
|
153
157
|
};
|
|
154
158
|
|
|
155
159
|
export type SelectLadderizedEmitFn = (event: string, ...args: unknown[]) => void;
|
|
@@ -37,10 +37,11 @@
|
|
|
37
37
|
:helper-text="props.helperText"
|
|
38
38
|
:helper-icon="props.helperIcon"
|
|
39
39
|
:display-helper="props.displayHelper"
|
|
40
|
-
readonly
|
|
40
|
+
:readonly="!props.writableInputText"
|
|
41
41
|
:active="props.active"
|
|
42
42
|
:disabled="props.disabled"
|
|
43
43
|
:error="props.error"
|
|
44
|
+
@blur="handleInputChange"
|
|
44
45
|
>
|
|
45
46
|
<template #icon>
|
|
46
47
|
<div
|
|
@@ -121,5 +122,6 @@ const {
|
|
|
121
122
|
inputText,
|
|
122
123
|
handleSelectedLadderizedItem,
|
|
123
124
|
handleClear,
|
|
125
|
+
handleInputChange,
|
|
124
126
|
} = useSelectLadderized(props, emit as SelectLadderizedEmitFn);
|
|
125
127
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, toRefs, computed, watch } from 'vue';
|
|
2
|
-
import { useVModel, onClickOutside } from '@vueuse/core';
|
|
2
|
+
import { useVModel, onClickOutside, watchDeep } from '@vueuse/core';
|
|
3
3
|
|
|
4
4
|
import type { SelectLadderizedPropTypes } from './select-ladderized';
|
|
5
5
|
|
|
@@ -17,6 +17,7 @@ export const useSelectLadderized = (
|
|
|
17
17
|
supportingLabelClasses: 'spr-body-sm-regular spr-text-color-supporting',
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
+
// Wrapper for input field
|
|
20
21
|
const ladderizedSelectState = ref<HTMLDivElement | null>(null);
|
|
21
22
|
|
|
22
23
|
// Popper Variables
|
|
@@ -31,6 +32,7 @@ export const useSelectLadderized = (
|
|
|
31
32
|
// Input Variables
|
|
32
33
|
const inputText = ref<string>('');
|
|
33
34
|
const wasCleared = ref<boolean>(false);
|
|
35
|
+
const isCustomInput = ref<boolean>(false);
|
|
34
36
|
|
|
35
37
|
const isLeafNode = (item: MenuListType): boolean => {
|
|
36
38
|
return !item.sublevel || item.sublevel.length === 0;
|
|
@@ -57,6 +59,7 @@ export const useSelectLadderized = (
|
|
|
57
59
|
|
|
58
60
|
const handleSelectedLadderizedItem = (selectedItems: string[], selectedItem?: MenuListType) => {
|
|
59
61
|
wasCleared.value = false;
|
|
62
|
+
isCustomInput.value = false;
|
|
60
63
|
|
|
61
64
|
// If the selectedItems is a single value (leaf from search), reconstruct the full path
|
|
62
65
|
let fullPath: string[] | null = null;
|
|
@@ -157,15 +160,17 @@ export const useSelectLadderized = (
|
|
|
157
160
|
};
|
|
158
161
|
|
|
159
162
|
// Watch for changes in modelValue to update inputText
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
watchDeep(
|
|
164
|
+
ladderizedSelectModel,
|
|
162
165
|
(newVal) => {
|
|
166
|
+
if (isCustomInput.value) return;
|
|
167
|
+
|
|
163
168
|
if (wasCleared.value) {
|
|
164
169
|
inputText.value = '';
|
|
165
170
|
wasCleared.value = false;
|
|
166
171
|
|
|
167
172
|
return;
|
|
168
|
-
}
|
|
173
|
+
};
|
|
169
174
|
|
|
170
175
|
if (Array.isArray(newVal) && newVal.length > 0) {
|
|
171
176
|
// Treat the array as a single path for ladderized select
|
|
@@ -194,6 +199,13 @@ export const useSelectLadderized = (
|
|
|
194
199
|
emit('popper-state', newState);
|
|
195
200
|
});
|
|
196
201
|
|
|
202
|
+
const handleInputChange = () => {
|
|
203
|
+
if (!props.writableInputText) return;
|
|
204
|
+
wasCleared.value = false;
|
|
205
|
+
isCustomInput.value = true;
|
|
206
|
+
ladderizedSelectModel.value = [inputText.value];
|
|
207
|
+
};
|
|
208
|
+
|
|
197
209
|
// Close only when clicking completely outside both the popper and the trigger wrapper.
|
|
198
210
|
onClickOutside(ladderizedSelectPopperRef, (event) => {
|
|
199
211
|
const triggerWrapper = ladderizedSelectState.value;
|
|
@@ -216,5 +228,6 @@ export const useSelectLadderized = (
|
|
|
216
228
|
inputText,
|
|
217
229
|
handleSelectedLadderizedItem,
|
|
218
230
|
handleClear,
|
|
231
|
+
handleInputChange,
|
|
219
232
|
};
|
|
220
233
|
};
|
|
@@ -123,6 +123,14 @@ export const multiSelectPropTypes = {
|
|
|
123
123
|
type: String,
|
|
124
124
|
default: '',
|
|
125
125
|
},
|
|
126
|
+
supportingDisplayText: {
|
|
127
|
+
type: String,
|
|
128
|
+
default: '',
|
|
129
|
+
},
|
|
130
|
+
persistentDisplayText: {
|
|
131
|
+
type: Boolean,
|
|
132
|
+
default: false,
|
|
133
|
+
},
|
|
126
134
|
displaySelectedCountOnly: {
|
|
127
135
|
type: Boolean,
|
|
128
136
|
default: false,
|
|
@@ -183,14 +191,22 @@ export const multiSelectPropTypes = {
|
|
|
183
191
|
type: String,
|
|
184
192
|
default: '',
|
|
185
193
|
},
|
|
194
|
+
disabledUnselectedItems: {
|
|
195
|
+
type: Boolean,
|
|
196
|
+
default: false,
|
|
197
|
+
},
|
|
186
198
|
};
|
|
187
199
|
|
|
188
200
|
export const multiSelectEmitTypes = {
|
|
189
201
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
190
202
|
'update:modelValue': (_value: unknown) => true,
|
|
191
203
|
'update:searchValue': (value: string) => typeof value === 'string',
|
|
204
|
+
'search-string': (value: string) => typeof value === 'string',
|
|
192
205
|
'infinite-scroll-trigger': Boolean,
|
|
193
206
|
'popper-state': Boolean,
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
208
|
+
'get-selected-options': (_value: unknown) => true,
|
|
209
|
+
'get-single-selected-item': (item: MenuListType) => item,
|
|
194
210
|
};
|
|
195
211
|
|
|
196
212
|
export type MultiSelectPropTypes = ExtractPropTypes<typeof multiSelectPropTypes>;
|
|
@@ -162,10 +162,13 @@
|
|
|
162
162
|
:loading="props.loading"
|
|
163
163
|
:item-icon="props.itemIcon"
|
|
164
164
|
:lozenge="props.lozenge"
|
|
165
|
+
:supporting-display-text="props.supportingDisplayText"
|
|
165
166
|
:display-list-item-selected="props.displayListItemSelected"
|
|
166
167
|
:disabled-local-search="props.disabledLocalSearch"
|
|
168
|
+
:disabled-unselected-items="props.disabledUnselectedItems"
|
|
167
169
|
multi-select
|
|
168
170
|
@update:model-value="handleMultiSelectedItem"
|
|
171
|
+
@get-single-selected-item="emit('get-single-selected-item', $event)"
|
|
169
172
|
/>
|
|
170
173
|
</div>
|
|
171
174
|
</template>
|