Skip to content

Commit 622c30b

Browse files
authored
Fix range picker click (#94)
* fix: mousedown can't open picker out of input elements * fix: click can't open picker out of input elements * test: click trigger range picker * chore * test: refine test cases * test: add description for wired case * test: fix focus by attachTo body * chore * chore * fix: typo * refactor: use setTimeout rather than Promise * chore * refactor * refactor * refactor * chore * test: remove useless beforeEach hooks * test: merge some cases
1 parent 07fc664 commit 622c30b

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

examples/range.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export default () => {
5454
allowClear
5555
ref={rangePickerRef}
5656
defaultValue={[moment('1990-09-03'), moment('1989-11-28')]}
57+
clearIcon={<span>X</span>}
58+
suffixIcon={<span>O</span>}
5759
/>
5860
<RangePicker<Moment>
5961
{...sharedProps}

src/RangePicker.tsx

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,17 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
374374
}
375375
}
376376

377+
function triggerOpenAndFocus(index: 0 | 1) {
378+
triggerOpen(true, index);
379+
// Use setTimeout to make sure panel DOM exists
380+
setTimeout(() => {
381+
const inputRef = [startInputRef, endInputRef][index];
382+
if (inputRef.current) {
383+
inputRef.current.focus();
384+
}
385+
}, 0);
386+
}
387+
377388
function triggerChange(newValue: RangeValue<DateType>, sourceIndex: 0 | 1) {
378389
let values = newValue;
379390
const startValue = getValue(values, 0);
@@ -454,15 +465,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
454465
!openRecordsRef.current[nextOpenIndex] &&
455466
getValue(values, sourceIndex)
456467
) {
457-
triggerOpen(true, nextOpenIndex);
458-
459468
// Delay to focus to avoid input blur trigger expired selectedValues
460-
setTimeout(() => {
461-
const inputRef = [startInputRef, endInputRef][nextOpenIndex];
462-
if (inputRef.current) {
463-
inputRef.current.focus();
464-
}
465-
}, 0);
469+
triggerOpenAndFocus(nextOpenIndex);
466470
} else {
467471
triggerOpen(false, sourceIndex);
468472
}
@@ -565,6 +569,35 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
565569
value: endText,
566570
});
567571

572+
// ========================== Click Picker ==========================
573+
const onPickerClick = (e: MouseEvent) => {
574+
// When click inside the picker & outside the picker's input elements
575+
// the panel should still be opened
576+
if (
577+
!mergedOpen &&
578+
!startInputRef.current.contains(e.target as Node) &&
579+
!endInputRef.current.contains(e.target as Node)
580+
) {
581+
if (!mergedDisabled[0]) {
582+
triggerOpenAndFocus(0);
583+
} else if (!mergedDisabled[1]) {
584+
triggerOpenAndFocus(1);
585+
}
586+
}
587+
};
588+
589+
const onPickerMouseDown = (e: MouseEvent) => {
590+
// shouldn't affect input elements if picker is active
591+
if (
592+
mergedOpen &&
593+
(startFocused || endFocused) &&
594+
!startInputRef.current.contains(e.target as Node) &&
595+
!endInputRef.current.contains(e.target as Node)
596+
) {
597+
e.preventDefault();
598+
}
599+
};
600+
568601
// ============================= Sync ==============================
569602
// Close should sync back with text value
570603
const startStr =
@@ -960,6 +993,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
960993
[`${prefixCls}-rtl`]: direction === 'rtl',
961994
})}
962995
style={style}
996+
onClick={onPickerClick}
997+
onMouseDown={onPickerMouseDown}
963998
{...getDataOrAriaProps(props)}
964999
>
9651000
<div

tests/range.spec.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,4 +1299,50 @@ describe('Picker.Range', () => {
12991299
expect(wrapper.isOpen()).toBeFalsy();
13001300
});
13011301
});
1302+
1303+
describe('click at non-input elements', () => {
1304+
it('should focus on the first element by default', () => {
1305+
jest.useFakeTimers();
1306+
const wrapper = mount(<MomentRangePicker />);
1307+
wrapper.find('.rc-picker').simulate('click');
1308+
expect(wrapper.isOpen()).toBeTruthy();
1309+
jest.runAllTimers();
1310+
expect(document.activeElement).toStrictEqual(
1311+
wrapper
1312+
.find('input')
1313+
.first()
1314+
.getDOMNode(),
1315+
);
1316+
jest.useRealTimers();
1317+
});
1318+
it('should focus on the second element if first is disabled', () => {
1319+
jest.useFakeTimers();
1320+
const wrapper = mount(<MomentRangePicker disabled={[true, false]} />);
1321+
wrapper.find('.rc-picker').simulate('click');
1322+
expect(wrapper.isOpen()).toBeTruthy();
1323+
jest.runAllTimers();
1324+
expect(document.activeElement).toStrictEqual(
1325+
wrapper
1326+
.find('input')
1327+
.last()
1328+
.getDOMNode(),
1329+
);
1330+
jest.useRealTimers();
1331+
});
1332+
it("shouldn't let mousedown blur the input", () => {
1333+
jest.useFakeTimers();
1334+
const preventDefault = jest.fn();
1335+
const wrapper = mount(<MomentRangePicker />, {
1336+
attachTo: document.body,
1337+
});
1338+
wrapper.find('.rc-picker').simulate('click');
1339+
jest.runAllTimers();
1340+
wrapper.find('.rc-picker').simulate('mousedown', {
1341+
preventDefault,
1342+
});
1343+
expect(wrapper.isOpen()).toBeTruthy();
1344+
expect(preventDefault).toHaveBeenCalled();
1345+
jest.useRealTimers();
1346+
});
1347+
});
13021348
});

0 commit comments

Comments
 (0)