mirror of
https://github.com/tenrok/vue-select.git
synced 2026-05-23 03:54:04 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
+2
-1
@@ -3,4 +3,5 @@ node_modules
|
||||
npm-debug.log
|
||||
.idea
|
||||
test/unit/coverage
|
||||
.coveralls.yml
|
||||
.coveralls.yml
|
||||
.flowconfig
|
||||
|
||||
+1
-5
@@ -2,9 +2,5 @@ language: node_js
|
||||
node_js:
|
||||
- "5"
|
||||
- "5.1"
|
||||
- "4"
|
||||
- "4.2"
|
||||
- "4.1"
|
||||
- "4.0"
|
||||
after_success:
|
||||
- codeclimate-test-reporter < ./test/unit/coverage/lcov.info
|
||||
- codeclimate-test-reporter < ./test/unit/coverage/lcov.info
|
||||
|
||||
@@ -1,229 +1,103 @@
|
||||
# vue-select [](https://travis-ci.org/sagalbot/vue-select) [](https://codeclimate.com/github/sagalbot/vue-select) [](https://gemnasium.com/github.com/sagalbot/vue-select)  
|
||||
|
||||
> A native Vue.js component that provides similar functionality to Select2 without the overhead of jQuery.
|
||||
|
||||
Rather than bringing in jQuery just to use Select2 or Chosen, this Vue.js component provides similar functionality without the extra overhead of jQuery, while providing the same awesome data-binding features you expect from Vue. Vue-select has no JavaScript dependencies other than Vue, and is designed to mimic [Select2](https://github.com/select2/select2).
|
||||
|
||||
> A native Vue.js select component that provides similar functionality to Select2 without the overhead of jQuery.
|
||||
|
||||
#### Features
|
||||
|
||||
- **AJAX Support +v1.2.0**
|
||||
- Tagging Support **+v.1.1.0**
|
||||
- No JS Dependencies
|
||||
- AJAX Support
|
||||
- Tagging
|
||||
- List Filtering/Searching
|
||||
- Supports Vuex
|
||||
- Select Single/Multiple Options
|
||||
- Bootstrap Friendly Markup
|
||||
- +95% Test Coverage
|
||||
- ~32kb minified
|
||||
|
||||
#### Upcoming/In Progress
|
||||
## Documentation
|
||||
- **[Demo & Docs](http://sagalbot.github.io/vue-select/)**
|
||||
- **[Example on JSBin](http://jsbin.com/saxaru/8/edit?html,js,output)**
|
||||
|
||||
- ~~Tagging (adding options not present in list, see `taggable` branch)~~ **+v.1.1.0**
|
||||
- ~~Asyncronous Option Loading~~ **+v.1.2.0**
|
||||
- Rich Option Templating
|
||||
## Install
|
||||
|
||||
## Live Examples & Docs
|
||||
- [Demo & Docs](http://sagalbot.github.io/vue-select/)
|
||||
- [Live Example on JSBin](http://jsbin.com/saxaru/5/edit?html,js,output)
|
||||
###### Vue Compatibility
|
||||
- `vue ~2.0` use `vue-select ~2.0`
|
||||
- `vue ~1.0` use `vue-select ~1.0`
|
||||
|
||||
## Install / Usage
|
||||
#### NPM
|
||||
Install the package. _You should install `vue-select@1.3.3` for use with vue `~1.0`._
|
||||
|
||||
#### NPM Based WorkFlows
|
||||
``` bash
|
||||
```bash
|
||||
$ npm install vue-select
|
||||
```
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="myApp">
|
||||
<v-select :value.sync="selected" :options="options"></v-select>
|
||||
</div>
|
||||
</template>
|
||||
Register the component
|
||||
|
||||
<script>
|
||||
```js
|
||||
import Vue from 'vue'
|
||||
import vSelect from 'vue-select'
|
||||
export default {
|
||||
components: {vSelect},
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
options: ['foo','bar','baz']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Vue.component(vSelect)
|
||||
```
|
||||
|
||||
#### Browser Globals
|
||||
|
||||
`v1.3.0+` no longer requires any toolchain to use the component:
|
||||
|
||||
Just include `vue` & `vue-select.js` - I recommend using [npmcdn](https://npmcdn.com/#/).
|
||||
You may now use the component in your markup
|
||||
|
||||
```html
|
||||
<!-- use the latest release -->
|
||||
<script src="https://npmcdn.com/vue-select@latest"></script>
|
||||
<!-- or point to a specific release -->
|
||||
<script src="https://npmcdn.com/vue-select@1.30"></script>
|
||||
<v-select v-model="selected" :options="['foo','bar']"></v-select>
|
||||
```
|
||||
|
||||
#### CDN
|
||||
|
||||
Just include `vue` & `vue-select.js` - I recommend using [unpkg](https://unpkg.com/#/).
|
||||
|
||||
```html
|
||||
<script scr="https://unpkg.com/vue@latest"></script>
|
||||
<!-- use the latest release -->
|
||||
<script src="https://unpkg.com/vue-select@latest"></script>
|
||||
<!-- or point to a specific release -->
|
||||
<script src="https://unpkg.com/vue-select@1.3.3"></script>
|
||||
```
|
||||
|
||||
Then register the component in your javascript:
|
||||
|
||||
```js
|
||||
Vue.component('v-select', VueSelect.VueSelect);
|
||||
```
|
||||
|
||||
From there you can use as normal. Here's an [example on JSBin](http://jsbin.com/saxaru/5/edit?html,js,output).
|
||||
You may now use the component in your markup
|
||||
|
||||
## Parameters
|
||||
```javascript
|
||||
/**
|
||||
* 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: {
|
||||
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,
|
||||
|
||||
/**
|
||||
* Enable/disable creating options from searchInput.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
taggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
/**
|
||||
* When true, newly created tags will be added to
|
||||
* the options list.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
pushTags: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
/**
|
||||
* User defined function for adding Options
|
||||
* @type {Function}
|
||||
*/
|
||||
createOption: {
|
||||
type: Function,
|
||||
default: function (newOption) {
|
||||
if (typeof this.options[0] === 'object') {
|
||||
return {[this.label]: newOption}
|
||||
}
|
||||
return newOption
|
||||
}
|
||||
}
|
||||
```html
|
||||
<v-select v-model="selected" :options="['foo','bar']"></v-select>
|
||||
```
|
||||
|
||||
Here's an [example on JSBin](http://jsbin.com/saxaru/5/edit?html,js,output).
|
||||
|
||||
## Build Setup for Contributing
|
||||
## Basic Usage
|
||||
|
||||
If there's a feature you'd like to see or you find a bug, feel free to fork and submit a PR. If your adding functionality, add tests to go with it.
|
||||
#### Syncing a Selected Value
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
The most common use case for `vue-select` is to have the chosen value synced with a parent component. `vue-select` takes advantage of the `v-model` syntax to sync values with a parent.
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
```html
|
||||
<v-select v-model="selected"></v-select>
|
||||
```
|
||||
```js
|
||||
new Vue({
|
||||
data: {
|
||||
selected: null
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# run unit tests
|
||||
npm test
|
||||
#### Setting Options
|
||||
|
||||
# run unit tests on save
|
||||
npm run test-watch
|
||||
```
|
||||
`vue-select` accepts arrays of strings and objects to use as options through the `options` prop.
|
||||
|
||||
```html
|
||||
<v-select :options="['foo','bar']"></v-select>
|
||||
```
|
||||
|
||||
When provided an array of objects, `vue-select` will display a single value of the object. By default, `vue-select` will look for a key named 'label' on the object to use as display text.
|
||||
|
||||
```html
|
||||
<v-select :options="[{label: 'foo', value: 'Foo'}]"></v-select>
|
||||
```
|
||||
|
||||
### For more information, please visit the [vue-select documentation.](https://sagalbot.github.io/vue-select)
|
||||
|
||||
@@ -5,7 +5,7 @@ var projectRoot = path.resolve(__dirname, '../')
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/dev.js'
|
||||
app: process.argv.indexOf('--docs') > 0 ? './docs/docs.js' : './src/dev.js',
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
@@ -19,7 +19,9 @@ module.exports = {
|
||||
'src': path.resolve(__dirname, '../src'),
|
||||
'assets': path.resolve(__dirname, '../docs/assets'),
|
||||
'mixins': path.resolve(__dirname, '../src/mixins'),
|
||||
'components': path.resolve(__dirname, '../docs/components')
|
||||
'components': path.resolve(__dirname, '../src/components'),
|
||||
'docs': path.resolve(__dirname, '../docs'),
|
||||
'vue$': 'vue/dist/vue.common.js',
|
||||
}
|
||||
},
|
||||
resolveLoader: {
|
||||
@@ -29,21 +31,21 @@ module.exports = {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue'
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel',
|
||||
loader: 'babel-loader',
|
||||
include: projectRoot,
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
loader: 'json-loader'
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'vue-html'
|
||||
loader: 'vue-html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)(\?.*)?$/,
|
||||
|
||||
@@ -12,7 +12,7 @@ Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
loaders: utils.styleLoaders()
|
||||
loaders: utils.styleLoaders().concat({ test: /\.md$/, loader: "html!markdown" })
|
||||
},
|
||||
// eval-source-map is faster for development
|
||||
devtool: '#eval-source-map',
|
||||
@@ -27,8 +27,13 @@ module.exports = merge(baseWebpackConfig, {
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
template: process.argv.indexOf('--docs') > 0 ? './docs/docs.html' : 'dev.html',
|
||||
inject: true
|
||||
})
|
||||
]
|
||||
],
|
||||
markdownLoader: {
|
||||
highlight: function (code) {
|
||||
return require('highlight.js').highlightAuto(code).value;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<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"> -->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.v-select {
|
||||
width: 25em;
|
||||
margin: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<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>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+30
-8
@@ -6,10 +6,22 @@
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<examples></examples>
|
||||
<params></params>
|
||||
<div id="docs" class="container-fluid">
|
||||
<div class="col-md-2 col-md-offset-1">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="#">Install & Usage</a></li>
|
||||
<li><a href="#">Examples</a></li>
|
||||
<li><a href="#">Ajax</a></li>
|
||||
<li><a href="#">Parameters</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<article v-html="install"></article>
|
||||
<article v-html="vModel"></article>
|
||||
<article v-html="single"></article>
|
||||
<article v-html="reactive"></article>
|
||||
<article v-html="labels"></article>
|
||||
<article v-html="ajax"></article>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,11 +34,21 @@
|
||||
* for the demo site at http://sagalbot.github.io/vue-select/.
|
||||
*/
|
||||
|
||||
import Examples from './components/Examples.vue'
|
||||
import Params from './components/Params.vue'
|
||||
import Ajax from './components/snippets/Ajax.vue'
|
||||
// import Examples from './components/Examples.vue'
|
||||
// import Params from './components/Params.vue'
|
||||
// import Ajax from './components/snippets/Ajax.vue'
|
||||
|
||||
export default {
|
||||
components: { Params, Examples, Ajax }
|
||||
// components: { Params, Examples, Ajax }
|
||||
data () {
|
||||
return {
|
||||
install: require('./md/Install.md'),
|
||||
vModel: require('./md/VModel.md'),
|
||||
single: require('./md/SingleMultiple.md'),
|
||||
reactive: require('./md/ReactiveOptions.md'),
|
||||
labels: require('./md/CustomLabels.md'),
|
||||
ajax: require('./md/Ajax.md'),
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,8 +25,10 @@ pre[class*="language-"] {
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
background: #f5f7ff;
|
||||
background: $code-white;
|
||||
color: #5e6687;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
@@ -150,4 +152,4 @@ pre[class*="language-"] {
|
||||
pre > code.highlight {
|
||||
outline: 0.4em solid #c94922;
|
||||
outline-offset: .4em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
$orange: #F16745;
|
||||
$orange: #e96900;
|
||||
$yellow: #FFC65D;
|
||||
$green: #7BC8A4;
|
||||
$green: #42b983;
|
||||
$blue: #4CC3D9;
|
||||
$purple: #93648D;
|
||||
$black: #404040;
|
||||
$black: #34495e;
|
||||
$red: #ff6666;
|
||||
|
||||
// Code
|
||||
$code-blue: #66d9ef;
|
||||
$code-purple: #ae81ff;
|
||||
$code-black: #272822;
|
||||
$code-white: #f8f8f2;
|
||||
$code-white: #f8f8f8;
|
||||
$code-grey: #708090;
|
||||
$code-green: #a6e22e;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<code :class="class"><slot></slot></code>
|
||||
<code :class="cssClass"><slot></slot></code>
|
||||
</template>
|
||||
|
||||
<script type="text/babel">
|
||||
@@ -12,9 +12,9 @@
|
||||
export default {
|
||||
props: ['lang'],
|
||||
computed: {
|
||||
class () {
|
||||
cssClass () {
|
||||
return `language-${this.lang}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<p>The resulting vue-select, and it's value: <v-code lang="json">{{ install | json }}</v-code></p>
|
||||
<v-select :value.sync="install" :options="['foo','bar','baz']"></v-select>
|
||||
<p>The resulting vue-select, and it's value: <v-code lang="json">{{ install }}</v-code></p>
|
||||
<v-select v-model="install" :options="['foo','bar','baz']"></v-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<p>The <code>.sync</code> data-binding modifier is completely optional. You may use <code>value</code> without a two-way binding to preselect options.</p>
|
||||
<p>Here we have preselected 'Canada' by setting <code>syncedVal: 'Canada'</code> on the parent component. The buttons below demonstrate how you can set the <code>value</code> from the parent.</p>
|
||||
|
||||
<p>Current value: <v-code>{{ syncedVal | json }}</v-code></p>
|
||||
<p>Current value: <v-code>{{ syncedVal }}</v-code></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
@@ -73,7 +73,7 @@
|
||||
<pre><v-code lang="markup"><v-select :value.sync="syncedVal" :options="countries"></v-select></v-code></pre>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<v-select :options="simple" :value.sync="syncedVal"></v-select>
|
||||
<v-select :options="simple" v-model="syncedVal"></v-select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@@ -100,10 +100,10 @@
|
||||
</article>
|
||||
|
||||
<article class="doc-row" id="ex-vuex">
|
||||
<h3 class="page-header">On-Change Callback <small>Vuex Compatibility</small></h3>
|
||||
<h3 class="page-header">Change Event <small>Vuex Compatibility</small></h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>vue-select provides an <code>onChange</code> property that accepts a callback function. This function is passed the currently selected value(s) as it's only parameter.</p>
|
||||
<p>vue-select provides an <code>change</code> event. This function is passed the currently selected value(s) as it's only parameter.</p>
|
||||
<p>This is very useful when integrating with Vuex, as it will allow your to trigger an action to update your vuex state object. Choose a callback and see it in action.</p>
|
||||
|
||||
<div class="form-inline">
|
||||
@@ -122,7 +122,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<pre><v-code lang="markup"><v-select :on-change="consoleCallback" :options="countries"></v-select></v-code></pre>
|
||||
<pre><v-code lang="markup"><v-select v-on:change="consoleCallback" :options="countries"></v-select></v-code></pre>
|
||||
<pre><v-code lang="javascript">methods: {
|
||||
consoleCallback(val) {
|
||||
console.dir(JSON.stringify(val))
|
||||
@@ -137,7 +137,7 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<ajax></ajax>
|
||||
<!-- <ajax></ajax> -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
|
||||
export default {
|
||||
components: {vSelect,vCode,InstallSnippet,Ajax},
|
||||
// components: {vSelect,vCode,InstallSnippet},
|
||||
data () {
|
||||
return {
|
||||
countries,
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<div>
|
||||
<div class="github-search panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<v-select :debounce="250" :value.sync="repo" :options="options" :on-search="getOptions" placeholder="Search GitHub Repositories..." label="full_name"></v-select>
|
||||
<v-select :debounce="250" v-model="repo" :options="options" :on-search="getOptions" placeholder="Search GitHub Repositories..." label="full_name"></v-select>
|
||||
</div>
|
||||
<div class="panel-body" v-if="repo">
|
||||
<img :src="repo.owner.avatar_url" alt="{{ repo.owner.login }}" class="gravatar">
|
||||
@@ -119,4 +119,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
+17
-15
@@ -1,16 +1,15 @@
|
||||
<template>
|
||||
<h2 class="page-header">Parameters</h2>
|
||||
<pre v-pre><code class="language-javascript">props: {
|
||||
<div>
|
||||
<h2 class="page-header">Parameters</h2>
|
||||
<pre v-pre><code class="language-javascript">props: {
|
||||
|
||||
/**
|
||||
/**
|
||||
* Contains the currently selected value. Very similar to a
|
||||
* `value` attribute on an &lt;input&gt;. 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.
|
||||
* `value` attribute on an <input>. You can listen for changes
|
||||
* using 'change' event using v-on
|
||||
* @type {Object||String||null}
|
||||
*/
|
||||
value: {
|
||||
value: {
|
||||
default: null
|
||||
},
|
||||
|
||||
@@ -90,16 +89,19 @@
|
||||
* @default {null}
|
||||
*/
|
||||
onChange: Function
|
||||
}
|
||||
|
||||
}
|
||||
</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Note that this file (and anything other than src/components/Select.vue)
|
||||
* has nothing to do with how you use vue-select. These files are used
|
||||
* for the demo site at http://sagalbot.github.io/vue-select/. They'll
|
||||
* be moved out of this repo in the very near future to avoid confusion.
|
||||
*/
|
||||
/**
|
||||
* Note that this file (and anything other than src/components/Select.vue)
|
||||
* has nothing to do with how you use vue-select. These files are used
|
||||
* for the demo site at http://sagalbot.github.io/vue-select/. They'll
|
||||
* be moved out of this repo in the very near future to avoid confusion.
|
||||
*/
|
||||
export default {}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -113,13 +113,14 @@
|
||||
</style>
|
||||
|
||||
<script type="text/babel">
|
||||
import GitHubSearchBasic from 'components/GitHubSearchBasic.vue'
|
||||
import GitHubSearch from 'components/GitHubSearch.vue'
|
||||
import AjaxProps from './AjaxProps.vue'
|
||||
import AjaxExample from './AjaxExample.vue'
|
||||
|
||||
// import GitHubSearchBasic from 'docs/components/GitHubSearchBasic.vue'
|
||||
// import GitHubSearch from 'docs/components/GitHubSearch.vue'
|
||||
// import AjaxProps from './AjaxProps.vue'
|
||||
// import AjaxExample from './AjaxExample.vue'
|
||||
|
||||
export default {
|
||||
components: {GitHubSearchBasic, GitHubSearch, AjaxProps, AjaxExample},
|
||||
// components: {GitHubSearchBasic, GitHubSearch, AjaxProps, AjaxExample},
|
||||
data() {
|
||||
return {
|
||||
basicSource: false,
|
||||
@@ -127,4 +128,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<p>Install from GitHub via NPM</p>
|
||||
<pre><v-code lang="bash">npm install sagalbot/vue-select</v-code></pre>
|
||||
<div>
|
||||
<p>Install from GitHub via NPM</p>
|
||||
<pre><v-code lang="bash">npm install sagalbot/vue-select</v-code></pre>
|
||||
|
||||
<p>To use the vue-select component in your templates, simply import it, and register it with your component.</p>
|
||||
<pre><v-code lang="markup"><template>
|
||||
<p>To use the vue-select component in your templates, simply import it, and register it with your component.</p>
|
||||
<pre><v-code lang="markup"><template>
|
||||
<div id="myApp">
|
||||
<v-select :value.sync="selected" :options="options"></v-select>
|
||||
</div>
|
||||
@@ -22,16 +23,19 @@
|
||||
}
|
||||
</script></v-code>
|
||||
</pre>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
/**
|
||||
* Note that this file (and anything other than src/components/Select.vue)
|
||||
* has nothing to do with how you use vue-select. These files are used
|
||||
* for the demo site at http://sagalbot.github.io/vue-select/. They'll
|
||||
* be moved out of this repo in the very near future to avoid confusion.
|
||||
*/
|
||||
/**
|
||||
* Note that this file (and anything other than src/components/Select.vue)
|
||||
* has nothing to do with how you use vue-select. These files are used
|
||||
* for the demo site at http://sagalbot.github.io/vue-select/. They'll
|
||||
* be moved out of this repo in the very near future to avoid confusion.
|
||||
*/
|
||||
import vCode from '../Code.vue'
|
||||
export default {
|
||||
components: {vCode}
|
||||
components: {
|
||||
vCode
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<meta property="og:url" content="http://sagalbot.github.io/vue-select/">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="jumbotron jumbotron-top">
|
||||
<div class="container">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
@@ -43,12 +44,9 @@
|
||||
|
||||
<v-select
|
||||
id="v-select"
|
||||
taggable
|
||||
:placeholder="placeholder"
|
||||
:value="selected"
|
||||
:options="options"
|
||||
:multiple="multiple"
|
||||
:on-change="setSelected"
|
||||
multiple
|
||||
>
|
||||
</v-select>
|
||||
|
||||
@@ -79,7 +77,8 @@
|
||||
<i role="presentation" class="glyphicon glyphicon-chevron-down"></i>
|
||||
</a>
|
||||
</div>
|
||||
<app></app>
|
||||
<docs></docs>
|
||||
</div>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'prismjs'
|
||||
import Vue from 'vue'
|
||||
import App from './Docs.vue'
|
||||
import Docs from './Docs.vue'
|
||||
import store from './vuex/store'
|
||||
import Resource from 'vue-resource'
|
||||
import vSelect from '../src/components/Select.vue'
|
||||
import vCode from './components/Code.vue'
|
||||
import countries from './data/advanced'
|
||||
|
||||
Vue.use(Resource)
|
||||
|
||||
@@ -23,27 +24,13 @@ import { setSelected, toggleMultiple, setPlaceholder, toggleOptionType } from '.
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: 'body',
|
||||
el: '#app',
|
||||
store,
|
||||
components: { App },
|
||||
vuex: {
|
||||
getters: {
|
||||
placeholder (store) {
|
||||
return store.placeholder
|
||||
},
|
||||
selected (store) {
|
||||
return store.selected
|
||||
},
|
||||
type (store) {
|
||||
return store.optionType
|
||||
},
|
||||
options (store) {
|
||||
return store.options[store.optionType]
|
||||
},
|
||||
multiple (store) {
|
||||
return store.multiple
|
||||
}
|
||||
},
|
||||
actions: { setSelected, toggleMultiple, setPlaceholder, toggleOptionType }
|
||||
components: { Docs },
|
||||
data () {
|
||||
return {
|
||||
options: countries,
|
||||
placeholder: 'Choose a country..',
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
## AJAX Remote Option Loading
|
||||
|
||||
|
||||
The `onSearch` prop allows you to load options via ajax in a parent component when the search text is updated. It is invoked with two parameters, `search` & `loading`.
|
||||
|
||||
#### onSearch Callback Parameters <small>search, loading</small>
|
||||
|
||||
`search` is a string containing the current search text. `loading` is a function that accepts a boolean value, and is used to toggle the 'loading' class on the top-level vue-select wrapper.
|
||||
|
||||
#### Loading Spinner
|
||||
|
||||
Vue Select includes a default loading spinner that appears when the loading class is present. The `spinner` slot allows you to implement your own spinner.
|
||||
|
||||
<div id="spinner-example" :class="{loading:spinner}"><button class="btn btn-sm btn-default" @click="spinner = !spinner">Toggle Spinner</button>
|
||||
|
||||
<div class="spinner" v-show="spinner">Loading...</div>
|
||||
|
||||
#### Debounce Input
|
||||
|
||||
Vue Select also accepts a `debounce` prop that can be used to prevent `onSearch` from being called until input has completed.
|
||||
|
||||
#### Library Agnostic
|
||||
|
||||
Since Vue.js does not ship with ajax functionality as part of the core library, it's up to you to process the ajax requests in your parent component.
|
||||
|
||||
|
||||
#### Example <small>GitHub API</small>
|
||||
|
||||
In this example, [Vue Resource](https://github.com/vuejs/vue-resource) is used to access the [GitHub API](https://developer.github.com/v3/).
|
||||
|
||||
<git-hub-search-basic></git-hub-search-basic><ajax-example></ajax-example></div>
|
||||
@@ -0,0 +1,28 @@
|
||||
```html
|
||||
<v-select
|
||||
:debounce="250"
|
||||
:on-search="getOptions"
|
||||
:options="options"
|
||||
placeholder="Search GitHub Repositories..."
|
||||
label="full_name"
|
||||
>
|
||||
</v-select>
|
||||
```
|
||||
```js
|
||||
data() {
|
||||
return {
|
||||
options: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOptions(search, loading) {
|
||||
loading(true)
|
||||
this.$http.get('https://api.github.com/search/repositories', {
|
||||
q: search
|
||||
}).then(resp => {
|
||||
this.options = resp.data.items
|
||||
loading(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
```js
|
||||
/**
|
||||
* Accept a callback function that will be run
|
||||
* when the search text changes. The callback
|
||||
* will be invoked with these parameters:
|
||||
|
||||
* @param {search} String Current search text
|
||||
* @param {loading} Function(bool) Toggle loading class
|
||||
*/
|
||||
onSearch: {
|
||||
type: Function,
|
||||
default: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Milliseconds to wait before invoking this.onSearch().
|
||||
* Used to prevent sending an AJAX request until input
|
||||
* has completed.
|
||||
*/
|
||||
debounce: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
### Custom Labels
|
||||
|
||||
By default when the `options` array contains objects, `vue-select` looks for the `label` key for display. If your data source doesn't contain that key, you can set your own using the `label` prop.
|
||||
|
||||
On this page, the list of countries used in the examples contains `value` and `label` properties: `{value: "CA", label: "Canada"}`. In this example, we'll display the country code instead of the label.
|
||||
|
||||
`<v-select label="value" :options="countries"></v-select>`
|
||||
|
||||
<v-select label="value" :options="countries"></v-select>
|
||||
@@ -0,0 +1,45 @@
|
||||
## NPM Based WorkFlows
|
||||
``` bash
|
||||
$ npm install vue-select
|
||||
```
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div id="myApp">
|
||||
<v-select v-model="selected" :options="options"></v-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vSelect from 'vue-select'
|
||||
export default {
|
||||
components: {vSelect},
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
options: ['foo','bar','baz']
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Browser Globals
|
||||
|
||||
`v1.3.0+` no longer requires any toolchain to use the component:
|
||||
|
||||
Just include `vue` & `vue-select.js` - I recommend using [unpkg.com](https://unpkg.com/#/).
|
||||
|
||||
```html
|
||||
<!-- use the latest release -->
|
||||
<script src="https://unpkg.com/vue-select@latest"></script>
|
||||
<!-- or point to a specific release -->
|
||||
<script src="https://unpkg.com/vue-select@1.30"></script>
|
||||
```
|
||||
Then register the component in your javascript:
|
||||
|
||||
```js
|
||||
Vue.component('v-select', VueSelect.VueSelect);
|
||||
```
|
||||
|
||||
From there you can use as normal. Here's an [example on JSBin](http://jsbin.com/saxaru/5/edit?html,js,output).
|
||||
@@ -0,0 +1,26 @@
|
||||
### Change Event <small>Vuex Compatibility</small>
|
||||
|
||||
vue-select provides a `change` event. This function is passed the currently selected value(s) as it's only parameter.
|
||||
|
||||
This is very useful when integrating with Vuex, as it will allow your to trigger an action to update your vuex state object. Choose a callback and see it in action.
|
||||
|
||||
<div class="form-inline">
|
||||
<div class="radio"><label><input type="radio" v-model="callback" value="console"> `console.log(val)`</label> </div>
|
||||
<div class="radio"><label><input type="radio" v-model="callback" value="alert"> `alert(val)`</label> </div>
|
||||
</div>
|
||||
|
||||
```html
|
||||
<v-select v-on:change="consoleCallback" :options="countries"></v-select>
|
||||
```
|
||||
|
||||
```js
|
||||
methods: {
|
||||
consoleCallback(val) {
|
||||
console.dir(JSON.stringify(val))
|
||||
},
|
||||
|
||||
alertCallback(val) {
|
||||
alert(JSON.stringify(val))
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
### Reactive Options
|
||||
|
||||
When the list of options provided by the parent changes, vue-select will react as you'd expect.
|
||||
|
||||
<div style="margin-top:0;" class="radio">
|
||||
<label>
|
||||
<input type="radio" name="reactive-options" v-model="reactive" :value="countries">
|
||||
`<v-select :options="countries"></v-select>`
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="reactive-options" v-model="reactive" :value="['foo','bar','baz']">
|
||||
`<v-select options="['foo','bar','baz']"></v-select>`
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<v-select :options="reactive"></v-select>
|
||||
@@ -0,0 +1,29 @@
|
||||
<article class="doc-row" id="ex-multiple">
|
||||
|
||||
### Single/Multiple Selection
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
#### Single Option Select
|
||||
|
||||
```html
|
||||
<v-select :options="countries"></v-select>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
#### Multiple Option Select
|
||||
|
||||
```html
|
||||
<v-select multiple :options="countries"></v-select>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
@@ -0,0 +1,15 @@
|
||||
### Two-Way Value Syncing
|
||||
|
||||
The most common use case for vue-select is being able to sync the components value with a parent component. The `value` property supports two-way data binding to accomplish this. The `.sync` data-binding modifier is completely optional. You may use `value` without a two-way binding to preselect options. Here we have preselected 'Canada' by setting `syncedVal: 'Canada'` on the parent component. The buttons below demonstrate how you can set the `value` from the parent. Current value: <v-code>{{ syncedVal }}</v-code>
|
||||
|
||||
<div class="form-group">
|
||||
`<v-select v-model="syncedVal" :options="countries"></v-select>`
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<v-select v-model="syncedVal" :options="countries"></v-select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button @click="syncedVal = 'United States'" class="btn btn-default">Set to United States</button>
|
||||
<button @click="syncedVal = 'Canada'" class="btn btn-default">Set to Canada</button>
|
||||
</div>
|
||||
@@ -1,15 +0,0 @@
|
||||
export const setSelected = ({ dispatch }, selected) => {
|
||||
dispatch('SET_SELECTED', selected)
|
||||
}
|
||||
|
||||
export const toggleOptionType = ({ dispatch }) => {
|
||||
dispatch('TOGGLE_OPTION_TYPE')
|
||||
}
|
||||
|
||||
export const setPlaceholder = ({ dispatch }, placeholder) => {
|
||||
dispatch('SET_PLACEHOLDER', placeholder)
|
||||
}
|
||||
|
||||
export const toggleMultiple = ({ dispatch }) => {
|
||||
dispatch('TOGGLE_MULTIPLE')
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
Vue.config.debug = true
|
||||
|
||||
const state = {
|
||||
selected: null,
|
||||
placeholder: 'Select a Country',
|
||||
multiple: true,
|
||||
maxHeight: '400px',
|
||||
options: {
|
||||
advanced: require('../data/advanced.js'),
|
||||
simple: require('../data/simple.js'),
|
||||
},
|
||||
optionType: 'advanced'
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_SELECTED (state, selected) {
|
||||
state.selected = selected
|
||||
},
|
||||
|
||||
TOGGLE_OPTION_TYPE (state) {
|
||||
if( state.optionType === 'advanced' ) {
|
||||
state.optionType = 'simple'
|
||||
} else {
|
||||
state.optionType = 'advanced'
|
||||
}
|
||||
},
|
||||
|
||||
SET_PLACEHOLDER (state, placeholder) {
|
||||
state.placeholder = placeholder
|
||||
},
|
||||
|
||||
TOGGLE_MULTIPLE (state) {
|
||||
state.multiple = ! state.multiple
|
||||
},
|
||||
|
||||
SET_MAX_HEIGHT (state, maxHeight) {
|
||||
state.maxHeight = maxHeight
|
||||
}
|
||||
}
|
||||
|
||||
export default new Vuex.Store({
|
||||
state,
|
||||
mutations
|
||||
})
|
||||
+16
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-select",
|
||||
"version": "1.3.3",
|
||||
"version": "2.0.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,
|
||||
@@ -8,6 +8,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "node build/dev-server.js",
|
||||
"dev:docs": "node build/dev-server.js --docs",
|
||||
"build": "node build/build.js",
|
||||
"lint": "eslint --ext .js,.vue src test/unit/specs",
|
||||
"test": "karma start test/unit/karma.conf.js --single-run",
|
||||
@@ -23,6 +24,9 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/sagalbot/vue-select.git"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "2.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
@@ -38,6 +42,8 @@
|
||||
"file-loader": "^0.8.4",
|
||||
"function-bind": "^1.0.2",
|
||||
"gh-pages": "^0.11.0",
|
||||
"highlight.js": "^9.9.0",
|
||||
"html-loader": "^0.4.4",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"http-proxy-middleware": "^0.15.2",
|
||||
"inject-loader": "^2.0.1",
|
||||
@@ -52,6 +58,7 @@
|
||||
"karma-spec-reporter": "0.0.26",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"lolex": "^1.4.0",
|
||||
"markdown-loader": "^0.1.7",
|
||||
"node-sass": "^3.7.0",
|
||||
"ora": "^0.2.0",
|
||||
"phantomjs-prebuilt": "^2.1.3",
|
||||
@@ -59,13 +66,15 @@
|
||||
"sass-loader": "^3.2.0",
|
||||
"shelljs": "^0.7.0",
|
||||
"url-loader": "^0.5.7",
|
||||
"vue": "^1.0.24",
|
||||
"vue-hot-reload-api": "^1.2.0",
|
||||
"vue-html-loader": "^1.0.0",
|
||||
"vue-loader": "^8.3.0",
|
||||
"vue-resource": "^0.8.0",
|
||||
"vue": "^2.1.8",
|
||||
"vue-hot-reload-api": "^2.0.7",
|
||||
"vue-html-loader": "^1.2.3",
|
||||
"vue-loader": "^10.0.2",
|
||||
"vue-markdown-loader": "^0.6.1",
|
||||
"vue-resource": "^1.0.3",
|
||||
"vue-style-loader": "^1.0.0",
|
||||
"vuex": "^0.6.3",
|
||||
"vue-template-compiler": "^2.1.8",
|
||||
"vuex": "^2.1.1",
|
||||
"webpack": "^1.12.2",
|
||||
"webpack-dev-middleware": "^1.4.0",
|
||||
"webpack-hot-middleware": "^2.6.0",
|
||||
|
||||
+679
-562
File diff suppressed because it is too large
Load Diff
+8
-3
@@ -1,12 +1,17 @@
|
||||
import Vue from 'vue'
|
||||
import vSelect from '../src/components/Select.vue'
|
||||
import vSelect from './components/Select.vue'
|
||||
import countries from 'docs/data/advanced.js'
|
||||
|
||||
Vue.component('v-select', vSelect)
|
||||
|
||||
Vue.config.debug = true
|
||||
Vue.config.devtools = true
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: 'body'
|
||||
el: '#app',
|
||||
data: {
|
||||
placeholder: "placeholder",
|
||||
value: null,
|
||||
options: countries
|
||||
}
|
||||
})
|
||||
|
||||
+4
-4
@@ -23,7 +23,7 @@ module.exports = {
|
||||
*/
|
||||
onSearch: {
|
||||
type: Function,
|
||||
default: false
|
||||
default: function(search, loading){}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -59,9 +59,9 @@ module.exports = {
|
||||
*/
|
||||
toggleLoading(toggle = null) {
|
||||
if (toggle == null) {
|
||||
return this.loading = !this.loading
|
||||
return this.showLoading = !this.showLoading
|
||||
}
|
||||
return this.loading = toggle
|
||||
return this.showLoading = toggle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// flow
|
||||
|
||||
module.exports = {
|
||||
watch: {
|
||||
typeAheadPointer() {
|
||||
@@ -30,8 +32,10 @@ module.exports = {
|
||||
*/
|
||||
pixelsToPointerTop() {
|
||||
let pixelsToPointerTop = 0
|
||||
for (let i = 0; i < this.typeAheadPointer; i++) {
|
||||
pixelsToPointerTop += this.$els.dropdownMenu.children[i].offsetHeight
|
||||
if( this.$refs.dropdownMenu ) {
|
||||
for (let i = 0; i < this.typeAheadPointer; i++) {
|
||||
pixelsToPointerTop += this.$refs.dropdownMenu.children[i].offsetHeight
|
||||
}
|
||||
}
|
||||
return pixelsToPointerTop
|
||||
},
|
||||
@@ -50,7 +54,7 @@ module.exports = {
|
||||
* @returns {number}
|
||||
*/
|
||||
pointerHeight() {
|
||||
let element = this.$els.dropdownMenu.children[this.typeAheadPointer]
|
||||
let element = this.$refs.dropdownMenu ? this.$refs.dropdownMenu.children[this.typeAheadPointer] : false
|
||||
return element ? element.offsetHeight : 0
|
||||
},
|
||||
|
||||
@@ -60,8 +64,8 @@ module.exports = {
|
||||
*/
|
||||
viewport() {
|
||||
return {
|
||||
top: this.$els.dropdownMenu.scrollTop,
|
||||
bottom: this.$els.dropdownMenu.offsetHeight + this.$els.dropdownMenu.scrollTop
|
||||
top: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop: 0,
|
||||
bottom: this.$refs.dropdownMenu ? this.$refs.dropdownMenu.offsetHeight + this.$refs.dropdownMenu.scrollTop : 0
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,7 +75,7 @@ module.exports = {
|
||||
* @returns {*}
|
||||
*/
|
||||
scrollTo(position) {
|
||||
return this.$els.dropdownMenu.scrollTop = position
|
||||
return this.$refs.dropdownMenu ? this.$refs.dropdownMenu.scrollTop = position : null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ module.exports = function (config) {
|
||||
webpackMiddleware: {
|
||||
noInfo: true
|
||||
},
|
||||
specReporter: {
|
||||
suppressSkipped: true
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: './coverage',
|
||||
reporters: [
|
||||
|
||||
+289
-150
@@ -1,15 +1,15 @@
|
||||
// flow
|
||||
/* global describe, it, expect */
|
||||
|
||||
import Vue from 'vue'
|
||||
import vSelect from 'src/components/Select.vue'
|
||||
// import vSelect from '../../../dist/vue-select'
|
||||
import pointerScroll from 'src/mixins/pointerScroll.js'
|
||||
|
||||
Vue.component('v-select', vSelect)
|
||||
|
||||
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
|
||||
const Mock = require('!!vue?inject!src/components/Select.vue')
|
||||
|
||||
Vue.component('v-select', vSelect)
|
||||
|
||||
/**
|
||||
* Simulate a DOM event.
|
||||
* @param target
|
||||
@@ -25,6 +25,13 @@ function trigger(target, event, process) {
|
||||
return e
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a Mouse event.
|
||||
* @param target
|
||||
* @param event
|
||||
* @param process
|
||||
* @returns {Event}
|
||||
*/
|
||||
function triggerMouse(target, event, process) {
|
||||
var e = document.createEvent('MouseEvent')
|
||||
e.initEvent('event', true, true)
|
||||
@@ -33,6 +40,13 @@ function triggerMouse(target, event, process) {
|
||||
return e
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a Focus event.
|
||||
* @param target
|
||||
* @param event
|
||||
* @param process
|
||||
* @returns {Event}
|
||||
*/
|
||||
function triggerFocusEvent(target, event, process) {
|
||||
var e = document.createEvent('FocusEvent')
|
||||
e.initEvent('event', true, true)
|
||||
@@ -51,7 +65,7 @@ function searchSubmit(vm, search = false) {
|
||||
vm.$children[0].search = search
|
||||
}
|
||||
|
||||
trigger(vm.$children[0].$els.search, 'keyup', function (e) {
|
||||
trigger(vm.$children[0].$refs.search, 'keyup', function (e) {
|
||||
e.keyCode = 13
|
||||
})
|
||||
}
|
||||
@@ -61,67 +75,75 @@ describe('Select.vue', () => {
|
||||
describe('Selecting values', () => {
|
||||
it('can accept an array with pre-selected values', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
value: 'one',
|
||||
options: ['one', 'two', 'three']
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$children[0].value).toEqual(vm.value)
|
||||
expect(vm.$children[0].mutableValue).toEqual(vm.value)
|
||||
})
|
||||
|
||||
it('can accept an array of objects and pre-selected value (single)', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: {label: 'This is Foo', value: 'foo'},
|
||||
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$children[0].value).toEqual(vm.value)
|
||||
expect(vm.$children[0].mutableValue).toEqual(vm.value)
|
||||
})
|
||||
|
||||
it('can accept an array of objects and pre-selected values (multiple)', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}],
|
||||
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$children[0].value).toEqual(vm.value)
|
||||
expect(vm.$children[0].mutableValue).toEqual(vm.value)
|
||||
})
|
||||
|
||||
it('can deselect a pre-selected object', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true"></v-select></div>',
|
||||
data: {
|
||||
value: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}],
|
||||
options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}]
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].select({label: 'This is Foo', value: 'foo'})
|
||||
expect(vm.$children[0].value.length).toEqual(1)
|
||||
expect(vm.$children[0].mutableValue.length).toEqual(1)
|
||||
})
|
||||
|
||||
it('can deselect a pre-selected string', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true"></v-select></div>',
|
||||
data: {
|
||||
value: ['foo', 'bar'],
|
||||
options: ['foo','bar']
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].select('foo')
|
||||
expect(vm.$children[0].value.length).toEqual(1)
|
||||
expect(vm.$children[0].mutableValue.length).toEqual(1)
|
||||
}),
|
||||
|
||||
it('can deselect an option when multiple is false', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select :value="'foo'"></v-select></div>`,
|
||||
}).$mount()
|
||||
vm.$children[0].deselect('foo')
|
||||
expect(vm.$children[0].mutableValue).toEqual(null)
|
||||
})
|
||||
|
||||
it('can determine if the value prop is empty', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [],
|
||||
@@ -131,28 +153,28 @@ describe('Select.vue', () => {
|
||||
var select = vm.$children[0]
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
|
||||
select.$set('value', ['one'])
|
||||
select.select(['one'])
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', [{l: 'f'}])
|
||||
select.select([{l: 'f'}])
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', 'one')
|
||||
select.select('one')
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', {label: 'foo', value: 'foo'})
|
||||
select.select({label: 'foo', value: 'foo'})
|
||||
expect(select.isValueEmpty).toEqual(false)
|
||||
|
||||
select.$set('value', '')
|
||||
select.select('')
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
|
||||
select.$set('value', null)
|
||||
select.select(null)
|
||||
expect(select.isValueEmpty).toEqual(true)
|
||||
})
|
||||
|
||||
it('should reset the selected values when the multiple property changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="multiple"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="multiple"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
@@ -164,10 +186,10 @@ describe('Select.vue', () => {
|
||||
vm.multiple = false
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual(null)
|
||||
expect(vm.$children[0].mutableValue).toEqual(null)
|
||||
vm.multiple = true
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual([])
|
||||
expect(vm.$children[0].mutableValue).toEqual([])
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -175,20 +197,20 @@ describe('Select.vue', () => {
|
||||
|
||||
it('can retain values present in a new array of options', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" v-model="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
options: ['one', 'two', 'three']
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].$set('options', ['one', 'five', 'six'])
|
||||
expect(vm.$children[0].value).toEqual(['one'])
|
||||
vm.options = ['one', 'five', 'six']
|
||||
expect(vm.$children[0].mutableValue).toEqual(['one'])
|
||||
})
|
||||
|
||||
it('can determine if an object is already selected', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" multiple :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" multiple v-model="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
@@ -199,59 +221,99 @@ describe('Select.vue', () => {
|
||||
expect(vm.$children[0].isOptionSelected({label: 'one'})).toEqual(true)
|
||||
})
|
||||
|
||||
describe('onChange Callback', () => {
|
||||
it('can run a callback when the selection changes', (done) => {
|
||||
it('can use v-model syntax for a two way binding to a parent component', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" v-model="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: 'foo',
|
||||
options: ['foo','bar','baz']
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
expect(vm.$children[0].value).toEqual('foo')
|
||||
expect(vm.$children[0].mutableValue).toEqual('foo')
|
||||
|
||||
vm.$children[0].mutableValue = 'bar'
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.value).toEqual('bar')
|
||||
done()
|
||||
})
|
||||
}),
|
||||
|
||||
it('can check if a string value is selected when the value is an object and multiple is true', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select multiple :value="[{label: 'foo', value: 'bar'}]"></v-select></div>`,
|
||||
}).$mount()
|
||||
expect(vm.$children[0].isOptionSelected('foo')).toEqual(true)
|
||||
}),
|
||||
|
||||
describe('change Event', () => {
|
||||
it('will trigger the input event when the selection changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select :value="['foo']" :options="['foo','bar','baz']" :on-change="cb"></v-select></div>`,
|
||||
components: {vSelect},
|
||||
methods: {
|
||||
cb(val) {
|
||||
}
|
||||
template: `<div><v-select ref="select" :value="['foo']" :options="['foo','bar','baz']" v-on:input="foo = arguments[0]"></v-select></div>`,
|
||||
data: {
|
||||
foo: ''
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0], 'onChange')
|
||||
|
||||
vm.$children[0].select('bar')
|
||||
vm.$refs.select.select('bar')
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].onChange).toHaveBeenCalledWith('bar')
|
||||
vm.$children[0].select('baz')
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].onChange).toHaveBeenCalledWith('baz')
|
||||
done()
|
||||
})
|
||||
expect(vm.foo).toEqual('bar')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should run onChange when multiple is true and the value changes', (done) => {
|
||||
it('should run change when multiple is true and the value changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select v-ref:select :value="['foo']" :options="['foo','bar','baz']" multiple :on-change="cb"></v-select></div>`,
|
||||
methods: {
|
||||
cb(val) {
|
||||
}
|
||||
template: `<div><v-select ref="select" :value="['foo']" :options="['foo','bar','baz']" multiple v-on:input="foo = arguments[0]"></v-select></div>`,
|
||||
data: {
|
||||
foo: ''
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0], 'onChange')
|
||||
|
||||
vm.$children[0].select('bar')
|
||||
vm.$refs.select.select('bar')
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].onChange).toHaveBeenCalledWith(['foo','bar'])
|
||||
vm.$children[0].select('baz')
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].onChange).toHaveBeenCalledWith(['foo','bar','baz'])
|
||||
done()
|
||||
})
|
||||
expect(vm.foo).toEqual(['foo','bar'])
|
||||
done()
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Filtering Options', () => {
|
||||
it('should filter an array of strings', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select ref="select" :options="['foo','bar','baz']" v-model="value"></v-select></div>`,
|
||||
data: {value: 'foo'}
|
||||
}).$mount()
|
||||
vm.$refs.select.search = 'ba'
|
||||
expect(vm.$refs.select.filteredOptions).toEqual(['bar','baz'])
|
||||
})
|
||||
|
||||
it('should filter without case-sensitivity', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select ref="select" :options="['Foo','Bar','Baz']" v-model="value"></v-select></div>`,
|
||||
data: {value: 'foo'}
|
||||
}).$mount()
|
||||
vm.$refs.select.search = 'ba'
|
||||
expect(vm.$refs.select.filteredOptions).toEqual(['Bar','Baz'])
|
||||
})
|
||||
|
||||
it('can filter an array of objects based on the objects label key', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select ref="select" :options="[{label: 'Foo', value: 'foo'}, {label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]" v-model="value"></v-select></div>`,
|
||||
data: {value: 'foo'}
|
||||
}).$mount()
|
||||
vm.$refs.select.search = 'ba'
|
||||
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Toggling Dropdown', () => {
|
||||
it('should not open the dropdown when the el is clicked but the component is disabled', (done) => {
|
||||
const vm = new Vue({
|
||||
@@ -274,7 +336,7 @@ describe('Select.vue', () => {
|
||||
|
||||
it('should open the dropdown when the el is clicked', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
@@ -282,7 +344,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(true)
|
||||
@@ -297,20 +359,20 @@ describe('Select.vue', () => {
|
||||
components: {vSelect},
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0].$els.search, 'blur')
|
||||
spyOn(vm.$children[0].$refs.search, 'blur')
|
||||
|
||||
vm.$children[0].open = true
|
||||
vm.$children[0].toggleDropdown({target: vm.$children[0].$el})
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].$els.search.blur).toHaveBeenCalled()
|
||||
expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should close the dropdown on search blur', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" multiple :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" multiple :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
@@ -319,30 +381,55 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
vm.$children[0].open = true
|
||||
triggerFocusEvent(vm.$children[0].$els.toggle, 'blur')
|
||||
triggerFocusEvent(vm.$children[0].$refs.toggle, 'blur')
|
||||
expect(vm.$children[0].open).toEqual(true)
|
||||
})
|
||||
|
||||
it('will close the dropdown and emit the search:blur event from onSearchBlur', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select></v-select></div>',
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0], '$emit')
|
||||
vm.$children[0].open = true
|
||||
vm.$children[0].onSearchBlur()
|
||||
|
||||
expect(vm.$children[0].open).toEqual(false)
|
||||
expect(vm.$children[0].$emit).toHaveBeenCalledWith('search:blur')
|
||||
})
|
||||
|
||||
it('will open the dropdown and emit the search:focus event from onSearchFocus', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select></v-select></div>',
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0], '$emit')
|
||||
vm.$children[0].onSearchFocus()
|
||||
|
||||
expect(vm.$children[0].open).toEqual(true)
|
||||
expect(vm.$children[0].$emit).toHaveBeenCalledWith('search:focus')
|
||||
})
|
||||
|
||||
it('will close the dropdown on escape, if search is empty', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select></v-select></div>',
|
||||
components: {vSelect},
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0].$els.search, 'blur')
|
||||
spyOn(vm.$children[0].$refs.search, 'blur')
|
||||
|
||||
vm.$children[0].open = true
|
||||
vm.$children[0].onEscape()
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].$els.search.blur).toHaveBeenCalled()
|
||||
expect(vm.$children[0].$refs.search.blur).toHaveBeenCalled()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove existing search text on escape keyup', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" multiple :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" multiple :value="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
@@ -393,7 +480,7 @@ describe('Select.vue', () => {
|
||||
|
||||
vm.$children[0].typeAheadPointer = 1
|
||||
|
||||
trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 38)
|
||||
trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38)
|
||||
expect(vm.$children[0].typeAheadPointer).toEqual(0)
|
||||
})
|
||||
|
||||
@@ -407,7 +494,7 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
vm.$children[0].typeAheadPointer = 1
|
||||
trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 40)
|
||||
trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40)
|
||||
expect(vm.$children[0].typeAheadPointer).toEqual(2)
|
||||
})
|
||||
|
||||
@@ -437,7 +524,7 @@ describe('Select.vue', () => {
|
||||
|
||||
vm.$children[0].typeAheadPointer = 1
|
||||
spyOn(vm.$children[0], 'maybeAdjustScroll')
|
||||
trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 38)
|
||||
trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 38)
|
||||
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -451,7 +538,7 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$children[0], 'maybeAdjustScroll')
|
||||
trigger(vm.$children[0].$els.search, 'keydown', (e) => e.keyCode = 40)
|
||||
trigger(vm.$children[0].$refs.search, 'keydown', (e) => e.keyCode = 40)
|
||||
expect(vm.$children[0].maybeAdjustScroll).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -496,21 +583,26 @@ describe('Select.vue', () => {
|
||||
expect(vm.$children[0].scrollTo).toHaveBeenCalledWith(1)
|
||||
})
|
||||
|
||||
it('should scroll down if the pointer is below the current viewport bounds', () => {
|
||||
/**
|
||||
* @link https://github.com/vuejs/vue-loader/issues/434
|
||||
* @todo vue-loader/inject-loader fails when used twice in the same file,
|
||||
* so the mock here is abastracted to a separate file.
|
||||
*/
|
||||
xit('should scroll down if the pointer is below the current viewport bounds', () => {
|
||||
let methods = Object.assign(pointerScroll.methods, {
|
||||
pixelsToPointerBottom() {
|
||||
return 2
|
||||
},
|
||||
viewport() {
|
||||
return {top: 0, bottom: 1}
|
||||
}
|
||||
pixelsToPointerBottom() {
|
||||
return 2
|
||||
},
|
||||
viewport() {
|
||||
return {top: 0, bottom: 1}
|
||||
}
|
||||
})
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="[\'one\', \'two\', \'three\']"></v-select></div>',
|
||||
template: `<div><v-select :options="['one', 'two', 'three']"></v-select></div>`,
|
||||
components: {
|
||||
'v-select': Mock({
|
||||
'../mixins/pointerScroll': {methods}
|
||||
})
|
||||
'v-select': Mock({
|
||||
'../mixins/pointerScroll': {methods}
|
||||
})
|
||||
},
|
||||
}).$mount()
|
||||
|
||||
@@ -523,19 +615,23 @@ describe('Select.vue', () => {
|
||||
describe('Measuring pixel distances', () => {
|
||||
it('should calculate pointerHeight as the offsetHeight of the pointer element if it exists', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="[\'one\', \'two\', \'three\']""></v-select></div>',
|
||||
components: {vSelect},
|
||||
template: `<div><v-select :options="['one', 'two', 'three']"></v-select></div>`,
|
||||
}).$mount()
|
||||
|
||||
// Fresh instances start with the pointer at -1
|
||||
vm.$children[0].typeAheadPointer = -1
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||
// dropdown must be open for $refs to exist
|
||||
vm.$children[0].open = true
|
||||
|
||||
vm.$children[0].typeAheadPointer = 100
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||
Vue.nextTick(() => {
|
||||
// Fresh instances start with the pointer at -1
|
||||
vm.$children[0].typeAheadPointer = -1
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||
|
||||
vm.$children[0].typeAheadPointer = 1
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(vm.$children[0].$els.dropdownMenu.children[1].offsetHeight)
|
||||
vm.$children[0].typeAheadPointer = 100
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(0)
|
||||
|
||||
vm.$children[0].typeAheadPointer = 1
|
||||
expect(vm.$children[0].pointerHeight()).toEqual(vm.$children[0].$refs.dropdownMenu.children[1].offsetHeight)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -543,16 +639,16 @@ describe('Select.vue', () => {
|
||||
describe('Removing values', () => {
|
||||
it('can remove the given tag when its close icon is clicked', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" v-model="value" :multiple="true"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
options: ['one', 'two', 'three']
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].$els.toggle.querySelector('.close').click()
|
||||
vm.$children[0].$refs.toggle.querySelector('.close').click()
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual([])
|
||||
expect(vm.$children[0].mutableValue).toEqual([])
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -560,7 +656,7 @@ describe('Select.vue', () => {
|
||||
it('should remove the last item in the value array on delete keypress when multiple is true', () => {
|
||||
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" v-model="value" :multiple="true"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one', 'two'],
|
||||
@@ -569,13 +665,13 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
vm.$children[0].maybeDeleteValue()
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual(['one'])
|
||||
expect(vm.$children[0].mutableValue).toEqual(['one'])
|
||||
})
|
||||
})
|
||||
|
||||
it('should set value to null on delete keypress when multiple is false', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" v-model="value"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: 'one',
|
||||
@@ -584,7 +680,7 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
vm.$children[0].maybeDeleteValue()
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual(null)
|
||||
expect(vm.$children[0].mutableValue).toEqual(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -592,14 +688,14 @@ describe('Select.vue', () => {
|
||||
describe('Labels', () => {
|
||||
it('can generate labels using a custom label key', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select label="name" :options="options" :value.sync="value" :multiple="true"></v-select></div>',
|
||||
template: '<div><v-select label="name" :options="options" v-model="value" :multiple="true"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{name: 'Baz'}],
|
||||
options: [{name: 'Foo'}, {name: 'Baz'}]
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$children[0].$els.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
|
||||
expect(vm.$children[0].$refs.toggle.querySelector('.selected-tag').textContent).toContain('Baz')
|
||||
})
|
||||
|
||||
it('should display a placeholder if the value is empty', (done) => {
|
||||
@@ -612,20 +708,18 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
expect(vm.$children[0].searchPlaceholder).toEqual('foo')
|
||||
vm.$children[0].value = {label: 'one'}
|
||||
vm.$children[0].mutableValue = {label: 'one'}
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].searchPlaceholder).not.toBeDefined()
|
||||
done()
|
||||
})
|
||||
|
||||
// expect(vm.$children[0].searchPlaceholder()).toEqual('foo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('When Tagging Is Enabled', () => {
|
||||
it('can determine if a given option string already exists', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select :options="options" taggable></v-select></div>',
|
||||
template: '<div><v-select ref="select" :options="options" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
options: ['one', 'two']
|
||||
@@ -638,7 +732,7 @@ describe('Select.vue', () => {
|
||||
|
||||
it('can determine if a given option object already exists', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select :options="options" taggable></v-select></div>',
|
||||
template: '<div><v-select ref="select" :options="options" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
options: [{label: 'one'}, {label: 'two'}]
|
||||
@@ -651,7 +745,7 @@ describe('Select.vue', () => {
|
||||
|
||||
it('can determine if a given option object already exists when using custom labels', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select :options="options" label="foo" taggable></v-select></div>',
|
||||
template: '<div><v-select ref="select" :options="options" label="foo" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
options: [{foo: 'one'}, {foo: 'two'}]
|
||||
@@ -664,7 +758,7 @@ describe('Select.vue', () => {
|
||||
|
||||
it('can add the current search text as the first item in the options list', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
@@ -678,7 +772,7 @@ describe('Select.vue', () => {
|
||||
|
||||
it('can select the current search text as a string', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
@@ -688,14 +782,14 @@ describe('Select.vue', () => {
|
||||
|
||||
searchSubmit(vm, 'three')
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual(['one', 'three'])
|
||||
expect(vm.$children[0].mutableValue).toEqual(['one', 'three'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can select the current search text as an object', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
@@ -705,14 +799,14 @@ describe('Select.vue', () => {
|
||||
|
||||
searchSubmit(vm, 'two')
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual([{label: 'one'}, {label: 'two'}])
|
||||
expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}, {label: 'two'}])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a freshly created option/tag to the options list when pushTags is true', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" push-tags :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
template: '<div><v-select :options="options" push-tags :value="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
@@ -721,12 +815,12 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
searchSubmit(vm, 'three')
|
||||
expect(vm.$children[0].options).toEqual(['one', 'two', 'three'])
|
||||
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three'])
|
||||
})
|
||||
|
||||
it('wont add a freshly created option/tag to the options list when pushTags is false', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" :taggable="true"></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: ['one'],
|
||||
@@ -735,14 +829,13 @@ describe('Select.vue', () => {
|
||||
}).$mount()
|
||||
|
||||
searchSubmit(vm, 'three')
|
||||
expect(vm.$children[0].options).toEqual(['one', 'two'])
|
||||
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two'])
|
||||
})
|
||||
|
||||
it('should select an existing option if the search string matches a string from options', (done) => {
|
||||
let two = 'two'
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||
data: {
|
||||
value: null,
|
||||
options: ['one', two]
|
||||
@@ -753,7 +846,7 @@ describe('Select.vue', () => {
|
||||
searchSubmit(vm)
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value[0]).toBe(two)
|
||||
expect(vm.$children[0].mutableValue[0]).toBe(two)
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -762,7 +855,6 @@ describe('Select.vue', () => {
|
||||
let two = {label: 'two'}
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
options: [{label: 'one'}, two]
|
||||
}
|
||||
@@ -775,80 +867,127 @@ describe('Select.vue', () => {
|
||||
// This needs to be wrapped in nextTick() twice so that filteredOptions can
|
||||
// calculate after setting the search text, and move the typeAheadPointer index to 0.
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value.label).toBe(two.label)
|
||||
expect(vm.$children[0].mutableValue.label).toBe(two.label)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not reset the selected value when the options property changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" taggable></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: [{label: 'one'}],
|
||||
options: [{label: 'one'}]
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].options = [{label: 'two'}]
|
||||
vm.$children[0].mutableOptions = [{label: 'two'}]
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual([{label: 'one'}])
|
||||
expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not allow duplicate tags when using string options', (done) => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select ref="select" taggable multiple></v-select></div>`,
|
||||
}).$mount()
|
||||
vm.$refs.select.search = 'one'
|
||||
searchSubmit(vm)
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.mutableValue).toEqual(['one'])
|
||||
expect(vm.$refs.select.search).toEqual('')
|
||||
vm.$refs.select.search = 'one'
|
||||
searchSubmit(vm)
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.mutableValue).toEqual([])
|
||||
expect(vm.$refs.select.search).toEqual('')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not allow duplicate tags when using object options', (done) => {
|
||||
const vm = new Vue({
|
||||
template: `<div><v-select ref="select" taggable multiple></v-select></div>`,
|
||||
}).$mount()
|
||||
vm.$refs.select.search = 'one'
|
||||
searchSubmit(vm)
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.mutableValue).toEqual(['one'])
|
||||
expect(vm.$refs.select.search).toEqual('')
|
||||
vm.$refs.select.search = 'one'
|
||||
searchSubmit(vm)
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.mutableValue).toEqual([])
|
||||
expect(vm.$refs.select.search).toEqual('')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('Asynchronous Loading', () => {
|
||||
it('can toggle the loading class', () => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select></v-select></div>',
|
||||
template: '<div><v-select ref="select"></v-select></div>',
|
||||
}).$mount()
|
||||
|
||||
vm.$refs.select.toggleLoading()
|
||||
expect(vm.$refs.select.loading).toEqual(true)
|
||||
expect(vm.$refs.select.showLoading).toEqual(true)
|
||||
|
||||
vm.$refs.select.toggleLoading(true)
|
||||
expect(vm.$refs.select.loading).toEqual(true)
|
||||
expect(vm.$refs.select.showLoading).toEqual(true)
|
||||
})
|
||||
|
||||
it('should trigger the onSearch callback when the search text changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select :on-search="foo"></v-select></div>',
|
||||
template: '<div><v-select ref="select" :on-search="foo"></v-select></div>',
|
||||
data: {
|
||||
called: false
|
||||
},
|
||||
methods: {
|
||||
foo() {
|
||||
foo(val) {
|
||||
this.called = val
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$refs.select, 'onSearch')
|
||||
vm.$refs.select.search = 'foo'
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.onSearch).toHaveBeenCalledWith('foo', vm.$refs.select.toggleLoading)
|
||||
expect(vm.called).toEqual('foo')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not trigger the onSearch callback if the search text is empty', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select v-ref:select search="foo" :on-search="foo"></v-select></div>',
|
||||
template: '<div><v-select ref="select" search="foo" :on-search="foo"></v-select></div>',
|
||||
data: { called: false },
|
||||
methods: {
|
||||
foo() {
|
||||
foo(val) {
|
||||
this.called = ! this.called
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
|
||||
spyOn(vm.$refs.select, 'onSearch')
|
||||
vm.$refs.select.search = ''
|
||||
|
||||
vm.$refs.select.search = 'foo'
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.onSearch).not.toHaveBeenCalled()
|
||||
done()
|
||||
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 v-ref:select :on-search="foo"></v-select></div>',
|
||||
template: '<div><v-select loading ref="select" :on-search="foo"></v-select></div>',
|
||||
methods: {
|
||||
foo(search, loading) {
|
||||
loading(false)
|
||||
@@ -858,14 +997,14 @@ describe('Select.vue', () => {
|
||||
|
||||
vm.$refs.select.search = 'foo'
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.loading).toEqual(false)
|
||||
expect(vm.$refs.select.showLoading).toEqual(false)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can set loading to true from the onSearch callback', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select loading v-ref:select :on-search="foo"></v-select></div>',
|
||||
template: '<div><v-select loading ref="select" :on-search="foo"></v-select></div>',
|
||||
methods: {
|
||||
foo(search, loading) {
|
||||
loading(true)
|
||||
@@ -877,7 +1016,7 @@ describe('Select.vue', () => {
|
||||
select.onSearch(select.search, select.toggleLoading)
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$refs.select.loading).toEqual(true)
|
||||
expect(vm.$refs.select.showLoading).toEqual(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
@@ -886,31 +1025,31 @@ describe('Select.vue', () => {
|
||||
describe('Reset on options change', () => {
|
||||
it('should not reset the selected value by default when the options property changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||
data: {
|
||||
value: 'one',
|
||||
options: ['one', 'two', 'three']
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].options = ['four', 'five', 'six']
|
||||
vm.$children[0].mutableOptions = ['four', 'five', 'six']
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual('one')
|
||||
expect(vm.$children[0].mutableValue).toEqual('one')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should reset the selected value when the options property changes', (done) => {
|
||||
const vm = new Vue({
|
||||
template: '<div><v-select :options="options" :value.sync="value" reset-on-options-change></v-select></div>',
|
||||
template: '<div><v-select :options="options" :value="value" reset-on-options-change></v-select></div>',
|
||||
components: {vSelect},
|
||||
data: {
|
||||
value: 'one',
|
||||
options: ['one', 'two', 'three']
|
||||
}
|
||||
}).$mount()
|
||||
vm.$children[0].options = ['four', 'five', 'six']
|
||||
vm.$children[0].mutableOptions = ['four', 'five', 'six']
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$children[0].value).toEqual(null)
|
||||
expect(vm.$children[0].mutableValue).toEqual(null)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user