diff --git a/README.md b/README.md index 91814eb7..53888399 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ export default () => ( | onFocus | called when focus | function | - | | onPopupScroll | called when menu is scrolled | function | - | | onSelect | called when a option is selected. param is option's value and option instance | Function(value, option:Option) | - | +| tabSelectsValue | whether to enable the hot key for tab selection | boolean | true | | onDeselect | called when a option is deselected. param is option's value. only called for multiple or tags | Function(value, option:Option) | - | | onInputKeyDown | called when key down on input | Function(event) | - | | defaultActiveFirstOption | whether active first option by default | boolean | true | diff --git a/src/OptionList.tsx b/src/OptionList.tsx index a88e1afe..0e7a99cf 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -52,6 +52,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, r onActiveValue, defaultActiveFirstOption, onSelect, + tabSelectsValue, menuItemSelectedIcon, rawValues, fieldNames, @@ -185,6 +186,20 @@ const OptionList: React.ForwardRefRenderFunction = (_, r }; // ========================= Keyboard ========================= + const selectOptionHotKeyLogic = (event) => { + // value + const item = memoFlattenOptions[activeIndex]; + if (item && !item?.data?.disabled && !overMaxCount) { + onSelectValue(item.value); + } else { + onSelectValue(undefined); + } + + if (open) { + event.preventDefault(); + } + }; + React.useImperativeHandle(ref, () => ({ onKeyDown: (event) => { const { which, ctrlKey } = event; @@ -217,20 +232,16 @@ const OptionList: React.ForwardRefRenderFunction = (_, r } // >>> Select (Tab / Enter) - case KeyCode.TAB: - case KeyCode.ENTER: { - // value - const item = memoFlattenOptions[activeIndex]; - if (item && !item?.data?.disabled && !overMaxCount) { - onSelectValue(item.value); + case KeyCode.TAB: { + if (tabSelectsValue) { + selectOptionHotKeyLogic(event); } else { - onSelectValue(undefined); + toggleOpen(false); } - - if (open) { - event.preventDefault(); - } - + break; + } + case KeyCode.ENTER: { + selectOptionHotKeyLogic(event); break; } diff --git a/src/Select.tsx b/src/Select.tsx index a4e3dcf2..71a69aec 100644 --- a/src/Select.tsx +++ b/src/Select.tsx @@ -124,6 +124,7 @@ export interface SelectProps>> Select onSelect?: SelectHandler, OptionType>; onDeselect?: SelectHandler, OptionType>; + tabSelectsValue?: boolean; // >>> Options /** @@ -181,6 +182,7 @@ const Select = React.forwardRef; fieldNames?: FieldNames; diff --git a/tests/OptionList.test.tsx b/tests/OptionList.test.tsx index c148fd19..b8d60d23 100644 --- a/tests/OptionList.test.tsx +++ b/tests/OptionList.test.tsx @@ -64,6 +64,7 @@ describe('OptionList', () => { options, onActiveValue: () => {}, onSelect: () => {}, + tabSelectsValue: true, rawValues: values || new Set(), virtual: true, ...props, @@ -244,6 +245,47 @@ describe('OptionList', () => { expect(toggleOpen).toHaveBeenCalledWith(false); }); + it('should not select active option when tab key pressed with tabSelectsValue false', () => { + const onActiveValue = jest.fn(); + const onSelect = jest.fn(); + const toggleOpen = jest.fn(); + const listRef = React.createRef(); + + render( + generateList({ + options: [{ value: '1' }, { value: '2' }], + onActiveValue, + onSelect, + toggleOpen, + ref: listRef, + tabSelectsValue: false, // 关闭 tab 选中功能 + }), + ); + + act(() => { + toggleOpen(true); + }); + + act(() => { + listRef.current.onKeyDown({ which: KeyCode.DOWN } as any); + }); + + expect(onActiveValue).toHaveBeenCalledWith( + '2', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + + act(() => { + listRef.current.onKeyDown({ which: KeyCode.TAB } as any); + }); + + // 断言选择未被触发 + expect(onSelect).toHaveBeenCalledTimes(0); + // 断言弹窗状态关闭 + expect(toggleOpen).toHaveBeenCalledWith(false); + }); + // mocked how we detect running platform in test environment it('special key operation on Mac', () => { const onActiveValue = jest.fn();