Merge pull request #3643 from SergioCrisostomo/datepicker-keyboard

Datepicker keyboard
This commit is contained in:
Aresn 2018-05-21 15:27:09 +08:00 committed by GitHub
commit bdb26ef7c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 494 additions and 77 deletions

View file

@ -249,14 +249,17 @@
<template>
<div style="width: 500px;margin: 100px;">
<Row>
<Col span="12">
<DatePicker type="date" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker>
</Col>
<Col span="12">
<DatePicker type="daterange" show-week-numbers placement="bottom-end" placeholder="Select date" style="width: 200px"></DatePicker>
</Col>
</Row>
<p><input type="text"></p>
<DatePicker type="month" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker>
<DatePicker type="year" show-week-numbers placeholder="Select date" style="width: 200px"></DatePicker>
<DatePicker type="date" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker>
<DatePicker type="datetime" show-week-numbers confirm placeholder="Select date" style="width: 400px"></DatePicker>
<DatePicker type="daterange" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker>
<DatePicker type="datetimerange" transfer show-week-numbers placeholder="Select date" style="width: 400px"></DatePicker>
<Time-Picker :steps="[1, 1, 15]" :value="new Date()"></Time-Picker>
</div>
</template>
<script>

View file

@ -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');
}
}
}
};

View file

@ -7,9 +7,9 @@
</div>
<span
:class="getCellCls(cell)"
v-for="(cell, i) in readCells"
v-for="(cell, i) in cells"
:key="String(cell.date) + i"
@click="handleClick(cell)"
@click="handleClick(cell, $event)"
@mouseenter="handleMouseMove(cell)"
>
<em>{{ cell.desc }}</em>
@ -61,7 +61,7 @@
const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay));
return this.showWeekNumbers ? [''].concat(weekDays) : weekDays;
},
readCells () {
cells () {
const tableYear = this.tableDate.getFullYear();
const tableMonth = this.tableDate.getMonth();
const today = clearHours(new Date()); // timestamp of today
@ -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)
}
];
},

View file

@ -2,6 +2,7 @@
import {clearHours} from '../util';
export default {
name: 'PanelTable',
props: {
tableDate: {
type: Date,
@ -26,7 +27,10 @@ export default {
selecting: false
})
},
focusedDate: {
type: Date,
required: true,
}
},
computed: {
dates(){

View file

@ -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
}
];

View file

@ -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 () {

View file

@ -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
}
];

View file

