/src"
+ },
+ "coverageReporters": [
+ "text",
+ "text-summary"
+ ]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged",
+ "commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
+ }
+ },
+ "lint-staged": {
+ "**/*.{js,vue}": [
+ "eslint --fix",
+ "prettier --write",
+ "git add"
+ ]
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie < 10"
+ ]
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..80a4f7e
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,76 @@
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import babel from '@rollup/plugin-babel';
+import vue from 'rollup-plugin-vue';
+import { terser } from 'rollup-plugin-terser';
+import pkg from './package.json';
+
+const external = Object.keys(pkg.dependencies);
+
+const input = 'src/index.js';
+
+const plugins = [
+ resolve({
+ extensions: ['.js', '.jsx', '.es6', '.es', '.mjs', '.vue'],
+ }),
+ vue(),
+ babel({
+ babelHelpers: 'bundled',
+ skipPreflightCheck: true,
+ extensions: ['.js', '.jsx', '.es6', '.es', '.mjs', '.vue'],
+ }),
+ commonjs(),
+];
+
+const esm = {
+ external,
+ input,
+ output: [
+ {
+ file: pkg.module,
+ format: 'esm',
+ exports: 'named',
+ },
+ ],
+ plugins,
+};
+
+const umd = {
+ input,
+ external: 'vue',
+ output: [
+ {
+ file: pkg.main,
+ format: 'umd',
+ name: 'DatePicker',
+ sourcemap: true,
+ globals: {
+ vue: 'Vue',
+ },
+ },
+ {
+ file: pkg.main.replace(/\.js$/, '.min.js'),
+ format: 'umd',
+ name: 'DatePicker',
+ sourcemap: true,
+ globals: {
+ vue: 'Vue',
+ },
+ plugins: [
+ terser({
+ output: { comments: false },
+ compress: {
+ keep_infinity: true,
+ pure_getters: true,
+ },
+ warnings: true,
+ ecma: 5,
+ toplevel: false,
+ }),
+ ],
+ },
+ ],
+ plugins,
+};
+
+export default [esm, umd];
diff --git a/rollup.locale.config.js b/rollup.locale.config.js
new file mode 100644
index 0000000..78d2f9e
--- /dev/null
+++ b/rollup.locale.config.js
@@ -0,0 +1,56 @@
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import babel from '@rollup/plugin-babel';
+
+const fs = require('fs');
+const path = require('path');
+
+const localePath = path.resolve(__dirname, 'src/locale');
+const fileList = fs.readdirSync(localePath);
+
+const plugins = [
+ resolve({
+ extensions: ['.js', '.json'],
+ }),
+ babel({
+ babelHelpers: 'bundled',
+ exclude: 'node_modules/**',
+ }),
+ commonjs(),
+];
+
+const umd = fileList.map((file) => {
+ const input = path.join(localePath, file);
+ const external = ['vue2-datepicker'];
+ const name = path.basename(file, '.js').replace(/-(\w+)/g, (m, p1) => p1.toUpperCase());
+ return {
+ input,
+ plugins,
+ external,
+ output: {
+ file: `locale/${file}`,
+ format: 'umd',
+ name: `DatePicker.lang.${name}`,
+ globals: {
+ 'vue2-datepicker': 'DatePicker',
+ },
+ },
+ };
+});
+
+const esm = fileList.map((file) => {
+ const input = path.join(localePath, file);
+ const external = (id) => !id.startsWith('.') && !id.startsWith('/');
+ return {
+ input,
+ plugins,
+ external,
+ output: {
+ file: `locale/es/${file}`,
+ format: 'esm',
+ exports: 'named',
+ },
+ };
+});
+
+export default [...esm, ...umd];
diff --git a/src/calendar/calendar-panel.js b/src/calendar/calendar-panel.js
new file mode 100644
index 0000000..00fd58c
--- /dev/null
+++ b/src/calendar/calendar-panel.js
@@ -0,0 +1,260 @@
+import {
+ getValidDate,
+ isValidDate,
+ createDate,
+ setMonth,
+ startOfYear,
+ startOfMonth,
+ startOfDay,
+} from '../util/date';
+import TableDate from './table-date';
+import TableMonth from './table-month';
+import TableYear from './table-year';
+
+export default {
+ name: 'CalendarPanel',
+ inject: {
+ prefixClass: {
+ default: 'mx',
+ },
+ dispatchDatePicker: {
+ default: () => () => {},
+ },
+ },
+ emits: ['select', 'update:calendar'],
+ props: {
+ value: {},
+ defaultValue: {
+ default() {
+ const date = new Date();
+ date.setHours(0, 0, 0, 0);
+ return date;
+ },
+ },
+ defaultPanel: {
+ type: String,
+ },
+ disabledDate: {
+ type: Function,
+ default: () => false,
+ },
+ type: {
+ type: String,
+ default: 'date',
+ },
+ getClasses: {
+ type: Function,
+ default: () => [],
+ },
+ showWeekNumber: {
+ type: Boolean,
+ default: undefined,
+ },
+ titleFormat: {
+ type: String,
+ default: 'YYYY-MM-DD',
+ },
+ calendar: Date,
+ // update date when select year or month
+ partialUpdate: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ data() {
+ const panels = ['date', 'month', 'year'];
+ const index = Math.max(panels.indexOf(this.type), panels.indexOf(this.defaultPanel));
+ const panel = index !== -1 ? panels[index] : 'date';
+ return {
+ panel,
+ innerCalendar: new Date(),
+ };
+ },
+ computed: {
+ innerValue() {
+ const value = Array.isArray(this.value) ? this.value : [this.value];
+ const map = {
+ year: startOfYear,
+ month: startOfMonth,
+ date: startOfDay,
+ };
+ const start = map[this.type] || map.date;
+ return value.filter(isValidDate).map(v => start(v));
+ },
+ calendarYear() {
+ return this.innerCalendar.getFullYear();
+ },
+ calendarMonth() {
+ return this.innerCalendar.getMonth();
+ },
+ },
+ watch: {
+ value: {
+ immediate: true,
+ handler: 'initCalendar',
+ },
+ calendar: {
+ handler: 'initCalendar',
+ },
+ defaultValue: {
+ handler: 'initCalendar',
+ },
+ },
+ methods: {
+ initCalendar() {
+ let calendarDate = this.calendar;
+ if (!isValidDate(calendarDate)) {
+ const { length } = this.innerValue;
+ calendarDate = getValidDate(length > 0 ? this.innerValue[length - 1] : this.defaultValue);
+ }
+ this.innerCalendar = startOfMonth(calendarDate);
+ },
+ isDisabled(date) {
+ return this.disabledDate(new Date(date), this.innerValue);
+ },
+ emitDate(date, type) {
+ if (!this.isDisabled(date)) {
+ this.$emit('select', date, type, this.innerValue);
+ // someone need get the first selected date to set range value. (#429)
+ this.dispatchDatePicker('pick', date, type);
+ }
+ },
+ handleCalendarChange(calendar, type) {
+ const oldCalendar = new Date(this.innerCalendar);
+ this.innerCalendar = calendar;
+ this.$emit('update:calendar', calendar);
+ this.dispatchDatePicker('calendar-change', calendar, oldCalendar, type);
+ },
+ handelPanelChange(panel) {
+ this.panel = panel;
+ },
+ handleSelectYear(year) {
+ if (this.type === 'year') {
+ const date = this.getYearCellDate(year);
+ this.emitDate(date, 'year');
+ } else {
+ this.handleCalendarChange(createDate(year, this.calendarMonth), 'year');
+ this.handelPanelChange('month');
+ if (this.partialUpdate && this.innerValue.length === 1) {
+ const date = new Date(this.innerValue[0]);
+ date.setFullYear(year);
+ this.emitDate(date, 'year');
+ }
+ }
+ },
+ handleSelectMonth(month) {
+ if (this.type === 'month') {
+ const date = this.getMonthCellDate(month);
+ this.emitDate(date, 'month');
+ } else {
+ this.handleCalendarChange(createDate(this.calendarYear, month), 'month');
+ this.handelPanelChange('date');
+ if (this.partialUpdate && this.innerValue.length === 1) {
+ const date = new Date(this.innerValue[0]);
+ date.setFullYear(this.calendarYear);
+ this.emitDate(setMonth(date, month), 'month');
+ }
+ }
+ },
+ handleSelectDate(date) {
+ this.emitDate(date, this.type === 'week' ? 'week' : 'date');
+ },
+ getMonthCellDate(month) {
+ return createDate(this.calendarYear, month);
+ },
+ getYearCellDate(year) {
+ return createDate(year, 0);
+ },
+ getDateClasses(cellDate) {
+ const notCurrentMonth = cellDate.getMonth() !== this.calendarMonth;
+ const classes = [];
+ if (cellDate.getTime() === new Date().setHours(0, 0, 0, 0)) {
+ classes.push('today');
+ }
+ if (notCurrentMonth) {
+ classes.push('not-current-month');
+ }
+ const state = this.getStateClass(cellDate);
+ if (!(state === 'active' && notCurrentMonth)) {
+ classes.push(state);
+ }
+ return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
+ },
+ getMonthClasses(month) {
+ if (this.type !== 'month') {
+ return this.calendarMonth === month ? 'active' : '';
+ }
+ const classes = [];
+ const cellDate = this.getMonthCellDate(month);
+ classes.push(this.getStateClass(cellDate));
+ return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
+ },
+ getYearClasses(year) {
+ if (this.type !== 'year') {
+ return this.calendarYear === year ? 'active' : '';
+ }
+ const classes = [];
+ const cellDate = this.getYearCellDate(year);
+ classes.push(this.getStateClass(cellDate));
+ return classes.concat(this.getClasses(cellDate, this.innerValue, classes.join(' ')));
+ },
+ getStateClass(cellDate) {
+ if (this.isDisabled(cellDate)) {
+ return 'disabled';
+ }
+ if (this.innerValue.some(v => v.getTime() === cellDate.getTime())) {
+ return 'active';
+ }
+ return '';
+ },
+ getWeekState(row) {
+ if (this.type !== 'week') return '';
+ const start = row[0].getTime();
+ const end = row[6].getTime();
+ const active = this.innerValue.some(v => {
+ const time = v.getTime();
+ return time >= start && time <= end;
+ });
+ return active ? `${this.prefixClass}-active-week` : '';
+ },
+ },
+ render() {
+ const { panel, innerCalendar } = this;
+ if (panel === 'year') {
+ return (
+
+ );
+ }
+ if (panel === 'month') {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ },
+};
diff --git a/src/calendar/calendar-range.js b/src/calendar/calendar-range.js
new file mode 100644
index 0000000..04fe5eb
--- /dev/null
+++ b/src/calendar/calendar-range.js
@@ -0,0 +1,134 @@
+import CalendarPanel from './calendar-panel';
+import { getValidDate, isValidDate, isValidRangeDate, startOfMonth } from '../util/date';
+
+export default {
+ name: 'CalendarRange',
+ inject: {
+ prefixClass: {
+ default: 'mx',
+ },
+ },
+ emits: ['select'],
+ props: {
+ ...CalendarPanel.props,
+ },
+ data() {
+ return {
+ innerValue: [],
+ calendars: [],
+ };
+ },
+ computed: {
+ // Minimum difference between start and end calendars
+ calendarMinDiff() {
+ const map = {
+ date: 1, // type:date min 1 month
+ month: 1 * 12, // type:month min 1 year
+ year: 10 * 12, // type:year min 10 year
+ };
+ return map[this.type] || map.date;
+ },
+ calendarMaxDiff() {
+ return Infinity;
+ },
+ defaultValues() {
+ return Array.isArray(this.defaultValue)
+ ? this.defaultValue
+ : [this.defaultValue, this.defaultValue];
+ },
+ },
+ watch: {
+ value: {
+ immediate: true,
+ handler() {
+ this.innerValue = isValidRangeDate(this.value)
+ ? this.value
+ : [new Date(NaN), new Date(NaN)];
+ const calendars = this.innerValue.map((v, i) =>
+ startOfMonth(getValidDate(v, this.defaultValues[i]))
+ );
+ this.updateCalendars(calendars);
+ },
+ },
+ },
+ methods: {
+ handleSelect(date, type) {
+ const [startValue, endValue] = this.innerValue;
+ if (isValidDate(startValue) && !isValidDate(endValue)) {
+ if (startValue.getTime() > date.getTime()) {
+ this.innerValue = [date, startValue];
+ } else {
+ this.innerValue = [startValue, date];
+ }
+ this.emitDate(this.innerValue, type);
+ } else {
+ this.innerValue = [date, new Date(NaN)];
+ }
+ },
+ emitDate(dates, type) {
+ this.$emit('select', dates, type);
+ },
+ updateStartCalendar(value) {
+ this.updateCalendars([value, this.calendars[1]], 1);
+ },
+ updateEndCalendar(value) {
+ this.updateCalendars([this.calendars[0], value], 0);
+ },
+ updateCalendars(calendars, adjustIndex = 1) {
+ const gap = this.getCalendarGap(calendars);
+ if (gap) {
+ const calendar = new Date(calendars[adjustIndex]);
+ calendar.setMonth(calendar.getMonth() + (adjustIndex === 0 ? -gap : gap));
+ calendars[adjustIndex] = calendar;
+ }
+ this.calendars = calendars;
+ },
+ getCalendarGap(calendars) {
+ const [calendarLeft, calendarRight] = calendars;
+ const yearDiff = calendarRight.getFullYear() - calendarLeft.getFullYear();
+ const monthDiff = calendarRight.getMonth() - calendarLeft.getMonth();
+ const diff = yearDiff * 12 + monthDiff;
+ const min = this.calendarMinDiff;
+ const max = this.calendarMaxDiff;
+ if (diff < min) {
+ return min - diff;
+ }
+ if (diff > max) {
+ return max - diff;
+ }
+ return 0;
+ },
+ getRangeClasses(cellDate, currentDates, classnames) {
+ const classes = [].concat(this.getClasses(cellDate, currentDates, classnames));
+ if (
+ !/disabled|active|not-current-month/.test(classnames) &&
+ currentDates.length === 2 &&
+ cellDate.getTime() > currentDates[0].getTime() &&
+ cellDate.getTime() < currentDates[1].getTime()
+ ) {
+ classes.push('in-range');
+ }
+ return classes;
+ },
+ },
+ render() {
+ const calendarRange = this.calendars.map((calendar, index) => {
+ const props = {
+ ...this.$props,
+ calendar,
+ value: this.innerValue,
+ defaultValue: this.defaultValues[index],
+ getClasses: this.getRangeClasses,
+ // don't update when range is true
+ partialUpdate: false,
+ onSelect: this.handleSelect,
+ 'onUpdate:calendar': index === 0 ? this.updateStartCalendar : this.updateEndCalendar,
+ };
+ return ;
+ });
+
+ const { prefixClass } = this;
+
+ return {calendarRange}
;
+ },
+};
diff --git a/src/calendar/icon-button.vue b/src/calendar/icon-button.vue
new file mode 100644
index 0000000..70a88e1
--- /dev/null
+++ b/src/calendar/icon-button.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/src/calendar/table-date.vue b/src/calendar/table-date.vue
new file mode 100644
index 0000000..6afed79
--- /dev/null
+++ b/src/calendar/table-date.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{ day }} |
+
+
+
+
+ |
+ {{ getWeekNumber(row[0]) }}
+ |
+
+ {{ cell.getDate() }}
+ |
+
+
+
+
+
+
+
+
diff --git a/src/calendar/table-month.vue b/src/calendar/table-month.vue
new file mode 100644
index 0000000..d21bfe1
--- /dev/null
+++ b/src/calendar/table-month.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{ cell.text }}
+ |
+
+
+
+
+
+
+
diff --git a/src/calendar/table-year.vue b/src/calendar/table-year.vue
new file mode 100644
index 0000000..9ce62d6
--- /dev/null
+++ b/src/calendar/table-year.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ {{ firstYear }}
+
+ {{ lastYear }}
+
+
+
+
+
+
+
diff --git a/src/date-picker.js b/src/date-picker.js
new file mode 100644
index 0000000..1216b13
--- /dev/null
+++ b/src/date-picker.js
@@ -0,0 +1,573 @@
+import { parse, format, getWeek } from 'date-format-parse';
+import { isValidDate, isValidRangeDate, isValidDates } from './util/date';
+import { pick, isObject, mergeDeep } from './util/base';
+import { getLocale } from './locale';
+import Popup from './popup';
+import IconCalendar from './icon/icon-calendar';
+import IconClose from './icon/icon-close';
+import CalendarPanel from './calendar/calendar-panel';
+import CalendarRange from './calendar/calendar-range';
+import TimePanel from './time/time-panel';
+import TimeRange from './time/time-range';
+import DatetimePanel from './datetime/datetime-panel';
+import DatetimeRange from './datetime/datetime-range';
+
+const componentMap = {
+ default: CalendarPanel,
+ time: TimePanel,
+ datetime: DatetimePanel,
+};
+const componentRangeMap = {
+ default: CalendarRange,
+ time: TimeRange,
+ datetime: DatetimeRange,
+};
+
+export default {
+ name: 'DatePicker',
+ provide() {
+ return {
+ // make locale reactive
+ getLocale: () => this.locale,
+ getWeek: this.getWeek,
+ prefixClass: this.prefixClass,
+ dispatchDatePicker: this.$emit.bind(this),
+ };
+ },
+ props: {
+ ...DatetimePanel.props,
+ modelValue: {},
+ valueType: {
+ type: String,
+ default: 'date', // date, format, timestamp, or token like 'YYYY-MM-DD'
+ },
+ type: {
+ type: String, // ['date', 'datetime', 'time', 'year', 'month', 'week']
+ default: 'date',
+ },
+ format: {
+ type: String,
+ },
+ formatter: {
+ type: Object,
+ },
+ range: {
+ type: Boolean,
+ default: false,
+ },
+ multiple: {
+ type: Boolean,
+ default: false,
+ },
+ rangeSeparator: {
+ type: String,
+ },
+ lang: {
+ type: [String, Object],
+ },
+ placeholder: {
+ type: String,
+ default: '',
+ },
+ editable: {
+ type: Boolean,
+ default: true,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ clearable: {
+ type: Boolean,
+ default: true,
+ },
+ prefixClass: {
+ type: String,
+ default: 'mx',
+ },
+ inputClass: {},
+ inputAttr: {
+ type: Object,
+ default: () => ({}),
+ },
+ appendToBody: {
+ type: Boolean,
+ default: true,
+ },
+ open: {
+ type: Boolean,
+ default: undefined,
+ },
+ popupClass: {},
+ popupStyle: {
+ type: Object,
+ default: () => ({}),
+ },
+ inline: {
+ type: Boolean,
+ default: false,
+ },
+ confirm: {
+ type: Boolean,
+ default: false,
+ },
+ confirmText: {
+ type: String,
+ default: 'OK',
+ },
+ renderInputText: {
+ type: Function,
+ },
+ shortcuts: {
+ type: Array,
+ validator(value) {
+ return (
+ Array.isArray(value) &&
+ value.every(
+ (v) => isObject(v) && typeof v.text === 'string' && typeof v.onClick === 'function'
+ )
+ );
+ },
+ default() {
+ return [];
+ },
+ },
+ },
+ data() {
+ return {
+ // cache the innervalue, wait to confirm
+ currentValue: null,
+ userInput: null,
+ defaultOpen: false,
+ };
+ },
+ computed: {
+ popupVisible() {
+ return !this.disabled && (typeof this.open === 'boolean' ? this.open : this.defaultOpen);
+ },
+ innerRangeSeparator() {
+ return this.rangeSeparator || (this.multiple ? ',' : ' ~ ');
+ },
+ innerFormat() {
+ const map = {
+ date: 'YYYY-MM-DD',
+ datetime: 'YYYY-MM-DD HH:mm:ss',
+ year: 'YYYY',
+ month: 'YYYY-MM',
+ time: 'HH:mm:ss',
+ week: 'w',
+ };
+ return this.format || map[this.type] || map.date;
+ },
+ innerValue() {
+ let value = this.modelValue !== undefined ? this.modelValue : this.value;
+ if (this.validMultipleType) {
+ value = Array.isArray(value) ? value : [];
+ return value.map(this.value2date);
+ }
+ if (this.range) {
+ value = Array.isArray(value) ? value.slice(0, 2) : [null, null];
+ return value.map(this.value2date);
+ }
+ return this.value2date(value);
+ },
+ text() {
+ if (this.userInput !== null) {
+ return this.userInput;
+ }
+ if (typeof this.renderInputText === 'function') {
+ return this.renderInputText(this.innerValue);
+ }
+ if (!this.isValidValue(this.innerValue)) {
+ return '';
+ }
+ if (Array.isArray(this.innerValue)) {
+ return this.innerValue.map((v) => this.formatDate(v)).join(this.innerRangeSeparator);
+ }
+ return this.formatDate(this.innerValue);
+ },
+ showClearIcon() {
+ return !this.disabled && this.clearable && this.text;
+ },
+ locale() {
+ if (isObject(this.lang)) {
+ return mergeDeep(getLocale(), this.lang);
+ }
+ return getLocale(this.lang);
+ },
+ validMultipleType() {
+ const types = ['date', 'month', 'year'];
+ return this.multiple && !this.range && types.indexOf(this.type) !== -1;
+ },
+ },
+ watch: {
+ innerValue: {
+ immediate: true,
+ handler(val) {
+ this.currentValue = val;
+ },
+ },
+ },
+ created() {
+ if (typeof this.format === 'object') {
+ console.warn(
+ "[vue2-datepicker]: The prop `format` don't support Object any more. You can use the new prop `formatter` to replace it"
+ );
+ }
+ },
+ methods: {
+ handleClickOutSide(evt) {
+ const { target } = evt;
+ if (!this.$el.contains(target)) {
+ this.closePopup();
+ }
+ },
+ getFormatter(key) {
+ return (
+ (isObject(this.formatter) && this.formatter[key]) ||
+ (isObject(this.format) && this.format[key])
+ );
+ },
+ getWeek(date, options) {
+ if (typeof this.getFormatter('getWeek') === 'function') {
+ return this.getFormatter('getWeek')(date, options);
+ }
+ return getWeek(date, options);
+ },
+ parseDate(value, fmt) {
+ fmt = fmt || this.innerFormat;
+ if (typeof this.getFormatter('parse') === 'function') {
+ return this.getFormatter('parse')(value, fmt);
+ }
+ const backupDate = new Date();
+ return parse(value, fmt, { locale: this.locale.formatLocale, backupDate });
+ },
+ formatDate(date, fmt) {
+ fmt = fmt || this.innerFormat;
+ if (typeof this.getFormatter('stringify') === 'function') {
+ return this.getFormatter('stringify')(date, fmt);
+ }
+ return format(date, fmt, { locale: this.locale.formatLocale });
+ },
+ // transform the outer value to inner date
+ value2date(value) {
+ switch (this.valueType) {
+ case 'date':
+ return value instanceof Date ? new Date(value.getTime()) : new Date(NaN);
+ case 'timestamp':
+ return typeof value === 'number' ? new Date(value) : new Date(NaN);
+ case 'format':
+ return typeof value === 'string' ? this.parseDate(value) : new Date(NaN);
+ default:
+ return typeof value === 'string' ? this.parseDate(value, this.valueType) : new Date(NaN);
+ }
+ },
+ // transform the inner date to outer value
+ date2value(date) {
+ if (!isValidDate(date)) return null;
+ switch (this.valueType) {
+ case 'date':
+ return date;
+ case 'timestamp':
+ return date.getTime();
+ case 'format':
+ return this.formatDate(date);
+ default:
+ return this.formatDate(date, this.valueType);
+ }
+ },
+ emitValue(date, type) {
+ // fix IE11/10 trigger input event when input is focused. (placeholder !== '')
+ this.userInput = null;
+ const value = Array.isArray(date) ? date.map(this.date2value) : this.date2value(date);
+ this.$emit('update:modelValue', value);
+ this.$emit('input', value);
+ this.$emit('change', value, type);
+ this.afterEmitValue(type);
+ return value;
+ },
+ afterEmitValue(type) {
+ // this.type === 'datetime', click the time should close popup
+ if (!type || type === this.type || type === 'time') {
+ this.closePopup();
+ }
+ },
+ isValidValue(value) {
+ if (this.validMultipleType) {
+ return isValidDates(value);
+ }
+ if (this.range) {
+ return isValidRangeDate(value);
+ }
+ return isValidDate(value);
+ },
+ isValidValueAndNotDisabled(value) {
+ if (!this.isValidValue(value)) {
+ return false;
+ }
+ const disabledDate =
+ typeof this.disabledDate === 'function' ? this.disabledDate : () => false;
+ const disabledTime =
+ typeof this.disabledTime === 'function' ? this.disabledTime : () => false;
+ if (!Array.isArray(value)) {
+ value = [value];
+ }
+ return value.every((v) => !disabledDate(v) && !disabledTime(v));
+ },
+ handleMultipleDates(date, dates) {
+ if (this.validMultipleType && dates) {
+ const nextDates = dates.filter((v) => v.getTime() !== date.getTime());
+ if (nextDates.length === dates.length) {
+ nextDates.push(date);
+ }
+ return nextDates;
+ }
+ return date;
+ },
+ handleSelectDate(val, type, dates) {
+ val = this.handleMultipleDates(val, dates);
+ if (this.confirm) {
+ this.currentValue = val;
+ } else {
+ this.emitValue(val, this.validMultipleType ? `multiple-${type}` : type);
+ }
+ },
+ handleClear(evt) {
+ evt.stopPropagation();
+ this.emitValue(this.range ? [null, null] : null);
+ this.$emit('clear');
+ },
+ handleConfirmDate() {
+ const value = this.emitValue(this.currentValue);
+ this.$emit('confirm', value);
+ },
+ handleSelectShortcut(evt) {
+ const index = evt.currentTarget.getAttribute('data-index');
+ const item = this.shortcuts[parseInt(index, 10)];
+ if (isObject(item) && typeof item.onClick === 'function') {
+ const date = item.onClick(this);
+ if (date) {
+ this.emitValue(date);
+ }
+ }
+ },
+ openPopup(evt) {
+ if (this.popupVisible) return;
+ this.defaultOpen = true;
+ this.$emit('open', evt);
+ this.$emit('update:open', true);
+ },
+ closePopup() {
+ if (!this.popupVisible) return;
+ this.defaultOpen = false;
+ this.$emit('close');
+ this.$emit('update:open', false);
+ },
+ blur() {
+ // when use slot input
+ if (this.$refs.input) {
+ this.$refs.input.blur();
+ }
+ },
+ focus() {
+ if (this.$refs.input) {
+ this.$refs.input.focus();
+ }
+ },
+ handleInputChange() {
+ if (!this.editable || this.userInput === null) return;
+ const text = this.userInput.trim();
+ this.userInput = null;
+ if (text === '') {
+ this.handleClear();
+ return;
+ }
+ let date;
+ if (this.validMultipleType) {
+ date = text.split(this.innerRangeSeparator).map((v) => this.parseDate(v.trim()));
+ } else if (this.range) {
+ let arr = text.split(this.innerRangeSeparator);
+ if (arr.length !== 2) {
+ // Maybe the separator during the day is the same as the separator for the date
+ // eg: 2019-10-09-2020-01-02
+ arr = text.split(this.innerRangeSeparator.trim());
+ }
+ date = arr.map((v) => this.parseDate(v.trim()));
+ } else {
+ date = this.parseDate(text);
+ }
+ if (this.isValidValueAndNotDisabled(date)) {
+ this.emitValue(date);
+ this.blur();
+ } else {
+ this.$emit('input-error', text);
+ }
+ },
+ handleInputInput(evt) {
+ this.userInput = evt.target.value;
+ },
+ handleInputKeydown(evt) {
+ const { keyCode } = evt;
+ // Tab 9 or Enter 13
+ if (keyCode === 9) {
+ this.closePopup();
+ } else if (keyCode === 13) {
+ this.handleInputChange();
+ }
+ },
+ handleInputBlur(evt) {
+ // tab close
+ this.$emit('blur', evt);
+ },
+ handleInputFocus(evt) {
+ this.openPopup(evt);
+ this.$emit('focus', evt);
+ },
+ hasSlot(name) {
+ return this.$slots[name];
+ },
+ renderSlot(name, fallback, props) {
+ const slotFn = this.$slots[name];
+ return slotFn ? slotFn(props) : fallback;
+ },
+ renderInput() {
+ const { prefixClass } = this;
+ const props = {
+ name: 'date',
+ type: 'text',
+ autocomplete: 'off',
+ value: this.text,
+ class: this.inputClass || `${this.prefixClass}-input`,
+ readonly: !this.editable,
+ disabled: this.disabled,
+ placeholder: this.placeholder,
+ ...this.inputAttr,
+ onKeydown: this.handleInputKeydown,
+ onFocus: this.handleInputFocus,
+ onBlur: this.handleInputBlur,
+ onInput: this.handleInputInput,
+ onChange: this.handleInputChange,
+ };
+ const input = this.renderSlot('input', , props);
+ return (
+
+ {input}
+ {this.showClearIcon ? (
+
+ {this.renderSlot('icon-clear', )}
+
+ ) : null}
+
+ {this.renderSlot('icon-calendar', )}
+
+
+ );
+ },
+ renderContent() {
+ const map = this.range ? componentRangeMap : componentMap;
+ const Component = map[this.type] || map.default;
+ const props = {
+ ...pick(this.$props, Object.keys(Component.props)),
+ value: this.currentValue,
+ };
+ const content = ;
+ return (
+
+ {this.renderSlot('content', content, {
+ value: this.currentValue,
+ emit: this.handleSelectDate,
+ })}
+
+ );
+ },
+ renderSidebar() {
+ const { prefixClass } = this;
+ return (
+
+ );
+ },
+ renderHeader() {
+ return (
+
+ );
+ },
+ renderFooter() {
+ const { prefixClass } = this;
+ return (
+
+ );
+ },
+ },
+ render() {
+ const { prefixClass, inline, disabled } = this;
+ const sidedar = this.hasSlot('sidebar') || this.shortcuts.length ? this.renderSidebar() : null;
+ const content = (
+
+ {this.hasSlot('header') ? this.renderHeader() : null}
+ {this.renderContent()}
+ {this.hasSlot('footer') || this.confirm ? this.renderFooter() : null}
+
+ );
+ return (
+
+ {!inline ? this.renderInput() : null}
+ {!inline ? (
+
+ ) : (
+
+ {sidedar}
+ {content}
+
+ )}
+
+ );
+ },
+};
diff --git a/src/datetime/datetime-panel.js b/src/datetime/datetime-panel.js
new file mode 100644
index 0000000..fd13d12
--- /dev/null
+++ b/src/datetime/datetime-panel.js
@@ -0,0 +1,89 @@
+import CalendarPanel from '../calendar/calendar-panel';
+import TimePanel from '../time/time-panel';
+import { assignTime, getValidDate } from '../util/date';
+import { pick } from '../util/base';
+
+export default {
+ name: 'DatetimePanel',
+ inject: {
+ prefixClass: {
+ default: 'mx',
+ },
+ },
+ emits: ['select'],
+ props: {
+ ...CalendarPanel.props,
+ ...TimePanel.props,
+ showTimePanel: {
+ type: Boolean,
+ default: undefined,
+ },
+ },
+ data() {
+ return {
+ defaultTimeVisible: false,
+ currentValue: this.value,
+ };
+ },
+ computed: {
+ timeVisible() {
+ return typeof this.showTimePanel === 'boolean' ? this.showTimePanel : this.defaultTimeVisible;
+ },
+ },
+ watch: {
+ value(val) {
+ this.currentValue = val;
+ },
+ },
+ methods: {
+ closeTimePanel() {
+ this.defaultTimeVisible = false;
+ },
+ openTimePanel() {
+ this.defaultTimeVisible = true;
+ },
+ emitDate(date, type) {
+ this.$emit('select', date, type);
+ },
+ handleSelect(date, type) {
+ if (type === 'date') {
+ this.openTimePanel();
+ }
+ let datetime = assignTime(date, getValidDate(this.value, this.defaultValue));
+ if (this.disabledTime(new Date(datetime))) {
+ // set the time of defalutValue;
+ datetime = assignTime(date, this.defaultValue);
+ if (this.disabledTime(new Date(datetime))) {
+ // if disabled don't emit date
+ this.currentValue = datetime;
+ return;
+ }
+ }
+ this.emitDate(datetime, type);
+ },
+ },
+ render() {
+ const calendarProps = {
+ ...pick(this.$props, Object.keys(CalendarPanel.props)),
+ type: 'date',
+ value: this.currentValue,
+ onSelect: this.handleSelect,
+ };
+ const timeProps = {
+ ...pick(this.$props, Object.keys(TimePanel.props)),
+ showTimeHeader: true,
+ value: this.currentValue,
+ onSelect: this.emitDate,
+ onClicktitle: this.closeTimePanel,
+ };
+
+ const { prefixClass } = this;
+
+ return (
+
+
+ {this.timeVisible && }
+
+ );
+ },
+};
diff --git a/src/datetime/datetime-range.js b/src/datetime/datetime-range.js
new file mode 100644
index 0000000..af6cf7d
--- /dev/null
+++ b/src/datetime/datetime-range.js
@@ -0,0 +1,95 @@
+import CalendarRange from '../calendar/calendar-range';
+import TimeRange from '../time/time-range';
+import { pick } from '../util/base';
+import { isValidRangeDate, assignTime } from '../util/date';
+
+export default {
+ name: 'DatetimeRange',
+ inject: {
+ prefixClass: {
+ default: 'mx',
+ },
+ },
+ props: {
+ ...CalendarRange.props,
+ ...TimeRange.props,
+ showTimePanel: {
+ type: Boolean,
+ default: undefined,
+ },
+ },
+ data() {
+ return {
+ defaultTimeVisible: false,
+ currentValue: this.value,
+ };
+ },
+ computed: {
+ timeVisible() {
+ return typeof this.showTimePanel === 'boolean' ? this.showTimePanel : this.defaultTimeVisible;
+ },
+ },
+ watch: {
+ value(val) {
+ this.currentValue = val;
+ },
+ },
+ methods: {
+ closeTimePanel() {
+ this.defaultTimeVisible = false;
+ },
+ openTimePanel() {
+ this.defaultTimeVisible = true;
+ },
+ emitDate(dates, type) {
+ this.$emit('select', dates, type);
+ },
+ handleSelect(dates, type) {
+ if (type === 'date') {
+ this.openTimePanel();
+ }
+ const defaultValues = Array.isArray(this.defaultValue)
+ ? this.defaultValue
+ : [this.defaultValue, this.defaultValue];
+ let datetimes = dates.map((date, i) => {
+ const time = isValidRangeDate(this.value) ? this.value[i] : defaultValues[i];
+ return assignTime(date, time);
+ });
+ if (datetimes[1].getTime() < datetimes[0].getTime()) {
+ datetimes = [datetimes[0], datetimes[0]];
+ }
+ if (datetimes.some(this.disabledTime)) {
+ datetimes = dates.map((date, i) => assignTime(date, defaultValues[i]));
+ if (datetimes.some(this.disabledTime)) {
+ this.currentValue = datetimes;
+ return;
+ }
+ }
+ this.emitDate(datetimes, type);
+ },
+ },
+ render() {
+ const calendarProps = {
+ ...pick(this.$props, Object.keys(CalendarRange.props)),
+ type: 'date',
+ value: this.currentValue,
+ onSelect: this.handleSelect,
+ };
+ const timeProps = {
+ ...pick(this.$props, Object.keys(TimeRange.props)),
+ value: this.currentValue,
+ showTimeHeader: true,
+ onSelect: this.emitDate,
+ onClicktitle: this.closeTimePanel,
+ };
+
+ const { prefixClass } = this;
+
+ return (
+
+
+ {this.timeVisible && }
+
+ );
+ },
+};
diff --git a/src/icon/icon-calendar.vue b/src/icon/icon-calendar.vue
new file mode 100644
index 0000000..9429b70
--- /dev/null
+++ b/src/icon/icon-calendar.vue
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/icon/icon-close.vue b/src/icon/icon-close.vue
new file mode 100644
index 0000000..77ef6a0
--- /dev/null
+++ b/src/icon/icon-close.vue
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..2473046
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,26 @@
+/* istanbul ignore file */
+import DatePicker from './date-picker';
+import CalendarPanel from './calendar/calendar-panel';
+import CalendarRange from './calendar/calendar-range';
+import TimePanel from './time/time-panel';
+import TimeRange from './time/time-range';
+import DatetimePanel from './datetime/datetime-panel';
+import DatetimeRange from './datetime/datetime-range';
+import { locale } from './locale';
+
+DatePicker.locale = locale;
+
+DatePicker.install = function install(app) {
+ app.component(DatePicker.name, DatePicker);
+};
+
+Object.assign(DatePicker, {
+ CalendarPanel,
+ CalendarRange,
+ TimePanel,
+ TimeRange,
+ DatetimePanel,
+ DatetimeRange,
+});
+
+export default DatePicker;
diff --git a/src/locale.js b/src/locale.js
new file mode 100644
index 0000000..d0077a0
--- /dev/null
+++ b/src/locale.js
@@ -0,0 +1,29 @@
+import enUS from './locale/en';
+
+let defaultLocale = 'en';
+const locales = {};
+locales[defaultLocale] = enUS;
+
+export function locale(name, object, isLocal) {
+ if (typeof name !== 'string') return locales[defaultLocale];
+ let l = defaultLocale;
+ if (locales[name]) {
+ l = name;
+ }
+ if (object) {
+ locales[name] = object;
+ l = name;
+ }
+ if (!isLocal) {
+ defaultLocale = l;
+ }
+ return locales[name] || locales[defaultLocale];
+}
+
+/**
+ * get locale object
+ * @param {string} name lang
+ */
+export function getLocale(name) {
+ return locale(name, null, true);
+}
diff --git a/src/locale/af.js b/src/locale/af.js
new file mode 100644
index 0000000..3a45ee1
--- /dev/null
+++ b/src/locale/af.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import af from 'date-format-parse/lib/locale/af';
+
+const lang = {
+ formatLocale: af,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('af', lang);
+
+export default lang;
diff --git a/src/locale/ar-dz.js b/src/locale/ar-dz.js
new file mode 100644
index 0000000..3eb6491
--- /dev/null
+++ b/src/locale/ar-dz.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import arDZ from 'date-format-parse/lib/locale/ar-dz';
+
+const lang = {
+ formatLocale: arDZ,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ar-dz', lang);
+
+export default lang;
diff --git a/src/locale/ar-sa.js b/src/locale/ar-sa.js
new file mode 100644
index 0000000..2a4f117
--- /dev/null
+++ b/src/locale/ar-sa.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import arSA from 'date-format-parse/lib/locale/ar-sa';
+
+const lang = {
+ formatLocale: arSA,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ar-sa', lang);
+
+export default lang;
diff --git a/src/locale/ar.js b/src/locale/ar.js
new file mode 100644
index 0000000..3426958
--- /dev/null
+++ b/src/locale/ar.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ar from 'date-format-parse/lib/locale/ar';
+
+const lang = {
+ formatLocale: ar,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ar', lang);
+
+export default lang;
diff --git a/src/locale/az.js b/src/locale/az.js
new file mode 100644
index 0000000..77781af
--- /dev/null
+++ b/src/locale/az.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import az from 'date-format-parse/lib/locale/az';
+
+const lang = {
+ formatLocale: az,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('az', lang);
+
+export default lang;
diff --git a/src/locale/be.js b/src/locale/be.js
new file mode 100644
index 0000000..9487ede
--- /dev/null
+++ b/src/locale/be.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import be from 'date-format-parse/lib/locale/be';
+
+const lang = {
+ formatLocale: be,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('be', lang);
+
+export default lang;
diff --git a/src/locale/bg.js b/src/locale/bg.js
new file mode 100644
index 0000000..e56e524
--- /dev/null
+++ b/src/locale/bg.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import bg from 'date-format-parse/lib/locale/bg';
+
+const lang = {
+ formatLocale: bg,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('bg', lang);
+
+export default lang;
diff --git a/src/locale/bm.js b/src/locale/bm.js
new file mode 100644
index 0000000..03b4acc
--- /dev/null
+++ b/src/locale/bm.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import bm from 'date-format-parse/lib/locale/bm';
+
+const lang = {
+ formatLocale: bm,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('bm', lang);
+
+export default lang;
diff --git a/src/locale/bn.js b/src/locale/bn.js
new file mode 100644
index 0000000..02fa737
--- /dev/null
+++ b/src/locale/bn.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import bn from 'date-format-parse/lib/locale/bn';
+
+const lang = {
+ formatLocale: bn,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('bn', lang);
+
+export default lang;
diff --git a/src/locale/ca.js b/src/locale/ca.js
new file mode 100644
index 0000000..2be0593
--- /dev/null
+++ b/src/locale/ca.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ca from 'date-format-parse/lib/locale/ca';
+
+const lang = {
+ formatLocale: ca,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ca', lang);
+
+export default lang;
diff --git a/src/locale/cs.js b/src/locale/cs.js
new file mode 100644
index 0000000..d54a649
--- /dev/null
+++ b/src/locale/cs.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import cs from 'date-format-parse/lib/locale/cs';
+
+const lang = {
+ formatLocale: cs,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('cs', lang);
+
+export default lang;
diff --git a/src/locale/cy.js b/src/locale/cy.js
new file mode 100644
index 0000000..ac0201b
--- /dev/null
+++ b/src/locale/cy.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import cy from 'date-format-parse/lib/locale/cy';
+
+const lang = {
+ formatLocale: cy,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('cy', lang);
+
+export default lang;
diff --git a/src/locale/da.js b/src/locale/da.js
new file mode 100644
index 0000000..142fb29
--- /dev/null
+++ b/src/locale/da.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import da from 'date-format-parse/lib/locale/da';
+
+const lang = {
+ formatLocale: da,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('da', lang);
+
+export default lang;
diff --git a/src/locale/de.js b/src/locale/de.js
new file mode 100644
index 0000000..1841e83
--- /dev/null
+++ b/src/locale/de.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import de from 'date-format-parse/lib/locale/de';
+
+const lang = {
+ formatLocale: de,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('de', lang);
+
+export default lang;
diff --git a/src/locale/el.js b/src/locale/el.js
new file mode 100644
index 0000000..28a440e
--- /dev/null
+++ b/src/locale/el.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import el from 'date-format-parse/lib/locale/el';
+
+const lang = {
+ formatLocale: el,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('el', lang);
+
+export default lang;
diff --git a/src/locale/en.js b/src/locale/en.js
new file mode 100644
index 0000000..7cf55ef
--- /dev/null
+++ b/src/locale/en.js
@@ -0,0 +1,10 @@
+import en from 'date-format-parse/lib/locale/en';
+
+const lang = {
+ formatLocale: en,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+export default lang;
diff --git a/src/locale/eo.js b/src/locale/eo.js
new file mode 100644
index 0000000..344f186
--- /dev/null
+++ b/src/locale/eo.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import eo from 'date-format-parse/lib/locale/eo';
+
+const lang = {
+ formatLocale: eo,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('eo', lang);
+
+export default lang;
diff --git a/src/locale/es.js b/src/locale/es.js
new file mode 100644
index 0000000..b2095f7
--- /dev/null
+++ b/src/locale/es.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import es from 'date-format-parse/lib/locale/es';
+
+const lang = {
+ formatLocale: es,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('es', lang);
+
+export default lang;
diff --git a/src/locale/et.js b/src/locale/et.js
new file mode 100644
index 0000000..47670be
--- /dev/null
+++ b/src/locale/et.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import et from 'date-format-parse/lib/locale/et';
+
+const lang = {
+ formatLocale: et,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('et', lang);
+
+export default lang;
diff --git a/src/locale/fi.js b/src/locale/fi.js
new file mode 100644
index 0000000..427b9fe
--- /dev/null
+++ b/src/locale/fi.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import fi from 'date-format-parse/lib/locale/fi';
+
+const lang = {
+ formatLocale: fi,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('fi', lang);
+
+export default lang;
diff --git a/src/locale/fr.js b/src/locale/fr.js
new file mode 100644
index 0000000..46f8aeb
--- /dev/null
+++ b/src/locale/fr.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import fr from 'date-format-parse/lib/locale/fr';
+
+const lang = {
+ formatLocale: fr,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('fr', lang);
+
+export default lang;
diff --git a/src/locale/gl.js b/src/locale/gl.js
new file mode 100644
index 0000000..0aa9c29
--- /dev/null
+++ b/src/locale/gl.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import gl from 'date-format-parse/lib/locale/gl';
+
+const lang = {
+ formatLocale: gl,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('gl', lang);
+
+export default lang;
diff --git a/src/locale/gu.js b/src/locale/gu.js
new file mode 100644
index 0000000..5bc971a
--- /dev/null
+++ b/src/locale/gu.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import gu from 'date-format-parse/lib/locale/gu';
+
+const lang = {
+ formatLocale: gu,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('gu', lang);
+
+export default lang;
diff --git a/src/locale/he.js b/src/locale/he.js
new file mode 100644
index 0000000..2c9fc96
--- /dev/null
+++ b/src/locale/he.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import he from 'date-format-parse/lib/locale/he';
+
+const lang = {
+ formatLocale: he,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('he', lang);
+
+export default lang;
diff --git a/src/locale/hi.js b/src/locale/hi.js
new file mode 100644
index 0000000..a3aae16
--- /dev/null
+++ b/src/locale/hi.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import hi from 'date-format-parse/lib/locale/hi';
+
+const lang = {
+ formatLocale: hi,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('hi', lang);
+
+export default lang;
diff --git a/src/locale/hr.js b/src/locale/hr.js
new file mode 100644
index 0000000..bf86469
--- /dev/null
+++ b/src/locale/hr.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import hr from 'date-format-parse/lib/locale/hr';
+
+const lang = {
+ formatLocale: hr,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('hr', lang);
+
+export default lang;
diff --git a/src/locale/hu.js b/src/locale/hu.js
new file mode 100644
index 0000000..5c21733
--- /dev/null
+++ b/src/locale/hu.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import hu from 'date-format-parse/lib/locale/hu';
+
+const lang = {
+ formatLocale: hu,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('hu', lang);
+
+export default lang;
diff --git a/src/locale/id.js b/src/locale/id.js
new file mode 100644
index 0000000..64bb237
--- /dev/null
+++ b/src/locale/id.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import id from 'date-format-parse/lib/locale/id';
+
+const lang = {
+ formatLocale: id,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('id', lang);
+
+export default lang;
diff --git a/src/locale/is.js b/src/locale/is.js
new file mode 100644
index 0000000..27480c5
--- /dev/null
+++ b/src/locale/is.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import is from 'date-format-parse/lib/locale/is';
+
+const lang = {
+ formatLocale: is,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('is', lang);
+
+export default lang;
diff --git a/src/locale/it.js b/src/locale/it.js
new file mode 100644
index 0000000..4adda78
--- /dev/null
+++ b/src/locale/it.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import it from 'date-format-parse/lib/locale/it';
+
+const lang = {
+ formatLocale: it,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('it', lang);
+
+export default lang;
diff --git a/src/locale/ja.js b/src/locale/ja.js
new file mode 100644
index 0000000..df4389f
--- /dev/null
+++ b/src/locale/ja.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ja from 'date-format-parse/lib/locale/ja';
+
+const lang = {
+ formatLocale: ja,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('ja', lang);
+
+export default lang;
diff --git a/src/locale/ka.js b/src/locale/ka.js
new file mode 100644
index 0000000..716933a
--- /dev/null
+++ b/src/locale/ka.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ka from 'date-format-parse/lib/locale/ka';
+
+const lang = {
+ formatLocale: ka,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ka', lang);
+
+export default lang;
diff --git a/src/locale/kk.js b/src/locale/kk.js
new file mode 100644
index 0000000..ec4a49d
--- /dev/null
+++ b/src/locale/kk.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import kk from 'date-format-parse/lib/locale/kk';
+
+const lang = {
+ formatLocale: kk,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('kk', lang);
+
+export default lang;
diff --git a/src/locale/ko.js b/src/locale/ko.js
new file mode 100644
index 0000000..1617d64
--- /dev/null
+++ b/src/locale/ko.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ko from 'date-format-parse/lib/locale/ko';
+
+const lang = {
+ formatLocale: ko,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('ko', lang);
+
+export default lang;
diff --git a/src/locale/lt.js b/src/locale/lt.js
new file mode 100644
index 0000000..e9d1961
--- /dev/null
+++ b/src/locale/lt.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import lt from 'date-format-parse/lib/locale/lt';
+
+const lang = {
+ formatLocale: lt,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('lt', lang);
+
+export default lang;
diff --git a/src/locale/lv.js b/src/locale/lv.js
new file mode 100644
index 0000000..399adcf
--- /dev/null
+++ b/src/locale/lv.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import lv from 'date-format-parse/lib/locale/lv';
+
+const lang = {
+ formatLocale: lv,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('lv', lang);
+
+export default lang;
diff --git a/src/locale/mk.js b/src/locale/mk.js
new file mode 100644
index 0000000..9191672
--- /dev/null
+++ b/src/locale/mk.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import mk from 'date-format-parse/lib/locale/mk';
+
+const lang = {
+ formatLocale: mk,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('mk', lang);
+
+export default lang;
diff --git a/src/locale/ms.js b/src/locale/ms.js
new file mode 100644
index 0000000..5d961f2
--- /dev/null
+++ b/src/locale/ms.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ms from 'date-format-parse/lib/locale/ms';
+
+const lang = {
+ formatLocale: ms,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ms', lang);
+
+export default lang;
diff --git a/src/locale/nb.js b/src/locale/nb.js
new file mode 100644
index 0000000..e3cffa9
--- /dev/null
+++ b/src/locale/nb.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import nb from 'date-format-parse/lib/locale/nb';
+
+const lang = {
+ formatLocale: nb,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('nb', lang);
+
+export default lang;
diff --git a/src/locale/nl-be.js b/src/locale/nl-be.js
new file mode 100644
index 0000000..3e7a897
--- /dev/null
+++ b/src/locale/nl-be.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import nlBE from 'date-format-parse/lib/locale/nl-be';
+
+const lang = {
+ formatLocale: nlBE,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('nl-be', lang);
+
+export default lang;
diff --git a/src/locale/nl.js b/src/locale/nl.js
new file mode 100644
index 0000000..e3e793a
--- /dev/null
+++ b/src/locale/nl.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import nl from 'date-format-parse/lib/locale/nl';
+
+const lang = {
+ formatLocale: nl,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('nl', lang);
+
+export default lang;
diff --git a/src/locale/pl.js b/src/locale/pl.js
new file mode 100644
index 0000000..5dbf80c
--- /dev/null
+++ b/src/locale/pl.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import pl from 'date-format-parse/lib/locale/pl';
+
+const lang = {
+ formatLocale: pl,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('pl', lang);
+
+export default lang;
diff --git a/src/locale/pt-br.js b/src/locale/pt-br.js
new file mode 100644
index 0000000..7a2ec59
--- /dev/null
+++ b/src/locale/pt-br.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ptBR from 'date-format-parse/lib/locale/pt-br';
+
+const lang = {
+ formatLocale: ptBR,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('pt-br', lang);
+
+export default lang;
diff --git a/src/locale/pt.js b/src/locale/pt.js
new file mode 100644
index 0000000..9a96646
--- /dev/null
+++ b/src/locale/pt.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import pt from 'date-format-parse/lib/locale/pt';
+
+const lang = {
+ formatLocale: pt,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('pt', lang);
+
+export default lang;
diff --git a/src/locale/ro.js b/src/locale/ro.js
new file mode 100644
index 0000000..dea27dd
--- /dev/null
+++ b/src/locale/ro.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ro from 'date-format-parse/lib/locale/ro';
+
+const lang = {
+ formatLocale: ro,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ro', lang);
+
+export default lang;
diff --git a/src/locale/ru.js b/src/locale/ru.js
new file mode 100644
index 0000000..a894536
--- /dev/null
+++ b/src/locale/ru.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ru from 'date-format-parse/lib/locale/ru';
+
+const lang = {
+ formatLocale: ru,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ru', lang);
+
+export default lang;
diff --git a/src/locale/sl.js b/src/locale/sl.js
new file mode 100644
index 0000000..1275099
--- /dev/null
+++ b/src/locale/sl.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import sl from 'date-format-parse/lib/locale/sl';
+
+const lang = {
+ formatLocale: sl,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('sl', lang);
+
+export default lang;
diff --git a/src/locale/sr.js b/src/locale/sr.js
new file mode 100644
index 0000000..06bc059
--- /dev/null
+++ b/src/locale/sr.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import sr from 'date-format-parse/lib/locale/sr';
+
+const lang = {
+ formatLocale: sr,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('sr', lang);
+
+export default lang;
diff --git a/src/locale/sv.js b/src/locale/sv.js
new file mode 100644
index 0000000..5f8f2b1
--- /dev/null
+++ b/src/locale/sv.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import sv from 'date-format-parse/lib/locale/sv';
+
+const lang = {
+ formatLocale: sv,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('sv', lang);
+
+export default lang;
diff --git a/src/locale/ta.js b/src/locale/ta.js
new file mode 100644
index 0000000..dc120a0
--- /dev/null
+++ b/src/locale/ta.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ta from 'date-format-parse/lib/locale/ta';
+
+const lang = {
+ formatLocale: ta,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ta', lang);
+
+export default lang;
diff --git a/src/locale/te.js b/src/locale/te.js
new file mode 100644
index 0000000..bd97856
--- /dev/null
+++ b/src/locale/te.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import te from 'date-format-parse/lib/locale/te';
+
+const lang = {
+ formatLocale: te,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('te', lang);
+
+export default lang;
diff --git a/src/locale/th.js b/src/locale/th.js
new file mode 100644
index 0000000..7d70505
--- /dev/null
+++ b/src/locale/th.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import th from 'date-format-parse/lib/locale/th';
+
+const lang = {
+ formatLocale: th,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('th', lang);
+
+export default lang;
diff --git a/src/locale/tr.js b/src/locale/tr.js
new file mode 100644
index 0000000..3320ab2
--- /dev/null
+++ b/src/locale/tr.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import tr from 'date-format-parse/lib/locale/tr';
+
+const lang = {
+ formatLocale: tr,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('tr', lang);
+
+export default lang;
diff --git a/src/locale/ug-cn.js b/src/locale/ug-cn.js
new file mode 100644
index 0000000..434a924
--- /dev/null
+++ b/src/locale/ug-cn.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import ugCN from 'date-format-parse/lib/locale/ug-cn';
+
+const lang = {
+ formatLocale: ugCN,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('ug-cn', lang);
+
+export default lang;
diff --git a/src/locale/uk.js b/src/locale/uk.js
new file mode 100644
index 0000000..7c06881
--- /dev/null
+++ b/src/locale/uk.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import uk from 'date-format-parse/lib/locale/uk';
+
+const lang = {
+ formatLocale: uk,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: true,
+};
+
+DatePicker.locale('uk', lang);
+
+export default lang;
diff --git a/src/locale/vi.js b/src/locale/vi.js
new file mode 100644
index 0000000..8405424
--- /dev/null
+++ b/src/locale/vi.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import vi from 'date-format-parse/lib/locale/vi';
+
+const lang = {
+ formatLocale: vi,
+ yearFormat: 'YYYY',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('vi', lang);
+
+export default lang;
diff --git a/src/locale/zh-cn.js b/src/locale/zh-cn.js
new file mode 100644
index 0000000..696f6b1
--- /dev/null
+++ b/src/locale/zh-cn.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import zhCN from 'date-format-parse/lib/locale/zh-cn';
+
+const lang = {
+ formatLocale: zhCN,
+ yearFormat: 'YYYY年',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('zh-cn', lang);
+
+export default lang;
diff --git a/src/locale/zh-tw.js b/src/locale/zh-tw.js
new file mode 100644
index 0000000..0ed2a90
--- /dev/null
+++ b/src/locale/zh-tw.js
@@ -0,0 +1,13 @@
+import DatePicker from 'vue2-datepicker';
+import zhTW from 'date-format-parse/lib/locale/zh-tw';
+
+const lang = {
+ formatLocale: zhTW,
+ yearFormat: 'YYYY年',
+ monthFormat: 'MMM',
+ monthBeforeYear: false,
+};
+
+DatePicker.locale('zh-tw', lang);
+
+export default lang;
diff --git a/src/popup.vue b/src/popup.vue
new file mode 100644
index 0000000..2f215e0
--- /dev/null
+++ b/src/popup.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/scrollbar/scrollbar-vertical.vue b/src/scrollbar/scrollbar-vertical.vue
new file mode 100644
index 0000000..f00d662
--- /dev/null
+++ b/src/scrollbar/scrollbar-vertical.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
diff --git a/src/style/animation.scss b/src/style/animation.scss
new file mode 100644
index 0000000..2d8e8d1
--- /dev/null
+++ b/src/style/animation.scss
@@ -0,0 +1,16 @@
+@import './var.scss';
+
+.#{$namespace}-zoom-in-down-enter-active,
+.#{$namespace}-zoom-in-down-leave-active {
+ opacity: 1;
+ transform: scaleY(1);
+ transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1),
+ opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1);
+ transform-origin: center top;
+}
+
+.#{$namespace}-zoom-in-down-enter,
+.#{$namespace}-zoom-in-down-leave-to {
+ opacity: 0;
+ transform: scaleY(0);
+}
diff --git a/src/style/btn.scss b/src/style/btn.scss
new file mode 100644
index 0000000..90bd7cd
--- /dev/null
+++ b/src/style/btn.scss
@@ -0,0 +1,28 @@
+@import './var.scss';
+
+.#{$namespace}-btn {
+ box-sizing: border-box;
+ line-height: 1;
+ font-size: 14px;
+ font-weight: 500;
+ padding: 7px 15px;
+ margin: 0;
+ cursor: pointer;
+ background-color: transparent;
+ outline: none;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ border-radius: 4px;
+ color: $default-color;
+ white-space: nowrap;
+ &:hover {
+ border-color: $primary-color;
+ color: $primary-color;
+ }
+}
+
+.#{$namespace}-btn-text {
+ border: 0;
+ padding: 0 4px;
+ text-align: left;
+ line-height: inherit;
+}
diff --git a/src/style/icon.scss b/src/style/icon.scss
new file mode 100644
index 0000000..e0ed7a1
--- /dev/null
+++ b/src/style/icon.scss
@@ -0,0 +1,37 @@
+@import './var.scss';
+
+.#{$namespace}-icon-left:before,
+.#{$namespace}-icon-right:before,
+.#{$namespace}-icon-double-left:before,
+.#{$namespace}-icon-double-right:before,
+.#{$namespace}-icon-double-left:after,
+.#{$namespace}-icon-double-right:after {
+ content: '';
+ position: relative;
+ top: -1px;
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ vertical-align: middle;
+ border-style: solid;
+ border-color: currentColor;
+ border-width: 2px 0 0 2px;
+ border-radius: 1px;
+ box-sizing: border-box;
+ transform-origin: center;
+ transform: rotate(-45deg) scale(0.7);
+}
+
+.#{$namespace}-icon-double-left:after {
+ left: -4px;
+}
+
+.#{$namespace}-icon-double-right:before {
+ left: 4px;
+}
+
+.#{$namespace}-icon-right:before,
+.#{$namespace}-icon-double-right:before,
+.#{$namespace}-icon-double-right:after {
+ transform: rotate(135deg) scale(0.7);
+}
diff --git a/src/style/index.scss b/src/style/index.scss
new file mode 100644
index 0000000..5461cd7
--- /dev/null
+++ b/src/style/index.scss
@@ -0,0 +1,368 @@
+@import './var.scss';
+@import './icon.scss';
+@import './btn.scss';
+@import './scrollbar.scss';
+@import './animation.scss';
+
+.#{$namespace}-datepicker {
+ position: relative;
+ display: inline-block;
+ width: 210px;
+ svg {
+ width: 1em;
+ height: 1em;
+ vertical-align: -0.15em;
+ fill: currentColor;
+ overflow: hidden;
+ }
+}
+
+.#{$namespace}-datepicker-range {
+ width: 320px;
+}
+
+.#{$namespace}-datepicker-inline {
+ width: auto;
+}
+
+.#{$namespace}-input-wrapper {
+ position: relative;
+ .#{$namespace}-icon-clear {
+ display: none;
+ }
+ &:hover {
+ .#{$namespace}-icon-clear {
+ display: block;
+ }
+ .#{$namespace}-icon-clear + .#{$namespace}-icon-calendar {
+ display: none;
+ }
+ }
+}
+
+.#{$namespace}-input {
+ display: inline-block;
+ box-sizing: border-box;
+ width: 100%;
+ height: 34px;
+ padding: 6px 30px;
+ padding-left: 10px;
+ font-size: 14px;
+ line-height: 1.4;
+ color: $input-color;
+ background-color: #fff;
+ border: 1px solid $input-border-color;
+ border-radius: $input-border-radius;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+
+ &:hover,
+ &:focus {
+ border-color: $input-hover-border-color;
+ }
+ &:disabled,
+ &.disabled {
+ color: $disabled-color;
+ background-color: $disabled-background-color;
+ border-color: $input-border-color;
+ cursor: not-allowed;
+ }
+ &:focus {
+ outline: none;
+ }
+ &::-ms-clear {
+ display: none;
+ }
+}
+
+.#{$namespace}-icon-calendar,
+.#{$namespace}-icon-clear {
+ position: absolute;
+ top: 50%;
+ right: 8px;
+ transform: translateY(-50%);
+ font-size: 16px;
+ line-height: 1;
+ color: rgba(0, 0, 0, 0.5);
+ vertical-align: middle;
+}
+
+.#{$namespace}-icon-clear {
+ cursor: pointer;
+ &:hover {
+ color: rgba(0, 0, 0, 0.8);
+ }
+}
+
+.#{$namespace}-datepicker-main {
+ font: 14px/1.5 'Helvetica Neue', Helvetica, Arial, 'Microsoft Yahei', sans-serif;
+ color: $default-color;
+ background-color: #fff;
+ border: 1px solid $border-color;
+}
+
+.#{$namespace}-datepicker-popup {
+ position: absolute;
+ margin-top: 1px;
+ margin-bottom: 1px;
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ z-index: $popup-z-index;
+}
+
+.#{$namespace}-datepicker-sidebar {
+ float: left;
+ box-sizing: border-box;
+ width: $sidebar-margin-left;
+ padding: 6px;
+ overflow: auto;
+}
+
+.#{$namespace}-datepicker-sidebar + .#{$namespace}-datepicker-content {
+ margin-left: $sidebar-margin-left;
+ border-left: 1px solid $border-color;
+}
+
+.#{$namespace}-datepicker-body {
+ position: relative;
+ user-select: none;
+}
+
+.#{$namespace}-btn-shortcut {
+ display: block;
+ padding: 0 6px;
+ line-height: 24px;
+}
+
+.#{$namespace}-range-wrapper {
+ display: flex;
+ @media (max-width: 750px) {
+ flex-direction: column;
+ }
+}
+
+.#{$namespace}-datepicker-header {
+ padding: 6px 8px;
+ border-bottom: 1px solid $border-color;
+}
+
+.#{$namespace}-datepicker-footer {
+ padding: 6px 8px;
+ text-align: right;
+ border-top: 1px solid $border-color;
+}
+
+.#{$namespace}-calendar {
+ box-sizing: border-box;
+ width: 248px;
+ padding: 6px 12px;
+ & + & {
+ border-left: 1px solid $border-color;
+ }
+}
+
+.#{$namespace}-calendar-header {
+ box-sizing: border-box;
+ height: 34px;
+ line-height: 34px;
+ text-align: center;
+ overflow: hidden;
+}
+
+.#{$namespace}-btn-icon-left,
+.#{$namespace}-btn-icon-double-left {
+ float: left;
+}
+.#{$namespace}-btn-icon-right,
+.#{$namespace}-btn-icon-double-right {
+ float: right;
+}
+
+.#{$namespace}-calendar-header-label {
+ font-size: 14px;
+}
+
+.#{$namespace}-calendar-decade-separator {
+ margin: 0 2px;
+ &:after {
+ content: '~';
+ }
+}
+
+.#{$namespace}-calendar-content {
+ position: relative;
+ height: 224px;
+ box-sizing: border-box;
+ .cell {
+ cursor: pointer;
+ &:hover {
+ color: $calendar-hover-color;
+ background-color: $calendar-hover-background-color;
+ }
+ &.active {
+ color: $calendar-active-color;
+ background-color: $calendar-active-background-color;
+ }
+ &.in-range {
+ color: $calendar-in-range-color;
+ background-color: $calendar-in-range-background-color;
+ }
+ &.disabled {
+ cursor: not-allowed;
+ color: $disabled-color;
+ background-color: $disabled-background-color;
+ }
+ }
+}
+
+.#{$namespace}-calendar-week-mode {
+ .#{$namespace}-date-row {
+ cursor: pointer;
+ &:hover {
+ background-color: $calendar-hover-background-color;
+ }
+ &.#{$namespace}-active-week {
+ background-color: $calendar-in-range-background-color;
+ }
+ .cell {
+ &:hover {
+ color: inherit;
+ background-color: transparent;
+ }
+ &.active {
+ color: inherit;
+ background-color: transparent;
+ }
+ }
+ }
+}
+
+.#{$namespace}-week-number {
+ opacity: 0.5;
+}
+
+.#{$namespace}-table {
+ table-layout: fixed;
+ border-collapse: separate;
+ border-spacing: 0;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ text-align: center;
+ vertical-align: middle;
+ th {
+ padding: 0;
+ font-weight: 500;
+ }
+ td {
+ padding: 0;
+ }
+}
+
+.#{$namespace}-table-date {
+ td,
+ th {
+ height: 32px;
+ font-size: 12px;
+ }
+
+ .today {
+ color: $today-color;
+ }
+ .cell.not-current-month {
+ color: #ccc;
+ }
+}
+
+.#{$namespace}-time {
+ flex: 1;
+ width: 224px;
+ background: #fff;
+ & + & {
+ border-left: 1px solid $border-color;
+ }
+}
+.#{$namespace}-calendar-time {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.#{$namespace}-time-header {
+ @extend .#{$namespace}-calendar-header;
+ border-bottom: 1px solid $border-color;
+}
+
+.#{$namespace}-time-content {
+ height: 224px;
+ box-sizing: border-box;
+ overflow: hidden;
+}
+
+.#{$namespace}-time-columns {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.#{$namespace}-time-column {
+ flex: 1;
+ position: relative;
+ border-left: 1px solid $border-color;
+ text-align: center;
+
+ &:first-child {
+ border-left: 0;
+ }
+ .#{$namespace}-time-list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ &::after {
+ content: '';
+ display: block;
+ height: 32 * 6px;
+ }
+ }
+ .#{$namespace}-time-item {
+ cursor: pointer;
+ font-size: 12px;
+ height: 32px;
+ line-height: 32px;
+ &:hover {
+ color: $time-hover-color;
+ background-color: $time-hover-background-color;
+ }
+ &.active {
+ color: $time-active-color;
+ background-color: $time-active-background-color;
+ font-weight: 700;
+ }
+ &.disabled {
+ cursor: not-allowed;
+ color: $disabled-color;
+ background-color: $disabled-background-color;
+ }
+ }
+}
+
+.#{$namespace}-time-option {
+ cursor: pointer;
+ padding: 8px 10px;
+ font-size: 14px;
+ line-height: 20px;
+ &:hover {
+ color: $time-hover-color;
+ background-color: $time-hover-background-color;
+ }
+ &.active {
+ color: $time-active-color;
+ background-color: $time-active-background-color;
+ font-weight: 700;
+ }
+ &.disabled {
+ cursor: not-allowed;
+ color: $disabled-color;
+ background-color: $disabled-background-color;
+ }
+}
diff --git a/src/style/scrollbar.scss b/src/style/scrollbar.scss
new file mode 100644
index 0000000..101aeeb
--- /dev/null
+++ b/src/style/scrollbar.scss
@@ -0,0 +1,37 @@
+@import './var.scss';
+
+.#{$namespace}-scrollbar {
+ height: 100%;
+ &:hover {
+ .#{$namespace}-scrollbar-track {
+ opacity: 1;
+ }
+ }
+}
+
+.#{$namespace}-scrollbar-wrap {
+ height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.#{$namespace}-scrollbar-track {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ bottom: 2px;
+ width: 6px;
+ z-index: 1;
+ border-radius: 4px;
+ opacity: 0;
+ transition: opacity 0.24s ease-out;
+ .#{$namespace}-scrollbar-thumb {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ cursor: pointer;
+ border-radius: inherit;
+ background-color: rgba(144, 147, 153, 0.3);
+ transition: background-color 0.3s;
+ }
+}
diff --git a/src/style/var.scss b/src/style/var.scss
new file mode 100644
index 0000000..64ad837
--- /dev/null
+++ b/src/style/var.scss
@@ -0,0 +1,35 @@
+$namespace: 'mx' !default;
+
+$default-color: #73879c !default;
+$primary-color: #1284e7 !default;
+
+$today-color: mix(#fff, $primary-color, 10%) !default;
+
+$popup-z-index: 2001 !default;
+
+$input-border-color: #ccc !default;
+$input-color: #555 !default;
+$input-hover-border-color: #409aff !default;
+
+$disabled-color: #ccc !default;
+$disabled-background-color: #f3f3f3 !default;
+
+$border-color: #e8e8e8 !default;
+
+$calendar-active-color: #fff !default;
+$calendar-active-background-color: $primary-color !default;
+
+$calendar-hover-color: $default-color !default;
+$calendar-hover-background-color: mix(#fff, $calendar-active-background-color, 95%) !default;
+
+$calendar-in-range-color: $default-color !default;
+$calendar-in-range-background-color: mix(#fff, $calendar-active-background-color, 85%) !default;
+
+$time-active-color: $primary-color !default;
+$time-active-background-color: transparent !default;
+
+$time-hover-color: $default-color !default;
+$time-hover-background-color: mix(#fff, $calendar-active-background-color, 95%) !default;
+
+$input-border-radius: 4px !default;
+$sidebar-margin-left: 100px !default;
diff --git a/src/time/list-columns.vue b/src/time/list-columns.vue
new file mode 100644
index 0000000..32224c3
--- /dev/null
+++ b/src/time/list-columns.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
diff --git a/src/time/list-options.vue b/src/time/list-options.vue
new file mode 100644
index 0000000..c5b1062
--- /dev/null
+++ b/src/time/list-options.vue
@@ -0,0 +1,118 @@
+
+
+
+ {{ item.text }}
+
+
+
+
+
diff --git a/src/time/time-panel.vue b/src/time/time-panel.vue
new file mode 100644
index 0000000..11d5e13
--- /dev/null
+++ b/src/time/time-panel.vue
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/time/time-range.js b/src/time/time-range.js
new file mode 100644
index 0000000..9aabf65
--- /dev/null
+++ b/src/time/time-range.js
@@ -0,0 +1,92 @@
+import TimePanel from './time-panel';
+import { isValidRangeDate } from '../util/date';
+
+export default {
+ name: 'TimeRange',
+ inject: {
+ prefixClass: {
+ default: 'mx',
+ },
+ },
+ emits: ['select'],
+ props: {
+ ...TimePanel.props,
+ },
+ data() {
+ return {
+ startValue: new Date(NaN),
+ endValue: new Date(NaN),
+ };
+ },
+ watch: {
+ value: {
+ immediate: true,
+ handler() {
+ if (isValidRangeDate(this.value)) {
+ const [startValue, endValue] = this.value;
+ this.startValue = startValue;
+ this.endValue = endValue;
+ } else {
+ this.startValue = new Date(NaN);
+ this.endValue = new Date(NaN);
+ }
+ },
+ },
+ },
+ methods: {
+ emitChange(type, index) {
+ const date = [this.startValue, this.endValue];
+ this.$emit('select', date, type === 'time' ? 'time-range' : type, index);
+ },
+ handleSelectStart(date, type) {
+ this.startValue = date;
+ // check the NaN
+ if (!(this.endValue.getTime() >= date.getTime())) {
+ this.endValue = date;
+ }
+ this.emitChange(type, 0);
+ },
+ handleSelectEnd(date, type) {
+ // check the NaN
+ this.endValue = date;
+ if (!(this.startValue.getTime() <= date.getTime())) {
+ this.startValue = date;
+ }
+ this.emitChange(type, 1);
+ },
+ disabledStartTime(date) {
+ return this.disabledTime(date, 0);
+ },
+ disabledEndTime(date) {
+ return date.getTime() < this.startValue.getTime() || this.disabledTime(date, 1);
+ },
+ },
+ render() {
+ const defaultValues = Array.isArray(this.defaultValue)
+ ? this.defaultValue
+ : [this.defaultValue, this.defaultValue];
+
+ const { prefixClass } = this;
+
+ return (
+
+
+
+
+ );
+ },
+};
diff --git a/src/util/base.js b/src/util/base.js
new file mode 100644
index 0000000..f87ef33
--- /dev/null
+++ b/src/util/base.js
@@ -0,0 +1,68 @@
+/**
+ * chunk the array
+ * @param {Array} arr
+ * @param {Number} size
+ */
+export function chunk(arr, size) {
+ if (!Array.isArray(arr)) {
+ return [];
+ }
+ const result = [];
+ const len = arr.length;
+ let i = 0;
+ size = size || len;
+ while (i < len) {
+ result.push(arr.slice(i, (i += size)));
+ }
+ return result;
+}
+
+/**
+ * isObject
+ * @param {*} obj
+ * @returns {Boolean}
+ */
+export function isObject(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]';
+}
+
+/**
+ * pick object
+ * @param {Object} obj
+ * @param {Array|String} props
+ */
+export function pick(obj, props) {
+ if (!isObject(obj)) return {};
+ if (!Array.isArray(props)) {
+ props = [props];
+ }
+ const res = {};
+ props.forEach(prop => {
+ if (prop in obj) {
+ res[prop] = obj[prop];
+ }
+ });
+ return res;
+}
+
+/**
+ * deep merge two object without merging array
+ * @param {object} target
+ * @param {object} source
+ */
+export function mergeDeep(target, source) {
+ if (!isObject(target)) {
+ return {};
+ }
+ let result = target;
+ if (isObject(source)) {
+ Object.keys(source).forEach(key => {
+ let value = source[key];
+ if (isObject(value) && isObject(target[key])) {
+ value = mergeDeep(target[key], value);
+ }
+ result = { ...result, [key]: value };
+ });
+ }
+ return result;
+}
diff --git a/src/util/date.js b/src/util/date.js
new file mode 100644
index 0000000..b32001a
--- /dev/null
+++ b/src/util/date.js
@@ -0,0 +1,93 @@
+// new Date(10, 0, 1) The year from 0 to 99 will be incremented by 1900 automatically.
+export function createDate(y, M = 0, d = 1, h = 0, m = 0, s = 0, ms = 0) {
+ const date = new Date(y, M, d, h, m, s, ms);
+ if (y < 100 && y >= 0) {
+ date.setFullYear(y);
+ }
+ return date;
+}
+
+export function isValidDate(date) {
+ return date instanceof Date && !isNaN(date);
+}
+
+export function isValidRangeDate(date) {
+ return Array.isArray(date) && date.length === 2 && date.every(isValidDate) && date[0] <= date[1];
+}
+
+export function isValidDates(dates) {
+ return Array.isArray(dates) && dates.every(isValidDate);
+}
+
+export function getValidDate(value, ...backup) {
+ const date = new Date(value);
+ if (isValidDate(date)) {
+ return date;
+ }
+ if (backup.length) {
+ return getValidDate(...backup);
+ }
+ return new Date();
+}
+
+export function startOfYear(value) {
+ const date = new Date(value);
+ date.setMonth(0, 1);
+ date.setHours(0, 0, 0, 0);
+ return date;
+}
+
+export function startOfMonth(value) {
+ const date = new Date(value);
+ date.setDate(1);
+ date.setHours(0, 0, 0, 0);
+ return date;
+}
+
+export function startOfDay(value) {
+ const date = new Date(value);
+ date.setHours(0, 0, 0, 0);
+ return date;
+}
+
+export function getCalendar({ firstDayOfWeek, year, month }) {
+ const arr = [];
+ // change to the last day of the last month
+ const calendar = createDate(year, month, 0);
+ const lastDayInLastMonth = calendar.getDate();
+ // getDay() 0 is Sunday, 1 is Monday
+ const firstDayInLastMonth = lastDayInLastMonth - ((calendar.getDay() + 7 - firstDayOfWeek) % 7);
+ for (let i = firstDayInLastMonth; i <= lastDayInLastMonth; i++) {
+ arr.push(createDate(year, month, i - lastDayInLastMonth));
+ }
+ // change to the last day of the current month
+ calendar.setMonth(month + 1, 0);
+ const lastDayInCurrentMonth = calendar.getDate();
+ for (let i = 1; i <= lastDayInCurrentMonth; i++) {
+ arr.push(createDate(year, month, i));
+ }
+
+ const lastMonthLength = lastDayInLastMonth - firstDayInLastMonth + 1;
+ const nextMonthLength = 6 * 7 - lastMonthLength - lastDayInCurrentMonth;
+ for (let i = 1; i <= nextMonthLength; i++) {
+ arr.push(createDate(year, month, lastDayInCurrentMonth + i));
+ }
+ return arr;
+}
+
+export function setMonth(dirtyDate, dirtyMonth) {
+ const date = new Date(dirtyDate);
+ const month = Number(dirtyMonth);
+ const year = date.getFullYear();
+ const daysInMonth = createDate(year, month + 1, 0).getDate();
+ const day = date.getDate();
+ date.setMonth(month, Math.min(day, daysInMonth));
+ return date;
+}
+
+export function assignTime(target, source) {
+ const date = new Date(target);
+ const time = new Date(source);
+ date.setHours(time.getHours(), time.getMinutes(), time.getSeconds());
+ return date;
+}
diff --git a/src/util/dom.js b/src/util/dom.js
new file mode 100644
index 0000000..195aeff
--- /dev/null
+++ b/src/util/dom.js
@@ -0,0 +1,70 @@
+/**
+ * get the hidden element width, height
+ * @param {HTMLElement} element dom
+ */
+export function getPopupElementSize(element) {
+ const originalDisplay = element.style.display;
+ const originalVisibility = element.style.visibility;
+ element.style.display = 'block';
+ element.style.visibility = 'hidden';
+ const styles = window.getComputedStyle(element);
+ const width =
+ element.offsetWidth + parseInt(styles.marginLeft, 10) + parseInt(styles.marginRight, 10);
+ const height =
+ element.offsetHeight + parseInt(styles.marginTop, 10) + parseInt(styles.marginBottom, 10);
+ element.style.display = originalDisplay;
+ element.style.visibility = originalVisibility;
+ return { width, height };
+}
+
+/**
+ * get the popup position
+ * @param {HTMLElement} el relative element
+ * @param {Number} targetWidth target element's width
+ * @param {Number} targetHeight target element's height
+ * @param {Boolean} fixed
+ */
+export function getRelativePosition(el, targetWidth, targetHeight, fixed) {
+ let left = 0;
+ let top = 0;
+ let offsetX = 0;
+ let offsetY = 0;
+ const relativeRect = el.getBoundingClientRect();
+ const dw = document.documentElement.clientWidth;
+ const dh = document.documentElement.clientHeight;
+ if (fixed) {
+ offsetX = window.pageXOffset + relativeRect.left;
+ offsetY = window.pageYOffset + relativeRect.top;
+ }
+ if (dw - relativeRect.left < targetWidth && relativeRect.right < targetWidth) {
+ left = offsetX - relativeRect.left + 1;
+ } else if (relativeRect.left + relativeRect.width / 2 <= dw / 2) {
+ left = offsetX;
+ } else {
+ left = offsetX + relativeRect.width - targetWidth;
+ }
+ if (relativeRect.top <= targetHeight && dh - relativeRect.bottom <= targetHeight) {
+ top = offsetY + dh - relativeRect.top - targetHeight;
+ } else if (relativeRect.top + relativeRect.height / 2 <= dh / 2) {
+ top = offsetY + relativeRect.height;
+ } else {
+ top = offsetY - targetHeight;
+ }
+ return { left: `${left}px`, top: `${top}px` };
+}
+
+export function getScrollParent(node, until = document.body) {
+ if (!node || node === until) {
+ return null;
+ }
+
+ const style = (value, prop) => getComputedStyle(value, null).getPropertyValue(prop);
+
+ const regex = /(auto|scroll)/;
+
+ const scroll = regex.test(
+ style(node, 'overflow') + style(node, 'overflow-y') + style(node, 'overflow-x')
+ );
+
+ return scroll ? node : getScrollParent(node.parentNode, until);
+}
diff --git a/src/util/scrollbar-width.js b/src/util/scrollbar-width.js
new file mode 100644
index 0000000..a6bc786
--- /dev/null
+++ b/src/util/scrollbar-width.js
@@ -0,0 +1,23 @@
+let scrollBarWidth;
+
+export default function() {
+ if (typeof window === 'undefined') return 0;
+ if (scrollBarWidth !== undefined) return scrollBarWidth;
+
+ const outer = document.createElement('div');
+ outer.style.visibility = 'hidden';
+ outer.style.overflow = 'scroll';
+ outer.style.width = '100px';
+ outer.style.position = 'absolute';
+ outer.style.top = '-9999px';
+ document.body.appendChild(outer);
+
+ const inner = document.createElement('div');
+ inner.style.width = '100%';
+ outer.appendChild(inner);
+
+ scrollBarWidth = outer.offsetWidth - inner.offsetWidth;
+ outer.parentNode.removeChild(outer);
+
+ return scrollBarWidth;
+}
diff --git a/src/util/throttle.js b/src/util/throttle.js
new file mode 100644
index 0000000..a1b586c
--- /dev/null
+++ b/src/util/throttle.js
@@ -0,0 +1,12 @@
+/* istanbul ignore file */
+export function rafThrottle(fn) {
+ let isRunning = false;
+ return function fnBinfRaf(...args) {
+ if (isRunning) return;
+ isRunning = true;
+ requestAnimationFrame(() => {
+ isRunning = false;
+ fn.apply(this, args);
+ });
+ };
+}