325 lines
8.3 KiB
TypeScript

import { View } from '@tarojs/components'
import classnames from 'classnames'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import React from 'react'
import type { BaseEventOrig } from '@tarojs/components/types/common'
import type {
AtCalendarDefaultProps,
AtCalendarProps,
AtCalendarPropsWithDefaults,
AtCalendarState,
Calendar,
} from '../../types/calendar'
import AtCalendarBody from './body/index'
import AtCalendarController from './controller/index'
import './index.scss'
const defaultProps: AtCalendarDefaultProps = {
validDates: [],
marks: [],
isSwiper: true,
hideArrow: false,
isVertical: false,
selectedDates: [],
isMultiSelect: false,
format: 'YYYY/MM/DD',
currentDate: Date.now(),
monthFormat: 'YYYY年MM月',
}
export default class AtCalendar extends React.Component<
AtCalendarProps,
Readonly<AtCalendarState>
> {
static defaultProps: AtCalendarDefaultProps = defaultProps
public constructor(props: AtCalendarProps) {
super(props)
const { currentDate, isMultiSelect } = props as AtCalendarPropsWithDefaults
this.state = this.getInitializeState(currentDate, isMultiSelect)
}
public UNSAFE_componentWillReceiveProps(nextProps: AtCalendarProps): void {
const { currentDate, isMultiSelect } = nextProps
if (!currentDate || currentDate === this.props.currentDate) { return }
if (isMultiSelect && this.props.isMultiSelect) {
const { start, end } = currentDate as Calendar.SelectedDate
const { start: preStart, end: preEnd } = this.props
.currentDate as Calendar.SelectedDate
if (start === preStart && preEnd === end) {
return
}
}
const stateValue: AtCalendarState = this.getInitializeState(
currentDate,
isMultiSelect,
)
this.setState(stateValue)
}
private getSingleSelectdState = (value: Dayjs): Partial<AtCalendarState> => {
const { generateDate } = this.state
const stateValue: Partial<AtCalendarState> = {
selectedDate: this.getSelectedDate(value.valueOf()),
}
const dayjsGenerateDate: Dayjs = value.startOf('month')
const generateDateValue: number = dayjsGenerateDate.valueOf()
if (generateDateValue !== generateDate) {
this.triggerChangeDate(dayjsGenerateDate)
stateValue.generateDate = generateDateValue
}
return stateValue
}
private getMultiSelectedState = (
value: Dayjs,
): Pick<AtCalendarState, 'selectedDate'> => {
const { selectedDate } = this.state
const { end, start } = selectedDate
const valueUnix: number = value.valueOf()
const state: Pick<AtCalendarState, 'selectedDate'> = {
selectedDate,
}
if (end) {
state.selectedDate = this.getSelectedDate(valueUnix, 0)
}
else {
state.selectedDate.end = Math.max(valueUnix, +start)
state.selectedDate.start = Math.min(valueUnix, +start)
}
return state
}
private getSelectedDate = (
start: number,
end?: number,
): Calendar.SelectedDate => {
const stateValue: Calendar.SelectedDate = {
start,
end: start,
}
if (typeof end !== 'undefined') {
stateValue.end = end
}
return stateValue
}
private getInitializeState(
currentDate: Calendar.DateArg | Calendar.SelectedDate,
isMultiSelect?: boolean,
): AtCalendarState {
let end: number
let start: number
let generateDateValue: number
if (!currentDate) {
const dayjsStart = dayjs()
start = dayjsStart.startOf('day').valueOf()
generateDateValue = dayjsStart.startOf('month').valueOf()
return {
generateDate: generateDateValue,
selectedDate: {
start: '',
},
}
}
if (isMultiSelect) {
const { start: cStart, end: cEnd } = currentDate as Calendar.SelectedDate
const dayjsStart = dayjs(cStart)
start = dayjsStart.startOf('day').valueOf()
generateDateValue = dayjsStart.startOf('month').valueOf()
end = cEnd ? dayjs(cEnd).startOf('day').valueOf() : start
}
else {
const dayjsStart = dayjs(currentDate as Calendar.DateArg)
start = dayjsStart.startOf('day').valueOf()
generateDateValue = dayjsStart.startOf('month').valueOf()
end = start
}
return {
generateDate: generateDateValue,
selectedDate: this.getSelectedDate(start, end),
}
}
private triggerChangeDate = (value: Dayjs): void => {
const { format } = this.props
if (typeof this.props.onMonthChange !== 'function') { return }
this.props.onMonthChange(value.format(format))
}
private setMonth = (vectorCount: number): void => {
const { format } = this.props
const { generateDate } = this.state
const _generateDate: Dayjs = dayjs(generateDate).add(vectorCount, 'month')
this.setState({
generateDate: _generateDate.valueOf(),
})
if (vectorCount && typeof this.props.onMonthChange === 'function') {
this.props.onMonthChange(_generateDate.format(format))
}
}
private handleClickPreMonth = (isMinMonth?: boolean): void => {
if (isMinMonth === true) {
return
}
this.setMonth(-1)
if (typeof this.props.onClickPreMonth === 'function') {
this.props.onClickPreMonth()
}
}
private handleClickNextMonth = (isMaxMonth?: boolean): void => {
if (isMaxMonth === true) {
return
}
this.setMonth(1)
if (typeof this.props.onClickNextMonth === 'function') {
this.props.onClickNextMonth()
}
}
// picker 选择时间改变时触发
private handleSelectDate = (e: BaseEventOrig<{ value: string }>): void => {
const { value } = e.detail
const _generateDate: Dayjs = dayjs(value)
const _generateDateValue: number = _generateDate.valueOf()
if (this.state.generateDate === _generateDateValue) { return }
this.triggerChangeDate(_generateDate)
this.setState({
generateDate: _generateDateValue,
})
}
private handleDayClick = (item: Calendar.Item): void => {
const { isMultiSelect } = this.props
const { isDisabled, value } = item
if (isDisabled) { return }
const dayjsDate: Dayjs = dayjs(value)
let stateValue: Partial<AtCalendarState> = {}
if (isMultiSelect) {
stateValue = this.getMultiSelectedState(dayjsDate)
}
else {
stateValue = this.getSingleSelectdState(dayjsDate)
}
this.setState(stateValue as AtCalendarState, () => {
this.handleSelectedDate()
})
if (typeof this.props.onDayClick === 'function') {
this.props.onDayClick({ value: item.value })
}
}
private handleSelectedDate = (): void => {
const selectDate = this.state.selectedDate
if (typeof this.props.onSelectDate === 'function') {
const info: Calendar.SelectedDate = {
start: dayjs(selectDate.start).format(this.props.format),
}
if (selectDate.end) {
info.end = dayjs(selectDate.end).format(this.props.format)
}
this.props.onSelectDate({
value: info,
})
}
}
private handleDayLongClick = (item: Calendar.Item): void => {
if (typeof this.props.onDayLongClick === 'function') {
this.props.onDayLongClick({ value: item.value })
}
}
public render(): JSX.Element {
const { generateDate, selectedDate } = this.state
const {
validDates,
marks,
format,
minDate,
maxDate,
isSwiper,
className,
hideArrow,
isVertical,
monthFormat,
selectedDates,
} = this.props as AtCalendarPropsWithDefaults
return (
<View className={classnames('at-calendar', className)}>
<AtCalendarController
minDate={minDate}
maxDate={maxDate}
hideArrow={hideArrow}
monthFormat={monthFormat}
generateDate={generateDate}
onPreMonth={this.handleClickPreMonth}
onNextMonth={this.handleClickNextMonth}
onSelectDate={this.handleSelectDate}
/>
<AtCalendarBody
validDates={validDates}
marks={marks}
format={format}
minDate={minDate}
maxDate={maxDate}
isSwiper={isSwiper}
isVertical={isVertical}
selectedDate={selectedDate}
selectedDates={selectedDates}
generateDate={generateDate}
onDayClick={this.handleDayClick}
onSwipeMonth={this.setMonth}
onLongClick={this.handleDayLongClick}
/>
</View>
)
}
}