@ -46,6 +46,10 @@ export default {
pickerType: {
type: String,
require: true
},
focusedDate: {
type: Date,
required: true,
}
},
computed: {

View file

@ -41,6 +41,8 @@
:range-state="rangeState"
:show-week-numbers="showWeekNumbers"
:value="preSelecting.left ? [dates[0]] : dates"
:focused-date="focusedDate"
@on-change-range="handleChangeRange"
@on-pick="panelPickerHandlers.left"
@on-pick-click="handlePickClick"
@ -80,6 +82,8 @@
:disabled-date="disabledDate"
:show-week-numbers="showWeekNumbers"
:value="preSelecting.right ? [dates[dates.length - 1]] : dates"
:focused-date="focusedDate"
@on-change-range="handleChangeRange"
@on-pick="panelPickerHandlers.right"
@on-pick-click="handlePickClick"></component>
@ -178,7 +182,7 @@
[prefixCls + '-body-time']: this.showTime,
[prefixCls + '-body-date']: !this.showTime,
}
]
];
},
leftDatePanelLabel(){
return this.panelLabelConfig('left');
@ -224,10 +228,7 @@
// set panels positioning
const leftPanelDate = this.startDate || this.dates[0] || new Date();
this.leftPanelDate = leftPanelDate;
const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate());
this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate;
this.setPanelDates(this.startDate || this.dates[0] || new Date());
},
currentView(currentView){
const leftMonth = this.leftPanelDate.getMonth();
@ -246,6 +247,9 @@
},
selectionMode(type){
this.currentView = type || 'range';
},
focusedDate(date){
this.setPanelDates(date || new Date());
}
},
methods: {
@ -254,6 +258,11 @@
this.leftPickerTable = `${this.currentView}-table`;
this.rightPickerTable = `${this.currentView}-table`;
},
setPanelDates(leftPanelDate){
this.leftPanelDate = leftPanelDate;
const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate());
this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate;
},
panelLabelConfig (direction) {
const locale = this.t('i.locale');
const datePanelLabel = this.t('i.datepicker.datePanelLabel');

View file

@ -39,6 +39,8 @@
:value="dates"
:selection-mode="selectionMode"
:disabled-date="disabledDate"
:focused-date="focusedDate"
@on-pick="panelPickerHandlers"
@on-pick-click="handlePickClick"
></component>
@ -51,6 +53,8 @@
:format="format"
:time-disabled="timeDisabled"
:disabled-date="disabledDate"
:focused-date="focusedDate"
v-bind="timePickerOptions"
@on-pick="handlePick"
@on-pick-click="handlePickClick"
@ -150,7 +154,6 @@
},
currentView (currentView) {
this.$emit('on-selection-mode-change', currentView);
this.pickertable = this.getTableType(currentView);
if (this.currentView === 'time') {
this.$nextTick(() => {
@ -162,6 +165,13 @@
selectionMode(type){
this.currentView = type;
this.pickerTable = this.getTableType(type);
},
focusedDate(date){
const isDifferentYear = date.getFullYear() !== this.panelDate.getFullYear();
const isDifferentMonth = isDifferentYear || date.getMonth() !== this.panelDate.getMonth();
if (isDifferentYear || isDifferentMonth){
this.panelDate = date;
}
}
},
methods: {

View file

@ -1,5 +1,9 @@
<template>
<div :class="[prefixCls]" v-clickoutside="handleClose">
<div
:class="wrapperClasses"
v-click-outside:mousedown.capture="handleClose"
v-click-outside.capture="handleClose"
>
<div ref="reference" :class="[prefixCls + '-rel']">
<slot>
<i-input
@ -12,10 +16,14 @@
:placeholder="placeholder"
:value="visualValue"
:name="name"
ref="input"
@on-input-change="handleInputChange"
@on-focus="handleFocus"
@on-blur="handleBlur"
@on-click="handleIconClick"
@click.native="handleFocus"
@keydown.native="handleKeydown"
@mouseenter.native="handleInputMouseenter"
@mouseleave.native="handleInputMouseleave"
@ -48,6 +56,7 @@
:show-week-numbers="showWeekNumbers"
:picker-type="type"
:multiple="multiple"
:focused-date="focusedDate"
:time-picker-options="timePickerOptions"
@ -69,21 +78,49 @@
import iInput from '../../components/input/input.vue';
import Drop from '../../components/select/dropdown.vue';
import clickoutside from '../../directives/clickoutside';
import vClickOutside from 'v-click-outside-x/index';
import TransferDom from '../../directives/transfer-dom';
import { oneOf } from '../../utils/assist';
import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP } from './util';
import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP, getDayCountOfMonth } from './util';
import {findComponentsDownward} from '../../utils/assist';
import Emitter from '../../mixins/emitter';
const prefixCls = 'ivu-date-picker';
const pickerPrefixCls = 'ivu-picker';
const isEmptyArray = val => val.reduce((isEmpty, str) => isEmpty && !str || (typeof str === 'string' && str.trim() === ''), true);
const keyValueMapper = {
40: 'up',
39: 'right',
38: 'down',
37: 'left',
};
const mapPossibleValues = (key, horizontal, vertical) => {
if (key === 'left') return horizontal * -1;
if (key === 'right') return horizontal * 1;
if (key === 'up') return vertical * 1;
if (key === 'down') return vertical * -1;
};
const pulseElement = (el) => {
const pulseClass = 'ivu-date-picker-btn-pulse';
el.classList.add(pulseClass);
setTimeout(() => el.classList.remove(pulseClass), 200);
};
const extractTime = date => {
if (!date) return [0, 0, 0];
return [
date.getHours(), date.getMinutes(), date.getSeconds()
];
};
export default {
name: 'CalendarPicker',
mixins: [ Emitter ],
components: { iInput, Drop },
directives: { clickoutside, TransferDom },
directives: { clickOutside: vClickOutside.directive, TransferDom },
props: {
format: {
type: String
@ -172,6 +209,7 @@
const isRange = this.type.includes('range');
const emptyArray = isRange ? [null, null] : [null];
const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value);
const focusedTime = initialValue.map(extractTime);
return {
prefixCls: prefixCls,
@ -181,10 +219,24 @@
disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
disableCloseUnderTransfer: false, // transfer Drop,
selectionMode: this.onSelectionModeChange(this.type),
forceInputRerender: 1
forceInputRerender: 1,
isFocused: false,
focusedDate: initialValue[0] || this.startDate || new Date(),
focusedTime: {
column: 0, // which column inside the picker
picker: 0, // which picker
time: focusedTime, // the values array into [hh, mm, ss],
active: false
},
internalFocus: false,
};
},
computed: {
wrapperClasses(){
return [prefixCls, {
[prefixCls + '-focused']: this.isFocused
}];
},
publicVModelValue(){
if (this.multiple){
return this.internalValue.slice();
@ -232,32 +284,254 @@
handleTransferClick () {
if (this.transfer) this.disableCloseUnderTransfer = true;
},
handleClose () {
handleClose (e) {
if (this.disableCloseUnderTransfer) {
this.disableCloseUnderTransfer = false;
return false;
}
if (this.open !== null) return;
if (e && e.type === 'mousedown' && this.visible) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this.visible) {
const pickerPanel = this.$refs.pickerPanel && this.$refs.pickerPanel.$el;
if (e && pickerPanel && pickerPanel.contains(e.target)) return; // its a click inside own component, lets ignore it.
this.visible = false;
e && e.preventDefault();
e && e.stopPropagation();
return;
}
this.isFocused = false;
this.disableClickOutSide = false;
},
handleFocus () {
handleFocus (e) {
if (this.readonly) return;
this.isFocused = true;
if (e && e.type === 'focus') return; // just focus, don't open yet
this.visible = true;
this.$refs.pickerPanel.onToggleVisibility(true);
},
handleBlur () {
this.visible = false;
handleBlur (e) {
if (this.internalFocus){
this.internalFocus = false;
return;
}
if (this.visible) {
e.preventDefault();
return;
}
this.isFocused = false;
this.onSelectionModeChange(this.type);
this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views
this.reset();
this.$refs.pickerPanel.onToggleVisibility(false);
},
handleKeydown(e){
const keyCode = e.keyCode;
// handle "tab" key
if (keyCode === 9){
if (this.visible){
e.stopPropagation();
e.preventDefault();
if (this.isConfirm){
const selector = `.${pickerPrefixCls}-confirm > *`;
const tabbable = this.$refs.drop.$el.querySelectorAll(selector);
this.internalFocus = true;
const element = [...tabbable][e.shiftKey ? 'pop' : 'shift']();
element.focus();
} else {
this.handleClose();
}
} else {
this.focused = false;
}
}
// open the panel
const arrows = [37, 38, 39, 40];
if (!this.visible && arrows.includes(keyCode)){
this.visible = true;
return;
}
// close on "esc" key
if (keyCode === 27){
if (this.visible) {
e.stopPropagation();
this.handleClose();
}
}
// select date, "Enter" key
if (keyCode === 13){
const timePickers = findComponentsDownward(this, 'TimeSpinner');
if (timePickers.length > 0){
const columnsPerPicker = timePickers[0].showSeconds ? 3 : 2;
const pickerIndex = Math.floor(this.focusedTime.column / columnsPerPicker);
const value = this.focusedTime.time[pickerIndex];
timePickers[pickerIndex].chooseValue(value);
return;
}
if (this.type.match(/range/)){
this.$refs.pickerPanel.handleRangePick(this.focusedDate, 'date');
} else {
const panels = findComponentsDownward(this, 'PanelTable');
const compareDate = (d) => {
const sliceIndex = ['year', 'month', 'date'].indexOf((this.type)) + 1;
return [d.getFullYear(), d.getMonth(), d.getDate()].slice(0, sliceIndex).join('-');
};
const dateIsValid = panels.find(({cells}) => {
return cells.find(({date, disabled}) => compareDate(date) === compareDate(this.focusedDate) && !disabled);
});
if (dateIsValid) this.onPick(this.focusedDate, false, 'date');
}
}
if (!arrows.includes(keyCode)) return; // ignore rest of keys
// navigate times and dates
if (this.focusedTime.active) e.preventDefault(); // to prevent cursor from moving
this.navigateDatePanel(keyValueMapper[keyCode], e.shiftKey);
},
reset(){
this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset();
},
navigateTimePanel(direction){
this.focusedTime.active = true;
const horizontal = direction.match(/left|right/);
const vertical = direction.match(/up|down/);
const timePickers = findComponentsDownward(this, 'TimeSpinner');
const maxNrOfColumns = (timePickers[0].showSeconds ? 3 : 2) * timePickers.length;
const column = (currentColumn => {
const incremented = currentColumn + (horizontal ? (direction === 'left' ? -1 : 1) : 0);
return (incremented + maxNrOfColumns) % maxNrOfColumns;
})(this.focusedTime.column);
const columnsPerPicker = maxNrOfColumns / timePickers.length;
const pickerIndex = Math.floor(column / columnsPerPicker);
const col = column % columnsPerPicker;
if (horizontal){
const time = this.internalValue.map(extractTime);
this.focusedTime = {
...this.focusedTime,
column: column,
time: time
};
timePickers.forEach((instance, i) => {
if (i === pickerIndex) instance.updateFocusedTime(col, time[pickerIndex]);
else instance.updateFocusedTime(-1, instance.focusedTime);
});
}
if (vertical){
const increment = direction === 'up' ? 1 : -1;
const timeParts = ['hours', 'minutes', 'seconds'];
const pickerPossibleValues = timePickers[pickerIndex][`${timeParts[col]}List`];
const nextIndex = pickerPossibleValues.findIndex(({text}) => this.focusedTime.time[pickerIndex][col] === text) + increment;
const nextValue = pickerPossibleValues[nextIndex % pickerPossibleValues.length].text;
const times = this.focusedTime.time.map((time, i) => {
if (i !== pickerIndex) return time;
time[col] = nextValue;
return time;
});
this.focusedTime = {
...this.focusedTime,
time: times
};
timePickers.forEach((instance, i) => {
if (i === pickerIndex) instance.updateFocusedTime(col, times[i]);
else instance.updateFocusedTime(-1, instance.focusedTime);
});
}
},
navigateDatePanel(direction, shift){
const timePickers = findComponentsDownward(this, 'TimeSpinner');
if (timePickers.length > 0) {
// we are in TimePicker mode
this.navigateTimePanel(direction, shift, timePickers);
return;
}
if (shift){
if (this.type === 'year'){
this.focusedDate = new Date(
this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 10),
this.focusedDate.getMonth(),
this.focusedDate.getDate()
);
} else {
this.focusedDate = new Date(
this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 1),
this.focusedDate.getMonth() + mapPossibleValues(direction, 1, 0),
this.focusedDate.getDate()
);
}
const position = direction.match(/left|down/) ? 'prev' : 'next';
const double = direction.match(/up|down/) ? '-double' : '';
// pulse button
const button = this.$refs.drop.$el.querySelector(`.ivu-date-picker-${position}-btn-arrow${double}`);
if (button) pulseElement(button);
return;
}
const initialDate = this.focusedDate || (this.internalValue && this.internalValue[0]) || new Date();
const focusedDate = new Date(initialDate);
if (this.type.match(/^date/)){
const lastOfMonth = getDayCountOfMonth(initialDate.getFullYear(), initialDate.getMonth());
const startDay = initialDate.getDate();
const nextDay = focusedDate.getDate() + mapPossibleValues(direction, 1, 7);
if (nextDay < 1) {
if (direction.match(/left|right/)) {
focusedDate.setMonth(focusedDate.getMonth() + 1);
focusedDate.setDate(nextDay);
} else {
focusedDate.setDate(startDay + Math.floor((lastOfMonth - startDay) / 7) * 7);
}
} else if (nextDay > lastOfMonth){
if (direction.match(/left|right/)) {
focusedDate.setMonth(focusedDate.getMonth() - 1);
focusedDate.setDate(nextDay);
} else {
focusedDate.setDate(startDay % 7);
}
} else {
focusedDate.setDate(nextDay);
}
}
if (this.type.match(/^month/)) {
focusedDate.setMonth(focusedDate.getMonth() + mapPossibleValues(direction, 1, 3));
}
if (this.type.match(/^year/)) {
focusedDate.setFullYear(focusedDate.getFullYear() + mapPossibleValues(direction, 1, 3));
}
this.focusedDate = focusedDate;
},
handleInputChange (event) {
const isArrayValue = this.type.includes('range') || this.multiple;
const oldValue = this.visualValue;
@ -377,6 +651,12 @@
this.internalValue = Array.isArray(dates) ? dates : [dates];
}
this.focusedDate = this.internalValue[0];
this.focusedTime = {
...this.focusedTime,
time: this.internalValue.map(extractTime)
};
if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
if (!this.isConfirm) this.visible = visible;
this.emitChange(type);
@ -384,22 +664,23 @@
onPickSuccess(){
this.visible = false;
this.$emit('on-ok');
this.focus();
this.reset();
},
focus() {
this.$refs.input.focus();
}
},
watch: {
visible (state) {
if (state === false){
this.$refs.drop.destroy();
const input = this.$el.querySelector('input');
if (input) input.blur();
}
this.$refs.drop.update();
this.$emit('on-open-change', state);
},
value(val) {
this.internalValue = this.parseDate(val);
},
open (val) {
this.visible = val === true;
@ -421,6 +702,9 @@
this.$emit('input', this.publicVModelValue); // to update v-model
}
if (this.open !== null) this.visible = this.open;
// to handle focus from confirm buttons
this.$on('focus-input', () => this.focus());
}
};
</script>

View file

@ -5,6 +5,7 @@ import RangeDatePickerPanel from '../panel/Date/date-range.vue';
import { oneOf } from '../../../utils/assist';
export default {
name: 'CalendarPicker',
mixins: [Picker],
props: {
type: {

View file

@ -3,7 +3,7 @@ import TimePickerPanel from '../panel/Time/time.vue';
import RangeTimePickerPanel from '../panel/Time/time-range.vue';
import Options from '../time-mixins';
import { oneOf } from '../../../utils/assist';
import { findComponentsDownward, oneOf } from '../../../utils/assist';
export default {
mixins: [Picker, Options],
@ -30,4 +30,14 @@ export default {
};
}
},
watch: {
visible(visible){
if (visible) {
this.$nextTick(() => {
const spinners = findComponentsDownward(this, 'TimeSpinner');
spinners.forEach(instance => instance.updateScroll());
});
}
}
}
};

View file

@ -44,17 +44,23 @@
margin: 2px;
color: @btn-disable-color;
}
&-cell:hover{
em{
background: @date-picker-cell-hover-bg;
}
}
&-focused{
em{
box-shadow: 0 0 0 1px @primary-color inset;
}
}
&-cell{
span&{
width: 28px;
height: 28px;
cursor: pointer;
}
&:hover{
em{
background: @date-picker-cell-hover-bg;
}
}
&-prev-month,&-next-month{
em{
color: @btn-disable-color;
@ -154,6 +160,11 @@
margin: 0;
}
}
.@{date-picker-prefix-cls}-cells-cell-focused{
background-color: tint(@primary-color, 80%);
}
}
&-header{
@ -169,6 +180,11 @@
}
}
}
&-btn-pulse{
background-color: tint(@primary-color, 80%) !important;
border-radius: @border-radius-small;
transition: background-color @transition-time @ease-in-out;
}
&-prev-btn{
float: left;
&-arrow-double{
@ -216,6 +232,10 @@
max-height: none;
width: auto;
}
&-focused input{
.active();
}
}
.@{picker-prefix-cls} {
@ -289,9 +309,9 @@
color: @link-active-color;
}
}
& > span&-time-disabled{
color: @btn-disable-color;
cursor: @cursor-disabled;
&-time{
float: left;
}
}
}

