Skip to content

Commit 8c32273

Browse files
authored
fix: backfill should not affected by mouse (#549)
* test: test driven * fix backfill logic
1 parent 5bb7de6 commit 8c32273

File tree

5 files changed

+54
-18
lines changed

5 files changed

+54
-18
lines changed

src/OptionList.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
FlattenOptionData as SelectFlattenOptionData,
1111
OptionData,
1212
RenderNode,
13+
OnActiveValue,
1314
} from './interface';
1415
import { RawValueType, FlattenOptionsType } from './interface/generator';
1516

@@ -33,7 +34,7 @@ export interface OptionListProps<OptionsType extends object[]> {
3334
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
3435
onToggleOpen: (open?: boolean) => void;
3536
/** Tell Select that some value is now active to make accessibility work */
36-
onActiveValue: (value: RawValueType, index: number) => void;
37+
onActiveValue: OnActiveValue;
3738
onScroll: React.UIEventHandler<HTMLDivElement>;
3839

3940
/** Tell Select that mouse enter the popup to force re-render */
@@ -115,17 +116,19 @@ const OptionList: React.RefForwardingComponent<
115116
};
116117

117118
const [activeIndex, setActiveIndex] = React.useState(() => getEnabledActiveIndex(0));
118-
const setActive = (index: number) => {
119+
const setActive = (index: number, fromKeyboard = false) => {
119120
setActiveIndex(index);
120121

122+
const info = { source: fromKeyboard ? ('keyboard' as const) : ('mouse' as const) };
123+
121124
// Trigger active event
122125
const flattenItem = memoFlattenOptions[index];
123126
if (!flattenItem) {
124-
onActiveValue(null, -1);
127+
onActiveValue(null, -1, info);
125128
return;
126129
}
127130

128-
onActiveValue((flattenItem.data as OptionData).value, index);
131+
onActiveValue((flattenItem.data as OptionData).value, index, info);
129132
};
130133

131134
// Auto active first item when list length or searchValue changed
@@ -184,7 +187,7 @@ const OptionList: React.RefForwardingComponent<
184187
if (offset !== 0) {
185188
const nextActiveIndex = getEnabledActiveIndex(activeIndex + offset, offset);
186189
scrollIntoView(nextActiveIndex);
187-
setActive(nextActiveIndex);
190+
setActive(nextActiveIndex, true);
188191
}
189192

190193
break;

src/generate.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import classNames from 'classnames';
1414
import useMergedState from 'rc-util/lib/hooks/useMergedState';
1515
import Selector, { RefSelectorProps } from './Selector';
1616
import SelectTrigger, { RefTriggerProps } from './SelectTrigger';
17-
import { RenderNode, Mode, RenderDOMFunc } from './interface';
17+
import { RenderNode, Mode, RenderDOMFunc, OnActiveValue } from './interface';
1818
import {
1919
GetLabeledValue,
2020
FilterOptions,
@@ -187,11 +187,10 @@ export interface GenerateConfig<OptionsType extends object[]> {
187187
/** Convert single raw value into { label, value } format. Will be called by each value */
188188
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionsType>>;
189189
filterOptions: FilterOptions<OptionsType>;
190-
findValueOption:
191-
| (// Need still support legacy ts api
192-
(values: RawValueType[], options: FlattenOptionsType<OptionsType>) => OptionsType)
193-
| (// New API add prevValueOptions support
194-
(
190+
findValueOption: // Need still support legacy ts api
191+
| ((values: RawValueType[], options: FlattenOptionsType<OptionsType>) => OptionsType)
192+
// New API add prevValueOptions support
193+
| ((
195194
values: RawValueType[],
196195
options: FlattenOptionsType<OptionsType>,
197196
info?: { prevValueOptions?: OptionsType[] },
@@ -874,10 +873,10 @@ export default function generateSelector<
874873
const mergedDefaultActiveFirstOption =
875874
defaultActiveFirstOption !== undefined ? defaultActiveFirstOption : mode !== 'combobox';
876875

877-
const onActiveValue = (active: RawValueType, index: number) => {
876+
const onActiveValue: OnActiveValue = (active, index, { source = 'keyboard' } = {}) => {
878877
setAccessibilityIndex(index);
879878

880-
if (backfill && mode === 'combobox' && active !== null) {
879+
if (backfill && mode === 'combobox' && active !== null && source === 'keyboard') {
881880
setActiveValue(String(active));
882881
}
883882
};

src/interface/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { Key } from './generator';
2+
import { Key, RawValueType } from './generator';
33

44
export type RenderDOMFunc = (props: any) => HTMLElement;
55

@@ -8,6 +8,12 @@ export type RenderNode = React.ReactNode | ((props: any) => React.ReactNode);
88
export type Mode = 'multiple' | 'tags' | 'combobox';
99

1010
// ======================== Option ========================
11+
export type OnActiveValue = (
12+
active: RawValueType,
13+
index: number,
14+
info?: { source?: 'keyboard' | 'mouse' },
15+
) => void;
16+
1117
export interface OptionCoreData {
1218
key?: Key;
1319
disabled?: boolean;

tests/Combobox.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,22 @@ describe('Select.Combobox', () => {
235235
expect(handleSelect).toHaveBeenCalledWith('One', expect.objectContaining({ value: 'One' }));
236236
});
237237

238+
it('mouse should not trigger', () => {
239+
const wrapper = mount(
240+
<Select mode="combobox" backfill open>
241+
<Option value="One">One</Option>
242+
<Option value="Two">Two</Option>
243+
</Select>,
244+
);
245+
246+
wrapper
247+
.find('.rc-select-item-option')
248+
.first()
249+
.simulate('mouseMove');
250+
251+
expect(wrapper.find('input').props().value).toBeFalsy();
252+
});
253+
238254
// https://github.com/ant-design/ant-design/issues/25345
239255
it('dynamic options', () => {
240256
const onChange = jest.fn();

tests/OptionList.test.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('OptionList', () => {
6868
}),
6969
);
7070

71-
expect(onActiveValue).toHaveBeenCalledWith('1', expect.anything());
71+
expect(onActiveValue).toHaveBeenCalledWith('1', expect.anything(), expect.anything());
7272
});
7373

7474
it('key operation', () => {
@@ -86,13 +86,21 @@ describe('OptionList', () => {
8686
act(() => {
8787
listRef.current.onKeyDown({ which: KeyCode.DOWN } as any);
8888
});
89-
expect(onActiveValue).toHaveBeenCalledWith('2', expect.anything());
89+
expect(onActiveValue).toHaveBeenCalledWith(
90+
'2',
91+
expect.anything(),
92+
expect.objectContaining({ source: 'keyboard' }),
93+
);
9094

9195
onActiveValue.mockReset();
9296
act(() => {
9397
listRef.current.onKeyDown({ which: KeyCode.UP } as any);
9498
});
95-
expect(onActiveValue).toHaveBeenCalledWith('1', expect.anything());
99+
expect(onActiveValue).toHaveBeenCalledWith(
100+
'1',
101+
expect.anything(),
102+
expect.objectContaining({ source: 'keyboard' }),
103+
);
96104
});
97105

98106
it('hover to active', () => {
@@ -109,7 +117,11 @@ describe('OptionList', () => {
109117
.find('.rc-select-item-option')
110118
.last()
111119
.simulate('mouseMove');
112-
expect(onActiveValue).toHaveBeenCalledWith('2', expect.anything());
120+
expect(onActiveValue).toHaveBeenCalledWith(
121+
'2',
122+
expect.anything(),
123+
expect.objectContaining({ source: 'mouse' }),
124+
);
113125

114126
// Same item not repeat trigger
115127
onActiveValue.mockReset();

0 commit comments

Comments
 (0)