2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-05-17 02:29:37 +03:00

Sass & Class Renames (#759)

* - add autoprefixer
- add cssnano
- add postcss-loader
- remove unused packages

* create RTL scss module

* add vs__ prefix to open-indicator, extract to module

* module for dropdown-toggle

* vs__clear module

* vs__dropdown-menu module

* rename `selected-tag` to `vs__selected`

* remove rtl class

* remove dropdown class

* search-input scss module

* move animations to global module

* refactor dropdown list items

* - spinner slot is now scoped with `loading` variable
- move spinner to scss module

* apply vs__search class directly to search input: if you're using the slot, you might not want default styles

* finish global modules

* make RTL a component state

* - update component states to use vs-- prefix
- rename dropdownClasses to stateClasses

* remove unused property

* Closes #760

* fix states

* more state fixes

* rename .close to vs__deselect

* - simplify dev.html
- start on 'sandbox' development

* update build

* - update webpack config
- move Sandbox to VuePress folder

* update external framework version links

* assign grid areas, ensure 100% height outside of docs

* limit specificity

* first pass at assigning variables

* assign 'darkest'

* remove max-height prop

* rename 'component' variables to 'state'

* update badges

* add deprecation notice to docs

* bump travis config

* add coveralls coverage reporter

* bump netlify config

* additional pass pulling up to variables

* start converting to SVG icons

* middle align action icons

* update netlify config

* netlify bump

* fix travis

* fix travis

* try lcov

* netlify attempt

* prune old packages

* bump travis config
This commit is contained in:
Jeff Sagal
2019-02-18 22:01:39 -08:00
committed by GitHub
parent 06800bfb16
commit dc91310860
43 changed files with 1122 additions and 1644 deletions
+5 -4
View File
@@ -1,8 +1,9 @@
language: node_js
cache: npm
node_js:
- node
cache:
directories:
- node_modules
script:
- yarn test --coverage --coverageReporters=text-lcov | coveralls
- yarn test --coverage --coverageReporters=text-lcov
- codecov
+8 -19
View File
@@ -1,6 +1,5 @@
const path = require('path');
const webpack = require('webpack');
const chokidar = require('chokidar');
const VueLoaderPlugin = require('vue-loader').VueLoaderPlugin;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
@@ -8,7 +7,9 @@ const env = process.env.NODE_ENV === 'production'
? 'production'
: 'development';
const extractOrInjectStyles = process.env.NODE_ENV !== 'production'
const devtool = env === 'production' ? 'source-map' : 'eval-source-map';
const extractOrInjectStyles = env !== 'production'
? 'vue-style-loader'
: MiniCssExtractPlugin.loader;
@@ -19,7 +20,7 @@ module.exports = {
publicPath: '/',
filename: '[name].js',
},
// devtool: env === 'production' ? 'source-map' : 'eval-source-map',
devtool,
resolve: {
extensions: ['.js', '.vue'],
alias: {
@@ -47,13 +48,10 @@ module.exports = {
use: [
extractOrInjectStyles,
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.html$/,
loader: 'vue-html-loader',
},
],
},
plugins: [
@@ -65,17 +63,8 @@ module.exports = {
}),
new VueLoaderPlugin(),
],
devServer: {
hot: true,
hotOnly: true,
inline: true,
port: 8080,
before (app, server) {
chokidar.watch([
'./**/*.html',
]).on('all', function () {
server.sockWrite(server.sockets, 'content-changed');
});
},
stats: {
children: false,
modules: false,
},
};
+23 -1
View File
@@ -1,12 +1,15 @@
const merge = require('webpack-merge');
const chokidar = require('chokidar');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const baseWebpackConfig = require('./webpack.base.conf');
/**
*
*/
module.exports = merge(baseWebpackConfig, {
entry: './dev/dev.js',
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './dev/dev.html',
inject: true,
}),
@@ -14,4 +17,23 @@ module.exports = merge(baseWebpackConfig, {
optimization: {
noEmitOnErrors: true,
},
devServer: {
hot: true,
hotOnly: true,
open: true,
inline: true,
stats: {
children: false,
modules: false,
chunks: false,
},
port: 8080,
before (app, server) {
chokidar.watch([
'./**/*.html',
]).on('all', function () {
server.sockWrite(server.sockets, 'content-changed');
});
},
},
});
+19 -114
View File
@@ -1,130 +1,35 @@
<template>
<div id="app">
<v-select placeholder="default" :options="options"></v-select>
<v-select placeholder="default, RTL" :options="options" dir="rtl"></v-select>
<v-select placeholder="default, options=[1,5,10]" :options="[1,5,10]"></v-select>
<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="multiple, closeOnSelect=true" multiple :options="['cat', 'dog', 'bear']"></v-select>
<v-select placeholder="multiple, closeOnSelect=false" multiple :close-on-select="false" :options="['cat', 'dog', 'bear']"></v-select>
<v-select placeholder="searchable=false" :options="options" :searchable="false"></v-select>
<v-select placeholder="search github.." label="full_name" @search="search" :options="ajaxRes"></v-select>
<v-select placeholder="custom option template" :options="options" multiple>
<template slot="selected-option" slot-scope="option">
{{option.label}}
<sandbox hide-help>
<template slot-scope="config">
<v-select v-bind="config" />
</template>
<template slot="option" slot-scope="option">
{{option.label}} ({{option.value}})
</template>
</v-select>
<v-select placeholder="custom option template for string array" taggable :options="['cat', 'dog', 'bear']" multiple>
<template slot="selected-option" slot-scope="option">
{{option.label}}
</template>
<template slot="option" slot-scope="option">
{{option.label}}
</template>
</v-select>
<v-select multiple placeholder="custom label template" :options="options">
<span
slot="selected-option-container"
slot-scope="props"
class="selected-tag"
>
{{ props.option.label }} ({{ props.option.value }})
<button v-if="props.multiple" @click="props.deselect(props.option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</v-select>
<v-select placeholder="select on tab" :select-on-tab="true" :options="options"></v-select>
<v-select placeholder="disabled" disabled value="disabled"></v-select>
<v-select placeholder="disabled multiple" disabled multiple :value="['disabled', 'multiple']"></v-select>
<v-select placeholder="filterable=false, @search=searchPeople" label="first_name" :filterable="false" @search="searchPeople" :options="people"></v-select>
<v-select placeholder="filtering with fuse.js" label="title" :options="fuseSearchOptions" :filter="fuseSearch">
<template slot="option" slot-scope="option">
<strong>{{ option.title }}</strong><br>
<em>{{ `${option.author.firstName} ${option.author.lastName}` }}</em>
</template>
</v-select>
</sandbox>
</div>
</template>
<script>
import Fuse from "fuse.js";
import debounce from "lodash/debounce";
import vSelect from "../src/components/Select.vue";
import countries from "./data/countryCodes";
import fuseSearchOptions from "./data/books";
import vSelect from '../src/components/Select';
import Sandbox from '../docs/.vuepress/components/Sandbox';
// import countries from '../docs/.vuepress/data/countryCodes';
// import books from '../docs/.vuepress/data/books';
export default {
components: { vSelect },
data() {
return {
placeholder: "placeholder",
value: null,
options: countries,
ajaxRes: [],
people: [],
fuseSearchOptions
};
},
methods: {
search(search, loading) {
loading(true);
this.getRepositories(search, loading, this);
},
searchPeople(search, loading) {
loading(true);
this.getPeople(loading, this);
},
getPeople: debounce((loading, vm) => {
vm.$http.get(`https://reqres.in/api/users?per_page=10`).then(res => {
vm.people = res.data.data;
loading(false);
});
}, 250),
getRepositories: debounce((search, loading, vm) => {
vm.$http
.get(`https://api.github.com/search/repositories?q=${search}`)
.then(res => {
vm.ajaxRes = res.data.items;
loading(false);
});
}, 250),
fuseSearch(options, search) {
return new Fuse(options, {
keys: ["title", "author.firstName", "author.lastName"]
}).search(search);
}
}
components: {Sandbox, vSelect},
};
</script>
<style>
/*@import "https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css";*/
/*@import "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.3.2/css/bulma.min.css";*/
/*@import "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css";*/
/*@import "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css";*/
body,
html {
html,
body {
margin: 0;
height: 100%;
font-family: -apple-system, sans-serif;
}
}
#app {
height: 95vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
align-content: center;
}
.v-select {
width: 25em;
margin: 1em;
}
#app {
height: 100%;
}
</style>
+4 -77
View File
@@ -1,87 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Vue Select Dev</title>
<!--<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://cdnjs.cloudflare.com/ajax/libs/foundation/6.5.3/css/foundation.min.css">-->
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css">-->
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">-->
<!--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css">-->
<style>
#app {
height: 95vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
align-content: center;
font-family: -apple-system, sans-serif;
}
.v-select {
width: 25em;
margin: 1em;
}
</style>
</head>
<body>
<div id="app">
<v-select placeholder="default" :options="options"></v-select>
<v-select placeholder="default, RTL" :options="options" dir="rtl"></v-select>
<v-select placeholder="default, options=[1,5,10]" :options="[1,5,10]"></v-select>
<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="multiple, closeOnSelect=true" multiple :options="['cat', 'dog', 'bear']"></v-select>
<v-select placeholder="multiple, closeOnSelect=false" multiple :close-on-select="false" :options="['cat', 'dog', 'bear']"></v-select>
<v-select placeholder="searchable=false" :options="options" :searchable="false"></v-select>
<v-select placeholder="search github.." label="full_name" @search="search" :options="ajaxRes"></v-select>
<v-select placeholder="custom option template" :options="options" multiple>
<template slot="selected-option" slot-scope="option">
{{option.label}}
</template>
<template slot="option" slot-scope="option">
{{option.label}} ({{option.value}})
</template>
</v-select>
<v-select placeholder="custom option template for string array" taggable :options="['cat', 'dog', 'bear']" multiple>
<template slot="selected-option" slot-scope="option">
{{option.label}}
</template>
<template slot="option" slot-scope="option">
{{option.label}}
</template>
</v-select>
<v-select multiple placeholder="custom label template" :options="options">
<span
slot="selected-option-container"
slot-scope="props"
class="selected-tag"
>
{{ props.option.label }} ({{ props.option.value }})
<button v-if="props.multiple" @click="props.deselect(props.option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</v-select>
<v-select placeholder="select on tab" :select-on-tab="true" :options="options"></v-select>
<v-select placeholder="disabled" disabled value="disabled"></v-select>
<v-select placeholder="disabled multiple" disabled multiple :value="['disabled', 'multiple']"></v-select>
<v-select placeholder="filterable=false, @search=searchPeople" label="first_name" :filterable="false" @search="searchPeople" :options="people"></v-select>
<v-select placeholder="filtering with fuse.js" label="title" :options="fuseSearchOptions" :filter="fuseSearch">
<template slot="option" scope="option">
<strong>{{ option.title }}</strong><br>
<em>{{ option.author.firstName + ' ' + option.author.lastName }}</em>
</template>
</v-select>
<v-select placeholder="Vue select with no options and a custom no-option span" >
<span slot="no-options">Custom no options message</span>
</v-select>
</div>
<div id="app"></div>
</body>
</html>
-123
View File
@@ -1,123 +0,0 @@
<template>
<div id="home">
<div class="container">
<h1>Vue Select</h1>
<p class="accolades lead">
<a href="https://github.com/sagalbot/vue-select">
<img src="https://img.shields.io/github/stars/sagalbot/vue-select.svg?label=Stars&style=flat-square"
alt="GitHub Stars">
</a>
<a href="https://www.npmjs.com/package/vue-select">
<img src="https://img.shields.io/npm/dm/vue-select.svg?style=flat-square" alt="Downloads per Month">
</a>
<a href="https://codeclimate.com/github/sagalbot/vue-select/maintainability">
<img src="https://img.shields.io/codeclimate/maintainability/sagalbot/vue-select.svg?style=flat-square"
alt="Maintainability"/>
</a>
<img src="https://img.shields.io/github/license/sagalbot/vue-select.svg?style=flat-square" alt="MIT License">
<img src="https://img.shields.io/github/release/sagalbot/vue-select.svg?style=flat-square"
alt="Current Release">
</p>
<p class="lead">
A Vue.js select component that provides similar functionality
to Select2/Chosen without the overhead of jQuery.
</p>
<ClientOnly>
<v-select id="v-select" :options="options" @input="redirect" label="title">
<template slot="option" slot-scope="option">
<span class="octicon" :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
</ClientOnly>
<section class="content">
<div class="feature-list">
<ul class="list-vue">
<li>Supports Vuex</li>
<li>Tagging Support</li>
<li>Custom Templating</li>
<li>Zero JS/CSS dependencies</li>
<li>Custom Filtering Algorithms</li>
</ul>
<ul class="list-vue">
<li>+95% Test Coverage</li>
<li>Select Single/Multiple</li>
<li>Typeahead Suggestions</li>
<li>Supports RTL & Translations</li>
<li>Plays nice with CSS Frameworks</li>
</ul>
</div>
<p>
And so much more! Get started with: <br>
<code>yarn add vue-select</code>
</p>
<div class="cta">
<a class="btn btn-primary btn-outline btn-lg" href="https://github.com/sagalbot/vue-select">
<span class="octicon octicon-mark-github"></span> View on GitHub
</a>
<router-link class="btn btn-primary btn-outline btn-lg" to="/docs/">
<span class="octicon octicon-book"></span> Read the Docs
</router-link>
</div>
</section>
</div>
</div>
</template>
<style lang="scss" scoped>
@import "../scss/home";
</style>
<script>
export default {
data () {
return {
loading: false,
selected: null,
options: [
{
title: 'Read the Docs',
icon: 'octicon-book',
url: 'docs/',
},
{
title: 'View on GitHub',
icon: 'octicon-mark-github',
url: 'https://github.com/sagalbot/vue-select',
},
{
title: 'View on NPM',
icon: 'octicon-database',
url: 'https://www.npmjs.com/package/vue-select',
},
{
title: 'View Code Climate Analysis',
icon: 'octicon-graph',
url: 'https://codeclimate.com/github/sagalbot/vue-select',
},
{
title: 'View Codepen Examples',
icon: 'octicon-pencil',
url: 'https://codepen.io/collection/nrkgxV/',
},
],
};
},
methods: {
redirect (option) {
// if (window) {
// window.location = option.url;
// }
},
},
};
</script>
+281
View File
@@ -0,0 +1,281 @@
<template>
<div id="sandbox-wrap">
<div id="config">
<div class="list-item" v-if="!hideHelp">
<p>Use the controls below to adjust the props used
by the vue-select components.</p>
<p>The API provides
more props than are shown here, these are some
commonly adjusted settings.</p>
</div>
<h5 class="list-item">Basic Features</h5>
<div class="list-item">
<label for="multiple">
<input id="multiple" type="checkbox" v-model="configuration.multiple">
<code>:multiple="{{ configuration.multiple ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="disabled">
<input id="disabled" type="checkbox" v-model="configuration.disabled">
<code>:disabled="{{ configuration.disabled ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="clearable">
<input id="clearable" type="checkbox" v-model="configuration.clearable">
<code>:clearable="{{ configuration.clearable ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="searchable">
<input id="searchable" type="checkbox" v-model="configuration.searchable">
<code>:searchable="{{ configuration.searchable ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="filterable">
<input id="filterable" type="checkbox" v-model="configuration.filterable">
<code>:filterable="{{ configuration.searchable ? 'true' : 'false' }}"</code>
</label>
</div>
<h5 class="list-item">Tagging</h5>
<div class="list-item">
<label for="taggable">
<input id="taggable" type="checkbox" v-model="configuration.taggable">
<code>:taggable="{{ configuration.taggable ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="noDrop">
<input id="noDrop" type="checkbox" v-model="configuration.noDrop">
<code>:no-drop="{{ configuration.noDrop ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="pushTags">
<input id="pushTags" type="checkbox" v-model="configuration.pushTags">
<code>:push-tags="{{ configuration.pushTags ? 'true' : 'false' }}"</code>
</label>
</div>
<h5 class="list-item">UX</h5>
<div class="list-item">
<label for="selectOnTab">
<input id="selectOnTab" type="checkbox" v-model="configuration.selectOnTab">
<code>:select-on-tab="{{ configuration.selectOnTab ? 'true' : 'false' }}"</code>
</label>
</div>
<div class="list-item">
<label for="closeOnSelect">
<input id="closeOnSelect" type="checkbox" v-model="configuration.closeOnSelect">
<code>:close-on-select="{{ configuration.closeOnSelect ? 'true' : 'false' }}"</code>
</label>
</div>
<h5 class="list-item">Localization / i18n</h5>
<div class="list-item">
<label for="rtl">
<input id="rtl" type="radio" v-model="configuration.dir" value="rtl">
<code>dir="rtl"</code>
</label>
<label for="ltr">
<input id="ltr" type="radio" v-model="configuration.dir" value="ltr">
<code>dir="ltr"</code>
</label>
</div>
</div>
<div id="sandbox">
<slot v-bind="configuration">
<div class="example">
<v-select v-bind="configuration" placeholder="country objects"></v-select>
</div>
<div class="example">
<v-select v-bind="configuration" placeholder="country objects, using option scoped slots">
<template slot="selected-option" slot-scope="{ label, value }">
{{ label }} -- {{ value }}
</template>
<template slot="option" slot-scope="{ label, value }">
{{ label }} ({{ value }})
</template>
</v-select>
</div>
<div class="example">
<v-select v-bind="configuration" :options="['cat', 'dog', 'bear']" placeholder="string options, option slots">
<template slot="selected-option" slot-scope="{ label }">
{{ label }}
</template>
<template slot="option" slot-scope="{ label }">
{{ label }}
</template>
</v-select>
</div>
<div class="example">
<v-select v-bind="configuration" :options="[1,5,10]" placeholder="options=[1,5,10]"></v-select>
</div>
<div class="example">
<v-select v-bind="configuration" label="title" :options="optionDataSets.books" :filter="fuseSearch"
placeholder="advanced filtering w/ fuse.js + scoped slots">
<template slot="option" slot-scope="option">
<strong>{{ option.title }}</strong><br>
<em>{{ `${option.author.firstName} ${option.author.lastName}` }}</em>
</template>
</v-select>
</div>
<div class="example">
<v-select
v-bind="configuration"
placeholder="search github repositories.."
label="full_name"
@search="search"
:options="ajaxRes"
>
</v-select>
</div>
<div class="example">
<v-select v-bind="configuration" :options="[]" placeholder="options=[]"></v-select>
</div>
</slot>
</div>
</div>
</template>
<script>
import Fuse from 'fuse.js';
import debounce from 'lodash/debounce';
import vSelect from '../../../src/components/Select.vue';
import countries from '../data/countryCodes';
import books from '../data/books';
const defaultConfig = () => ({
options: countries,
multiple: false,
dir: 'ltr',
clearable: true,
searchable: true,
filterable: true,
noDrop: false,
closeOnSelect: true,
disabled: false,
selectOntab: false,
placeholder: 'placeholder',
});
export default {
props: {
hideHelp: {
type: Boolean,
default: false,
},
},
components: {vSelect},
data () {
return {
configuration: defaultConfig(),
value: null,
ajaxRes: [],
people: [],
optionDataSet: 'countries',
optionDataSets: {
countries,
books,
},
};
},
methods: {
search (search, loading) {
loading(true);
this.getRepositories(search, loading, this);
},
searchPeople (search, loading) {
loading(true);
this.getPeople(loading, this);
},
getPeople: debounce((loading, vm) => {
vm.$http.get(`https://reqres.in/api/users?per_page=10`).then(res => {
vm.people = res.data.data;
loading(false);
});
}, 250),
getRepositories: debounce((search, loading, vm) => {
vm.$http
.get(`https://api.github.com/search/repositories?q=${search}`)
.then(res => {
vm.ajaxRes = res.data.items;
loading(false);
});
}, 250),
fuseSearch (options, search) {
return new Fuse(options, {
keys: ['title', 'author.firstName', 'author.lastName'],
}).search(search);
},
},
};
</script>
<style scoped>
#sandbox-wrap {
min-height: 100%;
display: grid;
grid-template-columns: auto 75%;
grid-template-rows: auto;
grid-template-areas:
"sidebar component"
}
#config {
grid-area: sidebar;
border-right: 1px solid #eaecef;
display: flex;
flex-direction: column;
justify-content: center;
}
#sandbox {
grid-area: component;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.list-item {
margin-top: 0;
margin-bottom: 1rem;
padding: 1rem 1rem 0;
}
.list-item:not(:first-child) {
border-top: 1px solid #eaecef;
}
.example {
margin-bottom: 2rem;
}
.v-select {
width: 25em;
}
</style>
+38 -21
View File
@@ -1,13 +1,13 @@
const isDeployPreview = process.env.hasOwnProperty('DEPLOY_PREVIEW');
const meta = {
title: 'Vue Select | VueJS Select2/Chosen Component',
description: 'A well-tested, native Vue.js component that provides similar functionality to Select2/Chosen without the overhead of jQuery.',
icon: 'static/vue-logo.png',
description: 'Everything you wish the native <select> element could do, wrapped up into a zero dependency, highly extensible Vue component.',
url: 'http://sagalbot.github.io/vue-select/',
icon: '/vue-logo.png',
};
module.exports = {
title: 'Vue Select',
description: meta.description,
head: [
let head = [
[
'link',
{
@@ -22,13 +22,23 @@ module.exports = {
rel: 'stylesheet',
type: 'text/css',
}],
[
'link',
{
href: 'https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.min.css',
rel: 'stylesheet',
type: 'text/css',
}],
['link', { rel: 'icon', href: `/vue-logo.png` }],
['meta', { name: 'theme-color', content: '#3eaf7c' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
['link', { rel: 'apple-touch-icon', href: `/icons/apple-touch-icon-152x152.png` }],
['link', { rel: 'mask-icon', href: '/icons/safari-pinned-tab.svg', color: '#3eaf7c' }],
['meta', { name: 'msapplication-TileImage', content: '/icons/msapplication-icon-144x144.png' }],
['meta', { name: 'msapplication-TileColor', content: '#000000' }]
];
if (isDeployPreview) {
head.push(
['meta', {name: 'robots', content: 'noindex'}],
['meta', {name: 'googlebot', content: 'noindex'}],
);
} else {
head.push(
['meta', {name: 'title', content: meta.title}],
['meta', {name: 'description', content: meta.description}],
['link', {rel: 'icon', href: meta.icon, type: 'image/png'}],
@@ -40,16 +50,24 @@ module.exports = {
['meta', {property: 'twitter:title', content: meta.title}],
['meta', {property: 'og:title', content: meta.title}],
['meta', {property: 'og:site_name', content: meta.title}],
[
'meta',
{property: 'og:url', content: 'http://sagalbot.github.io/vue-select/'}],
],
serviceWorker: true,
ga: 'UA-12818324-8',
['meta', {property: 'og:url', content: meta.url}],
);
}
module.exports = {
title: 'Vue Select',
description: meta.description,
head,
serviceWorker: !isDeployPreview,
ga: isDeployPreview ? '' : 'UA-12818324-8',
themeConfig: {
repo: 'sagalbot/vue-select',
editLinks: true,
docsDir: 'docs',
nav: [
{text: 'Home', link: '/'},
{text: 'Sandbox', link: '/sandbox'},
],
sidebar: {
'/': [
{
@@ -78,11 +96,10 @@ module.exports = {
children: [
['api/props', 'Props'],
['api/slots', 'Slots'],
['api/events', 'Events']
['api/events', 'Events'],
],
},
],
},
},
};
-47
View File
@@ -1,47 +0,0 @@
@import 'variables';
#v-select {
font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
max-width: 500px;
margin: 0 auto;
text-align: left;
.dropdown-toggle {
background: #fff;
border-color: rgba(82, 166, 183, 0.39);
}
&.dropdown.open .dropdown-toggle,
&.dropdown.open .dropdown-menu,
&.dropdown.open .open-indicator:before {
border-color: #4CC3D9;
}
.active a {
background: rgba(50, 50, 50, .1);
color: #333;
}
&.dropdown li {
border-bottom: 1px solid rgba($code-grey, .1);
&:last-child {
border-bottom: none;
}
}
&.dropdown li a {
padding: 10px 20px;
display: inline-flex;
width: 100%;
align-items: center;
font-size: 1.5em;
.octicon {
font-size: 1.5em;
width: 1.5em;
}
}
&.dropdown .highlight a,
&.dropdown li:hover a {
background: #4CC3D9;
color: #fff;
}
}
File diff suppressed because one or more lines are too long
-17
View File
@@ -1,17 +0,0 @@
$orange: #e96900;
$yellow: #FFC65D;
$green: #42b983;
$blue: #4CC3D9;
$purple: #93648D;
$black: #34495e;
$red: #ff6666;
$gradient: linear-gradient(45deg, rgba(76,195,217,0) 0%,rgba(152,227,234,1) 100%);
// Code
$code-blue: #66d9ef;
$code-purple: #ae81ff;
$code-black: #272822;
$code-white: #f8f8f8;
$code-grey: #708090;
$code-green: #a6e22e;
-3
View File
@@ -1,3 +0,0 @@
@import '~normalize.css';
@import 'demo';
@import 'cyan_theme';
+11 -6
View File
@@ -1,10 +1,11 @@
# Vue Select
![Build Status](https://travis-ci.org/sagalbot/vue-select.svg?branch=master)
![Current Release](https://img.shields.io/github/release/sagalbot/vue-select.svg?style=flat-square)
![Bundle Size](https://img.shields.io/bundlephobia/min/vue-select.svg?style=flat-square)
![Monthly Downloads](https://img.shields.io/npm/dm/vue-select.svg?style=flat-square)
![Code Coverage](https://img.shields.io/coveralls/github/sagalbot/vue-select.svg?style=flat-square)
![Maintainability Score](https://img.shields.io/codeclimate/maintainability/sagalbot/vue-select.svg?style=flat-square)
![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)
> Everything you wish the native `<select>` element could do, wrapped
up into a zero dependency, highly extensible Vue component.
@@ -16,15 +17,19 @@ template that fits the 80% use case for a select dropdown. Here it is by default
<v-select :options="['Option One','Option Two','Option Three']" />
</div>
If you want to get a quick sense of what vue-select can do, check out
[the sandbox](sandbox.md).
#### Features
- AJAX Support
- Tagging
- List Filtering/Searching
- Supports Vuex
- Filtering/Searching
- Vuex Support
- AJAX Support
- SSR Support
- Select Single/Multiple Options
- Tested with Bootstrap 3/4, Bulma, Foundation
- +95% Test Coverage
- ~33kb minified with CSS
- ~20kb Total / ~5kb CSS / ~15kb JS
- Zero dependencies
#### Resources
+5
View File
@@ -50,6 +50,11 @@ clearable: {
## maxHeight
::: warning Deprecated in `v2.x` & Removed in `v3.0`
This prop was removed in `v3.0`. You can use the `$vs-dropdown-max-height`
SCSS variable to adjust this setting in `v3.x`.
:::
Sets the max-height property on the dropdown list.
```js
+2 -6
View File
@@ -6,7 +6,7 @@ The most common use case for `vue-select` is to have the chosen value synced wit
<v-select v-model="selected"></v-select>
```
<CodePen url="Kqxbjw" height="25"/>
<CodePen url="Kqxbjw" height="250"/>
If you don't require the `value` to be synced, you can also pass the prop directly:
@@ -31,14 +31,10 @@ By default, `vue-select` supports choosing a single value. If you need multiple
To allow input that's not present within the options, set the `taggable` prop to true.
If you want new tags to be pushed to the options list, set `push-tags` to true.
```html
<v-select taggable></v-select>
```
<CodePen url="XVoWxm" height="350"/>
## Return a Single Key from an Object
<CodePen url="XVoWxm" height="350"/>
When the `options` array contains objects, `vue-select` returns the whole object as dropdown value upon selection. You can specify your own `index` prop to return only the value contained in the specific property.
For example, consider an object with `value` and `label` properties:
+6
View File
@@ -0,0 +1,6 @@
---
sidebar: false
editLink: false
layout: Sandbox
---
+8 -1
View File
@@ -2,7 +2,14 @@
#
# “publish” is the directory to publish (relative to root of your repo),
# “command” is your build command,
[build]
publish = "docs/.vuepress/dist"
command = "yarn build:docs"
# Deploy Preview context
#
# All deploys resulting from a pull/merge request will
# inherit these settings.
[context.deploy-preview]
publish = "docs/.vuepress/dist"
command = "yarn add --dev webpack@~4.28 && yarn && yarn build:preview"
+9 -10
View File
@@ -8,10 +8,11 @@
"license": "MIT",
"scripts": {
"start": "npm run dev",
"dev": "webpack-dev-server --config build/webpack.dev.conf.js --hot --progress -d",
"dev:docs": "vuepress dev docs",
"serve": "webpack-dev-server --config build/webpack.dev.conf.js --hot --progress -d",
"serve:docs": "vuepress dev docs",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js --progress",
"build:docs": "vuepress build docs",
"build:preview": "cross-env DEPLOY_PREVIEW=true vuepress build --debug docs",
"test": "jest"
},
"repository": {
@@ -29,29 +30,27 @@
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@babel/runtime": "^7.3.1",
"@vue/cli-service": "^3.4.0",
"@vue/test-utils": "^1.0.0-beta.29",
"autoprefixer": "^9.4.7",
"babel-core": "^7.0.0-bridge.0",
"babel-loader": "^8.0.0",
"chokidar": "^2.0.4",
"coveralls": "^3.0.2",
"cross-env": "^5.2.0",
"css-loader": "^1.0.0",
"eventsource-polyfill": "^0.9.6",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^3.0.0",
"function-bind": "^1.0.2",
"css-loader": "^2.1.0",
"cssnano": "^4.1.10",
"fuse.js": "^3.2.0",
"gh-pages": "^0.11.0",
"html-loader": "^0.5.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.1.0",
"jest-serializer-vue": "^2.0.2",
"jest-transform-stub": "^2.0.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.10.0",
"postcss-loader": "^3.0.0",
"postcss-scss": "^2.0.0",
"sass-loader": "^7.1.0",
"shelljs": "^0.7.0",
"url-loader": "^1.1.2",
"vue": "^2.6.4",
"vue-html-loader": "^1.2.4",
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: 'default',
}),
],
};
+5
View File
@@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
<path d="M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z"/>
</svg>
</template>
+5
View File
@@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="10">
<path d="M9.211364 7.59931l4.48338-4.867229c.407008-.441854.407008-1.158247 0-1.60046l-.73712-.80023c-.407008-.441854-1.066904-.441854-1.474243 0L7 5.198617 2.51662.33139c-.407008-.441853-1.066904-.441853-1.474243 0l-.737121.80023c-.407008.441854-.407008 1.158248 0 1.600461l4.48338 4.867228L7 10l2.211364-2.40069z"/>
</svg>
</template>
+52 -351
View File
@@ -1,363 +1,69 @@
<style>
.v-select {
position: relative;
font-family: inherit;
}
.v-select,
.v-select * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* Rtl support - Because we're using a flexbox-based layout, the `dir="rtl"` HTML
attribute does most of the work for us by rearranging the child elements visually.
*/
.v-select[dir="rtl"] .vs__actions {
padding: 0 3px 0 6px;
}
.v-select[dir="rtl"] .dropdown-toggle .clear {
margin-left: 6px;
margin-right: 0;
}
.v-select[dir="rtl"] .selected-tag .close {
margin-left: 0;
margin-right: 2px;
}
.v-select[dir="rtl"] .dropdown-menu {
text-align: right;
}
/* Open Indicator */
.v-select .open-indicator {
display: flex;
align-items: center;
cursor: pointer;
pointer-events: all;
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);
opacity: 1;
width: 12px; /* To account for extra width from rotating. */
}
.v-select .open-indicator:before {
border-color: rgba(60, 60, 60, .5);
border-style: solid;
border-width: 3px 3px 0 0;
content: '';
display: inline-block;
height: 10px;
width: 10px;
vertical-align: text-top;
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;
}
/* Open Indicator States */
.v-select.open .open-indicator:before {
transform: rotate(315deg);
}
.v-select.loading .open-indicator {
opacity: 0;
}
/* Dropdown Toggle */
.v-select .dropdown-toggle {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: flex;
padding: 0 0 4px 0;
background: none;
border: 1px solid rgba(60, 60, 60, .26);
border-radius: 4px;
white-space: normal;
}
.v-select .vs__selected-options {
display: flex;
flex-basis: 100%;
flex-grow: 1;
flex-wrap: wrap;
padding: 0 2px;
position: relative;
}
.v-select .vs__actions {
display: flex;
align-items: stretch;
padding: 0 6px 0 3px;
}
/* Clear Button */
.v-select .dropdown-toggle .clear {
font-size: 23px;
font-weight: 700;
line-height: 1;
color: rgba(60, 60, 60, 0.5);
padding: 0;
border: 0;
background-color: transparent;
cursor: pointer;
margin-right: 6px;
}
/* Dropdown Toggle States */
.v-select.searchable .dropdown-toggle {
cursor: text;
}
.v-select.unsearchable .dropdown-toggle {
cursor: pointer;
}
.v-select.open .dropdown-toggle {
border-bottom-color: transparent;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
/* Dropdown Menu */
.v-select .dropdown-menu {
display:block;
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
min-width: 160px;
padding: 5px 0;
margin: 0;
width: 100%;
overflow-y: auto;
border: 1px solid rgba(0, 0, 0, .26);
box-shadow: 0px 3px 6px 0px rgba(0,0,0,.15);
border-top: none;
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 {
display: flex;
align-items: center;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
color: #333;
line-height: 1.42857143; /* Normalize line height */
margin: 4px 2px 0px 2px;
padding: 0 0.25em;
transition: opacity .25s;
}
.v-select.single .selected-tag {
background-color: transparent;
border-color: transparent;
}
.v-select.single.open .selected-tag {
position: absolute;
opacity: .4;
}
.v-select.single.searching .selected-tag {
display: none;
}
.v-select .selected-tag .close {
margin-left: 2px;
font-size: 1.25em;
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.single.searching:not(.open):not(.loading) input[type="search"] {
opacity: .2;
}
/* 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;
display: inline-block;
border: 1px solid transparent;
border-left: none;
outline: none;
margin: 4px 0 0 0;
padding: 0 7px;
max-width: 100%;
background: none;
box-shadow: none;
flex-grow: 1;
width: 0;
}
.v-select.unsearchable input[type="search"] {
opacity: 0;
}
.v-select.unsearchable input[type="search"]:hover {
cursor: pointer;
}
/* 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 .dropdown-menu .active > a {
color: #333;
background: rgba(50, 50, 50, .1);
}
.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 {
align-self: center;
opacity: 0;
font-size: 5px;
text-indent: -9999em;
overflow: hidden;
border-top: .9em solid rgba(100, 100, 100, .1);
border-right: .9em solid rgba(100, 100, 100, .1);
border-bottom: .9em solid rgba(100, 100, 100, .1);
border-left: .9em solid rgba(60, 60, 60, .45);
transform: translateZ(0);
animation: vSelectSpinner 1.1s infinite linear;
transition: opacity .1s;
}
.v-select .spinner,
.v-select .spinner:after {
border-radius: 50%;
width: 5em;
height: 5em;
}
/* Disabled state */
.v-select.disabled .dropdown-toggle,
.v-select.disabled .dropdown-toggle .clear,
.v-select.disabled .dropdown-toggle input,
.v-select.disabled .selected-tag .close,
.v-select.disabled .open-indicator {
cursor: not-allowed;
background-color: rgb(248, 248, 248);
}
/* Loading Spinner States */
.v-select.loading .spinner {
opacity: 1;
}
/* KeyFrames */
@-webkit-keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
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 lang="scss">
@import '../scss/vue-select.scss';
</style>
<template>
<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="dropdown-toggle">
<div :dir="dir" class="v-select" :class="stateClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="vs__dropdown-toggle">
<div class="vs__selected-options" ref="selectedOptions">
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot v-for="option in valueAsArray"
name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}"
:deselect="deselect"
:multiple="multiple"
:disabled="disabled">
<span class="vs__selected" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="vs__deselect" aria-label="Deselect option">
<deselect />
</button>
</span>
</slot>
<slot name="search" v-bind="scope.search">
<input v-bind="scope.search.attributes" v-on="scope.search.events">
<input class="vs__search" v-bind="scope.search.attributes" v-on="scope.search.events">
</slot>
</div>
<div class="vs__actions">
<button
v-show="showClearButton"
:disabled="disabled"
@click="clearSelection"
type="button"
class="clear"
class="vs__clear"
title="Clear selection"
>
<span aria-hidden="true">&times;</span>
<deselect />
</button>
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
<open-indicator v-if="!noDrop" ref="openIndicator" role="presentation" class="vs__open-indicator" />
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
<slot name="spinner" v-bind="scope.spinner">
<div class="vs__spinner" v-show="mutableLoading">Loading...</div>
</slot>
</div>
</div>
<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="dropdown-menu" :style="{ 'max-height': maxHeight }" role="listbox" @mousedown="onMousedown">
<li role="option" v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
<a @mousedown.prevent.stop="select(option)">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown="onMousedown">
<li
role="option"
v-for="(option, index) in filteredOptions"
:key="index"
class="vs__dropdown-option"
:class="{ active: isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer }"
@mouseover="typeAheadPointer = index"
@mousedown.prevent.stop="select(option)"
>
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
</a>
</li>
<li v-if="!filteredOptions.length" class="no-options" @mousedown.stop="">
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
<slot name="no-options">Sorry, no matching options.</slot>
</li>
</ul>
@@ -369,8 +75,12 @@
import pointerScroll from '../mixins/pointerScroll'
import typeAheadPointer from '../mixins/typeAheadPointer'
import ajax from '../mixins/ajax'
import Deselect from './Deselect'
import OpenIndicator from './OpenIndicator'
export default {
components: {Deselect, OpenIndicator},
mixins: [pointerScroll, typeAheadPointer, ajax],
props: {
@@ -416,16 +126,6 @@
default: true
},
/**
* Sets the max-height property on the dropdown list.
* @deprecated
* @type {String}
*/
maxHeight: {
type: String,
default: '400px'
},
/**
* Enable/disable filtering the options.
* @type {Boolean}
@@ -454,13 +154,12 @@
},
/**
* Sets a Vue transition property on the `.dropdown-menu`. vue-select
* does not include CSS for transitions, you'll need to add them yourself.
* Sets a Vue transition property on the `.vs__dropdown-menu`.
* @type {String}
*/
transition: {
type: String,
default: 'fade'
default: 'vs__fade'
},
/**
@@ -902,7 +601,7 @@
*/
toggleDropdown(e) {
if (e.target === this.$refs.openIndicator || e.target === this.searchEl || e.target === this.$refs.toggle ||
e.target.classList.contains('selected-tag') || e.target === this.$el) {
e.target.classList.contains('vs__selected') || e.target === this.$el) {
if (this.open) {
this.searchEl.blur() // dropdown will close on blur
} else {
@@ -1147,7 +846,7 @@
'role': 'combobox',
'type': 'search',
'autocomplete': 'off',
'class': 'form-control',
'value': this.search,
},
events: {
'keydown': this.onSearchKeyDown,
@@ -1157,23 +856,25 @@
'input': (e) => this.search = e.target.value,
},
},
spinner: {
loading: this.mutableLoading
}
};
},
/**
* Classes to be output on .dropdown
* Holds the current state of the component.
* @return {Object}
*/
dropdownClasses() {
stateClasses() {
return {
open: this.dropdownOpen,
single: !this.multiple,
searching: this.searching,
searchable: this.searchable,
unsearchable: !this.searchable,
loading: this.mutableLoading,
rtl: this.dir === 'rtl', // This can be removed - styling is handled by `dir="rtl"` attribute
disabled: this.disabled
'vs--open': this.dropdownOpen,
'vs--single': !this.multiple,
'vs--searching': this.searching,
'vs--searchable': this.searchable,
'vs--unsearchable': !this.searchable,
'vs--loading': this.mutableLoading,
'vs--disabled': this.disabled
}
},
@@ -1191,7 +892,7 @@
* @return {Boolean} True if non empty value
*/
searching() {
return !!this.search
return !! this.search
},
/**
+31
View File
@@ -0,0 +1,31 @@
$transition-timing-function: cubic-bezier(1.0, 0.5, 0.8, 1.0);
$transition-duration: .15s;
/* KeyFrames */
@-webkit-keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes vSelectSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Dropdown Default Transition */
.vs__fade-enter-active,
.vs__fade-leave-active {
transition: opacity $transition-duration $transition-timing-function;
}
.vs__fade-enter,
.vs__fade-leave-to {
opacity: 0;
}
+9
View File
@@ -0,0 +1,9 @@
.v-select {
position: relative;
font-family: inherit;
}
.v-select,
.v-select * {
box-sizing: border-box;
}
+53
View File
@@ -0,0 +1,53 @@
/** Component States */
/*
* Disabled
*
* When the component is disabled, all interaction
* should be prevented. Here we modify the bg color,
* and change the cursor displayed on the interactive
* components.
*/
$disabled-bg: $vs-state-disabled-bg;
$disabled-color: $vs-state-disabled-color;
$disabled-cursor: $vs-state-disabled-cursor;
.vs--disabled {
.vs__dropdown-toggle,
.vs__clear,
.vs__search,
.vs__selected,
.vs__open-indicator {
cursor: $disabled-cursor;
background-color: $disabled-bg;
}
}
/*
* RTL - Right to Left Support
*
* Because we're using a flexbox layout, the `dir="rtl"`
* HTML attribute does most of the work for us by
* rearranging the child elements visually.
*/
.v-select[dir="rtl"] {
.vs__actions {
padding: 0 3px 0 6px;
}
.vs__clear {
margin-left: 6px;
margin-right: 0;
}
.vs__deselect {
margin-left: 0;
margin-right: 2px;
}
.vs__dropdown-menu {
text-align: right;
}
}
+44
View File
@@ -0,0 +1,44 @@
$vs-colors: (
lightest: rgba(60, 60, 60, 0.26),
light: rgba(60, 60, 60, 0.5),
dark: #333,
darkest: rgba(0, 0, 0, .15),
) !default;
// Global Component Variables
$vs-component-line-height: 1.4 !default;
$vs-component-placeholder-color: inherit !default;
// Active State
$vs-state-active-bg: #5897fb !default;
$vs-state-active-color: #fff !default;
// Disabled State
$vs-state-disabled-bg: rgb(248, 248, 248) !default;
$vs-state-disabled-color: map_get($vs-colors, 'light') !default;
$vs-state-disabled-controls-color: map_get($vs-colors, 'light') !default;
$vs-state-disabled-cursor: not-allowed !default;
// Borders
$vs-border-width: 1px !default;
$vs-border-style: solid !default;
$vs-border-radius: 4px !default;
$vs-border-color: map_get($vs-colors, 'lightest') !default;
// Component Controls: Clear, Open Indicator
$vs-controls-color: map_get($vs-colors, 'light') !default;
$vs-controls-size: 1 !default;
$vs-controls-deselect-text-shadow: 0 1px 0 #fff;
// Selected
$vs-selected-bg: #f0f0f0 !default;
$vs-selected-border-color: $vs-border-color !default;
$vs-selected-border-style: $vs-border-style !default;
$vs-selected-border-width: $vs-border-width !default;
// Dropdown
$vs-dropdown-z-index: 1000 !default;
$vs-dropdown-min-width: 160px !default;
$vs-dropdown-max-height: 350px !default;
$vs-dropdown-box-shadow: 0px 3px 6px 0px map_get($vs-colors, 'darkest') !default;
$vs-dropdown-bg: #fff !default;
+10
View File
@@ -0,0 +1,10 @@
/* Clear Button */
.vs__clear {
fill: $vs-controls-color;
padding: 0;
border: 0;
background-color: transparent;
cursor: pointer;
margin-right: 8px;
}
+37
View File
@@ -0,0 +1,37 @@
/* Dropdown Menu */
$border-width: $vs-border-width;
$border-style: solid;
$border-color: $vs-border-color;
$border-radius: $vs-border-radius;
$box-shadow: $vs-dropdown-box-shadow;
$bg-color: $vs-dropdown-bg;
$z-index: $vs-dropdown-z-index;
$min-width: $vs-dropdown-min-width;
$max-height: $vs-dropdown-max-height;
.vs__dropdown-menu {
display: block;
position: absolute;
top: calc(100% - #{$border-width}); // -{#$border-width} here ensures the left and right borders of the dropdown appear flush with the toggle.
left: 0;
z-index: $z-index;
padding: 5px 0;
margin: 0;
width: 100%;
max-height: $max-height;
min-width: $min-width;
overflow-y: auto;
box-shadow: $box-shadow;
border: $border-width $border-style $border-color;
border-top-style: none;
border-radius: 0 0 $border-radius $border-radius;
text-align: left;
list-style: none;
background: $bg-color;
}
.vs__no-options {
text-align: center;
}
+18
View File
@@ -0,0 +1,18 @@
/* List Items */
.vs__dropdown-option {
line-height: 1.42857143; /* Normalize line height */
display: block;
padding: 3px 20px;
clear: both;
color: #333; /* Overrides most CSS frameworks */
white-space: nowrap;
&:hover {
cursor: pointer;
}
}
.vs__dropdown-option--highlight {
background: $vs-state-active-bg;
color: $vs-state-active-color;
}
+54
View File
@@ -0,0 +1,54 @@
/**
Dropdown Toggle
The dropdown toggle is the primary wrapper of the component. It
has two direct descendants: .vs__selected-options, and .vs__actions.
.vs__selected-options holds the .vs__selected's as well as the
main search input.
.vs__actions holds the clear button and dropdown toggle.
*/
$border-width: $vs-border-width;
$border-style: $vs-border-style;
$border-color: $vs-border-color;
$border-radius: $vs-border-radius;
.vs__dropdown-toggle {
appearance: none;
display: flex;
padding: 0 0 4px 0;
background: none;
border: $border-width $border-style $border-color;
border-radius: $border-radius;
white-space: normal;
}
.vs__selected-options {
display: flex;
flex-basis: 100%;
flex-grow: 1;
flex-wrap: wrap;
padding: 0 2px;
position: relative;
}
.vs__actions {
display: flex;
align-items: center;
padding: 4px 6px 0 3px;
}
/* Dropdown Toggle States */
.vs--searchable .vs__dropdown-toggle {
cursor: text;
}
.vs--unsearchable .vs__dropdown-toggle {
cursor: pointer;
}
.vs--open .vs__dropdown-toggle {
border-bottom-color: transparent;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
+29
View File
@@ -0,0 +1,29 @@
// Open Indicator
// The open indicator appears as a down facing
// caret on the right side of the select.
$transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
$transition-duration: 150ms;
$open-indicator-color: $vs-controls-color;
$open-indicator-size: $vs-controls-size;
.vs__open-indicator {
fill: $open-indicator-color;
transform: scale($open-indicator-size);
transition: transform $transition-duration $transition-timing-function;
transition-timing-function: $transition-timing-function;
}
// Open State
.vs--open .vs__open-indicator {
transform: rotate(180deg) scale($open-indicator-size);
}
// Loading State
.vs--loading .vs__open-indicator {
opacity: 0;
}
+54
View File
@@ -0,0 +1,54 @@
/* Search Input */
$line-height: $vs-component-line-height;
$font-size: 1em;
.vs__search::-webkit-search-decoration,
.vs__search::-webkit-search-cancel-button,
.vs__search::-webkit-search-results-button,
.vs__search::-webkit-search-results-decoration,
.vs__search::-ms-clear {
display: none;
}
.vs__search,
.vs__search:focus {
appearance: none;
line-height: $line-height;
font-size: $font-size;
border: 1px solid transparent;
border-left: none;
outline: none;
margin: 4px 0 0 0;
padding: 0 7px;
background: none;
box-shadow: none;
width: 0;
max-width: 100%;
flex-grow: 1;
}
.vs__search::placeholder {
color: $vs-component-placeholder-color;
}
/**
States
*/
// Unsearchable
.vs--unsearchable {
.vs__search {
opacity: 0;
&:hover {
cursor: pointer;
}
}
}
// Single, when searching but not loading or open
.vs--single.vs--searching:not(.vs--open):not(.vs--loading) {
.vs__search {
opacity: .2;
}
}
+40
View File
@@ -0,0 +1,40 @@
/* Selected Tags */
.vs__selected {
display: flex;
align-items: center;
background-color: $vs-selected-bg;
border: $vs-selected-border-width $vs-selected-border-style $vs-selected-border-color;
border-radius: $vs-border-radius;
color: map_get($vs-colors, 'dark');
line-height: $vs-component-line-height;
margin: 4px 2px 0px 2px;
padding: 0 0.25em;
}
.vs__deselect {
display: inline-flex;
appearance: none;
margin-left: 4px;
padding: 0;
border: 0;
cursor: pointer;
background: none;
fill: $vs-controls-color;
text-shadow: $vs-controls-deselect-text-shadow;
}
/* States */
.vs--single {
.vs__selected {
background-color: transparent;
border-color: transparent;
}
&.vs--open .vs__selected {
position: absolute;
opacity: .4;
}
&.vs--searching .vs__selected {
display: none;
}
}
+26
View File
@@ -0,0 +1,26 @@
/* Loading Spinner */
.vs__spinner {
align-self: center;
opacity: 0;
font-size: 5px;
text-indent: -9999em;
overflow: hidden;
border-top: .9em solid rgba(100, 100, 100, .1);
border-right: .9em solid rgba(100, 100, 100, .1);
border-bottom: .9em solid rgba(100, 100, 100, .1);
border-left: .9em solid rgba(60, 60, 60, .45);
transform: translateZ(0);
animation: vSelectSpinner 1.1s infinite linear;
transition: opacity .1s;
}
.vs__spinner,
.vs__spinner:after {
border-radius: 50%;
width: 5em;
height: 5em;
}
/* Loading Spinner States */
.vs--loading .vs__spinner {
opacity: 1;
}
+13
View File
@@ -0,0 +1,13 @@
@import "global/variables";
@import "global/component";
@import "global/animations";
@import "global/states";
@import "modules/dropdown-toggle";
@import "modules/open-indicator";
@import "modules/clear";
@import "modules/dropdown-menu";
@import "modules/dropdown-option";
@import "modules/selected";
@import "modules/search-input";
@import "modules/spinner";
+4 -4
View File
@@ -4,7 +4,7 @@ describe("Removing values", () => {
it("can remove the given tag when its close icon is clicked", () => {
const Select = selectWithProps({ multiple: true, value: ["foo"] });
Select.find(".close").trigger("click");
Select.find(".vs__deselect").trigger("click");
expect(Select.vm.mutableValue).toEqual([]);
});
@@ -16,7 +16,7 @@ describe("Removing values", () => {
disabled: true
});
Select.find(".close").trigger("click");
Select.find(".vs__deselect").trigger("click");
expect(Select.vm.mutableValue).toEqual(["one"]);
});
@@ -68,7 +68,7 @@ describe("Removing values", () => {
});
expect(Select.vm.mutableValue).toEqual("foo");
Select.find("button.clear").trigger("click");
Select.find("button.vs__clear").trigger("click");
expect(Select.vm.mutableValue).toEqual(null);
});
@@ -79,7 +79,7 @@ describe("Removing values", () => {
disabled: true
});
expect(Select.find("button.clear").attributes().disabled).toEqual(
expect(Select.find("button.vs__clear").attributes().disabled).toEqual(
"disabled"
);
});
+3 -3
View File
@@ -23,7 +23,7 @@ describe("Toggling Dropdown", () => {
options: [{ label: "one" }]
});
const selectedTag = Select.find(".selected-tag").element;
const selectedTag = Select.find(".vs__selected").element;
Select.vm.toggleDropdown({ target: selectedTag });
expect(Select.vm.open).toEqual(true);
@@ -127,9 +127,9 @@ describe("Toggling Dropdown", () => {
it("should have an open class when dropdown is active", () => {
const Select = selectWithProps();
expect(Select.vm.dropdownClasses.open).toEqual(false);
expect(Select.vm.stateClasses['vs--open']).toEqual(false);
Select.vm.open = true;
expect(Select.vm.dropdownClasses.open).toEqual(true);
expect(Select.vm.stateClasses['vs--open']).toEqual(true);
});
});
+1 -1
View File
@@ -9,7 +9,7 @@ describe("Labels", () => {
label: "name",
value: { name: "Foo" }
});
expect(Select.find(".selected-tag").text()).toBe("Foo");
expect(Select.find(".vs__selected").text()).toBe("Foo");
});
it("will console.warn when options contain objects without a valid label key", () => {
+1 -1
View File
@@ -137,7 +137,7 @@ describe("When index prop is defined", () => {
}
});
expect(Select.find(".selected-tag").text()).toContain("Baz");
expect(Select.find(".vs__selected").text()).toContain("Baz");
});
it("will console.warn when attempting to select an option with an undefined index", () => {
+187 -671
View File
File diff suppressed because it is too large Load Diff