View file

@ -70,6 +70,9 @@
color: @primary-color;
background: @background-color-select-hover;
}
&-focused{
background-color: tint(@primary-color, 80%);
}
}
}

View file

@ -116,7 +116,7 @@ describe('DatePicker.vue', () => {
`);
const picker = vm.$children[0];
picker.handleIconClick();
picker.handleFocus({type: 'focus'});
vm.$nextTick(() => {
const displayField = vm.$el.querySelector('.ivu-input');
const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell');
@ -169,7 +169,7 @@ describe('DatePicker.vue', () => {
});
const picker = vm.$children[0];
picker.handleIconClick();
picker.handleFocus({type: 'focus'});
vm.$nextTick(() => {
const panel = vm.$el.querySelector('.ivu-picker-panel-content');
const dayPanel = panel.querySelector('[class="ivu-date-picker-cells"]');
@ -243,7 +243,7 @@ describe('DatePicker.vue', () => {
`);
const picker = vm.$children[0];
picker.handleIconClick();
picker.handleFocus({type: 'focus'});
vm.$nextTick(() => {
const displayField = vm.$el.querySelector('.ivu-input');
const clickableCells = vm.$el.querySelectorAll('.ivu-date-picker-cells-cell');
@ -266,7 +266,9 @@ describe('DatePicker.vue', () => {
// it should be closed by now
expect(picker.visible).to.equal(false);
// open picker again
picker.handleIconClick();
picker.handleFocus({type: 'focus'});
picker.visible = true;
vm.$nextTick(() => {
expect(picker.visible).to.equal(true);
@ -355,7 +357,7 @@ describe('DatePicker.vue', () => {
`);
const picker = vm.$children[0];
picker.handleIconClick();
picker.handleFocus({type: 'focus'});
vm.$nextTick(() => {
const now = new Date();
const labels = vm.$el.querySelectorAll('.ivu-picker-panel-body .ivu-date-picker-header-label');

View file

@ -11,7 +11,7 @@ describe('TimePicker.vue', () => {
<Time-Picker></Time-Picker>
`);
const picker = vm.$children[0];
picker.handleIconClick(); // open the picker panels
picker.handleFocus({type: 'focus'}); // open the picker panels
vm.$nextTick(() => {
const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');
@ -28,7 +28,7 @@ describe('TimePicker.vue', () => {
<Time-Picker format="HH:mm"></Time-Picker>
`);
const picker = vm.$children[0];
picker.handleIconClick(); // open the picker panels
picker.handleFocus({type: 'focus'}); // open the picker panels
vm.$nextTick(() => {
const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');
@ -44,7 +44,7 @@ describe('TimePicker.vue', () => {
<Time-Picker :steps="[1, 15]"></Time-Picker>
`);
const picker = vm.$children[0];
picker.handleIconClick(); // open the picker panels
picker.handleFocus({type: 'focus'}); // open the picker panels
vm.$nextTick(() => {
const spiners = picker.$el.querySelectorAll('.ivu-time-picker-cells-list');