From b3c332ad862cf06389f96d76e050ee009ecf9ba9 Mon Sep 17 00:00:00 2001 From: astfn <876458652@qq.com> Date: Tue, 11 Mar 2025 15:03:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20[tabSelectsValue=20api]=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=E6=98=AF=E5=90=A6=E5=90=AF=E7=94=A8?= =?UTF-8?q?tab=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=8E=A7=E5=88=B6=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E9=80=89=E9=A1=B9=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + src/OptionList.tsx | 33 ++++++++++++++++++++------------- src/Select.tsx | 4 ++++ src/SelectContext.ts | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) 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..847f1907 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,12 @@ const OptionList: React.ForwardRefRenderFunction = (_, r } // >>> Select (Tab / Enter) - case KeyCode.TAB: + case KeyCode.TAB: { + tabSelectsValue && selectOptionHotKeyLogic(event); + break; + } case KeyCode.ENTER: { - // value - const item = memoFlattenOptions[activeIndex]; - if (item && !item?.data?.disabled && !overMaxCount) { - onSelectValue(item.value); - } else { - onSelectValue(undefined); - } - - if (open) { - event.preventDefault(); - } - + 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; From 43b3b63fa6b1a2d7cd33fbccb523527b06866272 Mon Sep 17 00:00:00 2001 From: astfn <876458652@qq.com> Date: Tue, 11 Mar 2025 16:26:20 +0800 Subject: [PATCH 2/3] fix: Make the code conform to Lint validation --- src/OptionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OptionList.tsx b/src/OptionList.tsx index 847f1907..ff9d9615 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -233,7 +233,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, r // >>> Select (Tab / Enter) case KeyCode.TAB: { - tabSelectsValue && selectOptionHotKeyLogic(event); + if (tabSelectsValue) selectOptionHotKeyLogic(event); break; } case KeyCode.ENTER: { From f6cbf9aec05fd08bff6a840e280c9d0dbb9ebd4f Mon Sep 17 00:00:00 2001 From: astfn <876458652@qq.com> Date: Fri, 14 Mar 2025 17:15:42 +0800 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20[tabSelectsValue]=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=AE=8C=E5=96=84=EF=BC=8C=E8=A1=A5=E5=85=85=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OptionList.tsx | 6 +++++- tests/OptionList.test.tsx | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/OptionList.tsx b/src/OptionList.tsx index ff9d9615..0e7a99cf 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -233,7 +233,11 @@ const OptionList: React.ForwardRefRenderFunction = (_, r // >>> Select (Tab / Enter) case KeyCode.TAB: { - if (tabSelectsValue) selectOptionHotKeyLogic(event); + if (tabSelectsValue) { + selectOptionHotKeyLogic(event); + } else { + toggleOpen(false); + } break; } case KeyCode.ENTER: { 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();