import {MouseEvent, FocusEvent} from 'react' import {InputWrapper} from '@befe/brick-comp-input' import {SvgCalendar} from '@befe/brick-icon' import {Icon} from '@befe/brick-comp-icon' import {createRef, createStateOperator, safeInvoke} from '@befe/brick-utils' import dayjs, {Dayjs} from 'dayjs' import {debounce} from 'lodash-es'; import {syncDisplayedDatePair} from '../utils/sync-range-displayed-date-pair'; import {createFocusHandler} from './range-input-utils/create-focus-handler' import {createRangeFieldUserInput} from './range-input-utils/create-range-field-input' import {TypePickerSize, TypePickerStatus} from './prop-types' import {InnerRangePicker} from './inner-range-picker' import {RangeInputCloseableIcon} from './partial-input-closable-icon' import {preventPickerBlurred} from './create-inner-date-picker'; export function createRangePickerUserInput( comp: InnerRangePicker ) { const [ getState, setState, ] = createStateOperator(comp) const startValueProp = 'startValue' const endValueProp = 'endValue' const startUserInputTextProp = 'startUserInputText' const endUserInputTextProp = 'endUserInputText' const startDisplayedProp = 'startDisplayedDate' const endDisplayedProp = 'endDisplayedDate' const startInputRef = createRef() const endInputRef = createRef() const closeableIcon = new RangeInputCloseableIcon(comp, { hoverState: 'isHoverOverCalendar', isClearable: () => !!comp.props.clearable, isDisabled: () => !!comp.props.disabled, hasValue: () => { let hasValue = false if (comp.state.isStartUserInput) { hasValue = hasValue || !!comp.state.startUserInputText } else { hasValue = hasValue || !!comp.state.startValue } if (comp.state.isEndUserInput) { hasValue = hasValue || !!comp.state.endUserInputText } else { hasValue = hasValue || !!comp.state.endValue } return hasValue }, renderNode: function renderNode() { return ( ) }, isFocus: () => comp.state.isStartUserInput || comp.state.isEndUserInput, onClear: () => { comp.setState({ open: false, startValue: null, endValue: null, startUserInputText: '', endUserInputText: '', }) comp.triggerChange({ startValue: null, endValue: null, }) comp.setOpenStatus({ open: false, isStartUserInput: false, isEndUserInput: false, }) }, }) function setRangeValue( startValue: Dayjs | null, endValue: Dayjs | null, isChangingEndValue = false ) { let [ startUserInputText, endUserInputText, ] = getState([ startUserInputTextProp, endUserInputTextProp, ]) as [string, string] const today = dayjs(); // start 日期在 disabled 范围内, 整体清空 if (startValue && comp.props.getDisabledItem && comp.props.getDisabledItem(startValue, today)) { startValue = null; startUserInputText = ''; endValue = null; endUserInputText = ''; } // end 日期在 disabled 范围内, 仅清空 end 值 if (endValue && comp.props.getDisabledItem && comp.props.getDisabledItem(endValue, today)) { endValue = null; endUserInputText = ''; } // 如果 end 日期早于 start 日期, 清空 end 值 if (startValue && endValue && endValue.isBefore(startValue)) { endValue = null; endUserInputText = ''; } const { startDisplayedDate, endDisplayedDate, } = syncDisplayedDatePair( comp.props.mode, startValue, endValue ); const nextRangeState = { [startValueProp]: startValue, [startUserInputTextProp]: startUserInputText as (string | undefined), [endValueProp]: endValue, [endUserInputTextProp]: endUserInputText as (string | undefined), [startDisplayedProp]: startDisplayedDate, [endDisplayedProp]: endDisplayedDate, isSelecting: false, }; if (isChangingEndValue) { delete nextRangeState[endUserInputTextProp]; } else { delete nextRangeState[startUserInputTextProp]; } setState(nextRangeState) comp.triggerChange({startValue, endValue}) } const handleBlurOnInput = debounce((e: FocusEvent) => { if ( startInputRef.elem !== document.activeElement && endInputRef.elem !== document.activeElement ) { safeInvoke(comp.props.onBlur, e); } }, 0) const context = { startFocusHandler: createFocusHandler( comp, 'startFocus' ), endFocusHandler: createFocusHandler( comp, 'endFocus' ), inputStart: createRangeFieldUserInput(comp, { isInputProp: 'isStartUserInput', inputValueProp: startUserInputTextProp, valueProp: startValueProp, displayedProp: startDisplayedProp, onCustomChange: (startValue: Dayjs | null) => { const endValue = getState(endValueProp) as Dayjs setRangeValue(startValue, endValue); }, onBlur: e => { handleBlurOnInput(e); }, }), inputEnd: createRangeFieldUserInput(comp, { isInputProp: 'isEndUserInput', inputValueProp: endUserInputTextProp, valueProp: endValueProp, displayedProp: endDisplayedProp, onCustomChange: (endValue: Dayjs | null) => { const startValue = getState(startValueProp) as Dayjs setRangeValue(startValue, endValue, true); }, onBlur: e => { handleBlurOnInput(e); }, }), refInput: createRef(), } function isFocus() { const {startFocus, endFocus} = comp.state return startFocus || endFocus } function init() { context.startFocusHandler.init({ onFocus: context.inputStart.inputStart, onBlur: context.inputStart.inputEnd, }) context.endFocusHandler.init({ onFocus: context.inputEnd.inputStart, onBlur: context.inputEnd.inputEnd, }) } init() const iconHooks = closeableIcon.hooks const handleMouseEnter = (e: MouseEvent) => { safeInvoke(comp.props.onMouseEnter, e); iconHooks.mouseEnter(); } const handleMouseLeave = (e: MouseEvent) => { safeInvoke(comp.props.onMouseLeave, e); iconHooks.mouseLeave(); } const handleStartMouseDown = () => { if (startInputRef.elem === document.activeElement) { comp.setState({ isStartUserInput: true, }) } } const handleEndMouseDown = () => { if (endInputRef.elem === document.activeElement) { comp.setState({ isEndUserInput: true, }) } } return { renderInput(opts: { status?: TypePickerStatus disabled?: boolean size?: TypePickerSize }) { const disabled = opts.disabled const { autoFocus, } = comp.props; return ( ) }, get inputWrapperElem(): HTMLDivElement | null { return context.refInput.elem }, get startInputElem(): HTMLInputElement | null { return startInputRef.elem }, get endInputElem(): HTMLInputElement | null { return endInputRef.elem }, formatStartInputValue: context.inputStart.formatInputValue, formatEndInputValue: context.inputEnd.formatInputValue, } }