diff --git a/src/OptionList.tsx b/src/OptionList.tsx index fc5682f5b..d4c0109fb 100644 --- a/src/OptionList.tsx +++ b/src/OptionList.tsx @@ -85,6 +85,17 @@ const OptionList: React.ForwardRefRenderFunction = (_, r listRef.current?.scrollTo(typeof args === 'number' ? { index: args } : args); }; + // https://github.com/ant-design/ant-design/issues/34975 + const isSelected = React.useCallback( + (value: RawValueType) => { + if (mode === 'combobox') { + return false; + } + return rawValues.has(value); + }, + [mode, [...rawValues].toString(), rawValues.size], + ); + // ========================== Active ========================== const getEnabledActiveIndex = (index: number, offset: number = 1): number => { const len = memoFlattenOptions.length; @@ -94,7 +105,7 @@ const OptionList: React.ForwardRefRenderFunction = (_, r const { group, data } = memoFlattenOptions[current] || {}; - if (!group && !data?.disabled && !overMaxCount) { + if (!group && !data?.disabled && (isSelected(data.value) || !overMaxCount)) { return current; } } @@ -122,17 +133,6 @@ const OptionList: React.ForwardRefRenderFunction = (_, r setActive(defaultActiveFirstOption !== false ? getEnabledActiveIndex(0) : -1); }, [memoFlattenOptions.length, searchValue]); - // https://github.com/ant-design/ant-design/issues/34975 - const isSelected = React.useCallback( - (value: RawValueType) => { - if (mode === 'combobox') { - return false; - } - return rawValues.has(value); - }, - [mode, [...rawValues].toString(), rawValues.size], - ); - // https://github.com/ant-design/ant-design/issues/48036 const isAriaSelected = React.useCallback( (value: RawValueType) => { diff --git a/tests/OptionList.test.tsx b/tests/OptionList.test.tsx index 6526778f9..ba7186a08 100644 --- a/tests/OptionList.test.tsx +++ b/tests/OptionList.test.tsx @@ -393,4 +393,70 @@ describe('OptionList', () => { }); expect(global.scrollToArgs).toEqual({ index: 1 }); }); + + // Test keyboard navigation behavior when maxCount limit is reached + // Verifies that: + // 1. Can navigate between already selected options + // 2. Cannot navigate to unselected options when maxCount is reached + // 3. Navigation wraps around between selected options + it('should allow keyboard navigation on selected options when reach maxCount', () => { + const onActiveValue = jest.fn(); + const listRef = React.createRef(); + + render( + generateList({ + multiple: true, + maxCount: 2, + options: [ + { value: '1', label: '1' }, + { value: '2', label: '2' }, + { value: '3', label: '3' }, + ], + values: new Set(['1', '2']), // Pre-select first two options + onActiveValue, + ref: listRef, + }), + ); + + onActiveValue.mockReset(); + + // Press down key - should move to option '2' + act(() => { + listRef.current.onKeyDown({ which: KeyCode.DOWN } as any); + }); + expect(onActiveValue).toHaveBeenCalledWith( + '2', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + + // Press down key again - should wrap to option '1' + onActiveValue.mockReset(); + act(() => { + listRef.current.onKeyDown({ which: KeyCode.DOWN } as any); + }); + expect(onActiveValue).toHaveBeenCalledWith( + '1', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + + // Press up key - should move back to option '2' + onActiveValue.mockReset(); + act(() => { + listRef.current.onKeyDown({ which: KeyCode.UP } as any); + }); + expect(onActiveValue).toHaveBeenCalledWith( + '2', + expect.anything(), + expect.objectContaining({ source: 'keyboard' }), + ); + + // Press down key - should not activate option '3' since maxCount is reached + onActiveValue.mockReset(); + act(() => { + listRef.current.onKeyDown({ which: KeyCode.DOWN } as any); + }); + expect(onActiveValue).not.toHaveBeenCalledWith('3', expect.anything(), expect.anything()); + }); });