import * as React from 'react'; import { useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; import warning from 'rc-util/lib/warning'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import type { DisabledTimes, PanelMode, PickerMode, RangeValue, EventValue } from './interface'; import type { PickerBaseProps, PickerDateProps, PickerTimeProps, PickerRefConfig } from './Picker'; import type { SharedTimeProps } from './panels/TimePanel'; import PickerTrigger from './PickerTrigger'; import PickerPanel from './PickerPanel'; import usePickerInput from './hooks/usePickerInput'; import getDataOrAriaProps, { toArray, getValue, updateValues } from './utils/miscUtil'; import { getDefaultFormat, getInputSize, elementsContains } from './utils/uiUtil'; import type { ContextOperationRefProps } from './PanelContext'; import PanelContext from './PanelContext'; import { isEqual, getClosingViewDate, isSameDate, isSameWeek, isSameQuarter, formatValue, parseValue, } from './utils/dateUtil'; import useValueTexts from './hooks/useValueTexts'; import useTextValueMapping from './hooks/useTextValueMapping'; import type { GenerateConfig } from './generate'; import type { PickerPanelProps } from '.'; import RangeContext from './RangeContext'; import useRangeDisabled from './hooks/useRangeDisabled'; import getExtraFooter from './utils/getExtraFooter'; import getRanges from './utils/getRanges'; import useRangeViewDates from './hooks/useRangeViewDates'; import type { DateRender } from './panels/DatePanel/DateBody'; import useHoverValue from './hooks/useHoverValue'; function reorderValues( values: RangeValue, generateConfig: GenerateConfig, ): RangeValue { if (values && values[0] && values[1] && generateConfig.isAfter(values[0], values[1])) { return [values[1], values[0]]; } return values; } function canValueTrigger( value: EventValue, index: number, disabled: [boolean, boolean], allowEmpty?: [boolean, boolean] | null, ): boolean { if (value) { return true; } if (allowEmpty && allowEmpty[index]) { return true; } if (disabled[(index + 1) % 2]) { return true; } return false; } export type RangeType = 'start' | 'end'; export type RangeInfo = { range: RangeType; }; export type RangeDateRender = ( currentDate: DateType, today: DateType, info: RangeInfo, ) => React.ReactNode; export type RangePickerSharedProps = { id?: string; value?: RangeValue; defaultValue?: RangeValue; defaultPickerValue?: [DateType, DateType]; placeholder?: [string, string]; disabled?: boolean | [boolean, boolean]; disabledTime?: (date: EventValue, type: RangeType) => DisabledTimes; ranges?: Record< string, Exclude, null> | (() => Exclude, null>) >; separator?: React.ReactNode; allowEmpty?: [boolean, boolean]; mode?: [PanelMode, PanelMode]; onChange?: (values: RangeValue, formatString: [string, string]) => void; onCalendarChange?: ( values: RangeValue, formatString: [string, string], info: RangeInfo, ) => void; onPanelChange?: (values: RangeValue, modes: [PanelMode, PanelMode]) => void; onFocus?: React.FocusEventHandler; onBlur?: React.FocusEventHandler; onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; onOk?: (dates: RangeValue) => void; direction?: 'ltr' | 'rtl'; autoComplete?: string; /** @private Internal control of active picker. Do not use since it's private usage */ activePickerIndex?: 0 | 1; dateRender?: RangeDateRender; panelRender?: (originPanel: React.ReactNode) => React.ReactNode; }; type OmitPickerProps = Omit< Props, | 'value' | 'defaultValue' | 'defaultPickerValue' | 'placeholder' | 'disabled' | 'disabledTime' | 'showToday' | 'showTime' | 'mode' | 'onChange' | 'onSelect' | 'onPanelChange' | 'pickerValue' | 'onPickerValueChange' | 'onOk' | 'dateRender' >; type RangeShowTimeObject = Omit, 'defaultValue'> & { defaultValue?: DateType[]; }; export type RangePickerBaseProps = {} & RangePickerSharedProps & OmitPickerProps>; export type RangePickerDateProps = { showTime?: boolean | RangeShowTimeObject; } & RangePickerSharedProps & OmitPickerProps>; export type RangePickerTimeProps = { order?: boolean; } & RangePickerSharedProps & OmitPickerProps>; export type RangePickerProps = | RangePickerBaseProps | RangePickerDateProps | RangePickerTimeProps; // TMP type to fit for ts 3.9.2 type OmitType = Omit, 'picker'> & Omit, 'picker'> & Omit, 'picker'>; type MergedRangePickerProps = { picker?: PickerMode; } & OmitType; function InnerRangePicker(props: RangePickerProps) { const { prefixCls = 'rc-picker', id, style, className, popupStyle, dropdownClassName, transitionName, dropdownAlign, getPopupContainer, generateConfig, locale, placeholder, autoFocus, disabled, format, picker = 'date', showTime, use12Hours, separator = '~', value, defaultValue, defaultPickerValue, open, defaultOpen, disabledDate, disabledTime, dateRender, panelRender, ranges, allowEmpty, allowClear, suffixIcon, clearIcon, pickerRef, inputReadOnly, mode, renderExtraFooter, onChange, onOpenChange, onPanelChange, onCalendarChange, onFocus, onBlur, onMouseEnter, onMouseLeave, onOk, onKeyDown, components, order, direction, activePickerIndex, autoComplete = 'off', } = props as MergedRangePickerProps; const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; // We record opened status here in case repeat open with picker const openRecordsRef = useRef>({}); const containerRef = useRef(null); const panelDivRef = useRef(null); const startInputDivRef = useRef(null); const endInputDivRef = useRef(null); const separatorRef = useRef(null); const startInputRef = useRef(null); const endInputRef = useRef(null); // ============================= Misc ============================== const formatList = toArray(getDefaultFormat(format, picker, showTime, use12Hours)); // Active picker const [mergedActivePickerIndex, setMergedActivePickerIndex] = useMergedState<0 | 1>(0, { value: activePickerIndex, }); // Operation ref const operationRef: React.MutableRefObject = useRef< ContextOperationRefProps >(null); const mergedDisabled = React.useMemo<[boolean, boolean]>(() => { if (Array.isArray(disabled)) { return disabled; } return [disabled || false, disabled || false]; }, [disabled]); // ============================= Value ============================= const [mergedValue, setInnerValue] = useMergedState>(null, { value, defaultValue, postState: values => picker === 'time' && !order ? values : reorderValues(values, generateConfig), }); // =========================== View Date =========================== // Config view panel const [getViewDate, setViewDate] = useRangeViewDates({ values: mergedValue, picker, defaultDates: defaultPickerValue, generateConfig, }); // ========================= Select Values ========================= const [selectedValue, setSelectedValue] = useMergedState(mergedValue, { postState: values => { let postValues = values; if (mergedDisabled[0] && mergedDisabled[1]) { return postValues; } // Fill disabled unit for (let i = 0; i < 2; i += 1) { if (mergedDisabled[i] && !getValue(postValues, i) && !getValue(allowEmpty, i)) { postValues = updateValues(postValues, generateConfig.getNow(), i); } } return postValues; }, }); // ============================= Modes ============================= const [mergedModes, setInnerModes] = useMergedState<[PanelMode, PanelMode]>([picker, picker], { value: mode, }); useEffect(() => { setInnerModes([picker, picker]); }, [picker]); const triggerModesChange = (modes: [PanelMode, PanelMode], values: RangeValue) => { setInnerModes(modes); if (onPanelChange) { onPanelChange(values, modes); } }; // ========================= Disable Date ========================== const [disabledStartDate, disabledEndDate] = useRangeDisabled( { picker, selectedValue, locale, disabled: mergedDisabled, disabledDate, generateConfig, }, openRecordsRef.current[1], openRecordsRef.current[0], ); // ============================= Open ============================== const [mergedOpen, triggerInnerOpen] = useMergedState(false, { value: open, defaultValue: defaultOpen, postState: postOpen => (mergedDisabled[mergedActivePickerIndex] ? false : postOpen), onChange: newOpen => { if (onOpenChange) { onOpenChange(newOpen); } if (!newOpen && operationRef.current && operationRef.current.onClose) { operationRef.current.onClose(); } }, }); const startOpen = mergedOpen && mergedActivePickerIndex === 0; const endOpen = mergedOpen && mergedActivePickerIndex === 1; // ============================= Popup ============================= // Popup min width const [popupMinWidth, setPopupMinWidth] = useState(0); useEffect(() => { if (!mergedOpen && containerRef.current) { setPopupMinWidth(containerRef.current.offsetWidth); } }, [mergedOpen]); // ============================ Trigger ============================ const triggerRef = React.useRef(); function triggerOpen(newOpen: boolean, index: 0 | 1) { if (newOpen) { clearTimeout(triggerRef.current); openRecordsRef.current[index] = true; setMergedActivePickerIndex(index); triggerInnerOpen(newOpen); // Open to reset view date if (!mergedOpen) { setViewDate(null, index); } } else if (mergedActivePickerIndex === index) { triggerInnerOpen(newOpen); // Clean up async // This makes ref not quick refresh in case user open another input with blur trigger const openRecords = openRecordsRef.current; triggerRef.current = setTimeout(() => { if (openRecords === openRecordsRef.current) { openRecordsRef.current = {}; } }); } } function triggerOpenAndFocus(index: 0 | 1) { triggerOpen(true, index); // Use setTimeout to make sure panel DOM exists setTimeout(() => { const inputRef = [startInputRef, endInputRef][index]; if (inputRef.current) { inputRef.current.focus(); } }, 0); } function triggerChange(newValue: RangeValue, sourceIndex: 0 | 1) { let values = newValue; let startValue = getValue(values, 0); let endValue = getValue(values, 1); // >>>>> Format start & end values if (startValue && endValue && generateConfig.isAfter(startValue, endValue)) { if ( // WeekPicker only compare week (picker === 'week' && !isSameWeek(generateConfig, locale.locale, startValue, endValue)) || // QuotaPicker only compare week (picker === 'quarter' && !isSameQuarter(generateConfig, startValue, endValue)) || // Other non-TimePicker compare date (picker !== 'week' && picker !== 'quarter' && picker !== 'time' && !isSameDate(generateConfig, startValue, endValue)) ) { // Clean up end date when start date is after end date if (sourceIndex === 0) { values = [startValue, null]; endValue = null; } else { startValue = null; values = [null, endValue]; } // Clean up cache since invalidate openRecordsRef.current = { [sourceIndex]: true, }; } else if (picker !== 'time' || order !== false) { // Reorder when in same date values = reorderValues(values, generateConfig); } } setSelectedValue(values); const startStr = values && values[0] ? formatValue(values[0], { generateConfig, locale, format: formatList[0] }) : ''; const endStr = values && values[1] ? formatValue(values[1], { generateConfig, locale, format: formatList[0] }) : ''; if (onCalendarChange) { const info: RangeInfo = { range: sourceIndex === 0 ? 'start' : 'end' }; onCalendarChange(values, [startStr, endStr], info); } // >>>>> Trigger `onChange` event const canStartValueTrigger = canValueTrigger(startValue, 0, mergedDisabled, allowEmpty); const canEndValueTrigger = canValueTrigger(endValue, 1, mergedDisabled, allowEmpty); const canTrigger = values === null || (canStartValueTrigger && canEndValueTrigger); if (canTrigger) { // Trigger onChange only when value is validate setInnerValue(values); if ( onChange && (!isEqual(generateConfig, getValue(mergedValue, 0), startValue) || !isEqual(generateConfig, getValue(mergedValue, 1), endValue)) ) { onChange(values, [startStr, endStr]); } } // >>>>> Open picker when // Always open another picker if possible let nextOpenIndex: 0 | 1 = null; if (sourceIndex === 0 && !mergedDisabled[1]) { nextOpenIndex = 1; } else if (sourceIndex === 1 && !mergedDisabled[0]) { nextOpenIndex = 0; } if ( nextOpenIndex !== null && nextOpenIndex !== mergedActivePickerIndex && (!openRecordsRef.current[nextOpenIndex] || !getValue(values, nextOpenIndex)) && getValue(values, sourceIndex) ) { // Delay to focus to avoid input blur trigger expired selectedValues triggerOpenAndFocus(nextOpenIndex); } else { triggerOpen(false, sourceIndex); } } const forwardKeyDown = (e: React.KeyboardEvent) => { if (mergedOpen && operationRef.current && operationRef.current.onKeyDown) { // Let popup panel handle keyboard return operationRef.current.onKeyDown(e); } /* istanbul ignore next */ /* eslint-disable no-lone-blocks */ { warning( false, 'Picker not correct forward KeyDown operation. Please help to fire issue about this.', ); return false; } }; // ============================= Text ============================== const sharedTextHooksProps = { formatList, generateConfig, locale, }; const [startValueTexts, firstStartValueText] = useValueTexts( getValue(selectedValue, 0), sharedTextHooksProps, ); const [endValueTexts, firstEndValueText] = useValueTexts( getValue(selectedValue, 1), sharedTextHooksProps, ); const onTextChange = (newText: string, index: 0 | 1) => { const inputDate = parseValue(newText, { locale, formatList, generateConfig, }); const disabledFunc = index === 0 ? disabledStartDate : disabledEndDate; if (inputDate && !disabledFunc(inputDate)) { setSelectedValue(updateValues(selectedValue, inputDate, index)); setViewDate(inputDate, index); } }; const [startText, triggerStartTextChange, resetStartText] = useTextValueMapping({ valueTexts: startValueTexts, onTextChange: newText => onTextChange(newText, 0), }); const [endText, triggerEndTextChange, resetEndText] = useTextValueMapping({ valueTexts: endValueTexts, onTextChange: newText => onTextChange(newText, 1), }); const [rangeHoverValue, setRangeHoverValue] = useState>(null); // ========================== Hover Range ========================== const [hoverRangedValue, setHoverRangedValue] = useState>(null); const [startHoverValue, onStartEnter, onStartLeave] = useHoverValue(startText, { formatList, generateConfig, locale, }); const [endHoverValue, onEndEnter, onEndLeave] = useHoverValue(endText, { formatList, generateConfig, locale, }); const onDateMouseEnter = (date: DateType) => { setHoverRangedValue(updateValues(selectedValue, date, mergedActivePickerIndex)); if (mergedActivePickerIndex === 0) { onStartEnter(date); } else { onEndEnter(date); } }; const onDateMouseLeave = () => { setHoverRangedValue(updateValues(selectedValue, null, mergedActivePickerIndex)); if (mergedActivePickerIndex === 0) { onStartLeave(); } else { onEndLeave(); } }; // ============================= Input ============================= const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({ blurToCancel: needConfirmButton, forwardKeyDown, onBlur, isClickOutside: (target: EventTarget | null) => !elementsContains( [panelDivRef.current, startInputDivRef.current, endInputDivRef.current], target as HTMLElement, ), onFocus: (e: React.FocusEvent) => { setMergedActivePickerIndex(index); if (onFocus) { onFocus(e); } }, triggerOpen: (newOpen: boolean) => { triggerOpen(newOpen, index); }, onSubmit: () => { triggerChange(selectedValue, index); resetText(); }, onCancel: () => { triggerOpen(false, index); setSelectedValue(mergedValue); resetText(); }, }); const [startInputProps, { focused: startFocused, typing: startTyping }] = usePickerInput({ ...getSharedInputHookProps(0, resetStartText), open: startOpen, value: startText, onKeyDown: (e, preventDefault) => { onKeyDown?.(e, preventDefault); }, }); const [endInputProps, { focused: endFocused, typing: endTyping }] = usePickerInput({ ...getSharedInputHookProps(1, resetEndText), open: endOpen, value: endText, onKeyDown: (e, preventDefault) => { onKeyDown?.(e, preventDefault); }, }); // ========================== Click Picker ========================== const onPickerClick = (e: MouseEvent) => { // When click inside the picker & outside the picker's input elements // the panel should still be opened if ( !mergedOpen && !startInputRef.current.contains(e.target as Node) && !endInputRef.current.contains(e.target as Node) ) { if (!mergedDisabled[0]) { triggerOpenAndFocus(0); } else if (!mergedDisabled[1]) { triggerOpenAndFocus(1); } } }; const onPickerMouseDown = (e: MouseEvent) => { // shouldn't affect input elements if picker is active if ( mergedOpen && (startFocused || endFocused) && !startInputRef.current.contains(e.target as Node) && !endInputRef.current.contains(e.target as Node) ) { e.preventDefault(); } }; // ============================= Sync ============================== // Close should sync back with text value const startStr = mergedValue && mergedValue[0] ? formatValue(mergedValue[0], { locale, format: 'YYYYMMDDHHmmss', generateConfig, }) : ''; const endStr = mergedValue && mergedValue[1] ? formatValue(mergedValue[1], { locale, format: 'YYYYMMDDHHmmss', generateConfig, }) : ''; useEffect(() => { if (!mergedOpen) { setSelectedValue(mergedValue); if (!startValueTexts.length || startValueTexts[0] === '') { triggerStartTextChange(''); } else if (firstStartValueText !== startText) { resetStartText(); } if (!endValueTexts.length || endValueTexts[0] === '') { triggerEndTextChange(''); } else if (firstEndValueText !== endText) { resetEndText(); } } }, [mergedOpen, startValueTexts, endValueTexts]); // Sync innerValue with control mode useEffect(() => { setSelectedValue(mergedValue); }, [startStr, endStr]); // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { if ( value && Array.isArray(disabled) && ((getValue(disabled, 0) && !getValue(value, 0)) || (getValue(disabled, 1) && !getValue(value, 1))) ) { warning( false, '`disabled` should not set with empty `value`. You should set `allowEmpty` or `value` instead.', ); } } // ============================ Private ============================ if (pickerRef) { pickerRef.current = { focus: () => { if (startInputRef.current) { startInputRef.current.focus(); } }, blur: () => { if (startInputRef.current) { startInputRef.current.blur(); } if (endInputRef.current) { endInputRef.current.blur(); } }, }; } // ============================ Ranges ============================= const rangeLabels = Object.keys(ranges || {}); const rangeList = rangeLabels.map(label => { const range = ranges![label]; const newValues = typeof range === 'function' ? range() : range; return { label, onClick: () => { triggerChange(newValues, null); triggerOpen(false, mergedActivePickerIndex); }, onMouseEnter: () => { setRangeHoverValue(newValues); }, onMouseLeave: () => { setRangeHoverValue(null); }, }; }); // ============================= Panel ============================= function renderPanel( panelPosition: 'left' | 'right' | false = false, panelProps: Partial> = {}, ) { let panelHoverRangedValue: RangeValue = null; if ( mergedOpen && hoverRangedValue && hoverRangedValue[0] && hoverRangedValue[1] && generateConfig.isAfter(hoverRangedValue[1], hoverRangedValue[0]) ) { panelHoverRangedValue = hoverRangedValue; } let panelShowTime: | boolean | SharedTimeProps | undefined = showTime as SharedTimeProps; if (showTime && typeof showTime === 'object' && showTime.defaultValue) { const timeDefaultValues: DateType[] = showTime.defaultValue!; panelShowTime = { ...showTime, defaultValue: getValue(timeDefaultValues, mergedActivePickerIndex) || undefined, }; } let panelDateRender: DateRender | null = null; if (dateRender) { panelDateRender = (date, today) => dateRender(date, today, { range: mergedActivePickerIndex ? 'end' : 'start', }); } return ( {...(props as any)} {...panelProps} dateRender={panelDateRender} showTime={panelShowTime} mode={mergedModes[mergedActivePickerIndex]} generateConfig={generateConfig} style={undefined} direction={direction} disabledDate={mergedActivePickerIndex === 0 ? disabledStartDate : disabledEndDate} disabledTime={date => { if (disabledTime) { return disabledTime(date, mergedActivePickerIndex === 0 ? 'start' : 'end'); } return false; }} class={classNames({ [`${prefixCls}-panel-focused`]: mergedActivePickerIndex === 0 ? !startTyping : !endTyping, })} value={getValue(selectedValue, mergedActivePickerIndex)} locale={locale} tabIndex={-1} onPanelChange={(date, newMode) => { // clear hover value when panel change if (mergedActivePickerIndex === 0) { onStartLeave(true); } if (mergedActivePickerIndex === 1) { onEndLeave(true); } triggerModesChange( updateValues(mergedModes, newMode, mergedActivePickerIndex), updateValues(selectedValue, date, mergedActivePickerIndex), ); let viewDate = date; if (panelPosition === 'right' && mergedModes[mergedActivePickerIndex] === newMode) { viewDate = getClosingViewDate(viewDate, newMode as any, generateConfig, -1); } setViewDate(viewDate, mergedActivePickerIndex); }} onOk={null} onSelect={undefined} onChange={undefined} defaultValue={ mergedActivePickerIndex === 0 ? getValue(selectedValue, 1) : getValue(selectedValue, 0) } defaultPickerValue={undefined} /> ); } let arrowLeft: number = 0; let panelLeft: number = 0; if ( mergedActivePickerIndex && startInputDivRef.current && separatorRef.current && panelDivRef.current ) { // Arrow offset arrowLeft = startInputDivRef.current.offsetWidth + separatorRef.current.offsetWidth; if (panelDivRef.current.offsetWidth && arrowLeft > panelDivRef.current.offsetWidth) { panelLeft = arrowLeft; } } const arrowPositionStyle = direction === 'rtl' ? { right: arrowLeft } : { left: arrowLeft }; function renderPanels() { let panels: React.ReactNode; const extraNode = getExtraFooter( prefixCls, mergedModes[mergedActivePickerIndex], renderExtraFooter, ); const rangesNode = getRanges({ prefixCls, components, needConfirmButton, okDisabled: !getValue(selectedValue, mergedActivePickerIndex) || (disabledDate && disabledDate(selectedValue[mergedActivePickerIndex])), locale, rangeList, onOk: () => { if (getValue(selectedValue, mergedActivePickerIndex)) { // triggerChangeOld(selectedValue); triggerChange(selectedValue, mergedActivePickerIndex); if (onOk) { onOk(selectedValue); } } }, }); if (picker !== 'time' && !showTime) { const viewDate = getViewDate(mergedActivePickerIndex); const nextViewDate = getClosingViewDate(viewDate, picker, generateConfig); const currentMode = mergedModes[mergedActivePickerIndex]; const showDoublePanel = currentMode === picker; const leftPanel = renderPanel(showDoublePanel ? 'left' : false, { pickerValue: viewDate, onPickerValueChange: newViewDate => { setViewDate(newViewDate, mergedActivePickerIndex); }, }); const rightPanel = renderPanel('right', { pickerValue: nextViewDate, onPickerValueChange: newViewDate => { setViewDate( getClosingViewDate(newViewDate, picker, generateConfig, -1), mergedActivePickerIndex, ); }, }); if (direction === 'rtl') { panels = ( <> {rightPanel} {showDoublePanel && leftPanel} ); } else { panels = ( <> {leftPanel} {showDoublePanel && rightPanel} ); } } else { panels = renderPanel(); } let mergedNodes: React.ReactNode = ( <>
{panels}
{(extraNode || rangesNode) && (
{extraNode} {rangesNode}
)} ); if (panelRender) { mergedNodes = panelRender(mergedNodes); } return (
{ e.preventDefault(); }} > {mergedNodes}
); } const rangePanel = (
{renderPanels()}
); // ============================= Icons ============================= let suffixNode: React.ReactNode; if (suffixIcon) { suffixNode = {suffixIcon}; } let clearNode: React.ReactNode; if ( allowClear && ((getValue(mergedValue, 0) && !mergedDisabled[0]) || (getValue(mergedValue, 1) && !mergedDisabled[1])) ) { clearNode = ( { e.preventDefault(); e.stopPropagation(); }} onMouseUp={e => { e.preventDefault(); e.stopPropagation(); let values = mergedValue; if (!mergedDisabled[0]) { values = updateValues(values, null, 0); } if (!mergedDisabled[1]) { values = updateValues(values, null, 1); } triggerChange(values, null); triggerOpen(false, mergedActivePickerIndex); }} class={`${prefixCls}-clear`} > {clearIcon || } ); } const inputSharedProps = { size: getInputSize(picker, formatList[0], generateConfig), }; let activeBarLeft: number = 0; let activeBarWidth: number = 0; if (startInputDivRef.current && endInputDivRef.current && separatorRef.current) { if (mergedActivePickerIndex === 0) { activeBarWidth = startInputDivRef.current.offsetWidth; } else { activeBarLeft = arrowLeft; activeBarWidth = endInputDivRef.current.offsetWidth; } } const activeBarPositionStyle = direction === 'rtl' ? { right: activeBarLeft } : { left: activeBarLeft }; // ============================ Return ============================= const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { const values = updateValues(selectedValue, date, mergedActivePickerIndex); if (type === 'submit' || (type !== 'key' && !needConfirmButton)) { // triggerChange will also update selected values triggerChange(values, mergedActivePickerIndex); // clear hover value style if (mergedActivePickerIndex === 0) { onStartLeave(); } else { onEndLeave(); } } else { setSelectedValue(values); } }; return (
{ triggerStartTextChange(e.target.value); }} autoFocus={autoFocus} placeholder={getValue(placeholder, 0) || ''} ref={startInputRef} {...startInputProps} {...inputSharedProps} autoComplete={autoComplete} />
{separator}
{ triggerEndTextChange(e.target.value); }} placeholder={getValue(placeholder, 1) || ''} ref={endInputRef} {...endInputProps} {...inputSharedProps} autoComplete={autoComplete} />
{suffixNode} {clearNode}
); } // Wrap with class component to enable pass generic with instance method class RangePicker extends React.Component> { pickerRef = React.createRef(); focus = () => { if (this.pickerRef.current) { this.pickerRef.current.focus(); } }; blur = () => { if (this.pickerRef.current) { this.pickerRef.current.blur(); } }; render() { return ( {...this.props} pickerRef={this.pickerRef as React.MutableRefObject} /> ); } } export default RangePicker;