Merge pull request #32 from eleme/feat-next/upgrade-select-and-input

update select and input
pull/2/head
baiyaaaaa 2016-07-29 11:33:19 +08:00 committed by GitHub
commit 8de3a3158c
8 changed files with 163 additions and 128 deletions

View File

@ -1,8 +1,5 @@
<script>
export default {
created() {
this.template = '<span>label: {{ label }}, value: {{ value }}</span>';
},
data() {
return {
options: [{
@ -128,13 +125,13 @@
## 基本用法
<el-select :value.sync="value">
<el-select v-model="value">
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value">
<el-select v-model="value">
<el-option
v-for="item in options"
:label="item.label"
@ -172,13 +169,13 @@
## 禁用状态
<el-select :value.sync="value2" disabled>
<el-select v-model="value2" disabled>
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value2" disabled>
<el-select v-model="value2" disabled>
<el-option
v-for="item in options"
:label="item.label"
@ -216,13 +213,13 @@
## 有禁用选项
<el-select :value.sync="value3">
<el-select v-model="value3">
<el-option v-for="item in options2" :label="item.label" :value="item.value" :disabled="item.disabled"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value3">
<el-select v-model="value3">
<el-option
v-for="item in options2"
:label="item.label"
@ -262,13 +259,13 @@
## 可清空单选
<el-select :value.sync="value4" clearable>
<el-select v-model="value4" clearable>
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value4" clearable>
<el-select v-model="value4" clearable>
<el-option
v-for="item in options"
:label="item.label"
@ -306,7 +303,7 @@
## 指定初始被选项
<el-select :value.sync="value5">
<el-select v-model="value5">
<el-option v-for="item in options" :label="item.label" :value="item.value" :selected="item.value === '选项2'"></el-option>
</el-select>
@ -314,7 +311,7 @@
```html
<template>
<el-select :value.sync="value5">
<el-select v-model="value5">
<el-option
v-for="item in options"
:label="item.label"
@ -355,7 +352,7 @@
```html
<template>
<el-select :value.sync="value5">
<el-select v-model="value5">
<el-option
v-for="item in options"
:label="item.label"
@ -393,32 +390,29 @@
## 自定义模板
<el-select :value.sync="value6">
<el-select v-model="value6">
<el-option
v-for="item in options"
:label="item.label"
:value="item.value"
:template="template">
:value="item.value">
<span>label: {{ item.label }}, value: {{ item.value }}</span>
</el-option>
</el-select>
```html
<template>
<el-select :value.sync="value6">
<el-select v-model="value6">
<el-option
v-for="item in options"
:label="item.label"
:value="item.value"
:template="optionTemplate">
:value="item.value">
<span>label: {{ item.label }}, value: {{ item.value }}</span>
</el-option>
</el-select>
</template>
<script>
export default {
cerated () {
this.optionTemplate = '<span>label: {{ label }}, value: {{ value }}</span>';
},
data() {
return {
options: [{
@ -446,7 +440,7 @@
## 多选
<el-select :value.sync="value7" multiple>
<el-select v-model="value7" multiple>
<el-option
v-for="item in options"
:label="item.label"
@ -456,7 +450,7 @@
```html
<template>
<el-select :value.sync="value7" multiple>
<el-select v-model="value7" multiple>
<el-option
v-for="item in options"
:label="item.label"
@ -494,13 +488,13 @@
## 自定义宽度
<el-select :value.sync="value8" :width="300" :dropdown-width="350">
<el-select v-model="value8" :width="300" :dropdown-width="350">
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value8" :width="300" :dropdown-width="350">
<el-select v-model="value8" :width="300" :dropdown-width="350">
<el-option
v-for="item in options"
:label="item.label"
@ -538,7 +532,7 @@
## 分组
<el-select :value.sync="value9">
<el-select v-model="value9">
<el-option-group v-for="group in options3" :label="group.label">
<el-option v-for="item in group.options" :label="item.label" :value="item.value"></el-option>
</el-option-group>
@ -546,7 +540,7 @@
```html
<template>
<el-select :value.sync="value9">
<el-select v-model="value9">
<el-option-group v-for="group in options3" :label="group.label">
<el-option
v-for="item in group.options"
@ -592,13 +586,13 @@
## 选项较多时的交互
<el-select :value.sync="value10">
<el-select v-model="value10">
<el-option v-for="item in options4" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value10">
<el-select v-model="value10">
<el-option
v-for="item in options4"
:label="item.label"
@ -654,13 +648,13 @@
## 可搜索
<el-select :value.sync="value11" filterable>
<el-select v-model="value11" filterable>
<el-option v-for="item in options" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select :value.sync="value11" filterable>
<el-select v-model="value11" filterable>
<el-option
v-for="item in options"
:label="item.label"
@ -698,14 +692,14 @@
## 服务端搜索
<el-select :value.sync="value12" multiple filterable remote :remote-method="remoteMethod" :loading="loading">
<el-select v-model="value12" multiple filterable remote :remote-method="remoteMethod" :loading="loading">
<el-option v-for="item in options5" :label="item.label" :value="item.value"></el-option>
</el-select>
```html
<template>
<el-select
:value.sync="value12"
v-model="value12"
multiple
filterable
remote
@ -776,4 +770,3 @@
| label | 选项的标签,若不设置则默认与 `value` 相同 | string/number | | |
| disabled | 是否禁用该选项 | boolean | | false |
| selected | 选项是否被初始选中 | boolean | | false |
| template | 选项的自定义模板 | String | | `<span>{{ label }}</span>` |

View File

@ -12,20 +12,20 @@
:name="name"
class="el-input__inner"
:placeholder="placeholder"
v-model="value"
v-model="currentValue"
:disabled="disabled"
:readonly="readonly"
@focus="$emit('onfocus', value)"
@focus="$emit('onfocus', currentValue)"
@blur="handleBlur"
:number="number"
:maxlength="maxlength"
:minlength="minlength"
:autocomplete="autoComplete"
v-el:input
ref="input"
>
</template>
<!-- 写成垂直的方式会导致 placeholder 失效, 蜜汁bug -->
<textarea v-else v-model="value" class="el-textarea__inner" :name="name" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" @focus="$emit('onfocus', value)" @blur="handleBlur"></textarea>
<textarea v-else v-model="currentValue" class="el-textarea__inner" :name="name" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" @focus="$emit('onfocus', val)" @blur="handleBlur"></textarea>
</div>
</template>
<script>
@ -37,9 +37,7 @@
mixins: [emitter],
props: {
value: {
required: true
},
value: {},
placeholder: {
type: String,
default: ''
@ -79,26 +77,43 @@
maxlength: Number,
minlength: Number
},
events: {
inputSelect() {
this.$els.input.select();
}
},
methods: {
handleBlur(event) {
this.$emit('onblur', this.value);
this.dispatch('form-item', 'el.form.blur', [this.value]);
this.$emit('onblur', this.currentValue);
this.dispatch('form-item', 'el.form.blur', [this.currentValue]);
},
inputSelect() {
this.$refs.input.select();
}
},
data() {
return {
currentValue: ''
};
},
created() {
this.$on('inputSelect', this.inputSelect);
},
computed: {
validating() {
return this.$parent.validating;
}
},
watch: {
'value'(val) {
this.$emit('onchange', this.value);
this.dispatch('form-item', 'el.form.change', this.value);
this.currentValue = val;
},
'currentValue'(val) {
this.$emit('input', val);
this.$emit('onchange', val);
this.dispatch('form-item', 'el.form.change', val);
}
}
};

View File

@ -28,14 +28,16 @@
if (this.popper) {
this.popper.update();
} else {
this.popper = new Popper(this.$parent.$els.reference, this.$el, {
gpuAcceleration: false,
placement: 'bottom-start',
boundariesPadding: 0,
forceAbsolute: true
});
this.popper.onCreate(popper => {
this.resetTransformOrigin(popper);
this.$nextTick(() => {
this.popper = new Popper(this.$parent.$refs.reference.$el, this.$el, {
gpuAcceleration: false,
placement: 'bottom-start',
boundariesPadding: 0,
forceAbsolute: true
});
this.popper.onCreate(popper => {
this.resetTransformOrigin(popper);
});
});
}
},

View File

@ -1,10 +1,12 @@
<template>
<li class="el-select-group__title">{{ label }}</li>
<li>
<ul class="el-select-group">
<slot></slot>
</ul>
</li>
<ul class="el-select-group__wrap">
<li class="el-select-group__title">{{ label }}</li>
<li>
<ul class="el-select-group">
<slot></slot>
</ul>
</li>
</ul>
</template>
<script type="text/babel">

View File

@ -5,7 +5,9 @@
class="el-select-dropdown__item"
v-show="queryPassed"
:class="{ 'selected': itemSelected(), 'is-disabled': disabled, 'hover': parent.hoverIndex === index }">
<include></include>
<slot>
<span>{{ label }}</span>
</slot>
</li>
</template>
@ -32,10 +34,6 @@
disabled: {
type: Boolean,
default: false
},
template: {
type: String,
default: '<span>{{ label }}</span>'
}
},
@ -48,18 +46,20 @@
};
},
computed: {
currentSelected() {
return this.selected || (this.$parent.multiple ? this.$parent.value.indexOf(this.value) > -1 : this.$parent.value === this.value);
}
},
watch: {
selected(val) {
currentSelected(val) {
if (val === true) {
this.dispatch('select', 'addOptionToValue', this);
}
}
},
partials: {
'el-selectmenu-default': '<span>{{ label }}</span>'
},
methods: {
disableOptions() {
this.disabled = true;
@ -94,22 +94,17 @@
},
created() {
// Vue 1.x 2.0
this.$options._linkerCachable = false;
this.parent = this.$parent;
while (!this.parent.isSelect) {
this.parent = this.parent.$parent;
}
this.label = this.label || ((typeof this.value === 'string' || typeof this.value === 'number') ? this.value : '');
this.selected = this.selected || (this.parent.multiple ? this.parent.value.indexOf(this.value) > -1 : this.parent.value === this.value);
this.parent.options.push(this);
this.parent.optionsCount++;
this.parent.filteredOptionsCount++;
this.index = this.parent.options.indexOf(this);
this.$options.template = this.$options.template.replace(/<include><\/include>/g, this.template);
if (this.selected === true) {
if (this.currentSelected === true) {
this.dispatch('select', 'addOptionToValue', this);
}

View File

@ -1,12 +1,12 @@
<template>
<div class="el-select" :class="{ 'is-multiple': multiple, 'is-small': size === 'small' }">
<div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" v-el:tags :style="{ 'max-width': width - 36 + 'px' }">
<div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" ref="tags" :style="{ 'max-width': inputWidth - 36 + 'px' }">
<el-tag
v-for="item in selected"
closable
:hit="item.hitState"
type="primary"
@click="deleteTag($event, item)"
@click.native="deleteTag($event, item)"
close-transition>{{ item.label }}</el-tag>
<input
@ -23,33 +23,33 @@
:debounce="remote ? 300 : 0"
v-if="filterable"
:style="{ width: inputLength + 'px' }"
v-el:input>
ref="input">
</div>
<el-input
v-el:reference
:value.sync="selectedLabel"
ref="reference"
v-model="selectedLabel"
type="text"
:placeholder="placeholder"
:placeholder="currentPlaceholder"
:name="name"
:disabled="disabled"
:readonly="!filterable || multiple"
@click="toggleMenu"
@keyup="debouncedOnInputChange"
@keydown.down.prevent="navigateOptions('next')"
@keydown.up.prevent="navigateOptions('prev')"
@keydown.enter.prevent="selectOption"
@keydown.esc.prevent="visible = false"
@keydown.tab="visible = false"
@mouseenter="inputHovering = true"
@mouseleave="inputHovering = false"
@click.native="toggleMenu"
@keyup.native="debouncedOnInputChange"
@keydown.native.down.prevent="navigateOptions('next')"
@keydown.native.up.prevent="navigateOptions('prev')"
@keydown.native.enter.prevent="selectOption"
@keydown.native.esc.prevent="visible = false"
@keydown.native.tab="visible = false"
@mouseenter.native="inputHovering = true"
@mouseleave.native="inputHovering = false"
:icon="showCloseIcon ? 'circle-close' : 'caret-top'"
:style="{ 'width': width + 'px' }"
v-element-clickoutside="visible = false">
:style="{ 'width': inputWidth + 'px' }"
v-element-clickoutside="handleClose">
</el-input>
<el-select-menu
v-el:popper
ref="popper"
v-show="visible && nodataText !== false"
transition="md-fade-bottom"
transition="fade"
:style="{ 'width': dropdownWidth ? dropdownWidth + 'px' : '100%' }">
<ul class="el-select-dropdown__list" v-show="options.length > 0 && filteredOptionsCount > 0">
<slot></slot>
@ -65,6 +65,7 @@
import ElSelectMenu from 'packages/select-dropdown/index.js';
import ElTag from 'packages/tag/index.js';
import debounce from 'throttle-debounce/debounce';
import Clickoutside from 'main/utils/clickoutside';
export default {
mixins: [emitter],
@ -80,6 +81,8 @@
showCloseIcon() {
let criteria = this.clearable && this.inputHovering && !this.multiple && this.options.indexOf(this.selected) > -1;
if (!this.$el) return false;
let icon = this.$el.querySelector('.el-input__icon');
if (icon) {
if (criteria) {
@ -109,6 +112,14 @@
}
}
return null;
},
inputWidth() {
if (!this.width) {
return this.multiple ? 220 : 180;
}
return this.width;
}
},
@ -119,7 +130,7 @@
},
directives: {
ElementClickoutside: require('vue-clickoutside')
ElementClickoutside: Clickoutside
},
props: {
@ -161,7 +172,8 @@
voidRemoteQuery: false,
bottomOverflowBeforeHidden: 0,
optionsAllDisabled: false,
inputHovering: false
inputHovering: false,
currentPlaceholder: ''
};
},
@ -172,7 +184,12 @@
}
},
placeholder(val) {
this.currentPlaceholder = val;
},
value(val) {
this.$emit('input', val);
this.$emit('change', val);
if (this.valueChangeBySelected) {
this.valueChangeBySelected = false;
@ -211,11 +228,11 @@
return;
}
this.valueChangeBySelected = true;
this.value = val.map(item => item.value);
this.$emit('input', val.map(item => item.value));
if (this.selected.length > 0) {
this.placeholder = '';
this.currentPlaceholder = '';
} else {
this.placeholder = this.cachedPlaceHolder;
this.currentPlaceholder = this.cachedPlaceHolder;
}
this.$nextTick(() => {
this.resetInputHeight();
@ -223,17 +240,20 @@
if (this.filterable) {
this.query = '';
this.hoverIndex = -1;
this.$els.input.focus();
this.$refs.input.focus();
this.inputLength = 20;
}
} else {
if (val.value) {
this.value = val.value;
this.$emit('input', val.value);
}
}
},
query(val) {
this.$nextTick(() => {
this.broadcast('select-dropdown', 'updatePopper');
});
if (this.multiple && this.filterable) {
this.resetInputHeight();
}
@ -258,13 +278,13 @@
if (!val) {
this.$el.querySelector('.el-input__icon').classList.remove('is-reverse');
this.broadcast('select-dropdown', 'destroyPopper');
if (this.$els.input) {
this.$els.input.blur();
if (this.$refs.input) {
this.$refs.input.blur();
}
this.resetHoverIndex();
if (!this.multiple) {
if (this.dropdownUl && this.selected.$el) {
this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$els.popper.getBoundingClientRect().bottom;
this.bottomOverflowBeforeHidden = this.selected.$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
}
if (this.selected && this.selected.value) {
this.selectedLabel = this.selected.label;
@ -282,13 +302,13 @@
if (this.filterable) {
this.query = this.selectedLabel;
if (this.multiple) {
this.$els.input.focus();
this.$refs.input.focus();
} else {
this.broadcast('input', 'inputSelect');
}
}
if (!this.dropdownUl) {
let dropdownChildNodes = this.$els.popper.childNodes;
let dropdownChildNodes = this.$refs.popper.$el.childNodes;
this.dropdownUl = [].filter.call(dropdownChildNodes, item => item.tagName === 'UL')[0];
}
if (!this.multiple && this.dropdownUl) {
@ -305,6 +325,10 @@
},
methods: {
handleClose() {
this.visible = false;
},
toggleLastOptionHitState(hit) {
if (!Array.isArray(this.selected)) return;
const option = this.selected[this.selected.length - 1];
@ -340,21 +364,21 @@
},
managePlaceholder() {
if (this.placeholder !== '') {
this.placeholder = this.$els.input.value ? '' : this.cachedPlaceHolder;
if (this.currentPlaceholder !== '') {
this.currentPlaceholder = this.$refs.input.value ? '' : this.cachedPlaceHolder;
}
},
resetInputState(e) {
if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
this.inputLength = this.$els.input.value.length * 12 + 20;
this.inputLength = this.$refs.input.value.length * 12 + 20;
},
resetInputHeight() {
this.$nextTick(() => {
let inputChildNodes = this.$els.reference.childNodes;
let inputChildNodes = this.$refs.reference.$el.childNodes;
let input = [].filter.call(inputChildNodes, item => item.tagName === 'INPUT')[0];
input.style.height = Math.max(this.$els.tags.clientHeight + 6, this.size === 'small' ? 28 : 36) + 'px';
input.style.height = Math.max(this.$refs.tags.clientHeight + 6, this.size === 'small' ? 28 : 36) + 'px';
this.broadcast('select-dropdown', 'updatePopper');
});
},
@ -429,8 +453,8 @@
},
resetScrollTop() {
let bottomOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().bottom - this.$els.popper.getBoundingClientRect().bottom;
let topOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().top - this.$els.popper.getBoundingClientRect().top;
let bottomOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().bottom - this.$refs.popper.$el.getBoundingClientRect().bottom;
let topOverflowDistance = this.options[this.hoverIndex].$el.getBoundingClientRect().top - this.$refs.popper.$el.getBoundingClientRect().top;
if (bottomOverflowDistance > 0) {
this.dropdownUl.scrollTop += bottomOverflowDistance;
}
@ -468,14 +492,12 @@
},
created() {
this.cachedPlaceHolder = this.placeholder;
this.cachedPlaceHolder = this.currentPlaceholder = this.placeholder;
if (this.multiple) {
this.selectedInit = true;
this.selected = [];
}
if (!this.width) {
this.width = this.multiple ? 220 : 180;
}
this.debouncedOnInputChange = debounce(this.debounce, () => {
this.onInputChange();
});

View File

@ -7,6 +7,12 @@
margin: 0;
padding: 0;
@e wrap {
list-style: none;
margin: 0;
padding: 0;
}
@e title {
padding-left: 10px;
font-size: 12px;

View File

@ -1,8 +1,8 @@
import PopperJS from './popper';
/**
* @param {HTMLElement} [reference=$els.reference] - The reference element used to position the popper.
* @param {HTMLElement} [popper=$els.popper] - The HTML element used as popper, or a configuration used to generate the popper.
* @param {HTMLElement} [reference=$refs.reference] - The reference element used to position the popper.
* @param {HTMLElement} [popper=$refs.popper] - The HTML element used as popper, or a configuration used to generate the popper.
* @param {String} [placement=button] - Placement of the popper accepted values: top(-start, -end), right(-start, -end), bottom(-start, -right), left(-start, -end)
* @param {Number} [offset=0] - Amount of pixels the popper will be shifted (can be negative).
* @param {Boolean} [visible=false] Visibility of the popup element.
@ -47,8 +47,8 @@ export default {
return;
}
this.popper = this.popper || this.$els.popper;
this.reference = this.reference || this.$els.reference;
this.popper = this.popper || this.$refs.popper;
this.reference = this.reference || this.$refs.reference;
if (!this.popper || !this.reference) {
return;