This commit is contained in:
prefert 2020-09-03 15:53:31 +08:00
parent 8f9d3604e6
commit 5bb83ff8e5
258 changed files with 12974 additions and 7838 deletions

View file

@ -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){