import React, { forwardRef, useCallback, useMemo, useRef, useState, useEffect, useImperativeHandle } from 'react' import { StyleSheet, Text, View } from 'react-native' import MpxPickerView from '../mpx-picker-view' import MpxPickerViewColumn from '../mpx-picker-view-column' import { DateProps, TimeValue } from './type' import { HandlerRef } from '../useNodesRef' import { useUpdateEffect } from '../utils' import { years, months, daysInMonth, wrapDate, daysInMonthLength, START_YEAR, END_YEAR } from './dateData' type FormatObj = { indexArr: number[] rangeArr: string[][] nameArr?: string[] } const START_DATE: TimeValue = `${START_YEAR}-01-01` const END_DATE: TimeValue = `${END_YEAR}-12-31` const START_DATE_ARR = [START_YEAR, 1, 1] const END_DATE_ARR = [END_YEAR, 12, 31] const styles = StyleSheet.create({ pickerContainer: { height: 240, paddingHorizontal: 10, borderTopLeftRadius: 10, borderTopRightRadius: 10 }, pickerIndicator: { height: 45 }, pickerItem: { fontSize: 16, lineHeight: 45, textAlign: 'center' } }) const getColumnLength = (fields: DateProps['fields'] = 'day') => { return fields === 'year' ? 1 : fields === 'month' ? 2 : 3 } const compareDateStr = (date1: TimeValue | number[], date2: TimeValue | number[]) => { const [y1 = START_YEAR, m1 = 0, d1 = 0] = typeof date1 === 'string' ? date1.split('-').map(Number) : date1 const [y2 = START_YEAR, m2 = 0, d2 = 0] = typeof date2 === 'string' ? date2.split('-').map(Number) : date2 const num1 = y1 * 10000 + m1 * 100 + d1 const num2 = y2 * 10000 + m2 * 100 + d2 if (num1 === num2) { return 0 } return num1 > num2 ? 1 : -1 } const getDateArr = (date: TimeValue | number[]): number[] => { const [y, m, d] = typeof date === 'string' ? date.split('-').map(Number) : date return [y || 0, m || 0, d || 0] } const calibrateDate = (date: TimeValue | number[], start: TimeValue, end: TimeValue): number[] => { let startArr = getDateArr(start) let endArr = getDateArr(end) let dateArr = getDateArr(date) if (compareDateStr(startArr, endArr) > 0) { startArr = START_DATE_ARR } if (compareDateStr(endArr, startArr) < 0) { endArr = END_DATE_ARR } if (compareDateStr(start, end) > 0) { startArr = START_DATE_ARR endArr = END_DATE_ARR } if (compareDateStr(dateArr, endArr) > 0) { dateArr = endArr } if (compareDateStr(dateArr, startArr) < 0) { dateArr = startArr } return dateArr } const initDateStr2Arr = (dateStr: TimeValue | number[], start: TimeValue, end: TimeValue): number[] => { if (!dateStr) { const today = new Date() const todayYear = today.getFullYear() const todayMonth = today.getMonth() + 1 const todayDay = today.getDate() dateStr = [todayYear, todayMonth, todayDay] } const [y, m, d] = getDateArr(dateStr) const year = Math.min(Math.max(START_YEAR, y), END_YEAR) const month = Math.min(Math.max(1, m), 12) const day = Math.min(Math.max(1, d), daysInMonthLength(year, month)) const res = [year, month, day] return calibrateDate(res, start, end) } const valueStr2Obj = ( _value: TimeValue | number[] = '', // eg: 2025-2-12 limit: number, start: TimeValue, end: TimeValue ): FormatObj => { const [y, m, d] = initDateStr2Arr(_value, start, end) const ans = { indexArr: [y - START_YEAR], rangeArr: [years] } if (limit === 2) { ans.indexArr.push(m - 1) ans.rangeArr.push(months) } else if (limit === 3) { const days = daysInMonth(y, m) ans.indexArr.push(m - 1, d - 1) ans.rangeArr.push(months, days) } return ans } const valueChanged2Obj = (currentObj: FormatObj, value: number[], limit = 3) => { const currentValue = currentObj.indexArr const rangeArr = currentObj.rangeArr if (limit === 3 && (currentValue[0] !== value[0] || currentValue[1] !== value[1])) { const days = daysInMonth(value[0], value[1] + 1) rangeArr[2] = days const maxIndex = days.length - 1 if (value[2] > maxIndex) { value[2] = maxIndex } } return { indexArr: value, rangeArr } } const valueChanged2Obj2 = (value: number[], limit = 3, start: TimeValue, end: TimeValue) => { const y = value[0] + START_YEAR const m = value[1] + 1 const d = value[2] + 1 return valueStr2Obj([y, m, d], limit, start, end) } const valueNum2String = (value: number[]) => { return value.map((item, index) => { if (index === 0) { return item + START_YEAR } else { return wrapDate()(item + 1) } }).join('-') } const hasDiff = (currentValue: number[], value: number[], limit = 3) => { for (let i = 0; i < limit; i++) { if (currentValue[i] !== value[i]) { return true } } return false } const PickerDate = forwardRef< HandlerRef, DateProps >((props: DateProps, ref): React.JSX.Element => { const { value = '', start = START_DATE, end = END_DATE, fields, bindchange } = props const nodeRef = useRef(null) const columnLength = useMemo(() => getColumnLength(fields), [fields]) const [formatObj, setFormatObj] = useState(valueStr2Obj(value, columnLength, start, end)) const timerRef = useRef(null) useEffect(() => { return () => { timerRef.current && clearTimeout(timerRef.current) } }, []) useUpdateEffect(() => { const calibratedValue = valueStr2Obj(value, columnLength, start, end) setFormatObj(calibratedValue) }, [value, columnLength, start, end]) const updateValue = useCallback((value: TimeValue = '') => { const calibratedValue = valueStr2Obj(value, columnLength, start, end) setFormatObj(calibratedValue) }, [columnLength, start, end]) const _props = useRef(props) _props.current = props useImperativeHandle(ref, () => ({ updateValue, getNodeInstance: () => ({ props: _props, nodeRef, instance: { style: {} } }) })) const onChange = useCallback((e: { detail: { value: number[] } }) => { const { value } = e.detail const currentValue = formatObj.indexArr const newObj = valueChanged2Obj(formatObj, value, columnLength) if (hasDiff(currentValue, value, columnLength)) { setFormatObj(newObj) const newObj2 = valueChanged2Obj2(value, columnLength, start, end) if (hasDiff(newObj.indexArr, newObj2.indexArr, columnLength)) { timerRef.current && clearTimeout(timerRef.current) timerRef.current = setTimeout(() => setFormatObj(newObj2)) } } bindchange?.({ detail: { value: valueNum2String(newObj.indexArr) } }) }, [formatObj, columnLength, bindchange, start, end]) const renderColumn = () => { return formatObj.rangeArr?.map((item, index) => ( // @ts-expect-error ignore {item.map((item, index) => { return {item} })} )) } return ( {renderColumn()} ) }) PickerDate.displayName = 'MpxPickerDate' export default PickerDate