@volverjs/ui-vue 0.0.10-beta.40 → 0.0.10-beta.42
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 +185 -124
- 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 +185 -121
- 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 +107 -96
- 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 || Array.isArray(props.modelValue) ? [...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,40 @@ 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
|
-
|
|
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]
|
|
294
|
+
else if (!isSelected && isSelectable.value) {
|
|
295
|
+
if (!props.multiple) {
|
|
296
|
+
localModelValue.value.clear()
|
|
287
297
|
}
|
|
298
|
+
localModelValue.value.add(getOptionValue(option))
|
|
288
299
|
}
|
|
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
|
-
}
|
|
300
|
+
// force reactivity
|
|
301
|
+
localModelValue.value = new Set(localModelValue.value)
|
|
302
|
+
if (!props.multiple && !props.keepOpen) {
|
|
303
|
+
collapse()
|
|
304
304
|
}
|
|
305
|
-
emit('update:modelValue', toReturn)
|
|
306
305
|
}
|
|
307
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Auto select the first option if autoOpen is enabled
|
|
309
|
+
*/
|
|
310
|
+
watch(
|
|
311
|
+
() => props.options,
|
|
312
|
+
(newValue) => {
|
|
313
|
+
if (newValue?.length && props.autoselectFirst && !isDirty.value) {
|
|
314
|
+
onInput(newValue[0])
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{ immediate: true },
|
|
318
|
+
)
|
|
319
|
+
|
|
308
320
|
const selectProps = computed(() => ({
|
|
309
321
|
id: hasId.value,
|
|
310
322
|
name: props.name,
|
|
@@ -325,7 +337,8 @@ const selectProps = computed(() => ({
|
|
|
325
337
|
icon: propsDefaults.value.icon,
|
|
326
338
|
iconPosition: propsDefaults.value.iconPosition,
|
|
327
339
|
floating: propsDefaults.value.floating,
|
|
328
|
-
unselectable:
|
|
340
|
+
unselectable: isUnselectable.value,
|
|
341
|
+
autoselectFirst: propsDefaults.value.autoselectFirst,
|
|
329
342
|
multiple: propsDefaults.value.multiple,
|
|
330
343
|
label: propsDefaults.value.label,
|
|
331
344
|
placeholder: propsDefaults.value.placeholder,
|
|
@@ -357,7 +370,7 @@ const slotProps = computed(() => ({
|
|
|
357
370
|
modelValue: props.modelValue,
|
|
358
371
|
}))
|
|
359
372
|
|
|
360
|
-
//
|
|
373
|
+
// keyboard
|
|
361
374
|
onKeyStroke(
|
|
362
375
|
[' ', 'Enter'],
|
|
363
376
|
(e) => {
|
|
@@ -478,9 +491,7 @@ export default {
|
|
|
478
491
|
{{ getOptionLabel(option) }}
|
|
479
492
|
<button
|
|
480
493
|
v-if="
|
|
481
|
-
|
|
482
|
-
&& !readonly
|
|
483
|
-
&& !disabled
|
|
494
|
+
isUnselectable
|
|
484
495
|
"
|
|
485
496
|
:aria-label="
|
|
486
497
|
propsDefaults.deselectActionLabel
|
|
@@ -524,8 +535,8 @@ export default {
|
|
|
524
535
|
)"
|
|
525
536
|
v-bind="{
|
|
526
537
|
selected: isOptionSelected(item),
|
|
527
|
-
disabled:
|
|
528
|
-
unselectable,
|
|
538
|
+
disabled: isOptionDisabledOrNotSelectable(item),
|
|
539
|
+
unselectable: isUnselectable,
|
|
529
540
|
deselectHintLabel:
|
|
530
541
|
propsDefaults.deselectHintLabel,
|
|
531
542
|
selectHintLabel:
|
|
@@ -545,7 +556,7 @@ export default {
|
|
|
545
556
|
option,
|
|
546
557
|
selectedOptions,
|
|
547
558
|
selected: isOptionSelected(item),
|
|
548
|
-
disabled:
|
|
559
|
+
disabled: isOptionDisabledOrNotSelectable(item),
|
|
549
560
|
}"
|
|
550
561
|
>
|
|
551
562
|
{{ getOptionLabel(item) }}
|
|
@@ -556,8 +567,8 @@ export default {
|
|
|
556
567
|
v-else
|
|
557
568
|
v-bind="{
|
|
558
569
|
selected: isOptionSelected(option),
|
|
559
|
-
disabled:
|
|
560
|
-
unselectable,
|
|
570
|
+
disabled: isOptionDisabledOrNotSelectable(option),
|
|
571
|
+
unselectable: isUnselectable,
|
|
561
572
|
deselectHintLabel:
|
|
562
573
|
propsDefaults.deselectHintLabel,
|
|
563
574
|
selectHintLabel:
|
|
@@ -576,7 +587,7 @@ export default {
|
|
|
576
587
|
option,
|
|
577
588
|
selectedOptions,
|
|
578
589
|
selected: isOptionSelected(option),
|
|
579
|
-
disabled:
|
|
590
|
+
disabled: isOptionDisabledOrNotSelectable(option),
|
|
580
591
|
}"
|
|
581
592
|
>
|
|
582
593
|
{{ 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: {
|