element/packages/date-picker/src/picker.vue

497 lines
12 KiB
Vue
Raw Normal View History

2016-07-27 06:15:02 +00:00
<template>
<span
class="el-date-editor"
2016-08-09 02:10:33 +00:00
v-clickoutside="handleClose"
2016-07-27 06:15:02 +00:00
:class="{
'is-have-trigger': haveTrigger,
'is-active': pickerVisible,
2016-08-09 02:10:33 +00:00
'is-filled': !!this.value
2016-07-27 06:15:02 +00:00
}">
<input
class="el-date-editor__editor"
:readonly="readonly"
:type="editorType"
:placeholder="placeholder"
@focus="handleFocus"
@blur="handleBlur"
2016-08-09 02:10:33 +00:00
@keydown="handleKeydown"
@keyup="handleKeyup"
ref="reference"
v-model.lazy="visualValue" />
2016-07-27 06:15:02 +00:00
<span
@click="togglePicker()"
2016-08-09 02:10:33 +00:00
class="el-date-editor__trigger el-icon"
:class="[triggerClass]"
2016-07-27 06:15:02 +00:00
v-if="haveTrigger">
</span>
</span>
</template>
<script>
import Vue from 'vue';
2016-08-09 02:10:33 +00:00
import Clickoutside from 'main/utils/clickoutside';
2016-07-27 06:15:02 +00:00
import { merge, formatDate, parseDate, getWeekNumber } from './util';
import Popper from 'main/utils/popper.js';
const FUNCTION_KEYS = [13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40];
const RANGE_SEPARATOR = ' - ';
const DEFAULT_FORMATS = {
date: 'yyyy-MM-dd',
month: 'yyyy-MM',
datetime: 'yyyy-MM-dd HH:mm:ss',
time: 'HH:mm:ss',
timerange: 'HH:mm:ss',
daterange: 'yyyy-MM-dd',
datetimerange: 'yyyy-MM-dd HH:mm:ss'
};
const HAVE_TRIGGER_TYPES = [
'date',
'datetime',
'time',
'time-select',
'week',
'month',
'year',
'daterange',
'timerange',
'datetimerange'
];
const DATE_FORMATTER = function(value, format) {
return formatDate(value, format);
};
const DATE_PARSER = function(text, format) {
text = text.split(':');
if (text.length > 1) text = text.map(item => item.slice(-2));
text = text.join(':');
return parseDate(text, format);
};
const RANGE_FORMATTER = function(value, format) {
if (Array.isArray(value) && value.length === 2) {
const start = value[0];
const end = value[1];
if (start && end) {
return formatDate(start, format) + RANGE_SEPARATOR + formatDate(end, format);
}
}
return '';
};
const RANGE_PARSER = function(text, format) {
const array = text.split(RANGE_SEPARATOR);
if (array.length === 2) {
const range1 = array[0].split(':').map(item => item.slice(-2)).join(':');
const range2 = array[1].split(':').map(item => item.slice(-2)).join(':');
return [parseDate(range1, format), parseDate(range2, format)];
}
return [];
};
const TYPE_VALUE_RESOLVER_MAP = {
default: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
if (text === undefined || text === '') return null;
return text;
}
},
week: {
formatter(value) {
if (value instanceof Date) {
const weekNumber = getWeekNumber(value);
return value.getFullYear() + 'w' + (weekNumber > 9 ? weekNumber : '0' + weekNumber);
}
return value;
},
parser(text) {
const array = (text || '').split('w');
if (array.length === 2) {
const year = Number(array[0]);
const month = Number(array[1]);
if (!isNaN(year) && !isNaN(month) && month < 54) {
return text;
}
}
return null;
}
},
date: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
datetime: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
daterange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
datetimerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
timerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
time: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
month: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
year: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
const year = Number(text);
if (!isNaN(year)) return year;
return null;
}
},
number: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
let result = Number(text);
if (!isNaN(text)) {
return result;
} else {
return null;
}
}
}
};
export default {
props: {
format: String,
readonly: Boolean,
placeholder: String,
value: {},
haveTrigger: {},
pickerOptions: {}
},
directives: {
2016-08-09 02:10:33 +00:00
Clickoutside
2016-07-27 06:15:02 +00:00
},
data() {
return {
pickerVisible: false
};
},
watch: {
pickerVisible(val) {
val === true ? this.showPicker() : this.hidePicker();
}
},
computed: {
triggerClass() {
return this.type.indexOf('time') !== -1 ? 'el-icon-time' : 'el-icon-date';
},
editable() {
return this.type.indexOf('range') === -1;
},
selectionMode() {
if (this.type === 'week') {
return 'week';
} else if (this.type === 'month') {
return 'month';
} else if (this.type === 'year') {
return 'year';
}
return 'day';
},
haveTrigger() {
if (typeof this.showTrigger !== 'undefined') {
return this.showTrigger;
}
return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1;
},
visualValue: {
get() {
const value = this.value;
const formatter = (
TYPE_VALUE_RESOLVER_MAP[this.type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).formatter;
const format = DEFAULT_FORMATS[this.type];
return formatter(value, this.format || format);
},
set(value) {
if (value) {
const type = this.type;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]);
if (parsedValue) {
2016-08-09 02:10:33 +00:00
this.$emit('input', parsedValue);
2016-07-27 06:15:02 +00:00
}
return;
}
2016-08-09 02:10:33 +00:00
this.$emit('input', value);
2016-07-27 06:15:02 +00:00
}
},
editorType() {
return 'text';
}
},
methods: {
2016-08-09 02:10:33 +00:00
handleClose() {
this.pickerVisible = false;
},
2016-07-27 06:15:02 +00:00
handleFocus() {
const type = this.type;
if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1) {
if (!this.pickerVisible) {
this.showPicker();
}
}
this.$emit('focus', this);
},
handleBlur() {
this.$emit('blur', this);
},
handleKeydown(event) {
const keyCode = event.keyCode;
let selectionStart = event.target.selectionStart;
let selectionEnd = event.target.selectionEnd;
let length = event.target.value.length;
if (keyCode === 27) {
this.pickerVisible = this.picker.visible = false;
// left
} else if (keyCode === 37) {
event.preventDefault();
if (selectionEnd === length && selectionStart === length) {
event.target.selectionStart = length - 2;
} else if (selectionStart >= 3) {
event.target.selectionStart -= 3;
} else {
event.target.selectionStart = 0;
}
event.target.selectionEnd = event.target.selectionStart + 2;
// right
} else if (keyCode === 39) {
event.preventDefault();
if (selectionEnd === 0 && selectionStart === 0) {
event.target.selectionEnd = 2;
} else if (selectionEnd <= length - 3) {
event.target.selectionEnd += 3;
} else {
event.target.selectionEnd = length;
}
event.target.selectionStart = event.target.selectionEnd - 2;
}
},
handleKeyup(event) {
const keyCode = event.keyCode;
if (FUNCTION_KEYS.indexOf(keyCode) > -1) return;
if (!(this.picker && this.pickerVisible)) return;
const selectionStart = event.target.selectionStart;
const value = event.target.value;
const type = this.type;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]);
if (!parsedValue) return;
2016-08-09 02:10:33 +00:00
this.picker.value = parsedValue;
this.$emit('input', parsedValue);
2016-07-27 06:15:02 +00:00
if (this.type.indexOf('date') > -1) return;
setTimeout(_ => {
let start = selectionStart;
let end = selectionStart;
const offset = 2;
if (selectionStart === 9) {
start += offset;
}
if (selectionStart >= 12) {
if (selectionStart % 3 === 0) {
start += 1;
end = start;
} else if (selectionStart % 3 === 2) {
end = start + offset;
}
} else {
if (selectionStart % 3 === 1) {
start += 1;
end = start;
} else if (selectionStart % 3 === 0) {
end = start + offset;
}
}
event.target.selectionStart = start;
event.target.selectionEnd = end;
}, 0);
},
togglePicker() {
!this.pickerVisible ? this.showPicker() : this.hidePicker();
},
destroyPopper() {
if (this.popper) {
this.resetTransformOrigin(this.popper);
setTimeout(() => {
this.popper && this.popper.destroy();
this.popper = null;
}, 300);
}
},
hidePicker() {
if (this.picker) {
this.picker.resetView && this.picker.resetView();
this.pickerVisible = this.picker.visible = false;
this.destroyPopper();
}
},
showPicker() {
if (!this.picker) {
this.picker = new Vue(merge({
2016-08-09 02:10:33 +00:00
el: document.createElement('div')
2016-07-27 06:15:02 +00:00
}, this.panel));
this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange';
this.picker.selectionMode = this.selectionMode;
if (this.format) {
this.picker.format = this.format;
}
const options = this.pickerOptions;
if (options && options.selectableRange) {
let ranges = options.selectableRange;
const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser;
const format = DEFAULT_FORMATS.timerange;
ranges = Array.isArray(ranges) ? ranges : [ranges];
this.picker.selectableRange = ranges.map(range => parser(range, format));
delete this.pickerOptions.selectableRange;
}
if (this.type === 'time-select' && options) {
this.$watch('pickerOptions.minTime', val => {
this.picker.minTime = val;
});
}
for (const option in options) {
if (options.hasOwnProperty(option)) {
this.picker[option] = options[option];
}
}
this.$el.appendChild(this.picker.$el);
this.pickerVisible = this.picker.visible = true;
this.picker.resetView && this.picker.resetView();
this.picker.$on('pick', (date, visible = false) => {
2016-08-09 02:10:33 +00:00
this.$emit('input', date);
2016-07-27 06:15:02 +00:00
if (!visible) {
this.pickerVisible = this.picker.visible = false;
}
this.picker.resetView && this.picker.resetView();
});
this.picker.$on('select-range', (start, end) => {
setTimeout(() => {
2016-08-09 02:10:33 +00:00
this.$refs.reference.setSelectionRange(start, end);
this.$refs.reference.focus();
2016-07-27 06:15:02 +00:00
}, 0);
});
} else {
this.pickerVisible = this.picker.visible = true;
}
this.$nextTick(() => {
if (this.popper) {
this.popper.update();
return;
}
2016-08-09 02:10:33 +00:00
this.popper = new Popper(this.$refs.reference, this.picker.$el, {
2016-07-27 06:15:02 +00:00
gpuAcceleration: false,
placement: 'bottom-start',
boundariesPadding: 0,
forceAbsolute: true
});
this.popper.onCreate(popper => {
this.resetTransformOrigin(popper);
});
});
if (this.value instanceof Date) {
this.picker.date = new Date(this.value.getTime());
this.picker.resetView && this.picker.resetView();
} else {
this.picker.value = this.value;
}
this.$nextTick(() => {
if (this.type.indexOf('time') !== -1) {
this.picker.focusEditor && this.picker.focusEditor('hours');
}
this.picker.ajustScrollTop && this.picker.ajustScrollTop();
});
},
resetTransformOrigin(popper) {
let placementMap = { top: 'bottom', bottom: 'top' };
let placement = popper._popper.getAttribute('x-placement').split('-')[0];
let origin = placementMap[placement];
popper._popper.style.transformOrigin = `center ${ origin }`;
}
},
beforeDestroy() {
this.popper && this.popper.destroy();
}
};
</script>