2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-04 06:32:23 +03:00

Merge remote-tracking branch 'upstream/disable-component'

This commit is contained in:
es
2017-03-22 10:36:18 +08:00
9 changed files with 235 additions and 100 deletions
+3
View File
@@ -20,3 +20,6 @@ ratings:
exclude_paths:
- dist/
- test/
- docs/
- config/
- build/
+5 -4
View File
@@ -1,4 +1,4 @@
# vue-select [![Build Status](https://travis-ci.org/sagalbot/vue-select.svg?branch=master)](https://travis-ci.org/sagalbot/vue-select) [![Code Coverage](https://img.shields.io/codeclimate/coverage/github/sagalbot/vue-select.svg?style=flat-square)](https://codeclimate.com/github/sagalbot/vue-select) [![No Dependencies](https://img.shields.io/gemnasium/sagalbot/vue-select.svg?style=flat-square)](https://gemnasium.com/github.com/sagalbot/vue-select) ![MIT License](https://img.shields.io/github/license/sagalbot/vue-select.svg?style=flat-square) ![Current Release](https://img.shields.io/github/release/sagalbot/vue-select.svg?style=flat-square)
# vue-select [![Build Status](https://travis-ci.org/sagalbot/vue-select.svg?branch=master)](https://travis-ci.org/sagalbot/vue-select) [![Code Score](https://img.shields.io/codeclimate/github/sagalbot/vue-select.svg?style=flat-square)](https://lima.codeclimate.com/github/sagalbot/vue-select) [![Code Coverage](https://img.shields.io/codeclimate/coverage/github/sagalbot/vue-select.svg?style=flat-square)](https://codeclimate.com/github/sagalbot/vue-select) [![No Dependencies](https://img.shields.io/gemnasium/sagalbot/vue-select.svg?style=flat-square)](https://gemnasium.com/github.com/sagalbot/vue-select) ![MIT License](https://img.shields.io/github/license/sagalbot/vue-select.svg?style=flat-square) ![Current Release](https://img.shields.io/github/release/sagalbot/vue-select.svg?style=flat-square)
> A native Vue.js select component that provides similar functionality to Select2 without the overhead of jQuery.
@@ -8,9 +8,10 @@
- List Filtering/Searching
- Supports Vuex
- Select Single/Multiple Options
- Bootstrap Friendly Markup
- Tested with Bootstrap 3/4, Bulma, Foundation
- +95% Test Coverage
- ~32kb minified
- ~33kb minified with CSS
- Zero dependencies
## Documentation
- **[Demo & Docs](http://sagalbot.github.io/vue-select/)**
@@ -34,7 +35,7 @@ Register the component
```js
import Vue from 'vue'
import vSelect from 'vue-select'
Vue.component(vSelect)
Vue.component('v-select', vSelect)
```
You may now use the component in your markup
+6 -2
View File
@@ -4,8 +4,10 @@
<head>
<meta charset="utf-8">
<title>Vue Select Dev</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css"> -->
<!--<link href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css" rel="stylesheet">-->
<!--<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.2/css/bulma.min.css" rel="stylesheet">-->
<!--<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">-->
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css">-->
<style>
html,
body,
@@ -32,6 +34,8 @@
<v-select placeholder="multiple" multiple :options="options"></v-select>
<v-select placeholder="multiple, taggable" multiple taggable :options="options" no-drop></v-select>
<v-select placeholder="multiple, taggable, push-tags" multiple push-tags taggable :options="[{label: 'Foo', value: 'foo'}]"></v-select>
<v-select placeholder="unsearchable" :options="options" :searchable="false"></v-select>
<v-select placeholder="loading" loading></v-select>
</div>
</body>
+2 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vue-select",
"version": "2.0.0",
"version": "2.1.0",
"description": "A native Vue.js component that provides similar functionality to Select2 without the overhead of jQuery.",
"author": "Jeff Sagal <sagalbot@gmail.com>",
"private": false,
+149 -71
View File
@@ -1,6 +1,7 @@
<style>
.v-select {
position: relative;
font-family: sans-serif;
}
.v-select .disabled {
@@ -8,6 +9,14 @@
background-color: rgb(248, 248, 248) !important;
}
.v-select,
.v-select * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* Open Indicator */
.v-select .open-indicator {
position: absolute;
bottom: 6px;
@@ -19,16 +28,12 @@
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
opacity: 1;
transition: opacity .1s;
height: 20px; width: 10px;
}
.v-select.loading .open-indicator {
opacity: 0;
}
.v-select .open-indicator:before {
border-color: rgba(60, 60, 60, .5);
border-style: solid;
border-width: 0.25em 0.25em 0 0;
border-width: 3px 3px 0 0;
content: '';
display: inline-block;
height: 10px;
@@ -37,44 +42,74 @@
transform: rotate(133deg);
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
box-sizing: inherit;
}
.v-select.open .open-indicator {
bottom: 1px;
}
/* Open Indicator States */
.v-select.open .open-indicator:before {
transform: rotate(315deg);
}
.v-select.loading .open-indicator {
opacity: 0;
}
.v-select.open .open-indicator {
bottom: 1px;
}
/* Dropdown Toggle */
.v-select .dropdown-toggle {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: block;
padding: 0;
background: none;
border: 1px solid rgba(60, 60, 60, .26);
border-radius: 4px;
white-space: normal;
transition: border-radius .25s;
}
.v-select .dropdown-toggle:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
/* Dropdown Toggle States */
.v-select.searchable .dropdown-toggle {
cursor: text;
}
.v-select.unsearchable .dropdown-toggle {
cursor: pointer;
}
.v-select.open .dropdown-toggle {
border-bottom: none;
border-bottom-color: transparent;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.v-select > .dropdown-menu {
/* Dropdown Menu */
.v-select .dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
min-width: 160px;
padding: 5px 0;
margin: 0;
width: 100%;
overflow-y: scroll;
border: 1px solid rgba(0, 0, 0, .26);
box-shadow: 0px 3px 6px 0px rgba(0,0,0,.15);
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-radius: 0 0 4px 4px;
text-align: left;
list-style: none;
background: #fff;
}
.v-select .no-options {
text-align: center;
}
/* Selected Tags */
.v-select .selected-tag {
color: #333;
background-color: #f0f0f0;
@@ -82,19 +117,44 @@
border-radius: 4px;
height: 26px;
margin: 4px 1px 0px 3px;
padding: 0 0.25em;
padding: 1px 0.25em;
float: left;
line-height: 1.7em;
line-height: 24px;
}
.v-select .selected-tag .close {
float: none;
margin-right: 0;
font-size: 20px;
appearance: none;
padding: 0;
cursor: pointer;
background: 0 0;
border: 0;
font-weight: 700;
line-height: 1;
color: #000;
text-shadow: 0 1px 0 #fff;
filter: alpha(opacity=20);
opacity: .2;
}
.v-select input[type=search],
.v-select input[type=search]:focus {
/* Search Input */
.v-select input[type="search"]::-webkit-search-decoration,
.v-select input[type="search"]::-webkit-search-cancel-button,
.v-select input[type="search"]::-webkit-search-results-button,
.v-select input[type="search"]::-webkit-search-results-decoration {
display: none;
}
.v-select input[type="search"]::-ms-clear {
display: none;
}
.v-select input[type="search"],
.v-select input[type="search"]:focus {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
line-height: 1.42857143;
font-size:1em;
height: 34px;
display: inline-block;
border: none;
outline: none;
@@ -108,27 +168,40 @@
float: left;
clear: none;
}
.v-select input[type=search]:disabled {
/* Search Input States */
.v-select.unsearchable input[type="search"] {
max-width: 1px;
}
/* List Items */
.v-select li {
line-height: 1.42857143; /* Normalize line height */
}
.v-select li > a {
display: block;
padding: 3px 20px;
clear: both;
color: #333; /* Overrides most CSS frameworks */
white-space: nowrap;
}
.v-select li:hover {
cursor: pointer;
}
.v-select li a {
cursor: pointer;
}
.v-select .active a {
background: rgba(50, 50, 50, .1);
.v-select .dropdown-menu .active > a {
color: #333;
background: rgba(50, 50, 50, .1);
}
.v-select .highlight a,
.v-select li:hover > a,
.v-select .active > a:hover {
.v-select .dropdown-menu > .highlight > a {
/*
* required to override bootstrap 3's
* .dropdown-menu > li > a:hover {} styles
*/
background: #5897fb;
color: #fff;
}
.v-select .highlight:not(:last-child) {
margin-bottom: 0; /* Fixes Bulma Margin */
}
/* Loading Spinner */
.v-select .spinner {
opacity: 0;
position: absolute;
@@ -145,18 +218,17 @@
animation: vSelectSpinner 1.1s infinite linear;
transition: opacity .1s;
}
.v-select.loading .spinner {
opacity: 1;
}
.v-select .spinner,
.v-select .spinner:after {
border-radius: 50%;
width: 5em;
height: 5em;
}
/* Loading Spinner States */
.v-select.loading .spinner {
opacity: 1;
}
/* KeyFrames */
@-webkit-keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
@@ -165,7 +237,6 @@
transform: rotate(360deg);
}
}
@keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
@@ -174,22 +245,30 @@
transform: rotate(360deg);
}
}
/* Dropdown Default Transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity .15s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
<template>
<div class="dropdown v-select" :class="dropdownClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix', {'disabled': disabled}]" type="button">
<span class="selected-tag" v-for="option in valueAsArray" v-bind:key="option.index">
{{ getOptionLabel(option) }}
<button v-if="multiple" @click="deselect(option)" type="button" class="close">
<span aria-hidden="true">&times;</span>
</button>
</span>
<span class="selected-tag" v-for="option in valueAsArray" v-bind:key="option.index">
{{ getOptionLabel(option) }}
<button v-if="multiple" @click="deselect(option)" type="button" class="close">
<span aria-hidden="true">&times;</span>
</button>
</span>
<input
ref="search"
:debounce="debounce"
v-model="search"
@keydown.delete="maybeDeleteValue"
@keyup.esc="onEscape"
@@ -208,29 +287,25 @@
<i v-if="!noDrop" ref="openIndicator" role="presentation" :class="[{'disabled': disabled}, 'open-indicator']"></i>
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
<div class="spinner" v-show="mutableLoading">Loading...</div>
</slot>
</div>
<ul ref="dropdownMenu" v-if="dropdownOpen" :transition="transition" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
<li v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
<a @mousedown.prevent="select(option)">
{{ getOptionLabel(option) }}
</a>
</li>
<transition name="fade">
<li v-if="!filteredOptions.length" class="divider"></li>
</transition>
<transition name="fade">
<li v-if="!filteredOptions.length" class="text-center">
<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
<li v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
<a @mousedown.prevent="select(option)">
{{ getOptionLabel(option) }}
</a>
</li>
<li v-if="!filteredOptions.length" class="no-options">
<slot name="no-options">Sorry, no matching options.</slot>
</li>
</transition>
</ul>
</ul>
</transition>
</div>
</template>
<script type="text/babel">
import pointerScroll from '../mixins/pointerScroll'
import typeAheadPointer from '../mixins/typeAheadPointer'
@@ -317,7 +392,7 @@
*/
transition: {
type: String,
default: 'expand'
default: 'fade'
},
/**
@@ -697,6 +772,7 @@
return {
open: this.dropdownOpen,
searchable: this.searchable,
unsearchable: !this.searchable,
loading: this.mutableLoading
}
},
@@ -731,8 +807,10 @@
*/
filteredOptions() {
let options = this.mutableOptions.filter((option) => {
if (typeof option === 'object') {
if (typeof option === 'object' && option.hasOwnProperty(this.label)) {
return option[this.label].toLowerCase().indexOf(this.search.toLowerCase()) > -1
} else if (typeof option === 'object' && !option.hasOwnProperty(this.label)) {
return console.warn(`[vue-select warn]: Label key "option.${this.label}" does not exist in options object.\nhttp://sagalbot.github.io/vue-select/#ex-labels`)
}
return option.toLowerCase().indexOf(this.search.toLowerCase()) > -1
})
+5 -14
View File
@@ -24,16 +24,6 @@ module.exports = {
onSearch: {
type: Function,
default: function(search, loading){}
},
/**
* The number of milliseconds to wait before
* invoking this.onSearch(). Used to prevent
* sending an AJAX request until input is complete.
*/
debounce: {
type: Number,
default: 0
}
},
@@ -43,9 +33,10 @@ module.exports = {
* invoke the onSearch callback.
*/
search() {
if (this.search.length > 0 && this.onSearch) {
if (this.search.length > 0) {
this.onSearch(this.search, this.toggleLoading)
}
this.$emit('search', this.search, this.toggleLoading)
}
},
},
@@ -59,9 +50,9 @@ module.exports = {
*/
toggleLoading(toggle = null) {
if (toggle == null) {
return this.showLoading = !this.showLoading
return this.mutableLoading = !this.mutableLoading
}
return this.showLoading = toggle
return this.mutableLoading = toggle
}
}
}
+63 -6
View File
@@ -317,7 +317,7 @@ describe('Select.vue', () => {
describe('Toggling Dropdown', () => {
it('should not open the dropdown when the el is clicked but the component is disabled', (done) => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" disabled></v-select></div>',
template: '<div><v-select :options="options" :value="value" disabled></v-select></div>',
components: {vSelect},
data: {
value: [{label: 'one'}],
@@ -325,7 +325,7 @@ describe('Select.vue', () => {
}
}).$mount()
vm.$children[0].toggleDropdown({target: vm.$children[0].$els.search})
vm.$children[0].toggleDropdown({target: vm.$children[0].$refs.search})
Vue.nextTick(() => {
Vue.nextTick(() => {
expect(vm.$children[0].open).toEqual(false)
@@ -698,6 +698,20 @@ describe('Select.vue', () => {
expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
})
it('will console.warn when options contain objects without a valid label key', (done) => {
spyOn(console, 'warn')
const vm = new Vue({
template: '<div><v-select :options="[{}]"></v-select></div>',
}).$mount()
Vue.nextTick(() => {
expect(console.warn).toHaveBeenCalledWith(
'[vue-select warn]: Label key "option.label" does not exist in options object.' +
'\nhttp://sagalbot.github.io/vue-select/#ex-labels'
)
done()
})
})
it('should display a placeholder if the value is empty', (done) => {
const vm = new Vue({
template: '<div><v-select :options="options" placeholder="foo"></v-select></div>',
@@ -936,10 +950,10 @@ describe('Select.vue', () => {
}).$mount()
vm.$refs.select.toggleLoading()
expect(vm.$refs.select.showLoading).toEqual(true)
expect(vm.$refs.select.mutableLoading).toEqual(true)
vm.$refs.select.toggleLoading(true)
expect(vm.$refs.select.showLoading).toEqual(true)
expect(vm.$refs.select.mutableLoading).toEqual(true)
})
it('should trigger the onSearch callback when the search text changes', (done) => {
@@ -985,6 +999,49 @@ describe('Select.vue', () => {
})
})
it('should trigger the search event when the search text changes', (done) => {
const vm = new Vue({
template: '<div><v-select ref="select" @search="foo"></v-select></div>',
data: {
called: false
},
methods: {
foo(val) {
this.called = val
}
}
}).$mount()
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
expect(vm.called).toEqual('foo')
done()
})
})
it('should not trigger the search event if the search text is empty', (done) => {
const vm = new Vue({
template: '<div><v-select ref="select" search="foo" @search="foo"></v-select></div>',
data: { called: false },
methods: {
foo(val) {
this.called = ! this.called
}
}
}).$mount()
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
expect(vm.called).toBe(true)
vm.$refs.select.search = ''
Vue.nextTick(() => {
expect(vm.called).toBe(true)
done()
})
})
})
it('can set loading to false from the onSearch callback', (done) => {
const vm = new Vue({
template: '<div><v-select loading ref="select" :on-search="foo"></v-select></div>',
@@ -997,7 +1054,7 @@ describe('Select.vue', () => {
vm.$refs.select.search = 'foo'
Vue.nextTick(() => {
expect(vm.$refs.select.showLoading).toEqual(false)
expect(vm.$refs.select.mutableLoading).toEqual(false)
done()
})
})
@@ -1016,7 +1073,7 @@ describe('Select.vue', () => {
select.onSearch(select.search, select.toggleLoading)
Vue.nextTick(() => {
expect(vm.$refs.select.showLoading).toEqual(true)
expect(vm.$refs.select.mutableLoading).toEqual(true)
done()
})
})