Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [tabSelectsValue api]支持配置是否启用tab快捷键控制选中选项的逻辑 #1131

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 |
35 changes: 23 additions & 12 deletions src/OptionList.tsx
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
onActiveValue,
defaultActiveFirstOption,
onSelect,
tabSelectsValue,
menuItemSelectedIcon,
rawValues,
fieldNames,
@@ -185,6 +186,20 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, 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<RefOptionListProps, {}> = (_, 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;
}

4 changes: 4 additions & 0 deletions src/Select.tsx
Original file line number Diff line number Diff line change
@@ -124,6 +124,7 @@ export interface SelectProps<ValueType = any, OptionType extends BaseOptionType
// >>> Select
onSelect?: SelectHandler<ArrayElementType<ValueType>, OptionType>;
onDeselect?: SelectHandler<ArrayElementType<ValueType>, OptionType>;
tabSelectsValue?: boolean;

// >>> Options
/**
@@ -181,6 +182,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
onSelect,
onDeselect,
popupMatchSelectWidth = true,
tabSelectsValue = true,

// Options
filterOption,
@@ -616,6 +618,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
onActiveValue,
defaultActiveFirstOption: mergedDefaultActiveFirstOption,
onSelect: onInternalSelect,
tabSelectsValue,
menuItemSelectedIcon,
rawValues,
fieldNames: mergedFieldNames,
@@ -634,6 +637,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
onActiveValue,
mergedDefaultActiveFirstOption,
onInternalSelect,
tabSelectsValue,
menuItemSelectedIcon,
rawValues,
mergedFieldNames,
1 change: 1 addition & 0 deletions src/SelectContext.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ export interface SelectContextProps {
onActiveValue: OnActiveValue;
defaultActiveFirstOption?: boolean;
onSelect: OnInternalSelect;
tabSelectsValue: boolean;
menuItemSelectedIcon?: RenderNode;
rawValues: Set<RawValueType>;
fieldNames?: FieldNames;
42 changes: 42 additions & 0 deletions tests/OptionList.test.tsx
Original file line number Diff line number Diff line change
@@ -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<RefOptionListProps>();

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();