Commit fed3e09d154d9da3cba89b5872358cea864d7243

Authored by 梁灏
1 parent 1183836a

add AutoComplete component

examples/app.vue
... ... @@ -56,6 +56,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
56 56 <li><router-link to="/notice">Notice</router-link></li>
57 57 <li><router-link to="/avatar">Avatar</router-link></li>
58 58 <li><router-link to="/color-picker">ColorPicker</router-link></li>
  59 + <li><router-link to="/auto-complete">AutoComplete</router-link></li>
59 60 </ul>
60 61 </nav>
61 62 <router-view></router-view>
... ...
examples/main.js
... ... @@ -188,6 +188,10 @@ const router = new VueRouter({
188 188 {
189 189 path: '/color-picker',
190 190 component: require('./routers/color-picker.vue')
  191 + },
  192 + {
  193 + path: '/auto-complete',
  194 + component: require('./routers/auto-complete.vue')
191 195 }
192 196 ]
193 197 });
... ...
examples/routers/auto-complete.vue 0 → 100644
  1 +<template>
  2 + <div style="margin: 100px;width: 200px;">
  3 + <AutoComplete transfer v-model="value" :data="data" @on-change="hc" :filter-method="fm">
  4 + <!--<Option v-for="item in data" :value="item" :label="item" :key="item">-->
  5 + <!--<span style="color: red">{{ item }}</span>-->
  6 + <!--</Option>-->
  7 + </AutoComplete>
  8 + </div>
  9 +</template>
  10 +<script>
  11 +
  12 + export default {
  13 + props: {
  14 +
  15 + },
  16 + data () {
  17 + return {
  18 + value: '',
  19 +// data: [],
  20 + data: ['Burns Bay Road', 'Downing Street', 'Wall Street']
  21 + };
  22 + },
  23 + computed: {},
  24 + methods: {
  25 + handleSearch (value) {
  26 + this.data = !value ? [] : [
  27 + value + '@qq.com',
  28 + value + '@sina.com',
  29 + value + '@163.com'
  30 + ]
  31 + },
  32 + hc (v) {
  33 + console.log(v)
  34 + },
  35 + fm (value, item) {
  36 + return item.toUpperCase().indexOf(value.toUpperCase()) !== -1;
  37 + }
  38 + }
  39 + };
  40 +</script>
0 41 \ No newline at end of file
... ...
src/components/auto-complete/auto-complete.vue 0 → 100644
  1 +<template>
  2 + <i-select
  3 + ref="select"
  4 + class="ivu-auto-complete"
  5 + :label="label"
  6 + :disabled="disabled"
  7 + :clearable="clearable"
  8 + :placeholder="placeholder"
  9 + :size="size"
  10 + filterable
  11 + remote
  12 + auto-complete
  13 + :remote-method="remoteMethod"
  14 + @on-change="handleChange"
  15 + :transfer="transfer">
  16 + <slot name="input">
  17 + <i-input
  18 + ref="input"
  19 + slot="input"
  20 + v-model="currentValue"
  21 + :placeholder="placeholder"
  22 + :disabled="disabled"
  23 + :size="size"
  24 + :icon="closeIcon"
  25 + @on-click="handleClear"
  26 + @on-focus="handleFocus"
  27 + @on-blur="handleBlur"></i-input>
  28 + </slot>
  29 + <slot>
  30 + <i-option v-for="item in filteredData" :value="item" :key="item">{{ item }}</i-option>
  31 + </slot>
  32 + </i-select>
  33 +</template>
  34 +<script>
  35 + import iSelect from '../select/select.vue';
  36 + import iOption from '../select/option.vue';
  37 + import iInput from '../input/input.vue';
  38 + import { oneOf } from '../../utils/assist';
  39 +
  40 + export default {
  41 + name: 'AutoComplete',
  42 + components: { iSelect, iOption, iInput },
  43 + props: {
  44 + value: {
  45 + type: [String, Number],
  46 + default: ''
  47 + },
  48 + label: {
  49 + type: [String, Number],
  50 + default: ''
  51 + },
  52 + data: {
  53 + type: Array,
  54 + default: () => []
  55 + },
  56 + disabled: {
  57 + type: Boolean,
  58 + default: false
  59 + },
  60 + clearable: {
  61 + type: Boolean,
  62 + default: false
  63 + },
  64 + placeholder: {
  65 + type: String
  66 + },
  67 + size: {
  68 + validator (value) {
  69 + return oneOf(value, ['small', 'large', 'default']);
  70 + }
  71 + },
  72 + filterMethod: {
  73 + type: [Function, Boolean],
  74 + default: false
  75 + },
  76 + transfer: {
  77 + type: Boolean,
  78 + default: false
  79 + }
  80 + },
  81 + data () {
  82 + return {
  83 + currentValue: this.value
  84 + };
  85 + },
  86 + computed: {
  87 + closeIcon () {
  88 + return this.clearable && this.currentValue ? 'ios-close' : '';
  89 + },
  90 + filteredData () {
  91 + if (this.filterMethod) {
  92 + return this.data.filter(item => this.filterMethod(this.currentValue, item));
  93 + } else {
  94 + return this.data;
  95 + }
  96 + }
  97 + },
  98 + watch: {
  99 + value (val) {
  100 + this.currentValue = val;
  101 + },
  102 + currentValue (val) {
  103 + this.$refs.select.query = val;
  104 + this.$emit('input', val);
  105 + this.$emit('on-change', val);
  106 + }
  107 + },
  108 + methods: {
  109 + remoteMethod (query) {
  110 + this.$emit('on-search', query);
  111 + },
  112 + handleChange (val) {
  113 + this.currentValue = val;
  114 + this.$refs.select.model = val;
  115 + this.$refs.input.blur();
  116 + this.$emit('on-select', val);
  117 + },
  118 + handleFocus () {
  119 + this.$refs.select.visible = true;
  120 + },
  121 + handleBlur () {
  122 + this.$refs.select.visible = false;
  123 + },
  124 + handleClear () {
  125 + this.currentValue = '';
  126 + this.$refs.select.model = '';
  127 + }
  128 + }
  129 + };
  130 +</script>
0 131 \ No newline at end of file
... ...
src/components/auto-complete/index.js 0 → 100644
  1 +import AutoComplete from './auto-complete.vue';
  2 +export default AutoComplete;
0 3 \ No newline at end of file
... ...
src/components/input/input.vue
... ... @@ -212,12 +212,19 @@
212 212  
213 213 this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
214 214 },
215   - focus() {
  215 + focus () {
216 216 if (this.type === 'textarea') {
217 217 this.$refs.textarea.focus();
218 218 } else {
219 219 this.$refs.input.focus();
220 220 }
  221 + },
  222 + blur () {
  223 + if (this.type === 'textarea') {
  224 + this.$refs.textarea.blur();
  225 + } else {
  226 + this.$refs.input.blur();
  227 + }
221 228 }
222 229 },
223 230 watch: {
... ...
src/components/select/option.vue
... ... @@ -3,6 +3,7 @@
3 3 </template>
4 4 <script>
5 5 import Emitter from '../../mixins/emitter';
  6 + import { findComponentUpward } from '../../utils/assist';
6 7  
7 8 const prefixCls = 'ivu-select-item';
8 9  
... ... @@ -29,7 +30,8 @@
29 30 index: 0, // for up and down to focus
30 31 isFocus: false,
31 32 hidden: false, // for search
32   - searchLabel: '' // the value is slot,only for search
  33 + searchLabel: '', // the value is slot,only for search
  34 + autoComplete: false
33 35 };
34 36 },
35 37 computed: {
... ... @@ -38,7 +40,7 @@
38 40 `${prefixCls}`,
39 41 {
40 42 [`${prefixCls}-disabled`]: this.disabled,
41   - [`${prefixCls}-selected`]: this.selected,
  43 + [`${prefixCls}-selected`]: this.selected && !this.autoComplete,
42 44 [`${prefixCls}-focus`]: this.isFocus
43 45 }
44 46 ];
... ... @@ -72,6 +74,9 @@
72 74 this.$on('on-query-change', (val) => {
73 75 this.queryChange(val);
74 76 });
  77 +
  78 + const Select = findComponentUpward(this, 'iSelect');
  79 + if (Select) this.autoComplete = Select.autoComplete;
