mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-07 07:12:23 +03:00
- search input is 100% width when value is empty
- value is no longer required, two-way binding is not enforced - add deprecated tag to maxHeight (this should just be changed with CSS) - add onChange prop for Vuex compatibility - fixed bug in isValueEmpty, added regression test - added docblocks
This commit is contained in:
+144
-11
@@ -104,7 +104,7 @@
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="dropdown" :class="cssClasses">
|
||||
<div class="dropdown" :class="dropdownClasses">
|
||||
<div v-el:toggle @mousedown.prevent="toggleDropdown" class="dropdown-toggle clearfix" type="button">
|
||||
<span class="form-control" v-if="!searchable && isValueEmpty">
|
||||
{{ placeholder }}
|
||||
@@ -121,16 +121,17 @@
|
||||
v-el:search
|
||||
v-show="searchable"
|
||||
v-model="search"
|
||||
@keydown.delete="maybeDeleteValue"
|
||||
@keydown.esc="onEscape"
|
||||
@keydown.up.prevent="typeAheadUp"
|
||||
@keydown.down.prevent="typeAheadDown"
|
||||
@keydown.enter.prevent="typeAheadSelect"
|
||||
@keyup.delete="maybeDeleteValue"
|
||||
@keyup.esc="onEscape"
|
||||
@keyup.up.prevent="typeAheadUp"
|
||||
@keyup.down.prevent="typeAheadDown"
|
||||
@keyup.enter.prevent="typeAheadSelect"
|
||||
@blur="open = false"
|
||||
@focus="open = true"
|
||||
type="search"
|
||||
class="form-control"
|
||||
:placeholder="searchPlaceholder"
|
||||
:style="{ width: isValueEmpty ? '100%' : 'auto' }"
|
||||
>
|
||||
|
||||
<i v-el:open-indicator role="presentation" class="open-indicator glyphicon-chevron-down glyphicon"></i>
|
||||
@@ -152,42 +153,104 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* Contains the currently selected value. Very similar to a
|
||||
* `value` attribute on an <input>. In most cases, you'll want
|
||||
* to set this as a two-way binding, using :value.sync. However,
|
||||
* this will not work with Vuex, in which case you'll need to use
|
||||
* the onChange callback property.
|
||||
* @type {Object||String||null}
|
||||
*/
|
||||
value: {
|
||||
twoway: true,
|
||||
required: true
|
||||
default: null
|
||||
},
|
||||
|
||||
/**
|
||||
* An array of strings or objects to be used as dropdown choices.
|
||||
* If you are using an array of objects, vue-select will look for
|
||||
* a `label` key (ex. [{label: 'This is Foo', value: 'foo'}]). A
|
||||
* custom label key can be set with the `label` prop.
|
||||
* @type {Object}
|
||||
*/
|
||||
options: {
|
||||
type: Array,
|
||||
default() { return [] },
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the max-height property on the dropdown list.
|
||||
* @deprecated
|
||||
* @type {String}
|
||||
*/
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: '400px'
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable/disable filtering the options.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
/**
|
||||
* Equivalent to the `multiple` attribute on a `<select>` input.
|
||||
* @type {Object}
|
||||
*/
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Equivalent to the `placeholder` attribute on an `<input>`.
|
||||
* @type {Object}
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a Vue transition property on the `.dropdown-menu`. vue-select
|
||||
* does not include CSS for transitions, you'll need to add them yourself.
|
||||
* @type {String}
|
||||
*/
|
||||
transition: {
|
||||
type: String,
|
||||
default: 'expand'
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables/disables clearing the search text when an option is selected.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
clearSearchOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells vue-select what key to use when generating option
|
||||
* labels when each `option` is an object.
|
||||
* @type {String}
|
||||
*/
|
||||
label: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* An optional callback function that is called each time the selected
|
||||
* value(s) change. When integrating with Vuex, use this callback to trigger
|
||||
* an action, rather than using :value.sync to retreive the selected value.
|
||||
* @type {Function}
|
||||
* @default {null}
|
||||
*/
|
||||
onChange: Function
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -199,6 +262,9 @@
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val, old) {
|
||||
this.onChange && val !== old ? this.onChange(val) : null
|
||||
},
|
||||
options() {
|
||||
this.$set('value', this.multiple ? [] : null)
|
||||
},
|
||||
@@ -207,10 +273,16 @@
|
||||
},
|
||||
filteredOptions() {
|
||||
this.typeAheadPointer = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Select a given option.
|
||||
* @param {Object||String} option
|
||||
* @return {void}
|
||||
*/
|
||||
select(option) {
|
||||
if (! this.isOptionSelected(option) ) {
|
||||
if (this.multiple) {
|
||||
@@ -230,6 +302,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
this.onAfterSelect(option)
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from this.select after each selection.
|
||||
* @param {Object||String} option
|
||||
* @return {void}
|
||||
*/
|
||||
onAfterSelect(option) {
|
||||
if (!this.multiple) {
|
||||
this.open = !this.open
|
||||
}
|
||||
@@ -237,6 +318,10 @@
|
||||
if( this.clearSearchOnSelect ) {
|
||||
this.search = ''
|
||||
}
|
||||
|
||||
// if( this.onChange ) {
|
||||
// this.onChange(this.$get('value'))
|
||||
// }
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -255,6 +340,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given option is currently selected.
|
||||
* @param {Object||String} option
|
||||
* @return {Boolean} True when selected || False otherwise
|
||||
*/
|
||||
isOptionSelected( option ) {
|
||||
if( this.multiple && this.value ) {
|
||||
return this.value.indexOf(option) !== -1
|
||||
@@ -263,6 +353,12 @@
|
||||
return this.value === option;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the selected option has option['value'] return it.
|
||||
* Otherwise, return the entire option.
|
||||
* @param {Object||String} option
|
||||
* @return {Object||String}
|
||||
*/
|
||||
getOptionValue( option ) {
|
||||
if( typeof option === 'object' && option.value ) {
|
||||
return option.value;
|
||||
@@ -309,6 +405,7 @@
|
||||
|
||||
/**
|
||||
* Select the option at the current typeAheadPointer position.
|
||||
* Optionally clear the search input on selection.
|
||||
* @return {void}
|
||||
*/
|
||||
typeAheadSelect() {
|
||||
@@ -321,6 +418,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If there is any text in the search input, remove it.
|
||||
* Otherwise, blur the search input to close the dropdown.
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
onEscape() {
|
||||
if( ! this.search.length ) {
|
||||
this.$els.search.blur()
|
||||
@@ -329,6 +431,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the value on Delete keypress when there is no
|
||||
* text in the search input, & there's tags to delete
|
||||
* @return {this.value}
|
||||
*/
|
||||
maybeDeleteValue() {
|
||||
if( ! this.$els.search.value.length && this.value ) {
|
||||
return this.multiple ? this.value.pop() : this.$set('value', null)
|
||||
@@ -337,31 +444,57 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
cssClasses() {
|
||||
|
||||
/**
|
||||
* Classes to be output on .dropdown
|
||||
* @return {Object}
|
||||
*/
|
||||
dropdownClasses() {
|
||||
return {
|
||||
open: this.open,
|
||||
searchable: this.searchable
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the placeholder string if it's set
|
||||
* & there is no value selected.
|
||||
* @return {String} Placeholder text
|
||||
*/
|
||||
searchPlaceholder() {
|
||||
if( this.isValueEmpty && this.placeholder ) {
|
||||
return this.placeholder;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The currently available options, filtered
|
||||
* by the search elements value.
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
filteredOptions() {
|
||||
return this.$options.filters.filterBy(this.options, this.search)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if there aren't any options selected.
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isValueEmpty() {
|
||||
if( this.value ) {
|
||||
if( typeof this.value === 'object' ) {
|
||||
return ! Object.keys(this.value).length
|
||||
}
|
||||
return ! this.value.length
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the current value in array format.
|
||||
* @return {Array}
|
||||
*/
|
||||
valueAsArray() {
|
||||
if( this.multiple ) {
|
||||
return this.value
|
||||
|
||||
@@ -103,16 +103,24 @@ describe('Select.vue', () => {
|
||||
options: ['one','two','three']
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
var select = vm.$children[0]
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
|
||||
select.$set('value', ['one'])
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
select.$set('value', 'one')
|
||||
select.$set('multiple', false)
|
||||
|
||||
select.$set('value', [{l:'f'}])
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', 'one')
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', {label: 'foo', value: 'foo'})
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', '')
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
|
||||
select.$set('value', null)
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
})
|
||||
@@ -169,6 +177,33 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
expect(vm.$children[0].$els.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
|
||||
})
|
||||
|
||||
it('can run a callback when the selection changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :on-change="foo" value="bar" :options="options"></v-select></div>',
|
||||
components: { vSelect },
|
||||
data: {
|
||||
val: null,
|
||||
options: ['foo','bar','baz']
|
||||
},
|
||||
methods: {
|
||||
foo(value) {
|
||||
this.val = value
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
vm.$children[0].select('foo')
|
||||
Vue.nextTick(function() {
|
||||
expect(vm.$get('val')).toEqual('foo')
|
||||
|
||||
vm.$children[0].$set('value', 'baz')
|
||||
Vue.nextTick(function() {
|
||||
expect(vm.$get('val')).toEqual('baz')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user