import isNil from 'lodash/isNil'; import React, { useMemo, useRef, useState, useEffect, memo } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { Button, Fill } from '@xrnjs/ui'; import { varCreator as varCreatorButton } from '../button/style'; import Col from '../col'; import DatePickerView from '../date-picker-view'; import { formatDate } from '../date-picker-view/helper'; import { useControllableValue } from 'ahooks'; import { useLangType, useLocale } from '../../components/Locale/locale'; import { varCreator as varCreatorPicker, styleCreator as styleCreatorPicker, } from '../picker/style'; import Row from '../row'; import Theme from '../theme'; import type { DatePickerRangeViewProps, DatePickerRangeValue, } from './interface'; import { usePersistFn } from '../hooks'; const defaultInitialValue: DatePickerRangeValue = [null, null]; const START_TIME_INDEX = 0 as const; const END_TIME_INDEX = 1 as const; type RangeIndex = typeof START_TIME_INDEX | typeof END_TIME_INDEX; const isValidDate = (value: Date | null | undefined): value is Date => value instanceof Date && !Number.isNaN(value.getTime()); const normalizeDate = (value: Date | null | undefined): Date | null => isValidDate(value) ? value : null; const normalizeRangeValue = ( value?: DatePickerRangeValue | null ): DatePickerRangeValue => [ normalizeDate(value?.[0]), normalizeDate(value?.[1]), ]; const ensureCompleteRangeValue = ( value: DatePickerRangeValue, activeIndex: RangeIndex, fallback?: Date | null ): DatePickerRangeValue => { const next = normalizeRangeValue(value); const fallbackDate = normalizeDate(fallback); const hasStartTime = isValidDate(next[START_TIME_INDEX]); const hasEndTime = isValidDate(next[END_TIME_INDEX]); if (!isValidDate(next[activeIndex]) && fallbackDate) { next[activeIndex] = fallbackDate; } if (!hasStartTime && hasEndTime) { next[START_TIME_INDEX] = next[END_TIME_INDEX]; } if (!hasEndTime && hasStartTime) { next[END_TIME_INDEX] = next[START_TIME_INDEX]; } return next; }; const DatePickerRangeView: React.FC = ({ initialValue, confirmButtonText, resetButtonText, onConfirm, placeholder, clearable, onClear, clearButtonText, // DateView mode = 'Y-D', min, max, renderLabel, loading, testID, ...restProps }) => { const _initialValue = normalizeRangeValue( !isNil(initialValue) ? initialValue : defaultInitialValue ); const locale = useLocale().DatePickerRangeView; const lang = useLangType(); const TOKENS = Theme.useThemeTokens(); const CV_BUTTON = Theme.createVar(TOKENS, varCreatorButton); const CV_PICKER = Theme.createVar(TOKENS, varCreatorPicker); // @ts-ignore const STYLES_PICKER = Theme.createStyle(CV_PICKER, styleCreatorPicker); const btnStyle = useMemo( () => ({ paddingHorizontal: CV_PICKER.picker_action_gap, }), [CV_PICKER.picker_action_gap] ); const [value, onChange] = useControllableValue( restProps, { defaultValue: [..._initialValue], } ); const safeValue = useMemo(() => normalizeRangeValue(value), [value]); const currentDate = useMemo(() => new Date(), []); const safeMin = useMemo(() => normalizeDate(min as Date), [min]); const safeMax = useMemo(() => normalizeDate(max as Date), [max]); const [dayActive, setDayActive] = useState(START_TIME_INDEX); const Values = useRef([...safeValue]); const OriginalValues = useRef([..._initialValue]); const [limitDates, setLimitDates] = useState([ safeMin, Values.current[END_TIME_INDEX] || safeMax, ]); const activeValue = safeValue[dayActive]; // 同步 value,避免外部 value 清空后,触发 onChangePickView 的时候把旧数据带出来 useEffect(() => { Values.current = [...safeValue]; }, [safeValue]); const onChangePickView = usePersistFn((v: Date) => { const nextValues = ensureCompleteRangeValue( [ dayActive === START_TIME_INDEX ? v : Values.current[START_TIME_INDEX], dayActive === END_TIME_INDEX ? v : Values.current[END_TIME_INDEX], ], dayActive, v ); Values.current = nextValues; onChange([...nextValues]); }); const onPressConfirm = usePersistFn(() => { const nextValues = ensureCompleteRangeValue( Values.current, dayActive, currentDate ); Values.current = nextValues; onChange([...nextValues]); onConfirm?.(nextValues); }); const onPressClear = usePersistFn(() => { onClear?.(Values.current); }); const onPressDay1 = usePersistFn(() => { setDayActive(START_TIME_INDEX); setLimitDates([safeMin, Values.current[END_TIME_INDEX] || safeMax]); }); const onPressDay2 = usePersistFn(() => { setDayActive(END_TIME_INDEX); setLimitDates([Values.current[START_TIME_INDEX] || safeMin, safeMax]); }); const onPressReset = usePersistFn(() => { Values.current = [...OriginalValues.current]; onChange([...Values.current]); setDayActive(START_TIME_INDEX); setLimitDates([safeMin, Values.current[END_TIME_INDEX] || safeMax]); }); // 把开时间提前锁定 useEffect(() => { onPressDay1(); }, [onPressDay1]); return ( <> {locale.labelStartTime} {isValidDate(safeValue[START_TIME_INDEX]) ? formatDate(mode, safeValue[START_TIME_INDEX], lang) : placeholder?.[START_TIME_INDEX] ?? locale.placeholder[START_TIME_INDEX]} {locale.labelEndTime} {isValidDate(safeValue[END_TIME_INDEX]) ? formatDate(mode, safeValue[END_TIME_INDEX], lang) : placeholder?.[END_TIME_INDEX] ?? locale.placeholder[END_TIME_INDEX]} {clearable ? ( ) : null} ); }; export default memo(DatePickerRangeView);