Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix range picker click #94

Merged
merged 18 commits into from Jun 29, 2020
2 changes: 2 additions & 0 deletions examples/range.tsx
Expand Up @@ -54,6 +54,8 @@ export default () => {
allowClear
ref={rangePickerRef}
defaultValue={[moment('1990-09-03'), moment('1989-11-28')]}
clearIcon={<span>X</span>}
suffixIcon={<span>O</span>}
/>
<RangePicker<Moment>
{...sharedProps}
Expand Down
51 changes: 43 additions & 8 deletions src/RangePicker.tsx
Expand Up @@ -374,6 +374,17 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
}
}

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<DateType>, sourceIndex: 0 | 1) {
let values = newValue;
const startValue = getValue(values, 0);
Expand Down Expand Up @@ -454,15 +465,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
!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);
}
Expand Down Expand Up @@ -565,6 +569,35 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
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 =
Expand Down Expand Up @@ -960,6 +993,8 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
style={style}
onClick={onPickerClick}
onMouseDown={onPickerMouseDown}
{...getDataOrAriaProps(props)}
>
<div
Expand Down
53 changes: 53 additions & 0 deletions tests/range.spec.tsx
Expand Up @@ -1299,4 +1299,57 @@ describe('Picker.Range', () => {
expect(wrapper.isOpen()).toBeFalsy();
});
});

describe('click at non-input elements', () => {
it('should open when click suffix', () => {
const wrapper = mount(<MomentRangePicker suffixIcon="o" />);
wrapper.find('.rc-picker-suffix').simulate('click');
expect(wrapper.isOpen()).toBeTruthy();
});
it('should open when click seperator', () => {
const wrapper = mount(<MomentRangePicker suffixIcon="o" />);
wrapper.find('.rc-picker-range-separator').simulate('click');
expect(wrapper.isOpen()).toBeTruthy();
});
it('should focus on the first element', () => {
07akioni marked this conversation as resolved.
Show resolved Hide resolved
jest.useFakeTimers();
const wrapper = mount(<MomentRangePicker suffixIcon="o" />);
wrapper.find('.rc-picker-suffix').simulate('click');
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(<MomentRangePicker suffixIcon="o" disabled={[true, false]} />);
wrapper.find('.rc-picker-suffix').simulate('click');
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(<MomentRangePicker suffixIcon="o" />, {
attachTo: document.body,
});
wrapper.find('.rc-picker-suffix').simulate('click');
jest.runAllTimers();
wrapper.find('.rc-picker-suffix').simulate('mousedown', {
preventDefault,
});
expect(preventDefault).toHaveBeenCalled();
jest.useRealTimers();
});
});
});