Skip to content

Commit 4402aa5

Browse files
committed
Merge remote-tracking branch 'origin/fix/onBlur-onSearch-once' into fix/onBlur-onSearch-once
2 parents e57a56a + 4da3e29 commit 4402aa5

File tree

7 files changed

+154
-44
lines changed

7 files changed

+154
-44
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rc-select",
3-
"version": "14.16.3",
3+
"version": "14.16.4",
44
"description": "React Select",
55
"engines": {
66
"node": ">=8.x"

src/OptionList.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
8585
listRef.current?.scrollTo(typeof args === 'number' ? { index: args } : args);
8686
};
8787

88+
// https://github.com/ant-design/ant-design/issues/34975
89+
const isSelected = React.useCallback(
90+
(value: RawValueType) => {
91+
if (mode === 'combobox') {
92+
return false;
93+
}
94+
return rawValues.has(value);
95+
},
96+
[mode, [...rawValues].toString(), rawValues.size],
97+
);
98+
8899
// ========================== Active ==========================
89100
const getEnabledActiveIndex = (index: number, offset: number = 1): number => {
90101
const len = memoFlattenOptions.length;
@@ -94,7 +105,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
94105

95106
const { group, data } = memoFlattenOptions[current] || {};
96107

97-
if (!group && !data?.disabled && !overMaxCount) {
108+
if (!group && !data?.disabled && (isSelected(data.value) || !overMaxCount)) {
98109
return current;
99110
}
100111
}
@@ -122,17 +133,6 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, r
122133
setActive(defaultActiveFirstOption !== false ? getEnabledActiveIndex(0) : -1);
123134
}, [memoFlattenOptions.length, searchValue]);
124135

