import React, { useState, useEffect, useRef, useCallback } from "react"; import moment, { Moment } from "moment"; import classNames from "classnames"; import { useTranslation } from "../i18n"; import { ControlledProps, useDefaultValue } from "../form/controlled"; import CalendarPart from "../calendar/CalendarPart"; import { CalendarTable, getTimeRange } from "../calendar/CalendarTable"; import { TimeSupportWrapper } from "./TimeSupportWrapper"; import { Input } from "../input/Input"; import { RangePicker } from "./RangePicker"; import { TimeDisabledProps } from "../timepicker/TimeProps"; import { getHourMinuteSecond, getValidTimeValue, isValidTimeValue, } from "../timepicker/util"; import { DropdownBox } from "../dropdown"; import { MonthPicker } from "./MonthPicker"; import { QuarterPicker } from "./QuarterPicker"; import { showTimeType, CalendarTableType, DateChangeContext, } from "../calendar/DateProps"; import { CommonDatePickerProps } from "./DatePickerProps"; import { getYearMonthDate, DatePickerTrigger, isBefore, isAfter, isValidDate, } from "./util"; import { Popover } from "../popover"; import { useDefault } from "../_util/use-default"; import { useConfig } from "../_util/config-context"; import { noop } from "../_util/noop"; import { Icon } from "../icon"; import { forwardRefWithStatics } from "../_util/forward-ref-with-statics"; import { mergeEventProps } from "../_util/merge-event-props"; import { KeyMap } from "../_util/key-map"; export interface DatePickerProps extends CommonDatePickerProps, ControlledProps { /** * 是否开启时间选择 */ showTime?: showTimeType; /** * 设置不可选日期,返回: * * - `true` 可选 * - `false` 不可选 */ disabledDate?: (date: Moment) => boolean; /** * 设置不可选时间 */ disabledTime?: (date: Moment) => TimeDisabledProps; } const getFormat = showTime => (showTime ? "YYYY-MM-DD HH:mm:ss" : "YYYY-MM-DD"); export const DatePicker = forwardRefWithStatics( function DatePicker(props: DatePickerProps, ref: React.Ref) { const t = useTranslation(moment); const { classPrefix } = useConfig(); const { header, className, style, showTime, value, onChange, disabled, format: _format, placeholder = showTime ? t.selectTime : t.selectDate, defaultOpen = false, open, onOpenChange = noop, onInputValueChange = noop, placement = "bottom-start", placementOffset = 5, closeOnScroll = true, escapeWithReference, popupContainer, overlayClassName, overlayStyle, clearable, ...restProps } = useDefaultValue(props); const [hover, setHover] = useState(false); const format = _format || getFormat(showTime); // 当前面板类型 const [type, setType] = useState("date"); // 当前面板展示时间 const [curViewMoment, setCurViewMoment] = useState( moment.isMoment(value) ? value : getDefaultViewMoment() ); // 当前选中日期 const [curValue, setCurValue] = useState(value); // 上次选中日期 const preValidValueRef = useRef(value); // 选择器是否展开 const [active, setActive] = useDefault(open, defaultOpen, onOpenChange); useEffect(() => { setCurValue(value); // eslint-disable-next-line react-hooks/exhaustive-deps }, [active]); // 输入框显示值 const inputRef = useRef(null); const getInputValue = useCallback( value => { return moment.isMoment(value) ? value.locale(t.locale).format(format) : ""; }, [t.locale, format] ); const [inputValue, setInputValue] = useState( getInputValue(curValue) ); const syncInputValue = useCallback( (value: Moment) => { const inputValue = getInputValue(value); setInputValue(inputValue); onInputValueChange(inputValue, { valid: true }); }, // eslint-disable-next-line react-hooks/exhaustive-deps [getInputValue] ); useEffect(() => { setCurValue(value); syncInputValue(value); }, [format, value, getInputValue, syncInputValue]); useEffect(() => { setCurViewMoment(curValue); }, [curValue]); useEffect(() => { preValidValueRef.current = value; }, [value]); function handleChange(value: Moment, context: DateChangeContext): void { // 同步日期/时间 if (showTime && moment.isMoment(value)) { value = syncDate(value, context.type); // eslint-disable-line no-param-reassign } setCurValue(value); syncInputValue(value); // 没有时间选择时没有二次确认选中 if (!showTime) { onChange(value, context); handleClose(); } } function handleOk(event): void { if (curValue) { onChange(curValue, { event }); handleClose(); } } function handleOpen(): void { if (disabled) { return; } setActive(true); setType("date"); } function handleClose(): void { syncInputValue(value); setActive(false); } /** * 同步日期/时间 * 如果当前修改为日期,则同步上次时间并修正; * 如果当前修改为时间,则同步上次日期; */ function syncDate(value: Moment, type: CalendarTableType): Moment { const preValidValue = preValidValueRef.current; const { range, disabledTime = () => ({}) } = props; // 如果包含上次选择,则以上次选择为基准同步 if (moment.isMoment(preValidValue)) { if (type === "date") { const time = getValidTimeValue(preValidValue, { range: getTimeRange(value, range), ...disabledTime(value), hourStep: showTime?.hourStep || 1, minuteStep: showTime?.minuteStep || 1, secondStep: showTime?.secondStep || 1, }); value.set(getHourMinuteSecond(time)); } if (type === "time") { value.set(getYearMonthDate(preValidValue)); } // 如果是首次选择,则将当前时间进行修正 } else if (type === "date") { // 合并 showTime.defaultValue if (moment.isMoment(showTime?.defaultValue)) { value.set(getHourMinuteSecond(showTime.defaultValue)); } const time = getValidTimeValue(value, { range: getTimeRange(value, range), ...disabledTime(value), hourStep: showTime?.hourStep || 1, minuteStep: showTime?.minuteStep || 1, secondStep: showTime?.secondStep || 1, }); value.set(getHourMinuteSecond(time)); } preValidValueRef.current = value; return value; } /** * 获取面板默认展示时间 */ function getDefaultViewMoment(): Moment { const { range } = props; const time = showTime && typeof showTime === "object" ? showTime.defaultValue : undefined; let m = null; if (!time) { m = moment(); } else { m = moment(getHourMinuteSecond(time)); } // range 判断 if (Array.isArray(range)) { let [start, end] = range; if (!moment.isMoment(start)) { start = moment(0); } if (!moment.isMoment(end)) { end = moment(2 ** 52); } if (isBefore(end, m, "month")) { return end; } if (isAfter(start, m, "month")) { return start; } } return m; } function handleInputChange(content: string) { setInputValue(content); const value = moment(content, format, true); if (!value.isValid()) { onInputValueChange(content, { valid: false }); return; } const valid = isValidDate(value, props); const validTime = showTime ? isValidTimeValue(value, { ...showTime, ...props }) : true; if (valid && validTime) { setCurValue(value); } onInputValueChange(content, { valid: true }); } function handleKeyDown(event: React.KeyboardEvent) { if (!active) { handleOpen(); return; } switch (event.key) { case KeyMap.Enter: handleOk(event); break; case KeyMap.Esc: handleClose(); break; } } return ( {!!header && {header}} setType(type as CalendarTableType)} > {props => ( )} } >
setHover(true)} onMouseLeave={() => setHover(false)} > { if (active) { event.stopPropagation(); } }} /> {clearable && !disabled && ( { event.stopPropagation(); onChange(null, { event }); }} /> )}
); }, { RangePicker, MonthPicker, QuarterPicker, defaultLabelAlign: "middle", } ); DatePicker.displayName = "DatePicker";