Merge remote-tracking branch 'upstream/main'

This commit is contained in:
2026-08-26 09:31:34 +08:00
parent 9b8c4bfab6
commit 394eb0285d
34137 changed files with 3589424 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2017-present Riophae Lee
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+172
View File
@@ -0,0 +1,172 @@
# vue-treeselect
[![npm](https://badgen.now.sh/npm/v/@riophae/vue-treeselect)](https://www.npmjs.com/package/@riophae/vue-treeselect) [![Build](https://badgen.now.sh/circleci/github/riophae/vue-treeselect)](https://circleci.com/gh/riophae/vue-treeselect/tree/master) [![Coverage](https://badgen.net/codecov/c/github/riophae/vue-treeselect)](https://codecov.io/gh/riophae/vue-treeselect?branch=master)
![npm monthly downloads](https://badgen.now.sh/npm/dm/@riophae/vue-treeselect)
![jsDelivr monthly hits](https://badgen.net/jsdelivr/hits/npm/@riophae/vue-treeselect) [![Known vulnerabilities](https://snyk.io/test/npm/@riophae/vue-treeselect/badge.svg)](https://snyk.io/test/npm/@riophae/vue-treeselect) ![License](https://badgen.net/github/license/riophae/vue-treeselect)
> A multi-select component with nested options support for Vue.js
![Vue-Treeselect Screenshot](https://raw.githubusercontent.com/riophae/vue-treeselect/master/screenshot.png)
### Features
- Single & multiple select with nested options support
- Fuzzy matching
- Async searching
- Delayed loading (load data of deep level options only when needed)
- Keyboard support (navigate using <kbd>Arrow Up</kbd> & <kbd>Arrow Down</kbd> keys, select option using <kbd>Enter</kbd> key, etc.)
- Rich options & highly customizable
- Supports a wide range of browsers (see [below](#browser-compatibility))
- RTL support
*Requires Vue 2.2+*
### Getting Started
It's recommended to install vue-treeselect via npm, and build your app using a bundler like [webpack](https://webpack.js.org/).
```bash
npm install --save @riophae/vue-treeselect
```
This example shows how to integrate vue-treeselect with your [Vue SFCs](https://vuejs.org/v2/guide/single-file-components.html).
```vue
<!-- Vue SFC -->
<template>
<div id="app">
<treeselect v-model="value" :multiple="true" :options="options" />
</div>
</template>
<script>
// import the component
import Treeselect from '@riophae/vue-treeselect'
// import the styles
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
export default {
// register the component
components: { Treeselect },
data() {
return {
// define the default value
value: null,
// define options
options: [ {
id: 'a',
label: 'a',
children: [ {
id: 'aa',
label: 'aa',
}, {
id: 'ab',
label: 'ab',
} ],
}, {
id: 'b',
label: 'b',
}, {
id: 'c',
label: 'c',
} ],
}
},
}
</script>
```
If you just don't want to use webpack or any other bundlers, you can simply include the standalone UMD build in your page. In this way, make sure Vue as a dependency is included before vue-treeselect.
```html
<html>
<head>
<!-- include Vue 2.x -->
<script src="https://cdn.jsdelivr.net/npm/vue@^2"></script>
<!-- include vue-treeselect & its styles. you can change the version tag to better suit your needs. -->
<script src="https://cdn.jsdelivr.net/npm/@riophae/vue-treeselect@^0.4.0/dist/vue-treeselect.umd.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@riophae/vue-treeselect@^0.4.0/dist/vue-treeselect.min.css">
</head>
<body>
<div id="app">
<treeselect v-model="value" :multiple="true" :options="options" />
</div>
</body>
<script>
// register the component
Vue.component('treeselect', VueTreeselect.Treeselect)
new Vue({
el: '#app',
data: {
// define the default value
value: null,
// define options
options: [ {
id: 'a',
label: 'a',
children: [ {
id: 'aa',
label: 'aa',
}, {
id: 'ab',
label: 'ab',
} ],
}, {
id: 'b',
label: 'b',
}, {
id: 'c',
label: 'c',
} ],
},
})
</script>
</html>
```
### Documentation & Live Demo
[Visit the website](https://vue-treeselect.js.org/)
Note: please use a desktop browser since the website hasn't been optimized for mobile devices.
### Browser Compatibility
- Chrome
- Edge
- Firefox
- IE ≥ 9
- Safari
It should function well on IE9, but the style can be slightly broken due to the lack of support of some relatively new CSS features, such as `transition` and `animation`. Nevertheless it should look 90% same as on modern browsers.
### Bugs
You can use this [pen](https://codepen.io/riophae/pen/MExgzP) to reproduce bugs and then [open an issue](https://github.com/riophae/vue-treeselect/issues/new).
### Contributing
1. Fork & clone the repo
2. Install dependencies by `yarn` or `npm install`
3. Check out a new branch
4. `npm run dev` & hack
5. Make sure `npm test` passes
6. Push your changes & file a pull request
### Credits
This project is inspired by [vue-multiselect](https://github.com/monterail/vue-multiselect), [react-select](https://github.com/JedWatson/react-select) and [Ant Design](https://github.com/ant-design/ant-design/). Special thanks go to their respective authors!
Some icons used in this project:
- "link" icon made by [Smashicons](https://www.flaticon.com/authors/smashicons) is licensed under [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/)
- "spinner" icon from [SpinKit](https://github.com/tobiasahlin/SpinKit) is licensed under the [MIT License](https://github.com/tobiasahlin/SpinKit/blob/master/LICENSE)
- "caret" icon made by [Dave Gandy](https://www.flaticon.com/authors/dave-gandy) is licensed under [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/)
- "delete" icon made by [Freepik](https://www.flaticon.com/authors/freepik) is licensed under [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/)
- "checkmark symbol" & "minus symbol" icons made by [Catalin Fertu](https://www.flaticon.com/authors/catalin-fertu) are licensed under [CC 3.0 BY](https://creativecommons.org/licenses/by/3.0/)
### License
Copyright (c) 2017-present [Riophae Lee](https://github.com/riophae).
Released under the [MIT License](https://github.com/riophae/vue-treeselect/blob/master/LICENSE).
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+954
View File
@@ -0,0 +1,954 @@
/*!
* vue-treeselect v0.4.0 | (c) 2017-2019 Riophae Lee
* Released under the MIT License.
* https://vue-treeselect.js.org/
*/
/**
* Dependencies
*/
/**
* Variables
*/
/**
* Mixins
*/
/**
* Helpers
*/
.vue-treeselect-helper-hide {
display: none;
}
.vue-treeselect-helper-zoom-effect-off {
-ms-transform: none !important;
transform: none !important;
}
/**
* Animations
*/
@keyframes vue-treeselect-animation-fade-in {
0% {
opacity: 0;
}
}
@keyframes vue-treeselect-animation-bounce {
0%,
100% {
transform: scale(0);
}
50% {
transform: scale(1);
}
}
@keyframes vue-treeselect-animation-rotate {
100% {
transform: rotate(360deg);
}
}
/**
* Transitions
*/
.vue-treeselect__multi-value-item--transition-enter-active,
.vue-treeselect__multi-value-item--transition-leave-active {
transition-duration: 200ms;
transition-property: transform, opacity;
}
.vue-treeselect__multi-value-item--transition-enter-active {
transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
}
.vue-treeselect__multi-value-item--transition-leave-active {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
position: absolute;
}
.vue-treeselect__multi-value-item--transition-enter,
.vue-treeselect__multi-value-item--transition-leave-to {
-ms-transform: scale(0.7);
transform: scale(0.7);
opacity: 0;
}
.vue-treeselect__multi-value-item--transition-move {
transition: 200ms transform cubic-bezier(0.165, 0.84, 0.44, 1);
}
/**
* Namespace
*/
.vue-treeselect {
position: relative;
text-align: left;
}
[dir="rtl"] .vue-treeselect {
text-align: right;
}
.vue-treeselect div,
.vue-treeselect span {
box-sizing: border-box;
}
.vue-treeselect svg {
fill: currentColor;
}
/**
* Control
*/
.vue-treeselect__control {
padding-left: 5px;
padding-right: 5px;
display: table;
table-layout: fixed;
width: 100%;
height: 36px;
border: 1px solid #ddd;
border-radius: 5px;
background: #fff;
transition-duration: 200ms;
transition-property: border-color, box-shadow, width, height, background-color, opacity;
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
.vue-treeselect:not(.vue-treeselect--disabled):not(.vue-treeselect--focused) .vue-treeselect__control:hover {
border-color: #cfcfcf;
}
.vue-treeselect--focused:not(.vue-treeselect--open) .vue-treeselect__control {
border-color: #039be5;
box-shadow: 0 0 0 3px rgba(3, 155, 229, 0.1);
}
.vue-treeselect--disabled .vue-treeselect__control {
background-color: #f9f9f9;
}
.vue-treeselect--open .vue-treeselect__control {
border-color: #cfcfcf;
}
.vue-treeselect--open.vue-treeselect--open-below .vue-treeselect__control {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.vue-treeselect--open.vue-treeselect--open-above .vue-treeselect__control {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.vue-treeselect__value-container,
.vue-treeselect__multi-value {
width: 100%;
vertical-align: middle;
}
.vue-treeselect__value-container {
display: table-cell;
position: relative;
}
.vue-treeselect--searchable:not(.vue-treeselect--disabled) .vue-treeselect__value-container {
cursor: text;
}
.vue-treeselect__multi-value {
display: inline-block;
}
.vue-treeselect--has-value .vue-treeselect__multi-value {
margin-bottom: 5px;
}
.vue-treeselect__placeholder,
.vue-treeselect__single-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 5px;
padding-right: 5px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
line-height: 34px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
}
.vue-treeselect__placeholder {
color: #bdbdbd;
}
.vue-treeselect__single-value {
color: #333;
}
.vue-treeselect--focused.vue-treeselect--searchable .vue-treeselect__single-value {
color: #bdbdbd;
}
.vue-treeselect--disabled .vue-treeselect__single-value {
position: static;
}
.vue-treeselect__multi-value-item-container {
display: inline-block;
padding-top: 5px;
padding-right: 5px;
vertical-align: top;
}
[dir="rtl"] .vue-treeselect__multi-value-item-container {
padding-right: 0;
padding-left: 5px;
}
.vue-treeselect__multi-value-item {
cursor: pointer;
display: inline-table;
background: #e3f2fd;
padding: 2px 0;
border: 1px solid transparent;
border-radius: 2px;
color: #039be5;
font-size: 12px;
vertical-align: top;
}
.vue-treeselect:not(.vue-treeselect--disabled) .vue-treeselect__multi-value-item:not(.vue-treeselect__multi-value-item-disabled):hover .vue-treeselect__multi-value-item:not(.vue-treeselect__multi-value-item-new) .vue-treeselect__multi-value-item:not(.vue-treeselect__multi-value-item-new):hover {
cursor: pointer;
background: #e3f2fd;
color: #039be5;
}
.vue-treeselect__multi-value-item.vue-treeselect__multi-value-item-disabled {
cursor: default;
background: #f5f5f5;
color: #757575;
}
.vue-treeselect--disabled .vue-treeselect__multi-value-item {
cursor: default;
background: #fff;
border-color: #e5e5e5;
color: #555;
}
.vue-treeselect__multi-value-item.vue-treeselect__multi-value-item-new {
background: #e8f5e9;
}
.vue-treeselect__multi-value-item.vue-treeselect__multi-value-item-new:hover {
background: #e8f5e9;
}
.vue-treeselect__value-remove,
.vue-treeselect__multi-value-label {
display: table-cell;
padding: 0 5px;
vertical-align: middle;
}
.vue-treeselect__value-remove {
color: #039be5;
padding-left: 5px;
border-left: 1px solid #fff;
line-height: 0;
}
[dir="rtl"] .vue-treeselect__value-remove {
border-left: 0 none;
border-right: 1px solid #fff;
}
.vue-treeselect__multi-value-item:hover .vue-treeselect__value-remove {
color: #e53935;
}
.vue-treeselect--disabled .vue-treeselect__value-remove,
.vue-treeselect__multi-value-item-disabled .vue-treeselect__value-remove {
display: none;
}
.vue-treeselect__value-remove > svg {
width: 6px;
height: 6px;
}
.vue-treeselect__multi-value-label {
padding-right: 5px;
white-space: pre-line;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.vue-treeselect__limit-tip {
display: inline-block;
padding-top: 5px;
padding-right: 5px;
vertical-align: top;
}
[dir="rtl"] .vue-treeselect__limit-tip {
padding-right: 0;
padding-left: 5px;
}
.vue-treeselect__limit-tip-text {
cursor: default;
display: block;
margin: 2px 0;
padding: 1px 0;
color: #bdbdbd;
font-size: 12px;
font-weight: 600;
}
.vue-treeselect__input-container {
display: block;
max-width: 100%;
outline: none;
}
.vue-treeselect--single .vue-treeselect__input-container {
font-size: inherit;
height: 100%;
}
.vue-treeselect--multi .vue-treeselect__input-container {
display: inline-block;
font-size: 12px;
vertical-align: top;
}
.vue-treeselect--searchable .vue-treeselect__input-container {
padding-left: 5px;
padding-right: 5px;
}
.vue-treeselect--searchable.vue-treeselect--multi.vue-treeselect--has-value .vue-treeselect__input-container {
padding-top: 5px;
padding-left: 0;
}
[dir="rtl"] .vue-treeselect--searchable.vue-treeselect--multi.vue-treeselect--has-value .vue-treeselect__input-container {
padding-left: 5px;
padding-right: 0;
}
.vue-treeselect--disabled .vue-treeselect__input-container {
display: none;
}
.vue-treeselect__input,
.vue-treeselect__sizer {
margin: 0;
line-height: inherit;
font-family: inherit;
font-size: inherit;
}
.vue-treeselect__input {
max-width: 100%;
margin: 0;
padding: 0;
border: 0;
outline: none;
box-sizing: content-box;
box-shadow: none;
background: none transparent;
line-height: 1;
vertical-align: middle;
}
.vue-treeselect__input::-ms-clear {
display: none;
}
.vue-treeselect--single .vue-treeselect__input {
width: 100%;
height: 100%;
}
.vue-treeselect--multi .vue-treeselect__input {
padding-top: 3px;
padding-bottom: 3px;
}
.vue-treeselect--has-value .vue-treeselect__input {
line-height: inherit;
vertical-align: top;
}
.vue-treeselect__sizer {
position: absolute;
top: 0;
left: 0;
visibility: hidden;
height: 0;
overflow: scroll;
white-space: pre;
}
.vue-treeselect__x-container {
display: table-cell;
vertical-align: middle;
width: 20px;
text-align: center;
line-height: 0;
cursor: pointer;
color: #ccc;
animation: 200ms vue-treeselect-animation-fade-in cubic-bezier(0.075, 0.82, 0.165, 1);
}
.vue-treeselect__x-container:hover {
color: #e53935;
}
.vue-treeselect__x {
width: 8px;
height: 8px;
}
.vue-treeselect__control-arrow-container {
display: table-cell;
vertical-align: middle;
width: 20px;
text-align: center;
line-height: 0;
cursor: pointer;
}
.vue-treeselect--disabled .vue-treeselect__control-arrow-container {
cursor: default;
}
.vue-treeselect__control-arrow {
width: 9px;
height: 9px;
color: #ccc;
}
.vue-treeselect:not(.vue-treeselect--disabled) .vue-treeselect__control-arrow-container:hover .vue-treeselect__control-arrow {
color: #616161;
}
.vue-treeselect--disabled .vue-treeselect__control-arrow {
opacity: 0.35;
}
.vue-treeselect__control-arrow--rotated {
-ms-transform: rotate(180deg);
transform: rotateZ(180deg);
}
/**
* Menu
*/
.vue-treeselect__menu-container {
position: absolute;
left: 0;
width: 100%;
overflow: visible;
transition: 0s;
}
.vue-treeselect--open-below:not(.vue-treeselect--append-to-body) .vue-treeselect__menu-container {
top: 100%;
}
.vue-treeselect--open-above:not(.vue-treeselect--append-to-body) .vue-treeselect__menu-container {
bottom: 100%;
}
.vue-treeselect__menu {
cursor: default;
padding-top: 5px;
padding-bottom: 5px;
display: block;
position: absolute;
overflow-x: hidden;
overflow-y: auto;
width: auto;
border: 1px solid #cfcfcf;
background: #fff;
line-height: 180%;
-webkit-overflow-scrolling: touch;
}
.vue-treeselect--open-below .vue-treeselect__menu {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
top: 0;
margin-top: -1px;
border-top-color: #f2f2f2;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.vue-treeselect--open-above .vue-treeselect__menu {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
bottom: 0;
margin-bottom: -1px;
border-bottom-color: #f2f2f2;
}
.vue-treeselect__indent-level-0 .vue-treeselect__option {
padding-left: 5px;
}
[dir="rtl"] .vue-treeselect__indent-level-0 .vue-treeselect__option {
padding-left: 5px;
padding-right: 5px;
}
.vue-treeselect__indent-level-0 .vue-treeselect__tip {
padding-left: 25px;
}
[dir="rtl"] .vue-treeselect__indent-level-0 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 25px;
}
.vue-treeselect__indent-level-1 .vue-treeselect__option {
padding-left: 25px;
}
[dir="rtl"] .vue-treeselect__indent-level-1 .vue-treeselect__option {
padding-left: 5px;
padding-right: 25px;
}
.vue-treeselect__indent-level-1 .vue-treeselect__tip {
padding-left: 45px;
}
[dir="rtl"] .vue-treeselect__indent-level-1 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 45px;
}
.vue-treeselect__indent-level-2 .vue-treeselect__option {
padding-left: 45px;
}
[dir="rtl"] .vue-treeselect__indent-level-2 .vue-treeselect__option {
padding-left: 5px;
padding-right: 45px;
}
.vue-treeselect__indent-level-2 .vue-treeselect__tip {
padding-left: 65px;
}
[dir="rtl"] .vue-treeselect__indent-level-2 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 65px;
}
.vue-treeselect__indent-level-3 .vue-treeselect__option {
padding-left: 65px;
}
[dir="rtl"] .vue-treeselect__indent-level-3 .vue-treeselect__option {
padding-left: 5px;
padding-right: 65px;
}
.vue-treeselect__indent-level-3 .vue-treeselect__tip {
padding-left: 85px;
}
[dir="rtl"] .vue-treeselect__indent-level-3 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 85px;
}
.vue-treeselect__indent-level-4 .vue-treeselect__option {
padding-left: 85px;
}
[dir="rtl"] .vue-treeselect__indent-level-4 .vue-treeselect__option {
padding-left: 5px;
padding-right: 85px;
}
.vue-treeselect__indent-level-4 .vue-treeselect__tip {
padding-left: 105px;
}
[dir="rtl"] .vue-treeselect__indent-level-4 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 105px;
}
.vue-treeselect__indent-level-5 .vue-treeselect__option {
padding-left: 105px;
}
[dir="rtl"] .vue-treeselect__indent-level-5 .vue-treeselect__option {
padding-left: 5px;
padding-right: 105px;
}
.vue-treeselect__indent-level-5 .vue-treeselect__tip {
padding-left: 125px;
}
[dir="rtl"] .vue-treeselect__indent-level-5 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 125px;
}
.vue-treeselect__indent-level-6 .vue-treeselect__option {
padding-left: 125px;
}
[dir="rtl"] .vue-treeselect__indent-level-6 .vue-treeselect__option {
padding-left: 5px;
padding-right: 125px;
}
.vue-treeselect__indent-level-6 .vue-treeselect__tip {
padding-left: 145px;
}
[dir="rtl"] .vue-treeselect__indent-level-6 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 145px;
}
.vue-treeselect__indent-level-7 .vue-treeselect__option {
padding-left: 145px;
}
[dir="rtl"] .vue-treeselect__indent-level-7 .vue-treeselect__option {
padding-left: 5px;
padding-right: 145px;
}
.vue-treeselect__indent-level-7 .vue-treeselect__tip {
padding-left: 165px;
}
[dir="rtl"] .vue-treeselect__indent-level-7 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 165px;
}
.vue-treeselect__indent-level-8 .vue-treeselect__option {
padding-left: 165px;
}
[dir="rtl"] .vue-treeselect__indent-level-8 .vue-treeselect__option {
padding-left: 5px;
padding-right: 165px;
}
.vue-treeselect__indent-level-8 .vue-treeselect__tip {
padding-left: 185px;
}
[dir="rtl"] .vue-treeselect__indent-level-8 .vue-treeselect__tip {
padding-left: 5px;
padding-right: 185px;
}
.vue-treeselect__option {
padding-left: 5px;
padding-right: 5px;
display: table;
table-layout: fixed;
width: 100%;
}
.vue-treeselect__option--highlight {
background: #f5f5f5;
}
.vue-treeselect--single .vue-treeselect__option--selected {
background: #e3f2fd;
font-weight: 600;
}
.vue-treeselect--single .vue-treeselect__option--selected:hover {
background: #e3f2fd;
}
.vue-treeselect__option--hide {
display: none;
}
.vue-treeselect__option-arrow-container,
.vue-treeselect__option-arrow-placeholder {
display: table-cell;
vertical-align: middle;
width: 20px;
text-align: center;
line-height: 0;
}
.vue-treeselect__option-arrow-container {
cursor: pointer;
}
.vue-treeselect__option-arrow {
display: inline-block;
width: 9px;
height: 9px;
color: #ccc;
vertical-align: middle;
transition: 200ms transform cubic-bezier(0.19, 1, 0.22, 1);
-ms-transform: rotate(-90deg);
transform: rotateZ(-90deg);
}
[dir="rtl"] .vue-treeselect__option-arrow {
-ms-transform: rotate(90deg);
transform: rotateZ(90deg);
}
.vue-treeselect__option-arrow-container:hover .vue-treeselect__option-arrow,
.vue-treeselect--branch-nodes-disabled .vue-treeselect__option:hover .vue-treeselect__option-arrow {
color: #616161;
}
.vue-treeselect__option-arrow--rotated {
-ms-transform: rotate(0);
transform: rotateZ(0);
}
[dir="rtl"] .vue-treeselect__option-arrow--rotated {
-ms-transform: rotate(0);
transform: rotateZ(0);
}
.vue-treeselect__option-arrow--rotated.vue-treeselect__option-arrow--prepare-enter {
-ms-transform: rotate(-90deg) !important;
transform: rotateZ(-90deg) !important;
}
[dir="rtl"] .vue-treeselect__option-arrow--rotated.vue-treeselect__option-arrow--prepare-enter {
-ms-transform: rotate(90deg) !important;
transform: rotateZ(90deg) !important;
}
.vue-treeselect__label-container {
display: table-cell;
vertical-align: middle;
cursor: pointer;
display: table;
width: 100%;
table-layout: fixed;
color: inherit;
}
.vue-treeselect__option--disabled .vue-treeselect__label-container {
cursor: not-allowed;
color: rgba(0, 0, 0, 0.25);
}
.vue-treeselect__checkbox-container {
display: table-cell;
width: 20px;
min-width: 20px;
height: 100%;
text-align: center;
vertical-align: middle;
}
.vue-treeselect__checkbox {
display: block;
margin: auto;
width: 12px;
height: 12px;
border-width: 1px;
border-style: solid;
border-radius: 2px;
position: relative;
transition: 200ms all cubic-bezier(0.075, 0.82, 0.165, 1);
}
.vue-treeselect__check-mark,
.vue-treeselect__minus-mark {
display: block;
position: absolute;
left: 1px;
top: 1px;
background-repeat: no-repeat;
opacity: 0;
transition: 200ms all ease;
}
.vue-treeselect__minus-mark {
width: 8px;
height: 8px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAgMAAAC5YVYYAAAACVBMVEUAAAD///////9zeKVjAAAAAnRSTlMAuLMp9oYAAAAPSURBVAjXY4CDrJUgBAMAGaECJ9dz3BAAAAAASUVORK5CYII=);
background-size: 8px 8px;
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {
.vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAAD///////////84wDuoAAAAA3RSTlMAyTzPIdReAAAAGUlEQVQI12PAD+b///+Nof7//79gAsLFCwAx/w4blADeeQAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAAD///////////84wDuoAAAAA3RSTlMAyTzPIdReAAAAGUlEQVQI12PAD+b///+Nof7//79gAsLFCwAx/w4blADeeQAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
.vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYBAMAAAASWSDLAAAAD1BMVEUAAAD///////////////+PQt5oAAAABHRSTlMAy2EFIuWxUgAAACRJREFUGNNjGBBgJOICBY7KDCoucODEAJSAS6FwUJShGjAQAADBPRGrK2/FhgAAAABJRU5ErkJggg==);
}
}
.vue-treeselect__checkbox--indeterminate > .vue-treeselect__minus-mark {
opacity: 1;
}
.vue-treeselect__checkbox--disabled .vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAgMAAAC5YVYYAAAACVBMVEUAAADi4uLh4eHOxeSRAAAAAnRSTlMAuLMp9oYAAAAPSURBVAjXY4CDrJUgBAMAGaECJ9dz3BAAAAAASUVORK5CYII=);
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {
.vue-treeselect__checkbox--disabled .vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAADi4uLi4uLh4eE5RQaIAAAAA3RSTlMAyTzPIdReAAAAGUlEQVQI12PAD+b///+Nof7//79gAsLFCwAx/w4blADeeQAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.vue-treeselect__checkbox--disabled .vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAADi4uLi4uLh4eE5RQaIAAAAA3RSTlMAyTzPIdReAAAAGUlEQVQI12PAD+b///+Nof7//79gAsLFCwAx/w4blADeeQAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
.vue-treeselect__checkbox--disabled .vue-treeselect__minus-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYBAMAAAASWSDLAAAAD1BMVEUAAADh4eHg4ODNzc3h4eEYfw2wAAAABHRSTlMAy2EFIuWxUgAAACRJREFUGNNjGBBgJOICBY7KDCoucODEAJSAS6FwUJShGjAQAADBPRGrK2/FhgAAAABJRU5ErkJggg==);
}
}
.vue-treeselect__check-mark {
width: 8px;
height: 8px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAQlBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////8IX9KGAAAAFXRSTlMA8u24NxILB+Tawb6jiH1zRz0xIQIIP3GUAAAAMklEQVQI1y3FtQEAMQDDQD+EGbz/qkEVOpyEOP6PudKjZNSXn4Jm2CKRdBKzSLsFWl8fMG0Bl6Jk1rMAAAAASUVORK5CYII=);
background-size: 8px 8px;
-ms-transform: scaleY(0.125);
transform: scaleY(0.125);
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {
.vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////98JRy6AAAAH3RSTlMAzu4sDenl38fBvo1OMyIdEQrj1cSihX5hYFpHNycIcQOASAAAAF9JREFUGNN9zEcOgDAMRFHTS0LvNfe/JRmHKAIJ/mqeLJn+k9uDtaeUeFnFziGsBucUTirrprfe81RqZ3Bb6hPWeuZwDFOHyf+ig9CCzQ7INBn7bG5kF+QSt13BHNJnF7AaCT4Y+CW7AAAAAElFTkSuQmCC);
}
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////98JRy6AAAAH3RSTlMAzu4sDenl38fBvo1OMyIdEQrj1cSihX5hYFpHNycIcQOASAAAAF9JREFUGNN9zEcOgDAMRFHTS0LvNfe/JRmHKAIJ/mqeLJn+k9uDtaeUeFnFziGsBucUTirrprfe81RqZ3Bb6hPWeuZwDFOHyf+ig9CCzQ7INBn7bG5kF+QSt13BHNJnF7AaCT4Y+CW7AAAAAElFTkSuQmCC);
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
.vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAWlBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////9ZMre9AAAAHXRSTlMA/PiJhGNI9XlEHJB/b2ldV08+Oibk49vPp6QhAYgGBuwAAACCSURBVCjPrdHdDoIwDAXgTWAqCigo/+f9X5OwnoUwtis4V92XNWladUl+rzQPeQJAN2EHxoOnsPn7/oYk8fxBv08Rr/deOH/aZ2Nm8ZJ+s573QGfWKnNuZGzWm3+lv2V3pcU1XQ385/yjmBoM3Z+dXvlbYLLD3ujhTaOM3KaIXvNkFkuSEvYy1LqOAAAAAElFTkSuQmCC);
}
}
.vue-treeselect__checkbox--checked > .vue-treeselect__check-mark {
opacity: 1;
-ms-transform: scaleY(1);
transform: scaleY(1);
}
.vue-treeselect__checkbox--disabled .vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAP1BMVEUAAADj4+Pf39/h4eHh4eHh4eHk5OTh4eHg4ODi4uLh4eHh4eHg4ODh4eHh4eHg4ODh4eHh4eHp6en////h4eFqcyvUAAAAFHRSTlMAOQfy7bgS5NrBvqOIfXNHMSELAgQ/iFsAAAA2SURBVAjXY4AANjYIzcjMAaVFuBkY+RkEWERYmRjYRXjANAOfiIgIFxNIAa8IpxBEi6AwiAQAK2MBd7xY8csAAAAASUVORK5CYII=);
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {
.vue-treeselect__checkbox--disabled .vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAXVBMVEUAAADh4eHh4eHh4eHi4uLb29vh4eHh4eHh4eHh4eHh4eHh4eHh4eHi4uLi4uLj4+Pi4uLk5OTo6Ojh4eHh4eHi4uLg4ODg4ODh4eHg4ODh4eHf39/g4OD////h4eEzIk+wAAAAHnRSTlMAzu6/LA3p5eLZx8ONTjYiHRIKooV+YWBaRzEnCANnm5rnAAAAZElEQVQY033P2wqAIAyA4VWaaWrnc/n+j5mbhBjUf7WPoTD47TJb4i5zTr/sRDRHuyFaoWX7uK/RlbctlPEuyI1f4WY9yQINEkf6rzzo8YIzmUFoCs7J1EjeIaa9bXIEmzl8dgOZEAj/+2IvzAAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
.vue-treeselect__checkbox--disabled .vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAXVBMVEUAAADh4eHh4eHh4eHi4uLb29vh4eHh4eHh4eHh4eHh4eHh4eHh4eHi4uLi4uLj4+Pi4uLk5OTo6Ojh4eHh4eHi4uLg4ODg4ODh4eHg4ODh4eHf39/g4OD////h4eEzIk+wAAAAHnRSTlMAzu6/LA3p5eLZx8ONTjYiHRIKooV+YWBaRzEnCANnm5rnAAAAZElEQVQY033P2wqAIAyA4VWaaWrnc/n+j5mbhBjUf7WPoTD47TJb4i5zTr/sRDRHuyFaoWX7uK/RlbctlPEuyI1f4WY9yQINEkf6rzzo8YIzmUFoCs7J1EjeIaa9bXIEmzl8dgOZEAj/+2IvzAAAAABJRU5ErkJggg==);
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
.vue-treeselect__checkbox--disabled .vue-treeselect__check-mark {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAUVBMVEUAAADh4eHh4eHh4eHh4eHi4uLi4uLh4eHh4eHh4eHf39/j4+Ph4eHh4eHh4eHg4ODi4uLh4eHh4eHi4uLh4eHh4eHh4eHh4eHh4eH////h4eF3FMFTAAAAGnRSTlMA+/eJhGhfSHE9JBzz5KaQf3pXT0Xbz0I5AYDw8F0AAAB+SURBVCjPrdHbDoMgEEVRKAii1dZe9fz/hxplTiKIT7qfYCWTEEZdUvOwbckNAD2WHeh3brHW5f5EzGQ+iN+b1Gt6KPvtv16Dn6JX9M9ya3/A1yfu5dlyduL6Hec7mXY6ddXLPP2lpABGZ8PWXfYLTJxZekVhhl7eTX24zZPNKXoRC7zQLjUAAAAASUVORK5CYII=);
}
}
.vue-treeselect__checkbox--unchecked {
border-color: #e0e0e0;
background: #fff;
}
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--unchecked {
border-color: #039be5;
background: #fff;
}
.vue-treeselect__checkbox--indeterminate {
border-color: #039be5;
background: #039be5;
}
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--indeterminate {
border-color: #039be5;
background: #039be5;
}
.vue-treeselect__checkbox--checked {
border-color: #039be5;
background: #039be5;
}
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--checked {
border-color: #039be5;
background: #039be5;
}
.vue-treeselect__checkbox--disabled {
border-color: #e0e0e0;
background-color: #f7f7f7;
}
.vue-treeselect__label-container:hover .vue-treeselect__checkbox--disabled {
border-color: #e0e0e0;
background-color: #f7f7f7;
}
.vue-treeselect__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: table-cell;
padding-left: 5px;
max-width: 100%;
vertical-align: middle;
cursor: inherit;
}
[dir="rtl"] .vue-treeselect__label {
padding-left: 0;
padding-right: 5px;
}
.vue-treeselect__count {
margin-left: 5px;
font-weight: 400;
opacity: 0.6;
}
[dir="rtl"] .vue-treeselect__count {
margin-left: 0;
margin-right: 5px;
}
.vue-treeselect__tip {
padding-left: 5px;
padding-right: 5px;
display: table;
table-layout: fixed;
width: 100%;
color: #757575;
}
.vue-treeselect__tip-text {
display: table-cell;
vertical-align: middle;
padding-left: 5px;
padding-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
font-size: 12px;
}
.vue-treeselect__error-tip .vue-treeselect__retry {
cursor: pointer;
margin-left: 5px;
font-style: normal;
font-weight: 600;
text-decoration: none;
color: #039be5;
}
[dir="rtl"] .vue-treeselect__error-tip .vue-treeselect__retry {
margin-left: 0;
margin-right: 5px;
}
.vue-treeselect__icon-container {
display: table-cell;
vertical-align: middle;
width: 20px;
text-align: center;
line-height: 0;
}
.vue-treeselect--single .vue-treeselect__icon-container {
padding-left: 5px;
}
[dir="rtl"] .vue-treeselect--single .vue-treeselect__icon-container {
padding-left: 0;
padding-right: 5px;
}
.vue-treeselect__icon-warning {
display: block;
margin: auto;
border-radius: 50%;
position: relative;
width: 12px;
height: 12px;
background: #fb8c00;
}
.vue-treeselect__icon-warning::after {
display: block;
position: absolute;
content: "";
left: 5px;
top: 2.5px;
width: 2px;
height: 1px;
border: 0 solid #fff;
border-top-width: 5px;
border-bottom-width: 1px;
}
.vue-treeselect__icon-error {
display: block;
margin: auto;
border-radius: 50%;
position: relative;
width: 12px;
height: 12px;
background: #e53935;
}
.vue-treeselect__icon-error::before,
.vue-treeselect__icon-error::after {
display: block;
position: absolute;
content: "";
background: #fff;
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.vue-treeselect__icon-error::before {
width: 6px;
height: 2px;
left: 3px;
top: 5px;
}
.vue-treeselect__icon-error::after {
width: 2px;
height: 6px;
left: 5px;
top: 3px;
}
.vue-treeselect__icon-loader {
display: block;
margin: auto;
position: relative;
width: 12px;
height: 12px;
text-align: center;
animation: 1.6s vue-treeselect-animation-rotate linear infinite;
}
.vue-treeselect__icon-loader::before,
.vue-treeselect__icon-loader::after {
border-radius: 50%;
position: absolute;
content: "";
left: 0;
top: 0;
display: block;
width: 100%;
height: 100%;
opacity: 0.6;
animation: 1.6s vue-treeselect-animation-bounce ease-in-out infinite;
}
.vue-treeselect__icon-loader::before {
background: #039be5;
}
.vue-treeselect__icon-loader::after {
background: #b3e5fc;
animation-delay: -0.8s;
}
/**
* Menu Portal
*/
.vue-treeselect__menu-placeholder {
display: none;
}
.vue-treeselect__portal-target {
position: absolute;
display: block;
left: 0;
top: 0;
height: 0;
width: 0;
padding: 0;
margin: 0;
border: 0;
overflow: visible;
box-sizing: border-box;
}
/*# sourceMappingURL=vue-treeselect.css.map*/
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+142
View File
@@ -0,0 +1,142 @@
{
"name": "@riophae/vue-treeselect",
"version": "0.4.0",
"description": "A multi-select component with nested options support for Vue.js",
"author": "Riophae Lee <riophaelee@gmail.com>",
"license": "MIT",
"homepage": "https://vue-treeselect.js.org/",
"repository": "riophae/vue-treeselect",
"main": "dist/vue-treeselect.cjs.js",
"unpkg": "dist/vue-treeselect.umd.min.js",
"css": "dist/vue-treeselect.min.css",
"files": [
"src",
"dist"
],
"scripts": {
"dev": "node build/dev-server.js",
"build-library": "node build/build-library.js",
"build-docs": "rimraf gh-pages && mkdir gh-pages && node build/build-docs.js",
"gh-pages": "npm run build-docs && gh-pages --dist gh-pages --branch gh-pages --dotfiles",
"cleanup-cov": "rimraf test/unit/coverage",
"unit": "npm run cleanup-cov && karma start test/unit/karma.conf.js --watch",
"testonly": "npm run cleanup-cov && karma start test/unit/karma.config.js --single-run",
"test": "npm run testonly",
"pretest": "npm run lint",
"lint:js": "eslint --ext .js --ext .vue --cache --cache-location node_modules/.cache/eslint --rule 'no-console: 2' .",
"lint:css": "stylelint '**/*.less'",
"lint": "npm run lint:js && npm run lint:css",
"verify-builds": "size-limit && node build/verify-builds.js",
"finish": "npm test && npm run build-library && npm run verify-builds"
},
"pre-commit": "lint",
"dependencies": {
"@babel/runtime": "^7.3.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"easings-css": "^1.0.0",
"fuzzysearch": "^1.0.3",
"is-promise": "^2.1.0",
"lodash": "^4.0.0",
"material-colors": "^1.2.6",
"watch-size": "^2.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.3.1",
"@size-limit/preset-big-lib": "^2.0.2",
"@vue/test-utils": "1.0.0-beta.16",
"autoprefixer": "^9.4.6",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.0",
"babel-plugin-istanbul": "^5.0.1",
"babel-plugin-transform-vue-jsx": "^4.0.1",
"cache-loader": "^4.0.1",
"chalk": "^2.3.2",
"codecov": "^3.0.0",
"connect-history-api-fallback": "^1.5.0",
"copy-webpack-plugin": "^5.0.3",
"css-loader": "^3.0.0",
"entities": "^2.0.0",
"eslint": "^6.1.0",
"eslint-config-riophae": "^0.9.1",
"eslint-friendly-formatter": "^4.0.0",
"eslint-import-resolver-webpack": "^0.11.0",
"eslint-loader": "^3.0.0",
"eslint-plugin-import": "^2.15.0",
"eslint-plugin-react": "^7.10.0",
"eslint-plugin-unicorn": "^11.0.2",
"eslint-plugin-vue": "^5.1.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.16.3",
"friendly-errors-webpack-plugin": "^1.7.0",
"gh-pages": "^2.0.0",
"html-webpack-plugin": "^3.1.0",
"jasmine-core": "^3.1.0",
"jstransformer-markdown-it": "^2.0.0",
"karma": "^4.0.0",
"karma-chrome-launcher": "^3.0.0",
"karma-coverage": "^2.0.1",
"karma-jasmine": "^2.0.0",
"karma-jasmine-matchers": "^4.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^4.0.2",
"less": "^3.0.1",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"open": "^6.2.0",
"optimize-css-assets-webpack-plugin": "^5.0.0",
"ora": "^4.0.1",
"postcss-loader": "^3.0.0",
"pre-commit": "^1.2.2",
"pug": "^2.0.0",
"pug-loader": "^2.4.0",
"puppeteer": "^1.19.0",
"raw-loader": "^3.0.0",
"regenerator-runtime": "^0.13.1",
"rimraf": "^3.0.0",
"run-series": "^1.1.8",
"script-loader": "^0.7.2",
"shallow-equal": "^1.0.0",
"shelljs": "^0.8.1",
"string.prototype.repeat": "^0.2.0",
"strip-indent": "^3.0.0",
"stylelint": "^11.0.0",
"stylelint-config-xo-space": "^0.13.0",
"terser-webpack-plugin": "^2.1.0",
"url-loader": "^2.0.1",
"vodal": "^2.3.3",
"vue": "^2.2.0",
"vue-loader": "^15.6.0",
"vue-style-loader": "^4.0.2",
"vue-template-compiler": "^2.4.4",
"vuex": "^3.0.1",
"webpack": "^4.6.0",
"webpack-bundle-analyzer": "^3.0.2",
"webpack-cdn-plugin": "^3.1.4",
"webpack-dev-middleware": "^3.1.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0",
"webpack-node-externals": "^1.7.2",
"yaku": "^0.19.3"
},
"peerDependencies": {
"vue": "^2.2.0"
},
"keywords": [
"vue",
"component",
"tree",
"treeview",
"select",
"dropdown",
"treeselect",
"multiselect",
"form",
"control",
"input",
"ui"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

@@ -0,0 +1,153 @@
<script>
import { onLeftClick, isPromise } from '../utils'
import SingleValue from './SingleValue'
import MultiValue from './MultiValue'
import DeleteIcon from './icons/Delete'
import ArrowIcon from './icons/Arrow'
export default {
name: 'vue-treeselect--control',
inject: [ 'instance' ],
computed: {
/* eslint-disable valid-jsdoc */
/**
* Should show the "×" button that resets value?
* @return {boolean}
*/
shouldShowX() {
const { instance } = this
return (
instance.clearable &&
!instance.disabled &&
instance.hasValue &&
(this.hasUndisabledValue || instance.allowClearingDisabled)
)
},
/**
* Should show the arrow button that toggles menu?
* @return {boolean}
*/
shouldShowArrow() {
const { instance } = this
if (!instance.alwaysOpen) return true
// Even with `alwaysOpen: true`, sometimes the menu is still closed,
// e.g. when the control is disabled.
return !instance.menu.isOpen
},
/**
* Has any undisabled option been selected?
* @type {boolean}
*/
hasUndisabledValue() {
const { instance } = this
return (
instance.hasValue &&
instance.internalValue.some(id => !instance.getNode(id).isDisabled)
)
},
/* eslint-enable valid-jsdoc */
},
methods: {
renderX() {
const { instance } = this
const title = instance.multiple ? instance.clearAllText : instance.clearValueText
if (!this.shouldShowX) return null
return (
<div class="vue-treeselect__x-container" title={title} onMousedown={this.handleMouseDownOnX}>
<DeleteIcon class="vue-treeselect__x" />
</div>
)
},
renderArrow() {
const { instance } = this
const arrowClass = {
'vue-treeselect__control-arrow': true,
'vue-treeselect__control-arrow--rotated': instance.menu.isOpen,
}
if (!this.shouldShowArrow) return null
return (
<div class="vue-treeselect__control-arrow-container" onMousedown={this.handleMouseDownOnArrow}>
<ArrowIcon class={arrowClass} />
</div>
)
},
handleMouseDownOnX: onLeftClick(function handleMouseDownOnX(evt) {
/**
* We don't use async/await here because we don't want
* to rely on Babel polyfill or regenerator runtime.
* See: https://babeljs.io/docs/plugins/transform-regenerator/
* We also don't want to assume there is a global `Promise`
* class, since we are targeting to support IE9 without the
* need of any polyfill.
*/
evt.stopPropagation()
evt.preventDefault()
const { instance } = this
const result = instance.beforeClearAll()
const handler = shouldClear => {
if (shouldClear) instance.clear()
}
if (isPromise(result)) {
// The handler will be called async.
result.then(handler)
} else {
// Keep the same behavior here.
setTimeout(() => handler(result), 0)
// Also, note that IE9 requires:
// setTimeout(() => fn(...args), delay)
// Instead of:
// setTimeout(fn, delay, ...args)
}
}),
handleMouseDownOnArrow: onLeftClick(function handleMouseDownOnArrow(evt) {
evt.preventDefault()
evt.stopPropagation()
const { instance } = this
// Focus the input or prevent blurring.
instance.focusInput()
instance.toggleMenu()
}),
// This is meant to be called by child `<Value />` component.
renderValueContainer(children) {
return (
<div class="vue-treeselect__value-container">
{children}
</div>
)
},
},
render() {
const { instance } = this
const ValueContainer = instance.single ? SingleValue : MultiValue
return (
<div class="vue-treeselect__control" onMousedown={instance.handleMouseDown}>
<ValueContainer ref="value-container" />
{this.renderX()}
{this.renderArrow()}
</div>
)
},
}
</script>
@@ -0,0 +1,37 @@
<script>
import { isNaN } from '../utils'
function stringifyValue(value) {
if (typeof value === 'string') return value
// istanbul ignore else
if (value != null && !isNaN(value)) return JSON.stringify(value)
// istanbul ignore next
return ''
}
export default {
name: 'vue-treeselect--hidden-fields',
inject: [ 'instance' ],
functional: true,
render(_, context) {
const { instance } = context.injections
if (!instance.name || instance.disabled || !instance.hasValue) return null
let stringifiedValues = instance.internalValue.map(stringifyValue)
if (instance.multiple && instance.joinValues) stringifiedValues = [
stringifiedValues.join(instance.delimiter),
]
return stringifiedValues.map((stringifiedValue, i) => (
<input type="hidden"
name={instance.name}
value={stringifiedValue}
key={'hidden-field-' + i}
/>
))
},
}
</script>
+295
View File
@@ -0,0 +1,295 @@
<script>
import { debounce, deepExtend, includes } from '../utils'
import { MIN_INPUT_WIDTH, KEY_CODES, INPUT_DEBOUNCE_DELAY } from '../constants'
const keysThatRequireMenuBeingOpen = [
KEY_CODES.ENTER,
KEY_CODES.END,
KEY_CODES.HOME,
KEY_CODES.ARROW_LEFT,
KEY_CODES.ARROW_UP,
KEY_CODES.ARROW_RIGHT,
KEY_CODES.ARROW_DOWN,
]
export default {
name: 'vue-treeselect--input',
inject: [ 'instance' ],
data: () => ({
inputWidth: MIN_INPUT_WIDTH,
value: '',
}),
computed: {
needAutoSize() {
const { instance } = this
return (
instance.searchable &&
!instance.disabled &&
instance.multiple
)
},
inputStyle() {
return {
width: this.needAutoSize ? `${this.inputWidth}px` : null,
}
},
},
watch: {
'instance.trigger.searchQuery'(newValue) {
this.value = newValue
},
value() {
// istanbul ignore else
if (this.needAutoSize) this.$nextTick(this.updateInputWidth)
},
},
created() {
this.debouncedCallback = debounce(
this.updateSearchQuery,
INPUT_DEBOUNCE_DELAY,
{ leading: true, trailing: true },
)
},
methods: {
clear() {
this.onInput({
target: { value: '' },
})
},
focus() {
const { instance } = this
if (!instance.disabled) {
this.$refs.input && this.$refs.input.focus()
}
},
blur() {
this.$refs.input && this.$refs.input.blur()
},
onFocus() {
const { instance } = this
instance.trigger.isFocused = true
// istanbul ignore else
if (instance.openOnFocus) instance.openMenu()
},
onBlur() {
const { instance } = this
const menu = instance.getMenu()
// #15
// istanbul ignore next
if (menu && document.activeElement === menu) {
return this.focus()
}
instance.trigger.isFocused = false
instance.closeMenu()
},
onInput(evt) {
const { value } = evt.target
this.value = value
if (value) {
this.debouncedCallback()
} else {
this.debouncedCallback.cancel()
this.updateSearchQuery()
}
},
// 用 keyUp 事件存在一个问题,删除输入框最后一个字符也会导致取消选中最后一项
onKeyDown(evt) {
const { instance } = this
// https://css-tricks.com/snippets/javascript/javascript-keycodes/
// https://stackoverflow.com/questions/4471582/javascript-keycode-vs-which
const key = 'which' in evt ? evt.which : /* istanbul ignore next */ evt.keyCode
if (evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey)
return
if (!instance.menu.isOpen && includes(keysThatRequireMenuBeingOpen, key)) {
evt.preventDefault()
return instance.openMenu()
}
switch (key) {
case KEY_CODES.BACKSPACE: {
if (instance.backspaceRemoves && !this.value.length) {
instance.removeLastValue()
}
break
}
case KEY_CODES.ENTER: {
evt.preventDefault()
if (instance.menu.current === null) return
const current = instance.getNode(instance.menu.current)
if (current.isBranch && instance.disableBranchNodes) return
instance.select(current)
break
}
case KEY_CODES.ESCAPE: {
if (this.value.length) {
this.clear()
} else if (instance.menu.isOpen) {
instance.closeMenu()
}
break
}
case KEY_CODES.END: {
evt.preventDefault()
instance.highlightLastOption()
break
}
case KEY_CODES.HOME: {
evt.preventDefault()
instance.highlightFirstOption()
break
}
case KEY_CODES.ARROW_LEFT: {
const current = instance.getNode(instance.menu.current)
if (current.isBranch && instance.shouldExpand(current)) {
evt.preventDefault()
instance.toggleExpanded(current)
} else if (!current.isRootNode && (current.isLeaf || (current.isBranch && !(instance.shouldExpand(current))))) {
evt.preventDefault()
instance.setCurrentHighlightedOption(current.parentNode)
}
break
}
case KEY_CODES.ARROW_UP: {
evt.preventDefault()
instance.highlightPrevOption()
break
}
case KEY_CODES.ARROW_RIGHT: {
const current = instance.getNode(instance.menu.current)
if (current.isBranch && !instance.shouldExpand(current)) {
evt.preventDefault()
instance.toggleExpanded(current)
}
break
}
case KEY_CODES.ARROW_DOWN: {
evt.preventDefault()
instance.highlightNextOption()
break
}
case KEY_CODES.DELETE: {
if (instance.deleteRemoves && !this.value.length) {
instance.removeLastValue()
}
break
}
default: {
// istanbul ignore else
instance.openMenu()
}
}
},
onMouseDown(evt) {
// istanbul ignore next
if (this.value.length) {
// Prevent it from bubbling to the top level and triggering `preventDefault()`
// to make the textbox unselectable
evt.stopPropagation()
}
},
renderInputContainer() {
const { instance } = this
const props = {}
const children = []
if (instance.searchable && !instance.disabled) {
children.push(this.renderInput())
if (this.needAutoSize) children.push(this.renderSizer())
}
if (!instance.searchable) {
deepExtend(props, {
on: {
focus: this.onFocus,
blur: this.onBlur,
keydown: this.onKeyDown,
},
ref: 'input',
})
}
if (!instance.searchable && !instance.disabled) {
deepExtend(props, {
attrs: {
tabIndex: instance.tabIndex,
},
})
}
return (
<div class="vue-treeselect__input-container" {...props}>
{children}
</div>
)
},
renderInput() {
const { instance } = this
return (
<input ref="input"
class="vue-treeselect__input"
type="text"
autocomplete="off"
tabIndex={instance.tabIndex}
required={instance.required && !instance.hasValue}
value={this.value}
style={this.inputStyle}
onFocus={this.onFocus}
onInput={this.onInput}
onBlur={this.onBlur}
onKeydown={this.onKeyDown}
onMousedown={this.onMouseDown}
/>
)
},
renderSizer() {
return (
<div ref="sizer" class="vue-treeselect__sizer">{this.value}</div>
)
},
updateInputWidth() {
this.inputWidth = Math.max(
MIN_INPUT_WIDTH,
this.$refs.sizer.scrollWidth + 15,
)
},
updateSearchQuery() {
const { instance } = this
instance.trigger.searchQuery = this.value
},
},
render() {
return this.renderInputContainer()
},
}
</script>
+313
View File
@@ -0,0 +1,313 @@
<script>
import { MENU_BUFFER } from '../constants'
import { watchSize, setupResizeAndScrollEventListeners } from '../utils'
import Option from './Option'
import Tip from './Tip'
const directionMap = {
top: 'top',
bottom: 'bottom',
above: 'top',
below: 'bottom',
}
export default {
name: 'vue-treeselect--menu',
inject: [ 'instance' ],
computed: {
menuStyle() {
const { instance } = this
return {
maxHeight: instance.maxHeight + 'px',
}
},
menuContainerStyle() {
const { instance } = this
return {
zIndex: instance.appendToBody ? null : instance.zIndex,
}
},
},
watch: {
'instance.menu.isOpen'(newValue) {
if (newValue) {
// In case `openMenu()` is just called and the menu is not rendered yet.
this.$nextTick(this.onMenuOpen)
} else {
this.onMenuClose()
}
},
},
created() {
this.menuSizeWatcher = null
this.menuResizeAndScrollEventListeners = null
},
mounted() {
const { instance } = this
if (instance.menu.isOpen) this.$nextTick(this.onMenuOpen)
},
destroyed() {
this.onMenuClose()
},
methods: {
renderMenu() {
const { instance } = this
if (!instance.menu.isOpen) return null
return (
<div ref="menu" class="vue-treeselect__menu" onMousedown={instance.handleMouseDown} style={this.menuStyle}>
{this.renderBeforeList()}
{instance.async
? this.renderAsyncSearchMenuInner()
: instance.localSearch.active
? this.renderLocalSearchMenuInner()
: this.renderNormalMenuInner()}
{this.renderAfterList()}
</div>
)
},
renderBeforeList() {
const { instance } = this
const beforeListRenderer = instance.$scopedSlots['before-list']
return beforeListRenderer
? beforeListRenderer()
: null
},
renderAfterList() {
const { instance } = this
const afterListRenderer = instance.$scopedSlots['after-list']
return afterListRenderer
? afterListRenderer()
: null
},
renderNormalMenuInner() {
const { instance } = this
if (instance.rootOptionsStates.isLoading) {
return this.renderLoadingOptionsTip()
} else if (instance.rootOptionsStates.loadingError) {
return this.renderLoadingRootOptionsErrorTip()
} else if (instance.rootOptionsStates.isLoaded && instance.forest.normalizedOptions.length === 0) {
return this.renderNoAvailableOptionsTip()
} else {
return this.renderOptionList()
}
},
renderLocalSearchMenuInner() {
const { instance } = this
if (instance.rootOptionsStates.isLoading) {
return this.renderLoadingOptionsTip()
} else if (instance.rootOptionsStates.loadingError) {
return this.renderLoadingRootOptionsErrorTip()
} else if (instance.rootOptionsStates.isLoaded && instance.forest.normalizedOptions.length === 0) {
return this.renderNoAvailableOptionsTip()
} else if (instance.localSearch.noResults) {
return this.renderNoResultsTip()
} else {
return this.renderOptionList()
}
},
renderAsyncSearchMenuInner() {
const { instance } = this
const entry = instance.getRemoteSearchEntry()
const shouldShowSearchPromptTip = instance.trigger.searchQuery === '' && !instance.defaultOptions
const shouldShowNoResultsTip = shouldShowSearchPromptTip
? false
: entry.isLoaded && entry.options.length === 0
if (shouldShowSearchPromptTip) {
return this.renderSearchPromptTip()
} else if (entry.isLoading) {
return this.renderLoadingOptionsTip()
} else if (entry.loadingError) {
return this.renderAsyncSearchLoadingErrorTip()
} else if (shouldShowNoResultsTip) {
return this.renderNoResultsTip()
} else {
return this.renderOptionList()
}
},
renderOptionList() {
const { instance } = this
return (
<div class="vue-treeselect__list">
{instance.forest.normalizedOptions.map(rootNode => (
<Option node={rootNode} key={rootNode.id} />
))}
</div>
)
},
renderSearchPromptTip() {
const { instance } = this
return (
<Tip type="search-prompt" icon="warning">{ instance.searchPromptText }</Tip>
)
},
renderLoadingOptionsTip() {
const { instance } = this
return (
<Tip type="loading" icon="loader">{ instance.loadingText }</Tip>
)
},
renderLoadingRootOptionsErrorTip() {
const { instance } = this
return (
<Tip type="error" icon="error">
{ instance.rootOptionsStates.loadingError }
<a class="vue-treeselect__retry" onClick={instance.loadRootOptions} title={instance.retryTitle}>
{ instance.retryText }
</a>
</Tip>
)
},
renderAsyncSearchLoadingErrorTip() {
const { instance } = this
const entry = instance.getRemoteSearchEntry()
// TODO: retryTitle?
return (
<Tip type="error" icon="error">
{ entry.loadingError }
<a class="vue-treeselect__retry" onClick={instance.handleRemoteSearch} title={instance.retryTitle}>
{ instance.retryText }
</a>
</Tip>
)
},
renderNoAvailableOptionsTip() {
const { instance } = this
return (
<Tip type="no-options" icon="warning">{ instance.noOptionsText }</Tip>
)
},
renderNoResultsTip() {
const { instance } = this
return (
<Tip type="no-results" icon="warning">{ instance.noResultsText }</Tip>
)
},
onMenuOpen() {
this.adjustMenuOpenDirection()
this.setupMenuSizeWatcher()
this.setupMenuResizeAndScrollEventListeners()
},
onMenuClose() {
this.removeMenuSizeWatcher()
this.removeMenuResizeAndScrollEventListeners()
},
adjustMenuOpenDirection() {
const { instance } = this
if (!instance.menu.isOpen) return
const $menu = instance.getMenu()
const $control = instance.getControl()
const menuRect = $menu.getBoundingClientRect()
const controlRect = $control.getBoundingClientRect()
const menuHeight = menuRect.height
const viewportHeight = window.innerHeight
const spaceAbove = controlRect.top
const spaceBelow = window.innerHeight - controlRect.bottom
const isControlInViewport = (
(controlRect.top >= 0 && controlRect.top <= viewportHeight) ||
(controlRect.top < 0 && controlRect.bottom > 0)
)
const hasEnoughSpaceBelow = spaceBelow > menuHeight + MENU_BUFFER
const hasEnoughSpaceAbove = spaceAbove > menuHeight + MENU_BUFFER
if (!isControlInViewport) {
instance.closeMenu()
} else if (instance.openDirection !== 'auto') {
instance.menu.placement = directionMap[instance.openDirection]
} else if (hasEnoughSpaceBelow || !hasEnoughSpaceAbove) {
instance.menu.placement = 'bottom'
} else {
instance.menu.placement = 'top'
}
},
setupMenuSizeWatcher() {
const { instance } = this
const $menu = instance.getMenu()
// istanbul ignore next
if (this.menuSizeWatcher) return
this.menuSizeWatcher = {
remove: watchSize($menu, this.adjustMenuOpenDirection),
}
},
setupMenuResizeAndScrollEventListeners() {
const { instance } = this
const $control = instance.getControl()
// istanbul ignore next
if (this.menuResizeAndScrollEventListeners) return
this.menuResizeAndScrollEventListeners = {
remove: setupResizeAndScrollEventListeners($control, this.adjustMenuOpenDirection),
}
},
removeMenuSizeWatcher() {
if (!this.menuSizeWatcher) return
this.menuSizeWatcher.remove()
this.menuSizeWatcher = null
},
removeMenuResizeAndScrollEventListeners() {
if (!this.menuResizeAndScrollEventListeners) return
this.menuResizeAndScrollEventListeners.remove()
this.menuResizeAndScrollEventListeners = null
},
},
render() {
return (
<div ref="menu-container" class="vue-treeselect__menu-container" style={this.menuContainerStyle}>
<transition name="vue-treeselect__menu--transition">
{this.renderMenu()}
</transition>
</div>
)
},
}
</script>
@@ -0,0 +1,179 @@
<script>
import Vue from 'vue'
import { watchSize, setupResizeAndScrollEventListeners, find } from '../utils'
import Menu from './Menu'
const PortalTarget = {
name: 'vue-treeselect--portal-target',
inject: [ 'instance' ],
watch: {
'instance.menu.isOpen'(newValue) {
if (newValue) {
this.setupHandlers()
} else {
this.removeHandlers()
}
},
'instance.menu.placement'() {
this.updateMenuContainerOffset()
},
},
created() {
this.controlResizeAndScrollEventListeners = null
this.controlSizeWatcher = null
},
mounted() {
const { instance } = this
if (instance.menu.isOpen) this.setupHandlers()
},
methods: {
setupHandlers() {
this.updateWidth()
this.updateMenuContainerOffset()
this.setupControlResizeAndScrollEventListeners()
this.setupControlSizeWatcher()
},
removeHandlers() {
this.removeControlResizeAndScrollEventListeners()
this.removeControlSizeWatcher()
},
setupControlResizeAndScrollEventListeners() {
const { instance } = this
const $control = instance.getControl()
// istanbul ignore next
if (this.controlResizeAndScrollEventListeners) return
this.controlResizeAndScrollEventListeners = {
remove: setupResizeAndScrollEventListeners($control, this.updateMenuContainerOffset),
}
},
setupControlSizeWatcher() {
const { instance } = this
const $control = instance.getControl()
// istanbul ignore next
if (this.controlSizeWatcher) return
this.controlSizeWatcher = {
remove: watchSize($control, () => {
this.updateWidth()
this.updateMenuContainerOffset()
}),
}
},
removeControlResizeAndScrollEventListeners() {
if (!this.controlResizeAndScrollEventListeners) return
this.controlResizeAndScrollEventListeners.remove()
this.controlResizeAndScrollEventListeners = null
},
removeControlSizeWatcher() {
if (!this.controlSizeWatcher) return
this.controlSizeWatcher.remove()
this.controlSizeWatcher = null
},
updateWidth() {
const { instance } = this
const $portalTarget = this.$el
const $control = instance.getControl()
const controlRect = $control.getBoundingClientRect()
$portalTarget.style.width = controlRect.width + 'px'
},
updateMenuContainerOffset() {
const { instance } = this
const $control = instance.getControl()
const $portalTarget = this.$el
const controlRect = $control.getBoundingClientRect()
const portalTargetRect = $portalTarget.getBoundingClientRect()
const offsetY = instance.menu.placement === 'bottom' ? controlRect.height : 0
const left = Math.round(controlRect.left - portalTargetRect.left) + 'px'
const top = Math.round(controlRect.top - portalTargetRect.top + offsetY) + 'px'
const menuContainerStyle = this.$refs.menu.$refs['menu-container'].style
const transformVariations = [ 'transform', 'webkitTransform', 'MozTransform', 'msTransform' ]
const transform = find(transformVariations, t => t in document.body.style)
// IE9 doesn't support `translate3d()`.
menuContainerStyle[transform] = `translate(${left}, ${top})`
},
},
render() {
const { instance } = this
const portalTargetClass = [ 'vue-treeselect__portal-target', instance.wrapperClass ]
const portalTargetStyle = { zIndex: instance.zIndex }
return (
<div class={portalTargetClass} style={portalTargetStyle} data-instance-id={instance.getInstanceId()}>
<Menu ref="menu" />
</div>
)
},
destroyed() {
this.removeHandlers()
},
}
let placeholder
export default {
name: 'vue-treeselect--menu-portal',
created() {
this.portalTarget = null
},
mounted() {
this.setup()
},
destroyed() {
this.teardown()
},
methods: {
setup() {
const el = document.createElement('div')
document.body.appendChild(el)
this.portalTarget = new Vue({
el,
parent: this,
...PortalTarget,
})
},
teardown() {
document.body.removeChild(this.portalTarget.$el)
this.portalTarget.$el.innerHTML = ''
this.portalTarget.$destroy()
this.portalTarget = null
},
},
render() {
if (!placeholder) placeholder = (
<div class="vue-treeselect__menu-placeholder" />
)
return placeholder
},
}
</script>
@@ -0,0 +1,56 @@
<script>
import MultiValueItem from './MultiValueItem'
import Input from './Input'
import Placeholder from './Placeholder'
export default {
name: 'vue-treeselect--multi-value',
inject: [ 'instance' ],
methods: {
renderMultiValueItems() {
const { instance } = this
return instance.internalValue
.slice(0, instance.limit)
.map(instance.getNode)
.map(node => (
<MultiValueItem key={`multi-value-item-${node.id}`} node={node} />
))
},
renderExceedLimitTip() {
const { instance } = this
const count = instance.internalValue.length - instance.limit
if (count <= 0) return null
return (
<div class="vue-treeselect__limit-tip vue-treeselect-helper-zoom-effect-off" key="exceed-limit-tip">
<span class="vue-treeselect__limit-tip-text">{ instance.limitText(count) }</span>
</div>
)
},
},
render() {
const { renderValueContainer } = this.$parent
const transitionGroupProps = {
props: {
tag: 'div',
name: 'vue-treeselect__multi-value-item--transition',
appear: true,
},
}
return renderValueContainer(
<transition-group class="vue-treeselect__multi-value" {...transitionGroupProps}>
{this.renderMultiValueItems()}
{this.renderExceedLimitTip()}
<Placeholder key="placeholder" />
<Input ref="input" key="input" />
</transition-group>,
)
},
}
</script>
@@ -0,0 +1,45 @@
<script>
import { onLeftClick } from '../utils'
import DeleteIcon from './icons/Delete'
export default {
name: 'vue-treeselect--multi-value-item',
inject: [ 'instance' ],
props: {
node: {
type: Object,
required: true,
},
},
methods: {
handleMouseDown: onLeftClick(function handleMouseDown() {
const { instance, node } = this
// Deselect this node.
instance.select(node)
}),
},
render() {
const { instance, node } = this
const itemClass = {
'vue-treeselect__multi-value-item': true,
'vue-treeselect__multi-value-item-disabled': node.isDisabled,
'vue-treeselect__multi-value-item-new': node.isNew,
}
const customValueLabelRenderer = instance.$scopedSlots['value-label']
const labelRenderer = customValueLabelRenderer ? customValueLabelRenderer({ node }) : node.label
return (
<div class="vue-treeselect__multi-value-item-container">
<div class={itemClass} onMousedown={this.handleMouseDown}>
<span class="vue-treeselect__multi-value-label">{ labelRenderer }</span>
<span class="vue-treeselect__icon vue-treeselect__value-remove"><DeleteIcon /></span>
</div>
</div>
)
},
}
</script>
+300
View File
@@ -0,0 +1,300 @@
<script>
import { UNCHECKED, INDETERMINATE, CHECKED } from '../constants'
import { onLeftClick } from '../utils'
import Tip from './Tip'
import ArrowIcon from './icons/Arrow'
let arrowPlaceholder, checkMark, minusMark
const Option = {
name: 'vue-treeselect--option',
inject: [ 'instance' ],
props: {
node: {
type: Object,
required: true,
},
},
computed: {
shouldExpand() {
const { instance, node } = this
return node.isBranch && instance.shouldExpand(node)
},
shouldShow() {
const { instance, node } = this
return instance.shouldShowOptionInMenu(node)
},
},
methods: {
renderOption() {
const { instance, node } = this
const optionClass = {
'vue-treeselect__option': true,
'vue-treeselect__option--disabled': node.isDisabled,
'vue-treeselect__option--selected': instance.isSelected(node),
'vue-treeselect__option--highlight': node.isHighlighted,
'vue-treeselect__option--matched': instance.localSearch.active && node.isMatched,
'vue-treeselect__option--hide': !this.shouldShow,
}
return (
<div class={optionClass} onMouseenter={this.handleMouseEnterOption} data-id={node.id}>
{this.renderArrow()}
{this.renderLabelContainer([
this.renderCheckboxContainer([
this.renderCheckbox(),
]),
this.renderLabel(),
])}
</div>
)
},
renderSubOptionsList() {
if (!this.shouldExpand) return null
return (
<div class="vue-treeselect__list">
{this.renderSubOptions()}
{this.renderNoChildrenTip()}
{this.renderLoadingChildrenTip()}
{this.renderLoadingChildrenErrorTip()}
</div>
)
},
renderArrow() {
const { instance, node } = this
if (instance.shouldFlattenOptions && this.shouldShow) return null
if (node.isBranch) {
const transitionProps = {
props: {
name: 'vue-treeselect__option-arrow--prepare',
appear: true,
},
}
const arrowClass = {
'vue-treeselect__option-arrow': true,
'vue-treeselect__option-arrow--rotated': this.shouldExpand,
}
return (
<div class="vue-treeselect__option-arrow-container" onMousedown={this.handleMouseDownOnArrow}>
<transition {...transitionProps}>
<ArrowIcon class={arrowClass} />
</transition>
</div>
)
}
// For leaf nodes, we render a placeholder to keep its label aligned to
// branch nodes. Unless there is no branch nodes at all (a normal
// non-tree select).
if (/*node.isLeaf && */instance.hasBranchNodes) {
if (!arrowPlaceholder) arrowPlaceholder = (
<div class="vue-treeselect__option-arrow-placeholder">&nbsp;</div>
)
return arrowPlaceholder
}
return null
},
renderLabelContainer(children) {
return (
<div class="vue-treeselect__label-container" onMousedown={this.handleMouseDownOnLabelContainer}>
{children}
</div>
)
},
renderCheckboxContainer(children) {
const { instance, node } = this
if (instance.single) return null
if (instance.disableBranchNodes && node.isBranch) return null
return (
<div class="vue-treeselect__checkbox-container">
{children}
</div>
)
},
renderCheckbox() {
const { instance, node } = this
const checkedState = instance.forest.checkedStateMap[node.id]
const checkboxClass = {
'vue-treeselect__checkbox': true,
'vue-treeselect__checkbox--checked': checkedState === CHECKED,
'vue-treeselect__checkbox--indeterminate': checkedState === INDETERMINATE,
'vue-treeselect__checkbox--unchecked': checkedState === UNCHECKED,
'vue-treeselect__checkbox--disabled': node.isDisabled,
}
if (!checkMark) checkMark = (
<span class="vue-treeselect__check-mark" />
)
if (!minusMark) minusMark = (
<span class="vue-treeselect__minus-mark" />
)
return (
<span class={checkboxClass}>
{checkMark}
{minusMark}
</span>
)
},
renderLabel() {
const { instance, node } = this
const shouldShowCount = (
node.isBranch && (instance.localSearch.active
? instance.showCountOnSearchComputed
: instance.showCount
)
)
const count = shouldShowCount
? instance.localSearch.active
? instance.localSearch.countMap[node.id][instance.showCountOf]
: node.count[instance.showCountOf]
: NaN
const labelClassName = 'vue-treeselect__label'
const countClassName = 'vue-treeselect__count'
const customLabelRenderer = instance.$scopedSlots['option-label']
if (customLabelRenderer) return customLabelRenderer({
node,
shouldShowCount,
count,
labelClassName,
countClassName,
})
return (
<label class={labelClassName}>
{node.label}
{shouldShowCount && (
<span class={countClassName}>({count})</span>
)}
</label>
)
},
renderSubOptions() {
const { node } = this
if (!node.childrenStates.isLoaded) return null
return node.children.map(childNode => (
<Option node={childNode} key={childNode.id} />
))
},
renderNoChildrenTip() {
const { instance, node } = this
if (!node.childrenStates.isLoaded || node.children.length) return null
return (
<Tip type="no-children" icon="warning">{ instance.noChildrenText }</Tip>
)
},
renderLoadingChildrenTip() {
const { instance, node } = this
if (!node.childrenStates.isLoading) return null
return (
<Tip type="loading" icon="loader">{ instance.loadingText }</Tip>
)
},
renderLoadingChildrenErrorTip() {
const { instance, node } = this
if (!node.childrenStates.loadingError) return null
return (
<Tip type="error" icon="error">
{ node.childrenStates.loadingError }
<a class="vue-treeselect__retry" title={instance.retryTitle} onMousedown={this.handleMouseDownOnRetry}>
{ instance.retryText }
</a>
</Tip>
)
},
handleMouseEnterOption(evt) {
const { instance, node } = this
// Equivalent to `self` modifier.
// istanbul ignore next
if (evt.target !== evt.currentTarget) return
instance.setCurrentHighlightedOption(node, false)
},
handleMouseDownOnArrow: onLeftClick(function handleMouseDownOnOptionArrow() {
const { instance, node } = this
instance.toggleExpanded(node)
}),
handleMouseDownOnLabelContainer: onLeftClick(function handleMouseDownOnLabelContainer() {
const { instance, node } = this
if (node.isBranch && instance.disableBranchNodes) {
instance.toggleExpanded(node)
} else {
instance.select(node)
}
}),
handleMouseDownOnRetry: onLeftClick(function handleMouseDownOnRetry() {
const { instance, node } = this
instance.loadChildrenOptions(node)
}),
},
render() {
const { node } = this
const indentLevel = this.instance.shouldFlattenOptions ? 0 : node.level
const listItemClass = {
'vue-treeselect__list-item': true,
[`vue-treeselect__indent-level-${indentLevel}`]: true,
}
const transitionProps = {
props: {
name: 'vue-treeselect__list--transition',
},
}
return (
<div class={listItemClass}>
{this.renderOption()}
{node.isBranch && (
<transition {...transitionProps}>
{this.renderSubOptionsList()}
</transition>
)}
</div>
)
},
}
// eslint-disable-next-line vue/require-direct-export
export default Option
</script>
@@ -0,0 +1,21 @@
<script>
export default {
name: 'vue-treeselect--placeholder',
inject: [ 'instance' ],
render() {
const { instance } = this
const placeholderClass = {
'vue-treeselect__placeholder': true,
'vue-treeselect-helper-zoom-effect-off': true,
'vue-treeselect-helper-hide': instance.hasValue || instance.trigger.searchQuery,
}
return (
<div class={placeholderClass}>
{ instance.placeholder }
</div>
)
},
}
</script>
@@ -0,0 +1,34 @@
<script>
import Input from './Input'
import Placeholder from './Placeholder'
export default {
name: 'vue-treeselect--single-value',
inject: [ 'instance' ],
methods: {
renderSingleValueLabel() {
const { instance } = this
const node = instance.selectedNodes[0]
const customValueLabelRenderer = instance.$scopedSlots['value-label']
return customValueLabelRenderer
? customValueLabelRenderer({ node })
: node.label
},
},
render() {
const { instance, $parent: { renderValueContainer } } = this
const shouldShowValue = instance.hasValue && !instance.trigger.searchQuery
return renderValueContainer([
shouldShowValue && (
<div class="vue-treeselect__single-value">
{ this.renderSingleValueLabel() }
</div>
),
<Placeholder />,
<Input ref="input" />,
])
},
}
</script>
+32
View File
@@ -0,0 +1,32 @@
<script>
export default {
name: 'vue-treeselect--tip',
functional: true,
props: {
type: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
},
render(_, context) {
const { props, children } = context
return (
<div class={`vue-treeselect__tip vue-treeselect__${props.type}-tip`}>
<div class="vue-treeselect__icon-container">
<span class={`vue-treeselect__icon-${props.icon}`} />
</div>
<span class={`vue-treeselect__tip-text vue-treeselect__${props.type}-tip-text`}>
{children}
</span>
</div>
)
},
}
</script>
@@ -0,0 +1,41 @@
<script>
import treeselectMixin from '../mixins/treeselectMixin'
import HiddenFields from './HiddenFields'
import Control from './Control'
import Menu from './Menu'
import MenuPortal from './MenuPortal'
export default {
name: 'vue-treeselect',
mixins: [ treeselectMixin ],
computed: {
wrapperClass() {
return {
'vue-treeselect': true,
'vue-treeselect--single': this.single,
'vue-treeselect--multi': this.multiple,
'vue-treeselect--searchable': this.searchable,
'vue-treeselect--disabled': this.disabled,
'vue-treeselect--focused': this.trigger.isFocused,
'vue-treeselect--has-value': this.hasValue,
'vue-treeselect--open': this.menu.isOpen,
'vue-treeselect--open-above': this.menu.placement === 'top',
'vue-treeselect--open-below': this.menu.placement === 'bottom',
'vue-treeselect--branch-nodes-disabled': this.disableBranchNodes,
'vue-treeselect--append-to-body': this.appendToBody,
}
},
},
render() {
return (
<div ref="wrapper" class={this.wrapperClass}>
<HiddenFields />
<Control ref="control" />
{this.appendToBody ? <MenuPortal ref="portal" /> : <Menu ref="menu" />}
</div>
)
},
}
</script>
@@ -0,0 +1,11 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 292.362 292.362">
<path d="M286.935 69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952 0-9.233 1.807-12.85 5.424C1.807 72.998 0 77.279 0 82.228c0 4.948 1.807 9.229 5.424 12.847l127.907 127.907c3.621 3.617 7.902 5.428 12.85 5.428s9.233-1.811 12.847-5.428L286.935 95.074c3.613-3.617 5.427-7.898 5.427-12.847 0-4.948-1.814-9.229-5.427-12.85z" />
</svg>
</template>
<script>
export default {
name: 'vue-treeselect--arrow',
}
</script>
@@ -0,0 +1,11 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 348.333 348.333">
<path d="M336.559 68.611L231.016 174.165l105.543 105.549c15.699 15.705 15.699 41.145 0 56.85-7.844 7.844-18.128 11.769-28.407 11.769-10.296 0-20.581-3.919-28.419-11.769L174.167 231.003 68.609 336.563c-7.843 7.844-18.128 11.769-28.416 11.769-10.285 0-20.563-3.919-28.413-11.769-15.699-15.698-15.699-41.139 0-56.85l105.54-105.549L11.774 68.611c-15.699-15.699-15.699-41.145 0-56.844 15.696-15.687 41.127-15.687 56.829 0l105.563 105.554L279.721 11.767c15.705-15.687 41.139-15.687 56.832 0 15.705 15.699 15.705 41.145.006 56.844z" />
</svg>
</template>
<script>
export default {
name: 'vue-treeselect--x',
}
</script>
+50
View File
@@ -0,0 +1,50 @@
// Magic value that indicates a root level node.
export const NO_PARENT_NODE = null
// Types of checked state.
export const UNCHECKED = 0
export const INDETERMINATE = 1
export const CHECKED = 2
// Types of count number.
export const ALL_CHILDREN = 'ALL_CHILDREN'
export const ALL_DESCENDANTS = 'ALL_DESCENDANTS'
export const LEAF_CHILDREN = 'LEAF_CHILDREN'
export const LEAF_DESCENDANTS = 'LEAF_DESCENDANTS'
// Action types of delayed loading.
export const LOAD_ROOT_OPTIONS = 'LOAD_ROOT_OPTIONS'
export const LOAD_CHILDREN_OPTIONS = 'LOAD_CHILDREN_OPTIONS'
export const ASYNC_SEARCH = 'ASYNC_SEARCH'
// Acceptable values of `valueConsistsOf` prop.
export const ALL = 'ALL'
export const BRANCH_PRIORITY = 'BRANCH_PRIORITY'
export const LEAF_PRIORITY = 'LEAF_PRIORITY'
export const ALL_WITH_INDETERMINATE = 'ALL_WITH_INDETERMINATE'
// Acceptable values of `sortValueBy` prop.
export const ORDER_SELECTED = 'ORDER_SELECTED'
export const LEVEL = 'LEVEL'
export const INDEX = 'INDEX'
// Key codes look-up table.
export const KEY_CODES = {
BACKSPACE: 8,
ENTER: 13,
ESCAPE: 27,
END: 35,
HOME: 36,
ARROW_LEFT: 37,
ARROW_UP: 38,
ARROW_RIGHT: 39,
ARROW_DOWN: 40,
DELETE: 46,
}
// Other constants.
export const INPUT_DEBOUNCE_DELAY = process.env.NODE_ENV === 'testing'
? /* to speed up unit testing */ 10
: /* istanbul ignore next */ 200
export const MIN_INPUT_WIDTH = 5
export const MENU_BUFFER = 40
+14
View File
@@ -0,0 +1,14 @@
import Treeselect from './components/Treeselect'
import treeselectMixin from './mixins/treeselectMixin'
import './style.less'
export default Treeselect
export { Treeselect, treeselectMixin }
export {
// Delayed loading.
LOAD_ROOT_OPTIONS,
LOAD_CHILDREN_OPTIONS,
ASYNC_SEARCH,
} from './constants'
export const VERSION = PKG_VERSION
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
rules: {
'import/no-default-export': 2,
'import/prefer-default-export': 0,
},
}
+1
View File
@@ -0,0 +1 @@
export { default as constant } from 'lodash/constant'
+1
View File
@@ -0,0 +1 @@
export const createMap = () => Object.create(null)
+1
View File
@@ -0,0 +1 @@
export { default as debounce } from 'lodash/debounce'
+25
View File
@@ -0,0 +1,25 @@
function isPlainObject(value) {
if (value == null || typeof value !== 'object') return false
return Object.getPrototypeOf(value) === Object.prototype
}
function copy(obj, key, value) {
if (isPlainObject(value)) {
obj[key] || (obj[key] = {})
deepExtend(obj[key], value)
} else {
obj[key] = value
}
}
export function deepExtend(target, source) {
if (isPlainObject(source)) {
const keys = Object.keys(source)
for (let i = 0, len = keys.length; i < len; i++) {
copy(target, keys[i], source[keys[i]])
}
}
return target
}
+6
View File
@@ -0,0 +1,6 @@
export function find(arr, predicate, ctx) {
for (let i = 0, len = arr.length; i < len; i++) {
if (predicate.call(ctx, arr[i], i, arr)) return arr[i]
}
return undefined
}
+1
View File
@@ -0,0 +1 @@
export { default as identity } from 'lodash/identity'
+3
View File
@@ -0,0 +1,3 @@
export function includes(arrOrStr, elem) {
return arrOrStr.indexOf(elem) !== -1
}
+38
View File
@@ -0,0 +1,38 @@
// ========================
// Debugging Helpers
// ========================
export { warning } from './warning'
// ========================
// DOM Utilities
// ========================
export { onLeftClick } from './onLeftClick'
export { scrollIntoView } from './scrollIntoView'
export { debounce } from './debounce'
export { watchSize } from './watchSize'
export { setupResizeAndScrollEventListeners } from './setupResizeAndScrollEventListeners'
// ========================
// Language Helpers
// ========================
export { isNaN } from './isNaN'
export { isPromise } from './isPromise'
export { once } from './once'
export { noop } from './noop'
export { identity } from './identity'
export { constant } from './constant'
export { createMap } from './createMap'
export { deepExtend } from './deepExtend'
export { last } from './last'
export { includes } from './includes'
export { find } from './find'
export { removeFromArray } from './removeFromArray'
// ========================
// Other Utilities
// ========================
export { quickDiff } from './quickDiff'
+3
View File
@@ -0,0 +1,3 @@
export function isNaN(x) {
return x !== x
}
+1
View File
@@ -0,0 +1 @@
export { default as isPromise } from 'is-promise'
+1
View File
@@ -0,0 +1 @@
export { default as last } from 'lodash/last'
+1
View File
@@ -0,0 +1 @@
export { default as noop } from 'lodash/noop'
@@ -0,0 +1,7 @@
export function onLeftClick(mouseDownHandler) {
return function onMouseDown(evt, ...args) {
if (evt.type === 'mousedown' && evt.button === 0) {
mouseDownHandler.call(this, evt, ...args)
}
}
}
+1
View File
@@ -0,0 +1 @@
export { default as once } from 'lodash/once'
+9
View File
@@ -0,0 +1,9 @@
export function quickDiff(arrA, arrB) {
if (arrA.length !== arrB.length) return true
for (let i = 0; i < arrA.length; i++) {
if (arrA[i] !== arrB[i]) return true
}
return false
}
@@ -0,0 +1,4 @@
export function removeFromArray(arr, elem) {
const idx = arr.indexOf(elem)
if (idx !== -1) arr.splice(idx, 1)
}
@@ -0,0 +1,15 @@
// from react-select
export function scrollIntoView($scrollingEl, $focusedEl) {
const scrollingReact = $scrollingEl.getBoundingClientRect()
const focusedRect = $focusedEl.getBoundingClientRect()
const overScroll = $focusedEl.offsetHeight / 3
if (focusedRect.bottom + overScroll > scrollingReact.bottom) {
$scrollingEl.scrollTop = Math.min(
$focusedEl.offsetTop + $focusedEl.clientHeight - $scrollingEl.offsetHeight + overScroll,
$scrollingEl.scrollHeight,
)
} else if (focusedRect.top - overScroll < scrollingReact.top) {
$scrollingEl.scrollTop = Math.max($focusedEl.offsetTop - overScroll, 0)
}
}
@@ -0,0 +1,34 @@
function findScrollParents($el) {
const $scrollParents = []
let $parent = $el.parentNode
while ($parent && $parent.nodeName !== 'BODY' && $parent.nodeType === document.ELEMENT_NODE) {
if (isScrollElment($parent)) $scrollParents.push($parent)
$parent = $parent.parentNode
}
$scrollParents.push(window)
return $scrollParents
}
function isScrollElment($el) {
// Firefox wants us to check `-x` and `-y` variations as well
const { overflow, overflowX, overflowY } = getComputedStyle($el)
return /(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)
}
export function setupResizeAndScrollEventListeners($el, listener) {
const $scrollParents = findScrollParents($el)
window.addEventListener('resize', listener, { passive: true })
$scrollParents.forEach(scrollParent => {
scrollParent.addEventListener('scroll', listener, { passive: true })
})
return function removeEventListeners() {
window.removeEventListener('resize', listener, { passive: true })
$scrollParents.forEach($scrollParent => {
$scrollParent.removeEventListener('scroll', listener, { passive: true })
})
}
}
+11
View File
@@ -0,0 +1,11 @@
import { noop } from './noop'
export const warning = process.env.NODE_ENV === 'production'
? /* istanbul ignore next */ noop
: function warning(checker, complainer) {
if (!checker()) {
const message = [ '[Vue-Treeselect Warning]' ].concat(complainer())
// eslint-disable-next-line no-console
console.error(...message)
}
}
+67
View File
@@ -0,0 +1,67 @@
import watchSizeForBrowsersOtherThanIE9 from 'watch-size'
import { removeFromArray } from './removeFromArray'
let intervalId
const registered = []
const INTERVAL_DURATION = 100
function run() {
intervalId = setInterval(() => {
registered.forEach(test)
}, INTERVAL_DURATION)
}
function stop() {
clearInterval(intervalId)
intervalId = null
}
function test(item) {
const { $el, listener, lastWidth, lastHeight } = item
const width = $el.offsetWidth
const height = $el.offsetHeight
if (lastWidth !== width || lastHeight !== height) {
item.lastWidth = width
item.lastHeight = height
listener({ width, height })
}
}
function watchSizeForIE9($el, listener) {
const item = {
$el,
listener,
lastWidth: null,
lastHeight: null,
}
const unwatch = () => {
removeFromArray(registered, item)
if (!registered.length) stop()
}
registered.push(item)
// The original watch-size will call the listener on initialization.
// Keep the same behavior here.
test(item)
run()
return unwatch
}
export function watchSize($el, listener) {
// See: https://stackoverflow.com/a/31293352
const isIE9 = document.documentMode === 9
// watch-size will call the listener on initialization.
// Disable this behavior with a lock to achieve a clearer code logic.
let locked = true
const wrappedListener = (...args) => locked || listener(...args)
const implementation = isIE9
? watchSizeForIE9
: watchSizeForBrowsersOtherThanIE9
const removeSizeWatcher = implementation($el, wrappedListener)
locked = false // unlock after initialization
return removeSizeWatcher
}