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
10 changes: 9 additions & 1 deletion src/PickerInput/RangePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,15 @@ function RangePicker<DateType extends object = any>(
// ========================================================

// ======================== Change ========================
const onSelectorChange = (date: DateType, index: number) => {
const onSelectorChange = (date: DateType | null, index: number) => {
if (!date) {
resetRangeValueChange();
triggerSubmitChange(allowEmpty[index] ? fillCalendarValue(null, index) : null);
triggerOpen(false, { force: true });
onClear?.();
return;
}

triggerRangeValueChange(index, 'input', date);
};

Expand Down
14 changes: 12 additions & 2 deletions src/PickerInput/Selector/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
invalid?: boolean;

clearIcon?: React.ReactNode;
clearable?: boolean;
}

const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {
Expand All @@ -65,6 +66,7 @@ const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {
preserveInvalidOnBlur = false,
invalid,
clearIcon,
clearable,
// Pass to input
...restProps
} = props;
Expand Down Expand Up @@ -142,10 +144,18 @@ const Input = React.forwardRef<InputRef, InputProps>((props, ref) => {

// Directly trigger `onChange` if `format` is empty
const onInternalChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
const text = event.target.value;

// Empty text is a valid clear action when the picker is clearable.
// Handle it before the mask logic, which normally ignores invalid text.
if (clearable && !text && value) {
setInputValue(text);
onChange(text);
return;
}

// Hack `onChange` with format to do nothing
if (!format) {
const text = event.target.value;

onModify(text);
setInputValue(text);
onChange(text);
Expand Down
2 changes: 1 addition & 1 deletion src/PickerInput/Selector/RangeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface RangeSelectorProps<DateType = any> extends SelectorProps<DateTy
separator?: React.ReactNode;

value?: [DateType?, DateType?];
onChange: (date: DateType, index?: number) => void;
onChange: (date: DateType | null, index?: number) => void;

disabled: [boolean, boolean];

Expand Down
7 changes: 6 additions & 1 deletion src/PickerInput/Selector/SingleSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ function SingleSelector<DateType extends object = any>(
const rootProps = useRootProps(restProps);

// ======================== Change ========================
const onSingleChange = (date: DateType) => {
const onSingleChange = (date: DateType | null) => {
if (!date) {
onClear();
return;
}

onChange([date], 'input');
};

Expand Down
10 changes: 10 additions & 0 deletions src/PickerInput/Selector/hooks/useInputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function useInputProps<DateType extends object = any>(
| 'autoComplete'
| 'open'
| 'picker'
| 'clearIcon'
> & {
id?: string | string[];
value?: DateType[];
Expand Down Expand Up @@ -73,6 +74,7 @@ export default function useInputProps<DateType extends object = any>(
allHelp,

picker,
clearIcon,
} = props;

// ======================== Parser ========================
Expand Down Expand Up @@ -161,6 +163,8 @@ export default function useInputProps<DateType extends object = any>(

disabled: getProp(disabled),

clearable: !!clearIcon,

onFocus: (event) => {
onFocus(event, index);
},
Expand All @@ -184,6 +188,12 @@ export default function useInputProps<DateType extends object = any>(
return;
}

if (!text && clearIcon) {
onInvalid(false, index);
onChange(null, index);
return;
}

// Tell outer that the value typed is invalid.
// If text is empty, it means valid.
onInvalid(!!text, index);
Expand Down
37 changes: 37 additions & 0 deletions tests/picker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,43 @@ describe('Picker.Basic', () => {
});
});

it('clears a selected value when the input text is removed', () => {
const onChange = jest.fn();
const onClear = jest.fn();
const { container } = render(
<DayPicker
defaultValue={getDay('2000-11-11')}
format={{ format: 'YYYY-MM-DD', type: 'mask' }}
onChange={onChange}
onClear={onClear}
/>,
);

openPicker(container);
fireEvent.change(container.querySelector('input'), { target: { value: '' } });

expect(onChange).toHaveBeenCalledWith(null, null);
expect(onClear).toHaveBeenCalledTimes(1);
expect(container.querySelector('input')).toHaveValue('');
expect(isOpen()).toBeFalsy();
});

it('does not manually clear when allowClear is false', async () => {
const onChange = jest.fn();
const { container } = render(
<DayPicker defaultValue={getDay('2000-11-11')} onChange={onChange} allowClear={false} />,
);
const input = container.querySelector('input');

openPicker(container);
fireEvent.change(input, { target: { value: '' } });
fireEvent.blur(input);
await waitFakeTimer();

expect(onChange).not.toHaveBeenCalled();
expect(input).toHaveValue('2000-11-11');
});

// https://github.com/ant-design/ant-design/issues/49400
it('should not throw errow when input end year first', () => {
const { container } = render(<DayRangePicker picker="year" />);
Expand Down
42 changes: 42 additions & 0 deletions tests/range.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,48 @@ describe('Picker.Range', () => {
expect(onChange).not.toHaveBeenCalled();
});

it('clears the range when an input value is manually removed', () => {
const onChange = jest.fn();
const onClear = jest.fn();
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-11'), getDay('1990-09-23')]}
onChange={onChange}
onClear={onClear}
/>,
);

openPicker(container);
fireEvent.change(container.querySelectorAll('input')[0], { target: { value: '' } });

expect(onChange).toHaveBeenCalledWith(null, null);
expect(onClear).toHaveBeenCalledTimes(1);
matchValues(container, '', '');
expect(isOpen()).toBeFalsy();
});

it('keeps the other range value when the cleared field allows empty', () => {
const onChange = jest.fn();
const onClear = jest.fn();
const end = getDay('1990-09-23');
const { container } = render(
<DayRangePicker
defaultValue={[getDay('1990-09-11'), end]}
allowEmpty={[true, false]}
onChange={onChange}
onClear={onClear}
/>,
);

openPicker(container);
fireEvent.change(container.querySelectorAll('input')[0], { target: { value: '' } });

expect(onChange).toHaveBeenCalledWith([null, end], ['', '1990-09-23']);
expect(onClear).toHaveBeenCalledTimes(1);
matchValues(container, '', '1990-09-23');
expect(isOpen()).toBeFalsy();
});

describe('disabled', () => {
it('should no panel open with disabled', () => {
const { baseElement } = render(<DayRangePicker disabled />);
Expand Down