save
This commit is contained in:
parent
8f9d3604e6
commit
5bb83ff8e5
258 changed files with 12974 additions and 7838 deletions
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div
|
||||
:class="classes"
|
||||
v-click-outside.capture="onClickOutside"
|
||||
v-click-outside:mousedown.capture="onClickOutside"
|
||||
v-click-outside:touchstart.capture="onClickOutside"
|
||||
v-click-outside:[capture]="onClickOutside"
|
||||
v-click-outside:[capture].mousedown="onClickOutside"
|
||||
v-click-outside:[capture].touchstart="onClickOutside"
|
||||
>
|
||||
<div
|
||||
ref="reference"
|
||||
|
@ -35,7 +35,7 @@
|
|||
:values="values"
|
||||
:clearable="canBeCleared"
|
||||
:prefix="prefix"
|
||||
:disabled="disabled"
|
||||
:disabled="itemDisabled"
|
||||
:remote="remote"
|
||||
:input-element-id="elementId"
|
||||
:initial-label="initialLabel"
|
||||
|
@ -43,11 +43,14 @@
|
|||
:query-prop="query"
|
||||
:max-tag-count="maxTagCount"
|
||||
:max-tag-placeholder="maxTagPlaceholder"
|
||||
:allow-create="allowCreate"
|
||||
:show-create-item="showCreateItem"
|
||||
|
||||
@on-query-change="onQueryChange"
|
||||
@on-input-focus="isFocused = true"
|
||||
@on-input-blur="isFocused = false"
|
||||
@on-clear="clearSingleSelect"
|
||||
@on-enter="handleCreateItem"
|
||||
>
|
||||
<slot name="prefix" slot="prefix"></slot>
|
||||
</select-head>
|
||||
|
@ -63,10 +66,12 @@
|
|||
:transfer="transfer"
|
||||
v-transfer-dom
|
||||
>
|
||||
<ul v-show="showNotFoundLabel && !$slots.empty" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
|
||||
<!--feature #5327-->
|
||||
<ul v-if="showNotFoundLabel && $slots.empty" :class="[prefixCls + '-not-found']" @mousedown.prevent><li><slot name="empty"></slot></li></ul>
|
||||
<ul v-show="showNotFoundLabel && !allowCreate" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
|
||||
<ul :class="prefixCls + '-dropdown-list'">
|
||||
<li :class="prefixCls + '-item'" v-if="showCreateItem" @click="handleCreateItem">
|
||||
{{ query }}
|
||||
<Icon type="md-return-left" :class="prefixCls + '-item-enter'" />
|
||||
</li>
|
||||
<functional-options
|
||||
v-if="(!remote) || (remote && !loading)"
|
||||
:options="selectOptions"
|
||||
|
@ -81,10 +86,12 @@
|
|||
</template>
|
||||
<script>
|
||||
import Drop from './dropdown.vue';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import Icon from '../icon';
|
||||
import {directive as clickOutside} from '../../directives/v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import { oneOf, findComponentsDownward } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import mixinsForm from '../../mixins/form';
|
||||
import Locale from '../../mixins/locale';
|
||||
import SelectHead from './select-head.vue';
|
||||
import FunctionalOptions from './functional-options.vue';
|
||||
|
@ -157,8 +164,8 @@
|
|||
|
||||
export default {
|
||||
name: 'iSelect',
|
||||
mixins: [ Emitter, Locale ],
|
||||
components: { FunctionalOptions, Drop, SelectHead },
|
||||
mixins: [ Emitter, Locale, mixinsForm ],
|
||||
components: { FunctionalOptions, Drop, SelectHead, Icon },
|
||||
directives: { clickOutside, TransferDom },
|
||||
props: {
|
||||
value: {
|
||||
|
@ -254,13 +261,31 @@
|
|||
// 3.4.0
|
||||
maxTagPlaceholder: {
|
||||
type: Function
|
||||
},
|
||||
// 4.0.0
|
||||
allowCreate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 4.0.0
|
||||
capture: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW ? true : this.$IVIEW.capture;
|
||||
}
|
||||
},
|
||||
// 4.2.0
|
||||
// 搜索时,只按 label 进行搜索
|
||||
filterByLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.$on('on-select-selected', this.onOptionClick);
|
||||
|
||||
// set the initial values if there are any
|
||||
if ( this.selectOptions.length > 0){
|
||||
if (!this.remote && this.selectOptions.length > 0){
|
||||
this.values = this.getInitialValue().map(value => {
|
||||
if (typeof value !== 'number' && !value) return null;
|
||||
return this.getOptionData(value);
|
||||
|
@ -288,8 +313,6 @@
|
|||
hasExpectedValue: false,
|
||||
preventRemoteCall: false,
|
||||
filterQueryChange: false, // #4273
|
||||
// #6349
|
||||
hideMenuTimer: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -298,7 +321,7 @@
|
|||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-visible`]: this.visible,
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-disabled`]: this.itemDisabled,
|
||||
[`${prefixCls}-multiple`]: this.multiple,
|
||||
[`${prefixCls}-single`]: !this.multiple,
|
||||
[`${prefixCls}-show-clear`]: this.showCloseIcon,
|
||||
|
@ -334,6 +357,17 @@
|
|||
return this.loadingText;
|
||||
}
|
||||
},
|
||||
showCreateItem () {
|
||||
let state = false;
|
||||
if (this.allowCreate && this.query !== '') {
|
||||
state = true;
|
||||
const $options = findComponentsDownward(this, 'iOption');
|
||||
if ($options && $options.length) {
|
||||
if ($options.find(item => item.optionLabel === this.query)) state = false;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
},
|
||||
transitionName () {
|
||||
return this.placement === 'bottom' ? 'slide-up' : 'slide-down';
|
||||
},
|
||||
|
@ -359,7 +393,7 @@
|
|||
},
|
||||
canBeCleared(){
|
||||
const uiStateMatch = this.hasMouseHoverHead || this.active;
|
||||
const qualifiesForClear = !this.multiple && !this.disabled && this.clearable;
|
||||
const qualifiesForClear = !this.multiple && !this.itemDisabled && this.clearable;
|
||||
return uiStateMatch && qualifiesForClear && this.reset; // we return a function
|
||||
},
|
||||
selectOptions() {
|
||||
|
@ -414,8 +448,9 @@
|
|||
const optionPassesFilter = this.filterable ? this.validateOption(cOptions) : option;
|
||||
if (!optionPassesFilter) continue;
|
||||
}
|
||||
|
||||
optionCounter = optionCounter + 1;
|
||||
selectOptions.push(this.processOption(option, selectedValues, currentIndex === optionCounter));
|
||||
selectOptions.push(this.processOption(option, selectedValues, optionCounter === currentIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,7 +460,7 @@
|
|||
return extractOptions(this.selectOptions);
|
||||
},
|
||||
selectTabindex(){
|
||||
return this.disabled || this.filterable ? -1 : 0;
|
||||
return this.itemDisabled || this.filterable ? -1 : 0;
|
||||
},
|
||||
remote(){
|
||||
return typeof this.remoteMethod === 'function';
|
||||
|
@ -445,17 +480,22 @@
|
|||
}
|
||||
},
|
||||
clearSingleSelect(){ // PUBLIC API
|
||||
// fix #446
|
||||
if (!this.multiple) this.$emit('input', '');
|
||||
this.$emit('on-clear');
|
||||
this.hideMenu();
|
||||
if (this.clearable) this.reset();
|
||||
this.$emit('on-clear'); // #6331
|
||||
},
|
||||
getOptionData(value){
|
||||
const option = this.flatOptions.find(({componentOptions}) => componentOptions.propsData.value === value);
|
||||
if (!option) return null;
|
||||
const label = getOptionLabel(option);
|
||||
// 修复多选时,选项有disabled属性,选中项仍然能删除的 bug
|
||||
const disabled = option.componentOptions.propsData.disabled;
|
||||
return {
|
||||
value: value,
|
||||
label: label,
|
||||
disabled: disabled
|
||||
};
|
||||
},
|
||||
getInitialValue(){
|
||||
|
@ -475,6 +515,7 @@
|
|||
const optionValue = option.componentOptions.propsData.value;
|
||||
const disabled = option.componentOptions.propsData.disabled;
|
||||
const isSelected = values.includes(optionValue);
|
||||
|
||||
const propsData = {
|
||||
...option.componentOptions.propsData,
|
||||
selected: isSelected,
|
||||
|
@ -492,22 +533,19 @@
|
|||
},
|
||||
|
||||
validateOption({children, elm, propsData}){
|
||||
const value = propsData.value;
|
||||
const label = propsData.label || '';
|
||||
const textContent = (elm && elm.textContent) || (children || []).reduce((str, node) => {
|
||||
const nodeText = node.elm ? node.elm.textContent : node.text;
|
||||
return `${str} ${nodeText}`;
|
||||
}, '') || '';
|
||||
const stringValues = [label, textContent];
|
||||
const stringValues = this.filterByLabel ? [label].toString() : [value, label, textContent].toString();
|
||||
const query = this.query.toLowerCase().trim();
|
||||
const findValuesIndex = stringValues.findIndex(item=>{
|
||||
let itemToLowerCase = item.toLowerCase();
|
||||
return itemToLowerCase.includes(query);
|
||||
});
|
||||
return findValuesIndex === -1 ? false : true;
|
||||
return stringValues.toLowerCase().includes(query);
|
||||
},
|
||||
|
||||
toggleMenu (e, force) {
|
||||
if (this.disabled) {
|
||||
if (this.itemDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -517,22 +555,9 @@
|
|||
this.broadcast('Drop', 'on-update-popper');
|
||||
}
|
||||
},
|
||||
updateFocusIndex(){
|
||||
this.focusIndex = this.flatOptions.findIndex((opt) => {
|
||||
if (!opt || !opt.componentOptions) return false;
|
||||
return opt.componentOptions.propsData.value === this.publicValue;
|
||||
});
|
||||
},
|
||||
hideMenu () {
|
||||
this.toggleMenu(null, false);
|
||||
setTimeout(() =>{
|
||||
this.unchangedQuery = true;
|
||||
// resolve if we use filterable, dropItem not selected #6349
|
||||
this.hideMenuTimer = setTimeout(()=>{
|
||||
this.updateFocusIndex();
|
||||
this.hideMenuTimer = null;
|
||||
});
|
||||
}, ANIMATION_TIMEOUT);
|
||||
setTimeout(() => this.unchangedQuery = true, ANIMATION_TIMEOUT);
|
||||
},
|
||||
onClickOutside(event){
|
||||
if (this.visible) {
|
||||
|
@ -577,31 +602,32 @@
|
|||
},
|
||||
handleKeydown (e) {
|
||||
const key = e.key || e.code;
|
||||
if ( key === 'Backspace'){
|
||||
const keyCode = e.keyCode || e.which;
|
||||
if (key === 'Backspace' || keyCode===8){
|
||||
return; // so we don't call preventDefault
|
||||
}
|
||||
|
||||
if (this.visible) {
|
||||
e.preventDefault();
|
||||
if ( key === 'Tab'){
|
||||
if (key === 'Tab'){
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// Esc slide-up
|
||||
if ( key === 'Escape') {
|
||||
if (key === 'Escape') {
|
||||
e.stopPropagation();
|
||||
this.hideMenu();
|
||||
}
|
||||
// next
|
||||
if ( key === 'ArrowUp') {
|
||||
if (key === 'ArrowUp') {
|
||||
this.navigateOptions(-1);
|
||||
}
|
||||
// prev
|
||||
if ( key === 'ArrowDown') {
|
||||
if (key === 'ArrowDown') {
|
||||
this.navigateOptions(1);
|
||||
}
|
||||
// enter
|
||||
if ( key === 'Enter') {
|
||||
if (key === 'Enter') {
|
||||
if (this.focusIndex === -1) return this.hideMenu();
|
||||
const optionComponent = this.flatOptions[this.focusIndex];
|
||||
|
||||
|
@ -650,6 +676,7 @@
|
|||
},
|
||||
onOptionClick(option) {
|
||||
if (this.multiple){
|
||||
|
||||
// keep the query for remote select
|
||||
if (this.remote) this.lastRemoteQuery = this.lastRemoteQuery || this.query;
|
||||
else this.lastRemoteQuery = '';
|
||||
|
@ -660,6 +687,7 @@
|
|||
} else {
|
||||
this.values = this.values.concat(option);
|
||||
}
|
||||
|
||||
this.isFocused = true; // so we put back focus after clicking with mouse on option elements
|
||||
} else {
|
||||
this.query = String(option.label).trim();
|
||||
|
@ -702,12 +730,9 @@
|
|||
this.query = query;
|
||||
this.unchangedQuery = this.visible;
|
||||
this.filterQueryChange = true;
|
||||
if(this.filterable){
|
||||
this.updateFocusIndex();
|
||||
}
|
||||
},
|
||||
toggleHeaderFocus({type}){
|
||||
if (this.disabled) {
|
||||
if (this.itemDisabled) {
|
||||
return;
|
||||
}
|
||||
this.isFocused = type === 'focus';
|
||||
|
@ -720,15 +745,35 @@
|
|||
this.hasExpectedValue = true;
|
||||
}
|
||||
},
|
||||
// 4.0.0 create new item
|
||||
handleCreateItem () {
|
||||
if (this.allowCreate && this.query !== '' && this.showCreateItem) {
|
||||
const query = this.query;
|
||||
this.$emit('on-create', query);
|
||||
this.query = '';
|
||||
|
||||
const option = {
|
||||
value: query,
|
||||
label: query,
|
||||
tag: undefined
|
||||
};
|
||||
if (this.multiple) {
|
||||
this.onOptionClick(option);
|
||||
} else {
|
||||
// 单选时如果不在 nextTick 里执行,无法赋值
|
||||
this.$nextTick(() => this.onOptionClick(option));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(value){
|
||||
const {getInitialValue, getOptionData, publicValue, values} = this;
|
||||
|
||||
this.checkUpdateStatus();
|
||||
const vModelValue = (publicValue && this.labelInValue) ?
|
||||
(this.multiple ? publicValue.map(({value}) => value) : publicValue.value) : publicValue;
|
||||
|
||||
if (value === '') this.values = [];
|
||||
else if (checkValuesNotEqual(value,vModelValue,values)) {
|
||||
else if (checkValuesNotEqual(value,publicValue,values)) {
|
||||
this.$nextTick(() => this.values = getInitialValue().map(getOptionData).filter(Boolean));
|
||||
if (!this.multiple) this.dispatch('FormItem', 'on-form-change', this.publicValue);
|
||||
}
|
||||
|
@ -792,15 +837,14 @@
|
|||
const optionInstance = findChild(this, ({$options}) => {
|
||||
return $options.componentName === 'select-item' && $options.propsData.value === optionValue;
|
||||
});
|
||||
if(optionInstance && optionInstance.$el ){
|
||||
let bottomOverflowDistance = optionInstance.$el.getBoundingClientRect().bottom - this.$refs.dropdown.$el.getBoundingClientRect().bottom;
|
||||
let topOverflowDistance = optionInstance.$el.getBoundingClientRect().top - this.$refs.dropdown.$el.getBoundingClientRect().top;
|
||||
if (bottomOverflowDistance > 0) {
|
||||
this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
|
||||
}
|
||||
if (topOverflowDistance < 0) {
|
||||
this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
|
||||
}
|
||||
|
||||
let bottomOverflowDistance = optionInstance.$el.getBoundingClientRect().bottom - this.$refs.dropdown.$el.getBoundingClientRect().bottom;
|
||||
let topOverflowDistance = optionInstance.$el.getBoundingClientRect().top - this.$refs.dropdown.$el.getBoundingClientRect().top;
|
||||
if (bottomOverflowDistance > 0) {
|
||||
this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
|
||||
}
|
||||
if (topOverflowDistance < 0) {
|
||||
this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
|
||||
}
|
||||
},
|
||||
dropVisible(open){
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue