Commit fed3e09d154d9da3cba89b5872358cea864d7243

Authored by 梁灏
1 parent 1183836a

add AutoComplete component

@@ -56,6 +56,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; } @@ -56,6 +56,7 @@ li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
56 <li><router-link to="/notice">Notice</router-link></li> 56 <li><router-link to="/notice">Notice</router-link></li>
57 <li><router-link to="/avatar">Avatar</router-link></li> 57 <li><router-link to="/avatar">Avatar</router-link></li>
58 <li><router-link to="/color-picker">ColorPicker</router-link></li> 58 <li><router-link to="/color-picker">ColorPicker</router-link></li>
  59 + <li><router-link to="/auto-complete">AutoComplete</router-link></li>
59 </ul> 60 </ul>
60 </nav> 61 </nav>
61 <router-view></router-view> 62 <router-view></router-view>
@@ -188,6 +188,10 @@ const router = new VueRouter({ @@ -188,6 +188,10 @@ const router = new VueRouter({
188 { 188 {
189 path: '/color-picker', 189 path: '/color-picker',
190 component: require('./routers/color-picker.vue') 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 3 \ No newline at end of file
src/components/input/input.vue
@@ -212,12 +212,19 @@ @@ -212,12 +212,19 @@
212 212
213 this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); 213 this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
214 }, 214 },
215 - focus() { 215 + focus () {
216 if (this.type === 'textarea') { 216 if (this.type === 'textarea') {
217 this.$refs.textarea.focus(); 217 this.$refs.textarea.focus();
218 } else { 218 } else {
219 this.$refs.input.focus(); 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 watch: { 230 watch: {
src/components/select/option.vue
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 </template> 3 </template>
4 <script> 4 <script>
5 import Emitter from '../../mixins/emitter'; 5 import Emitter from '../../mixins/emitter';
  6 + import { findComponentUpward } from '../../utils/assist';
6 7
7 const prefixCls = 'ivu-select-item'; 8 const prefixCls = 'ivu-select-item';
8 9
@@ -29,7 +30,8 @@ @@ -29,7 +30,8 @@
29 index: 0, // for up and down to focus 30 index: 0, // for up and down to focus
30 isFocus: false, 31 isFocus: false,
31 hidden: false, // for search 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 computed: { 37 computed: {
@@ -38,7 +40,7 @@ @@ -38,7 +40,7 @@
38 `${prefixCls}`, 40 `${prefixCls}`,
39 { 41 {
40 [`${prefixCls}-disabled`]: this.disabled, 42 [`${prefixCls}-disabled`]: this.disabled,
41 - [`${prefixCls}-selected`]: this.selected, 43 + [`${prefixCls}-selected`]: this.selected && !this.autoComplete,
42 [`${prefixCls}-focus`]: this.isFocus 44 [`${prefixCls}-focus`]: this.isFocus
43 } 45 }
44 ]; 46 ];
@@ -72,6 +74,9 @@ @@ -72,6 +74,9 @@
72 this.$on('on-query-change', (val) => { 74 this.$on('on-query-change', (val) => {
73 this.queryChange(val); 75 this.queryChange(val);
74 }); 76 });
  77 +
  78 + const Select = findComponentUpward(this, 'iSelect');
  79 + if (Select) this.autoComplete = Select.autoComplete;
75 }, 80 },
76 beforeDestroy () { 81 beforeDestroy () {
77 this.dispatch('iSelect', 'remove'); 82 this.dispatch('iSelect', 'remove');
src/components/select/select.vue
1 <template> 1 <template>
2 <div :class="classes" v-clickoutside="handleClose"> 2 <div :class="classes" v-clickoutside="handleClose">
3 <div 3 <div
4 - :class="[prefixCls + '-selection']" 4 + :class="selectionCls"
5 ref="reference" 5 ref="reference"
6 @click="toggleMenu"> 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 </div> 29 </div>
28 <transition :name="transitionName"> 30 <transition :name="transitionName">
29 <Drop 31 <Drop
@@ -33,7 +35,7 @@ @@ -33,7 +35,7 @@
33 ref="dropdown" 35 ref="dropdown"
34 :data-transfer="transfer" 36 :data-transfer="transfer"
35 v-transfer-dom> 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 <ul v-show="(!notFound && !remote) || (remote && !loading && !notFound)" :class="[prefixCls + '-dropdown-list']"><slot></slot></ul> 39 <ul v-show="(!notFound && !remote) || (remote && !loading && !notFound)" :class="[prefixCls + '-dropdown-list']"><slot></slot></ul>
38 <ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul> 40 <ul v-show="loading" :class="[prefixCls + '-loading']">{{ localeLoadingText }}</ul>
39 </Drop> 41 </Drop>
@@ -123,6 +125,11 @@ @@ -123,6 +125,11 @@
123 transfer: { 125 transfer: {
124 type: Boolean, 126 type: Boolean,
125 default: false 127 default: false
  128 + },
  129 + // Use for AutoComplete
  130 + autoComplete: {
  131 + type: Boolean,
  132 + default: false
126 } 133 }
127 }, 134 },
128 data () { 135 data () {
@@ -161,7 +168,13 @@ @@ -161,7 +168,13 @@
161 dropdownCls () { 168 dropdownCls () {
162 return { 169 return {
163 [prefixCls + '-dropdown-transfer']: this.transfer, 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 showPlaceholder () { 180 showPlaceholder () {
@@ -225,16 +238,19 @@ @@ -225,16 +238,19 @@
225 let status = true; 238 let status = true;
226 const options = this.$slots.default || []; 239 const options = this.$slots.default || [];
227 if (!this.loading && this.remote && this.query === '' && !options.length) status = false; 240 if (!this.loading && this.remote && this.query === '' && !options.length) status = false;
  241 +
  242 + if (this.autoComplete && !options.length) status = false;
  243 +
228 return this.visible && status; 244 return this.visible && status;
229 }, 245 },
230 - notFountShow () { 246 + notFoundShow () {
231 const options = this.$slots.default || []; 247 const options = this.$slots.default || [];
232 return (this.notFound && !this.remote) || (this.remote && !this.loading && !options.length); 248 return (this.notFound && !this.remote) || (this.remote && !this.loading && !options.length);
233 } 249 }
234 }, 250 },
235 methods: { 251 methods: {
236 toggleMenu () { 252 toggleMenu () {
237 - if (this.disabled) { 253 + if (this.disabled || this.autoComplete) {
238 return false; 254 return false;
239 } 255 }
240 this.visible = !this.visible; 256 this.visible = !this.visible;
@@ -543,6 +559,7 @@ @@ -543,6 +559,7 @@
543 }, 559 },
544 handleBlur () { 560 handleBlur () {
545 setTimeout(() => { 561 setTimeout(() => {
  562 + if (this.autoComplete) return;
546 const model = this.model; 563 const model = this.model;
547 564
548 if (this.multiple) { 565 if (this.multiple) {
@@ -737,7 +754,7 @@ @@ -737,7 +754,7 @@
737 if (this.multiple) { 754 if (this.multiple) {
738 this.$refs.input.focus(); 755 this.$refs.input.focus();
739 } else { 756 } else {
740 - this.$refs.input.select(); 757 + if (!this.autoComplete) this.$refs.input.select();
741 } 758 }
742 if (this.remote) { 759 if (this.remote) {
743 this.findChild(child => { 760 this.findChild(child => {
@@ -753,7 +770,7 @@ @@ -753,7 +770,7 @@
753 this.broadcast('Drop', 'on-update-popper'); 770 this.broadcast('Drop', 'on-update-popper');
754 } else { 771 } else {
755 if (this.filterable) { 772 if (this.filterable) {
756 - this.$refs.input.blur(); 773 + if (!this.autoComplete) this.$refs.input.blur();
757 // #566 reset options visible 774 // #566 reset options visible
758 setTimeout(() => { 775 setTimeout(() => {
759 this.broadcastQuery(''); 776 this.broadcastQuery('');
@@ -3,6 +3,7 @@ import &#39;core-js/fn/array/find-index&#39;; @@ -3,6 +3,7 @@ import &#39;core-js/fn/array/find-index&#39;;
3 3
4 import Affix from './components/affix'; 4 import Affix from './components/affix';
5 import Alert from './components/alert'; 5 import Alert from './components/alert';
  6 +import AutoComplete from './components/auto-complete';
6 import Avatar from './components/avatar'; 7 import Avatar from './components/avatar';
7 import BackTop from './components/back-top'; 8 import BackTop from './components/back-top';
8 import Badge from './components/badge'; 9 import Badge from './components/badge';
@@ -51,6 +52,7 @@ import locale from &#39;./locale&#39;; @@ -51,6 +52,7 @@ import locale from &#39;./locale&#39;;
51 const iview = { 52 const iview = {
52 Affix, 53 Affix,
53 Alert, 54 Alert,
  55 + AutoComplete,
54 Avatar, 56 Avatar,
55 BackTop, 57 BackTop,
56 Badge, 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 \ No newline at end of file 16 \ No newline at end of file
src/styles/components/index.less
@@ -40,4 +40,5 @@ @@ -40,4 +40,5 @@
40 @import "upload"; 40 @import "upload";
41 @import "tree"; 41 @import "tree";
42 @import "avatar"; 42 @import "avatar";
43 -@import "color-picker";  
44 \ No newline at end of file 43 \ No newline at end of file
  44 +@import "color-picker";
  45 +@import "auto-complete";
45 \ No newline at end of file 46 \ No newline at end of file