Add keyboard navigation to date|time picker
This commit is contained in:
parent
2bf3e04753
commit
75cb299868
16 changed files with 467 additions and 67 deletions
|
@ -1,37 +1,47 @@
|
|||
<template>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="timeClasses" v-if="showTime" @click="handleToggleTime">
|
||||
<template v-if="isTime">{{ t('i.datepicker.selectDate') }}</template>
|
||||
<template v-else>{{ t('i.datepicker.selectTime') }}</template>
|
||||
</span>
|
||||
<i-button size="small" type="text" @click.native="handleClear">{{ t('i.datepicker.clear') }}</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess">{{ t('i.datepicker.ok') }}</i-button>
|
||||
<div :class="[prefixCls + '-confirm']" @keydown.tab.capture="handleTab">
|
||||
<i-button :class="timeClasses" size="small" type="text" :disabled="timeDisabled" v-if="showTime" @click="handleToggleTime">
|
||||
{{labels.time}}
|
||||
</i-button>
|
||||
<i-button size="small" type="ghost" @click.native="handleClear" @keydown.enter.native="handleClear">
|
||||
{{labels.clear}}
|
||||
</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess" @keydown.enter.native="handleSuccess">
|
||||
{{labels.ok}}
|
||||
</i-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import iButton from '../../button/button.vue';
|
||||
import Locale from '../../../mixins/locale';
|
||||
import Emitter from '../../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-picker';
|
||||
|
||||
export default {
|
||||
mixins: [ Locale ],
|
||||
components: { iButton },
|
||||
mixins: [Locale, Emitter],
|
||||
components: {iButton},
|
||||
props: {
|
||||
showTime: false,
|
||||
isTime: false,
|
||||
timeDisabled: false
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeClasses () {
|
||||
return {
|
||||
[`${prefixCls}-confirm-time-disabled`]: this.timeDisabled
|
||||
};
|
||||
return `${prefixCls}-confirm-time`;
|
||||
},
|
||||
labels(){
|
||||
const labels = ['time', 'clear', 'ok'];
|
||||
const values = [(this.isTime ? 'selectDate' : 'selectTime'), 'clear', 'ok'];
|
||||
return labels.reduce((obj, key, i) => {
|
||||
obj[key] = this.t('i.datepicker.' + values[i]);
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -44,6 +54,17 @@
|
|||
handleToggleTime () {
|
||||
if (this.timeDisabled) return;
|
||||
this.$emit('on-pick-toggle-time');
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
},
|
||||
handleTab(e) {
|
||||
const tabbables = [...this.$el.children];
|
||||
const expectedFocus = tabbables[e.shiftKey ? 'shift' : 'pop']();
|
||||
|
||||
if (document.activeElement === expectedFocus) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:class="getCellCls(cell)"
|
||||
v-for="(cell, i) in readCells"
|
||||
:key="String(cell.date) + i"
|
||||
@click="handleClick(cell)"
|
||||
@click="handleClick(cell, $event)"
|
||||
@mouseenter="handleMouseMove(cell)"
|
||||
>
|
||||
<em>{{ cell.desc }}</em>
|
||||
|
@ -99,7 +99,9 @@
|
|||
[`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth',
|
||||
[`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth',
|
||||
[`${prefixCls}-cell-week-label`]: cell.type === 'weekLabel',
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end,
|
||||
[`${prefixCls}-focused`]: clearHours(cell.date) === clearHours(this.focusedDate)
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
|
|
|
@ -26,7 +26,10 @@ export default {
|
|||
selecting: false
|
||||
})
|
||||
},
|
||||
|
||||
focusedDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dates(){
|
||||
|
|
|
@ -38,14 +38,16 @@
|
|||
|
||||
const tableYear = this.tableDate.getFullYear();
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), this.focusedDate.getMonth(), 1));
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.date = new Date(tableYear, i, 1);
|
||||
cell.text = this.tCell(i + 1);
|
||||
const time = clearHours(cell.date);
|
||||
const day = clearHours(cell.date);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month';
|
||||
cell.selected = selectedDays.includes(time);
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,7 @@
|
|||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
|
|
|
@ -22,8 +22,10 @@
|
|||
import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-time-picker-cells';
|
||||
const timeParts = ['hours', 'minutes', 'seconds'];
|
||||
|
||||
export default {
|
||||
name: 'TimeSpinner',
|
||||
mixins: [Options],
|
||||
props: {
|
||||
hours: {
|
||||
|
@ -51,7 +53,9 @@
|
|||
return {
|
||||
spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one),
|
||||
prefixCls: prefixCls,
|
||||
compiled: false
|
||||
compiled: false,
|
||||
focusedColumn: -1, // which column inside the picker
|
||||
focusedTime: [0, 0, 0] // the values array into [hh, mm, ss]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -66,6 +70,7 @@
|
|||
hoursList () {
|
||||
let hours = [];
|
||||
const step = this.spinerSteps[0];
|
||||
const focusedHour = this.focusedColumn === 0 && this.focusedTime[0];
|
||||
const hour_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -76,6 +81,7 @@
|
|||
for (let i = 0; i < 24; i += step) {
|
||||
const hour = deepCopy(hour_tmpl);
|
||||
hour.text = i;
|
||||
hour.focused = i === focusedHour;
|
||||
|
||||
if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) {
|
||||
hour.disabled = true;
|
||||
|
@ -90,6 +96,7 @@
|
|||
minutesList () {
|
||||
let minutes = [];
|
||||
const step = this.spinerSteps[1];
|
||||
const focusedMinute = this.focusedColumn === 1 && this.focusedTime[1];
|
||||
const minute_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -100,6 +107,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const minute = deepCopy(minute_tmpl);
|
||||
minute.text = i;
|
||||
minute.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) {
|
||||
minute.disabled = true;
|
||||
|
@ -113,6 +121,7 @@
|
|||
secondsList () {
|
||||
let seconds = [];
|
||||
const step = this.spinerSteps[2];
|
||||
const focusedMinute = this.focusedColumn === 2 && this.focusedTime[2];
|
||||
const second_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -123,6 +132,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const second = deepCopy(second_tmpl);
|
||||
second.text = i;
|
||||
second.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) {
|
||||
second.disabled = true;
|
||||
|
@ -141,15 +151,32 @@
|
|||
`${prefixCls}-cell`,
|
||||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
chooseValue(values){
|
||||
const changes = timeParts.reduce((obj, part, i) => {
|
||||
const value = values[i];
|
||||
if (this[part] === value) return obj;
|
||||
return {
|
||||
...obj,
|
||||
[part]: value
|
||||
};
|
||||
}, {});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
this.emitChange(changes);
|
||||
}
|
||||
},
|
||||
handleClick (type, cell) {
|
||||
if (cell.disabled) return;
|
||||
const data = {};
|
||||
data[type] = cell.text;
|
||||
this.$emit('on-change', data);
|
||||
const data = {[type]: cell.text};
|
||||
this.emitChange(data);
|
||||
},
|
||||
emitChange(changes){
|
||||
this.$emit('on-change', changes);
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
scroll (type, index) {
|
||||
|
@ -168,15 +195,19 @@
|
|||
return index;
|
||||
},
|
||||
updateScroll () {
|
||||
const times = ['hours', 'minutes', 'seconds'];
|
||||
this.$nextTick(() => {
|
||||
times.forEach(type => {
|
||||
timeParts.forEach(type => {
|
||||
this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]);
|
||||
});
|
||||
});
|
||||
},
|
||||
formatTime (text) {
|
||||
return text < 10 ? '0' + text : text;
|
||||
},
|
||||
updateFocusedTime(col, time) {
|
||||
this.focusedColumn = col;
|
||||
this.focusedTime = time.slice();
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -191,6 +222,13 @@
|
|||
seconds (val) {
|
||||
if (!this.compiled) return;
|
||||
this.scroll('seconds', this.secondsList.findIndex(obj => obj.text == val));
|
||||
},
|
||||
focusedTime(updated, old){
|
||||
timeParts.forEach((part, i) => {
|
||||
if (updated[i] === old[i] || typeof updated[i] === 'undefined') return;
|
||||
const valueIndex = this[`${part}List`].findIndex(obj => obj.text === updated[i]);
|
||||
this.scroll(part, valueIndex);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -39,13 +39,15 @@
|
|||
};
|
||||
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), 0, 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), 0, 1));
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.date = new Date(this.startYear + i, 0, 1);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'year';
|
||||
const time = clearHours(cell.date);
|
||||
cell.selected = selectedDays.includes(time);
|
||||
const day = clearHours(cell.date);
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,7 @@
|
|||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue