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>
|
||||
<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>
|
||||
<script>
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
@ -22,15 +22,19 @@
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isFocused: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selected: false,
|
||||
index: 0, // for up and down to focus
|
||||
isFocus: false,
|
||||
hidden: false, // for search
|
||||
searchLabel: '', // the value is slot,only for search
|
||||
searchLabel: '', // the slot value (textContent)
|
||||
autoComplete: false
|
||||
};
|
||||
},
|
||||
|
@ -41,53 +45,34 @@
|
|||
{
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-selected`]: this.selected && !this.autoComplete,
|
||||
[`${prefixCls}-focus`]: this.isFocus
|
||||
[`${prefixCls}-focus`]: this.isFocused
|
||||
}
|
||||
];
|
||||
},
|
||||
showLabel () {
|
||||
return (this.label) ? this.label : this.value;
|
||||
},
|
||||
optionLabel(){
|
||||
return (this.$el && this.$el.textContent) || this.label;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select () {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (this.disabled) 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 () {
|
||||
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');
|
||||
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>
|
||||
|
|
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;
|
||||
transition: all @transition-time @ease-in-out;
|
||||
|
||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover, &-focused {
|
||||
.hover();
|
||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
||||
.@{select-prefix-cls}-arrow {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-show-clear &-selection:hover .@{select-prefix-cls}-arrow:nth-of-type(2){
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
.inner-arrow();
|
||||
}
|
||||
|
@ -51,14 +42,9 @@
|
|||
.active();
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-arrow:nth-of-type(2) {
|
||||
.@{select-prefix-cls}-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
&:focus{
|
||||
outline: 0;
|
||||
.@{select-prefix-cls}-selection{
|
||||
.active();
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +52,7 @@
|
|||
.@{select-prefix-cls}-selection {
|
||||
.disabled();
|
||||
|
||||
.@{select-prefix-cls}-arrow:nth-of-type(1) {
|
||||
.@{select-prefix-cls}-arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -74,7 +60,7 @@
|
|||
border-color: @border-color-base;
|
||||
box-shadow: none;
|
||||
|
||||
.@{select-prefix-cls}-arrow:nth-of-type(2) {
|
||||
.@{select-prefix-cls}-arrow {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue