Commit 75cb299868c5bee3978ea424da3c5145acefae82
1 parent
2bf3e047
Add keyboard navigation to date|time picker
Showing
16 changed files
with
467 additions
and
67 deletions
Show diff stats
src/components/date-picker/base/confirm.vue
1 | 1 | <template> |
2 | - <div :class="[prefixCls + '-confirm']"> | |
3 | - <span :class="timeClasses" v-if="showTime" @click="handleToggleTime"> | |
4 | - <template v-if="isTime">{{ t('i.datepicker.selectDate') }}</template> | |
5 | - <template v-else>{{ t('i.datepicker.selectTime') }}</template> | |
6 | - </span> | |
7 | - <i-button size="small" type="text" @click.native="handleClear">{{ t('i.datepicker.clear') }}</i-button> | |
8 | - <i-button size="small" type="primary" @click.native="handleSuccess">{{ t('i.datepicker.ok') }}</i-button> | |
2 | + <div :class="[prefixCls + '-confirm']" @keydown.tab.capture="handleTab"> | |
3 | + <i-button :class="timeClasses" size="small" type="text" :disabled="timeDisabled" v-if="showTime" @click="handleToggleTime"> | |
4 | + {{labels.time}} | |
5 | + </i-button> | |
6 | + <i-button size="small" type="ghost" @click.native="handleClear" @keydown.enter.native="handleClear"> | |
7 | + {{labels.clear}} | |
8 | + </i-button> | |
9 | + <i-button size="small" type="primary" @click.native="handleSuccess" @keydown.enter.native="handleSuccess"> | |
10 | + {{labels.ok}} | |
11 | + </i-button> | |
9 | 12 | </div> |
10 | 13 | </template> |
11 | 14 | <script> |
12 | 15 | import iButton from '../../button/button.vue'; |
13 | 16 | import Locale from '../../../mixins/locale'; |
17 | + import Emitter from '../../../mixins/emitter'; | |
14 | 18 | |
15 | 19 | const prefixCls = 'ivu-picker'; |
16 | 20 | |
17 | 21 | export default { |
18 | - mixins: [ Locale ], | |
19 | - components: { iButton }, | |
22 | + mixins: [Locale, Emitter], | |
23 | + components: {iButton}, | |
20 | 24 | props: { |
21 | 25 | showTime: false, |
22 | 26 | isTime: false, |
23 | 27 | timeDisabled: false |
24 | 28 | }, |
25 | - data () { | |
29 | + data() { | |
26 | 30 | return { |
27 | 31 | prefixCls: prefixCls |
28 | 32 | }; |
29 | 33 | }, |
30 | 34 | computed: { |
31 | 35 | timeClasses () { |
32 | - return { | |
33 | - [`${prefixCls}-confirm-time-disabled`]: this.timeDisabled | |
34 | - }; | |
36 | + return `${prefixCls}-confirm-time`; | |
37 | + }, | |
38 | + labels(){ | |
39 | + const labels = ['time', 'clear', 'ok']; | |
40 | + const values = [(this.isTime ? 'selectDate' : 'selectTime'), 'clear', 'ok']; | |
41 | + return labels.reduce((obj, key, i) => { | |
42 | + obj[key] = this.t('i.datepicker.' + values[i]); | |
43 | + return obj; | |
44 | + }, {}); | |
35 | 45 | } |
36 | 46 | }, |
37 | 47 | methods: { |
... | ... | @@ -44,6 +54,17 @@ |
44 | 54 | handleToggleTime () { |
45 | 55 | if (this.timeDisabled) return; |
46 | 56 | this.$emit('on-pick-toggle-time'); |
57 | + this.dispatch('CalendarPicker', 'focus-input'); | |
58 | + }, | |
59 | + handleTab(e) { | |
60 | + const tabbables = [...this.$el.children]; | |
61 | + const expectedFocus = tabbables[e.shiftKey ? 'shift' : 'pop'](); | |
62 | + | |
63 | + if (document.activeElement === expectedFocus) { | |
64 | + e.preventDefault(); | |
65 | + e.stopPropagation(); | |
66 | + this.dispatch('CalendarPicker', 'focus-input'); | |
67 | + } | |
47 | 68 | } |
48 | 69 | } |
49 | 70 | }; | ... | ... |
src/components/date-picker/base/date-table.vue
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | :class="getCellCls(cell)" |
10 | 10 | v-for="(cell, i) in readCells" |
11 | 11 | :key="String(cell.date) + i" |
12 | - @click="handleClick(cell)" | |
12 | + @click="handleClick(cell, $event)" | |
13 | 13 | @mouseenter="handleMouseMove(cell)" |
14 | 14 | > |
15 | 15 | <em>{{ cell.desc }}</em> |
... | ... | @@ -99,7 +99,9 @@ |
99 | 99 | [`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth', |
100 | 100 | [`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth', |
101 | 101 | [`${prefixCls}-cell-week-label`]: cell.type === 'weekLabel', |
102 | - [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end | |
102 | + [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end, | |
103 | + [`${prefixCls}-focused`]: clearHours(cell.date) === clearHours(this.focusedDate) | |
104 | + | |
103 | 105 | } |
104 | 106 | ]; |
105 | 107 | }, | ... | ... |
src/components/date-picker/base/mixin.js
src/components/date-picker/base/month-table.vue
... | ... | @@ -38,14 +38,16 @@ |
38 | 38 | |
39 | 39 | const tableYear = this.tableDate.getFullYear(); |
40 | 40 | const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1))); |
41 | + const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), this.focusedDate.getMonth(), 1)); | |
41 | 42 | |
42 | 43 | for (let i = 0; i < 12; i++) { |
43 | 44 | const cell = deepCopy(cell_tmpl); |
44 | 45 | cell.date = new Date(tableYear, i, 1); |
45 | 46 | cell.text = this.tCell(i + 1); |
46 | - const time = clearHours(cell.date); | |
47 | + const day = clearHours(cell.date); | |
47 | 48 | cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month'; |
48 | - cell.selected = selectedDays.includes(time); | |
49 | + cell.selected = selectedDays.includes(day); | |
50 | + cell.focused = day === focusedDate; | |
49 | 51 | cells.push(cell); |
50 | 52 | } |
51 | 53 | |
... | ... | @@ -59,6 +61,7 @@ |
59 | 61 | { |
60 | 62 | [`${prefixCls}-cell-selected`]: cell.selected, |
61 | 63 | [`${prefixCls}-cell-disabled`]: cell.disabled, |
64 | + [`${prefixCls}-cell-focused`]: cell.focused, | |
62 | 65 | [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end |
63 | 66 | } |
64 | 67 | ]; | ... | ... |
src/components/date-picker/base/time-spinner.vue
... | ... | @@ -22,8 +22,10 @@ |
22 | 22 | import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist'; |
23 | 23 | |
24 | 24 | const prefixCls = 'ivu-time-picker-cells'; |
25 | + const timeParts = ['hours', 'minutes', 'seconds']; | |
25 | 26 | |
26 | 27 | export default { |
28 | + name: 'TimeSpinner', | |
27 | 29 | mixins: [Options], |
28 | 30 | props: { |
29 | 31 | hours: { |
... | ... | @@ -51,7 +53,9 @@ |
51 | 53 | return { |
52 | 54 | spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one), |
53 | 55 | prefixCls: prefixCls, |
54 | - compiled: false | |
56 | + compiled: false, | |
57 | + focusedColumn: -1, // which column inside the picker | |
58 | + focusedTime: [0, 0, 0] // the values array into [hh, mm, ss] | |
55 | 59 | }; |
56 | 60 | }, |
57 | 61 | computed: { |
... | ... | @@ -66,6 +70,7 @@ |
66 | 70 | hoursList () { |
67 | 71 | let hours = []; |
68 | 72 | const step = this.spinerSteps[0]; |
73 | + const focusedHour = this.focusedColumn === 0 && this.focusedTime[0]; | |
69 | 74 | const hour_tmpl = { |
70 | 75 | text: 0, |
71 | 76 | selected: false, |
... | ... | @@ -76,6 +81,7 @@ |
76 | 81 | for (let i = 0; i < 24; i += step) { |
77 | 82 | const hour = deepCopy(hour_tmpl); |
78 | 83 | hour.text = i; |
84 | + hour.focused = i === focusedHour; | |
79 | 85 | |
80 | 86 | if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) { |
81 | 87 | hour.disabled = true; |
... | ... | @@ -90,6 +96,7 @@ |
90 | 96 | minutesList () { |
91 | 97 | let minutes = []; |
92 | 98 | const step = this.spinerSteps[1]; |
99 | + const focusedMinute = this.focusedColumn === 1 && this.focusedTime[1]; | |
93 | 100 | const minute_tmpl = { |
94 | 101 | text: 0, |
95 | 102 | selected: false, |
... | ... | @@ -100,6 +107,7 @@ |
100 | 107 | for (let i = 0; i < 60; i += step) { |
101 | 108 | const minute = deepCopy(minute_tmpl); |
102 | 109 | minute.text = i; |
110 | + minute.focused = i === focusedMinute; | |
103 | 111 | |
104 | 112 | if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) { |
105 | 113 | minute.disabled = true; |
... | ... | @@ -113,6 +121,7 @@ |
113 | 121 | secondsList () { |
114 | 122 | let seconds = []; |
115 | 123 | const step = this.spinerSteps[2]; |
124 | + const focusedMinute = this.focusedColumn === 2 && this.focusedTime[2]; | |
116 | 125 | const second_tmpl = { |
117 | 126 | text: 0, |
118 | 127 | selected: false, |
... | ... | @@ -123,6 +132,7 @@ |
123 | 132 | for (let i = 0; i < 60; i += step) { |
124 | 133 | const second = deepCopy(second_tmpl); |
125 | 134 | second.text = i; |
135 | + second.focused = i === focusedMinute; | |
126 | 136 | |
127 | 137 | if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) { |
128 | 138 | second.disabled = true; |
... | ... | @@ -141,15 +151,32 @@ |
141 | 151 | `${prefixCls}-cell`, |
142 | 152 | { |
143 | 153 | [`${prefixCls}-cell-selected`]: cell.selected, |
154 | + [`${prefixCls}-cell-focused`]: cell.focused, | |
144 | 155 | [`${prefixCls}-cell-disabled`]: cell.disabled |
156 | + | |
145 | 157 | } |
146 | 158 | ]; |
147 | 159 | }, |
160 | + chooseValue(values){ | |
161 | + const changes = timeParts.reduce((obj, part, i) => { | |
162 | + const value = values[i]; | |
163 | + if (this[part] === value) return obj; | |
164 | + return { | |
165 | + ...obj, | |
166 | + [part]: value | |
167 | + }; | |
168 | + }, {}); | |
169 | + if (Object.keys(changes).length > 0) { | |
170 | + this.emitChange(changes); | |
171 | + } | |
172 | + }, | |
148 | 173 | handleClick (type, cell) { |
149 | 174 | if (cell.disabled) return; |
150 | - const data = {}; | |
151 | - data[type] = cell.text; | |
152 | - this.$emit('on-change', data); | |
175 | + const data = {[type]: cell.text}; | |
176 | + this.emitChange(data); | |
177 | + }, | |
178 | + emitChange(changes){ | |
179 | + this.$emit('on-change', changes); | |
153 | 180 | this.$emit('on-pick-click'); |
154 | 181 | }, |
155 | 182 | scroll (type, index) { |
... | ... | @@ -168,15 +195,19 @@ |
168 | 195 | return index; |
169 | 196 | }, |
170 | 197 | updateScroll () { |
171 | - const times = ['hours', 'minutes', 'seconds']; | |
172 | 198 | this.$nextTick(() => { |
173 | - times.forEach(type => { | |
199 | + timeParts.forEach(type => { | |
174 | 200 | this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]); |
175 | 201 | }); |
176 | 202 | }); |
177 | 203 | }, |
178 | 204 | formatTime (text) { |
179 | 205 | return text < 10 ? '0' + text : text; |
206 | + }, | |
207 | + updateFocusedTime(col, time) { | |
208 | + this.focusedColumn = col; | |
209 | + this.focusedTime = time.slice(); | |
210 | + | |
180 | 211 | } |
181 | 212 | }, |
182 | 213 | watch: { |
... | ... | @@ -191,6 +222,13 @@ |
191 | 222 | seconds (val) { |
192 | 223 | if (!this.compiled) return; |
193 | 224 | this.scroll('seconds', this.secondsList.findIndex(obj => obj.text == val)); |
225 | + }, | |
226 | + focusedTime(updated, old){ | |
227 | + timeParts.forEach((part, i) => { | |
228 | + if (updated[i] === old[i] || typeof updated[i] === 'undefined') return; | |
229 | + const valueIndex = this[`${part}List`].findIndex(obj => obj.text === updated[i]); | |
230 | + this.scroll(part, valueIndex); | |
231 | + }); | |
194 | 232 | } |
195 | 233 | }, |
196 | 234 | mounted () { | ... | ... |
src/components/date-picker/base/year-table.vue
... | ... | @@ -39,13 +39,15 @@ |
39 | 39 | }; |
40 | 40 | |
41 | 41 | const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), 0, 1))); |
42 | + const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), 0, 1)); | |
42 | 43 | |
43 | 44 | for (let i = 0; i < 10; i++) { |
44 | 45 | const cell = deepCopy(cell_tmpl); |
45 | 46 | cell.date = new Date(this.startYear + i, 0, 1); |
46 | 47 | cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'year'; |
47 | - const time = clearHours(cell.date); | |
48 | - cell.selected = selectedDays.includes(time); | |
48 | + const day = clearHours(cell.date); | |
49 | + cell.selected = selectedDays.includes(day); | |
50 | + cell.focused = day === focusedDate; | |
49 | 51 | cells.push(cell); |
50 | 52 | } |
51 | 53 | |
... | ... | @@ -59,6 +61,7 @@ |
59 | 61 | { |
60 | 62 | [`${prefixCls}-cell-selected`]: cell.selected, |
61 | 63 | [`${prefixCls}-cell-disabled`]: cell.disabled, |
64 | + [`${prefixCls}-cell-focused`]: cell.focused, | |
62 | 65 | [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end |
63 | 66 | } |
64 | 67 | ]; | ... | ... |
src/components/date-picker/panel/Date/date-panel-mixin.js
src/components/date-picker/panel/Date/date-range.vue
... | ... | @@ -41,6 +41,8 @@ |
41 | 41 | :range-state="rangeState" |
42 | 42 | :show-week-numbers="showWeekNumbers" |
43 | 43 | :value="preSelecting.left ? [dates[0]] : dates" |
44 | + :focused-date="focusedDate" | |
45 | + | |
44 | 46 | @on-change-range="handleChangeRange" |
45 | 47 | @on-pick="panelPickerHandlers.left" |
46 | 48 | @on-pick-click="handlePickClick" |
... | ... | @@ -80,6 +82,8 @@ |
80 | 82 | :disabled-date="disabledDate" |
81 | 83 | :show-week-numbers="showWeekNumbers" |
82 | 84 | :value="preSelecting.right ? [dates[dates.length - 1]] : dates" |
85 | + :focused-date="focusedDate" | |
86 | + | |
83 | 87 | @on-change-range="handleChangeRange" |
84 | 88 | @on-pick="panelPickerHandlers.right" |
85 | 89 | @on-pick-click="handlePickClick"></component> |
... | ... | @@ -178,7 +182,7 @@ |
178 | 182 | [prefixCls + '-body-time']: this.showTime, |
179 | 183 | [prefixCls + '-body-date']: !this.showTime, |
180 | 184 | } |
181 | - ] | |
185 | + ]; | |
182 | 186 | }, |
183 | 187 | leftDatePanelLabel(){ |
184 | 188 | return this.panelLabelConfig('left'); |
... | ... | @@ -224,10 +228,7 @@ |
224 | 228 | |
225 | 229 | |
226 | 230 | // set panels positioning |
227 | - const leftPanelDate = this.startDate || this.dates[0] || new Date(); | |
228 | - this.leftPanelDate = leftPanelDate; | |
229 | - const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate()); | |
230 | - this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate; | |
231 | + this.setPanelDates(this.startDate || this.dates[0] || new Date()); | |
231 | 232 | }, |
232 | 233 | currentView(currentView){ |
233 | 234 | const leftMonth = this.leftPanelDate.getMonth(); |
... | ... | @@ -246,6 +247,9 @@ |
246 | 247 | }, |
247 | 248 | selectionMode(type){ |
248 | 249 | this.currentView = type || 'range'; |
250 | + }, | |
251 | + focusedDate(date){ | |
252 | + this.setPanelDates(date || new Date()); | |
249 | 253 | } |
250 | 254 | }, |
251 | 255 | methods: { |
... | ... | @@ -254,6 +258,11 @@ |
254 | 258 | this.leftPickerTable = `${this.currentView}-table`; |
255 | 259 | this.rightPickerTable = `${this.currentView}-table`; |
256 | 260 | }, |
261 | + setPanelDates(leftPanelDate){ | |
262 | + this.leftPanelDate = leftPanelDate; | |
263 | + const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate()); | |
264 | + this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate; | |
265 | + }, | |
257 | 266 | panelLabelConfig (direction) { |
258 | 267 | const locale = this.t('i.locale'); |
259 | 268 | const datePanelLabel = this.t('i.datepicker.datePanelLabel'); | ... | ... |
src/components/date-picker/panel/Date/date.vue
... | ... | @@ -39,6 +39,8 @@ |
39 | 39 | :value="dates" |
40 | 40 | :selection-mode="selectionMode" |
41 | 41 | :disabled-date="disabledDate" |
42 | + :focused-date="focusedDate" | |
43 | + | |
42 | 44 | @on-pick="panelPickerHandlers" |
43 | 45 | @on-pick-click="handlePickClick" |
44 | 46 | ></component> |
... | ... | @@ -51,6 +53,8 @@ |
51 | 53 | :format="format" |
52 | 54 | :time-disabled="timeDisabled" |
53 | 55 | :disabled-date="disabledDate" |
56 | + :focused-date="focusedDate" | |
57 | + | |
54 | 58 | v-bind="timePickerOptions" |
55 | 59 | @on-pick="handlePick" |
56 | 60 | @on-pick-click="handlePickClick" |
... | ... | @@ -150,7 +154,6 @@ |
150 | 154 | }, |
151 | 155 | currentView (currentView) { |
152 | 156 | this.$emit('on-selection-mode-change', currentView); |
153 | - this.pickertable = this.getTableType(currentView); | |
154 | 157 | |
155 | 158 | if (this.currentView === 'time') { |
156 | 159 | this.$nextTick(() => { |
... | ... | @@ -162,6 +165,13 @@ |
162 | 165 | selectionMode(type){ |
163 | 166 | this.currentView = type; |
164 | 167 | this.pickerTable = this.getTableType(type); |
168 | + }, | |
169 | + focusedDate(date){ | |
170 | + const isDifferentYear = date.getFullYear() !== this.panelDate.getFullYear(); | |
171 | + const isDifferentMonth = isDifferentYear || date.getMonth() !== this.panelDate.getMonth(); | |
172 | + if (isDifferentYear || isDifferentMonth){ | |
173 | + this.panelDate = date; | |
174 | + } | |
165 | 175 | } |
166 | 176 | }, |
167 | 177 | methods: { | ... | ... |
src/components/date-picker/picker.vue
1 | 1 | <template> |
2 | - <div :class="[prefixCls]" v-clickoutside="handleClose"> | |
2 | + <div | |
3 | + :class="wrapperClasses" | |
4 | + v-click-outside:mousedown.capture="handleClose" | |
5 | + v-click-outside.capture="handleClose" | |
6 | + > | |
3 | 7 | <div ref="reference" :class="[prefixCls + '-rel']"> |
4 | 8 | <slot> |
5 | 9 | <i-input |
... | ... | @@ -12,10 +16,14 @@ |
12 | 16 | :placeholder="placeholder" |
13 | 17 | :value="visualValue" |
14 | 18 | :name="name" |
19 | + ref="input" | |
20 | + | |
15 | 21 | @on-input-change="handleInputChange" |
16 | 22 | @on-focus="handleFocus" |
17 | 23 | @on-blur="handleBlur" |
18 | 24 | @on-click="handleIconClick" |
25 | + @click.native="handleFocus" | |
26 | + @keydown.native="handleKeydown" | |
19 | 27 | @mouseenter.native="handleInputMouseenter" |
20 | 28 | @mouseleave.native="handleInputMouseleave" |
21 | 29 | |
... | ... | @@ -48,6 +56,7 @@ |
48 | 56 | :show-week-numbers="showWeekNumbers" |
49 | 57 | :picker-type="type" |
50 | 58 | :multiple="multiple" |
59 | + :focused-date="focusedDate" | |
51 | 60 | |
52 | 61 | :time-picker-options="timePickerOptions" |
53 | 62 | |
... | ... | @@ -69,21 +78,49 @@ |
69 | 78 | |
70 | 79 | import iInput from '../../components/input/input.vue'; |
71 | 80 | import Drop from '../../components/select/dropdown.vue'; |
72 | - import clickoutside from '../../directives/clickoutside'; | |
81 | + import vClickOutside from 'v-click-outside-x/index'; | |
73 | 82 | import TransferDom from '../../directives/transfer-dom'; |
74 | 83 | import { oneOf } from '../../utils/assist'; |
75 | - import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP } from './util'; | |
84 | + import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP, getDayCountOfMonth } from './util'; | |
85 | + import {findComponentsDownward} from '../../utils/assist'; | |
76 | 86 | import Emitter from '../../mixins/emitter'; |
77 | 87 | |
78 | 88 | const prefixCls = 'ivu-date-picker'; |
89 | + const pickerPrefixCls = 'ivu-picker'; | |
79 | 90 | |
80 | 91 | const isEmptyArray = val => val.reduce((isEmpty, str) => isEmpty && !str || (typeof str === 'string' && str.trim() === ''), true); |
92 | + const keyValueMapper = { | |
93 | + 40: 'up', | |
94 | + 39: 'right', | |
95 | + 38: 'down', | |
96 | + 37: 'left', | |
97 | + }; | |
98 | + | |
99 | + const mapPossibleValues = (key, horizontal, vertical) => { | |
100 | + if (key === 'left') return horizontal * -1; | |
101 | + if (key === 'right') return horizontal * 1; | |
102 | + if (key === 'up') return vertical * 1; | |
103 | + if (key === 'down') return vertical * -1; | |
104 | + }; | |
105 | + | |
106 | + const pulseElement = (el) => { | |
107 | + const pulseClass = 'ivu-date-picker-btn-pulse'; | |
108 | + el.classList.add(pulseClass); | |
109 | + setTimeout(() => el.classList.remove(pulseClass), 200); | |
110 | + }; | |
111 | + | |
112 | + const extractTime = date => { | |
113 | + if (!date) return [0, 0, 0]; | |
114 | + return [ | |
115 | + date.getHours(), date.getMinutes(), date.getSeconds() | |
116 | + ]; | |
117 | + }; | |
118 | + | |
81 | 119 | |
82 | 120 | export default { |
83 | - name: 'CalendarPicker', | |
84 | 121 | mixins: [ Emitter ], |
85 | 122 | components: { iInput, Drop }, |
86 | - directives: { clickoutside, TransferDom }, | |
123 | + directives: { clickOutside: vClickOutside.directive, TransferDom }, | |
87 | 124 | props: { |
88 | 125 | format: { |
89 | 126 | type: String |
... | ... | @@ -172,6 +209,7 @@ |
172 | 209 | const isRange = this.type.includes('range'); |
173 | 210 | const emptyArray = isRange ? [null, null] : [null]; |
174 | 211 | const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value); |
212 | + const focusedTime = initialValue.map(extractTime); | |
175 | 213 | |
176 | 214 | return { |
177 | 215 | prefixCls: prefixCls, |
... | ... | @@ -181,10 +219,24 @@ |
181 | 219 | disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker |
182 | 220 | disableCloseUnderTransfer: false, // transfer ๆจกๅผไธ๏ผ็นๅปDropไนไผ่งฆๅๅ ณ้ญ, |
183 | 221 | selectionMode: this.onSelectionModeChange(this.type), |
184 | - forceInputRerender: 1 | |
222 | + forceInputRerender: 1, | |
223 | + isFocused: false, | |
224 | + focusedDate: initialValue[0] || new Date(), | |
225 | + focusedTime: { | |
226 | + column: 0, // which column inside the picker | |
227 | + picker: 0, // which picker | |
228 | + time: focusedTime, // the values array into [hh, mm, ss], | |
229 | + active: false | |
230 | + }, | |
231 | + internalFocus: false, | |
185 | 232 | }; |
186 | 233 | }, |
187 | 234 | computed: { |
235 | + wrapperClasses(){ | |
236 | + return [prefixCls, { | |
237 | + [prefixCls + '-focused']: this.isFocused | |
238 | + }]; | |
239 | + }, | |
188 | 240 | publicVModelValue(){ |
189 | 241 | if (this.multiple){ |
190 | 242 | return this.internalValue.slice(); |
... | ... | @@ -232,32 +284,246 @@ |
232 | 284 | handleTransferClick () { |
233 | 285 | if (this.transfer) this.disableCloseUnderTransfer = true; |
234 | 286 | }, |
235 | - handleClose () { | |
287 | + handleClose (e) { | |
236 | 288 | if (this.disableCloseUnderTransfer) { |
237 | 289 | this.disableCloseUnderTransfer = false; |
238 | 290 | return false; |
239 | 291 | } |
240 | - if (this.open !== null) return; | |
241 | 292 | |
242 | - this.visible = false; | |
293 | + if (e && e.type === 'mousedown' && this.visible) { | |
294 | + e.preventDefault(); | |
295 | + e.stopPropagation(); | |
296 | + return; | |
297 | + } | |
298 | + | |
299 | + if (this.visible) { | |
300 | + const pickerPanel = this.$refs.pickerPanel && this.$refs.pickerPanel.$el; | |
301 | + if (e && pickerPanel && pickerPanel.contains(e.target)) return; // its a click inside own component, lets ignore it. | |
302 | + | |
303 | + this.visible = false; | |
304 | + e && e.preventDefault(); | |
305 | + e && e.stopPropagation(); | |
306 | + return; | |
307 | + } | |
308 | + | |
309 | + this.isFocused = false; | |
243 | 310 | this.disableClickOutSide = false; |
244 | 311 | }, |
245 | - handleFocus () { | |
312 | + handleFocus (e) { | |
246 | 313 | if (this.readonly) return; |
314 | + this.isFocused = true; | |
315 | + if (e && e.type === 'focus') return; // just focus, don't open yet | |
247 | 316 | this.visible = true; |
248 | - this.$refs.pickerPanel.onToggleVisibility(true); | |
249 | 317 | }, |
250 | - handleBlur () { | |
251 | - this.visible = false; | |
318 | + handleBlur (e) { | |
319 | + if (this.internalFocus){ | |
320 | + this.internalFocus = false; | |
321 | + return; | |
322 | + } | |
323 | + if (this.visible) { | |
324 | + e.preventDefault(); | |
325 | + return; | |
326 | + } | |
327 | + | |
328 | + this.isFocused = false; | |
252 | 329 | this.onSelectionModeChange(this.type); |
253 | 330 | this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views |
254 | 331 | this.reset(); |
255 | 332 | this.$refs.pickerPanel.onToggleVisibility(false); |
256 | 333 | |
257 | 334 | }, |
335 | + handleKeydown(e){ | |
336 | + const keyCode = e.keyCode; | |
337 | + | |
338 | + // handle "tab" key | |
339 | + if (keyCode === 9){ | |
340 | + if (this.visible){ | |
341 | + e.stopPropagation(); | |
342 | + e.preventDefault(); | |
343 | + | |
344 | + if (this.isConfirm){ | |
345 | + const selector = `.${pickerPrefixCls}-confirm > *`; | |
346 | + const tabbable = this.$refs.drop.$el.querySelectorAll(selector); | |
347 | + this.internalFocus = true; | |
348 | + const element = [...tabbable][e.shiftKey ? 'pop' : 'shift'](); | |
349 | + element.focus(); | |
350 | + } else { | |
351 | + this.handleClose(); | |
352 | + } | |
353 | + } else { | |
354 | + this.focused = false; | |
355 | + } | |
356 | + } | |
357 | + | |
358 | + // open the panel | |
359 | + const arrows = [37, 38, 39, 40]; | |
360 | + if (!this.visible && arrows.includes(keyCode)){ | |
361 | + this.visible = true; | |
362 | + return; | |
363 | + } | |
364 | + | |
365 | + // close on "esc" key | |
366 | + if (keyCode === 27){ | |
367 | + if (this.visible) { | |
368 | + e.stopPropagation(); | |
369 | + this.handleClose(); | |
370 | + } | |
371 | + } | |
372 | + | |
373 | + // select date, "Enter" key | |
374 | + if (keyCode === 13){ | |
375 | + const timePickers = findComponentsDownward(this, 'TimeSpinner'); | |
376 | + if (timePickers.length > 0){ | |
377 | + const columnsPerPicker = timePickers[0].showSeconds ? 3 : 2; | |
378 | + const pickerIndex = Math.floor(this.focusedTime.column / columnsPerPicker); | |
379 | + const value = this.focusedTime.time[pickerIndex]; | |
380 | + | |
381 | + timePickers[pickerIndex].chooseValue(value); | |
382 | + return; | |
383 | + } | |
384 | + | |
385 | + if (this.type.match(/range/)){ | |
386 | + this.$refs.pickerPanel.handleRangePick(this.focusedDate, 'date'); | |
387 | + } else { | |
388 | + this.onPick(this.focusedDate, false, 'date'); | |
389 | + } | |
390 | + } | |
391 | + | |
392 | + if (!arrows.includes(keyCode)) return; // ignore rest of keys | |
393 | + | |
394 | + // navigate times and dates | |
395 | + if (this.focusedTime.active) e.preventDefault(); // to prevent cursor from moving | |
396 | + this.navigateDatePanel(keyValueMapper[keyCode], e.shiftKey); | |
397 | + }, | |
258 | 398 | reset(){ |
259 | 399 | this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset(); |
260 | 400 | }, |
401 | + navigateTimePanel(direction){ | |
402 | + | |
403 | + this.focusedTime.active = true; | |
404 | + const horizontal = direction.match(/left|right/); | |
405 | + const vertical = direction.match(/up|down/); | |
406 | + const timePickers = findComponentsDownward(this, 'TimeSpinner'); | |
407 | + | |
408 | + const maxNrOfColumns = (timePickers[0].showSeconds ? 3 : 2) * timePickers.length; | |
409 | + const column = (currentColumn => { | |
410 | + const incremented = currentColumn + (horizontal ? (direction === 'left' ? -1 : 1) : 0); | |
411 | + return (incremented + maxNrOfColumns) % maxNrOfColumns; | |
412 | + })(this.focusedTime.column); | |
413 | + | |
414 | + const columnsPerPicker = maxNrOfColumns / timePickers.length; | |
415 | + const pickerIndex = Math.floor(column / columnsPerPicker); | |
416 | + const col = column % columnsPerPicker; | |
417 | + | |
418 | + | |
419 | + if (horizontal){ | |
420 | + const time = this.internalValue.map(extractTime); | |
421 | + | |
422 | + this.focusedTime = { | |
423 | + ...this.focusedTime, | |
424 | + column: column, | |
425 | + time: time | |
426 | + }; | |
427 | + timePickers.forEach((instance, i) => { | |
428 | + if (i === pickerIndex) instance.updateFocusedTime(col, time[pickerIndex]); | |
429 | + else instance.updateFocusedTime(-1, instance.focusedTime); | |
430 | + }); | |
431 | + } | |
432 | + | |
433 | + if (vertical){ | |
434 | + const increment = direction === 'up' ? 1 : -1; | |
435 | + const timeParts = ['hours', 'minutes', 'seconds']; | |
436 | + | |
437 | + | |
438 | + const pickerPossibleValues = timePickers[pickerIndex][`${timeParts[col]}List`]; | |
439 | + const nextIndex = pickerPossibleValues.findIndex(({text}) => this.focusedTime.time[pickerIndex][col] === text) + increment; | |
440 | + const nextValue = pickerPossibleValues[nextIndex % pickerPossibleValues.length].text; | |
441 | + const times = this.focusedTime.time.map((time, i) => { | |
442 | + if (i !== pickerIndex) return time; | |
443 | + time[col] = nextValue; | |
444 | + return time; | |
445 | + }); | |
446 | + this.focusedTime = { | |
447 | + ...this.focusedTime, | |
448 | + time: times | |
449 | + }; | |
450 | + | |
451 | + timePickers.forEach((instance, i) => { | |
452 | + if (i === pickerIndex) instance.updateFocusedTime(col, times[i]); | |
453 | + else instance.updateFocusedTime(-1, instance.focusedTime); | |
454 | + }); | |
455 | + } | |
456 | + }, | |
457 | + navigateDatePanel(direction, shift){ | |
458 | + | |
459 | + const timePickers = findComponentsDownward(this, 'TimeSpinner'); | |
460 | + if (timePickers.length > 0) { | |
461 | + // we are in TimePicker mode | |
462 | + this.navigateTimePanel(direction, shift, timePickers); | |
463 | + return; | |
464 | + } | |
465 | + | |
466 | + if (shift){ | |
467 | + if (this.type === 'year'){ | |
468 | + this.focusedDate = new Date( | |
469 | + this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 10), | |
470 | + this.focusedDate.getMonth(), | |
471 | + this.focusedDate.getDate() | |
472 | + ); | |
473 | + } else { | |
474 | + this.focusedDate = new Date( | |
475 | + this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 1), | |
476 | + this.focusedDate.getMonth() + mapPossibleValues(direction, 1, 0), | |
477 | + this.focusedDate.getDate() | |
478 | + ); | |
479 | + } | |
480 | + | |
481 | + const position = direction.match(/left|down/) ? 'prev' : 'next'; | |
482 | + const double = direction.match(/up|down/) ? '-double' : ''; | |
483 | + | |
484 | + // pulse button | |
485 | + const button = this.$refs.drop.$el.querySelector(`.ivu-date-picker-${position}-btn-arrow${double}`); | |
486 | + if (button) pulseElement(button); | |
487 | + return; | |
488 | + } | |
489 | + | |
490 | + const initialDate = this.focusedDate || (this.internalValue && this.internalValue[0]) || new Date(); | |
491 | + const focusedDate = new Date(initialDate); | |
492 | + | |
493 | + if (this.type.match(/^date/)){ | |
494 | + const lastOfMonth = getDayCountOfMonth(initialDate.getFullYear(), initialDate.getMonth()); | |
495 | + const startDay = initialDate.getDate(); | |
496 | + const nextDay = focusedDate.getDate() + mapPossibleValues(direction, 1, 7); | |
497 | + | |
498 | + if (nextDay < 1) { | |
499 | + if (direction.match(/left|right/)) { | |
500 | + focusedDate.setMonth(focusedDate.getMonth() + 1); | |
501 | + focusedDate.setDate(nextDay); | |
502 | + } else { | |
503 | + focusedDate.setDate(startDay + Math.floor((lastOfMonth - startDay) / 7) * 7); | |
504 | + } | |
505 | + } else if (nextDay > lastOfMonth){ | |
506 | + if (direction.match(/left|right/)) { | |
507 | + focusedDate.setMonth(focusedDate.getMonth() - 1); | |
508 | + focusedDate.setDate(nextDay); | |
509 | + } else { | |
510 | + focusedDate.setDate(startDay % 7); | |
511 | + } | |
512 | + } else { | |
513 | + focusedDate.setDate(nextDay); | |
514 | + } | |
515 | + } | |
516 | + | |
517 | + if (this.type.match(/^month/)) { | |
518 | + focusedDate.setMonth(focusedDate.getMonth() + mapPossibleValues(direction, 1, 3)); | |
519 | + } | |
520 | + | |
521 | + if (this.type.match(/^year/)) { | |
522 | + focusedDate.setFullYear(focusedDate.getFullYear() + mapPossibleValues(direction, 1, 3)); | |
523 | + } | |
524 | + | |
525 | + this.focusedDate = focusedDate; | |
526 | + }, | |
261 | 527 | handleInputChange (event) { |
262 | 528 | const isArrayValue = this.type.includes('range') || this.multiple; |
263 | 529 | const oldValue = this.visualValue; |
... | ... | @@ -377,6 +643,12 @@ |
377 | 643 | this.internalValue = Array.isArray(dates) ? dates : [dates]; |
378 | 644 | } |
379 | 645 | |
646 | + this.focusedDate = this.internalValue[0]; | |
647 | + this.focusedTime = { | |
648 | + ...this.focusedTime, | |
649 | + time: this.internalValue.map(extractTime) | |
650 | + }; | |
651 | + | |
380 | 652 | if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode |
381 | 653 | if (!this.isConfirm) this.visible = visible; |
382 | 654 | this.emitChange(type); |
... | ... | @@ -384,22 +656,23 @@ |
384 | 656 | onPickSuccess(){ |
385 | 657 | this.visible = false; |
386 | 658 | this.$emit('on-ok'); |
659 | + this.focus(); | |
387 | 660 | this.reset(); |
388 | 661 | }, |
662 | + focus() { | |
663 | + this.$refs.input.focus(); | |
664 | + } | |
389 | 665 | }, |
390 | 666 | watch: { |
391 | 667 | visible (state) { |
392 | 668 | if (state === false){ |
393 | 669 | this.$refs.drop.destroy(); |
394 | - const input = this.$el.querySelector('input'); | |
395 | - if (input) input.blur(); | |
396 | 670 | } |
397 | 671 | this.$refs.drop.update(); |
398 | 672 | this.$emit('on-open-change', state); |
399 | 673 | }, |
400 | 674 | value(val) { |
401 | 675 | this.internalValue = this.parseDate(val); |
402 | - | |
403 | 676 | }, |
404 | 677 | open (val) { |
405 | 678 | this.visible = val === true; |
... | ... | @@ -421,6 +694,9 @@ |
421 | 694 | this.$emit('input', this.publicVModelValue); // to update v-model |
422 | 695 | } |
423 | 696 | if (this.open !== null) this.visible = this.open; |
697 | + | |
698 | + // to handle focus from confirm buttons | |
699 | + this.$on('focus-input', () => this.focus()); | |
424 | 700 | } |
425 | 701 | }; |
426 | 702 | </script> | ... | ... |
src/components/date-picker/picker/date-picker.js
src/components/date-picker/picker/time-picker.js
... | ... | @@ -3,7 +3,7 @@ import TimePickerPanel from '../panel/Time/time.vue'; |
3 | 3 | import RangeTimePickerPanel from '../panel/Time/time-range.vue'; |
4 | 4 | import Options from '../time-mixins'; |
5 | 5 | |
6 | -import { oneOf } from '../../../utils/assist'; | |
6 | +import { findComponentsDownward, oneOf } from '../../../utils/assist'; | |
7 | 7 | |
8 | 8 | export default { |
9 | 9 | mixins: [Picker, Options], |
... | ... | @@ -30,4 +30,14 @@ export default { |
30 | 30 | }; |
31 | 31 | } |
32 | 32 | }, |
33 | + watch: { | |
34 | + visible(visible){ | |
35 | + if (visible) { | |
36 | + this.$nextTick(() => { | |
37 | + const spinners = findComponentsDownward(this, 'TimeSpinner'); | |
38 | + spinners.forEach(instance => instance.updateScroll()); | |
39 | + }); | |
40 | + } | |
41 | + } | |
42 | + } | |
33 | 43 | }; | ... | ... |
src/styles/components/date-picker.less
... | ... | @@ -44,17 +44,18 @@ |
44 | 44 | margin: 2px; |
45 | 45 | color: @btn-disable-color; |
46 | 46 | } |
47 | + &-cell:hover, &-focused{ | |
48 | + em{ | |
49 | + background: @date-picker-cell-hover-bg; | |
50 | + } | |
51 | + } | |
52 | + | |
47 | 53 | &-cell{ |
48 | 54 | span&{ |
49 | 55 | width: 28px; |
50 | 56 | height: 28px; |
51 | 57 | cursor: pointer; |
52 | 58 | } |
53 | - &:hover{ | |
54 | - em{ | |
55 | - background: @date-picker-cell-hover-bg; | |
56 | - } | |
57 | - } | |
58 | 59 | &-prev-month,&-next-month{ |
59 | 60 | em{ |
60 | 61 | color: @btn-disable-color; |
... | ... | @@ -154,6 +155,11 @@ |
154 | 155 | margin: 0; |
155 | 156 | } |
156 | 157 | } |
158 | + | |
159 | + .@{date-picker-prefix-cls}-cells-cell-focused{ | |
160 | + background-color: tint(@primary-color, 80%); | |
161 | + } | |
162 | + | |
157 | 163 | } |
158 | 164 | |
159 | 165 | &-header{ |
... | ... | @@ -169,6 +175,11 @@ |
169 | 175 | } |
170 | 176 | } |
171 | 177 | } |
178 | + &-btn-pulse{ | |
179 | + background-color: tint(@primary-color, 80%) !important; | |
180 | + border-radius: @border-radius-small; | |
181 | + transition: background-color @transition-time @ease-in-out; | |
182 | + } | |
172 | 183 | &-prev-btn{ |
173 | 184 | float: left; |
174 | 185 | &-arrow-double{ |
... | ... | @@ -216,6 +227,10 @@ |
216 | 227 | max-height: none; |
217 | 228 | width: auto; |
218 | 229 | } |
230 | + | |
231 | + &-focused input{ | |
232 | + .active(); | |
233 | + } | |
219 | 234 | } |
220 | 235 | |
221 | 236 | .@{picker-prefix-cls} { |
... | ... | @@ -289,9 +304,9 @@ |
289 | 304 | color: @link-active-color; |
290 | 305 | } |
291 | 306 | } |
292 | - & > span&-time-disabled{ | |
293 | - color: @btn-disable-color; | |
294 | - cursor: @cursor-disabled; | |
307 | + | |
308 | + &-time{ | |
309 | + float: left; | |
295 | 310 | } |
296 | 311 | } |
297 | 312 | } | ... | ... |
src/styles/components/time-picker.less
... | ... | @@ -70,6 +70,9 @@ |
70 | 70 | color: @primary-color; |
71 | 71 | background: @background-color-select-hover; |
72 | 72 | } |
73 | + &-focused{ | |
74 | + background-color: tint(@primary-color, 80%); | |
75 | + } | |
73 | 76 | } |
74 | 77 | } |
75 | 78 | |
... | ... | @@ -165,4 +168,4 @@ |
165 | 168 | } |
166 | 169 | } |
167 | 170 | } |
168 | -} | |
169 | 171 | \ No newline at end of file |
172 | +} | ... | ... |
test/unit/specs/date-picker.spec.js
... | ... | @@ -116,7 +116,7 @@ describe('DatePicker.vue', () => { |
116 | 116 | `); |
117 | 117 | |
118 | 118 | const picker = vm.$children[0]; |
119 | - picker.handleIconClick(); | |
119 | + picker.handleFocus({type: 'focus'}); | |
120 | 120 | vm.$nextTick(() => { |
121 | 121 | const displayField = vm.$el.querySelector('.ivu-input'); |
122 | 122 | const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); |
... | ... | @@ -169,7 +169,7 @@ describe('DatePicker.vue', () => { |
169 | 169 | }); |
170 | 170 | |
171 | 171 | const picker = vm.$children[0]; |
172 | - picker.handleIconClick(); | |
172 | + picker.handleFocus({type: 'focus'}); | |
173 | 173 | vm.$nextTick(() => { |
174 | 174 | const panel = vm.$el.querySelector('.ivu-picker-panel-content'); |
175 | 175 | const dayPanel = panel.querySelector('[class="ivu-date-picker-cells"]'); |
... | ... | @@ -243,7 +243,7 @@ describe('DatePicker.vue', () => { |
243 | 243 | `); |
244 | 244 | |
245 | 245 | const picker = vm.$children[0]; |
246 | - picker.handleIconClick(); | |
246 | + picker.handleFocus({type: 'focus'}); | |
247 | 247 | vm.$nextTick(() => { |
248 | 248 | const displayField = vm.$el.querySelector('.ivu-input'); |
249 | 249 | const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); |
... | ... | @@ -266,9 +266,11 @@ describe('DatePicker.vue', () => { |
266 | 266 | // it should be closed by now |
267 | 267 | expect(picker.visible).to.equal(false); |
268 | 268 | // open picker again |
269 | - picker.handleIconClick(); | |
269 | + picker.handleFocus({type: 'focus'}); | |
270 | + picker.visible = true; | |
270 | 271 | |
271 | - vm.$nextTick(() => { | |
272 | + | |
273 | + vm.$nextTick(() => { | |
272 | 274 | expect(picker.visible).to.equal(true); |
273 | 275 | expect(JSON.stringify(picker.internalValue)).to.equal('[null,null]'); |
274 | 276 | expect(displayField.value).to.equal(''); |
... | ... | @@ -355,7 +357,7 @@ describe('DatePicker.vue', () => { |
355 | 357 | `); |
356 | 358 | |
357 | 359 | const picker = vm.$children[0]; |
358 | - picker.handleIconClick(); | |
360 | + picker.handleFocus({type: 'focus'}); | |
359 | 361 | vm.$nextTick(() => { |
360 | 362 | const now = new Date(); |
361 | 363 | const labels = vm.$el.querySelectorAll('.ivu-picker-panel-body .ivu-date-picker-header-label'); | ... | ... |
test/unit/specs/time-spinner.spec.js
... | ... | @@ -11,7 +11,7 @@ describe('TimePicker.vue', () => { |
11 | 11 | <Time-Picker></Time-Picker> |
12 | 12 | `); |
13 | 13 | const picker = vm.$children[0]; |
14 | - picker.handleIconClick(); // open the picker panels | |
14 | + picker.handleFocus({type: 'focus'}); // open the picker panels | |
15 | 15 | |
16 | 16 | vm.$nextTick(() => { |
17 | 17 | const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); |
... | ... | @@ -28,7 +28,7 @@ describe('TimePicker.vue', () => { |
28 | 28 | <Time-Picker format="HH:mm"></Time-Picker> |
29 | 29 | `); |
30 | 30 | const picker = vm.$children[0]; |
31 | - picker.handleIconClick(); // open the picker panels | |
31 | + picker.handleFocus({type: 'focus'}); // open the picker panels | |
32 | 32 | |
33 | 33 | vm.$nextTick(() => { |
34 | 34 | const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); |
... | ... | @@ -44,7 +44,7 @@ describe('TimePicker.vue', () => { |
44 | 44 | <Time-Picker :steps="[1, 15]"></Time-Picker> |
45 | 45 | `); |
46 | 46 | const picker = vm.$children[0]; |
47 | - picker.handleIconClick(); // open the picker panels | |
47 | + picker.handleFocus({type: 'focus'}); // open the picker panels | |
48 | 48 | |
49 | 49 | vm.$nextTick(() => { |
50 | 50 | const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); | ... | ... |