Refactor Select!
This commit is contained in:
parent
aaa96346f4
commit
c9b86944ec
5 changed files with 647 additions and 672 deletions
29
src/components/select/functional-options.vue
Normal file
29
src/components/select/functional-options.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const returnArrayFn = () => [];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: returnArrayFn
|
||||||
|
},
|
||||||
|
slotOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: returnArrayFn
|
||||||
|
},
|
||||||
|
slotUpdateHook: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
functional: true,
|
||||||
|
render(h, {props, parent}){
|
||||||
|
// to detect changes in the $slot children/options we do this hack
|
||||||
|
// so we can trigger the parents computed properties and have everything reactive
|
||||||
|
// although $slot.default is not
|
||||||
|
if (props.slotOptions !== parent.$slots.default) props.slotUpdateHook();
|
||||||
|
return props.options;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<li :class="classes" @click.stop="select" @mouseout.stop="blur" v-show="!hidden"><slot>{{ showLabel }}</slot></li>
|
<li :class="classes" @click.stop="select"><slot>{{ showLabel }}</slot></li>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Emitter from '../../mixins/emitter';
|
import Emitter from '../../mixins/emitter';
|
||||||
|
@ -22,15 +22,19 @@
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isFocused: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
selected: false,
|
searchLabel: '', // the slot value (textContent)
|
||||||
index: 0, // for up and down to focus
|
|
||||||
isFocus: false,
|
|
||||||
hidden: false, // for search
|
|
||||||
searchLabel: '', // the value is slot,only for search
|
|
||||||
autoComplete: false
|
autoComplete: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -41,53 +45,34 @@
|
||||||
{
|
{
|
||||||
[`${prefixCls}-disabled`]: this.disabled,
|
[`${prefixCls}-disabled`]: this.disabled,
|
||||||
[`${prefixCls}-selected`]: this.selected && !this.autoComplete,
|
[`${prefixCls}-selected`]: this.selected && !this.autoComplete,
|
||||||
[`${prefixCls}-focus`]: this.isFocus
|
[`${prefixCls}-focus`]: this.isFocused
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
showLabel () {
|
showLabel () {
|
||||||
return (this.label) ? this.label : this.value;
|
return (this.label) ? this.label : this.value;
|
||||||
|
},
|
||||||
|
optionLabel(){
|
||||||
|
return (this.$el && this.$el.textContent) || this.label;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
select () {
|
select () {
|
||||||
if (this.disabled) {
|
if (this.disabled) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dispatch('iSelect', 'on-select-selected', this.value);
|
this.dispatch('iSelect', 'on-select-selected', {
|
||||||
|
value: this.value,
|
||||||
|
label: this.optionLabel,
|
||||||
|
});
|
||||||
|
this.$emit('on-select-selected', {
|
||||||
|
value: this.value,
|
||||||
|
label: this.optionLabel,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
blur () {
|
|
||||||
this.isFocus = false;
|
|
||||||
},
|
|
||||||
queryChange (val) {
|
|
||||||
const parsedQuery = val.replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, '\\$1');
|
|
||||||
this.hidden = !new RegExp(parsedQuery, 'i').test(this.searchLabel);
|
|
||||||
},
|
|
||||||
// 在使用函数防抖后,设置 key 后,不更新组件了,导致SearchLabel 不更新 #1865
|
|
||||||
updateSearchLabel () {
|
|
||||||
this.searchLabel = this.$el.textContent;
|
|
||||||
},
|
|
||||||
onSelectClose(){
|
|
||||||
this.isFocus = false;
|
|
||||||
},
|
|
||||||
onQueryChange(val){
|
|
||||||
this.queryChange(val);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.updateSearchLabel();
|
|
||||||
this.dispatch('iSelect', 'append');
|
|
||||||
this.$on('on-select-close', this.onSelectClose);
|
|
||||||
this.$on('on-query-change',this.onQueryChange);
|
|
||||||
|
|
||||||
const Select = findComponentUpward(this, 'iSelect');
|
const Select = findComponentUpward(this, 'iSelect');
|
||||||
if (Select) this.autoComplete = Select.autoComplete;
|
if (Select) this.autoComplete = Select.autoComplete;
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
|
||||||
this.dispatch('iSelect', 'remove');
|
|
||||||
this.$off('on-select-close', this.onSelectClose);
|
|
||||||
this.$off('on-query-change',this.onQueryChange);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
196
src/components/select/select-head.vue
Normal file
196
src/components/select/select-head.vue
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<div @click="onHeaderClick">
|
||||||
|
<div class="ivu-tag ivu-tag-checked" v-for="item in selectedMultiple">
|
||||||
|
<span class="ivu-tag-text">{{ item.label }}</span>
|
||||||
|
<Icon type="ios-close-empty" @click.native.stop="removeTag(item)"></Icon>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
:class="singleDisplayClasses"
|
||||||
|
v-show="singleDisplayValue"
|
||||||
|
>{{ singleDisplayValue }}</span>
|
||||||
|
<input
|
||||||
|
:id="inputElementId"
|
||||||
|
type="text"
|
||||||
|
v-if="filterable"
|
||||||
|
v-model="query"
|
||||||
|
:disabled="disabled"
|
||||||
|
:class="[prefixCls + '-input']"
|
||||||
|
:placeholder="showPlaceholder ? localePlaceholder : ''"
|
||||||
|
:style="inputStyle"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
|
@keydown="resetInputState"
|
||||||
|
@keydown.delete="handleInputDelete"
|
||||||
|
@focus="onInputFocus"
|
||||||
|
@blur="onInputFocus"
|
||||||
|
|
||||||
|
ref="input">
|
||||||
|
<Icon type="ios-close" :class="[prefixCls + '-arrow']" v-if="resetSelect" @click.native.stop="resetSelect"></Icon>
|
||||||
|
<Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!resetSelect && !remote && !disabled"></Icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Icon from '../icon';
|
||||||
|
import Emitter from '../../mixins/emitter';
|
||||||
|
import Locale from '../../mixins/locale';
|
||||||
|
|
||||||
|
const prefixCls = 'ivu-select';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'iSelectHead',
|
||||||
|
mixins: [ Emitter, Locale ],
|
||||||
|
components: { Icon },
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
filterable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
initialLabel: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: [Function, Boolean],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
inputElementId: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
queryProp: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
prefixCls: prefixCls,
|
||||||
|
query: '',
|
||||||
|
inputLength: 20,
|
||||||
|
remoteInitialLabel: this.initialLabel,
|
||||||
|
preventRemoteCall: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
singleDisplayClasses(){
|
||||||
|
const {filterable, multiple, showPlaceholder} = this;
|
||||||
|
return [{
|
||||||
|
[prefixCls + '-placeholder']: showPlaceholder && !filterable,
|
||||||
|
[prefixCls + '-selected-value']: !showPlaceholder && !multiple && !filterable,
|
||||||
|
}];
|
||||||
|
},
|
||||||
|
singleDisplayValue(){
|
||||||
|
if ((this.multiple && this.values.length > 0) || this.filterable) return '';
|
||||||
|
return `${this.selectedSingle}` || this.localePlaceholder;
|
||||||
|
},
|
||||||
|
showPlaceholder () {
|
||||||
|
let status = false;
|
||||||
|
if (!this.multiple) {
|
||||||
|
const value = this.values[0];
|
||||||
|
if (typeof value === 'undefined' || String(value).trim() === ''){
|
||||||
|
status = !this.remoteInitialLabel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.values.length > 0) {
|
||||||
|
status = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
},
|
||||||
|
resetSelect(){
|
||||||
|
return !this.showPlaceholder && this.clearable;
|
||||||
|
},
|
||||||
|
inputStyle () {
|
||||||
|
let style = {};
|
||||||
|
|
||||||
|
if (this.multiple) {
|
||||||
|
if (this.showPlaceholder) {
|
||||||
|
style.width = '100%';
|
||||||
|
} else {
|
||||||
|
style.width = `${this.inputLength}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
localePlaceholder () {
|
||||||
|
if (this.placeholder === undefined) {
|
||||||
|
return this.t('i.select.placeholder');
|
||||||
|
} else {
|
||||||
|
return this.placeholder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedSingle(){
|
||||||
|
const selected = this.values[0];
|
||||||
|
return selected ? selected.label : (this.remoteInitialLabel || '');
|
||||||
|
},
|
||||||
|
selectedMultiple(){
|
||||||
|
return this.multiple ? this.values : [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onInputFocus(e){
|
||||||
|
this.$emit(e.type === 'focus' ? 'on-input-focus' : 'on-input-blur');
|
||||||
|
},
|
||||||
|
removeTag (value) {
|
||||||
|
if (this.disabled) return false;
|
||||||
|
this.dispatch('iSelect', 'on-select-selected', value);
|
||||||
|
},
|
||||||
|
resetInputState () {
|
||||||
|
this.inputLength = this.$refs.input.value.length * 12 + 20;
|
||||||
|
},
|
||||||
|
handleInputDelete () {
|
||||||
|
if (this.multiple && this.selectedMultiple.length && this.query === '') {
|
||||||
|
this.removeTag(this.selectedMultiple[this.selectedMultiple.length - 1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHeaderClick(e){
|
||||||
|
if (this.filterable && e.target === this.$el){
|
||||||
|
this.$refs.input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
values ([value]) {
|
||||||
|
if (!this.filterable) return;
|
||||||
|
this.preventRemoteCall = true;
|
||||||
|
if (this.multiple){
|
||||||
|
this.query = '';
|
||||||
|
this.preventRemoteCall = false; // this should be after the query change setter above
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// #982
|
||||||
|
if (typeof value === 'undefined' || value === '' || value === null) this.query = '';
|
||||||
|
else this.query = value.label;
|
||||||
|
},
|
||||||
|
query (val) {
|
||||||
|
if (this.preventRemoteCall) {
|
||||||
|
this.preventRemoteCall = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('on-query-change', val);
|
||||||
|
},
|
||||||
|
queryProp(query){
|
||||||
|
if (query !== this.query) this.query = query;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
File diff suppressed because it is too large
Load diff
|
@ -25,23 +25,14 @@
|
||||||
border: 1px solid @border-color-base;
|
border: 1px solid @border-color-base;
|
||||||
transition: all @transition-time @ease-in-out;
|
transition: all @transition-time @ease-in-out;
|
||||||
|
|
||||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
&:hover, &-focused {
|
||||||
display: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
.hover();
|
||||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
.@{select-prefix-cls}-arrow {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-show-clear &-selection:hover .@{select-prefix-cls}-arrow:nth-of-type(2){
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-arrow {
|
&-arrow {
|
||||||
.inner-arrow();
|
.inner-arrow();
|
||||||
}
|
}
|
||||||
|
@ -51,14 +42,9 @@
|
||||||
.active();
|
.active();
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{select-prefix-cls}-arrow:nth-of-type(2) {
|
.@{select-prefix-cls}-arrow {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
display: inline-block;
|
||||||
}
|
|
||||||
&:focus{
|
|
||||||
outline: 0;
|
|
||||||
.@{select-prefix-cls}-selection{
|
|
||||||
.active();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +52,7 @@
|
||||||
.@{select-prefix-cls}-selection {
|
.@{select-prefix-cls}-selection {
|
||||||
.disabled();
|
.disabled();
|
||||||
|
|
||||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
.@{select-prefix-cls}-arrow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +60,7 @@
|
||||||
border-color: @border-color-base;
|
border-color: @border-color-base;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
.@{select-prefix-cls}-arrow:nth-of-type(2) {
|
.@{select-prefix-cls}-arrow {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue