@volverjs/ui-vue 0.0.10-beta.40 → 0.0.10-beta.41
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/VvCheckboxGroup/VvCheckboxGroup.es.js +12 -3
- package/dist/components/VvCheckboxGroup/VvCheckboxGroup.umd.js +1 -1
- package/dist/components/VvCombobox/VvCombobox.es.js +183 -125
- package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
- package/dist/components/VvCombobox/VvCombobox.vue.d.ts +6 -0
- package/dist/components/VvCombobox/index.d.ts +54 -22
- package/dist/components/VvRadioGroup/VvRadioGroup.es.js +12 -3
- package/dist/components/VvRadioGroup/VvRadioGroup.umd.js +1 -1
- package/dist/components/VvSelect/VvSelect.es.js +79 -46
- package/dist/components/VvSelect/VvSelect.umd.js +1 -1
- package/dist/components/VvSelect/VvSelect.vue.d.ts +3 -0
- package/dist/components/VvSelect/index.d.ts +14 -0
- package/dist/components/index.es.js +183 -122
- package/dist/components/index.umd.js +1 -1
- package/dist/icons.es.js +3 -3
- package/dist/icons.umd.js +1 -1
- package/dist/stories/Combobox/Combobox.stories.d.ts +1 -0
- package/dist/stories/Combobox/ComboboxMultiple.stories.d.ts +1 -0
- package/dist/stories/Select/Select.stories.d.ts +1 -0
- package/package.json +18 -18
- package/src/assets/icons/detailed.json +1 -1
- package/src/assets/icons/normal.json +1 -1
- package/src/assets/icons/simple.json +1 -1
- package/src/components/VvCombobox/VvCombobox.vue +105 -97
- package/src/components/VvCombobox/index.ts +19 -2
- package/src/components/VvSelect/VvSelect.vue +66 -46
- package/src/components/VvSelect/index.ts +8 -1
- package/src/composables/useOptions.ts +12 -11
- package/src/stories/Combobox/Combobox.settings.ts +18 -3
- package/src/stories/Combobox/Combobox.stories.ts +8 -0
- package/src/stories/Combobox/Combobox.test.ts +6 -4
- package/src/stories/Combobox/ComboboxMultiple.stories.ts +9 -0
- package/src/stories/Combobox/ComboboxOptions.stories.ts +9 -13
- package/src/stories/Select/Select.stories.ts +8 -0
- package/src/stories/Select/Select.test.ts +5 -3
|
@@ -27,13 +27,11 @@ const propsDefaults = useDefaults<typeof VvComboboxProps>(
|
|
|
27
27
|
props,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return option.options?.length
|
|
36
|
-
}
|
|
30
|
+
// template ref
|
|
31
|
+
const inputEl: Ref<HTMLElement | null> = ref(null)
|
|
32
|
+
const inputSearchEl: Ref<HTMLElement | null> = ref(null)
|
|
33
|
+
const wrapperEl: Ref<HTMLElement | null> = ref(null)
|
|
34
|
+
const dropdownEl = ref<typeof VvDropdown | null>(null)
|
|
37
35
|
|
|
38
36
|
// hint slot
|
|
39
37
|
const {
|
|
@@ -43,11 +41,6 @@ const {
|
|
|
43
41
|
hintSlotScope,
|
|
44
42
|
} = HintSlotFactory(propsDefaults, slots)
|
|
45
43
|
|
|
46
|
-
// template ref
|
|
47
|
-
const inputEl: Ref<HTMLElement | null> = ref(null)
|
|
48
|
-
const inputSearchEl: Ref<HTMLElement | null> = ref(null)
|
|
49
|
-
const wrapperEl: Ref<HTMLElement | null> = ref(null)
|
|
50
|
-
|
|
51
44
|
// focus
|
|
52
45
|
const { focused } = useComponentFocus(inputEl, emit)
|
|
53
46
|
const { focused: focusedWithin } = useFocusWithin(wrapperEl)
|
|
@@ -87,17 +80,17 @@ watch(debouncedSearchText, () => {
|
|
|
87
80
|
// expanded
|
|
88
81
|
const expanded = ref(false)
|
|
89
82
|
function toggleExpanded() {
|
|
90
|
-
if (
|
|
83
|
+
if (isDisabledOrReadonly.value)
|
|
91
84
|
return
|
|
92
85
|
expanded.value = !expanded.value
|
|
93
86
|
}
|
|
94
87
|
function expand() {
|
|
95
|
-
if (
|
|
88
|
+
if (isDisabledOrReadonly.value || expanded.value)
|
|
96
89
|
return
|
|
97
90
|
expanded.value = true
|
|
98
91
|
}
|
|
99
92
|
function collapse() {
|
|
100
|
-
if (
|
|
93
|
+
if (isDisabledOrReadonly.value || !expanded.value)
|
|
101
94
|
return
|
|
102
95
|
expanded.value = false
|
|
103
96
|
}
|
|
@@ -116,6 +109,14 @@ function onAfterCollapse() {
|
|
|
116
109
|
}
|
|
117
110
|
}
|
|
118
111
|
|
|
112
|
+
// group
|
|
113
|
+
function isGroup(option: T) {
|
|
114
|
+
if (typeof option === 'string') {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
return option.options?.length
|
|
118
|
+
}
|
|
119
|
+
|
|
119
120
|
// data
|
|
120
121
|
const {
|
|
121
122
|
id,
|
|
@@ -135,24 +136,62 @@ const hasDropdownId = computed(() => `${hasId.value}-dropdown`)
|
|
|
135
136
|
const hasSearchId = computed(() => `${hasId.value}-search`)
|
|
136
137
|
const hasLabelId = computed(() => `${hasId.value}-label`)
|
|
137
138
|
|
|
139
|
+
// tabindex
|
|
140
|
+
const isDisabledOrReadonly = computed(() => props.disabled || props.readonly)
|
|
141
|
+
const hasTabindex = computed(() => {
|
|
142
|
+
return isDisabledOrReadonly.value ? -1 : props.tabindex
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// modelValue
|
|
146
|
+
const localModelValue = computed({
|
|
147
|
+
get: () => {
|
|
148
|
+
if (Array.isArray(props.modelValue)) {
|
|
149
|
+
return new Set(props.modelValue)
|
|
150
|
+
}
|
|
151
|
+
return props.modelValue !== undefined && props.modelValue !== null ? new Set([props.modelValue]) : new Set()
|
|
152
|
+
},
|
|
153
|
+
set: (value: Set<unknown>) => {
|
|
154
|
+
emit('update:modelValue', props.multiple ? [...value] : [...value].pop())
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
const sizeOfModelValue = computed(() => localModelValue.value.size)
|
|
158
|
+
const isDirty = computed(() => sizeOfModelValue.value > 0)
|
|
159
|
+
const hasMaxValues = computed(() => {
|
|
160
|
+
if (!props.multiple) {
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
if (props.maxValues === undefined) {
|
|
164
|
+
return Infinity
|
|
165
|
+
}
|
|
166
|
+
return Number(props.maxValues)
|
|
167
|
+
})
|
|
168
|
+
const isUnselectable = computed(() => {
|
|
169
|
+
if (isDisabledOrReadonly.value) {
|
|
170
|
+
return false
|
|
171
|
+
}
|
|
172
|
+
// DEPRECATED: Must be removed in the future
|
|
173
|
+
if (!props.unselectable) {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
return sizeOfModelValue.value > Number(props.minValues)
|
|
177
|
+
})
|
|
178
|
+
const isSelectable = computed(() => {
|
|
179
|
+
if (isDisabledOrReadonly.value) {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
if (!props.multiple) {
|
|
183
|
+
return true
|
|
184
|
+
}
|
|
185
|
+
return sizeOfModelValue.value < hasMaxValues.value
|
|
186
|
+
})
|
|
187
|
+
|
|
138
188
|
// loading
|
|
139
189
|
const localLoading = ref(false)
|
|
140
190
|
const isLoading = computed(() => localLoading.value || loading.value)
|
|
141
191
|
|
|
142
|
-
// ref
|
|
143
|
-
const dropdownEl = ref()
|
|
144
|
-
|
|
145
192
|
// icons
|
|
146
193
|
const { hasIconBefore, hasIconAfter } = useComponentIcon(icon, iconPosition)
|
|
147
194
|
|
|
148
|
-
// dirty
|
|
149
|
-
const isDirty = computed(() => !isEmpty(props.modelValue))
|
|
150
|
-
|
|
151
|
-
// tabindex
|
|
152
|
-
const hasTabindex = computed(() => {
|
|
153
|
-
return disabled.value || readonly.value ? -1 : props.tabindex
|
|
154
|
-
})
|
|
155
|
-
|
|
156
195
|
// styles
|
|
157
196
|
const bemCssClasses = useModifiers(
|
|
158
197
|
'vv-select',
|
|
@@ -172,6 +211,7 @@ const bemCssClasses = useModifiers(
|
|
|
172
211
|
})),
|
|
173
212
|
)
|
|
174
213
|
|
|
214
|
+
// options
|
|
175
215
|
const {
|
|
176
216
|
getOptionLabel,
|
|
177
217
|
getOptionValue,
|
|
@@ -179,7 +219,10 @@ const {
|
|
|
179
219
|
isOptionDisabled,
|
|
180
220
|
} = useOptions(props)
|
|
181
221
|
|
|
182
|
-
|
|
222
|
+
function isOptionDisabledOrNotSelectable(option: T) {
|
|
223
|
+
return isOptionDisabled(option) || (!isSelectable.value && !isOptionSelected(option))
|
|
224
|
+
}
|
|
225
|
+
|
|
183
226
|
const filteredOptions = computedAsync(async () => {
|
|
184
227
|
if (propsDefaults.value.searchFunction) {
|
|
185
228
|
localLoading.value = true
|
|
@@ -200,22 +243,11 @@ const filteredOptions = computedAsync(async () => {
|
|
|
200
243
|
})
|
|
201
244
|
|
|
202
245
|
/**
|
|
203
|
-
* Check if an option
|
|
246
|
+
* Check if an option is selected
|
|
204
247
|
* @param {T} option
|
|
205
248
|
*/
|
|
206
249
|
function isOptionSelected(option: T) {
|
|
207
|
-
|
|
208
|
-
// check if contain whole option or option value
|
|
209
|
-
return (
|
|
210
|
-
contains(option, props.modelValue)
|
|
211
|
-
|| contains(getOptionValue(option), props.modelValue)
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
// check if modelValue is equal to option or option value
|
|
215
|
-
return (
|
|
216
|
-
equals(option, props.modelValue)
|
|
217
|
-
|| equals(getOptionValue(option), props.modelValue)
|
|
218
|
-
)
|
|
250
|
+
return localModelValue.value.has(getOptionValue(option))
|
|
219
251
|
}
|
|
220
252
|
|
|
221
253
|
/**
|
|
@@ -251,60 +283,37 @@ function onClickInput() {
|
|
|
251
283
|
}
|
|
252
284
|
|
|
253
285
|
/**
|
|
254
|
-
* Function triggered on
|
|
286
|
+
* Function triggered on option click
|
|
255
287
|
* @param option {T} option value
|
|
256
288
|
*/
|
|
257
289
|
function onInput(option: T) {
|
|
258
|
-
|
|
259
|
-
|
|
290
|
+
const isSelected = isOptionSelected(option)
|
|
291
|
+
if (isSelected && isUnselectable.value) {
|
|
292
|
+
localModelValue.value.delete(getOptionValue(option))
|
|
260
293
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const value = getOptionValue(option)
|
|
264
|
-
let toReturn = value
|
|
265
|
-
|
|
266
|
-
// check multiple prop, override value with array and remove or add the value
|
|
267
|
-
if (props.multiple) {
|
|
268
|
-
// check max-values prop and block check new values
|
|
269
|
-
if (Array.isArray(props.modelValue)) {
|
|
270
|
-
const maxValues = Number(props.maxValues)
|
|
271
|
-
if (
|
|
272
|
-
props.maxValues !== undefined
|
|
273
|
-
&& maxValues >= 0
|
|
274
|
-
&& props.modelValue?.length >= maxValues
|
|
275
|
-
) {
|
|
276
|
-
if (!contains(value, props.modelValue)) {
|
|
277
|
-
// maxValues reached
|
|
278
|
-
return
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
toReturn = contains(value, props.modelValue)
|
|
282
|
-
? removeFromList(value, props.modelValue)
|
|
283
|
-
: [...props.modelValue, value]
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
toReturn = [value]
|
|
287
|
-
}
|
|
294
|
+
else if (!isSelected && isSelectable.value) {
|
|
295
|
+
localModelValue.value.add(getOptionValue(option))
|
|
288
296
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (Array.isArray(props.modelValue)) {
|
|
294
|
-
if (props.unselectable && props.modelValue.includes(value)) {
|
|
295
|
-
toReturn = []
|
|
296
|
-
}
|
|
297
|
-
else {
|
|
298
|
-
toReturn = [value]
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else if (props.unselectable && value === props.modelValue) {
|
|
302
|
-
toReturn = undefined
|
|
303
|
-
}
|
|
297
|
+
// force reactivity
|
|
298
|
+
localModelValue.value = new Set(localModelValue.value)
|
|
299
|
+
if (!props.multiple && !props.keepOpen) {
|
|
300
|
+
collapse()
|
|
304
301
|
}
|
|
305
|
-
emit('update:modelValue', toReturn)
|
|
306
302
|
}
|
|
307
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Auto select the first option if autoOpen is enabled
|
|
306
|
+
*/
|
|
307
|
+
watch(
|
|
308
|
+
() => props.options,
|
|
309
|
+
(newValue) => {
|
|
310
|
+
if (newValue?.length && props.autoselectFirst && !isDirty.value) {
|
|
311
|
+
onInput(newValue[0])
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{ immediate: true },
|
|
315
|
+
)
|
|
316
|
+
|
|
308
317
|
const selectProps = computed(() => ({
|
|
309
318
|
id: hasId.value,
|
|
310
319
|
name: props.name,
|
|
@@ -325,7 +334,8 @@ const selectProps = computed(() => ({
|
|
|
325
334
|
icon: propsDefaults.value.icon,
|
|
326
335
|
iconPosition: propsDefaults.value.iconPosition,
|
|
327
336
|
floating: propsDefaults.value.floating,
|
|
328
|
-
unselectable:
|
|
337
|
+
unselectable: isUnselectable.value,
|
|
338
|
+
autoselectFirst: propsDefaults.value.autoselectFirst,
|
|
329
339
|
multiple: propsDefaults.value.multiple,
|
|
330
340
|
label: propsDefaults.value.label,
|
|
331
341
|
placeholder: propsDefaults.value.placeholder,
|
|
@@ -357,7 +367,7 @@ const slotProps = computed(() => ({
|
|
|
357
367
|
modelValue: props.modelValue,
|
|
358
368
|
}))
|
|
359
369
|
|
|
360
|
-
//
|
|
370
|
+
// keyboard
|
|
361
371
|
onKeyStroke(
|
|
362
372
|
[' ', 'Enter'],
|
|
363
373
|
(e) => {
|
|
@@ -478,9 +488,7 @@ export default {
|
|
|
478
488
|
{{ getOptionLabel(option) }}
|
|
479
489
|
<button
|
|
480
490
|
v-if="
|
|
481
|
-
|
|
482
|
-
&& !readonly
|
|
483
|
-
&& !disabled
|
|
491
|
+
isUnselectable
|
|
484
492
|
"
|
|
485
493
|
:aria-label="
|
|
486
494
|
propsDefaults.deselectActionLabel
|
|
@@ -524,8 +532,8 @@ export default {
|
|
|
524
532
|
)"
|
|
525
533
|
v-bind="{
|
|
526
534
|
selected: isOptionSelected(item),
|
|
527
|
-
disabled:
|
|
528
|
-
unselectable,
|
|
535
|
+
disabled: isOptionDisabledOrNotSelectable(item),
|
|
536
|
+
unselectable: isUnselectable,
|
|
529
537
|
deselectHintLabel:
|
|
530
538
|
propsDefaults.deselectHintLabel,
|
|
531
539
|
selectHintLabel:
|
|
@@ -545,7 +553,7 @@ export default {
|
|
|
545
553
|
option,
|
|
546
554
|
selectedOptions,
|
|
547
555
|
selected: isOptionSelected(item),
|
|
548
|
-
disabled:
|
|
556
|
+
disabled: isOptionDisabledOrNotSelectable(item),
|
|
549
557
|
}"
|
|
550
558
|
>
|
|
551
559
|
{{ getOptionLabel(item) }}
|
|
@@ -556,8 +564,8 @@ export default {
|
|
|
556
564
|
v-else
|
|
557
565
|
v-bind="{
|
|
558
566
|
selected: isOptionSelected(option),
|
|
559
|
-
disabled:
|
|
560
|
-
unselectable,
|
|
567
|
+
disabled: isOptionDisabledOrNotSelectable(option),
|
|
568
|
+
unselectable: isUnselectable,
|
|
561
569
|
deselectHintLabel:
|
|
562
570
|
propsDefaults.deselectHintLabel,
|
|
563
571
|
selectHintLabel:
|
|
@@ -576,7 +584,7 @@ export default {
|
|
|
576
584
|
option,
|
|
577
585
|
selectedOptions,
|
|
578
586
|
selected: isOptionSelected(option),
|
|
579
|
-
disabled:
|
|
587
|
+
disabled: isOptionDisabledOrNotSelectable(option),
|
|
580
588
|
}"
|
|
581
589
|
>
|
|
582
590
|
{{ getOptionLabel(option) }}
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
IconProps,
|
|
13
13
|
TabindexProps,
|
|
14
14
|
FloatingLabelProps,
|
|
15
|
-
UnselectableProps,
|
|
16
15
|
IdNameProps,
|
|
17
16
|
DropdownProps,
|
|
18
17
|
LabelProps,
|
|
@@ -43,7 +42,6 @@ export const VvComboboxProps = {
|
|
|
43
42
|
...OptionsProps,
|
|
44
43
|
...IconProps,
|
|
45
44
|
...FloatingLabelProps,
|
|
46
|
-
...UnselectableProps,
|
|
47
45
|
...DropdownProps,
|
|
48
46
|
...LabelProps,
|
|
49
47
|
/**
|
|
@@ -126,10 +124,22 @@ export const VvComboboxProps = {
|
|
|
126
124
|
* Manage modelValue as string[] or object[]
|
|
127
125
|
*/
|
|
128
126
|
multiple: Boolean,
|
|
127
|
+
/**
|
|
128
|
+
* The min number of selected values
|
|
129
|
+
*/
|
|
130
|
+
minValues: {
|
|
131
|
+
type: [Number, String],
|
|
132
|
+
default: 0,
|
|
133
|
+
},
|
|
129
134
|
/**
|
|
130
135
|
* The max number of selected values
|
|
131
136
|
*/
|
|
132
137
|
maxValues: [Number, String],
|
|
138
|
+
/**
|
|
139
|
+
* If true the input will be unselectable
|
|
140
|
+
* @deprecated use minValues instead
|
|
141
|
+
*/
|
|
142
|
+
unselectable: { type: Boolean, default: true },
|
|
133
143
|
/**
|
|
134
144
|
* The select label separator visible to the user
|
|
135
145
|
*/
|
|
@@ -170,6 +180,13 @@ export const VvComboboxProps = {
|
|
|
170
180
|
type: Boolean,
|
|
171
181
|
default: false,
|
|
172
182
|
},
|
|
183
|
+
/**
|
|
184
|
+
* Select first option automatically
|
|
185
|
+
*/
|
|
186
|
+
autoselectFirst: {
|
|
187
|
+
type: Boolean,
|
|
188
|
+
default: false,
|
|
189
|
+
},
|
|
173
190
|
/**
|
|
174
191
|
* Keep open dropdown on single select
|
|
175
192
|
*/
|
|
@@ -20,7 +20,7 @@ const propsDefaults = useDefaults<typeof VvSelectProps>(
|
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
// template refs
|
|
23
|
-
const
|
|
23
|
+
const selectEl = ref()
|
|
24
24
|
|
|
25
25
|
// hint
|
|
26
26
|
const {
|
|
@@ -30,6 +30,17 @@ const {
|
|
|
30
30
|
hintSlotScope,
|
|
31
31
|
} = HintSlotFactory(propsDefaults, slots)
|
|
32
32
|
|
|
33
|
+
// focus
|
|
34
|
+
const { focused } = useComponentFocus(selectEl, emit)
|
|
35
|
+
|
|
36
|
+
// group
|
|
37
|
+
function isGroup(option: T) {
|
|
38
|
+
if (typeof option === 'string') {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
return option.options?.length
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
// data
|
|
34
45
|
const {
|
|
35
46
|
id,
|
|
@@ -44,34 +55,48 @@ const {
|
|
|
44
55
|
floating,
|
|
45
56
|
multiple,
|
|
46
57
|
} = toRefs(props)
|
|
47
|
-
|
|
48
|
-
// computed
|
|
49
58
|
const hasId = useUniqueId(id)
|
|
50
59
|
const hasHintId = computed(() => `${hasId.value}-hint`)
|
|
51
60
|
|
|
52
|
-
//
|
|
53
|
-
const
|
|
61
|
+
// tabindex
|
|
62
|
+
const isDisabledOrReadonly = computed(() => props.disabled || props.readonly)
|
|
63
|
+
const hasTabindex = computed(() => {
|
|
64
|
+
return isDisabledOrReadonly.value ? -1 : props.tabindex
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// modelValue
|
|
68
|
+
const localModelValue = computed({
|
|
69
|
+
get: () => {
|
|
70
|
+
return props.modelValue
|
|
71
|
+
},
|
|
72
|
+
set: (newValue) => {
|
|
73
|
+
if (Array.isArray(newValue)) {
|
|
74
|
+
newValue = newValue.filter(item => item !== undefined)
|
|
75
|
+
if (newValue.length === 0 && !props.unselectable) {
|
|
76
|
+
selectEl.value.value = props.modelValue
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
emit('update:modelValue', newValue)
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
const isDirty = computed(() => {
|
|
84
|
+
if (Array.isArray(localModelValue.value)) {
|
|
85
|
+
return localModelValue.value.length > 0
|
|
86
|
+
}
|
|
87
|
+
return localModelValue.value !== undefined && localModelValue.value !== null
|
|
88
|
+
})
|
|
54
89
|
|
|
55
90
|
// visibility
|
|
56
|
-
const isVisible = useElementVisibility(
|
|
91
|
+
const isVisible = useElementVisibility(selectEl)
|
|
57
92
|
watch(isVisible, (newValue) => {
|
|
58
93
|
if (newValue && props.autofocus) {
|
|
59
94
|
focused.value = true
|
|
60
95
|
}
|
|
61
96
|
})
|
|
62
|
-
|
|
63
97
|
// icons
|
|
64
98
|
const { hasIconBefore, hasIconAfter } = useComponentIcon(icon, iconPosition)
|
|
65
99
|
|
|
66
|
-
// dirty
|
|
67
|
-
const isDirty = computed(() => !isEmpty(props.modelValue))
|
|
68
|
-
|
|
69
|
-
// disabled
|
|
70
|
-
const isDisabled = computed(() => props.disabled || props.readonly)
|
|
71
|
-
const hasTabindex = computed(() => {
|
|
72
|
-
return isDisabled.value ? -1 : props.tabindex
|
|
73
|
-
})
|
|
74
|
-
|
|
75
100
|
// invalid
|
|
76
101
|
const isInvalid = computed(() => {
|
|
77
102
|
if (props.invalid === true) {
|
|
@@ -102,12 +127,34 @@ const bemCssClasses = useModifiers(
|
|
|
102
127
|
})),
|
|
103
128
|
)
|
|
104
129
|
|
|
130
|
+
// options
|
|
131
|
+
const {
|
|
132
|
+
getOptionLabel,
|
|
133
|
+
getOptionValue,
|
|
134
|
+
isOptionDisabled,
|
|
135
|
+
getOptionGrouped,
|
|
136
|
+
} = useOptions(props)
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Auto select the first option if autoOpen is enabled
|
|
140
|
+
*/
|
|
141
|
+
watch(
|
|
142
|
+
() => props.options,
|
|
143
|
+
(newValue) => {
|
|
144
|
+
if (newValue?.length && props.autoselectFirst && !isDirty.value) {
|
|
145
|
+
const firstOptionValue = getOptionValue(newValue[0])
|
|
146
|
+
localModelValue.value = props.multiple ? [firstOptionValue] : firstOptionValue
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{ immediate: true },
|
|
150
|
+
)
|
|
151
|
+
|
|
105
152
|
// attrs
|
|
106
153
|
const hasAttrs: SelectHTMLAttributes = computed(() => {
|
|
107
154
|
return {
|
|
108
155
|
'name': props.name,
|
|
109
156
|
'tabindex': hasTabindex.value,
|
|
110
|
-
'disabled':
|
|
157
|
+
'disabled': isDisabledOrReadonly.value,
|
|
111
158
|
'required': props.required,
|
|
112
159
|
'size': props.size,
|
|
113
160
|
'autocomplete': props.autocomplete,
|
|
@@ -128,33 +175,6 @@ const slotProps = computed(() => ({
|
|
|
128
175
|
invalid: props.invalid,
|
|
129
176
|
modelValue: props.modelValue,
|
|
130
177
|
}))
|
|
131
|
-
|
|
132
|
-
const {
|
|
133
|
-
getOptionLabel,
|
|
134
|
-
getOptionValue,
|
|
135
|
-
isOptionDisabled,
|
|
136
|
-
getOptionGrouped,
|
|
137
|
-
} = useOptions(props)
|
|
138
|
-
|
|
139
|
-
const localModelValue = computed({
|
|
140
|
-
get: () => {
|
|
141
|
-
return props.modelValue
|
|
142
|
-
},
|
|
143
|
-
set: (newValue) => {
|
|
144
|
-
if (Array.isArray(newValue)) {
|
|
145
|
-
newValue = newValue.filter(item => item !== undefined)
|
|
146
|
-
}
|
|
147
|
-
emit('update:modelValue', newValue)
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// Grouped options
|
|
152
|
-
function isGroup(option: T) {
|
|
153
|
-
if (typeof option === 'string') {
|
|
154
|
-
return false
|
|
155
|
-
}
|
|
156
|
-
return option.options?.length
|
|
157
|
-
}
|
|
158
178
|
</script>
|
|
159
179
|
|
|
160
180
|
<script lang="ts">
|
|
@@ -180,9 +200,9 @@ export default {
|
|
|
180
200
|
/>
|
|
181
201
|
<select
|
|
182
202
|
:id="hasId"
|
|
183
|
-
ref="
|
|
184
|
-
v-model="localModelValue"
|
|
203
|
+
ref="selectEl"
|
|
185
204
|
v-bind="hasAttrs"
|
|
205
|
+
v-model="localModelValue"
|
|
186
206
|
>
|
|
187
207
|
<option
|
|
188
208
|
v-if="placeholder"
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
IconProps,
|
|
13
13
|
IdNameProps,
|
|
14
14
|
FloatingLabelProps,
|
|
15
|
-
UnselectableProps,
|
|
16
15
|
AutofocusProps,
|
|
17
16
|
AutocompleteProps,
|
|
18
17
|
LabelProps,
|
|
18
|
+
UnselectableProps,
|
|
19
19
|
} from '../../props'
|
|
20
20
|
import type { Option } from '../../types/generic'
|
|
21
21
|
|
|
@@ -68,6 +68,13 @@ export const VvSelectProps = {
|
|
|
68
68
|
type: [String, Number, Boolean, Object, Array],
|
|
69
69
|
default: undefined,
|
|
70
70
|
},
|
|
71
|
+
/**
|
|
72
|
+
* Select first option automatically
|
|
73
|
+
*/
|
|
74
|
+
autoselectFirst: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false,
|
|
77
|
+
},
|
|
71
78
|
/**
|
|
72
79
|
* Select placeholder
|
|
73
80
|
*/
|
|
@@ -8,10 +8,11 @@ export function useOptions(props: any) {
|
|
|
8
8
|
if (typeof option === 'string') {
|
|
9
9
|
return option
|
|
10
10
|
}
|
|
11
|
+
if (typeof labelKey.value === 'function') {
|
|
12
|
+
return labelKey.value(option)
|
|
13
|
+
}
|
|
11
14
|
return String(
|
|
12
|
-
|
|
13
|
-
? labelKey.value(option)
|
|
14
|
-
: get(option as object, labelKey.value),
|
|
15
|
+
labelKey.value ? get(option as object, labelKey.value) : option,
|
|
15
16
|
)
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -19,20 +20,20 @@ export function useOptions(props: any) {
|
|
|
19
20
|
if (typeof option === 'string') {
|
|
20
21
|
return option
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (typeof valueKey.value === 'function') {
|
|
24
|
+
return valueKey.value(option)
|
|
25
|
+
}
|
|
26
|
+
return valueKey.value ? get(option as object, valueKey.value) : option
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const isOptionDisabled = <T extends string | Option>(option: T): boolean => {
|
|
29
30
|
if (typeof option === 'string') {
|
|
30
31
|
return false
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (typeof disabledKey.value === 'function') {
|
|
34
|
+
return disabledKey.value(option)
|
|
35
|
+
}
|
|
36
|
+
return disabledKey.value ? get(option as object, disabledKey.value) : false
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const getOptionGrouped = <T extends string | Option>(option: T) => {
|
|
@@ -60,8 +60,8 @@ export const argTypes: ArgTypes = {
|
|
|
60
60
|
...OptionsArgTypes,
|
|
61
61
|
...IconArgTypes,
|
|
62
62
|
...FloatingLabelArgTypes,
|
|
63
|
-
...UnselectableArgTypes,
|
|
64
63
|
...DropdownArgTypes,
|
|
64
|
+
...UnselectableArgTypes,
|
|
65
65
|
'triggerWidth': {
|
|
66
66
|
...DropdownArgTypes.triggerWidth,
|
|
67
67
|
table: {
|
|
@@ -215,9 +215,24 @@ export const argTypes: ArgTypes = {
|
|
|
215
215
|
},
|
|
216
216
|
'maxValues': {
|
|
217
217
|
description: 'Max number of selected values',
|
|
218
|
-
|
|
219
218
|
control: {
|
|
220
|
-
type: '
|
|
219
|
+
type: 'number',
|
|
220
|
+
},
|
|
221
|
+
table: {
|
|
222
|
+
type: {
|
|
223
|
+
summary: 'number',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
'minValues': {
|
|
228
|
+
description: 'Min number of selected values',
|
|
229
|
+
control: {
|
|
230
|
+
type: 'number',
|
|
231
|
+
},
|
|
232
|
+
table: {
|
|
233
|
+
type: {
|
|
234
|
+
summary: 'number',
|
|
235
|
+
},
|
|
221
236
|
},
|
|
222
237
|
},
|
|
223
238
|
'separator': {
|
|
@@ -161,6 +161,14 @@ export const KeepOpen: Story = {
|
|
|
161
161
|
},
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
export const autoselectFirst: Story = {
|
|
165
|
+
...Default,
|
|
166
|
+
args: {
|
|
167
|
+
...defaultArgs,
|
|
168
|
+
autoselectFirst: true,
|
|
169
|
+
},
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
export const Size: Story = {
|
|
165
173
|
...Default,
|
|
166
174
|
args: {
|