diff --git a/src/PickerInput/RangePicker.tsx b/src/PickerInput/RangePicker.tsx index 36c2dda2e..3b7e6776f 100644 --- a/src/PickerInput/RangePicker.tsx +++ b/src/PickerInput/RangePicker.tsx @@ -665,7 +665,15 @@ function RangePicker( // ======================================================== // ======================== 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); }; diff --git a/src/PickerInput/Selector/Input.tsx b/src/PickerInput/Selector/Input.tsx index 38a1fbe2a..6882bace6 100644 --- a/src/PickerInput/Selector/Input.tsx +++ b/src/PickerInput/Selector/Input.tsx @@ -46,6 +46,7 @@ export interface InputProps extends Omit((props, ref) => { @@ -65,6 +66,7 @@ const Input = React.forwardRef((props, ref) => { preserveInvalidOnBlur = false, invalid, clearIcon, + clearable, // Pass to input ...restProps } = props; @@ -142,10 +144,18 @@ const Input = React.forwardRef((props, ref) => { // Directly trigger `onChange` if `format` is empty const onInternalChange: React.ChangeEventHandler = (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); diff --git a/src/PickerInput/Selector/RangeSelector.tsx b/src/PickerInput/Selector/RangeSelector.tsx index 7208fbc72..3dff7bf27 100644 --- a/src/PickerInput/Selector/RangeSelector.tsx +++ b/src/PickerInput/Selector/RangeSelector.tsx @@ -30,7 +30,7 @@ export interface RangeSelectorProps extends SelectorProps void; + onChange: (date: DateType | null, index?: number) => void; disabled: [boolean, boolean]; diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index c2d7c61b2..9c796be84 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -131,7 +131,12 @@ function SingleSelector( const rootProps = useRootProps(restProps); // ======================== Change ======================== - const onSingleChange = (date: DateType) => { + const onSingleChange = (date: DateType | null) => { + if (!date) { + onClear(); + return; + } + onChange([date], 'input'); }; diff --git a/src/PickerInput/Selector/hooks/useInputProps.ts b/src/PickerInput/Selector/hooks/useInputProps.ts index 605551afb..48d9b237c 100644 --- a/src/PickerInput/Selector/hooks/useInputProps.ts +++ b/src/PickerInput/Selector/hooks/useInputProps.ts @@ -27,6 +27,7 @@ export default function useInputProps( | 'autoComplete' | 'open' | 'picker' + | 'clearIcon' > & { id?: string | string[]; value?: DateType[]; @@ -73,6 +74,7 @@ export default function useInputProps( allHelp, picker, + clearIcon, } = props; // ======================== Parser ======================== @@ -161,6 +163,8 @@ export default function useInputProps( disabled: getProp(disabled), + clearable: !!clearIcon, + onFocus: (event) => { onFocus(event, index); }, @@ -184,6 +188,12 @@ export default function useInputProps( 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); diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx index b33fa2fa4..7ab0a2933 100644 --- a/tests/picker.spec.tsx +++ b/tests/picker.spec.tsx @@ -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( + , + ); + + 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( + , + ); + 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(); diff --git a/tests/range.spec.tsx b/tests/range.spec.tsx index 43ebad195..079f2964d 100644 --- a/tests/range.spec.tsx +++ b/tests/range.spec.tsx @@ -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( + , + ); + + 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( + , + ); + + 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();