125-
// https://github.com/ant-design/ant-design/issues/34975
126-
const isSelected = React.useCallback(
127-
(value: RawValueType) => {
128-
if (mode === 'combobox') {
129-
return false;
130-
}
131-
return rawValues.has(value);
132-
},
133-
[mode, [...rawValues].toString(), rawValues.size],
134-
);
135-
136136
// https://github.com/ant-design/ant-design/issues/48036
137137
const isAriaSelected = React.useCallback(
138138
(value: RawValueType) => {

src/Select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export interface SelectProps<ValueType = any, OptionType extends BaseOptionType
161161
value?: ValueType | null;
162162
defaultValue?: ValueType | null;
163163
maxCount?: number;
164-
onChange?: (value: ValueType, option: OptionType | OptionType[]) => void;
164+
onChange?: (value: ValueType, option?: OptionType | OptionType[]) => void;
165165
}
166166

167167
function isRawValue(value: DraftValueType): value is RawValueType {

src/utils/keyUtil.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,39 @@ import KeyCode from 'rc-util/lib/KeyCode';
22

33
/** keyCode Judgment function */
44
export function isValidateOpenKey(currentKeyCode: number): boolean {
5-
return ![
6-
// System function button
7-
KeyCode.ESC,
8-
KeyCode.SHIFT,
9-
KeyCode.BACKSPACE,
10-
KeyCode.TAB,
11-
KeyCode.WIN_KEY,
12-
KeyCode.ALT,
13-
KeyCode.META,
14-
KeyCode.WIN_KEY_RIGHT,
15-
KeyCode.CTRL,
16-
KeyCode.SEMICOLON,
17-
KeyCode.EQUALS,
18-
KeyCode.CAPS_LOCK,
19-
KeyCode.CONTEXT_MENU,
20-
// F1-F12
21-
KeyCode.F1,
22-
KeyCode.F2,
23-
KeyCode.F3,
24-
KeyCode.F4,
25-
KeyCode.F5,
26-
KeyCode.F6,
27-
KeyCode.F7,
28-
KeyCode.F8,
29-
KeyCode.F9,
30-
KeyCode.F10,
31-
KeyCode.F11,
32-
KeyCode.F12,
33-
].includes(currentKeyCode);
5+
return (
6+
// Undefined for Edge bug:
7+
// https://github.com/ant-design/ant-design/issues/51292
8+
currentKeyCode &&
9+
// Other keys
10+
![
11+
// System function button
12+
KeyCode.ESC,
13+
KeyCode.SHIFT,
14+
KeyCode.BACKSPACE,
15+
KeyCode.TAB,
16+
KeyCode.WIN_KEY,
17+
KeyCode.ALT,
18+
KeyCode.META,
19+
KeyCode.WIN_KEY_RIGHT,
20+
KeyCode.CTRL,
21+
KeyCode.SEMICOLON,
22+
KeyCode.EQUALS,
23+
KeyCode.CAPS_LOCK,
24+
KeyCode.CONTEXT_MENU,
25+
// F1-F12
26+
KeyCode.F1,
27+
KeyCode.F2,
28+
KeyCode.F3,
29+
KeyCode.F4,
30+
KeyCode.F5,
31+
KeyCode.F6,
32+
KeyCode.F7,
33+
KeyCode.F8,
34+
KeyCode.F9,
35+
KeyCode.F10,
36+
KeyCode.F11,
37+
KeyCode.F12,
38+
].includes(currentKeyCode)
39+
);
3440
}

tests/Accessibility.test.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as React from 'react';
22
import KeyCode from 'rc-util/lib/KeyCode';
33
import Select from '../src';
44
import { injectRunAllTimers, expectOpen, keyDown } from './utils/common';
5-
import { fireEvent, render } from '@testing-library/react';
5+
import { act, fireEvent, render } from '@testing-library/react';
66

77
describe('Select.Accessibility', () => {
88
injectRunAllTimers(jest);
@@ -67,4 +67,39 @@ describe('Select.Accessibility', () => {
6767
.textContent,
6868
).toEqual('Light');
6969
});
70+
71+
// https://github.com/ant-design/ant-design/issues/51292
72+
it('edge bug', () => {
73+
const { container } = render(
74+
<Select
75+
mode="combobox"
76+
options={[
77+
{
78+
value: '123',
79+
},
80+
{
81+
value: '1234',
82+
},
83+
{
84+
value: '12345',
85+
},
86+
]}
87+
defaultValue="123"
88+
/>,
89+
);
90+
91+
// Invalid key
92+
keyDown(container.querySelector('input')!, undefined);
93+
act(() => {
94+
jest.runAllTimers();
95+
});
96+
expectOpen(container, false);
97+
98+
// Valid key
99+
keyDown(container.querySelector('input')!, KeyCode.A);
100+
act(() => {
101+
jest.runAllTimers();
102+
});
103+
expectOpen(container);
104+
});
70105
});

tests/OptionList.test.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,70 @@ describe('OptionList', () => {
393393
});
394394
expect(global.scrollToArgs).toEqual({ index: 1 });
395395
});
396+
397+
// Test keyboard navigation behavior when maxCount limit is reached
398+
// Verifies that:
399+
// 1. Can navigate between already selected options
400+
// 2. Cannot navigate to unselected options when maxCount is reached
401+
// 3. Navigation wraps around between selected options
402+
it('should allow keyboard navigation on selected options when reach maxCount', () => {
403+
const onActiveValue = jest.fn();
404+
const listRef = React.createRef<RefOptionListProps>();
405+
406+
render(
407+
generateList({
408+
multiple: true,
409+
maxCount: 2,
410+
options: [
411+
{ value: '1', label: '1' },
412+
{ value: '2', label: '2' },
413+
{ value: '3', label: '3' },
414+
],
415+
values: new Set(['1', '2']), // Pre-select first two options
416+
onActiveValue,
417+
ref: listRef,
418+
}),
419+
);
420+
421+
onActiveValue.mockReset();
422+
423+
// Press down key - should move to option '2'
424+
act(() => {
425+
listRef.current.onKeyDown({ which: KeyCode.DOWN } as any);
426+
});
427+
expect(onActiveValue).toHaveBeenCalledWith(
428+
'2',
429+
expect.anything(),
430+
expect.objectContaining({ source: 'keyboard' }),
431+
);
432+
433+
// Press down key again - should wrap to option '1'
434+
onActiveValue.mockReset();
435+
act(() => {
436+
listRef.current.onKeyDown({ which: KeyCode.DOWN } as any);
437+
});
438+
expect(onActiveValue).toHaveBeenCalledWith(
439+
'1',
440+
expect.anything(),
441+
expect.objectContaining({ source: 'keyboard' }),
442+
);
443+
444+
// Press up key - should move back to option '2'
445+
onActiveValue.mockReset();
446+
act(() => {
447+
listRef.current.onKeyDown({ which: KeyCode.UP } as any);
448+
});
449+
expect(onActiveValue).toHaveBeenCalledWith(
450+
'2',
451+
expect.anything(),
452+
expect.objectContaining({ source: 'keyboard' }),
453+
);
454+
455+
// Press down key - should not activate option '3' since maxCount is reached
456+
onActiveValue.mockReset();
457+
act(() => {
458+
listRef.current.onKeyDown({ which: KeyCode.DOWN } as any);
459+
});
460+
expect(onActiveValue).not.toHaveBeenCalledWith('3', expect.anything(), expect.anything());
461+
});
396462
});

tests/utils/common.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ export function injectRunAllTimers(jest: Jest) {
101101

102102
export function keyDown(element: HTMLElement, keyCode: number) {
103103
const event = createEvent.keyDown(element, { keyCode });
104+
Object.defineProperties(event, {
105+
which: { get: () => keyCode },
106+
});
104107

105108
act(() => {
106109
fireEvent(element, event);

0 commit comments

Comments
 (0)