// Utils import { raf } from '../utils/dom/raf'; import { isDate } from '../utils/validate/date'; import { getScrollTop } from '../utils/dom/scroll'; import { t, bem, copyDate, copyDates, getNextDay, compareDay, calcDateNum, compareMonth, createComponent, getDayByOffset } from './utils'; // Components import Popup from '../popup'; import Button from '../button'; import Toast from '../toast'; import Month from './components/Month'; import Header from './components/Header'; export default createComponent({ props: { title: String, color: String, value: Boolean, readonly: Boolean, formatter: Function, rowHeight: [Number, String], confirmText: String, rangePrompt: String, defaultDate: [Date, Array], getContainer: [String, Function], allowSameDay: Boolean, confirmDisabledText: String, type: { type: String, default: 'single' }, round: { type: Boolean, default: true }, position: { type: String, default: 'bottom' }, poppable: { type: Boolean, default: true }, maxRange: { type: [Number, String], default: null }, lazyRender: { type: Boolean, default: true }, showMark: { type: Boolean, default: true }, showTitle: { type: Boolean, default: true }, showConfirm: { type: Boolean, default: true }, showSubtitle: { type: Boolean, default: true }, closeOnPopstate: { type: Boolean, default: true }, closeOnClickOverlay: { type: Boolean, default: true }, safeAreaInsetBottom: { type: Boolean, default: true }, minDate: { type: Date, validator: isDate, default: function _default() { return new Date(); } }, maxDate: { type: Date, validator: isDate, default: function _default() { var now = new Date(); return new Date(now.getFullYear(), now.getMonth() + 6, now.getDate()); } }, firstDayOfWeek: { type: [Number, String], default: 0, validator: function validator(val) { return val >= 0 && val <= 6; } } }, inject: { vanPopup: { default: null } }, data: function data() { return { subtitle: '', currentDate: this.getInitialDate() }; }, computed: { months: function months() { var months = []; var cursor = new Date(this.minDate); cursor.setDate(1); do { months.push(new Date(cursor)); cursor.setMonth(cursor.getMonth() + 1); } while (compareMonth(cursor, this.maxDate) !== 1); return months; }, buttonDisabled: function buttonDisabled() { var type = this.type, currentDate = this.currentDate; if (currentDate) { if (type === 'range') { return !currentDate[0] || !currentDate[1]; } if (type === 'multiple') { return !currentDate.length; } } return !currentDate; }, dayOffset: function dayOffset() { return this.firstDayOfWeek ? this.firstDayOfWeek % 7 : 0; } }, watch: { value: 'init', type: function type() { this.reset(); }, defaultDate: function defaultDate(val) { this.currentDate = val; this.scrollIntoView(); } }, mounted: function mounted() { this.init(); // https://github.com/vant-ui/vant/issues/9845 if (!this.poppable) { var _this$vanPopup; (_this$vanPopup = this.vanPopup) == null ? void 0 : _this$vanPopup.$on('opened', this.onScroll); } }, /* istanbul ignore next */ activated: function activated() { this.init(); }, methods: { // @exposed-api reset: function reset(date) { if (date === void 0) { date = this.getInitialDate(); } this.currentDate = date; this.scrollIntoView(); }, init: function init() { var _this = this; if (this.poppable && !this.value) { return; } this.$nextTick(function () { // add Math.floor to avoid decimal height issues // https://github.com/vant-ui/vant/issues/5640 _this.bodyHeight = Math.floor(_this.$refs.body.getBoundingClientRect().height); _this.onScroll(); _this.scrollIntoView(); }); }, // @exposed-api scrollToDate: function scrollToDate(targetDate) { var _this2 = this; raf(function () { var displayed = _this2.value || !_this2.poppable; /* istanbul ignore if */ if (!targetDate || !displayed) { return; } _this2.months.some(function (month, index) { if (compareMonth(month, targetDate) === 0) { var _this2$$refs = _this2.$refs, body = _this2$$refs.body, months = _this2$$refs.months; months[index].scrollIntoView(body); return true; } return false; }); _this2.onScroll(); }); }, // scroll to current month scrollIntoView: function scrollIntoView() { var currentDate = this.currentDate; if (currentDate) { var targetDate = this.type === 'single' ? currentDate : currentDate[0]; this.scrollToDate(targetDate); } }, getInitialDate: function getInitialDate() { var type = this.type, minDate = this.minDate, maxDate = this.maxDate, defaultDate = this.defaultDate; if (defaultDate === null) { return defaultDate; } var defaultVal = new Date(); if (compareDay(defaultVal, minDate) === -1) { defaultVal = minDate; } else if (compareDay(defaultVal, maxDate) === 1) { defaultVal = maxDate; } if (type === 'range') { var _ref = defaultDate || [], startDay = _ref[0], endDay = _ref[1]; return [startDay || defaultVal, endDay || getNextDay(defaultVal)]; } if (type === 'multiple') { return defaultDate || [defaultVal]; } return defaultDate || defaultVal; }, // calculate the position of the elements // and find the elements that needs to be rendered onScroll: function onScroll() { var _this$$refs = this.$refs, body = _this$$refs.body, months = _this$$refs.months; var top = getScrollTop(body); var bottom = top + this.bodyHeight; var heights = months.map(function (item) { return item.getHeight(); }); var heightSum = heights.reduce(function (a, b) { return a + b; }, 0); // iOS scroll bounce may exceed the range if (bottom > heightSum && top > 0) { return; } var height = 0; var currentMonth; var visibleRange = [-1, -1]; for (var i = 0; i < months.length; i++) { var visible = height <= bottom && height + heights[i] >= top; if (visible) { visibleRange[1] = i; if (!currentMonth) { currentMonth = months[i]; visibleRange[0] = i; } if (!months[i].showed) { months[i].showed = true; this.$emit('month-show', { date: months[i].date, title: months[i].title }); } } height += heights[i]; } months.forEach(function (month, index) { month.visible = index >= visibleRange[0] - 1 && index <= visibleRange[1] + 1; }); /* istanbul ignore else */ if (currentMonth) { this.subtitle = currentMonth.title; } }, onClickDay: function onClickDay(item) { if (this.readonly) { return; } var date = item.date; var type = this.type, currentDate = this.currentDate; if (type === 'range') { if (!currentDate) { this.select([date, null]); return; } var startDay = currentDate[0], endDay = currentDate[1]; if (startDay && !endDay) { var compareToStart = compareDay(date, startDay); if (compareToStart === 1) { this.select([startDay, date], true); } else if (compareToStart === -1) { this.select([date, null]); } else if (this.allowSameDay) { this.select([date, date], true); } } else { this.select([date, null]); } } else if (type === 'multiple') { if (!currentDate) { this.select([date]); return; } var selectedIndex; var selected = this.currentDate.some(function (dateItem, index) { var equal = compareDay(dateItem, date) === 0; if (equal) { selectedIndex = index; } return equal; }); if (selected) { var _currentDate$splice = currentDate.splice(selectedIndex, 1), unselectedDate = _currentDate$splice[0]; this.$emit('unselect', copyDate(unselectedDate)); } else if (this.maxRange && currentDate.length >= this.maxRange) { Toast(this.rangePrompt || t('rangePrompt', this.maxRange)); } else { this.select([].concat(currentDate, [date])); } } else { this.select(date, true); } }, togglePopup: function togglePopup(val) { this.$emit('input', val); }, select: function select(date, complete) { var _this3 = this; var emit = function emit(date) { _this3.currentDate = date; _this3.$emit('select', copyDates(_this3.currentDate)); }; if (complete && this.type === 'range') { var valid = this.checkRange(date); if (!valid) { // auto selected to max range if showConfirm if (this.showConfirm) { emit([date[0], getDayByOffset(date[0], this.maxRange - 1)]); } else { emit(date); } return; } } emit(date); if (complete && !this.showConfirm) { this.onConfirm(); } }, checkRange: function checkRange(date) { var maxRange = this.maxRange, rangePrompt = this.rangePrompt; if (maxRange && calcDateNum(date) > maxRange) { Toast(rangePrompt || t('rangePrompt', maxRange)); return false; } return true; }, onConfirm: function onConfirm() { this.$emit('confirm', copyDates(this.currentDate)); }, genMonth: function genMonth(date, index) { var h = this.$createElement; var showMonthTitle = index !== 0 || !this.showSubtitle; return h(Month, { "ref": "months", "refInFor": true, "attrs": { "date": date, "type": this.type, "color": this.color, "minDate": this.minDate, "maxDate": this.maxDate, "showMark": this.showMark, "formatter": this.formatter, "rowHeight": this.rowHeight, "lazyRender": this.lazyRender, "currentDate": this.currentDate, "showSubtitle": this.showSubtitle, "allowSameDay": this.allowSameDay, "showMonthTitle": showMonthTitle, "firstDayOfWeek": this.dayOffset }, "scopedSlots": { 'top-info': this.$scopedSlots['top-info'], 'bottom-info': this.$scopedSlots['bottom-info'] }, "on": { "click": this.onClickDay } }); }, genFooterContent: function genFooterContent() { var h = this.$createElement; var slot = this.slots('footer'); if (slot) { return slot; } if (this.showConfirm) { var text = this.buttonDisabled ? this.confirmDisabledText : this.confirmText; return h(Button, { "attrs": { "round": true, "block": true, "type": "danger", "color": this.color, "disabled": this.buttonDisabled, "nativeType": "button" }, "class": bem('confirm'), "on": { "click": this.onConfirm } }, [text || t('confirm')]); } }, genFooter: function genFooter() { var h = this.$createElement; return h("div", { "class": bem('footer', { unfit: !this.safeAreaInsetBottom }) }, [this.genFooterContent()]); }, genCalendar: function genCalendar() { var _this4 = this; var h = this.$createElement; return h("div", { "class": bem() }, [h(Header, { "attrs": { "title": this.title, "showTitle": this.showTitle, "subtitle": this.subtitle, "showSubtitle": this.showSubtitle, "firstDayOfWeek": this.dayOffset }, "scopedSlots": { title: function title() { return _this4.slots('title'); } } }), h("div", { "ref": "body", "class": bem('body'), "on": { "scroll": this.onScroll } }, [this.months.map(this.genMonth)]), this.genFooter()]); } }, render: function render() { var _this5 = this; var h = arguments[0]; if (this.poppable) { var _attrs; var createListener = function createListener(name) { return function () { return _this5.$emit(name); }; }; return h(Popup, { "attrs": (_attrs = { "round": true, "value": this.value }, _attrs["round"] = this.round, _attrs["position"] = this.position, _attrs["closeable"] = this.showTitle || this.showSubtitle, _attrs["getContainer"] = this.getContainer, _attrs["closeOnPopstate"] = this.closeOnPopstate, _attrs["closeOnClickOverlay"] = this.closeOnClickOverlay, _attrs), "class": bem('popup'), "on": { "input": this.togglePopup, "open": createListener('open'), "opened": createListener('opened'), "close": createListener('close'), "closed": createListener('closed') } }, [this.genCalendar()]); } return this.genCalendar(); } });