@ecwenze1/vue-datepicker
Version:
A clean datepicker made with VueJs
482 lines (457 loc) • 15.7 kB
JavaScript
import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
// Styles
import './VDPickerAgenda.scss';
// Mixins
import colorable from '../../../mixins/colorable';
import Overlayable from '../../../mixins/overlayable';
import Localable from '../../../mixins/localable';
// Components
import VDPickerControls from '../VDPickerControls';
import VDPickerHeader from '../VDPickerHeader';
import VDPickerPresets from '../VDPickerPresets';
import VDPickerTableDate from '../VDPickerTableDate';
import VDPickerValidate from '../VDPickerValidate';
import VDPickerMonths from '../VDPickerMonths';
import VDPickerQuarters from '../VDPickerQuarters';
import VDPickerYears from '../VDPickerYears';
import VDIcon from '../../VDIcon';
// Functions
import PickerDate from '../utils/PickerDate';
import {
generateDates,
convertQuarterToMonth,
generateDate,
generateDateWithYearAndMonth,
generateMonthAndYear,
isAfterDate,
isBeforeDate,
isDateAfter,
} from '../utils/helpers';
// Constants
import { DATEPICKER_MODES } from '../../../constants';
// Helpers
import mixins from '../../../utils/mixins';
const baseMixins = mixins(
colorable,
Overlayable,
Localable,
);
export default baseMixins.extend({
name: 'VDPickerAgenda',
props: {
allowedDates: { type: Function },
buttonCancel: { type: String },
buttonValidate: { type: String },
color: { type: String },
date: { type: [Date, Object] },
headerFormat: { type: String },
fullscreen: { type: Boolean, default: false },
maxDate: { type: [String, Number, Date] },
minDate: { type: [String, Number, Date] },
name: { type: String },
noHeader: { type: Boolean, default: false },
range: { type: Boolean, default: false },
rangeHeaderText: { type: String, default: String },
rangePresets: { type: Array, default: undefined },
rtl: { type: Boolean, default: false },
type: { type: String, default: 'date' },
validate: { type: Boolean, default: false },
value: { type: Boolean, default: false },
visibleYearsNumber: { type: Number },
highlightDate: { type: [Date, Object] },
},
data: () => ({
height: 'auto',
// Displayed year/month
pickerDate: undefined,
// mutableDate stores selected date
mutableDate: undefined,
// Mode switch between date / month / quarter / year table
mode: DATEPICKER_MODES.date,
transitionDaysName: 'slide-h-next',
transitionLabelName: 'slide-v-next',
}),
computed: {
classes () {
return {
'vd-picker--rtl': this.rtl,
'vd-picker--bottomsheet': this.fullscreen,
'vd-picker--no-header': this.noHeader,
'vd-picker--validate': this.validate,
'vd-picker--range': this.range,
'vd-picker--range-selecting': this.range && !this.isRangeSelected,
};
},
isRangeSelected () {
if (!this.range) return false;
return typeof this.mutableDate === 'object' &&
Object.values(this.mutableDate).every(date => Boolean(date));
},
formatters () {
return generateDates({
headerFormat: this.headerFormat,
locale: this.locale,
maxDate: this.maxDate,
minDate: this.minDate,
mutableDate: this.mutableDate,
range: this.range,
rangeHeaderText: this.rangeHeaderText,
});
},
},
created () {
this.initAgenda();
},
beforeDestroy () {
clearAllBodyScrollLocks();
},
watch: {
value: 'initAgenda',
// When, date change (after being visibled),
// should update pickerDate & mutableDate
date: 'initDatesForPicker',
// When type change (after being visibled),
// should update update active mode
type: 'updateMode',
// When fullscreen is visibled => lock body scroll
// When fullscreen is hidden => unlock body scroll
fullscreen: {
handler (show) {
this.$nextTick(() => {
const targetElement = this.$refs.body;
if (show) {
disableBodyScroll(targetElement);
this.genOverlay();
} else {
this.removeOverlay(false);
enableBodyScroll(targetElement);
}
});
},
immediate: true,
},
// When fullscreen is visibled and visibled mode is 'year'
// => should keep scroll disabled, but should allow scroll into years list
mode (mode) {
if (mode !== 'year' || !this.fullscreen) return;
enableBodyScroll(this.$refs.body);
this.$nextTick(() => {
disableBodyScroll(this.$el.querySelector('.vd-picker__years'));
});
},
},
methods: {
initAgenda () {
this.initDatesForPicker(this.date);
this.updateMode(this.type);
},
updateTransitions (direction) {
this.transitionDaysName = `slide-h-${direction}`;
this.transitionLabelName = `slide-v-${direction}`;
},
// ------------------------------
// Handle dates change
// ------------------------------
reOrderSelectedDate (newDate) {
if (!this.mutableDate) return;
// Should update mutableDate if
// -> hovered day is before or after current selected date
if (isBeforeDate(newDate, this.mutableDate.start)) {
this.mutableDate = { start: undefined, end: this.mutableDate.start };
} else if (isAfterDate(newDate, this.mutableDate.end)) {
this.mutableDate = { start: this.mutableDate.end, end: undefined };
}
},
selectDate (day) {
if (this.range) {
// If rangeIsSelected or no dates selected => should reset, and select start
if (this.isRangeSelected || (!this.mutableDate.start && !this.mutableDate.end)) {
this.mutableDate = { start: day.clone(), end: undefined };
return;
}
// else, should update missing range (start or end)
this.reOrderSelectedDate(day);
this.emitSelectedDate({
...this.mutableDate,
...(this.mutableDate.start && { end: day.clone() }),
...(this.mutableDate.end && { start: day.clone() }),
});
return;
}
const direction = isDateAfter(day, this.mutableDate) ? 'next' : 'prev';
this.updateTransitions(direction);
this.emitSelectedDate(day.clone());
},
emitSelectedDate (date) {
// If range, when a preset is selected,
// should transition to end date month
if (this.range) {
this.pickerDate = new PickerDate(date.end.month(), date.end.year(), this.currentLocale);
}
this.mutableDate = date;
this.$emit('selectDate', this.mutableDate);
},
initDatesForPicker (date) {
let newDate = generateDate({
date: this.range ? (date.end || date.start) : date,
locale: this.currentLocale,
});
// If today's date is after maxDate, we should show maxDate month
if (isAfterDate(newDate, this.maxDate)) {
newDate = generateDate({ date: this.maxDate, locale: this.currentLocale });
}
if (this.range) {
this.pickerDate = new PickerDate(newDate.month(), newDate.year(), this.currentLocale);
this.mutableDate = date;
return;
}
// When type is quarter && date is not defined -> newDate equals today's date
// So we need to convert it to quarter month -> for example : 2020-11-20 => 2020-4
if (date == null && this.type === 'quarter') {
newDate = newDate.month(newDate.quarter());
}
// When type is quarter && date is defined -> newDate will convert quarter date to month
// For example : 2020-2 => 2020-02-01 but quarter 2 should be 2020-04-01
if (date != null && this.type === 'quarter') {
newDate = newDate.month(convertQuarterToMonth(newDate.month()));
}
this.pickerDate = new PickerDate(newDate.month(), newDate.year(), this.currentLocale);
this.mutableDate = date && date.month(newDate.month()).clone();
},
changeMonth (direction) {
let month = this.pickerDate.month + (direction === 'prev' ? -1 : +1);
let year = this.pickerDate.year;
if (month > 11 || month < 0) {
year += (direction === 'prev' ? -1 : +1);
month = (direction === 'prev' ? 11 : 0);
}
this.updateTransitions(direction);
this.pickerDate = new PickerDate(month, year, this.currentLocale);
},
changeYear (direction) {
let year = this.pickerDate.year + (direction === 'prev' ? -1 : +1);
const month = this.pickerDate.month;
this.updateTransitions(direction);
this.pickerDate = new PickerDate(month, year, this.currentLocale);
},
// ------------------------------
// Handle Year/Month/Quarter
// ------------------------------
updateMode (mode) {
this.mode = mode;
},
updateSelectedYearMonth (value, mode) {
const { year, month } = generateMonthAndYear(value, this.pickerDate, mode);
this.pickerDate = new PickerDate(month, year, this.currentLocale);
// When mode is year, we'll show month/quarter (unless type is year)
if (mode === DATEPICKER_MODES.year && this.type !== 'year') {
const nextActiveMode = this.type === 'quarter' ? 'quarter' : 'month';
return this.updateMode(nextActiveMode);
}
// When type is month|quarter|year
// Should emit date selected if it's not type date
if (this.type !== 'date') {
const newDate = generateDateWithYearAndMonth({
year: this.pickerDate.year,
month: this.pickerDate.month,
locale: this.currentLocale,
});
this.selectDate(newDate);
return;
}
this.updateMode('date');
},
// ------------------------------
// Generate Template
// ------------------------------
genTitle () {
const title = this.$createElement('p', this.name);
const icon = this.$createElement(VDIcon, {
on: {
click: () => this.$emit('close'),
},
}, ['close']);
return this.$createElement('div', {
staticClass: 'vd-picker__title',
}, [
title,
this.$createElement('div', {
staticClass: 'vd-picker__title-close',
}, [icon]),
]);
},
genHeader () {
return this.$createElement(VDPickerHeader, {
props: {
...this.formatters,
color: this.color,
mode: this.yearMonthMode,
mutableDate: this.mutableDate,
range: this.range,
transitionName: this.transitionLabelName,
type: this.type,
},
on: {
'update-mode': this.updateMode,
},
});
},
genPresets () {
return this.$createElement(VDPickerPresets, {
props: {
rangePresets: this.rangePresets,
mutableDate: this.mutableDate,
minDate: this.minDate,
maxDate: this.maxDate,
color: this.color,
locale: this.locale,
},
on: {
'update-range': this.emitSelectedDate,
},
});
},
genBody () {
const children = [
this.mode !== DATEPICKER_MODES.year && this.genControls(),
this.mode === DATEPICKER_MODES.date && this.genTableDate(),
this.mode === DATEPICKER_MODES.month && this.genMonths(),
this.mode === DATEPICKER_MODES.quarter && this.genQuarters(),
this.mode === DATEPICKER_MODES.year && this.genYears(),
];
return this.$createElement('div', {
staticClass: 'vd-picker__body',
ref: 'body',
}, children);
},
genControls () {
return this.$createElement(VDPickerControls, {
props: {
pickerDate: this.pickerDate,
transitionName: this.transitionLabelName,
color: this.color,
min: this.minDate,
max: this.maxDate,
mode: this.mode,
},
on: {
'on-navigation-click': this.mode === DATEPICKER_MODES.date ? this.changeMonth : this.changeYear,
'update-mode': this.updateMode,
},
});
},
genTableDate () {
return this.$createElement(VDPickerTableDate, {
props: {
allowedDates: this.allowedDates,
color: this.color,
pickerDate: this.pickerDate,
isRangeSelected: this.isRangeSelected,
locale: this.currentLocale,
maxDate: this.maxDate,
minDate: this.minDate,
mutableDate: this.mutableDate,
highlightDate: this.highlightDate,
range: this.range,
transitionName: this.transitionDaysName,
},
on: {
'update-month': this.changeMonth,
'update-hovered-day': this.reOrderSelectedDate,
'select-date': this.selectDate,
},
});
},
genMonths () {
const { minMonth, maxMonth } = this.formatters;
return this.$createElement(VDPickerMonths, {
props: {
active: this.mode === DATEPICKER_MODES.month,
allowedDates: this.type === DATEPICKER_MODES.month ? this.allowedDates : undefined,
color: this.color,
locale: this.currentLocale,
max: maxMonth,
min: minMonth,
mutableDate: this.mutableDate,
pickerDate: this.pickerDate,
range: this.range,
transitionName: this.transitionDaysName,
},
on: {
input: this.updateSelectedYearMonth,
},
});
},
genQuarters () {
const { minMonth, maxMonth } = this.formatters;
return this.$createElement(VDPickerQuarters, {
props: {
active: this.mode === DATEPICKER_MODES.quarter,
allowedDates: this.type === DATEPICKER_MODES.quarter ? this.allowedDates : undefined,
color: this.color,
locale: this.currentLocale,
max: maxMonth,
min: minMonth,
mutableDate: this.mutableDate,
pickerDate: this.pickerDate,
transitionName: this.transitionDaysName,
},
on: {
input: this.updateSelectedYearMonth,
},
});
},
genYears () {
const { minYear, maxYear } = this.formatters;
return this.$createElement(VDPickerYears, {
props: {
active: this.mode === DATEPICKER_MODES.year,
allowedDates: this.type === DATEPICKER_MODES.year ? this.allowedDates : undefined,
color: this.color,
max: maxYear,
min: minYear,
mutableDate: this.mutableDate,
pickerDate: this.pickerDate,
range: this.range,
visibleYearsNumber: this.visibleYearsNumber,
},
on: {
input: this.updateSelectedYearMonth,
},
});
},
genValidate () {
return this.$createElement(VDPickerValidate, {
props: {
buttonValidate: this.buttonValidate,
buttonCancel: this.buttonCancel,
color: this.color,
mutableDate: this.mutableDate,
range: this.range,
},
on: {
cancel: () => this.$emit('close'),
validate: () => this.$emit('validateDate'),
},
});
},
},
render (h) {
return h('div', {
staticClass: 'vd-picker',
class: this.classes,
ref: 'datepicker',
}, [
// -- Title should be visible only on fullscreen mode
this.fullscreen && this.genTitle(),
// -- Hide header if no-header sets
!this.noHeader && this.genHeader(),
// -- Presets available only for range
this.range && this.genPresets(),
// -- Body
this.genBody(),
// -- validate available only if sets
this.validate && this.genValidate(),
]);
},
});