edvoyui-component-library-test-flight 0.0.244 → 0.0.246
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/components/checkbox/EUICheckbox.d.ts +9 -0
- package/dist/components/checkbox/EUICheckbox.vue +45 -17
- package/dist/components/datepicker/EUIDatepicker.vue +3 -3
- package/dist/components/searchTagSelect/EUISearchTagSelect.d.ts +15 -0
- package/dist/components/searchTagSelect/EUISearchTagSelect.vue +184 -64
- package/dist/components/searchTagSelect/SearchInput.vue +1 -0
- package/dist/edvoy-ui.cjs.js +22 -22
- package/dist/edvoy-ui.css +1 -1
- package/dist/edvoy-ui.es.js +8162 -8070
- package/dist/edvoy-ui.umd.js +34 -34
- package/package.json +1 -1
|
@@ -28,6 +28,10 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
|
|
|
28
28
|
type: BooleanConstructor;
|
|
29
29
|
default: boolean;
|
|
30
30
|
};
|
|
31
|
+
indeterminate: {
|
|
32
|
+
type: BooleanConstructor;
|
|
33
|
+
default: boolean;
|
|
34
|
+
};
|
|
31
35
|
label: {
|
|
32
36
|
type: PropType<string>;
|
|
33
37
|
required: false;
|
|
@@ -56,6 +60,10 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
|
|
|
56
60
|
type: BooleanConstructor;
|
|
57
61
|
default: boolean;
|
|
58
62
|
};
|
|
63
|
+
indeterminate: {
|
|
64
|
+
type: BooleanConstructor;
|
|
65
|
+
default: boolean;
|
|
66
|
+
};
|
|
59
67
|
label: {
|
|
60
68
|
type: PropType<string>;
|
|
61
69
|
required: false;
|
|
@@ -70,6 +78,7 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
|
|
|
70
78
|
name: string;
|
|
71
79
|
errors: Record<string, any>;
|
|
72
80
|
modelValue: boolean;
|
|
81
|
+
indeterminate: boolean;
|
|
73
82
|
}, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
|
|
74
83
|
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
75
84
|
export default _default;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<label class="flex flex-row items-center scale-100 translate-x-0 translate-y-0 eui-checkbox">
|
|
4
|
-
<input
|
|
5
|
-
|
|
3
|
+
<label class="flex flex-row items-center scale-100 translate-x-0 translate-y-0 eui-checkbox" :class="{'select-none pointer-events-none opacity-50':disabled}">
|
|
4
|
+
<input
|
|
5
|
+
v-model="isChecked"
|
|
6
|
+
type="checkbox"
|
|
7
|
+
id="checkbox"
|
|
8
|
+
class="appearance-none sr-only input-checkbox"
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:indeterminate.prop="indeterminate"
|
|
11
|
+
:aria-checked="indeterminate ? 'mixed' : isChecked"
|
|
12
|
+
@change="toggle($event)"
|
|
13
|
+
/>
|
|
6
14
|
<div class="cbx-icon" for="checkbox">
|
|
7
15
|
<span>
|
|
8
16
|
<svg width="14px" height="14px">
|
|
@@ -45,6 +53,10 @@ const props = defineProps({
|
|
|
45
53
|
type: Boolean,
|
|
46
54
|
default: false,
|
|
47
55
|
},
|
|
56
|
+
indeterminate: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
default: false,
|
|
59
|
+
},
|
|
48
60
|
label: { type: String as PropType<string>, required: false, default: "" },
|
|
49
61
|
});
|
|
50
62
|
|
|
@@ -56,9 +68,7 @@ watch(isChecked, (newValue) => {
|
|
|
56
68
|
});
|
|
57
69
|
|
|
58
70
|
const toggle = ($event: Event) => {
|
|
59
|
-
|
|
60
|
-
emit("toggle", isChecked.value);
|
|
61
|
-
}
|
|
71
|
+
emit("toggle", ($event.target as HTMLInputElement).checked);
|
|
62
72
|
};
|
|
63
73
|
|
|
64
74
|
watch(props, (newValue) => {
|
|
@@ -69,37 +79,55 @@ watch(props, (newValue) => {
|
|
|
69
79
|
<style lang="scss">
|
|
70
80
|
.eui-checkbox .cbx-icon {
|
|
71
81
|
transition: all 0.2s ease;
|
|
72
|
-
@apply select-none cursor-pointer p-px rounded-md overflow-hidden inline-block scale-
|
|
82
|
+
@apply select-none cursor-pointer p-px rounded-md overflow-hidden inline-block scale-100;
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
.eui-checkbox .cbx-icon span {
|
|
76
86
|
transform: translate3d(0, 0, 0);
|
|
77
87
|
transition: all 0.2s ease;
|
|
78
|
-
@apply float-left align-middle relative size-5
|
|
88
|
+
@apply float-left align-middle relative size-5 -outline-offset-1 outline outline-1 outline-gray-400 rounded scale-100;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
.eui-checkbox .cbx-icon span > svg {
|
|
82
|
-
top:
|
|
83
|
-
left:
|
|
92
|
+
top: 5px;
|
|
93
|
+
left: 4px;
|
|
84
94
|
stroke-width: 1.75;
|
|
85
95
|
stroke-linecap: round;
|
|
86
96
|
stroke-linejoin: round;
|
|
87
97
|
stroke-dasharray: 18px;
|
|
88
98
|
stroke-dashoffset: 18px;
|
|
89
|
-
transition:
|
|
90
|
-
transition-delay: 0.1s;
|
|
99
|
+
transition: stroke-dashoffset 0.16s ease-out;
|
|
91
100
|
transform: translate3d(0, 0, 0);
|
|
92
101
|
@apply absolute fill-none stroke-white;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
.eui-checkbox .input-checkbox:checked + .cbx-icon > span {
|
|
96
|
-
animation: anim 0.
|
|
97
|
-
@apply bg-
|
|
104
|
+
.eui-checkbox .input-checkbox:checked:not(:indeterminate) + .cbx-icon > span {
|
|
105
|
+
animation: anim 0.18s ease-out;
|
|
106
|
+
@apply bg-violet-700 outline-violet-700;
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
.eui-checkbox .input-checkbox:
|
|
109
|
+
.eui-checkbox .input-checkbox:indeterminate + .cbx-icon > span {
|
|
110
|
+
@apply bg-violet-700 outline-violet-700;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.eui-checkbox .input-checkbox:indeterminate + .cbx-icon span > svg {
|
|
114
|
+
stroke-dashoffset: 18px;
|
|
115
|
+
transition: none;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.eui-checkbox .input-checkbox:indeterminate + .cbx-icon > span::after {
|
|
119
|
+
content: '';
|
|
120
|
+
animation: indeterminate-in 0.14s ease-out both;
|
|
121
|
+
@apply absolute left-1/2 top-1/2 h-0.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rounded bg-white;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.eui-checkbox .input-checkbox:checked:not(:indeterminate) + .cbx-icon span > svg {
|
|
101
125
|
stroke-dashoffset: 0;
|
|
102
|
-
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes indeterminate-in {
|
|
129
|
+
from { opacity: 0; scale: 0.5 1; }
|
|
130
|
+
to { opacity: 1; scale: 1 1; }
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
@keyframes anim {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
'is-open': isDatepickerOpen,
|
|
18
18
|
},
|
|
19
19
|
inputFilled
|
|
20
|
-
? 'overflow-hidden border border-gray-
|
|
20
|
+
? 'overflow-hidden border border-gray-200 cursor-pointer group h-14 rounded-xl eui-datepicker'
|
|
21
21
|
: 'h-10 eui-datepicker normal',
|
|
22
22
|
'relative w-full mb-2',
|
|
23
23
|
]"
|
|
@@ -283,7 +283,7 @@ const clearIconRight = computed(() =>
|
|
|
283
283
|
@apply min-w-44;
|
|
284
284
|
.dp__main {
|
|
285
285
|
.dp__input {
|
|
286
|
-
@apply pb-2 pt-6 border-0 transition-colors duration-100 ease-in-out text-sm font-medium leading-6 placeholder:text-gray-
|
|
286
|
+
@apply pb-2 pt-6 border-0 transition-colors duration-100 ease-in-out text-sm font-medium leading-6 placeholder:text-gray-600 placeholder:font-normal;
|
|
287
287
|
padding-left: v-bind(iconshowLeft);
|
|
288
288
|
padding-right: v-bind(iconshowRight);
|
|
289
289
|
}
|
|
@@ -300,7 +300,7 @@ const clearIconRight = computed(() =>
|
|
|
300
300
|
&.normal {
|
|
301
301
|
.dp__main {
|
|
302
302
|
.dp__input {
|
|
303
|
-
@apply py-2 ring-1 ring-gray-
|
|
303
|
+
@apply py-2 ring-1 ring-gray-200;
|
|
304
304
|
}
|
|
305
305
|
.dp__input.dp__input_focus {
|
|
306
306
|
@apply ring-2 h-10 rounded-md ring-purple-600;
|
|
@@ -15,6 +15,8 @@ interface Props {
|
|
|
15
15
|
searchable?: boolean;
|
|
16
16
|
backendPagination?: boolean;
|
|
17
17
|
hideNoData?: boolean;
|
|
18
|
+
/** Show a loading state while options are being fetched. */
|
|
19
|
+
loading?: boolean;
|
|
18
20
|
disabled?: boolean;
|
|
19
21
|
tag?: string;
|
|
20
22
|
tagColor?: "None" | "Success" | "Error";
|
|
@@ -26,6 +28,13 @@ interface Props {
|
|
|
26
28
|
ignoreDeselect?: boolean;
|
|
27
29
|
popperClasses?: string;
|
|
28
30
|
showCount?: boolean;
|
|
31
|
+
/** Show an All row when search has results; it selects every item in `items`. Enabled by default in multiple mode. */
|
|
32
|
+
showSelectAll?: boolean;
|
|
33
|
+
selectAllLabel?: string;
|
|
34
|
+
/** Maximum selected tags shown before the More/Less control; 0 shows all. */
|
|
35
|
+
maxVisibleTags?: number;
|
|
36
|
+
tagSize?: "sm" | "md" | "lg";
|
|
37
|
+
dropdownWidth?: string;
|
|
29
38
|
}
|
|
30
39
|
export type SelectItemKey = string | (string | number)[] | ((item: object, fallback?: any) => any);
|
|
31
40
|
declare function __VLS_template(): {
|
|
@@ -176,6 +185,7 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
|
|
|
176
185
|
onCreate?: ((value: string) => any) | undefined;
|
|
177
186
|
"onOn-deselect"?: ((value: any) => any) | undefined;
|
|
178
187
|
}>, {
|
|
188
|
+
loading: boolean;
|
|
179
189
|
disabled: boolean;
|
|
180
190
|
name: string;
|
|
181
191
|
items: any[];
|
|
@@ -199,6 +209,11 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
|
|
|
199
209
|
ignoreDeselect: boolean;
|
|
200
210
|
popperClasses: string;
|
|
201
211
|
showCount: boolean;
|
|
212
|
+
showSelectAll: boolean;
|
|
213
|
+
selectAllLabel: string;
|
|
214
|
+
maxVisibleTags: number;
|
|
215
|
+
tagSize: "sm" | "md" | "lg";
|
|
216
|
+
dropdownWidth: string;
|
|
202
217
|
}, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, false, {
|
|
203
218
|
componentRef: HTMLDivElement;
|
|
204
219
|
searchInputRef: import('../../../node_modules/vue').CreateComponentPublicInstanceWithMixins<Readonly<import('../../../node_modules/vue').ExtractPropTypes<{
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
<button
|
|
39
39
|
v-else-if="!showDropDown && selectedValues.length > 0"
|
|
40
40
|
type="button"
|
|
41
|
+
:disabled="disabled"
|
|
41
42
|
class="flex items-center justify-between w-full h-10 gap-2 px-4 py-2 text-left bg-white border border-gray-200 border-solid cursor-pointer search-textstyle rounded-3xl"
|
|
42
43
|
@click="handleOpen"
|
|
43
44
|
>
|
|
@@ -85,10 +86,10 @@
|
|
|
85
86
|
<template #content>
|
|
86
87
|
<div
|
|
87
88
|
v-if="showDropDown"
|
|
88
|
-
class="w-full p-4 overflow-y-auto text-sm bg-white border-2 border-gray-100 border-solid shadow-lg rounded-3xl max-h-72 scrollbar--hide"
|
|
89
|
+
:class="['w-full p-4 overflow-y-auto text-sm bg-white border-2 border-gray-100 border-solid shadow-lg rounded-3xl max-h-72 scrollbar--hide min-w-28', dropdownWidth]"
|
|
89
90
|
>
|
|
90
91
|
<h3
|
|
91
|
-
class="sticky z-10 p-2 mb-2 -mx-px -mt-3 font-
|
|
92
|
+
class="sticky z-10 p-2 pb-0 mb-2 -mx-px -mt-3 text-xs font-bold tracking-wide text-gray-500 uppercase bg-white -top-4"
|
|
92
93
|
>
|
|
93
94
|
Search Results
|
|
94
95
|
</h3>
|
|
@@ -100,14 +101,48 @@
|
|
|
100
101
|
aria-activedescendant="listbox-option-3"
|
|
101
102
|
class="space-y-0.5"
|
|
102
103
|
>
|
|
103
|
-
<li
|
|
104
|
+
<li
|
|
105
|
+
v-if="selectType === 'multiple' && showSelectAll && !loading && filteredItems.length"
|
|
106
|
+
role="option"
|
|
107
|
+
:aria-selected="allItemsSelected"
|
|
108
|
+
class="pb-0.5 border-b border-gray-100"
|
|
109
|
+
@click.stop
|
|
110
|
+
>
|
|
111
|
+
<EUICheckbox
|
|
112
|
+
:model-value="allItemsSelected"
|
|
113
|
+
:indeterminate="someItemsSelected && !allItemsSelected"
|
|
114
|
+
:disabled="disabled"
|
|
115
|
+
:class="[
|
|
116
|
+
'relative flex items-center text-sm transition-colors duration-75 w-full multiple-select rounded-md font-medium [&_label]:w-full [&_label]:h-9 [&_label]:px-2.5',
|
|
117
|
+
allItemsSelected || someItemsSelected
|
|
118
|
+
? 'text-gray-800 hover:bg-violet-50 hover:text-violet-600'
|
|
119
|
+
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-800',
|
|
120
|
+
]"
|
|
121
|
+
@toggle="handleSelectAll"
|
|
122
|
+
>
|
|
123
|
+
<template #label>
|
|
124
|
+
<span class="ms-2">{{ selectAllLabel }}</span>
|
|
125
|
+
</template>
|
|
126
|
+
</EUICheckbox>
|
|
127
|
+
</li>
|
|
128
|
+
<li v-if="loading" role="status" aria-live="polite" class="px-2 py-4 text-sm text-gray-500">
|
|
129
|
+
<span class="inline-flex items-center gap-2">
|
|
130
|
+
<span class="inline-block border-2 border-gray-200 rounded-full size-4 border-t-violet-600 animate-spin" aria-hidden="true"></span>
|
|
131
|
+
Loading options...
|
|
132
|
+
</span>
|
|
133
|
+
</li>
|
|
134
|
+
<li v-else-if="filteredItems.length === 0 && !hideNoData">
|
|
104
135
|
<slot name="no-items">
|
|
105
136
|
<div
|
|
106
|
-
|
|
107
|
-
class="px-2 py-4 mb-6 text-sm font-medium text-center text-gray-500 min-h-32"
|
|
137
|
+
class="px-2 py-4 text-sm font-medium text-center text-gray-500"
|
|
108
138
|
>
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
<template v-if="internalSearch.trim()">
|
|
140
|
+
No results found for "<strong>{{ internalSearch }}</strong>"
|
|
141
|
+
</template>
|
|
142
|
+
<template v-else-if="backendPagination">
|
|
143
|
+
Type in the search box to see results
|
|
144
|
+
</template>
|
|
145
|
+
<template v-else>No options available</template>
|
|
111
146
|
</div>
|
|
112
147
|
</slot>
|
|
113
148
|
</li>
|
|
@@ -135,7 +170,7 @@
|
|
|
135
170
|
<div
|
|
136
171
|
:class="[
|
|
137
172
|
selected(item)
|
|
138
|
-
? 'bg-
|
|
173
|
+
? 'bg-violet-50 text-violet-600'
|
|
139
174
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-800',
|
|
140
175
|
'relative py-2 px-4 flex items-center capitalize text-sm transition-colors duration-75 rounded',
|
|
141
176
|
]"
|
|
@@ -169,11 +204,11 @@
|
|
|
169
204
|
name="checkbox"
|
|
170
205
|
:class="[
|
|
171
206
|
selected(item)
|
|
172
|
-
? 'text-gray-800 hover:bg-
|
|
207
|
+
? 'text-gray-800 hover:bg-violet-50 hover:text-violet-600'
|
|
173
208
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-800',
|
|
174
|
-
'relative flex items-center capitalize text-sm transition-colors duration-75 w-full multiple-select rounded-md font-medium [&_label]:w-full [&_label]:
|
|
209
|
+
'relative flex items-center capitalize text-sm transition-colors duration-75 w-full multiple-select rounded-md font-medium [&_label]:w-full [&_label]:h-9 [&_label]:px-2.5',
|
|
175
210
|
]"
|
|
176
|
-
@
|
|
211
|
+
@toggle="
|
|
177
212
|
(checked) => handleCheckboxChange(item, checked)
|
|
178
213
|
"
|
|
179
214
|
>
|
|
@@ -197,26 +232,50 @@
|
|
|
197
232
|
<slot v-if="$slots.labelhint" name="labelhint"></slot>
|
|
198
233
|
<div
|
|
199
234
|
v-if="selectType === 'multiple' && selectedValues.length > 0 && showTags"
|
|
200
|
-
class="
|
|
235
|
+
class="my-2 space-y-2"
|
|
201
236
|
>
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
237
|
+
<div
|
|
238
|
+
class="px-0.5 gap-2 items-center flex-wrap"
|
|
239
|
+
:class="[
|
|
240
|
+
effectiveTagLimit ? 'flex w-full' : 'inline-flex',
|
|
241
|
+
tagsExpanded && effectiveTagLimit ? 'max-h-32 overflow-y-auto scrollbar--thin' : '',
|
|
242
|
+
]"
|
|
208
243
|
>
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@remove="handleTagRemove(item)"
|
|
244
|
+
<slot
|
|
245
|
+
v-for="(item, index) in visibleSelectedValues"
|
|
246
|
+
name="selection"
|
|
247
|
+
:item="item"
|
|
248
|
+
:clear="() => handleDeselect(item)"
|
|
249
|
+
:key="`selected-values-${index}`"
|
|
216
250
|
>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
251
|
+
<EUITag
|
|
252
|
+
class="capitalize"
|
|
253
|
+
:disabled="disabled"
|
|
254
|
+
:closeIcon="!disabled"
|
|
255
|
+
:size="tagSize"
|
|
256
|
+
color="secondary"
|
|
257
|
+
@remove="handleTagRemove(item)"
|
|
258
|
+
>
|
|
259
|
+
{{ startCase(item[itemText] || item) }}
|
|
260
|
+
</EUITag>
|
|
261
|
+
</slot>
|
|
262
|
+
<button
|
|
263
|
+
v-if="canExpandTags && !tagsExpanded"
|
|
264
|
+
type="button"
|
|
265
|
+
class="text-xs font-medium text-violet-600 hover:text-violet-700"
|
|
266
|
+
@click="tagsExpanded = true"
|
|
267
|
+
>
|
|
268
|
+
+{{ selectedValues.length - effectiveTagLimit }} more
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
271
|
+
<button
|
|
272
|
+
v-if="canExpandTags && tagsExpanded"
|
|
273
|
+
type="button"
|
|
274
|
+
class="text-xs font-medium text-gray-600 hover:text-gray-700"
|
|
275
|
+
@click="tagsExpanded = false"
|
|
276
|
+
>
|
|
277
|
+
Show less
|
|
278
|
+
</button>
|
|
220
279
|
</div>
|
|
221
280
|
<div
|
|
222
281
|
class="absolute top-0 px-2 text-xs text-white rounded right-6"
|
|
@@ -278,6 +337,8 @@ interface Props {
|
|
|
278
337
|
searchable?: boolean;
|
|
279
338
|
backendPagination?: boolean;
|
|
280
339
|
hideNoData?: boolean;
|
|
340
|
+
/** Show a loading state while options are being fetched. */
|
|
341
|
+
loading?: boolean;
|
|
281
342
|
disabled?: boolean;
|
|
282
343
|
tag?: string;
|
|
283
344
|
tagColor?: "None" | "Success" | "Error";
|
|
@@ -289,6 +350,13 @@ interface Props {
|
|
|
289
350
|
ignoreDeselect?: boolean;
|
|
290
351
|
popperClasses?: string;
|
|
291
352
|
showCount?: boolean;
|
|
353
|
+
/** Show an All row when search has results; it selects every item in `items`. Enabled by default in multiple mode. */
|
|
354
|
+
showSelectAll?: boolean;
|
|
355
|
+
selectAllLabel?: string;
|
|
356
|
+
/** Maximum selected tags shown before the More/Less control; 0 shows all. */
|
|
357
|
+
maxVisibleTags?: number;
|
|
358
|
+
tagSize?: "sm" | "md" | "lg";
|
|
359
|
+
dropdownWidth?: string,
|
|
292
360
|
}
|
|
293
361
|
const props = withDefaults(defineProps<Props>(), {
|
|
294
362
|
items: () => [],
|
|
@@ -302,6 +370,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
302
370
|
searchable: true,
|
|
303
371
|
backendPagination: false,
|
|
304
372
|
hideNoData: false,
|
|
373
|
+
loading: false,
|
|
305
374
|
disabled: false,
|
|
306
375
|
tag: "",
|
|
307
376
|
tagColor: "None",
|
|
@@ -314,6 +383,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
314
383
|
ignoreDeselect: false,
|
|
315
384
|
popperClasses: "",
|
|
316
385
|
showCount: false,
|
|
386
|
+
showSelectAll: true,
|
|
387
|
+
selectAllLabel: "All",
|
|
388
|
+
maxVisibleTags: 0,
|
|
389
|
+
tagSize: "md",
|
|
390
|
+
dropdownWidth: ''
|
|
317
391
|
});
|
|
318
392
|
|
|
319
393
|
|
|
@@ -336,7 +410,8 @@ const showDropDown = ref(false);
|
|
|
336
410
|
const options = ref<HTMLElement>();
|
|
337
411
|
const searchInputRef = ref<any>();
|
|
338
412
|
const componentRef = ref<HTMLElement>();
|
|
339
|
-
const
|
|
413
|
+
const tagsExpanded = ref(false);
|
|
414
|
+
let focusTimer: ReturnType<typeof setTimeout> | undefined;
|
|
340
415
|
|
|
341
416
|
// Computed properties
|
|
342
417
|
const selectedValues = computed({
|
|
@@ -362,6 +437,37 @@ const selected = computed(() => {
|
|
|
362
437
|
};
|
|
363
438
|
});
|
|
364
439
|
|
|
440
|
+
const sameItem = (a: any, b: any) =>
|
|
441
|
+
a != null &&
|
|
442
|
+
b != null &&
|
|
443
|
+
getPropertyFromItem(a, props.itemText) ===
|
|
444
|
+
getPropertyFromItem(b, props.itemText);
|
|
445
|
+
|
|
446
|
+
const allItemsSelected = computed(
|
|
447
|
+
() => props.items.length > 0 && props.items.every((item) => selected.value(item))
|
|
448
|
+
);
|
|
449
|
+
const someItemsSelected = computed(() =>
|
|
450
|
+
props.items.some((item) => selected.value(item))
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const effectiveTagLimit = computed(() =>
|
|
454
|
+
Number.isFinite(props.maxVisibleTags) && props.maxVisibleTags > 0
|
|
455
|
+
? Math.floor(props.maxVisibleTags)
|
|
456
|
+
: 0
|
|
457
|
+
);
|
|
458
|
+
const canExpandTags = computed(
|
|
459
|
+
() => effectiveTagLimit.value > 0 && selectedValues.value.length > effectiveTagLimit.value
|
|
460
|
+
);
|
|
461
|
+
const visibleSelectedValues = computed(() =>
|
|
462
|
+
canExpandTags.value && !tagsExpanded.value
|
|
463
|
+
? selectedValues.value.slice(0, effectiveTagLimit.value)
|
|
464
|
+
: selectedValues.value
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
watch(canExpandTags, (canExpand) => {
|
|
468
|
+
if (!canExpand) tagsExpanded.value = false;
|
|
469
|
+
});
|
|
470
|
+
|
|
365
471
|
export type SelectItemKey =
|
|
366
472
|
| string
|
|
367
473
|
| (string | number)[]
|
|
@@ -434,8 +540,7 @@ const filteredItems = computed(() => {
|
|
|
434
540
|
});
|
|
435
541
|
|
|
436
542
|
const handleDeselect = (item: any) => {
|
|
437
|
-
|
|
438
|
-
if (props.ignoreDeselect) return;
|
|
543
|
+
if (props.ignoreDeselect || props.disabled) return;
|
|
439
544
|
let index = -1;
|
|
440
545
|
if (typeof item === "object" && item[props.itemText]) {
|
|
441
546
|
index = selectedValues.value.findIndex(
|
|
@@ -464,10 +569,26 @@ const handleDeselect = (item: any) => {
|
|
|
464
569
|
};
|
|
465
570
|
|
|
466
571
|
const handleClearAll = () => {
|
|
572
|
+
if (props.disabled) return;
|
|
467
573
|
emit("update:modelValue", []);
|
|
468
574
|
emit("toggle");
|
|
469
575
|
};
|
|
470
576
|
|
|
577
|
+
const handleSelectAll = (checked: boolean) => {
|
|
578
|
+
if (props.disabled || props.selectType !== "multiple") return;
|
|
579
|
+
const next = checked
|
|
580
|
+
? [
|
|
581
|
+
...selectedValues.value,
|
|
582
|
+
...props.items.filter((item) => !selected.value(item)),
|
|
583
|
+
]
|
|
584
|
+
: selectedValues.value.filter(
|
|
585
|
+
(value: any) => !props.items.some((item) => sameItem(value, item))
|
|
586
|
+
);
|
|
587
|
+
emit("update:modelValue", next);
|
|
588
|
+
emit("on-change", next);
|
|
589
|
+
emit("toggle", next);
|
|
590
|
+
};
|
|
591
|
+
|
|
471
592
|
const handleTagRemove = (item: any) => {
|
|
472
593
|
handleDeselect(item);
|
|
473
594
|
};
|
|
@@ -499,6 +620,7 @@ const handleUpdate = (items: any) => {
|
|
|
499
620
|
};
|
|
500
621
|
|
|
501
622
|
const handleSelect = (item: any) => {
|
|
623
|
+
if (props.disabled) return;
|
|
502
624
|
let newSelection: any;
|
|
503
625
|
|
|
504
626
|
if (props.selectType === "multiple" && typeof item === "object") {
|
|
@@ -537,25 +659,28 @@ const handleSelect = (item: any) => {
|
|
|
537
659
|
};
|
|
538
660
|
|
|
539
661
|
const handleOpen = () => {
|
|
540
|
-
|
|
662
|
+
if (props.disabled || showDropDown.value) return;
|
|
541
663
|
|
|
542
664
|
// Only clear search for single select, preserve it for multiple select
|
|
543
665
|
if (props.selectType === "single") {
|
|
544
666
|
internalSearch.value = "";
|
|
545
667
|
}
|
|
546
668
|
showDropDown.value = true;
|
|
669
|
+
window.addEventListener("scroll", handleScroll, true);
|
|
547
670
|
emit("open");
|
|
548
671
|
|
|
549
672
|
nextTick(() => {
|
|
550
|
-
|
|
673
|
+
if (!showDropDown.value) return;
|
|
674
|
+
focusTimer = setTimeout(() => {
|
|
675
|
+
focusTimer = undefined;
|
|
676
|
+
if (!showDropDown.value) return;
|
|
551
677
|
internalSearch.value = "";
|
|
552
|
-
isOpening.value = false;
|
|
553
678
|
if (searchInputRef.value?.searchInput) {
|
|
554
|
-
searchInputRef.value.searchInput.focus();
|
|
679
|
+
searchInputRef.value.searchInput.focus({ preventScroll: true });
|
|
555
680
|
} else if (searchInputRef.value?.$el) {
|
|
556
681
|
const inputElement = searchInputRef.value.$el.querySelector("input");
|
|
557
682
|
if (inputElement) {
|
|
558
|
-
inputElement.focus();
|
|
683
|
+
inputElement.focus({ preventScroll: true });
|
|
559
684
|
}
|
|
560
685
|
}
|
|
561
686
|
}, 50);
|
|
@@ -563,6 +688,11 @@ const handleOpen = () => {
|
|
|
563
688
|
};
|
|
564
689
|
|
|
565
690
|
const handleClose = () => {
|
|
691
|
+
window.removeEventListener("scroll", handleScroll, true);
|
|
692
|
+
if (focusTimer) {
|
|
693
|
+
clearTimeout(focusTimer);
|
|
694
|
+
focusTimer = undefined;
|
|
695
|
+
}
|
|
566
696
|
if (showDropDown.value) {
|
|
567
697
|
emit("blur");
|
|
568
698
|
emit("close");
|
|
@@ -581,30 +711,25 @@ const handleSearchFocus = () => {
|
|
|
581
711
|
|
|
582
712
|
// Document click handler for outside clicks
|
|
583
713
|
const handleDocumentClick = (event: MouseEvent) => {
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const isOutsideComponent = !componentRef.value.contains(target);
|
|
593
|
-
|
|
594
|
-
// Check if click is outside any popper content
|
|
595
|
-
const popperElements = document.querySelectorAll(
|
|
596
|
-
'[data-popper-placement], .popper-defaultstyle, [role="tooltip"]'
|
|
597
|
-
);
|
|
598
|
-
const isOutsidePopper = Array.from(popperElements).every(
|
|
599
|
-
(popper) => !popper.contains(target)
|
|
600
|
-
);
|
|
601
|
-
|
|
602
|
-
if (isOutsideComponent && isOutsidePopper) {
|
|
603
|
-
handleClose();
|
|
604
|
-
}
|
|
714
|
+
// The trigger can be replaced during this click. composedPath retains its
|
|
715
|
+
// original ancestors, so an inside click stays inside after that DOM update.
|
|
716
|
+
if (
|
|
717
|
+
showDropDown.value &&
|
|
718
|
+
componentRef.value &&
|
|
719
|
+
!event.composedPath().includes(componentRef.value)
|
|
720
|
+
) {
|
|
721
|
+
handleClose();
|
|
605
722
|
}
|
|
606
723
|
};
|
|
607
724
|
|
|
725
|
+
const handleScroll = (event: Event) => {
|
|
726
|
+
if (!showDropDown.value) return;
|
|
727
|
+
const target = event.target;
|
|
728
|
+
// Keep the options usable; scrolling any ancestor or the page closes them.
|
|
729
|
+
if (target instanceof Node && componentRef.value?.contains(target)) return;
|
|
730
|
+
handleClose();
|
|
731
|
+
};
|
|
732
|
+
|
|
608
733
|
// Lifecycle hooks
|
|
609
734
|
onMounted(() => {
|
|
610
735
|
document.addEventListener("click", handleDocumentClick);
|
|
@@ -612,6 +737,8 @@ onMounted(() => {
|
|
|
612
737
|
|
|
613
738
|
onUnmounted(() => {
|
|
614
739
|
document.removeEventListener("click", handleDocumentClick);
|
|
740
|
+
window.removeEventListener("scroll", handleScroll, true);
|
|
741
|
+
if (focusTimer) clearTimeout(focusTimer);
|
|
615
742
|
});
|
|
616
743
|
|
|
617
744
|
// Watchers
|
|
@@ -619,13 +746,6 @@ watch(internalSearch, (value: string) => {
|
|
|
619
746
|
emit("search", value);
|
|
620
747
|
});
|
|
621
748
|
|
|
622
|
-
watch(
|
|
623
|
-
() => props.modelValue,
|
|
624
|
-
(newValue, oldValue) => {
|
|
625
|
-
console.log("changed from:", oldValue, "to:", newValue);
|
|
626
|
-
},
|
|
627
|
-
{ deep: true }
|
|
628
|
-
);
|
|
629
749
|
</script>
|
|
630
750
|
|
|
631
751
|
<style lang="scss">
|