diff --git a/src/PickerInput/Popup/PopupPanel.tsx b/src/PickerInput/Popup/PopupPanel.tsx index e59b4fba3..26aad7765 100644 --- a/src/PickerInput/Popup/PopupPanel.tsx +++ b/src/PickerInput/Popup/PopupPanel.tsx @@ -30,6 +30,7 @@ export default function PopupPanel( onSubmit, range, hoverValue, + hoverRangeValue, } = props; const { prefixCls, generateConfig } = React.useContext(PickerContext); @@ -70,10 +71,9 @@ export default function PopupPanel( hideHeader, }; + pickerProps.hoverValue = hoverValue; if (range) { - pickerProps.hoverRangeValue = hoverValue; - } else { - pickerProps.hoverValue = hoverValue; + pickerProps.hoverRangeValue = hoverRangeValue; } // ======================== Render ======================== diff --git a/src/PickerInput/RangePicker.tsx b/src/PickerInput/RangePicker.tsx index 36c2dda2e..d4540032e 100644 --- a/src/PickerInput/RangePicker.tsx +++ b/src/PickerInput/RangePicker.tsx @@ -24,6 +24,7 @@ import type { import type { PickerPanelProps } from '../PickerPanel'; import PickerTrigger from '../PickerTrigger'; import { pickTriggerProps } from '../PickerTrigger/util'; +import { isSameTimestamp } from '../utils/dateUtil'; import { fillIndex, getFromDate, toArray } from '../utils/miscUtil'; import PickerContext from './context'; import useCellRender from './hooks/useCellRender'; @@ -502,6 +503,17 @@ function RangePicker( return internalHoverValues || calendarValue; }, [calendarValue, internalHoverValues]); + // "Weak" hover only highlights the hovered cell instead of composing a range. + // Use it while choosing the first value so the pending selection remains selected. + const showWeakHover = + // Preset hover always previews the whole range. + hoverSource === 'cell' && + // Once the other field has a value, range hover takes precedence. + !calendarValue[(activeIndex + 1) % 2] && + // Only a changed active value needs weak hover. + !isSameTimestamp(generateConfig, calendarValue[activeIndex], mergedValue[activeIndex]); + const activeHoverValue = internalHoverValues?.[activeIndex]; + // Clean up `internalHoverValues` when closed React.useEffect(() => { if (!mergedOpen) { @@ -640,7 +652,8 @@ function RangePicker( defaultOpenValue={toArray(showTime?.defaultOpenValue)[activeIndex]} onPickerValueChange={setCurrentPickerValue} // Hover - hoverValue={hoverValues} + hoverValue={showWeakHover && activeHoverValue ? [activeHoverValue] : null} + hoverRangeValue={showWeakHover ? null : hoverValues} onHover={onPanelHover} // Submit needConfirm={needConfirm} diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx index 43ebad195..21e08a3f3 100644 --- a/tests/range.spec.tsx +++ b/tests/range.spec.tsx @@ -768,6 +768,41 @@ describe('Picker.Range', () => { expect(findCell(end)).not.toHaveClass('rc-picker-cell-range-end'); }); }); + + it.each([true, false])( + 'should keep the pending selection when hovering with needConfirm=%s', + async (needConfirm) => { + const { container } = render(); + openPicker(container); + selectCell(11); + + expect(findCell(11)).toHaveClass('rc-picker-cell-range-start'); + + fireEvent.mouseEnter(findCell(22)); + await waitFakeTimer(); + + expect(container.querySelectorAll('input')[0]).toHaveValue('1990-09-22 00:00:00'); + expect(findCell(11)).toHaveClass('rc-picker-cell-selected'); + expect(findCell(11)).not.toHaveClass('rc-picker-cell-range-start'); + expect(findCell(22)).toHaveClass('rc-picker-cell-hover'); + expect(findCell(22)).not.toHaveClass('rc-picker-cell-range-start'); + }, + ); + + it('should keep range hover after the first field is confirmed', async () => { + const { container } = render(); + openPicker(container); + selectCell(11); + fireEvent.click(document.querySelector('.rc-picker-ok button')); + selectCell(22); + + fireEvent.mouseEnter(findCell(25)); + await waitFakeTimer(); + + expect(findCell(11)).toHaveClass('rc-picker-cell-range-start'); + expect(findCell(15)).toHaveClass('rc-picker-cell-in-range'); + expect(findCell(25)).toHaveClass('rc-picker-cell-range-end'); + }); }); it('should close when user focus out', () => {