2016-12-12 10:37:52 +08:00
|
|
|
|
<template>
|
2018-05-18 13:06:43 +02:00
|
|
|
|
<div
|
|
|
|
|
:class="wrapperClasses"
|
|
|
|
|
v-click-outside:mousedown.capture="handleClose"
|
|
|
|
|
v-click-outside.capture="handleClose"
|
|
|
|
|
>
|
2017-03-07 18:06:56 +08:00
|
|
|
|
<div ref="reference" :class="[prefixCls + '-rel']">
|
2016-12-20 14:23:12 +08:00
|
|
|
|
<slot>
|
|
|
|
|
<i-input
|
2018-01-19 13:11:38 +01:00
|
|
|
|
:key="forceInputRerender"
|
2017-09-22 15:29:50 +08:00
|
|
|
|
:element-id="elementId"
|
2016-12-20 14:23:12 +08:00
|
|
|
|
:class="[prefixCls + '-editor']"
|
|
|
|
|
:readonly="!editable || readonly"
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
:size="size"
|
|
|
|
|
:placeholder="placeholder"
|
|
|
|
|
:value="visualValue"
|
2017-09-19 16:45:02 +08:00
|
|
|
|
:name="name"
|
2018-05-18 13:06:43 +02:00
|
|
|
|
ref="input"
|
|
|
|
|
|
2017-03-07 18:06:56 +08:00
|
|
|
|
@on-input-change="handleInputChange"
|
2016-12-20 14:23:12 +08:00
|
|
|
|
@on-focus="handleFocus"
|
2017-09-21 08:22:05 +02:00
|
|
|
|
@on-blur="handleBlur"
|
2016-12-20 14:23:12 +08:00
|
|
|
|
@on-click="handleIconClick"
|
2018-05-18 13:06:43 +02:00
|
|
|
|
@click.native="handleFocus"
|
|
|
|
|
@keydown.native="handleKeydown"
|
2017-03-07 18:06:56 +08:00
|
|
|
|
@mouseenter.native="handleInputMouseenter"
|
|
|
|
|
@mouseleave.native="handleInputMouseleave"
|
2018-01-17 16:40:38 +01:00
|
|
|
|
|
|
|
|
|
:icon="iconType"
|
|
|
|
|
></i-input>
|
2016-12-20 14:23:12 +08:00
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
2018-03-16 00:43:11 +08:00
|
|
|
|
<transition name="transition-drop">
|
2017-07-20 11:53:18 +08:00
|
|
|
|
<Drop
|
|
|
|
|
@click.native="handleTransferClick"
|
|
|
|
|
v-show="opened"
|
|
|
|
|
:class="{ [prefixCls + '-transfer']: transfer }"
|
|
|
|
|
:placement="placement"
|
|
|
|
|
ref="drop"
|
|
|
|
|
:data-transfer="transfer"
|
2018-09-10 16:10:35 +08:00
|
|
|
|
:transfer="transfer"
|
2017-07-20 11:53:18 +08:00
|
|
|
|
v-transfer-dom>
|
2018-01-16 22:26:00 +01:00
|
|
|
|
<div>
|
|
|
|
|
<component
|
|
|
|
|
:is="panel"
|
2018-01-24 13:33:48 +01:00
|
|
|
|
ref="pickerPanel"
|
2018-01-16 22:26:00 +01:00
|
|
|
|
:visible="visible"
|
|
|
|
|
:showTime="type === 'datetime' || type === 'datetimerange'"
|
|
|
|
|
:confirm="isConfirm"
|
|
|
|
|
:selectionMode="selectionMode"
|
|
|
|
|
:steps="steps"
|
|
|
|
|
:format="format"
|
|
|
|
|
:value="internalValue"
|
2018-01-17 13:05:15 +01:00
|
|
|
|
:start-date="startDate"
|
2018-01-17 13:23:10 +01:00
|
|
|
|
:split-panels="splitPanels"
|
2018-01-17 16:40:38 +01:00
|
|
|
|
:show-week-numbers="showWeekNumbers"
|
2018-01-24 09:46:07 +01:00
|
|
|
|
:picker-type="type"
|
2018-03-05 08:59:25 +01:00
|
|
|
|
:multiple="multiple"
|
2018-05-18 13:06:43 +02:00
|
|
|
|
:focused-date="focusedDate"
|
2018-01-16 22:26:00 +01:00
|
|
|
|
|
2018-03-03 12:51:53 +01:00
|
|
|
|
:time-picker-options="timePickerOptions"
|
|
|
|
|
|
2018-01-16 22:26:00 +01:00
|
|
|
|
v-bind="ownPickerProps"
|
|
|
|
|
|
|
|
|
|
@on-pick="onPick"
|
|
|
|
|
@on-pick-clear="handleClear"
|
|
|
|
|
@on-pick-success="onPickSuccess"
|
|
|
|
|
@on-pick-click="disableClickOutSide = true"
|
|
|
|
|
@on-selection-mode-change="onSelectionModeChange"
|
|
|
|
|
></component>
|
|
|
|
|
</div>
|
2017-03-07 18:06:56 +08:00
|
|
|
|
</Drop>
|
|
|
|
|
</transition>
|
2016-12-12 20:34:28 +08:00
|
|
|
|
</div>
|
2016-12-12 10:37:52 +08:00
|
|
|
|
</template>
|
|
|
|
|
<script>
|
2018-01-16 22:26:00 +01:00
|
|
|
|
|
|
|
|
|
|
2016-12-12 20:34:28 +08:00
|
|
|
|
import iInput from '../../components/input/input.vue';
|
|
|
|
|
import Drop from '../../components/select/dropdown.vue';
|
2018-05-30 15:29:46 +02:00
|
|
|
|
import {directive as clickOutside} from 'v-click-outside-x';
|
2017-07-20 11:53:18 +08:00
|
|
|
|
import TransferDom from '../../directives/transfer-dom';
|
2016-12-12 20:34:28 +08:00
|
|
|
|
import { oneOf } from '../../utils/assist';
|
2018-05-18 13:06:43 +02:00
|
|
|
|
import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP, getDayCountOfMonth } from './util';
|
|
|
|
|
import {findComponentsDownward} from '../../utils/assist';
|
2017-03-09 11:14:40 +08:00
|
|
|
|
import Emitter from '../../mixins/emitter';
|
2016-12-12 20:34:28 +08:00
|
|
|
|
|
|
|
|
|
const prefixCls = 'ivu-date-picker';
|
2018-05-18 13:06:43 +02:00
|
|
|
|
const pickerPrefixCls = 'ivu-picker';
|
2016-12-12 20:34:28 +08:00
|
|
|
|
|
2018-02-02 11:21:12 +01:00
|
|
|
|
const isEmptyArray = val => val.reduce((isEmpty, str) => isEmpty && !str || (typeof str === 'string' && str.trim() === ''), true);
|
2018-05-18 13:06:43 +02:00
|
|
|
|
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()
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2018-02-02 11:21:12 +01:00
|
|
|
|
|
2016-12-12 10:37:52 +08:00
|
|
|
|
export default {
|
2017-03-09 11:14:40 +08:00
|
|
|
|
mixins: [ Emitter ],
|
2016-12-12 20:34:28 +08:00
|
|
|
|
components: { iInput, Drop },
|
2018-05-30 15:29:46 +02:00
|
|
|
|
directives: { clickOutside, TransferDom },
|
2016-12-12 20:34:28 +08:00
|
|
|
|
props: {
|
|
|
|
|
format: {
|
|
|
|
|
type: String
|
|
|
|
|
},
|
|
|
|
|
readonly: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
disabled: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
editable: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
|
|
|
|
},
|
2016-12-20 10:31:28 +08:00
|
|
|
|
clearable: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
|
|
|
|
},
|
2016-12-20 13:48:39 +08:00
|
|
|
|
confirm: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2016-12-20 14:23:12 +08:00
|
|
|
|
open: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: null
|
|
|
|
|
},
|
2018-01-16 22:26:00 +01:00
|
|
|
|
multiple: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2018-03-03 12:51:53 +01:00
|
|
|
|
timePickerOptions: {
|
|
|
|
|
default: () => ({}),
|
|
|
|
|
type: Object,
|
|
|
|
|
},
|
2018-01-17 13:23:10 +01:00
|
|
|
|
splitPanels: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2018-01-17 16:40:38 +01:00
|
|
|
|
showWeekNumbers: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2018-01-17 13:05:15 +01:00
|
|
|
|
startDate: {
|
|
|
|
|
type: Date
|
|
|
|
|
},
|
2016-12-12 20:34:28 +08:00
|
|
|
|
size: {
|
|
|
|
|
validator (value) {
|
2017-08-25 10:53:52 +08:00
|
|
|
|
return oneOf(value, ['small', 'large', 'default']);
|
2018-06-28 14:35:49 +08:00
|
|
|
|
},
|
|
|
|
|
default () {
|
2018-08-07 16:35:27 +08:00
|
|
|
|
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
2016-12-12 20:34:28 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
placeholder: {
|
|
|
|
|
type: String,
|
|
|
|
|
default: ''
|
|
|
|
|
},
|
2016-12-22 09:18:11 +08:00
|
|
|
|
placement: {
|
2016-12-12 20:34:28 +08:00
|
|
|
|
validator (value) {
|
2016-12-22 09:18:11 +08:00
|
|
|
|
return oneOf(value, ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']);
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
2016-12-22 09:18:11 +08:00
|
|
|
|
default: 'bottom-start'
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
2017-07-20 11:53:18 +08:00
|
|
|
|
transfer: {
|
|
|
|
|
type: Boolean,
|
2018-06-28 14:35:49 +08:00
|
|
|
|
default () {
|
2018-08-07 16:35:27 +08:00
|
|
|
|
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
2018-06-28 14:35:49 +08:00
|
|
|
|
}
|
2017-09-19 16:45:02 +08:00
|
|
|
|
},
|
|
|
|
|
name: {
|
|
|
|
|
type: String
|
2017-09-22 15:29:50 +08:00
|
|
|
|
},
|
|
|
|
|
elementId: {
|
|
|
|
|
type: String
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
|
|
|
|
steps: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => []
|
|
|
|
|
},
|
|
|
|
|
value: {
|
2018-02-02 14:35:55 +01:00
|
|
|
|
type: [Date, String, Array]
|
2018-01-19 13:11:38 +01:00
|
|
|
|
},
|
|
|
|
|
options: {
|
|
|
|
|
type: Object,
|
|
|
|
|
default: () => ({})
|
2016-12-12 20:34:28 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
2018-01-16 22:26:00 +01:00
|
|
|
|
data(){
|
2018-02-02 11:21:12 +01:00
|
|
|
|
const isRange = this.type.includes('range');
|
|
|
|
|
const emptyArray = isRange ? [null, null] : [null];
|
2018-02-02 14:26:44 +01:00
|
|
|
|
const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value);
|
2018-05-18 13:06:43 +02:00
|
|
|
|
const focusedTime = initialValue.map(extractTime);
|
2018-02-02 14:26:44 +01:00
|
|
|
|
|
2016-12-12 20:34:28 +08:00
|
|
|
|
return {
|
|
|
|
|
prefixCls: prefixCls,
|
|
|
|
|
showClose: false,
|
|
|
|
|
visible: false,
|
2018-02-02 11:21:12 +01:00
|
|
|
|
internalValue: initialValue,
|
2017-03-07 18:06:56 +08:00
|
|
|
|
disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
|
2018-01-16 22:26:00 +01:00
|
|
|
|
disableCloseUnderTransfer: false, // transfer 模式下,点击Drop也会触发关闭,
|
2018-01-19 13:11:38 +01:00
|
|
|
|
selectionMode: this.onSelectionModeChange(this.type),
|
2018-05-18 13:06:43 +02:00
|
|
|
|
forceInputRerender: 1,
|
|
|
|
|
isFocused: false,
|
2018-05-21 08:26:28 +02:00
|
|
|
|
focusedDate: initialValue[0] || this.startDate || new Date(),
|
2018-05-18 13:06:43 +02:00
|
|
|
|
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,
|
2016-12-25 22:49:42 +08:00
|
|
|
|
};
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
|
|
|
|
computed: {
|
2018-05-18 13:06:43 +02:00
|
|
|
|
wrapperClasses(){
|
|
|
|
|
return [prefixCls, {
|
|
|
|
|
[prefixCls + '-focused']: this.isFocused
|
|
|
|
|
}];
|
|
|
|
|
},
|
2018-02-27 11:01:06 +01:00
|
|
|
|
publicVModelValue(){
|
2018-01-19 09:41:34 +01:00
|
|
|
|
if (this.multiple){
|
2018-01-19 10:30:40 +01:00
|
|
|
|
return this.internalValue.slice();
|
2018-01-19 09:41:34 +01:00
|
|
|
|
} else {
|
|
|
|
|
const isRange = this.type.includes('range');
|
2018-02-11 20:46:22 +01:00
|
|
|
|
let val = this.internalValue.map(date => date instanceof Date ? new Date(date) : (date || ''));
|
|
|
|
|
|
|
|
|
|
if (this.type.match(/^time/)) val = val.map(this.formatDate);
|
2018-01-19 10:30:40 +01:00
|
|
|
|
return (isRange || this.multiple) ? val : val[0];
|
2018-01-19 09:41:34 +01:00
|
|
|
|
}
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
2018-02-27 11:01:06 +01:00
|
|
|
|
publicStringValue(){
|
|
|
|
|
const {formatDate, publicVModelValue, type} = this;
|
|
|
|
|
if (type.match(/^time/)) return publicVModelValue;
|
2018-03-05 08:48:25 +01:00
|
|
|
|
if (this.multiple) return formatDate(publicVModelValue);
|
2018-02-27 11:01:06 +01:00
|
|
|
|
return Array.isArray(publicVModelValue) ? publicVModelValue.map(formatDate) : formatDate(publicVModelValue);
|
|
|
|
|
},
|
2016-12-20 14:23:12 +08:00
|
|
|
|
opened () {
|
|
|
|
|
return this.open === null ? this.visible : this.open;
|
|
|
|
|
},
|
2016-12-12 20:34:28 +08:00
|
|
|
|
iconType () {
|
2016-12-26 14:50:39 +08:00
|
|
|
|
let icon = 'ios-calendar-outline';
|
2018-06-25 13:27:14 +08:00
|
|
|
|
if (this.type === 'time' || this.type === 'timerange') icon = 'ios-time-outline';
|
|
|
|
|
if (this.showClose) icon = 'ios-close-circle';
|
2016-12-26 14:50:39 +08:00
|
|
|
|
return icon;
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
2016-12-22 15:08:05 +08:00
|
|
|
|
transition () {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
const bottomPlaced = this.placement.match(/^bottom/);
|
|
|
|
|
return bottomPlaced ? 'slide-up' : 'slide-down';
|
2016-12-22 15:08:05 +08:00
|
|
|
|
},
|
2018-01-16 22:26:00 +01:00
|
|
|
|
visualValue() {
|
2018-01-19 18:52:28 +01:00
|
|
|
|
return this.formatDate(this.internalValue);
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
|
|
|
|
isConfirm(){
|
2018-01-19 09:41:34 +01:00
|
|
|
|
return this.confirm || this.type === 'datetime' || this.type === 'datetimerange' || this.multiple;
|
2016-12-12 20:34:28 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
onSelectionModeChange(type){
|
|
|
|
|
if (type.match(/^date/)) type = 'date';
|
2018-01-24 13:33:48 +01:00
|
|
|
|
this.selectionMode = oneOf(type, ['year', 'month', 'date', 'time']) && type;
|
|
|
|
|
return this.selectionMode;
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
2017-07-20 11:53:18 +08:00
|
|
|
|
// 开启 transfer 时,点击 Drop 即会关闭,这里不让其关闭
|
|
|
|
|
handleTransferClick () {
|
|
|
|
|
if (this.transfer) this.disableCloseUnderTransfer = true;
|
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
handleClose (e) {
|
2017-07-20 11:53:18 +08:00
|
|
|
|
if (this.disableCloseUnderTransfer) {
|
|
|
|
|
this.disableCloseUnderTransfer = false;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-01-16 22:26:00 +01:00
|
|
|
|
|
2018-05-18 13:06:43 +02:00
|
|
|
|
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;
|
2016-12-22 09:18:11 +08:00
|
|
|
|
this.disableClickOutSide = false;
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
handleFocus (e) {
|
2016-12-15 23:29:31 +08:00
|
|
|
|
if (this.readonly) return;
|
2018-05-18 13:06:43 +02:00
|
|
|
|
this.isFocused = true;
|
|
|
|
|
if (e && e.type === 'focus') return; // just focus, don't open yet
|
2018-08-25 13:20:15 +08:00
|
|
|
|
if(!this.disabled){
|
|
|
|
|
this.visible = true;
|
|
|
|
|
}
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
handleBlur (e) {
|
|
|
|
|
if (this.internalFocus){
|
|
|
|
|
this.internalFocus = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this.visible) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.isFocused = false;
|
2018-01-24 13:33:48 +01:00
|
|
|
|
this.onSelectionModeChange(this.type);
|
2018-01-24 10:55:42 +01:00
|
|
|
|
this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views
|
2018-01-24 13:33:48 +01:00
|
|
|
|
this.reset();
|
2018-02-05 07:50:23 +01:00
|
|
|
|
this.$refs.pickerPanel.onToggleVisibility(false);
|
|
|
|
|
|
2018-01-24 13:33:48 +01:00
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
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 {
|
2018-05-21 08:24:56 +02:00
|
|
|
|
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');
|
2018-05-18 13:06:43 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
},
|
2018-01-24 13:33:48 +01:00
|
|
|
|
reset(){
|
|
|
|
|
this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset();
|
2017-09-21 08:22:05 +02:00
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
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;
|
|
|
|
|
},
|
2016-12-15 23:29:31 +08:00
|
|
|
|
handleInputChange (event) {
|
2018-01-19 13:11:38 +01:00
|
|
|
|
const isArrayValue = this.type.includes('range') || this.multiple;
|
2016-12-15 23:29:31 +08:00
|
|
|
|
const oldValue = this.visualValue;
|
2018-01-16 22:26:00 +01:00
|
|
|
|
const newValue = event.target.value;
|
2018-01-19 13:11:38 +01:00
|
|
|
|
const newDate = this.parseDate(newValue);
|
|
|
|
|
const disabledDateFn =
|
|
|
|
|
this.options &&
|
|
|
|
|
typeof this.options.disabledDate === 'function' &&
|
|
|
|
|
this.options.disabledDate;
|
|
|
|
|
const valueToTest = isArrayValue ? newDate : newDate[0];
|
2018-01-19 21:25:24 +01:00
|
|
|
|
const isDisabled = disabledDateFn && disabledDateFn(valueToTest);
|
2018-01-24 14:54:03 +01:00
|
|
|
|
const isValidDate = newDate.reduce((valid, date) => valid && date instanceof Date, true);
|
2016-12-15 23:29:31 +08:00
|
|
|
|
|
2018-01-24 14:54:03 +01:00
|
|
|
|
if (newValue !== oldValue && !isDisabled && isValidDate) {
|
2018-04-11 14:31:14 +02:00
|
|
|
|
this.emitChange(this.type);
|
2018-01-19 13:11:38 +01:00
|
|
|
|
this.internalValue = newDate;
|
|
|
|
|
} else {
|
|
|
|
|
this.forceInputRerender++;
|
2016-12-19 23:40:39 +08:00
|
|
|
|
}
|
2016-12-15 20:16:58 +08:00
|
|
|
|
},
|
|
|
|
|
handleInputMouseenter () {
|
2016-12-12 20:34:28 +08:00
|
|
|
|
if (this.readonly || this.disabled) return;
|
2016-12-20 10:31:28 +08:00
|
|
|
|
if (this.visualValue && this.clearable) {
|
2016-12-12 20:34:28 +08:00
|
|
|
|
this.showClose = true;
|
|
|
|
|
}
|
|
|
|
|
},
|
2016-12-15 20:16:58 +08:00
|
|
|
|
handleInputMouseleave () {
|
2016-12-12 20:34:28 +08:00
|
|
|
|
this.showClose = false;
|
|
|
|
|
},
|
|
|
|
|
handleIconClick () {
|
2017-03-29 10:29:12 +08:00
|
|
|
|
if (this.showClose) {
|
|
|
|
|
this.handleClear();
|
2017-06-07 10:49:15 +08:00
|
|
|
|
} else if (!this.disabled) {
|
2017-03-29 10:29:12 +08:00
|
|
|
|
this.handleFocus();
|
|
|
|
|
}
|
2016-12-20 13:48:39 +08:00
|
|
|
|
},
|
|
|
|
|
handleClear () {
|
2016-12-15 20:16:58 +08:00
|
|
|
|
this.visible = false;
|
2018-01-16 22:26:00 +01:00
|
|
|
|
this.internalValue = this.internalValue.map(() => null);
|
2016-12-22 15:08:05 +08:00
|
|
|
|
this.$emit('on-clear');
|
2017-03-09 11:14:40 +08:00
|
|
|
|
this.dispatch('FormItem', 'on-form-change', '');
|
2018-04-11 14:31:14 +02:00
|
|
|
|
this.emitChange(this.type);
|
2018-01-24 13:33:48 +01:00
|
|
|
|
this.reset();
|
2016-12-26 14:50:39 +08:00
|
|
|
|
|
2018-01-16 22:26:00 +01:00
|
|
|
|
setTimeout(
|
|
|
|
|
() => this.onSelectionModeChange(this.type),
|
|
|
|
|
500 // delay to improve dropdown close visual effect
|
|
|
|
|
);
|
2016-12-15 23:41:06 +08:00
|
|
|
|
},
|
2018-04-11 14:31:14 +02:00
|
|
|
|
emitChange (type) {
|
2017-03-27 10:30:45 +08:00
|
|
|
|
this.$nextTick(() => {
|
2018-04-11 14:31:14 +02:00
|
|
|
|
this.$emit('on-change', this.publicStringValue, type);
|
2018-02-27 11:01:06 +01:00
|
|
|
|
this.dispatch('FormItem', 'on-form-change', this.publicStringValue);
|
2017-03-27 10:30:45 +08:00
|
|
|
|
});
|
2017-03-13 18:58:31 +08:00
|
|
|
|
},
|
2018-01-19 10:30:40 +01:00
|
|
|
|
parseDate(val) {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
const isRange = this.type.includes('range');
|
2016-12-28 15:21:25 +08:00
|
|
|
|
const type = this.type;
|
2018-01-16 22:26:00 +01:00
|
|
|
|
const parser = (
|
2016-12-28 15:21:25 +08:00
|
|
|
|
TYPE_VALUE_RESOLVER_MAP[type] ||
|
2016-12-20 09:35:23 +08:00
|
|
|
|
TYPE_VALUE_RESOLVER_MAP['default']
|
2018-01-16 22:26:00 +01:00
|
|
|
|
).parser;
|
2018-01-19 18:52:28 +01:00
|
|
|
|
const format = this.format || DEFAULT_FORMATS[type];
|
|
|
|
|
const multipleParser = TYPE_VALUE_RESOLVER_MAP['multiple'].parser;
|
2016-12-20 09:35:23 +08:00
|
|
|
|
|
2018-01-16 22:26:00 +01:00
|
|
|
|
if (val && type === 'time' && !(val instanceof Date)) {
|
2018-01-19 18:52:28 +01:00
|
|
|
|
val = parser(val, format);
|
|
|
|
|
} else if (this.multiple && val) {
|
|
|
|
|
val = multipleParser(val, format);
|
2018-01-19 13:11:38 +01:00
|
|
|
|
} else if (isRange) {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
if (!val){
|
|
|
|
|
val = [null, null];
|
|
|
|
|
} else {
|
2018-01-24 11:15:20 +01:00
|
|
|
|
if (typeof val === 'string') {
|
|
|
|
|
val = parser(val, format);
|
2018-02-02 14:26:44 +01:00
|
|
|
|
} else if (type === 'timerange') {
|
2018-03-16 11:49:55 +01:00
|
|
|
|
val = parser(val, format).map(v => v || '');
|
2018-01-24 11:15:20 +01:00
|
|
|
|
} else {
|
2018-04-06 23:16:44 +02:00
|
|
|
|
const [start, end] = val;
|
|
|
|
|
if (start instanceof Date && end instanceof Date){
|
|
|
|
|
val = val.map(date => new Date(date));
|
|
|
|
|
} else if (typeof start === 'string' && typeof end === 'string'){
|
|
|
|
|
val = parser(val.join(RANGE_SEPARATOR), format);
|
|
|
|
|
} else if (!start || !end){
|
|
|
|
|
val = [null, null];
|
|
|
|
|
}
|
2018-01-24 11:15:20 +01:00
|
|
|
|
}
|
2018-01-16 22:26:00 +01:00
|
|
|
|
}
|
2018-01-19 10:30:40 +01:00
|
|
|
|
} else if (typeof val === 'string' && type.indexOf('time') !== 0){
|
2018-01-24 14:54:03 +01:00
|
|
|
|
val = parser(val, format) || null;
|
2018-01-16 22:26:00 +01:00
|
|
|
|
}
|
2018-01-19 18:52:28 +01:00
|
|
|
|
|
|
|
|
|
return (isRange || this.multiple) ? (val || []) : [val];
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
2018-01-19 10:30:40 +01:00
|
|
|
|
formatDate(value){
|
|
|
|
|
const format = DEFAULT_FORMATS[this.type];
|
2018-01-19 18:52:28 +01:00
|
|
|
|
|
|
|
|
|
if (this.multiple) {
|
|
|
|
|
const formatter = TYPE_VALUE_RESOLVER_MAP.multiple.formatter;
|
|
|
|
|
return formatter(value, this.format || format);
|
|
|
|
|
} else {
|
|
|
|
|
const {formatter} = (
|
|
|
|
|
TYPE_VALUE_RESOLVER_MAP[this.type] ||
|
|
|
|
|
TYPE_VALUE_RESOLVER_MAP['default']
|
|
|
|
|
);
|
|
|
|
|
return formatter(value, this.format || format);
|
|
|
|
|
}
|
2018-01-19 10:30:40 +01:00
|
|
|
|
},
|
2018-04-11 14:31:14 +02:00
|
|
|
|
onPick(dates, visible = false, type) {
|
2018-01-19 09:41:34 +01:00
|
|
|
|
if (this.multiple){
|
2018-02-02 11:36:11 +01:00
|
|
|
|
const pickedTimeStamp = dates.getTime();
|
|
|
|
|
const indexOfPickedDate = this.internalValue.findIndex(date => date && date.getTime() === pickedTimeStamp);
|
2018-01-19 09:41:34 +01:00
|
|
|
|
const allDates = [...this.internalValue, dates].filter(Boolean);
|
2018-02-02 11:36:11 +01:00
|
|
|
|
const timeStamps = allDates.map(date => date.getTime()).filter((ts, i, arr) => arr.indexOf(ts) === i && i !== indexOfPickedDate); // filter away duplicates
|
2018-01-19 09:41:34 +01:00
|
|
|
|
this.internalValue = timeStamps.map(ts => new Date(ts));
|
2018-01-16 22:26:00 +01:00
|
|
|
|
} else {
|
2018-08-10 14:58:31 +02:00
|
|
|
|
dates = this.parseDate(dates);
|
2018-01-16 22:26:00 +01:00
|
|
|
|
this.internalValue = Array.isArray(dates) ? dates : [dates];
|
2016-12-27 17:55:13 +08:00
|
|
|
|
}
|
2018-01-16 22:26:00 +01:00
|
|
|
|
|
2018-05-23 08:21:27 +02:00
|
|
|
|
if (this.internalValue[0]) this.focusedDate = this.internalValue[0];
|
2018-05-18 13:06:43 +02:00
|
|
|
|
this.focusedTime = {
|
|
|
|
|
...this.focusedTime,
|
|
|
|
|
time: this.internalValue.map(extractTime)
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-16 22:26:00 +01:00
|
|
|
|
if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
|
|
|
|
|
if (!this.isConfirm) this.visible = visible;
|
2018-04-11 14:31:14 +02:00
|
|
|
|
this.emitChange(type);
|
2018-01-16 22:26:00 +01:00
|
|
|
|
},
|
|
|
|
|
onPickSuccess(){
|
|
|
|
|
this.visible = false;
|
|
|
|
|
this.$emit('on-ok');
|
2018-05-18 13:06:43 +02:00
|
|
|
|
this.focus();
|
2018-01-24 13:33:48 +01:00
|
|
|
|
this.reset();
|
2018-01-17 16:40:38 +01:00
|
|
|
|
},
|
2018-05-18 13:06:43 +02:00
|
|
|
|
focus() {
|
2018-06-04 09:52:20 +02:00
|
|
|
|
this.$refs.input && this.$refs.input.focus();
|
2018-05-18 13:06:43 +02:00
|
|
|
|
}
|
2016-12-12 20:34:28 +08:00
|
|
|
|
},
|
|
|
|
|
watch: {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
visible (state) {
|
|
|
|
|
if (state === false){
|
2016-12-12 20:34:28 +08:00
|
|
|
|
this.$refs.drop.destroy();
|
2016-12-15 20:16:58 +08:00
|
|
|
|
}
|
2018-02-08 11:45:08 +01:00
|
|
|
|
this.$refs.drop.update();
|
2018-01-16 22:26:00 +01:00
|
|
|
|
this.$emit('on-open-change', state);
|
2016-12-15 20:16:58 +08:00
|
|
|
|
},
|
2018-01-16 22:26:00 +01:00
|
|
|
|
value(val) {
|
2018-01-19 10:30:40 +01:00
|
|
|
|
this.internalValue = this.parseDate(val);
|
2016-12-22 15:08:05 +08:00
|
|
|
|
},
|
|
|
|
|
open (val) {
|
2018-01-16 22:26:00 +01:00
|
|
|
|
this.visible = val === true;
|
|
|
|
|
},
|
|
|
|
|
type(type){
|
|
|
|
|
this.onSelectionModeChange(type);
|
2018-01-19 10:30:40 +01:00
|
|
|
|
},
|
2018-02-27 11:01:06 +01:00
|
|
|
|
publicVModelValue(now, before){
|
2018-01-19 10:30:40 +01:00
|
|
|
|
const newValue = JSON.stringify(now);
|
|
|
|
|
const oldValue = JSON.stringify(before);
|
2018-01-19 21:25:24 +01:00
|
|
|
|
const shouldEmitInput = newValue !== oldValue || typeof now !== typeof before;
|
|
|
|
|
if (shouldEmitInput) this.$emit('input', now); // to update v-model
|
2018-01-19 10:30:40 +01:00
|
|
|
|
},
|
2016-12-20 14:23:12 +08:00
|
|
|
|
},
|
2017-03-07 18:06:56 +08:00
|
|
|
|
mounted () {
|
2018-01-19 21:25:24 +01:00
|
|
|
|
const initialValue = this.value;
|
2018-02-27 11:01:06 +01:00
|
|
|
|
const parsedValue = this.publicVModelValue;
|
2018-01-19 21:25:24 +01:00
|
|
|
|
if (typeof initialValue !== typeof parsedValue || JSON.stringify(initialValue) !== JSON.stringify(parsedValue)){
|
2018-02-27 11:01:06 +01:00
|
|
|
|
this.$emit('input', this.publicVModelValue); // to update v-model
|
2018-01-19 21:25:24 +01:00
|
|
|
|
}
|
2016-12-22 15:08:05 +08:00
|
|
|
|
if (this.open !== null) this.visible = this.open;
|
2018-05-18 13:06:43 +02:00
|
|
|
|
|
|
|
|
|
// to handle focus from confirm buttons
|
|
|
|
|
this.$on('focus-input', () => this.focus());
|
2016-12-12 20:34:28 +08:00
|
|
|
|
}
|
2016-12-25 22:49:42 +08:00
|
|
|
|
};
|
|
|
|
|
</script>
|