Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ export type RangeValueType<DateType> = [
/** Used for change event, it should always be not undefined */
export type NoUndefinedRangeValueType<DateType> = [start: DateType | null, end: DateType | null];

export interface BaseRangePickerProps<DateType extends object>
extends Omit<SharedPickerProps<DateType>, 'showTime' | 'id'> {
export interface BaseRangePickerProps<DateType extends object> extends Omit<
SharedPickerProps<DateType>,
'showTime' | 'id'
> {
// Structure
id?: SelectorIdType;

Expand Down Expand Up @@ -132,7 +134,8 @@ export interface BaseRangePickerProps<DateType extends object>
}

export interface RangePickerProps<DateType extends object>
extends BaseRangePickerProps<DateType>,
extends
BaseRangePickerProps<DateType>,
Omit<RangeTimeProps<DateType>, 'format' | 'defaultValue' | 'defaultOpenValue'> {}

function getActiveRange(activeIndex: number) {
Expand Down Expand Up @@ -223,6 +226,7 @@ function RangePicker<DateType extends object = any>(

// Native
onClick,
onMouseDown,
} = filledProps;

// ========================= Refs =========================
Expand Down Expand Up @@ -269,6 +273,8 @@ function RangePicker<DateType extends object = any>(
updateSubmitIndex,
hasActiveSubmitValue,
] = useRangeActive(disabled, allowEmpty, mergedOpen);
const pendingKeyboardSwitchRef = React.useRef(false);
const keyboardSwitchInputRef = React.useRef(false);

const onSharedFocus = (event: React.FocusEvent<HTMLElement>, index?: number) => {
triggerFocus(true);
Expand Down Expand Up @@ -666,6 +672,9 @@ function RangePicker<DateType extends object = any>(
return;
}

keyboardSwitchInputRef.current = pendingKeyboardSwitchRef.current;
pendingKeyboardSwitchRef.current = false;

lastOperation('input');

triggerOpen(true, {
Expand All @@ -685,6 +694,14 @@ function RangePicker<DateType extends object = any>(
};

const onSelectorBlur: SelectorProps['onBlur'] = (event, index) => {
const relatedTarget = event.relatedTarget as Node | null;
if (
pendingKeyboardSwitchRef.current &&
!selectorRef.current.nativeElement.contains(relatedTarget)
) {
pendingKeyboardSwitchRef.current = false;
}

triggerOpen(false);
if (!needConfirm && lastOperation() === 'input') {
const nextIndex = nextActiveIndex(calendarValue);
Expand All @@ -694,8 +711,23 @@ function RangePicker<DateType extends object = any>(
onSharedBlur(event, index);
};

const onSelectorMouseDown: React.MouseEventHandler<HTMLDivElement> = (event) => {
const target = event.target as HTMLElement;
const rootNode = target.getRootNode();
const activeElement =
(rootNode as Document | ShadowRoot).activeElement ?? document.activeElement;

if (target.tagName === 'INPUT' && target !== activeElement) {
pendingKeyboardSwitchRef.current = false;
keyboardSwitchInputRef.current = false;
}

onMouseDown?.(event);
};

const onSelectorKeyDown: SelectorProps['onKeyDown'] = (event, preventDefault) => {
if (event.key === 'Tab') {
pendingKeyboardSwitchRef.current = true;
triggerPartConfirm(null, true);
}

Expand Down Expand Up @@ -739,7 +771,8 @@ function RangePicker<DateType extends object = any>(
const lastOp = lastOperation();

// Trade as confirm on field leave
if (!mergedOpen && lastOp === 'input') {
if (!mergedOpen && lastOp === 'input' && (!needConfirm || keyboardSwitchInputRef.current)) {
keyboardSwitchInputRef.current = false;
triggerOpen(false);
triggerPartConfirm(null, true);
}
Expand Down Expand Up @@ -822,6 +855,7 @@ function RangePicker<DateType extends object = any>(
onOpenChange={triggerOpen}
// Click
onClick={onSelectorClick}
onMouseDown={onSelectorMouseDown}
onClear={onSelectorClear}
// Invalid
invalid={submitInvalidates}
Expand Down
180 changes: 180 additions & 0 deletions tests/range.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,186 @@ describe('Picker.Range', () => {
expect(document.querySelector('input').value).toEqual('');
});

it('should not submit unconfirmed values on blur when allowEmpty lets fields switch', () => {
const onChange = jest.fn();
const onCalendarChange = jest.fn();
const { container } = render(
<DayRangePicker
showTime
allowEmpty
onChange={onChange}
onCalendarChange={onCalendarChange}
/>,
);

openPicker(container, 0);
selectCell(11);
expect(onCalendarChange).toHaveBeenCalledWith(expect.anything(), ['1990-09-11 00:00:00', ''], {
range: 'start',
});

openPicker(container, 1);
openPicker(container, 0);

fireEvent.mouseDown(document.body);
container.querySelectorAll('input')[0].blur();

for (let i = 0; i < 5; i += 1) {
act(() => {
jest.runAllTimers();
});
}

expect(onChange).not.toHaveBeenCalled();
matchValues(container, '', '');
});

it('should not submit typed values on blur before confirm', () => {
const onChange = jest.fn();
const { container } = render(<DayRangePicker showTime allowEmpty onChange={onChange} />);

const startInput = container.querySelectorAll<HTMLInputElement>('input')[0];

startInput.focus();
fireEvent.change(startInput, {
target: {
value: '1990-09-11 00:00:00',
},
});

fireEvent.mouseDown(document.body);
startInput.blur();

for (let i = 0; i < 5; i += 1) {
act(() => {
jest.runAllTimers();
});
}

expect(onChange).not.toHaveBeenCalled();
matchValues(container, '', '');
});

it('should submit typed values on blur after keyboard switch to next input', () => {
const onChange = jest.fn();
const { container } = render(<DayRangePicker showTime allowEmpty onChange={onChange} />);

const [startInput, endInput] = container.querySelectorAll<HTMLInputElement>('input');

startInput.focus();
fireEvent.change(startInput, {
target: {
value: '1990-09-11 00:00:00',
},
});
fireEvent.keyDown(startInput, {
key: 'Tab',
});

endInput.focus();
fireEvent.change(endInput, {
target: {
value: '1990-09-12 00:00:00',
},
});

fireEvent.mouseDown(document.body);
endInput.blur();

for (let i = 0; i < 5; i += 1) {
act(() => {
jest.runAllTimers();
});
}

expect(onChange).toHaveBeenCalledWith(expect.anything(), [
'1990-09-11 00:00:00',
'1990-09-12 00:00:00',
]);
matchValues(container, '1990-09-11 00:00:00', '1990-09-12 00:00:00');
});

it('should not confirm typed end value on blur after mouse switching to next input', () => {
const onChange = jest.fn();
const { container } = render(<DayRangePicker showTime allowEmpty onChange={onChange} />);

const [startInput, endInput] = container.querySelectorAll<HTMLInputElement>('input');

startInput.focus();
fireEvent.change(startInput, {
target: {
value: '1990-09-11 00:00:00',
},
});
fireEvent.keyDown(startInput, {
key: 'Tab',
});

fireEvent.mouseDown(endInput);
endInput.focus();
fireEvent.change(endInput, {
target: {
value: '1990-09-12 00:00:00',
},
});

fireEvent.mouseDown(document.body);
endInput.blur();

for (let i = 0; i < 5; i += 1) {
act(() => {
jest.runAllTimers();
});
}

expect(onChange).not.toHaveBeenCalledWith(expect.anything(), [
'1990-09-11 00:00:00',
'1990-09-12 00:00:00',
]);
expect(onChange).toHaveBeenCalled();
matchValues(container, '1990-09-11 00:00:00', '');
});

it('should keep keyboard switch allowance when clicking inside the current input', () => {
const onChange = jest.fn();
const { container } = render(<DayRangePicker showTime allowEmpty onChange={onChange} />);

const [startInput, endInput] = container.querySelectorAll<HTMLInputElement>('input');

startInput.focus();
fireEvent.change(startInput, {
target: {
value: '1990-09-11 00:00:00',
},
});
fireEvent.keyDown(startInput, {
key: 'Tab',
});

endInput.focus();
fireEvent.change(endInput, {
target: {
value: '1990-09-12 00:00:00',
},
});

fireEvent.mouseDown(endInput);
fireEvent.mouseDown(document.body);
endInput.blur();

for (let i = 0; i < 5; i += 1) {
act(() => {
jest.runAllTimers();
});
}

expect(onChange).toHaveBeenCalledWith(expect.anything(), [
'1990-09-11 00:00:00',
'1990-09-12 00:00:00',
]);
matchValues(container, '1990-09-11 00:00:00', '1990-09-12 00:00:00');
});

describe('viewDate', () => {
function matchTitle(title: string) {
expect(document.querySelector('.rc-picker-header-view').textContent).toEqual(title);
Expand Down
Loading