diff --git a/README.md b/README.md index 88a718a6d..d3b2e29ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# rc-picker +# rc-picker [![NPM version][npm-image]][npm-url] [![build status][github-actions-image]][github-actions-url] @@ -76,8 +76,9 @@ render(, mountNode); | getPopupContainer | function(trigger) | | to set the container of the floating layer, while the default is to create a div element in body | | onChange | Function(date: moment, dateString: string) | | a callback function, can be executed when the selected time is changing | | onOpenChange | Function(open:boolean) | | called when open/close picker | -| onFocus | (evnet:React.FocusEventHandler) => void | | called like input's on focus | -| onBlur | (evnet:React.FocusEventHandler) => void | | called like input's on blur | +| onFocus | (event:React.FocusEvent) => void | | called like input's on focus | +| onBlur | (event:React.FocusEvent) => void | | called like input's on blur | +| onKeyDown | (event:React.KeyboardEvent, preventDefault: () => void) => void | | input on keydown event | | direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. | ### PickerPanel @@ -102,7 +103,7 @@ render(, mountNode); | renderExtraFooter | (mode) => React.Node | | extra footer | | onSelect | Function(date: moment) | | a callback function, can be executed when the selected time | | onPanelChange | Function(value: moment, mode) | | callback when picker panel mode is changed | -| onMouseDown | (evnet:React.MouseEventHandler) => void | | callback when executed onMouseDown evnent | +| onMouseDown | (event:React.MouseEvent) => void | | callback when executed onMouseDown evnent | | direction | String: ltr or rtl | | Layout direction of picker component, it supports RTL direction too. | ### RangePicker diff --git a/examples/basic.tsx b/examples/basic.tsx index 7885a2ec1..a68704a96 100644 --- a/examples/basic.tsx +++ b/examples/basic.tsx @@ -29,6 +29,10 @@ export default () => { onChange, }; + const keyDown = (e, preventDefault) => { + if (e.keyCode === 13) preventDefault(); + }; + return (

Value: {value ? value.format('YYYY-MM-DD HH:mm:ss') : 'null'}

@@ -125,6 +129,10 @@ export default () => {

Keyboard navigation (Tab key) disabled

{...sharedProps} locale={enUS} tabIndex={-1} />
+
+

Keyboard event with prevent default behaviors

+ {...sharedProps} locale={enUS} onKeyDown={keyDown} /> +
); diff --git a/src/Picker.tsx b/src/Picker.tsx index 953d9894f..6201b91b6 100644 --- a/src/Picker.tsx +++ b/src/Picker.tsx @@ -77,6 +77,7 @@ export interface PickerSharedProps extends React.AriaAttributes { onMouseLeave?: React.MouseEventHandler; onClick?: React.MouseEventHandler; onContextMenu?: React.MouseEventHandler; + onKeyDown?: (event: React.KeyboardEvent, preventDefault: () => void) => void; // Internal /** @private Internal usage, do not use in production mode!!! */ @@ -170,6 +171,7 @@ function InnerPicker(props: PickerProps) { onMouseLeave, onContextMenu, onClick, + onKeyDown, onSelect, direction, autoComplete = 'off', @@ -310,6 +312,9 @@ function InnerPicker(props: PickerProps) { setSelectedValue(mergedValue); resetText(); }, + onKeyDown: (e, preventDefault) => { + onKeyDown?.(e, preventDefault); + }, onFocus, onBlur, }); diff --git a/src/RangePicker.tsx b/src/RangePicker.tsx index c4de3b751..5a7ab6571 100644 --- a/src/RangePicker.tsx +++ b/src/RangePicker.tsx @@ -210,6 +210,7 @@ function InnerRangePicker(props: RangePickerProps) { onFocus, onBlur, onOk, + onKeyDown, components, order, direction, @@ -610,12 +611,18 @@ function InnerRangePicker(props: RangePickerProps) { ...getSharedInputHookProps(0, resetStartText), open: startOpen, value: startText, + onKeyDown: (e, preventDefault) => { + onKeyDown?.(e, preventDefault); + }, }); const [endInputProps, { focused: endFocused, typing: endTyping }] = usePickerInput({ ...getSharedInputHookProps(1, resetEndText), open: endOpen, value: endText, + onKeyDown: (e, preventDefault) => { + onKeyDown?.(e, preventDefault); + }, }); // ========================== Click Picker ========================== diff --git a/src/hooks/usePickerInput.ts b/src/hooks/usePickerInput.ts index 9903baff3..6f2fa8a81 100644 --- a/src/hooks/usePickerInput.ts +++ b/src/hooks/usePickerInput.ts @@ -9,6 +9,7 @@ export default function usePickerInput({ isClickOutside, triggerOpen, forwardKeyDown, + onKeyDown, blurToCancel, onSubmit, onCancel, @@ -20,6 +21,10 @@ export default function usePickerInput({ isClickOutside: (clickElement: EventTarget | null) => boolean; triggerOpen: (open: boolean) => void; forwardKeyDown: (e: React.KeyboardEvent) => boolean; + onKeyDown: ( + e: React.KeyboardEvent, + preventDefault: () => void, + ) => void; blurToCancel?: boolean; onSubmit: () => void | boolean; onCancel: () => void; @@ -37,12 +42,22 @@ export default function usePickerInput({ const valueChangedRef = useRef(false); + const preventDefaultRef = useRef(false); + const inputProps: React.DOMAttributes = { onMouseDown: () => { setTyping(true); triggerOpen(true); }, onKeyDown: e => { + const preventDefault = (): void => { + preventDefaultRef.current = true; + }; + + onKeyDown(e, preventDefault); + + if (preventDefaultRef.current) return; + switch (e.which) { case KeyCode.ENTER: { if (!open) { diff --git a/tests/picker.spec.tsx b/tests/picker.spec.tsx index a177aab0b..966322ff2 100644 --- a/tests/picker.spec.tsx +++ b/tests/picker.spec.tsx @@ -866,4 +866,31 @@ describe('Picker.Basic', () => { wrapper.unmount(); }); }); + + describe('prevent default on keydown', () => { + it('should open picker panel if no prevent default', () => { + const keyDown = jest.fn(); + const wrapper = mount(); + + wrapper.closePicker(); + wrapper.keyDown(KeyCode.ENTER); + expect(wrapper.isOpen()).toBeTruthy(); + }); + + it('should not open if prevent default is called', () => { + const keyDown = jest.fn(({ which }, preventDefault) => { + if(which === 13) preventDefault(); + }); + const wrapper = mount(); + + wrapper.openPicker(); + expect(wrapper.isOpen()).toBeTruthy(); + + wrapper.keyDown(KeyCode.ESC); + expect(wrapper.isOpen()).toBeFalsy(); + + wrapper.keyDown(KeyCode.ENTER); + expect(wrapper.isOpen()).toBeFalsy(); + }); + }) });