diff --git a/examples/range.tsx b/examples/range.tsx index 906959dbb..0544f8151 100644 --- a/examples/range.tsx +++ b/examples/range.tsx @@ -54,6 +54,8 @@ export default () => { allowClear ref={rangePickerRef} defaultValue={[moment('1990-09-03'), moment('1989-11-28')]} + clearIcon={X} + suffixIcon={O} /> {...sharedProps} diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index 5ca56f2a7..976203337 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -374,6 +374,17 @@ function InnerRangePicker(props: RangePickerProps) { } } + 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; const startValue = getValue(values, 0); @@ -454,15 +465,8 @@ function InnerRangePicker(props: RangePickerProps) { !openRecordsRef.current[nextOpenIndex] && getValue(values, sourceIndex) ) { - triggerOpen(true, nextOpenIndex); - // Delay to focus to avoid input blur trigger expired selectedValues - setTimeout(() => { - const inputRef = [startInputRef, endInputRef][nextOpenIndex]; - if (inputRef.current) { - inputRef.current.focus(); - } - }, 0); + triggerOpenAndFocus(nextOpenIndex); } else { triggerOpen(false, sourceIndex); } @@ -565,6 +569,35 @@ function InnerRangePicker(props: RangePickerProps) { value: endText, }); + // ========================== 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 = @@ -960,6 +993,8 @@ function InnerRangePicker(props: RangePickerProps) { [`${prefixCls}-rtl`]: direction === 'rtl', })} style={style} + onClick={onPickerClick} + onMouseDown={onPickerMouseDown} {...getDataOrAriaProps(props)} >
{ expect(wrapper.isOpen()).toBeFalsy(); }); }); + + describe('click at non-input elements', () => { + it('should focus on the first element by default', () => { + jest.useFakeTimers(); + const wrapper = mount(); + wrapper.find('.rc-picker').simulate('click'); + expect(wrapper.isOpen()).toBeTruthy(); + jest.runAllTimers(); + expect(document.activeElement).toStrictEqual( + wrapper + .find('input') + .first() + .getDOMNode(), + ); + jest.useRealTimers(); + }); + it('should focus on the second element if first is disabled', () => { + jest.useFakeTimers(); + const wrapper = mount(); + wrapper.find('.rc-picker').simulate('click'); + expect(wrapper.isOpen()).toBeTruthy(); + jest.runAllTimers(); + expect(document.activeElement).toStrictEqual( + wrapper + .find('input') + .last() + .getDOMNode(), + ); + jest.useRealTimers(); + }); + it("shouldn't let mousedown blur the input", () => { + jest.useFakeTimers(); + const preventDefault = jest.fn(); + const wrapper = mount(, { + attachTo: document.body, + }); + wrapper.find('.rc-picker').simulate('click'); + jest.runAllTimers(); + wrapper.find('.rc-picker').simulate('mousedown', { + preventDefault, + }); + expect(wrapper.isOpen()).toBeTruthy(); + expect(preventDefault).toHaveBeenCalled(); + jest.useRealTimers(); + }); + }); });