diff --git a/src/PickerInput/Popup/PopupPanel.tsx b/src/PickerInput/Popup/PopupPanel.tsx index e59b4fba3..e83b096cf 100644 --- a/src/PickerInput/Popup/PopupPanel.tsx +++ b/src/PickerInput/Popup/PopupPanel.tsx @@ -14,6 +14,7 @@ export type PopupPanelProps = MustProp FooterProps & { multiplePanel?: boolean; range?: boolean; + confirmOnDoubleClick: boolean; onPickerValueChange: (date: DateType) => void; }; @@ -27,6 +28,7 @@ export default function PopupPanel( pickerValue, onPickerValueChange, needConfirm, + confirmOnDoubleClick, onSubmit, range, hoverValue, @@ -54,7 +56,7 @@ export default function PopupPanel( // ======================= Context ======================== const sharedContext: PickerHackContextProps = { onCellDblClick: () => { - if (needConfirm) { + if (needConfirm && confirmOnDoubleClick) { onSubmit(); } }, diff --git a/src/PickerInput/RangePicker.tsx b/src/PickerInput/RangePicker.tsx index 4daadcae0..2a6457dbe 100644 --- a/src/PickerInput/RangePicker.tsx +++ b/src/PickerInput/RangePicker.tsx @@ -56,8 +56,10 @@ export type RangeValueType = [ /** Used for change event, it should always be not undefined */ export type NoUndefinedRangeValueType = [start: DateType | null, end: DateType | null]; -export interface BaseRangePickerProps - extends Omit, 'showTime' | 'id'> { +export interface BaseRangePickerProps extends Omit< + SharedPickerProps, + 'showTime' | 'id' +> { // Structure id?: SelectorIdType; @@ -132,7 +134,8 @@ export interface BaseRangePickerProps } export interface RangePickerProps - extends BaseRangePickerProps, + extends + BaseRangePickerProps, Omit, 'format' | 'defaultValue' | 'defaultOpenValue'> {} function getActiveRange(activeIndex: number) { @@ -169,6 +172,7 @@ function RangePicker( defaultValue, value, needConfirm, + confirmOnDoubleClick, onKeyDown, // Disabled @@ -617,6 +621,7 @@ function RangePicker( onHover={onPanelHover} // Submit needConfirm={needConfirm} + confirmOnDoubleClick={confirmOnDoubleClick} onSubmit={triggerPartConfirm} onOk={triggerOk} // Preset diff --git a/src/PickerInput/SinglePicker.tsx b/src/PickerInput/SinglePicker.tsx index df2a86a81..2f8176bf3 100644 --- a/src/PickerInput/SinglePicker.tsx +++ b/src/PickerInput/SinglePicker.tsx @@ -36,8 +36,9 @@ import useSemantic from '../hooks/useSemantic'; // TODO: isInvalidateDate with showTime.disabledTime should not provide `range` prop -export interface BasePickerProps - extends SharedPickerProps { +export interface BasePickerProps< + DateType extends object = any, +> extends SharedPickerProps { // Structure id?: string; @@ -100,8 +101,7 @@ export interface BasePickerProps } export interface PickerProps - extends BasePickerProps, - Omit, 'format' | 'defaultValue'> {} + extends BasePickerProps, Omit, 'format' | 'defaultValue'> {} /** Internal usage. For cross function get same aligned props */ export type ReplacedPickerProps = { @@ -135,6 +135,7 @@ function Picker( defaultValue, value, needConfirm, + confirmOnDoubleClick, onChange, onKeyDown, @@ -531,6 +532,7 @@ function Picker( onHover={onPanelHover} // Submit needConfirm={needConfirm} + confirmOnDoubleClick={confirmOnDoubleClick} onSubmit={triggerConfirm} onOk={triggerOk} // Preset diff --git a/src/PickerInput/hooks/useFilledProps.ts b/src/PickerInput/hooks/useFilledProps.ts index 8cdd895cd..b0830afc2 100644 --- a/src/PickerInput/hooks/useFilledProps.ts +++ b/src/PickerInput/hooks/useFilledProps.ts @@ -50,6 +50,22 @@ type GetGeneric = T extends PickedProps ? U : never; type ToArrayType = T extends any[] ? T : DateType[]; +type FilledProps< + InProps extends PickedProps, + DateType extends GetGeneric, + UpdaterProps extends object, +> = Omit & + UpdaterProps & { + picker: PickerMode; + showTime?: ExcludeBooleanType; + needConfirm: boolean; + confirmOnDoubleClick: boolean; + value?: ToArrayType; + defaultValue?: ToArrayType; + pickerValue?: ToArrayType; + defaultPickerValue?: ToArrayType; + }; + function useList(value: T | T[], fillMode = false) { const values = React.useMemo(() => { const list = value ? toArray(value) : value; @@ -77,15 +93,7 @@ export default function useFilledProps< props: InProps, updater?: () => UpdaterProps, ): [ - filledProps: Omit & - UpdaterProps & { - picker: PickerMode; - showTime?: ExcludeBooleanType; - value?: ToArrayType; - defaultValue?: ToArrayType; - pickerValue?: ToArrayType; - defaultPickerValue?: ToArrayType; - }, + filledProps: FilledProps, internalPicker: InternalMode, complexPicker: boolean, formatList: FormatType[], @@ -132,7 +140,10 @@ export default function useFilledProps< /** The picker is `datetime` or `time` */ const multipleInteractivePicker = internalPicker === 'time' || internalPicker === 'datetime'; const complexPicker = multipleInteractivePicker || multiple; - const mergedNeedConfirm = needConfirm ?? multipleInteractivePicker; + const needConfirmConfig = + typeof needConfirm === 'object' && needConfirm !== null ? needConfirm : null; + const mergedNeedConfirm = needConfirmConfig ? true : (needConfirm ?? multipleInteractivePicker); + const mergedConfirmOnDoubleClick = needConfirmConfig?.confirmOnDoubleClick ?? true; // ========================== Time ========================== // Auto `format` need to check `showTime.showXXX` first. @@ -206,14 +217,22 @@ export default function useFilledProps< ); // ======================== Merged ======================== - const mergedProps = React.useMemo( - () => ({ - ...filledProps, - needConfirm: mergedNeedConfirm, - inputReadOnly: mergedInputReadOnly, - disabledDate: disabledBoundaryDate, - }), - [filledProps, mergedNeedConfirm, mergedInputReadOnly, disabledBoundaryDate], + const mergedProps = React.useMemo>( + () => + ({ + ...filledProps, + needConfirm: mergedNeedConfirm, + confirmOnDoubleClick: mergedConfirmOnDoubleClick, + inputReadOnly: mergedInputReadOnly, + disabledDate: disabledBoundaryDate, + }) as FilledProps, + [ + filledProps, + mergedNeedConfirm, + mergedConfirmOnDoubleClick, + mergedInputReadOnly, + disabledBoundaryDate, + ], ); return [mergedProps, internalPicker, complexPicker, formatList, maskFormat, isInvalidateDate]; diff --git a/src/interface.tsx b/src/interface.tsx index 964f80c2b..cf713bf53 100644 --- a/src/interface.tsx +++ b/src/interface.tsx @@ -315,6 +315,12 @@ export type SemanticName = 'root' | 'prefix' | 'input' | 'suffix'; export type PreviewValueType = 'hover'; +export type NeedConfirmConfig = + | boolean + | { + confirmOnDoubleClick?: boolean; + }; + export type PanelSemanticName = | 'root' | 'header' @@ -325,7 +331,8 @@ export type PanelSemanticName = | 'container'; export interface SharedPickerProps - extends SharedHTMLAttrs, + extends + SharedHTMLAttrs, Pick< SharedPanelProps, // Icon @@ -418,8 +425,9 @@ export interface SharedPickerProps * By default. Only `time` or `datetime` show the confirm button in panel. * `true` to make every picker need confirm. * `false` to trigger change on every time panel closed by the mode = picker. + * Config mode can customize how explicit confirm behaves. */ - needConfirm?: boolean; + needConfirm?: NeedConfirmConfig; /** * @deprecated. This is removed and not work anymore. diff --git a/tests/new-range.spec.tsx b/tests/new-range.spec.tsx index 4d80c6a4b..7354c921e 100644 --- a/tests/new-range.spec.tsx +++ b/tests/new-range.spec.tsx @@ -753,7 +753,9 @@ describe('NewPicker.Range', () => { it('double click to confirm if needConfirm', () => { const onChange = jest.fn(); - const { container } = render(); + const { container } = render( + , + ); openPicker(container); fireEvent.click(findCell(5)); @@ -773,6 +775,33 @@ describe('NewPicker.Range', () => { ]); }); + it('double click should not confirm if confirmOnDoubleClick is false', () => { + const onChange = jest.fn(); + + const { container } = render( + , + ); + + openPicker(container); + + const li = document.querySelector('.rc-picker-time-panel-column').querySelectorAll('li')[11]; + fireEvent.click(li); + fireEvent.doubleClick(li); + + act(() => { + jest.runAllTimers(); + }); + + expect(container.querySelectorAll('input')[0]).toHaveValue('11:00:00'); + expect(container.querySelectorAll('input')[1]).toHaveValue(''); + expect(onChange).not.toHaveBeenCalled(); + expect(isOpen()).toBeTruthy(); + }); + it('double click should not take action if !needConfirm', () => { const { container } = render(); diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx index 76bca254c..29adf4a6b 100644 --- a/tests/picker.spec.tsx +++ b/tests/picker.spec.tsx @@ -520,6 +520,24 @@ describe('Picker.Basic', () => { expect(isSame(onOk.mock.calls[0][0], '1990-09-03 13:22:33', 'second')).toBeTruthy(); expect(isSame(onChange.mock.calls[0][0], '1990-09-03 13:22:33', 'second')).toBeTruthy(); }); + + it('should not submit on double click when confirmOnDoubleClick is false', () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + openPicker(container); + fireEvent.click(findCell(5)); + fireEvent.doubleClick(findCell(5)); + + act(() => { + jest.runAllTimers(); + }); + + expect(onChange).not.toHaveBeenCalled(); + expect(isOpen()).toBeTruthy(); + }); }); it('renderExtraFooter', () => {