From fc50e26416feb1cbc3d07de23cd39bf6ba57eefc Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Thu, 24 Sep 2020 19:01:09 +0800 Subject: [PATCH] refactor(Calendar): refactor with composition api --- src/calendar/components/Month.js | 6 +- src/calendar/index.js | 534 +++++++++++++++---------------- 2 files changed, 269 insertions(+), 271 deletions(-) diff --git a/src/calendar/components/Month.js b/src/calendar/components/Month.js index 9e4c7050e..c76a58c08 100644 --- a/src/calendar/components/Month.js +++ b/src/calendar/components/Month.js @@ -253,7 +253,9 @@ export default createComponent({ watch( () => props.realRowHeight, () => { - height.value = useRect(monthRef).height; + nextTick(() => { + height.value = useRect(monthRef).height; + }); } ); @@ -269,9 +271,9 @@ export default createComponent({ }); useExpose({ - height, getDate, getTitle, + getHeight: () => height.value, setVisible, scrollIntoView, }); diff --git a/src/calendar/index.js b/src/calendar/index.js index 0fcaaa16f..35fc859ef 100644 --- a/src/calendar/index.js +++ b/src/calendar/index.js @@ -1,3 +1,5 @@ +import { ref, watch, reactive, computed, onMounted, onActivated } from 'vue'; + // Utils import { pick } from '../utils'; import { raf } from '../utils/dom/raf'; @@ -16,6 +18,11 @@ import { getDayByOffset, } from './utils'; +// Composition +import { useRefs } from '../composition/use-refs'; +import { useRect } from '../composition/use-rect'; +import { useExpose } from '../composition/use-expose'; + // Components import Popup from '../popup'; import Button from '../button'; @@ -111,123 +118,9 @@ export default createComponent({ emits: ['select', 'confirm', 'unselect', 'month-show', 'update:show'], - data() { - this.monthRefs = []; - return { - subtitle: '', - currentDate: this.getInitialDate(), - realRowHeight: 0, - }; - }, - - computed: { - months() { - const months = []; - const 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() { - const { type, currentDate } = this; - - if (currentDate) { - if (type === 'range') { - return !currentDate[0] || !currentDate[1]; - } - if (type === 'multiple') { - return !currentDate.length; - } - } - - return !currentDate; - }, - - dayOffset() { - return this.firstDayOfWeek ? this.firstDayOfWeek % 7 : 0; - }, - }, - - watch: { - show: 'init', - type: 'reset', - - defaultDate(val) { - this.currentDate = val; - this.scrollIntoView(); - }, - }, - - mounted() { - this.init(); - }, - - activated() { - this.init(); - }, - - methods: { - // @exposed-api - reset() { - this.currentDate = this.getInitialDate(); - this.scrollIntoView(); - }, - - init() { - if (this.poppable && !this.show) { - return; - } - - this.$nextTick(() => { - // add Math.floor to avoid decimal height issues - // https://github.com/youzan/vant/issues/5640 - this.bodyHeight = Math.floor( - this.$refs.body.getBoundingClientRect().height - ); - this.onScroll(); - this.scrollIntoView(); - }); - }, - - // scroll to current month - scrollIntoView() { - raf(() => { - const { currentDate } = this; - - if (!currentDate) { - return; - } - - const targetDate = - this.type === 'single' ? currentDate : currentDate[0]; - const displayed = this.show || !this.poppable; - - /* istanbul ignore if */ - if (!targetDate || !displayed) { - return; - } - - this.months.some((month, index) => { - if (compareMonth(month, targetDate) === 0) { - const { body } = this.$refs; - this.monthRefs[index].scrollIntoView(body); - return true; - } - - return false; - }); - }); - }, - - getInitialDate() { - const { type, minDate, maxDate, defaultDate } = this; + setup(props, { emit, slots }) { + const getInitialDate = () => { + const { type, minDate, maxDate, defaultDate } = props; if (defaultDate === null) { return defaultDate; @@ -251,18 +144,90 @@ export default createComponent({ } return defaultDate || defaultVal; - }, + }; + + let bodyHeight; + + const bodyRef = ref(); + + const state = reactive({ + subtitle: '', + currentDate: getInitialDate(), + realRowHeight: 0, + }); + + const [monthRefs, setMonthRefs] = useRefs(); + + const dayOffset = computed(() => + props.firstDayOfWeek ? props.firstDayOfWeek % 7 : 0 + ); + + const months = computed(() => { + const months = []; + const cursor = new Date(props.minDate); + + cursor.setDate(1); + + do { + months.push(new Date(cursor)); + cursor.setMonth(cursor.getMonth() + 1); + } while (compareMonth(cursor, props.maxDate) !== 1); + + return months; + }); + + const buttonDisabled = computed(() => { + const { currentDate } = state; + + if (currentDate) { + if (props.type === 'range') { + return !currentDate[0] || !currentDate[1]; + } + if (props.type === 'multiple') { + return !currentDate.length; + } + } + + return !currentDate; + }); + + // scroll to current month + const scrollIntoView = () => { + raf(() => { + const { currentDate } = state; + + if (!currentDate) { + return; + } + + const targetDate = + props.type === 'single' ? currentDate : currentDate[0]; + const displayed = props.show || !props.poppable; + + /* istanbul ignore if */ + if (!targetDate || !displayed) { + return; + } + + months.value.some((month, index) => { + if (compareMonth(month, targetDate) === 0) { + monthRefs.value[index].scrollIntoView(bodyRef.value); + return true; + } + + return false; + }); + }); + }; // calculate the position of the elements // and find the elements that needs to be rendered - onScroll() { - const { body } = this.$refs; - const { months, monthRefs } = this; - const top = getScrollTop(body); - const bottom = top + this.bodyHeight; + const onScroll = () => { + const top = getScrollTop(bodyRef.value); + const bottom = top + bodyHeight; - const heights = months.map( - (item, index) => monthRefs[index].height.value + const heights = months.value.map((item, index) => + monthRefs.value[index].getHeight() ); const heightSum = heights.reduce((a, b) => a + b, 0); @@ -275,46 +240,112 @@ export default createComponent({ let currentMonth; let visibleIndex; - for (let i = 0; i < months.length; i++) { + for (let i = 0; i < months.value.length; i++) { + const month = monthRefs.value[i]; const visible = height <= bottom && height + heights[i] >= top; if (visible && !currentMonth) { visibleIndex = i; - currentMonth = monthRefs[i]; + currentMonth = month; } - if (!monthRefs[i].visible && visible) { - this.$emit('month-show', { - date: monthRefs[i].getDate(), - title: monthRefs[i].getTitle(), + if (!month.visible && visible) { + emit('month-show', { + date: month.getDate(), + title: month.getTitle(), }); } height += heights[i]; } - months.forEach((month, index) => { + months.value.forEach((month, index) => { const visible = index >= visibleIndex - 1 && index <= visibleIndex + 1; - monthRefs[index].setVisible(visible); + monthRefs.value[index].setVisible(visible); }); /* istanbul ignore else */ if (currentMonth) { - this.subtitle = currentMonth.getTitle(); + state.subtitle = currentMonth.getTitle(); } - }, + }; - onClickDay(item) { - if (this.readonly) { + const init = () => { + if (props.poppable && !props.show) { + return; + } + + raf(() => { + // add Math.floor to avoid decimal height issues + // https://github.com/youzan/vant/issues/5640 + bodyHeight = Math.floor(useRect(bodyRef).height); + onScroll(); + scrollIntoView(); + }); + }; + + const reset = () => { + state.currentDate = getInitialDate(); + scrollIntoView(); + }; + + const checkRange = (date) => { + const { maxRange, rangePrompt } = props; + + if (maxRange && calcDateNum(date) > maxRange) { + Toast(rangePrompt || t('rangePrompt', maxRange)); + return false; + } + + return true; + }; + + const onConfirm = () => { + emit('confirm', copyDates(state.currentDate)); + }; + + const select = (date, complete) => { + const setCurrentDate = (date) => { + state.currentDate = date; + emit('select', copyDates(state.currentDate)); + }; + + if (complete && props.type === 'range') { + const valid = checkRange(date); + + if (!valid) { + // auto selected to max range if showConfirm + if (props.showConfirm) { + setCurrentDate([ + date[0], + getDayByOffset(date[0], props.maxRange - 1), + ]); + } else { + setCurrentDate(date); + } + return; + } + } + + setCurrentDate(date); + + if (complete && !props.showConfirm) { + onConfirm(); + } + }; + + const onClickDay = (item) => { + if (props.readonly) { return; } const { date } = item; - const { type, currentDate } = this; + const { type } = props; + const { currentDate } = state; if (type === 'range') { if (!currentDate) { - this.select([date, null]); + select([date, null]); return; } @@ -324,23 +355,23 @@ export default createComponent({ const compareToStart = compareDay(date, startDay); if (compareToStart === 1) { - this.select([startDay, date], true); + select([startDay, date], true); } else if (compareToStart === -1) { - this.select([date, null]); - } else if (this.allowSameDay) { - this.select([date, date], true); + select([date, null]); + } else if (props.allowSameDay) { + select([date, date], true); } } else { - this.select([date, null]); + select([date, null]); } } else if (type === 'multiple') { if (!currentDate) { - this.select([date]); + select([date]); return; } let selectedIndex; - const selected = this.currentDate.some((dateItem, index) => { + const selected = state.currentDate.some((dateItem, index) => { const equal = compareDay(dateItem, date) === 0; if (equal) { selectedIndex = index; @@ -350,78 +381,36 @@ export default createComponent({ if (selected) { const [unselectedDate] = currentDate.splice(selectedIndex, 1); - this.$emit('unselect', copyDate(unselectedDate)); - } else if (this.maxRange && currentDate.length >= this.maxRange) { - Toast(this.rangePrompt || t('rangePrompt', this.maxRange)); + emit('unselect', copyDate(unselectedDate)); + } else if (props.maxRange && currentDate.length >= props.maxRange) { + Toast(props.rangePrompt || t('rangePrompt', props.maxRange)); } else { - this.select([...currentDate, date]); + select([...currentDate, date]); } } else { - this.select(date, true); + select(date, true); } - }, + }; - togglePopup(val) { - this.$emit('update:show', val); - }, + const togglePopup = (val) => { + emit('update:show', val); + }; - select(date, complete) { - const emit = (date) => { - this.currentDate = date; - this.$emit('select', copyDates(this.currentDate)); - }; + const onUpdateHeight = (height) => { + state.realRowHeight = height; + }; - if (complete && this.type === 'range') { - const 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(date) { - const { maxRange, rangePrompt } = this; - - if (maxRange && calcDateNum(date) > maxRange) { - Toast(rangePrompt || t('rangePrompt', maxRange)); - return false; - } - - return true; - }, - - onConfirm() { - this.$emit('confirm', copyDates(this.currentDate)); - }, - - onUpdateHeight(height) { - this.realRowHeight = height; - }, - - genMonth(date, index) { - const showMonthTitle = index !== 0 || !this.showSubtitle; + const renderMonth = (date, index) => { + const showMonthTitle = index !== 0 || !props.showSubtitle; return ( { - this.monthRefs[index] = val; - }} + ref={setMonthRefs(index)} date={date} + currentDate={state.currentDate} + realRowHeight={state.realRowHeight} showMonthTitle={showMonthTitle} - firstDayOfWeek={this.dayOffset} - {...pick(this, [ + firstDayOfWeek={dayOffset.value} + {...pick(props, [ 'type', 'color', 'minDate', @@ -430,93 +419,100 @@ export default createComponent({ 'formatter', 'rowHeight', 'lazyRender', - 'currentDate', 'showSubtitle', 'allowSameDay', - 'realRowHeight', ])} - onClick={this.onClickDay} - onUpdate-height={this.onUpdateHeight} + onClick={onClickDay} + onUpdate-height={onUpdateHeight} /> ); - }, + }; - genFooterContent() { - if (this.$slots.footer) { - return this.$slots.footer(); + const renderFooterButton = () => { + if (slots.footer) { + return slots.footer(); } - if (this.showConfirm) { - const text = this.buttonDisabled - ? this.confirmDisabledText - : this.confirmText; + if (props.showConfirm) { + const text = buttonDisabled.value + ? props.confirmDisabledText + : props.confirmText; return ( ); } - }, + }; - genFooter() { - return ( -
- {this.genFooterContent()} + const renderFooter = () => ( +
+ {renderFooterButton()} +
+ ); + + const renderCalendar = () => ( +
+
+
+ {months.value.map(renderMonth)}
- ); - }, + {renderFooter()} +
+ ); - genCalendar() { - return ( -
-
-
- {this.months.map(this.genMonth)} -
- {this.genFooter()} -
- ); - }, - }, + watch(() => props.show, init); + watch(() => props.type, reset); + watch( + () => props.defaultDate, + (value) => { + state.currentDate = value; + scrollIntoView(); + } + ); - render() { - if (this.poppable) { - return ( - - {this.genCalendar()} - - ); - } + onMounted(init); + onActivated(init); - return this.genCalendar(); + useExpose({ reset }); + + return () => { + if (props.poppable) { + return ( + + {renderCalendar()} + + ); + } + + return renderCalendar(); + }; }, });