Commit bdb26ef7c80f44e30ebdc33de2ddd60a3cc19c1f
Committed by
GitHub
Merge pull request #3643 from SergioCrisostomo/datepicker-keyboard
Datepicker keyboard
Showing
17 changed files
with
494 additions
and
77 deletions
Show diff stats
examples/routers/date.vue
| ... | ... | @@ -249,14 +249,17 @@ |
| 249 | 249 | |
| 250 | 250 | <template> |
| 251 | 251 | <div style="width: 500px;margin: 100px;"> |
| 252 | - <Row> | |
| 253 | - <Col span="12"> | |
| 254 | - <DatePicker type="date" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker> | |
| 255 | - </Col> | |
| 256 | - <Col span="12"> | |
| 257 | - <DatePicker type="daterange" show-week-numbers placement="bottom-end" placeholder="Select date" style="width: 200px"></DatePicker> | |
| 258 | - </Col> | |
| 259 | - </Row> | |
| 252 | + <p><input type="text"></p> | |
| 253 | + | |
| 254 | + <DatePicker type="month" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker> | |
| 255 | + <DatePicker type="year" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker> | |
| 256 | + | |
| 257 | + <DatePicker type="date" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker> | |
| 258 | + <DatePicker type="datetime" show-week-numbers confirm placeholder="Select date" style="width: 400px"></DatePicker> | |
| 259 | + | |
| 260 | + <DatePicker type="daterange" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker> | |
| 261 | + <DatePicker type="datetimerange" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker> | |
| 262 | + <Time-Picker :steps="[1, 1, 15]" :value="new Date()"></Time-Picker> | |
| 260 | 263 | </div> |
| 261 | 264 | </template> |
| 262 | 265 | <script> | ... | ... |
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
| ... | ... | @@ -7,9 +7,9 @@ |
| 7 | 7 | </div> |
| 8 | 8 | <span |
| 9 | 9 | :class="getCellCls(cell)" |
| 10 | - v-for="(cell, i) in readCells" | |
| 10 | + v-for="(cell, i) in cells" | |
| 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> |
| ... | ... | @@ -61,7 +61,7 @@ |
| 61 | 61 | const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay)); |
| 62 | 62 | return this.showWeekNumbers ? [''].concat(weekDays) : weekDays; |
| 63 | 63 | }, |
| 64 | - readCells () { | |
| 64 | + cells () { | |
| 65 | 65 | const tableYear = this.tableDate.getFullYear(); |
| 66 | 66 | const tableMonth = this.tableDate.getMonth(); |
| 67 | 67 | const today = clearHours(new Date()); // timestamp of today |
| ... | ... | @@ -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
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | import {clearHours} from '../util'; |
| 3 | 3 | |
| 4 | 4 | export default { |
| 5 | + name: 'PanelTable', | |
| 5 | 6 | props: { |
| 6 | 7 | tableDate: { |
| 7 | 8 | type: Date, |
| ... | ... | @@ -26,7 +27,10 @@ export default { |
| 26 | 27 | selecting: false |
| 27 | 28 | }) |
| 28 | 29 | }, |
| 29 | - | |
| 30 | + focusedDate: { | |
| 31 | + type: Date, | |
| 32 | + required: true, | |
| 33 | + } | |
| 30 | 34 | }, |
| 31 | 35 | computed: { |
| 32 | 36 | dates(){ | ... | ... |
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] || this.startDate || 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,254 @@ |
| 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 | + const panels = findComponentsDownward(this, 'PanelTable'); | |
| 389 | + const compareDate = (d) => { | |
| 390 | + const sliceIndex = ['year', 'month', 'date'].indexOf((this.type)) + 1; | |
| 391 | + return [d.getFullYear(), d.getMonth(), d.getDate()].slice(0, sliceIndex).join('-'); | |
| 392 | + }; | |
| 393 | + const dateIsValid = panels.find(({cells}) => { | |
| 394 | + return cells.find(({date, disabled}) => compareDate(date) === compareDate(this.focusedDate) && !disabled); | |
| 395 | + }); | |
| 396 | + if (dateIsValid) this.onPick(this.focusedDate, false, 'date'); | |
| 397 | + } | |
| 398 | + } | |
| 399 | + | |
| 400 | + if (!arrows.includes(keyCode)) return; // ignore rest of keys | |
| 401 | + | |
| 402 | + // navigate times and dates | |
| 403 | + if (this.focusedTime.active) e.preventDefault(); // to prevent cursor from moving | |
| 404 | + this.navigateDatePanel(keyValueMapper[keyCode], e.shiftKey); | |
| 405 | + }, | |
| 258 | 406 | reset(){ |
| 259 | 407 | this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset(); |
| 260 | 408 | }, |
| 409 | + navigateTimePanel(direction){ | |
| 410 | + | |
| 411 | + this.focusedTime.active = true; | |
| 412 | + const horizontal = direction.match(/left|right/); | |
| 413 | + const vertical = direction.match(/up|down/); | |
| 414 | + const timePickers = findComponentsDownward(this, 'TimeSpinner'); | |
| 415 | + | |
| 416 | + const maxNrOfColumns = (timePickers[0].showSeconds ? 3 : 2) * timePickers.length; | |
| 417 | + const column = (currentColumn => { | |
| 418 | + const incremented = currentColumn + (horizontal ? (direction === 'left' ? -1 : 1) : 0); | |
| 419 | + return (incremented + maxNrOfColumns) % maxNrOfColumns; | |
| 420 | + })(this.focusedTime.column); | |
| 421 | + | |
| 422 | + const columnsPerPicker = maxNrOfColumns / timePickers.length; | |
| 423 | + const pickerIndex = Math.floor(column / columnsPerPicker); | |
| 424 | + const col = column % columnsPerPicker; | |
| 425 | + | |
| 426 | + | |
| 427 | + if (horizontal){ | |
| 428 | + const time = this.internalValue.map(extractTime); | |
| 429 | + | |
| 430 | + this.focusedTime = { | |
| 431 | + ...this.focusedTime, | |
| 432 | + column: column, | |
| 433 | + time: time | |
| 434 | + }; | |
| 435 | + timePickers.forEach((instance, i) => { | |
| 436 | + if (i === pickerIndex) instance.updateFocusedTime(col, time[pickerIndex]); | |
| 437 | + else instance.updateFocusedTime(-1, instance.focusedTime); | |
| 438 | + }); | |
| 439 | + } | |
| 440 | + | |
| 441 | + if (vertical){ | |
| 442 | + const increment = direction === 'up' ? 1 : -1; | |
| 443 | + const timeParts = ['hours', 'minutes', 'seconds']; | |
| 444 | + | |
| 445 | + | |
| 446 | + const pickerPossibleValues = timePickers[pickerIndex][`${timeParts[col]}List`]; | |
| 447 | + const nextIndex = pickerPossibleValues.findIndex(({text}) => this.focusedTime.time[pickerIndex][col] === text) + increment; | |
| 448 | + const nextValue = pickerPossibleValues[nextIndex % pickerPossibleValues.length].text; | |
| 449 | + const times = this.focusedTime.time.map((time, i) => { | |
| 450 | + if (i !== pickerIndex) return time; | |
| 451 | + time[col] = nextValue; | |
| 452 | + return time; | |
| 453 | + }); | |
| 454 | + this.focusedTime = { | |
| 455 | + ...this.focusedTime, | |
| 456 | + time: times | |
| 457 | + }; | |
| 458 | + | |
| 459 | + timePickers.forEach((instance, i) => { | |
| 460 | + if (i === pickerIndex) instance.updateFocusedTime(col, times[i]); | |
| 461 | + else instance.updateFocusedTime(-1, instance.focusedTime); | |
| 462 | + }); | |
| 463 | + } | |
| 464 | + }, | |
| 465 | + navigateDatePanel(direction, shift){ | |
| 466 | + | |
| 467 | + const timePickers = findComponentsDownward(this, 'TimeSpinner'); | |
| 468 | + if (timePickers.length > 0) { | |
| 469 | + // we are in TimePicker mode | |
| 470 | + this.navigateTimePanel(direction, shift, timePickers); | |
| 471 | + return; | |
| 472 | + } | |
| 473 | + | |
| 474 | + if (shift){ | |
| 475 | + if (this.type === 'year'){ | |
| 476 | + this.focusedDate = new Date( | |
| 477 | + this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 10), | |
| 478 | + this.focusedDate.getMonth(), | |
| 479 | + this.focusedDate.getDate() | |
| 480 | + ); | |
| 481 | + } else { | |
| 482 | + this.focusedDate = new Date( | |
| 483 | + this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 1), | |
| 484 | + this.focusedDate.getMonth() + mapPossibleValues(direction, 1, 0), | |
| 485 | + this.focusedDate.getDate() | |
| 486 | + ); | |
| 487 | + } | |
| 488 | + | |
| 489 | + const position = direction.match(/left|down/) ? 'prev' : 'next'; | |
| 490 | + const double = direction.match(/up|down/) ? '-double' : ''; | |
| 491 | + | |
| 492 | + // pulse button | |
| 493 | + const button = this.$refs.drop.$el.querySelector(`.ivu-date-picker-${position}-btn-arrow${double}`); | |
| 494 | + if (button) pulseElement(button); | |
| 495 | + return; | |
| 496 | + } | |
| 497 | + | |
| 498 | + const initialDate = this.focusedDate || (this.internalValue && this.internalValue[0]) || new Date(); | |
| 499 | + const focusedDate = new Date(initialDate); | |
| 500 | + | |
| 501 | + if (this.type.match(/^date/)){ | |
| 502 | + const lastOfMonth = getDayCountOfMonth(initialDate.getFullYear(), initialDate.getMonth()); | |
| 503 | + const startDay = initialDate.getDate(); | |
| 504 | + const nextDay = focusedDate.getDate() + mapPossibleValues(direction, 1, 7); | |
| 505 | + | |
| 506 | + if (nextDay < 1) { | |
| 507 | + if (direction.match(/left|right/)) { | |
| 508 | + focusedDate.setMonth(focusedDate.getMonth() + 1); | |
| 509 | + focusedDate.setDate(nextDay); | |
| 510 | + } else { | |
| 511 | + focusedDate.setDate(startDay + Math.floor((lastOfMonth - startDay) / 7) * 7); | |
| 512 | + } | |
| 513 | + } else if (nextDay > lastOfMonth){ | |
| 514 | + if (direction.match(/left|right/)) { | |
| 515 | + focusedDate.setMonth(focusedDate.getMonth() - 1); | |
| 516 | + focusedDate.setDate(nextDay); | |
| 517 | + } else { | |
| 518 | + focusedDate.setDate(startDay % 7); | |
| 519 | + } | |
| 520 | + } else { | |
| 521 | + focusedDate.setDate(nextDay); | |
| 522 | + } | |
| 523 | + } | |
| 524 | + | |
| 525 | + if (this.type.match(/^month/)) { | |
| 526 | + focusedDate.setMonth(focusedDate.getMonth() + mapPossibleValues(direction, 1, 3)); | |
| 527 | + } | |
| 528 | + | |
| 529 | + if (this.type.match(/^year/)) { | |
| 530 | + focusedDate.setFullYear(focusedDate.getFullYear() + mapPossibleValues(direction, 1, 3)); | |
| 531 | + } | |
| 532 | + | |
| 533 | + this.focusedDate = focusedDate; | |
| 534 | + }, | |
| 261 | 535 | handleInputChange (event) { |
| 262 | 536 | const isArrayValue = this.type.includes('range') || this.multiple; |
| 263 | 537 | const oldValue = this.visualValue; |
| ... | ... | @@ -377,6 +651,12 @@ |
| 377 | 651 | this.internalValue = Array.isArray(dates) ? dates : [dates]; |
| 378 | 652 | } |
| 379 | 653 | |
| 654 | + this.focusedDate = this.internalValue[0]; | |
| 655 | + this.focusedTime = { | |
| 656 | + ...this.focusedTime, | |
| 657 | + time: this.internalValue.map(extractTime) | |
| 658 | + }; | |
| 659 | + | |
| 380 | 660 | if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode |
| 381 | 661 | if (!this.isConfirm) this.visible = visible; |
| 382 | 662 | this.emitChange(type); |
| ... | ... | @@ -384,22 +664,23 @@ |
| 384 | 664 | onPickSuccess(){ |
| 385 | 665 | this.visible = false; |
| 386 | 666 | this.$emit('on-ok'); |
| 667 | + this.focus(); | |
| 387 | 668 | this.reset(); |
| 388 | 669 | }, |
| 670 | + focus() { | |
| 671 | + this.$refs.input.focus(); | |
| 672 | + } | |
| 389 | 673 | }, |
| 390 | 674 | watch: { |
| 391 | 675 | visible (state) { |
| 392 | 676 | if (state === false){ |
| 393 | 677 | this.$refs.drop.destroy(); |
| 394 | - const input = this.$el.querySelector('input'); | |
| 395 | - if (input) input.blur(); | |
| 396 | 678 | } |
| 397 | 679 | this.$refs.drop.update(); |
| 398 | 680 | this.$emit('on-open-change', state); |
| 399 | 681 | }, |
| 400 | 682 | value(val) { |
| 401 | 683 | this.internalValue = this.parseDate(val); |
| 402 | - | |
| 403 | 684 | }, |
| 404 | 685 | open (val) { |
| 405 | 686 | this.visible = val === true; |
| ... | ... | @@ -421,6 +702,9 @@ |
| 421 | 702 | this.$emit('input', this.publicVModelValue); // to update v-model |
| 422 | 703 | } |
| 423 | 704 | if (this.open !== null) this.visible = this.open; |
| 705 | + | |
| 706 | + // to handle focus from confirm buttons | |
| 707 | + this.$on('focus-input', () => this.focus()); | |
| 424 | 708 | } |
| 425 | 709 | }; |
| 426 | 710 | </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,23 @@ |
| 44 | 44 | margin: 2px; |
| 45 | 45 | color: @btn-disable-color; |
| 46 | 46 | } |
| 47 | + &-cell:hover{ | |
| 48 | + em{ | |
| 49 | + background: @date-picker-cell-hover-bg; | |
| 50 | + } | |
| 51 | + } | |
| 52 | + &-focused{ | |
| 53 | + em{ | |
| 54 | + box-shadow: 0 0 0 1px @primary-color inset; | |
| 55 | + } | |
| 56 | + } | |
| 57 | + | |
| 47 | 58 | &-cell{ |
| 48 | 59 | span&{ |
| 49 | 60 | width: 28px; |
| 50 | 61 | height: 28px; |
| 51 | 62 | cursor: pointer; |
| 52 | 63 | } |
| 53 | - &:hover{ | |
| 54 | - em{ | |
| 55 | - background: @date-picker-cell-hover-bg; | |
| 56 | - } | |
| 57 | - } | |
| 58 | 64 | &-prev-month,&-next-month{ |
| 59 | 65 | em{ |
| 60 | 66 | color: @btn-disable-color; |
| ... | ... | @@ -154,6 +160,11 @@ |
| 154 | 160 | margin: 0; |
| 155 | 161 | } |
| 156 | 162 | } |
| 163 | + | |
| 164 | + .@{date-picker-prefix-cls}-cells-cell-focused{ | |
| 165 | + background-color: tint(@primary-color, 80%); | |
| 166 | + } | |
| 167 | + | |
| 157 | 168 | } |
| 158 | 169 | |
| 159 | 170 | &-header{ |
| ... | ... | @@ -169,6 +180,11 @@ |
| 169 | 180 | } |
| 170 | 181 | } |
| 171 | 182 | } |
| 183 | + &-btn-pulse{ | |
| 184 | + background-color: tint(@primary-color, 80%) !important; | |
| 185 | + border-radius: @border-radius-small; | |
| 186 | + transition: background-color @transition-time @ease-in-out; | |
| 187 | + } | |
| 172 | 188 | &-prev-btn{ |
| 173 | 189 | float: left; |
| 174 | 190 | &-arrow-double{ |
| ... | ... | @@ -216,6 +232,10 @@ |
| 216 | 232 | max-height: none; |
| 217 | 233 | width: auto; |
| 218 | 234 | } |
| 235 | + | |
| 236 | + &-focused input{ | |
| 237 | + .active(); | |
| 238 | + } | |
| 219 | 239 | } |
| 220 | 240 | |
| 221 | 241 | .@{picker-prefix-cls} { |
| ... | ... | @@ -289,9 +309,9 @@ |
| 289 | 309 | color: @link-active-color; |
| 290 | 310 | } |
| 291 | 311 | } |
| 292 | - & > span&-time-disabled{ | |
| 293 | - color: @btn-disable-color; | |
| 294 | - cursor: @cursor-disabled; | |
| 312 | + | |
| 313 | + &-time{ | |
| 314 | + float: left; | |
| 295 | 315 | } |
| 296 | 316 | } |
| 297 | 317 | } | ... | ... |
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'); | ... | ... |