75 80 },
76 81 beforeDestroy () {
77 82 this.dispatch('iSelect', 'remove');
... ...
src/components/select/select.vue
1 1 <template>
2 2 <div :class="classes" v-clickoutside="handleClose">
3 3 <div
4   - :class="[prefixCls + '-selection']"
  4 + :class="selectionCls"
5 5 ref="reference"
6 6 @click="toggleMenu">
7   - <div class="ivu-tag" v-for="(item, index) in selectedMultiple">
8   - <span class="ivu-tag-text">{{ item.label }}</span>
9   - <Icon type="ios-close-empty" @click.native.stop="removeTag(index)"></Icon>
10   - </div>
11   - <span :class="[prefixCls + '-placeholder']" v-show="showPlaceholder && !filterable">{{ localePlaceholder }}</span>
12   - <span :class="[prefixCls + '-selected-value']" v-show="!showPlaceholder && !multiple && !filterable">{{ selectedSingle }}</span>
13   - <input
14   - type="text"
15   - v-if="filterable"
16   - v-model="query"
17   - :disabled="disabled"
18   - :class="[prefixCls + '-input']"
19   - :placeholder="showPlaceholder ? localePlaceholder : ''"
20   - :style="inputStyle"
21   - @blur="handleBlur"
22   - @keydown="resetInputState"
23   - @keydown.delete="handleInputDelete"
24   - ref="input">
25   - <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSingleSelect"></Icon>
26   - <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!remote"></Icon>
  7 + <slot name="input">
  8 + <div class="ivu-tag" v-for="(item, index) in selectedMultiple">
  9 + <span class="ivu-tag-text">{{ item.label }}</span>
  10 + <Icon type="ios-close-empty" @click.native.stop="removeTag(index)"></Icon>
  11 + </div>
  12 + <span :class="[prefixCls + '-placeholder']" v-show="showPlaceholder && !filterable">{{ localePlaceholder }}</span>
  13 + <span :class="[prefixCls + '-selected-value']" v-show="!showPlaceholder && !multiple && !filterable">{{ selectedSingle }}</span>
  14 + <input
  15 + type="text"
  16 + v-if="filterable"
  17 + v-model="query"
  18 + :disabled="disabled"
  19 + :class="[prefixCls + '-input']"
  20 + :placeholder="showPlaceholder ? localePlaceholder : ''"
  21 + :style="inputStyle"
  22 + @blur="handleBlur"
  23 + @keydown="resetInputState"
  24 + @keydown.delete="handleInputDelete"
  25 + ref="input">
  26 + <Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSingleSelect"></Icon>
  27 + <Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!remote"></Icon>
  28 + </slot>
27 29 </div>
28 30 <transition :name="transitionName">
29 31 <Drop
... ... @@ -33,7 +35,7 @@
33 35 ref="dropdown"
34 36 :data-transfer="transfer"
35 37 v-transfer-dom>
36   - <ul v-show="notFountShow" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
  38 + <ul v-show="notFoundShow" :class="[prefixCls + '-not-found']"><li>{{ localeNotFoundText }}</li></ul>
37 39 <ul v-show="(!notFound && !remote) || (remote && !loading && !notFound)" :class="[prefixCls + '-dropdown-list']"><slot></slot></ul>
38 40 <ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul>
39 41 </Drop>
... ... @@ -123,6 +125,11 @@
123 125 transfer: {
124 126 type: Boolean,
125 127 default: false
  128 + },
  129 + // Use for AutoComplete
  130 + autoComplete: {
  131 + type: Boolean,
  132 + default: false
126 133 }
127 134 },
128 135 data () {
... ... @@ -161,7 +168,13 @@
161 168 dropdownCls () {
162 169 return {
163 170 [prefixCls + '-dropdown-transfer']: this.transfer,
164   - [prefixCls + '-multiple']: this.multiple && this.transfer
  171 + [prefixCls + '-multiple']: this.multiple && this.transfer,
  172 + ['ivu-auto-complete']: this.autoComplete,
  173 + };
  174 + },
  175 + selectionCls () {
  176 + return {
  177 + [`${prefixCls}-selection`]: !this.autoComplete
165 178 };
166 179 },
167 180 showPlaceholder () {
... ... @@ -225,16 +238,19 @@
225 238 let status = true;
226 239 const options = this.$slots.default || [];
227 240 if (!this.loading && this.remote && this.query === '' && !options.length) status = false;
  241 +
  242 + if (this.autoComplete && !options.length) status = false;
  243 +
228 244 return this.visible && status;
229 245 },
230   - notFountShow () {
  246 + notFoundShow () {
231 247 const options = this.$slots.default || [];
232 248 return (this.notFound && !this.remote) || (this.remote && !this.loading && !options.length);
233 249 }
234 250 },
235 251 methods: {
236 252 toggleMenu () {
237   - if (this.disabled) {
  253 + if (this.disabled || this.autoComplete) {
238 254 return false;
239 255 }
240 256 this.visible = !this.visible;
... ... @@ -543,6 +559,7 @@
543 559 },
544 560 handleBlur () {
545 561 setTimeout(() => {
  562 + if (this.autoComplete) return;
546 563 const model = this.model;
547 564  
548 565 if (this.multiple) {
... ... @@ -737,7 +754,7 @@
737 754 if (this.multiple) {
738 755 this.$refs.input.focus();
739 756 } else {
740   - this.$refs.input.select();
  757 + if (!this.autoComplete) this.$refs.input.select();
741 758 }
742 759 if (this.remote) {
743 760 this.findChild(child => {
... ... @@ -753,7 +770,7 @@
753 770 this.broadcast('Drop', 'on-update-popper');
754 771 } else {
755 772 if (this.filterable) {
756   - this.$refs.input.blur();
  773 + if (!this.autoComplete) this.$refs.input.blur();
757 774 // #566 reset options visible
758 775 setTimeout(() => {
759 776 this.broadcastQuery('');
... ...
src/index.js
... ... @@ -3,6 +3,7 @@ import &#39;core-js/fn/array/find-index&#39;;
3 3  
4 4 import Affix from './components/affix';
5 5 import Alert from './components/alert';
  6 +import AutoComplete from './components/auto-complete';
6 7 import Avatar from './components/avatar';
7 8 import BackTop from './components/back-top';
8 9 import Badge from './components/badge';
... ... @@ -51,6 +52,7 @@ import locale from &#39;./locale&#39;;
51 52 const iview = {
52 53 Affix,
53 54 Alert,
  55 + AutoComplete,
54 56 Avatar,
55 57 BackTop,
56 58 Badge,
... ...
src/styles/components/auto-complete.less 0 → 100644
  1 +@auto-complete-prefix-cls: ~"@{css-prefix}auto-complete";
  2 +
  3 +.@{auto-complete-prefix-cls} {
  4 + .@{select-prefix-cls} {
  5 + &-not-found{
  6 + display: none;
  7 + }
  8 + }
  9 + .@{icon-prefix-cls}-ios-close{
  10 + display: none;
  11 + }
  12 + &:hover .@{icon-prefix-cls}-ios-close{
  13 + display: inline-block;
  14 + }
  15 +}
0 16 \ No newline at end of file
... ...
src/styles/components/index.less
... ... @@ -40,4 +40,5 @@
40 40 @import "upload";
41 41 @import "tree";
42 42 @import "avatar";
43   -@import "color-picker";
44 43 \ No newline at end of file
  44 +@import "color-picker";
  45 +@import "auto-complete";
45 46 \ No newline at end of file
... ...