Commit 75cb299868c5bee3978ea424da3c5145acefae82

Authored by Sergio Crisostomo
1 parent 2bf3e047

Add keyboard navigation to date|time picker

src/components/date-picker/base/confirm.vue
1 <template> 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 </div> 12 </div>
10 </template> 13 </template>
11 <script> 14 <script>
12 import iButton from '../../button/button.vue'; 15 import iButton from '../../button/button.vue';
13 import Locale from '../../../mixins/locale'; 16 import Locale from '../../../mixins/locale';
  17 + import Emitter from '../../../mixins/emitter';
14 18
15 const prefixCls = 'ivu-picker'; 19 const prefixCls = 'ivu-picker';
16 20
17 export default { 21 export default {
18 - mixins: [ Locale ],  
19 - components: { iButton }, 22 + mixins: [Locale, Emitter],
  23 + components: {iButton},
20 props: { 24 props: {
21 showTime: false, 25 showTime: false,
22 isTime: false, 26 isTime: false,
23 timeDisabled: false 27 timeDisabled: false
24 }, 28 },
25 - data () { 29 + data() {
26 return { 30 return {
27 prefixCls: prefixCls 31 prefixCls: prefixCls
28 }; 32 };
29 }, 33 },
30 computed: { 34 computed: {
31 timeClasses () { 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 methods: { 47 methods: {
@@ -44,6 +54,17 @@ @@ -44,6 +54,17 @@
44 handleToggleTime () { 54 handleToggleTime () {
45 if (this.timeDisabled) return; 55 if (this.timeDisabled) return;
46 this.$emit('on-pick-toggle-time'); 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,7 +9,7 @@
9 :class="getCellCls(cell)" 9 :class="getCellCls(cell)"
10 v-for="(cell, i) in readCells" 10 v-for="(cell, i) in readCells"
11 :key="String(cell.date) + i" 11 :key="String(cell.date) + i"
12 - @click="handleClick(cell)" 12 + @click="handleClick(cell, $event)"
13 @mouseenter="handleMouseMove(cell)" 13 @mouseenter="handleMouseMove(cell)"
14 > 14 >
15 <em>{{ cell.desc }}</em> 15 <em>{{ cell.desc }}</em>
@@ -99,7 +99,9 @@ @@ -99,7 +99,9 @@
99 [`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth', 99 [`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth',
100 [`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth', 100 [`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth',
101 [`${prefixCls}-cell-week-label`]: cell.type === 'weekLabel', 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
@@ -26,7 +26,10 @@ export default { @@ -26,7 +26,10 @@ export default {
26 selecting: false 26 selecting: false
27 }) 27 })
28 }, 28 },
29 - 29 + focusedDate: {
  30 + type: Date,
  31 + required: true,
  32 + }
30 }, 33 },
31 computed: { 34 computed: {
32 dates(){ 35 dates(){
src/components/date-picker/base/month-table.vue
@@ -38,14 +38,16 @@ @@ -38,14 +38,16 @@
38 38
39 const tableYear = this.tableDate.getFullYear(); 39 const tableYear = this.tableDate.getFullYear();
40 const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1))); 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 for (let i = 0; i < 12; i++) { 43 for (let i = 0; i < 12; i++) {
43 const cell = deepCopy(cell_tmpl); 44 const cell = deepCopy(cell_tmpl);
44 cell.date = new Date(tableYear, i, 1); 45 cell.date = new Date(tableYear, i, 1);
45 cell.text = this.tCell(i + 1); 46 cell.text = this.tCell(i + 1);
46 - const time = clearHours(cell.date); 47 + const day = clearHours(cell.date);
47 cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month'; 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 cells.push(cell); 51 cells.push(cell);
50 } 52 }
51 53
@@ -59,6 +61,7 @@ @@ -59,6 +61,7 @@
59 { 61 {
60 [`${prefixCls}-cell-selected`]: cell.selected, 62 [`${prefixCls}-cell-selected`]: cell.selected,
61 [`${prefixCls}-cell-disabled`]: cell.disabled, 63 [`${prefixCls}-cell-disabled`]: cell.disabled,
  64 + [`${prefixCls}-cell-focused`]: cell.focused,
62 [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end 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,8 +22,10 @@
22 import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist'; 22 import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist';
23 23
24 const prefixCls = 'ivu-time-picker-cells'; 24 const prefixCls = 'ivu-time-picker-cells';
  25 + const timeParts = ['hours', 'minutes', 'seconds'];
25 26
26 export default { 27 export default {
  28 + name: 'TimeSpinner',
27 mixins: [Options], 29 mixins: [Options],
28 props: { 30 props: {
29 hours: { 31 hours: {
@@ -51,7 +53,9 @@ @@ -51,7 +53,9 @@
51 return { 53 return {
52 spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one), 54 spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one),
53 prefixCls: prefixCls, 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 computed: { 61 computed: {
@@ -66,6 +70,7 @@ @@ -66,6 +70,7 @@
66 hoursList () { 70 hoursList () {
67 let hours = []; 71 let hours = [];
68 const step = this.spinerSteps[0]; 72 const step = this.spinerSteps[0];
  73 + const focusedHour = this.focusedColumn === 0 && this.focusedTime[0];
69 const hour_tmpl = { 74 const hour_tmpl = {
70 text: 0, 75 text: 0,
71 selected: false, 76 selected: false,
@@ -76,6 +81,7 @@ @@ -76,6 +81,7 @@
76 for (let i = 0; i < 24; i += step) { 81 for (let i = 0; i < 24; i += step) {
77 const hour = deepCopy(hour_tmpl); 82 const hour = deepCopy(hour_tmpl);
78 hour.text = i; 83 hour.text = i;
  84 + hour.focused = i === focusedHour;
79 85
80 if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) { 86 if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) {
81 hour.disabled = true; 87 hour.disabled = true;
@@ -90,6 +96,7 @@ @@ -90,6 +96,7 @@
90 minutesList () { 96 minutesList () {
91 let minutes = []; 97 let minutes = [];
92 const step = this.spinerSteps[1]; 98 const step = this.spinerSteps[1];
  99 + const focusedMinute = this.focusedColumn === 1 && this.focusedTime[1];
93 const minute_tmpl = { 100 const minute_tmpl = {
94 text: 0, 101 text: 0,
95 selected: false, 102 selected: false,
@@ -100,6 +107,7 @@ @@ -100,6 +107,7 @@
100 for (let i = 0; i < 60; i += step) { 107 for (let i = 0; i < 60; i += step) {
101 const minute = deepCopy(minute_tmpl); 108 const minute = deepCopy(minute_tmpl);
102 minute.text = i; 109 minute.text = i;
  110 + minute.focused = i === focusedMinute;
103 111
104 if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) { 112 if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) {
105 minute.disabled = true; 113 minute.disabled = true;
@@ -113,6 +121,7 @@ @@ -113,6 +121,7 @@
113 secondsList () { 121 secondsList () {
114 let seconds = []; 122 let seconds = [];
115 const step = this.spinerSteps[2]; 123 const step = this.spinerSteps[2];
  124 + const focusedMinute = this.focusedColumn === 2 && this.focusedTime[2];
116 const second_tmpl = { 125 const second_tmpl = {
117 text: 0, 126 text: 0,
118 selected: false, 127 selected: false,
@@ -123,6 +132,7 @@ @@ -123,6 +132,7 @@
123 for (let i = 0; i < 60; i += step) { 132 for (let i = 0; i < 60; i += step) {
124 const second = deepCopy(second_tmpl); 133 const second = deepCopy(second_tmpl);
125 second.text = i; 134 second.text = i;
  135 + second.focused = i === focusedMinute;
126 136
127 if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) { 137 if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) {
128 second.disabled = true; 138 second.disabled = true;
@@ -141,15 +151,32 @@ @@ -141,15 +151,32 @@
141 `${prefixCls}-cell`, 151 `${prefixCls}-cell`,
142 { 152 {
143 [`${prefixCls}-cell-selected`]: cell.selected, 153 [`${prefixCls}-cell-selected`]: cell.selected,
  154 + [`${prefixCls}-cell-focused`]: cell.focused,
144 [`${prefixCls}-cell-disabled`]: cell.disabled 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 handleClick (type, cell) { 173 handleClick (type, cell) {
149 if (cell.disabled) return; 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 this.$emit('on-pick-click'); 180 this.$emit('on-pick-click');
154 }, 181 },
155 scroll (type, index) { 182 scroll (type, index) {
@@ -168,15 +195,19 @@ @@ -168,15 +195,19 @@
168 return index; 195 return index;
169 }, 196 },
170 updateScroll () { 197 updateScroll () {
171 - const times = ['hours', 'minutes', 'seconds'];  
172 this.$nextTick(() => { 198 this.$nextTick(() => {
173 - times.forEach(type => { 199 + timeParts.forEach(type => {
174 this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]); 200 this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]);
175 }); 201 });
176 }); 202 });
177 }, 203 },
178 formatTime (text) { 204 formatTime (text) {
179 return text < 10 ? '0' + text : text; 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 watch: { 213 watch: {
@@ -191,6 +222,13 @@ @@ -191,6 +222,13 @@
191 seconds (val) { 222 seconds (val) {
192 if (!this.compiled) return; 223 if (!this.compiled) return;
193 this.scroll('seconds', this.secondsList.findIndex(obj => obj.text == val)); 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 mounted () { 234 mounted () {
src/components/date-picker/base/year-table.vue
@@ -39,13 +39,15 @@ @@ -39,13 +39,15 @@
39 }; 39 };
40 40
41 const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), 0, 1))); 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 for (let i = 0; i < 10; i++) { 44 for (let i = 0; i < 10; i++) {
44 const cell = deepCopy(cell_tmpl); 45 const cell = deepCopy(cell_tmpl);
45 cell.date = new Date(this.startYear + i, 0, 1); 46 cell.date = new Date(this.startYear + i, 0, 1);
46 cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'year'; 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 cells.push(cell); 51 cells.push(cell);
50 } 52 }
51 53
@@ -59,6 +61,7 @@ @@ -59,6 +61,7 @@
59 { 61 {
60 [`${prefixCls}-cell-selected`]: cell.selected, 62 [`${prefixCls}-cell-selected`]: cell.selected,
61 [`${prefixCls}-cell-disabled`]: cell.disabled, 63 [`${prefixCls}-cell-disabled`]: cell.disabled,
  64 + [`${prefixCls}-cell-focused`]: cell.focused,
62 [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end 65 [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
63 } 66 }
64 ]; 67 ];
src/components/date-picker/panel/Date/date-panel-mixin.js
@@ -46,6 +46,10 @@ export default { @@ -46,6 +46,10 @@ export default {
46 pickerType: { 46 pickerType: {
47 type: String, 47 type: String,
48 require: true 48 require: true
  49 + },
  50 + focusedDate: {
  51 + type: Date,
  52 + required: true,
49 } 53 }
50 }, 54 },
51 computed: { 55 computed: {
src/components/date-picker/panel/Date/date-range.vue
@@ -41,6 +41,8 @@ @@ -41,6 +41,8 @@
41 :range-state="rangeState" 41 :range-state="rangeState"
42 :show-week-numbers="showWeekNumbers" 42 :show-week-numbers="showWeekNumbers"
43 :value="preSelecting.left ? [dates[0]] : dates" 43 :value="preSelecting.left ? [dates[0]] : dates"
  44 + :focused-date="focusedDate"
  45 +
44 @on-change-range="handleChangeRange" 46 @on-change-range="handleChangeRange"
45 @on-pick="panelPickerHandlers.left" 47 @on-pick="panelPickerHandlers.left"
46 @on-pick-click="handlePickClick" 48 @on-pick-click="handlePickClick"
@@ -80,6 +82,8 @@ @@ -80,6 +82,8 @@
80 :disabled-date="disabledDate" 82 :disabled-date="disabledDate"
81 :show-week-numbers="showWeekNumbers" 83 :show-week-numbers="showWeekNumbers"
82 :value="preSelecting.right ? [dates[dates.length - 1]] : dates" 84 :value="preSelecting.right ? [dates[dates.length - 1]] : dates"
  85 + :focused-date="focusedDate"
  86 +
83 @on-change-range="handleChangeRange" 87 @on-change-range="handleChangeRange"
84 @on-pick="panelPickerHandlers.right" 88 @on-pick="panelPickerHandlers.right"
85 @on-pick-click="handlePickClick"></component> 89 @on-pick-click="handlePickClick"></component>
@@ -178,7 +182,7 @@ @@ -178,7 +182,7 @@
178 [prefixCls + '-body-time']: this.showTime, 182 [prefixCls + '-body-time']: this.showTime,
179 [prefixCls + '-body-date']: !this.showTime, 183 [prefixCls + '-body-date']: !this.showTime,
180 } 184 }
181 - ] 185 + ];
182 }, 186 },
183 leftDatePanelLabel(){ 187 leftDatePanelLabel(){
184 return this.panelLabelConfig('left'); 188 return this.panelLabelConfig('left');
@@ -224,10 +228,7 @@ @@ -224,10 +228,7 @@
224 228
225 229
226 // set panels positioning 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 currentView(currentView){ 233 currentView(currentView){
233 const leftMonth = this.leftPanelDate.getMonth(); 234 const leftMonth = this.leftPanelDate.getMonth();
@@ -246,6 +247,9 @@ @@ -246,6 +247,9 @@
246 }, 247 },
247 selectionMode(type){ 248 selectionMode(type){
248 this.currentView = type || 'range'; 249 this.currentView = type || 'range';
  250 + },
  251 + focusedDate(date){
  252 + this.setPanelDates(date || new Date());
249 } 253 }
250 }, 254 },
251 methods: { 255 methods: {
@@ -254,6 +258,11 @@ @@ -254,6 +258,11 @@
254 this.leftPickerTable = `${this.currentView}-table`; 258 this.leftPickerTable = `${this.currentView}-table`;
255 this.rightPickerTable = `${this.currentView}-table`; 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 panelLabelConfig (direction) { 266 panelLabelConfig (direction) {
258 const locale = this.t('i.locale'); 267 const locale = this.t('i.locale');
259 const datePanelLabel = this.t('i.datepicker.datePanelLabel'); 268 const datePanelLabel = this.t('i.datepicker.datePanelLabel');
src/components/date-picker/panel/Date/date.vue
@@ -39,6 +39,8 @@ @@ -39,6 +39,8 @@
39 :value="dates" 39 :value="dates"
40 :selection-mode="selectionMode" 40 :selection-mode="selectionMode"
41 :disabled-date="disabledDate" 41 :disabled-date="disabledDate"
  42 + :focused-date="focusedDate"
  43 +
42 @on-pick="panelPickerHandlers" 44 @on-pick="panelPickerHandlers"
43 @on-pick-click="handlePickClick" 45 @on-pick-click="handlePickClick"
44 ></component> 46 ></component>
@@ -51,6 +53,8 @@ @@ -51,6 +53,8 @@
51 :format="format" 53 :format="format"
52 :time-disabled="timeDisabled" 54 :time-disabled="timeDisabled"
53 :disabled-date="disabledDate" 55 :disabled-date="disabledDate"
  56 + :focused-date="focusedDate"
  57 +
54 v-bind="timePickerOptions" 58 v-bind="timePickerOptions"
55 @on-pick="handlePick" 59 @on-pick="handlePick"
56 @on-pick-click="handlePickClick" 60 @on-pick-click="handlePickClick"
@@ -150,7 +154,6 @@ @@ -150,7 +154,6 @@
150 }, 154 },
151 currentView (currentView) { 155 currentView (currentView) {
152 this.$emit('on-selection-mode-change', currentView); 156 this.$emit('on-selection-mode-change', currentView);
153 - this.pickertable = this.getTableType(currentView);  
154 157
155 if (this.currentView === 'time') { 158 if (this.currentView === 'time') {
156 this.$nextTick(() => { 159 this.$nextTick(() => {
@@ -162,6 +165,13 @@ @@ -162,6 +165,13 @@
162 selectionMode(type){ 165 selectionMode(type){
163 this.currentView = type; 166 this.currentView = type;
164 this.pickerTable = this.getTableType(type); 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 methods: { 177 methods: {
src/components/date-picker/picker.vue
1 <template> 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 <div ref="reference" :class="[prefixCls + '-rel']"> 7 <div ref="reference" :class="[prefixCls + '-rel']">
4 <slot> 8 <slot>
5 <i-input 9 <i-input
@@ -12,10 +16,14 @@ @@ -12,10 +16,14 @@
12 :placeholder="placeholder" 16 :placeholder="placeholder"
13 :value="visualValue" 17 :value="visualValue"
14 :name="name" 18 :name="name"
  19 + ref="input"
  20 +
15 @on-input-change="handleInputChange" 21 @on-input-change="handleInputChange"
16 @on-focus="handleFocus" 22 @on-focus="handleFocus"
17 @on-blur="handleBlur" 23 @on-blur="handleBlur"
18 @on-click="handleIconClick" 24 @on-click="handleIconClick"
  25 + @click.native="handleFocus"
  26 + @keydown.native="handleKeydown"
19 @mouseenter.native="handleInputMouseenter" 27 @mouseenter.native="handleInputMouseenter"
20 @mouseleave.native="handleInputMouseleave" 28 @mouseleave.native="handleInputMouseleave"
21 29
@@ -48,6 +56,7 @@ @@ -48,6 +56,7 @@
48 :show-week-numbers="showWeekNumbers" 56 :show-week-numbers="showWeekNumbers"
49 :picker-type="type" 57 :picker-type="type"
50 :multiple="multiple" 58 :multiple="multiple"
  59 + :focused-date="focusedDate"
51 60
52 :time-picker-options="timePickerOptions" 61 :time-picker-options="timePickerOptions"
53 62
@@ -69,21 +78,49 @@ @@ -69,21 +78,49 @@
69 78
70 import iInput from '../../components/input/input.vue'; 79 import iInput from '../../components/input/input.vue';
71 import Drop from '../../components/select/dropdown.vue'; 80 import Drop from '../../components/select/dropdown.vue';
72 - import clickoutside from '../../directives/clickoutside'; 81 + import vClickOutside from 'v-click-outside-x/index';
73 import TransferDom from '../../directives/transfer-dom'; 82 import TransferDom from '../../directives/transfer-dom';
74 import { oneOf } from '../../utils/assist'; 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 import Emitter from '../../mixins/emitter'; 86 import Emitter from '../../mixins/emitter';
77 87
78 const prefixCls = 'ivu-date-picker'; 88 const prefixCls = 'ivu-date-picker';
  89 + const pickerPrefixCls = 'ivu-picker';
79 90
80 const isEmptyArray = val => val.reduce((isEmpty, str) => isEmpty && !str || (typeof str === 'string' && str.trim() === ''), true); 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 export default { 120 export default {
83 - name: 'CalendarPicker',  
84 mixins: [ Emitter ], 121 mixins: [ Emitter ],
85 components: { iInput, Drop }, 122 components: { iInput, Drop },
86 - directives: { clickoutside, TransferDom }, 123 + directives: { clickOutside: vClickOutside.directive, TransferDom },
87 props: { 124 props: {
88 format: { 125 format: {
89 type: String 126 type: String
@@ -172,6 +209,7 @@ @@ -172,6 +209,7 @@
172 const isRange = this.type.includes('range'); 209 const isRange = this.type.includes('range');
173 const emptyArray = isRange ? [null, null] : [null]; 210 const emptyArray = isRange ? [null, null] : [null];
174 const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value); 211 const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value);
  212 + const focusedTime = initialValue.map(extractTime);
175 213
176 return { 214 return {
177 prefixCls: prefixCls, 215 prefixCls: prefixCls,
@@ -181,10 +219,24 @@ @@ -181,10 +219,24 @@
181 disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker 219 disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
182 disableCloseUnderTransfer: false, // transfer ๆจกๅผไธ‹๏ผŒ็‚นๅ‡ปDropไนŸไผš่งฆๅ‘ๅ…ณ้—ญ, 220 disableCloseUnderTransfer: false, // transfer ๆจกๅผไธ‹๏ผŒ็‚นๅ‡ปDropไนŸไผš่งฆๅ‘ๅ…ณ้—ญ,
183 selectionMode: this.onSelectionModeChange(this.type), 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 computed: { 234 computed: {
  235 + wrapperClasses(){
  236 + return [prefixCls, {
  237 + [prefixCls + '-focused']: this.isFocused
  238 + }];
  239 + },
188 publicVModelValue(){ 240 publicVModelValue(){
189 if (this.multiple){ 241 if (this.multiple){
190 return this.internalValue.slice(); 242 return this.internalValue.slice();
@@ -232,32 +284,246 @@ @@ -232,32 +284,246 @@
232 handleTransferClick () { 284 handleTransferClick () {
233 if (this.transfer) this.disableCloseUnderTransfer = true; 285 if (this.transfer) this.disableCloseUnderTransfer = true;
234 }, 286 },
235 - handleClose () { 287 + handleClose (e) {
236 if (this.disableCloseUnderTransfer) { 288 if (this.disableCloseUnderTransfer) {
237 this.disableCloseUnderTransfer = false; 289 this.disableCloseUnderTransfer = false;
238 return false; 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 this.disableClickOutSide = false; 310 this.disableClickOutSide = false;
244 }, 311 },
245 - handleFocus () { 312 + handleFocus (e) {
246 if (this.readonly) return; 313 if (this.readonly) return;
  314 + this.isFocused = true;
  315 + if (e && e.type === 'focus') return; // just focus, don't open yet
247 this.visible = true; 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 this.onSelectionModeChange(this.type); 329 this.onSelectionModeChange(this.type);
253 this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views 330 this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views
254 this.reset(); 331 this.reset();
255 this.$refs.pickerPanel.onToggleVisibility(false); 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 reset(){ 398 reset(){
259 this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset(); 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 handleInputChange (event) { 527 handleInputChange (event) {
262 const isArrayValue = this.type.includes('range') || this.multiple; 528 const isArrayValue = this.type.includes('range') || this.multiple;
263 const oldValue = this.visualValue; 529 const oldValue = this.visualValue;
@@ -377,6 +643,12 @@ @@ -377,6 +643,12 @@
377 this.internalValue = Array.isArray(dates) ? dates : [dates]; 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 if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode 652 if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
381 if (!this.isConfirm) this.visible = visible; 653 if (!this.isConfirm) this.visible = visible;
382 this.emitChange(type); 654 this.emitChange(type);
@@ -384,22 +656,23 @@ @@ -384,22 +656,23 @@
384 onPickSuccess(){ 656 onPickSuccess(){
385 this.visible = false; 657 this.visible = false;
386 this.$emit('on-ok'); 658 this.$emit('on-ok');
  659 + this.focus();
387 this.reset(); 660 this.reset();
388 }, 661 },
  662 + focus() {
  663 + this.$refs.input.focus();
  664 + }
389 }, 665 },
390 watch: { 666 watch: {
391 visible (state) { 667 visible (state) {
392 if (state === false){ 668 if (state === false){
393 this.$refs.drop.destroy(); 669 this.$refs.drop.destroy();
394 - const input = this.$el.querySelector('input');  
395 - if (input) input.blur();  
396 } 670 }
397 this.$refs.drop.update(); 671 this.$refs.drop.update();
398 this.$emit('on-open-change', state); 672 this.$emit('on-open-change', state);
399 }, 673 },
400 value(val) { 674 value(val) {
401 this.internalValue = this.parseDate(val); 675 this.internalValue = this.parseDate(val);
402 -  
403 }, 676 },
404 open (val) { 677 open (val) {
405 this.visible = val === true; 678 this.visible = val === true;
@@ -421,6 +694,9 @@ @@ -421,6 +694,9 @@
421 this.$emit('input', this.publicVModelValue); // to update v-model 694 this.$emit('input', this.publicVModelValue); // to update v-model
422 } 695 }
423 if (this.open !== null) this.visible = this.open; 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 </script> 702 </script>
src/components/date-picker/picker/date-picker.js
@@ -5,6 +5,7 @@ import RangeDatePickerPanel from &#39;../panel/Date/date-range.vue&#39;; @@ -5,6 +5,7 @@ import RangeDatePickerPanel from &#39;../panel/Date/date-range.vue&#39;;
5 import { oneOf } from '../../../utils/assist'; 5 import { oneOf } from '../../../utils/assist';
6 6
7 export default { 7 export default {
  8 + name: 'CalendarPicker',
8 mixins: [Picker], 9 mixins: [Picker],
9 props: { 10 props: {
10 type: { 11 type: {
src/components/date-picker/picker/time-picker.js
@@ -3,7 +3,7 @@ import TimePickerPanel from &#39;../panel/Time/time.vue&#39;; @@ -3,7 +3,7 @@ import TimePickerPanel from &#39;../panel/Time/time.vue&#39;;
3 import RangeTimePickerPanel from '../panel/Time/time-range.vue'; 3 import RangeTimePickerPanel from '../panel/Time/time-range.vue';
4 import Options from '../time-mixins'; 4 import Options from '../time-mixins';
5 5
6 -import { oneOf } from '../../../utils/assist'; 6 +import { findComponentsDownward, oneOf } from '../../../utils/assist';
7 7
8 export default { 8 export default {
9 mixins: [Picker, Options], 9 mixins: [Picker, Options],
@@ -30,4 +30,14 @@ export default { @@ -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,17 +44,18 @@
44 margin: 2px; 44 margin: 2px;
45 color: @btn-disable-color; 45 color: @btn-disable-color;
46 } 46 }
  47 + &-cell:hover, &-focused{
  48 + em{
  49 + background: @date-picker-cell-hover-bg;
  50 + }
  51 + }
  52 +
47 &-cell{ 53 &-cell{
48 span&{ 54 span&{
49 width: 28px; 55 width: 28px;
50 height: 28px; 56 height: 28px;
51 cursor: pointer; 57 cursor: pointer;
52 } 58 }
53 - &:hover{  
54 - em{  
55 - background: @date-picker-cell-hover-bg;  
56 - }  
57 - }  
58 &-prev-month,&-next-month{ 59 &-prev-month,&-next-month{
59 em{ 60 em{
60 color: @btn-disable-color; 61 color: @btn-disable-color;
@@ -154,6 +155,11 @@ @@ -154,6 +155,11 @@
154 margin: 0; 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 &-header{ 165 &-header{
@@ -169,6 +175,11 @@ @@ -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 &-prev-btn{ 183 &-prev-btn{
173 float: left; 184 float: left;
174 &-arrow-double{ 185 &-arrow-double{
@@ -216,6 +227,10 @@ @@ -216,6 +227,10 @@
216 max-height: none; 227 max-height: none;
217 width: auto; 228 width: auto;
218 } 229 }
  230 +
  231 + &-focused input{
  232 + .active();
  233 + }
219 } 234 }
220 235
221 .@{picker-prefix-cls} { 236 .@{picker-prefix-cls} {
@@ -289,9 +304,9 @@ @@ -289,9 +304,9 @@
289 color: @link-active-color; 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,6 +70,9 @@
70 color: @primary-color; 70 color: @primary-color;
71 background: @background-color-select-hover; 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,4 +168,4 @@
165 } 168 }
166 } 169 }
167 } 170 }
168 -}  
169 \ No newline at end of file 171 \ No newline at end of file
  172 +}
test/unit/specs/date-picker.spec.js
@@ -116,7 +116,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; { @@ -116,7 +116,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; {
116 `); 116 `);
117 117
118 const picker = vm.$children[0]; 118 const picker = vm.$children[0];
119 - picker.handleIconClick(); 119 + picker.handleFocus({type: 'focus'});
120 vm.$nextTick(() => { 120 vm.$nextTick(() => {
121 const displayField = vm.$el.querySelector('.ivu-input'); 121 const displayField = vm.$el.querySelector('.ivu-input');
122 const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); 122 const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell');
@@ -169,7 +169,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; { @@ -169,7 +169,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; {
169 }); 169 });
170 170
171 const picker = vm.$children[0]; 171 const picker = vm.$children[0];
172 - picker.handleIconClick(); 172 + picker.handleFocus({type: 'focus'});
173 vm.$nextTick(() => { 173 vm.$nextTick(() => {
174 const panel = vm.$el.querySelector('.ivu-picker-panel-content'); 174 const panel = vm.$el.querySelector('.ivu-picker-panel-content');
175 const dayPanel = panel.querySelector('[class="ivu-date-picker-cells"]'); 175 const dayPanel = panel.querySelector('[class="ivu-date-picker-cells"]');
@@ -243,7 +243,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; { @@ -243,7 +243,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; {
243 `); 243 `);
244 244
245 const picker = vm.$children[0]; 245 const picker = vm.$children[0];
246 - picker.handleIconClick(); 246 + picker.handleFocus({type: 'focus'});
247 vm.$nextTick(() => { 247 vm.$nextTick(() => {
248 const displayField = vm.$el.querySelector('.ivu-input'); 248 const displayField = vm.$el.querySelector('.ivu-input');
249 const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell'); 249 const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell');
@@ -266,9 +266,11 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; { @@ -266,9 +266,11 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; {
266 // it should be closed by now 266 // it should be closed by now
267 expect(picker.visible).to.equal(false); 267 expect(picker.visible).to.equal(false);
268 // open picker again 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 expect(picker.visible).to.equal(true); 274 expect(picker.visible).to.equal(true);
273 expect(JSON.stringify(picker.internalValue)).to.equal('[null,null]'); 275 expect(JSON.stringify(picker.internalValue)).to.equal('[null,null]');
274 expect(displayField.value).to.equal(''); 276 expect(displayField.value).to.equal('');
@@ -355,7 +357,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; { @@ -355,7 +357,7 @@ describe(&#39;DatePicker.vue&#39;, () =&gt; {
355 `); 357 `);
356 358
357 const picker = vm.$children[0]; 359 const picker = vm.$children[0];
358 - picker.handleIconClick(); 360 + picker.handleFocus({type: 'focus'});
359 vm.$nextTick(() => { 361 vm.$nextTick(() => {
360 const now = new Date(); 362 const now = new Date();
361 const labels = vm.$el.querySelectorAll('.ivu-picker-panel-body .ivu-date-picker-header-label'); 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(&#39;TimePicker.vue&#39;, () =&gt; { @@ -11,7 +11,7 @@ describe(&#39;TimePicker.vue&#39;, () =&gt; {
11 <Time-Picker></Time-Picker> 11 <Time-Picker></Time-Picker>
12 `); 12 `);
13 const picker = vm.$children[0]; 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 vm.$nextTick(() => { 16 vm.$nextTick(() => {
17 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); 17 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');
@@ -28,7 +28,7 @@ describe(&#39;TimePicker.vue&#39;, () =&gt; { @@ -28,7 +28,7 @@ describe(&#39;TimePicker.vue&#39;, () =&gt; {
28 <Time-Picker format="HH:mm"></Time-Picker> 28 <Time-Picker format="HH:mm"></Time-Picker>
29 `); 29 `);
30 const picker = vm.$children[0]; 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 vm.$nextTick(() => { 33 vm.$nextTick(() => {
34 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); 34 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');
@@ -44,7 +44,7 @@ describe(&#39;TimePicker.vue&#39;, () =&gt; { @@ -44,7 +44,7 @@ describe(&#39;TimePicker.vue&#39;, () =&gt; {
44 <Time-Picker :steps="[1, 15]"></Time-Picker> 44 <Time-Picker :steps="[1, 15]"></Time-Picker>
45 `); 45 `);
46 const picker = vm.$children[0]; 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 vm.$nextTick(() => { 49 vm.$nextTick(() => {
50 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list'); 50 const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');