From c1829e69f10b4a9e00dad7679925c22eb8e753fd Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Mon, 17 Jul 2017 18:24:24 +0800 Subject: [PATCH 001/193] support focus and blur event for some form component --- examples/docs/en-US/date-picker.md | 2 + examples/docs/en-US/datetime-picker.md | 4 +- examples/docs/en-US/input-number.md | 4 +- examples/docs/en-US/time-picker.md | 3 +- examples/docs/zh-CN/date-picker.md | 3 +- examples/docs/zh-CN/datetime-picker.md | 5 +- examples/docs/zh-CN/input-number.md | 2 + examples/docs/zh-CN/time-picker.md | 6 +-- packages/autocomplete/src/autocomplete.vue | 7 ++- packages/input-number/src/input-number.vue | 7 ++- test/unit/specs/autocomplete.spec.js | 56 ++++++++++++++++++++++ test/unit/specs/date-picker.spec.js | 21 ++++++++ test/unit/specs/input-number.spec.js | 22 +++++++++ test/unit/specs/time-picker.spec.js | 28 ++++++++++- test/unit/specs/time-select.spec.js | 29 +++++++++++ 15 files changed, 184 insertions(+), 15 deletions(-) diff --git a/examples/docs/en-US/date-picker.md b/examples/docs/en-US/date-picker.md index 532e1a5b1..76e19421c 100644 --- a/examples/docs/en-US/date-picker.md +++ b/examples/docs/en-US/date-picker.md @@ -307,3 +307,5 @@ Picking a date range is supported. | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | +| blur | triggers when input is blur | (event: Event) | +| focus | triggers when input is focus | (event: Event) | diff --git a/examples/docs/en-US/datetime-picker.md b/examples/docs/en-US/datetime-picker.md index d9291931c..1433ee3fe 100644 --- a/examples/docs/en-US/datetime-picker.md +++ b/examples/docs/en-US/datetime-picker.md @@ -254,9 +254,9 @@ Select date and time in one picker. | text | title of the shortcut | string | — | — | | onClick | callback function, triggers when the shortcut is clicked, with the `vm` as its parameter. You can change the picker value by emitting the `pick` event. Example: `vm.$emit('pick', new Date())`| function | — | — | - ### Events | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | - +| blur | triggers when input is blur | (event: Event) | +| focus | triggers when input is focus | (event: Event) | diff --git a/examples/docs/en-US/input-number.md b/examples/docs/en-US/input-number.md index 69bc688b9..7fdf5066c 100644 --- a/examples/docs/en-US/input-number.md +++ b/examples/docs/en-US/input-number.md @@ -140,4 +140,6 @@ Additional `large` and `small` sizes of the input box are available | Event Name | Description | Parameters | |----| ---- | -----| -|change | triggers when the value changes | value after change | \ No newline at end of file +|change | triggers when the value changes | value after change | +| blur | triggers when the icon inside Input is blur | (event: Event) | +| focus | triggers when the icon inside Input is focus | (event: Event) | diff --git a/examples/docs/en-US/time-picker.md b/examples/docs/en-US/time-picker.md index 6873514bc..951829bee 100644 --- a/examples/docs/en-US/time-picker.md +++ b/examples/docs/en-US/time-picker.md @@ -182,4 +182,5 @@ Can pick an arbitrary time range. | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | - +| blur | triggers when input is blur | (event: Event) | +| focus | triggers when input is focus | (event: Event) | diff --git a/examples/docs/zh-CN/date-picker.md b/examples/docs/zh-CN/date-picker.md index b87ffc942..8cd09b5e9 100644 --- a/examples/docs/zh-CN/date-picker.md +++ b/examples/docs/zh-CN/date-picker.md @@ -316,4 +316,5 @@ | 事件名称 | 说明 | 回调参数 | |---------|--------|---------| | change | 当 input 的值改变时触发,返回值和文本框一致 | 格式化后的值 | - +| blur | 当 input 失去焦点时触发 | (event: Event) | +| focus | 当 input 获得焦点时触发 | (event: Event) | diff --git a/examples/docs/zh-CN/datetime-picker.md b/examples/docs/zh-CN/datetime-picker.md index e2b18de51..e3d139d9b 100644 --- a/examples/docs/zh-CN/datetime-picker.md +++ b/examples/docs/zh-CN/datetime-picker.md @@ -253,10 +253,9 @@ | text | 标题文本 | string | — | — | | onClick | 选中后的回调函数,参数是 vm,可通过触发 'pick' 事件设置选择器的值。例如 vm.$emit('pick', new Date()) | function | — | — | - ### Events | Event Name | Description | Parameters | |---------|--------|---------| | change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | - - +| blur | 当 input 失去焦点时触发 | (event: Event) | +| focus | 当 input 获得焦点时触发 | (event: Event) | diff --git a/examples/docs/zh-CN/input-number.md b/examples/docs/zh-CN/input-number.md index cd9b09d68..cd0e8a251 100644 --- a/examples/docs/zh-CN/input-number.md +++ b/examples/docs/zh-CN/input-number.md @@ -137,3 +137,5 @@ | 事件名称 | 说明 | 回调参数 | |---------|--------|---------| | change | 绑定值被改变时触发 | 最后变更的值 | +| blur | 在组件 Input 失去焦点时触发 | (event: Event) | +| focus | 在组件 Input 获得焦点时触发 | (event: Event) | diff --git a/examples/docs/zh-CN/time-picker.md b/examples/docs/zh-CN/time-picker.md index 1f4c5d383..9bf5c6f59 100644 --- a/examples/docs/zh-CN/time-picker.md +++ b/examples/docs/zh-CN/time-picker.md @@ -176,11 +176,9 @@ | selectableRange | 可选时间段,例如`'18:30:00 - 20:30:00'`或者传入数组`['09:30:00 - 12:00:00', '14:30:00 - 18:30:00']` | string/array | — | — | | format | 时间格式化(TimePicker) | string | 小时:`HH`,分:`mm`,秒:`ss` | 'HH:mm:ss' | - ### Events | Event Name | Description | Parameters | |---------|--------|---------| | change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | - - - +| blur | 当 input 失去焦点时触发 | (event: Event) | +| focus | 当 input 获得焦点时触发 | (event: Event) | diff --git a/packages/autocomplete/src/autocomplete.vue b/packages/autocomplete/src/autocomplete.vue index 8b02949fb..0d8b61c09 100644 --- a/packages/autocomplete/src/autocomplete.vue +++ b/packages/autocomplete/src/autocomplete.vue @@ -14,6 +14,7 @@ @compositionend.native="handleComposition" @change="handleChange" @focus="handleFocus" + @blur="handleBlur" @keydown.up.native.prevent="highlight(highlightedIndex - 1)" @keydown.down.native.prevent="highlight(highlightedIndex + 1)" @keydown.enter.native.prevent="handleKeyEnter" @@ -129,12 +130,16 @@ } this.getData(value); }, - handleFocus() { + handleFocus(event) { this.activated = true; + this.$emit('focus', event); if (this.triggerOnFocus) { this.getData(this.value); } }, + handleBlur(event) { + this.$emit('blur', event); + }, close(e) { this.activated = false; }, diff --git a/packages/input-number/src/input-number.vue b/packages/input-number/src/input-number.vue index a21fa69f0..230c06202 100644 --- a/packages/input-number/src/input-number.vue +++ b/packages/input-number/src/input-number.vue @@ -27,6 +27,7 @@ @keydown.up.native.prevent="increase" @keydown.down.native.prevent="decrease" @blur="handleBlur" + @focus="handleFocus" @input="debounceHandleInput" :disabled="disabled" :size="size" @@ -175,9 +176,13 @@ if (newVal < this.min) return; this.setCurrentValue(newVal); }, - handleBlur() { + handleBlur(event) { + this.$emit('blur', event); this.$refs.input.setCurrentValue(this.currentValue); }, + handleFocus(event) { + this.$emit('focus', event); + }, setCurrentValue(newVal) { const oldVal = this.currentValue; if (newVal >= this.max) newVal = this.max; diff --git a/test/unit/specs/autocomplete.spec.js b/test/unit/specs/autocomplete.spec.js index 19bd2cde5..61081ab7e 100644 --- a/test/unit/specs/autocomplete.spec.js +++ b/test/unit/specs/autocomplete.spec.js @@ -384,4 +384,60 @@ describe('Autocomplete', () => { done(); }, 500); }); + it('event:focus & blur', done => { + vm = createVue({ + template: ` + + `, + data() { + return { + restaurants: [], + state: '' + }; + }, + methods: { + querySearch(queryString, cb) { + var restaurants = this.restaurants; + var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants; + cb(results); + }, + createFilter(queryString) { + return (restaurant) => { + return (restaurant.value.indexOf(queryString.toLowerCase()) === 0); + }; + }, + loadAll() { + return [ + { 'value': '三全鲜食(北新泾店)', 'address': '长宁区新渔路144号' }, + { 'value': 'Hot honey 首尔炸鸡(仙霞路)', 'address': '上海市长宁区淞虹路661号' }, + { 'value': '新旺角茶餐厅', 'address': '上海市普陀区真北路988号创邑金沙谷6号楼113' }, + { 'value': '泷千家(天山西路店)', 'address': '天山西路438号' } + ]; + } + }, + mounted() { + this.restaurants = this.loadAll(); + } + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.input.$on('focus', spyFocus); + vm.$refs.input.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); diff --git a/test/unit/specs/date-picker.spec.js b/test/unit/specs/date-picker.spec.js index 2c163e20c..21b9413a6 100644 --- a/test/unit/specs/date-picker.spec.js +++ b/test/unit/specs/date-picker.spec.js @@ -316,6 +316,27 @@ describe('DatePicker', () => { }, DELAY); }); + it('work for event focus and blur', done => { + vm = createVue({ + template: ` + + ` + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.picker.$on('focus', spyFocus); + vm.$refs.picker.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); it('default value', done => { diff --git a/test/unit/specs/input-number.spec.js b/test/unit/specs/input-number.spec.js index 9435e8a02..97eaa2fe6 100644 --- a/test/unit/specs/input-number.spec.js +++ b/test/unit/specs/input-number.spec.js @@ -289,4 +289,26 @@ describe('InputNumber', () => { done(); }); }); + it('event:focus & blur', done => { + vm = createVue({ + template: ` + + + ` + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.input.$on('focus', spyFocus); + vm.$refs.input.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); diff --git a/test/unit/specs/time-picker.spec.js b/test/unit/specs/time-picker.spec.js index 3b553029d..d4025b4d2 100644 --- a/test/unit/specs/time-picker.spec.js +++ b/test/unit/specs/time-picker.spec.js @@ -1,4 +1,4 @@ -import { createTest, destroyVM } from '../util'; +import { createTest, destroyVM, createVue } from '../util'; import TimePicker from 'packages/time-picker'; import Vue from 'vue'; @@ -176,6 +176,32 @@ describe('TimePicker', () => { done(); }, 20); }); + + it('event focus and blur', done => { + vm = createVue({ + template: ` + + + ` + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.picker.$on('focus', spyFocus); + vm.$refs.picker.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); describe('TimePicker(range)', () => { diff --git a/test/unit/specs/time-select.spec.js b/test/unit/specs/time-select.spec.js index 88eb71c47..006cb7c6c 100644 --- a/test/unit/specs/time-select.spec.js +++ b/test/unit/specs/time-select.spec.js @@ -198,4 +198,33 @@ describe('TimeSelect', () => { }, 50); }); + it('event focus and blur', done => { + vm = createVue({ + template: ` + + + ` + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.picker.$on('focus', spyFocus); + vm.$refs.picker.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); From b1860f52744cfa6f8f6d75213b0bc136e103fcb8 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Mon, 17 Jul 2017 18:34:53 +0800 Subject: [PATCH 002/193] support focus and blur for select --- packages/select/src/select.vue | 8 +++++++- test/unit/specs/select.spec.js | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/select/src/select.vue b/packages/select/src/select.vue index 78742403a..9f35ff895 100644 --- a/packages/select/src/select.vue +++ b/packages/select/src/select.vue @@ -51,6 +51,7 @@ :readonly="!filterable || multiple" :validate-event="false" @focus="handleFocus" + @blur="handleBlur" @click="handleIconClick" @mousedown.native="handleMouseDown" @keyup.native="debouncedOnInputChange" @@ -426,8 +427,13 @@ }); }, - handleFocus() { + handleFocus(event) { this.visible = true; + this.$emit('focus', event); + }, + + handleBlur(event) { + this.$emit('blur', event); }, handleIconClick(event) { diff --git a/test/unit/specs/select.spec.js b/test/unit/specs/select.spec.js index 958648dbe..f2b2b20bf 100644 --- a/test/unit/specs/select.spec.js +++ b/test/unit/specs/select.spec.js @@ -607,4 +607,26 @@ describe('Select', () => { }, 250); }); }); + + it('event:focus & blur', done => { + vm = createVue({ + template: ` + + ` + }, true); + + const spyFocus = sinon.spy(); + const spyBlur = sinon.spy(); + + vm.$refs.select.$on('focus', spyFocus); + vm.$refs.select.$on('blur', spyBlur); + vm.$el.querySelector('input').focus(); + vm.$el.querySelector('input').blur(); + + vm.$nextTick(_ => { + expect(spyFocus.calledOnce).to.be.true; + expect(spyBlur.calledOnce).to.be.true; + done(); + }); + }); }); From 965989b0169be26ffcb8e30c8e555a99367a6a9c Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Tue, 18 Jul 2017 12:09:03 +0800 Subject: [PATCH 003/193] Update doc --- examples/docs/en-US/date-picker.md | 4 ++-- examples/docs/en-US/datetime-picker.md | 4 ++-- examples/docs/en-US/input-number.md | 4 ++-- examples/docs/en-US/input.md | 4 ++-- examples/docs/en-US/select.md | 2 ++ examples/docs/en-US/time-picker.md | 4 ++-- examples/docs/zh-CN/select.md | 2 ++ 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/docs/en-US/date-picker.md b/examples/docs/en-US/date-picker.md index 76e19421c..bd26a1149 100644 --- a/examples/docs/en-US/date-picker.md +++ b/examples/docs/en-US/date-picker.md @@ -307,5 +307,5 @@ Picking a date range is supported. | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | -| blur | triggers when input is blur | (event: Event) | -| focus | triggers when input is focus | (event: Event) | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | diff --git a/examples/docs/en-US/datetime-picker.md b/examples/docs/en-US/datetime-picker.md index 1433ee3fe..5b21b12a3 100644 --- a/examples/docs/en-US/datetime-picker.md +++ b/examples/docs/en-US/datetime-picker.md @@ -258,5 +258,5 @@ Select date and time in one picker. | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | -| blur | triggers when input is blur | (event: Event) | -| focus | triggers when input is focus | (event: Event) | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | diff --git a/examples/docs/en-US/input-number.md b/examples/docs/en-US/input-number.md index 7fdf5066c..06cb7e505 100644 --- a/examples/docs/en-US/input-number.md +++ b/examples/docs/en-US/input-number.md @@ -141,5 +141,5 @@ Additional `large` and `small` sizes of the input box are available | Event Name | Description | Parameters | |----| ---- | -----| |change | triggers when the value changes | value after change | -| blur | triggers when the icon inside Input is blur | (event: Event) | -| focus | triggers when the icon inside Input is focus | (event: Event) | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | diff --git a/examples/docs/en-US/input.md b/examples/docs/en-US/input.md index dd0b0b78b..e28a7dc7e 100644 --- a/examples/docs/en-US/input.md +++ b/examples/docs/en-US/input.md @@ -622,8 +622,8 @@ Search data from server-side. | Event Name | Description | Parameters | |----| ----| ----| |click | triggers when the icon inside Input is clicked | (event: Event) | -| blur | triggers when the icon inside Input is blur | (event: Event) | -| focus | triggers when the icon inside Input is focus | (event: Event) | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | | change | triggers when the icon inside Input value change | (value: string \| number) | ### Autocomplete Attributes diff --git a/examples/docs/en-US/select.md b/examples/docs/en-US/select.md index 2481ed3d9..66444b75f 100644 --- a/examples/docs/en-US/select.md +++ b/examples/docs/en-US/select.md @@ -666,6 +666,8 @@ Create and select new items that are not included in select options | visible-change | triggers when the dropdown appears/disappears | true when it appears, and false otherwise | | remove-tag | triggers when a tag is removed in multiple mode | removed tag value | | clear | triggers when the clear icon is clicked in a clearable Select | — | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | ### Option Group Attributes | Attribute | Description | Type | Accepted Values | Default | diff --git a/examples/docs/en-US/time-picker.md b/examples/docs/en-US/time-picker.md index 951829bee..7783a3fbf 100644 --- a/examples/docs/en-US/time-picker.md +++ b/examples/docs/en-US/time-picker.md @@ -182,5 +182,5 @@ Can pick an arbitrary time range. | Event Name | Description | Parameters | |---------|--------|---------| | change | triggers when input value changes | formatted value | -| blur | triggers when input is blur | (event: Event) | -| focus | triggers when input is focus | (event: Event) | +| blur | triggers when Input blurs | (event: Event) | +| focus | triggers when Input focuses | (event: Event) | diff --git a/examples/docs/zh-CN/select.md b/examples/docs/zh-CN/select.md index b8cab5800..d6758c003 100644 --- a/examples/docs/zh-CN/select.md +++ b/examples/docs/zh-CN/select.md @@ -661,6 +661,8 @@ | visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false | | remove-tag | 多选模式下移除tag时触发 | 移除的tag值 | | clear | 可清空的单选模式下用户点击清空按钮时触发 | — | +| blur | 当 input 失去焦点时触发 | (event: Event) | +| focus | 当 input 获得焦点时触发 | (event: Event) | ### Option Group Attributes | 参数 | 说明 | 类型 | 可选值 | 默认值 | From 0a2dd8bd53a7417bcdd849de334037836f19440f Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Tue, 18 Jul 2017 13:47:35 +0800 Subject: [PATCH 004/193] Add focus method for some form component --- examples/docs/en-US/date-picker.md | 5 +++++ examples/docs/en-US/datetime-picker.md | 5 +++++ examples/docs/en-US/input-number.md | 5 +++++ examples/docs/en-US/input.md | 9 ++++----- examples/docs/en-US/select.md | 4 ++++ examples/docs/zh-CN/date-picker.md | 5 +++++ examples/docs/zh-CN/datetime-picker.md | 5 +++++ examples/docs/zh-CN/input-number.md | 5 +++++ examples/docs/zh-CN/input.md | 7 ++++++- examples/docs/zh-CN/select.md | 5 +++++ examples/docs/zh-CN/time-picker.md | 5 +++++ packages/cascader/src/main.vue | 2 +- packages/date-picker/src/picker.vue | 3 ++- packages/input-number/src/input-number.vue | 2 ++ packages/input/src/input.vue | 3 ++- packages/select/src/select.vue | 3 ++- src/mixins/focus.js | 9 +++++++++ test/unit/specs/date-picker.spec.js | 18 +++++++++++++++++ test/unit/specs/input-number.spec.js | 17 ++++++++++++++++ test/unit/specs/input.spec.js | 19 ++++++++++++++++++ test/unit/specs/select.spec.js | 17 ++++++++++++++++ test/unit/specs/time-picker.spec.js | 23 ++++++++++++++++++++++ test/unit/specs/time-select.spec.js | 19 ++++++++++++++++++ 23 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 src/mixins/focus.js diff --git a/examples/docs/en-US/date-picker.md b/examples/docs/en-US/date-picker.md index bd26a1149..328a4bdef 100644 --- a/examples/docs/en-US/date-picker.md +++ b/examples/docs/en-US/date-picker.md @@ -309,3 +309,8 @@ Picking a date range is supported. | change | triggers when input value changes | formatted value | | blur | triggers when Input blurs | (event: Event) | | focus | triggers when Input focuses | (event: Event) | + +### Methods +| Method | Description | Parameters | +|------|--------|-------| +| focus | focus the Input component | - | diff --git a/examples/docs/en-US/datetime-picker.md b/examples/docs/en-US/datetime-picker.md index 5b21b12a3..c5a721634 100644 --- a/examples/docs/en-US/datetime-picker.md +++ b/examples/docs/en-US/datetime-picker.md @@ -260,3 +260,8 @@ Select date and time in one picker. | change | triggers when input value changes | formatted value | | blur | triggers when Input blurs | (event: Event) | | focus | triggers when Input focuses | (event: Event) | + +### Methods +| Method | Description | Parameters | +|------|--------|-------| +| focus | focus the Input component | - | diff --git a/examples/docs/en-US/input-number.md b/examples/docs/en-US/input-number.md index 06cb7e505..196430afc 100644 --- a/examples/docs/en-US/input-number.md +++ b/examples/docs/en-US/input-number.md @@ -143,3 +143,8 @@ Additional `large` and `small` sizes of the input box are available |change | triggers when the value changes | value after change | | blur | triggers when Input blurs | (event: Event) | | focus | triggers when Input focuses | (event: Event) | + +### Methods +| Method | Description | Parameters | +|------|--------|-------| +| focus | focus the Input component | - | diff --git a/examples/docs/en-US/input.md b/examples/docs/en-US/input.md index e28a7dc7e..e2186f1c5 100644 --- a/examples/docs/en-US/input.md +++ b/examples/docs/en-US/input.md @@ -653,8 +653,7 @@ Attribute | Description | Type | Options | Default |----| ----| ----| |select | triggers when a suggestion is clicked | suggestion being clicked | - - - - - +### Methods +| Method | Description | Parameters | +|------|--------|-------| +| focus | focus the Input component | - | diff --git a/examples/docs/en-US/select.md b/examples/docs/en-US/select.md index 66444b75f..4a3371f6c 100644 --- a/examples/docs/en-US/select.md +++ b/examples/docs/en-US/select.md @@ -682,3 +682,7 @@ Create and select new items that are not included in select options | label | label of option, same as `value` if omitted | string/number | — | — | | disabled | whether option is disabled | boolean | — | false | +### Methods +| Method | Description | Parameters | +|------|--------|-------| +| focus | focus the Input component | - | diff --git a/examples/docs/zh-CN/date-picker.md b/examples/docs/zh-CN/date-picker.md index 8cd09b5e9..00e01d929 100644 --- a/examples/docs/zh-CN/date-picker.md +++ b/examples/docs/zh-CN/date-picker.md @@ -318,3 +318,8 @@ | change | 当 input 的值改变时触发,返回值和文本框一致 | 格式化后的值 | | blur | 当 input 失去焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/examples/docs/zh-CN/datetime-picker.md b/examples/docs/zh-CN/datetime-picker.md index e3d139d9b..ba5978048 100644 --- a/examples/docs/zh-CN/datetime-picker.md +++ b/examples/docs/zh-CN/datetime-picker.md @@ -259,3 +259,8 @@ | change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | | blur | 当 input 失去焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/examples/docs/zh-CN/input-number.md b/examples/docs/zh-CN/input-number.md index cd0e8a251..137f260d4 100644 --- a/examples/docs/zh-CN/input-number.md +++ b/examples/docs/zh-CN/input-number.md @@ -139,3 +139,8 @@ | change | 绑定值被改变时触发 | 最后变更的值 | | blur | 在组件 Input 失去焦点时触发 | (event: Event) | | focus | 在组件 Input 获得焦点时触发 | (event: Event) | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/examples/docs/zh-CN/input.md b/examples/docs/zh-CN/input.md index 42168cc62..710b48a46 100644 --- a/examples/docs/zh-CN/input.md +++ b/examples/docs/zh-CN/input.md @@ -802,7 +802,7 @@ export default { | on-icon-click | 点击图标的回调函数 | function | — | — | | icon | 输入框尾部图标 | string | — | — | -### props +### Props | 参数 | 说明 | 类型 | 可选值 | 默认值 | | -------- | ----------------- | ------ | ------ | ------ | | value | 指定选项的值为选项对象的某个属性值 | string | — | value | @@ -812,3 +812,8 @@ export default { | 事件名称 | 说明 | 回调参数 | |---------|--------|---------| | select | 点击选中建议项时触发 | 选中建议项 | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/examples/docs/zh-CN/select.md b/examples/docs/zh-CN/select.md index d6758c003..81ca9c761 100644 --- a/examples/docs/zh-CN/select.md +++ b/examples/docs/zh-CN/select.md @@ -676,3 +676,8 @@ | value | 选项的值 | string/number/object | — | — | | label | 选项的标签,若不设置则默认与 `value` 相同 | string/number | — | — | | disabled | 是否禁用该选项 | boolean | — | false | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/examples/docs/zh-CN/time-picker.md b/examples/docs/zh-CN/time-picker.md index 9bf5c6f59..f5ac81ad5 100644 --- a/examples/docs/zh-CN/time-picker.md +++ b/examples/docs/zh-CN/time-picker.md @@ -182,3 +182,8 @@ | change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | | blur | 当 input 失去焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) | + +### Methods +| 方法名 | 说明 | 参数 | +| ---- | ---- | ---- | +| focus | 使 input 获取焦点 | - | diff --git a/packages/cascader/src/main.vue b/packages/cascader/src/main.vue index 670944df6..be575ce39 100644 --- a/packages/cascader/src/main.vue +++ b/packages/cascader/src/main.vue @@ -327,7 +327,7 @@ export default { if (this.disabled) return; if (this.filterable) { this.menuVisible = true; - this.$refs.input.$refs.input.focus(); + this.$refs.input.focus(); return; } this.menuVisible = !this.menuVisible; diff --git a/packages/date-picker/src/picker.vue b/packages/date-picker/src/picker.vue index 956f5d74d..00b1b5987 100644 --- a/packages/date-picker/src/picker.vue +++ b/packages/date-picker/src/picker.vue @@ -31,6 +31,7 @@ import Clickoutside from 'element-ui/src/utils/clickoutside'; import { formatDate, parseDate, getWeekNumber, equalDate, isDate } from './util'; import Popper from 'element-ui/src/utils/vue-popper'; import Emitter from 'element-ui/src/mixins/emitter'; +import Focus from 'element-ui/src/mixins/focus'; import ElInput from 'element-ui/packages/input'; const NewPopper = { @@ -197,7 +198,7 @@ const valueEquals = function(a, b) { }; export default { - mixins: [Emitter, NewPopper], + mixins: [Emitter, NewPopper, Focus('reference')], props: { size: String, diff --git a/packages/input-number/src/input-number.vue b/packages/input-number/src/input-number.vue index 230c06202..9eac7e06d 100644 --- a/packages/input-number/src/input-number.vue +++ b/packages/input-number/src/input-number.vue @@ -48,9 +48,11 @@ import ElInput from 'element-ui/packages/input'; import { once, on } from 'element-ui/src/utils/dom'; import debounce from 'throttle-debounce/debounce'; + import Focus from 'element-ui/src/mixins/focus'; export default { name: 'ElInputNumber', + mixins: [Focus('input')], directives: { repeatClick: { bind(el, binding, vnode) { diff --git a/packages/input/src/input.vue b/packages/input/src/input.vue index 88f01e806..f06a6bf7b 100644 --- a/packages/input/src/input.vue +++ b/packages/input/src/input.vue @@ -57,6 +57,7 @@ +``` +::: + ### Custom Tab You can use named slot to customize the tab label content. @@ -341,6 +376,7 @@ Only card type Tabs support addable & closeable. | editable | whether Tab is addable and closable | boolean | — | false | | active-name(deprecated) | name of the selected tab | string | — | name of first tab | | value | name of the selected tab | string | — | name of first tab | +| tab-position | position of tabs | string | top/right/bottom/left | top | ### Tabs Events | Event Name | Description | Parameters | diff --git a/examples/docs/zh-CN/tabs.md b/examples/docs/zh-CN/tabs.md index 24110a176..80d00dda1 100644 --- a/examples/docs/zh-CN/tabs.md +++ b/examples/docs/zh-CN/tabs.md @@ -24,7 +24,8 @@ name: '2', content: 'Tab 2 content' }], - tabIndex: 2 + tabIndex: 2, + tabPosition: 'top' } }, methods: { @@ -172,6 +173,40 @@ ``` ::: +### 位置 + +可以通过 `tab-position` 设置标签的位置 + +:::demo 标签一共有四个方向的设置 `tabPosition="left|right|top|bottom"` + +```html + + +``` +::: + ### 自定义标签页 可以通过具名 `slot` 来实现自定义标签页的内容 @@ -339,6 +374,7 @@ | editable | 标签是否同时可增加和关闭 | boolean | — | false | | active-name(deprecated) | 选中选项卡的 name | string | — | 第一个选项卡的 name | | value | 绑定值,选中选项卡的 name | string | — | 第一个选项卡的 name | +| tab-position | 选项卡所在位置 | string | top/right/bottom/left | top | ### Tabs Events | 事件名称 | 说明 | 回调参数 | diff --git a/packages/tabs/src/tab-bar.vue b/packages/tabs/src/tab-bar.vue index 792d54d50..3e1eb4d16 100644 --- a/packages/tabs/src/tab-bar.vue +++ b/packages/tabs/src/tab-bar.vue @@ -9,6 +9,8 @@ tabs: Array }, + inject: ['rootTabs'], + computed: { barStyle: { cache: false, @@ -16,23 +18,27 @@ if (!this.$parent.$refs.tabs) return {}; let style = {}; let offset = 0; - let tabWidth = 0; - + let tabSize = 0; + const sizeName = ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'width' : 'height'; + const sizeDir = sizeName === 'width' ? 'x' : 'y'; + const firstUpperCase = str => { + return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); + }; this.tabs.every((tab, index) => { let $el = this.$parent.$refs.tabs[index]; if (!$el) { return false; } if (!tab.active) { - offset += $el.clientWidth; + offset += $el[`client${firstUpperCase(sizeName)}`]; return true; } else { - tabWidth = $el.clientWidth; + tabSize = $el[`client${firstUpperCase(sizeName)}`]; return false; } }); - const transform = `translateX(${offset}px)`; - style.width = tabWidth + 'px'; + const transform = `translate${firstUpperCase(sizeDir)}(${offset}px)`; + style[sizeName] = tabSize + 'px'; style.transform = transform; style.msTransform = transform; style.webkitTransform = transform; diff --git a/packages/tabs/src/tab-nav.vue b/packages/tabs/src/tab-nav.vue index 4af467b53..048ab2b98 100644 --- a/packages/tabs/src/tab-nav.vue +++ b/packages/tabs/src/tab-nav.vue @@ -3,6 +3,9 @@ import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'; function noop() {} + const firstUpperCase = str => { + return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); + }; export default { name: 'TabNav', @@ -11,6 +14,8 @@ TabBar }, + inject: ['rootTabs'], + props: { panes: Array, currentName: String, @@ -29,37 +34,47 @@ data() { return { scrollable: false, - navStyle: { - transform: '' - } + navOffset: 0 }; }, + computed: { + navStyle() { + const dir = ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'X' : 'Y'; + return { + transform: `translate${dir}(-${this.navOffset}px)` + }; + }, + sizeName() { + return ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'width' : 'height'; + } + }, + methods: { scrollPrev() { - const containerWidth = this.$refs.navScroll.offsetWidth; - const currentOffset = this.getCurrentScrollOffset(); + const containerSize = this.$refs.navScroll[`offset${firstUpperCase(this.sizeName)}`]; + const currentOffset = this.navOffset; if (!currentOffset) return; - let newOffset = currentOffset > containerWidth - ? currentOffset - containerWidth + let newOffset = currentOffset > containerSize + ? currentOffset - containerSize : 0; - this.setOffset(newOffset); + this.navOffset = newOffset; }, scrollNext() { - const navWidth = this.$refs.nav.offsetWidth; - const containerWidth = this.$refs.navScroll.offsetWidth; - const currentOffset = this.getCurrentScrollOffset(); + const navSize = this.$refs.nav[`offset${firstUpperCase(this.sizeName)}`]; + const containerSize = this.$refs.navScroll[`offset${firstUpperCase(this.sizeName)}`]; + const currentOffset = this.navOffset; - if (navWidth - currentOffset <= containerWidth) return; + if (navSize - currentOffset <= containerSize) return; - let newOffset = navWidth - currentOffset > containerWidth * 2 - ? currentOffset + containerWidth - : (navWidth - containerWidth); + let newOffset = navSize - currentOffset > containerSize * 2 + ? currentOffset + containerSize + : (navSize - containerSize); - this.setOffset(newOffset); + this.navOffset = newOffset; }, scrollToActiveTab() { if (!this.scrollable) return; @@ -69,7 +84,7 @@ const activeTabBounding = activeTab.getBoundingClientRect(); const navScrollBounding = navScroll.getBoundingClientRect(); const navBounding = nav.getBoundingClientRect(); - const currentOffset = this.getCurrentScrollOffset(); + const currentOffset = this.navOffset; let newOffset = currentOffset; if (activeTabBounding.left < navScrollBounding.left) { @@ -81,34 +96,27 @@ if (navBounding.right < navScrollBounding.right) { newOffset = nav.offsetWidth - navScrollBounding.width; } - this.setOffset(Math.max(newOffset, 0)); - }, - getCurrentScrollOffset() { - const { navStyle } = this; - return navStyle.transform - ? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) - : 0; - }, - setOffset(value) { - this.navStyle.transform = `translateX(-${value}px)`; + this.navOffset = Math.max(newOffset, 0); }, update() { - const navWidth = this.$refs.nav.offsetWidth; - const containerWidth = this.$refs.navScroll.offsetWidth; - const currentOffset = this.getCurrentScrollOffset(); + if (!this.$refs.nav) return; + const sizeName = this.sizeName; + const navSize = this.$refs.nav[`offset${firstUpperCase(sizeName)}`]; + const containerSize = this.$refs.navScroll[`offset${firstUpperCase(sizeName)}`]; + const currentOffset = this.navOffset; - if (containerWidth < navWidth) { - const currentOffset = this.getCurrentScrollOffset(); + if (containerSize < navSize) { + const currentOffset = this.navOffset; this.scrollable = this.scrollable || {}; this.scrollable.prev = currentOffset; - this.scrollable.next = currentOffset + containerWidth < navWidth; - if (navWidth - currentOffset < containerWidth) { - this.setOffset(navWidth - containerWidth); + this.scrollable.next = currentOffset + containerSize < navSize; + if (navSize - currentOffset < containerSize) { + this.navOffset = navSize - containerSize; } } else { this.scrollable = false; if (currentOffset > 0) { - this.setOffset(0); + this.navOffset = 0; } } } diff --git a/packages/tabs/src/tabs.vue b/packages/tabs/src/tabs.vue index f1bba2a80..faa257242 100644 --- a/packages/tabs/src/tabs.vue +++ b/packages/tabs/src/tabs.vue @@ -14,7 +14,17 @@ closable: Boolean, addable: Boolean, value: {}, - editable: Boolean + editable: Boolean, + tabPosition: { + type: String, + default: 'top' + } + }, + + provide() { + return { + rootTabs: this + }; }, data() { @@ -83,7 +93,8 @@ currentName, panes, editable, - addable + addable, + tabPosition } = this; const newButton = editable || addable @@ -108,20 +119,26 @@ }, ref: 'nav' }; + const header = ( +
+ {newButton} + +
+ ); + const panels = ( +
+ {this.$slots.default} +
+ ); return (
-
- {newButton} - -
-
- {this.$slots.default} -
+ { tabPosition !== 'bottom' ? [header, panels] : [panels, header] }
); }, diff --git a/packages/theme-default/src/tabs.css b/packages/theme-default/src/tabs.css index 07b73aede..8787cb972 100644 --- a/packages/theme-default/src/tabs.css +++ b/packages/theme-default/src/tabs.css @@ -48,6 +48,7 @@ @when scrollable { padding: 0 15px; + box-sizing: border-box; } } @e nav-scroll { @@ -76,7 +77,7 @@ padding: 0 16px; height: 42px; box-sizing: border-box; - line-height: @height; + line-height: 40px; display: inline-block; list-style: none; font-size: 14px; @@ -176,19 +177,164 @@ >.el-tabs__header .el-tabs__item { transition: all .3s cubic-bezier(.645,.045,.355,1); border: 1px solid transparent; - border-top: 0; - margin: * -1px; + margin: -1px -1px 0; &.is-active { background-color: var(--color-white); border-right-color: var(--color-base-gray); border-left-color: var(--color-base-gray); + } + } + } + @m bottom { + .el-tabs__header { + margin-bottom: 0; + margin-top: 10px; + } + &.el-tabs--border-card { + .el-tabs__header { + border-bottom: 0; + border-top: 1px solid var(--color-base-gray); + } + .el-tabs__nav-wrap { + margin-top: -1px; + margin-bottom: 0; + } + .el-tabs__item { + border: 1px solid transparent; + margin: 0 -1px -1px -1px; + } + } + } + @m left, right { + overflow: hidden; - &:first-child { - border-left-color: var(--color-base-gray); + .el-tabs__header, + .el-tabs__nav-wrap, + .el-tabs__nav-scroll { + height: 100%; + } + + .el-tabs__active-bar { + top: 0; + bottom: auto; + width: 3px; + height: auto; + } + + .el-tabs__nav-wrap { + margin-bottom: 0; + + &.is-scrollable { + padding: 30px 0; + } + } + + .el-tabs__nav { + float: none; + } + .el-tabs__item { + display: block; + } + + .el-tabs__nav-prev, + .el-tabs__nav-next { + height: 30px; + line-height: 30px; + width: 100%; + text-align: center; + cursor: pointer; + + i { + transform: rotateZ(90deg); + } + } + .el-tabs__nav-prev { + left: auto; + top: 0; + } + .el-tabs__nav-next { + right: auto; + bottom: 0; + } + } + @m left { + .el-tabs__header { + float: left; + border-bottom: none; + border-right: 1px solid var(--color-base-gray); + margin-bottom: 0; + margin-right: 10px; + } + .el-tabs__nav-wrap { + margin-right: -1px; + } + .el-tabs__active-bar { + right: 0; + left: auto; + } + .el-tabs__item { + text-align: right; + } + + &.el-tabs--card { + .el-tabs__item.is-active { + border: 1px solid rgb(209, 219, 229); + border-right-color: #fff; + border-radius: 4px 0 0 4px; + } + + .el-tabs__new-tab { + float: none; + } + } + + &.el-tabs--border-card { + .el-tabs__item { + border: 1px solid transparent; + margin: -1px 0 -1px -1px; + + &.is-active { + border-color: transparent; + border-top-color: rgb(209, 219, 229); + border-bottom-color: rgb(209, 219, 229); } - &:last-child { - border-right-color: var(--color-base-gray); + } + } + } + @m right { + .el-tabs__header { + float: right; + border-bottom: none; + border-left: 1px solid var(--color-base-gray); + margin-bottom: 0; + margin-left: 10px; + } + + .el-tabs__nav-wrap { + margin-left: -1px; + } + + .el-tabs__active-bar { + left: 0; + } + + &.el-tabs--card { + .el-tabs__item.is-active { + border: 1px solid rgb(209, 219, 229); + border-left-color: #fff; + border-radius: 0 4px 4px 0; + } + } + &.el-tabs--border-card { + .el-tabs__item { + border: 1px solid transparent; + margin: -1px -1px -1px 0; + + &.is-active { + border-color: transparent; + border-top-color: rgb(209, 219, 229); + border-bottom-color: rgb(209, 219, 229); } } } diff --git a/test/unit/specs/tabs.spec.js b/test/unit/specs/tabs.spec.js index 81fd3f0a2..900fd35c0 100644 --- a/test/unit/specs/tabs.spec.js +++ b/test/unit/specs/tabs.spec.js @@ -348,4 +348,111 @@ describe('Tabs', () => { }); }); }); + it('tab-position', done => { + vm = createVue({ + template: ` + + A + B + C + D + + ` + }, true); + + let paneList = vm.$el.querySelector('.el-tabs__content').children; + let spy = sinon.spy(); + + vm.$refs.tabs.$on('tab-click', spy); + + setTimeout(_ => { + const tabList = vm.$refs.tabs.$refs.nav.$refs.tabs; + expect(tabList[0].classList.contains('is-active')).to.be.true; + expect(paneList[0].style.display).to.not.ok; + + tabList[2].click(); + vm.$nextTick(_ => { + expect(spy.withArgs(vm.$refs['pane-click']).calledOnce).to.true; + expect(tabList[2].classList.contains('is-active')).to.be.true; + expect(paneList[2].style.display).to.not.ok; + done(); + }); + }, 100); + }); + it('horizonal-scrollable', done => { + vm = createVue({ + template: ` + + A + B + A + B + A + B + D + + ` + }, true); + + setTimeout(_ => { + const btnPrev = vm.$el.querySelector('.el-tabs__nav-prev'); + btnPrev.click(); + vm.$nextTick(_ => { + const tabNav = vm.$el.querySelector('.el-tabs__nav-wrap'); + expect(tabNav.__vue__.navOffset).to.be.equal(0); + + const btnNext = vm.$el.querySelector('.el-tabs__nav-next'); + btnNext.click(); + + vm.$nextTick(_ => { + expect(tabNav.__vue__.navOffset).to.not.be.equal(0); + + btnPrev.click(); + + vm.$nextTick(_ => { + expect(tabNav.__vue__.navOffset).to.be.equal(0); + done(); + }); + }); + }); + }, 100); + }); + it('vertical-scrollable', done => { + vm = createVue({ + template: ` + + A + B + A + B + A + B + D + + ` + }, true); + + setTimeout(_ => { + const btnPrev = vm.$el.querySelector('.el-tabs__nav-prev'); + btnPrev.click(); + vm.$nextTick(_ => { + const tabNav = vm.$el.querySelector('.el-tabs__nav-wrap'); + expect(tabNav.__vue__.navOffset).to.be.equal(0); + + const btnNext = vm.$el.querySelector('.el-tabs__nav-next'); + btnNext.click(); + + vm.$nextTick(_ => { + expect(tabNav.__vue__.navOffset).to.not.be.equal(0); + + btnPrev.click(); + + vm.$nextTick(_ => { + expect(tabNav.__vue__.navOffset).to.be.equal(0); + done(); + }); + }); + }); + }, 100); + }); }); From de284770643f4d5c70dfcc570c1829ad720829d6 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Sun, 30 Jul 2017 16:23:01 +0800 Subject: [PATCH 013/193] TimePicker: support keyboard control (#6050) * TimePicker: support keyboard control * fix time-range * fix timeformat --- .../date-picker/src/basic/time-spinner.vue | 33 +++++++++++++++- packages/date-picker/src/panel/time-range.vue | 38 ++++++++++++++++++- packages/date-picker/src/panel/time.vue | 19 +++++++++- .../date-picker/src/picker/time-picker.js | 38 +++++++++++++++++++ 4 files changed, 124 insertions(+), 4 deletions(-) diff --git a/packages/date-picker/src/basic/time-spinner.vue b/packages/date-picker/src/basic/time-spinner.vue index 012669977..3dfbdfa15 100644 --- a/packages/date-picker/src/basic/time-spinner.vue +++ b/packages/date-picker/src/basic/time-spinner.vue @@ -129,7 +129,8 @@ hoursPrivate: 0, minutesPrivate: 0, secondsPrivate: 0, - selectableRange: [] + selectableRange: [], + currentScrollbar: null }; }, @@ -162,6 +163,7 @@ } else if (type === 'seconds') { this.$emit('select-range', 6, 8); } + this.currentScrollbar = type; }, bindScrollEvent() { @@ -188,6 +190,35 @@ ajustElTop(type, value) { this[`${type}El`].scrollTop = Math.max(0, (value - 2.5) * 32 + 80); + }, + + scrollDown(step) { + if (!this.currentScrollbar) { + this.emitSelectRange('hours'); + } + + const label = this.currentScrollbar; + const hoursList = this.hoursList; + let now = this[label]; + + if (this.currentScrollbar === 'hours') { + let total = Math.abs(step); + step = step > 0 ? 1 : -1; + let length = hoursList.length; + while (length-- && total) { + now = (now + step + hoursList.length) % hoursList.length; + if (hoursList[now]) { + continue; + } + total--; + } + if (hoursList[now]) return; + } else { + now = (now + step + 60) % 60; + } + + this.$emit('change', { [label]: now }); + this.ajustElTop(label.slice(0, -1), now); } } }; diff --git a/packages/date-picker/src/panel/time-range.vue b/packages/date-picker/src/panel/time-range.vue index fca298d03..854731f1d 100644 --- a/packages/date-picker/src/panel/time-range.vue +++ b/packages/date-picker/src/panel/time-range.vue @@ -89,6 +89,14 @@ computed: { showSeconds() { return (this.format || '').indexOf('ss') !== -1; + }, + + offset() { + return this.showSeconds ? 11 : 8; + }, + + spinner() { + return this.selectionRange[0] < this.offset ? this.$refs.minSpinner : this.$refs.maxSpinner; } }, @@ -110,7 +118,8 @@ minSeconds: time.minTime.getSeconds(), format: 'HH:mm:ss', visible: false, - width: 0 + width: 0, + selectionRange: [0, 2] }; }, @@ -118,6 +127,12 @@ value(newVal) { this.panelCreated(); this.$nextTick(_ => this.ajustScrollTop()); + }, + + visible(val) { + if (val) { + this.$nextTick(() => this.$refs.minSpinner.emitSelectRange('hours')); + } } }, @@ -194,10 +209,12 @@ setMinSelectionRange(start, end) { this.$emit('select-range', start, end); + this.selectionRange = [start, end]; }, setMaxSelectionRange(start, end) { - this.$emit('select-range', start + 11, end + 11); + this.$emit('select-range', start + this.offset, end + this.offset); + this.selectionRange = [start + this.offset, end + this.offset]; }, handleConfirm(visible = false, first = false) { @@ -214,6 +231,23 @@ ajustScrollTop() { this.$refs.minSpinner.ajustScrollTop(); this.$refs.maxSpinner.ajustScrollTop(); + }, + + scrollDown(step) { + this.spinner.scrollDown(step); + }, + + changeSelectionRange(step) { + const list = this.showSeconds ? [0, 3, 6, 11, 14, 17] : [0, 3, 8, 11]; + const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []); + const index = list.indexOf(this.selectionRange[0]); + const next = (index + step + list.length) % list.length; + const half = list.length / 2; + if (next < half) { + this.$refs.minSpinner.emitSelectRange(mapping[next]); + } else { + this.$refs.maxSpinner.emitSelectRange(mapping[next - half]); + } } }, diff --git a/packages/date-picker/src/panel/time.vue b/packages/date-picker/src/panel/time.vue index 0d2c11a97..32373ad38 100644 --- a/packages/date-picker/src/panel/time.vue +++ b/packages/date-picker/src/panel/time.vue @@ -54,6 +54,9 @@ watch: { visible(val) { this.currentVisible = val; + if (val) { + this.$nextTick(() => this.$refs.spinner.emitSelectRange('hours')); + } }, pickerWidth(val) { @@ -92,7 +95,8 @@ selectableRange: [], currentDate: this.$options.defaultValue || this.date || new Date(), currentVisible: this.visible || false, - width: this.pickerWidth || 0 + width: this.pickerWidth || 0, + selectionRange: [0, 2] }; }, @@ -130,6 +134,7 @@ setSelectionRange(start, end) { this.$emit('select-range', start, end); + this.selectionRange = [start, end]; }, handleConfirm(visible = false, first) { @@ -140,6 +145,18 @@ ajustScrollTop() { return this.$refs.spinner.ajustScrollTop(); + }, + + scrollDown(step) { + this.$refs.spinner.scrollDown(step); + }, + + changeSelectionRange(step) { + const list = [0, 3].concat(this.showSeconds ? [6] : []); + const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []); + const index = list.indexOf(this.selectionRange[0]); + const next = (index + step + list.length) % list.length; + this.$refs.spinner.emitSelectRange(mapping[next]); } }, diff --git a/packages/date-picker/src/picker/time-picker.js b/packages/date-picker/src/picker/time-picker.js index 6834e515b..467621d30 100644 --- a/packages/date-picker/src/picker/time-picker.js +++ b/packages/date-picker/src/picker/time-picker.js @@ -34,5 +34,43 @@ export default { created() { this.type = this.isRange ? 'timerange' : 'time'; this.panel = this.isRange ? TimeRangePanel : TimePanel; + }, + + methods: { + handleKeydown(event) { + const keyCode = event.keyCode; + + // TAB or ESC + if (keyCode === 9 || keyCode === 27) { + this.pickerVisible = false; + event.stopPropagation(); + return; + } + + const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 }; + + // Left or Right + if (keyCode === 37 || keyCode === 39) { + const step = mapping[keyCode]; + this.picker.changeSelectionRange(step); + event.preventDefault(); + return; + } + + // Up or Down + if (keyCode === 38 || keyCode === 40) { + const step = mapping[keyCode]; + this.picker.scrollDown(step); + event.preventDefault(); + return; + } + + if (keyCode === 13) { + this.picker.handleConfirm(); + this.$refs.reference.$refs.input.blur(); + event.preventDefault(); + return; + } + } } }; From 258a165416b23b0f2b59524751b5fbea78b665bf Mon Sep 17 00:00:00 2001 From: baiyaaaaa Date: Sun, 30 Jul 2017 16:26:05 +0800 Subject: [PATCH 014/193] add name prop to form components (#6042) add name prop Revert "feature menu collapse" This reverts commit e5ef11d87825f02357c434aedfcef79192600b1b. --- examples/docs/en-US/date-picker.md | 1 + examples/docs/en-US/datetime-picker.md | 1 + examples/docs/en-US/input-number.md | 1 + examples/docs/en-US/input.md | 1 + examples/docs/en-US/time-picker.md | 1 + examples/docs/zh-CN/date-picker.md | 1 + examples/docs/zh-CN/datetime-picker.md | 1 + examples/docs/zh-CN/input-number.md | 1 + examples/docs/zh-CN/input.md | 1 + examples/docs/zh-CN/time-picker.md | 1 + packages/date-picker/src/picker.vue | 2 ++ packages/input-number/src/input-number.vue | 16 +++++++++------- 12 files changed, 21 insertions(+), 7 deletions(-) diff --git a/examples/docs/en-US/date-picker.md b/examples/docs/en-US/date-picker.md index 328a4bdef..1419f4843 100644 --- a/examples/docs/en-US/date-picker.md +++ b/examples/docs/en-US/date-picker.md @@ -287,6 +287,7 @@ Picking a date range is supported. | picker-options | additional options, check the table below | object | — | {} | | range-separator | range separator | string | - | ' - ' | | default-value | optional default time of the picker | Date | anything accepted by `new Date()` | - | +|name | same as `name` in native input | string | — | — | ### Picker Options | Attribute | Description | Type | Accepted Values | Default | diff --git a/examples/docs/en-US/datetime-picker.md b/examples/docs/en-US/datetime-picker.md index c5a721634..ff473cb8e 100644 --- a/examples/docs/en-US/datetime-picker.md +++ b/examples/docs/en-US/datetime-picker.md @@ -241,6 +241,7 @@ Select date and time in one picker. | popper-class | custom class name for DateTimePicker's dropdown | string | — | — | | picker-options | additional options, check the table below | object | — | {} | | range-separator | range separator | string | - | ' - ' | +|name | same as `name` in native input | string | — | — | ### Picker Options | Attribute | Description | Type | Accepted Values | Default | diff --git a/examples/docs/en-US/input-number.md b/examples/docs/en-US/input-number.md index 196430afc..e5c63a6c7 100644 --- a/examples/docs/en-US/input-number.md +++ b/examples/docs/en-US/input-number.md @@ -135,6 +135,7 @@ Additional `large` and `small` sizes of the input box are available |disabled| whether the component is disabled | boolean | — | false | |controls| whether to enable the control buttons | boolean | — | true | |debounce| debounce delay when typing, in millisecond | number | — | 300 | +|name | same as `name` in native input | string | — | — | ### Events diff --git a/examples/docs/en-US/input.md b/examples/docs/en-US/input.md index e2186f1c5..89c6d76fd 100644 --- a/examples/docs/en-US/input.md +++ b/examples/docs/en-US/input.md @@ -640,6 +640,7 @@ Attribute | Description | Type | Options | Default | popper-class | custom class name for autocomplete's dropdown | string | — | — | | trigger-on-focus | whether show suggestions when input focus | boolean | — | true | | on-icon-click | hook function when clicking on the input icon | function | — | — | +|name | same as `name` in native input | string | — | — | ### props | Attribute | Description | Type | Accepted Values | Default | diff --git a/examples/docs/en-US/time-picker.md b/examples/docs/en-US/time-picker.md index 7783a3fbf..e35ceef6e 100644 --- a/examples/docs/en-US/time-picker.md +++ b/examples/docs/en-US/time-picker.md @@ -161,6 +161,7 @@ Can pick an arbitrary time range. | align | alignment | left/center/right | left | | popper-class | custom class name for TimePicker's dropdown | string | — | — | | picker-options | additional options, check the table below | object | — | {} | +|name | same as `name` in native input | string | — | — | ### Time Select Options | Attribute | Description | Type | Accepted Values | Default | diff --git a/examples/docs/zh-CN/date-picker.md b/examples/docs/zh-CN/date-picker.md index 00e01d929..7ed483a8a 100644 --- a/examples/docs/zh-CN/date-picker.md +++ b/examples/docs/zh-CN/date-picker.md @@ -297,6 +297,7 @@ |picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | | range-separator | 选择范围时的分隔符 | string | - | ' - ' | | default-value | 可选,DatePicker打开时默认显示的时间 | Date | 可被new Date()解析 | - | +| name | 原生属性 | string | — | — | ### Picker Options | 参数 | 说明 | 类型 | 可选值 | 默认值 | diff --git a/examples/docs/zh-CN/datetime-picker.md b/examples/docs/zh-CN/datetime-picker.md index ba5978048..7d4d30ee0 100644 --- a/examples/docs/zh-CN/datetime-picker.md +++ b/examples/docs/zh-CN/datetime-picker.md @@ -240,6 +240,7 @@ | popper-class | DateTimePicker 下拉框的类名 | string | — | — | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | | range-separator | 选择范围时的分隔符 | string | - | ' - ' | +| name | 原生属性 | string | — | — | ### Picker Options | 参数 | 说明 | 类型 | 可选值 | 默认值 | diff --git a/examples/docs/zh-CN/input-number.md b/examples/docs/zh-CN/input-number.md index 137f260d4..1725557c7 100644 --- a/examples/docs/zh-CN/input-number.md +++ b/examples/docs/zh-CN/input-number.md @@ -132,6 +132,7 @@ | disabled | 是否禁用计数器 | boolean | — | false | | controls | 是否使用控制按钮 | boolean | — | true | | debounce | 输入时的去抖延迟,毫秒 | number | — | 300 | +| name | 原生属性 | string | — | — | ### Events | 事件名称 | 说明 | 回调参数 | diff --git a/examples/docs/zh-CN/input.md b/examples/docs/zh-CN/input.md index 710b48a46..a449dca4b 100644 --- a/examples/docs/zh-CN/input.md +++ b/examples/docs/zh-CN/input.md @@ -801,6 +801,7 @@ export default { | trigger-on-focus | 是否在输入框 focus 时显示建议列表 | boolean | — | true | | on-icon-click | 点击图标的回调函数 | function | — | — | | icon | 输入框尾部图标 | string | — | — | +| name | 原生属性 | string | — | — | ### Props | 参数 | 说明 | 类型 | 可选值 | 默认值 | diff --git a/examples/docs/zh-CN/time-picker.md b/examples/docs/zh-CN/time-picker.md index f5ac81ad5..b8f5663a7 100644 --- a/examples/docs/zh-CN/time-picker.md +++ b/examples/docs/zh-CN/time-picker.md @@ -160,6 +160,7 @@ | align | 对齐方式 | string | left, center, right | left | | popper-class | TimePicker 下拉框的类名 | string | — | — | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | +| name | 原生属性 | string | — | — | ### Time Select Options | 参数 | 说明 | 类型 | 可选值 | 默认值 | diff --git a/packages/date-picker/src/picker.vue b/packages/date-picker/src/picker.vue index 03ad385a9..8f573d39c 100644 --- a/packages/date-picker/src/picker.vue +++ b/packages/date-picker/src/picker.vue @@ -5,6 +5,7 @@ :readonly="!editable || readonly" :disabled="disabled" :size="size" + :name="name" v-clickoutside="handleClose" :placeholder="placeholder" @focus="handleFocus" @@ -205,6 +206,7 @@ export default { format: String, readonly: Boolean, placeholder: String, + name: String, disabled: Boolean, clearable: { type: Boolean, diff --git a/packages/input-number/src/input-number.vue b/packages/input-number/src/input-number.vue index 9eac7e06d..256970b79 100644 --- a/packages/input-number/src/input-number.vue +++ b/packages/input-number/src/input-number.vue @@ -33,14 +33,15 @@ :size="size" :max="max" :min="min" + :name="name" ref="input" > - - + + @@ -104,7 +105,8 @@ debounce: { type: Number, default: 300 - } + }, + name: String }, data() { return { From 97183e3bc644cab01182405acafb8509c2be51f6 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Tue, 1 Aug 2017 19:04:31 +0800 Subject: [PATCH 015/193] DatePicker: support keyboard control (#6131) * DatePicker: support keyboard control * DatePicker: support keyboard control * update test timeout * fix space --- packages/date-picker/src/panel/date.vue | 59 ++++++++++++++++++- .../date-picker/src/picker/date-picker.js | 23 ++++++++ test/unit/karma.conf.js | 5 ++ 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/date-picker/src/panel/date.vue b/packages/date-picker/src/panel/date.vue index 7f96f5fc0..03cdb4798 100644 --- a/packages/date-picker/src/panel/date.vue +++ b/packages/date-picker/src/panel/date.vue @@ -1,5 +1,5 @@