Skip to content

Commit 55e8dd2

Browse files
authored
chore: Move option aria to real element when virtual is disabled (#888)
* chore: opt for a11y with virtual=false * test: Update test case
1 parent 1f3c35f commit 55e8dd2

File tree

4 files changed

+152
-43
lines changed

4 files changed

+152
-43
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"rc-overflow": "^1.0.0",
5151
"rc-trigger": "^5.0.4",
5252
"rc-util": "^5.16.1",
53-
"rc-virtual-list": "^3.2.0"
53+
"rc-virtual-list": "^3.4.13"
5454
},
5555
"devDependencies": {
5656
"@testing-library/jest-dom": "^5.16.5",

src/OptionList.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import * as React from 'react';
2-
import { useEffect } from 'react';
3-
import type { ScrollConfig } from 'rc-virtual-list/lib/List';
1+
import classNames from 'classnames';
2+
import useMemo from 'rc-util/lib/hooks/useMemo';
43
import KeyCode from 'rc-util/lib/KeyCode';
54
import omit from 'rc-util/lib/omit';
65
import pickAttrs from 'rc-util/lib/pickAttrs';
7-
import useMemo from 'rc-util/lib/hooks/useMemo';
8-
import classNames from 'classnames';
96
import type { ListRef } from 'rc-virtual-list';
107
import List from 'rc-virtual-list';
11-
import TransBtn from './TransBtn';
12-
import { isPlatformMac } from './utils/platformUtil';
8+
import type { ScrollConfig } from 'rc-virtual-list/lib/List';
9+
import * as React from 'react';
10+
import { useEffect } from 'react';
1311
import useBaseProps from './hooks/useBaseProps';
14-
import SelectContext from './SelectContext';
15-
import type { BaseOptionType, RawValueType } from './Select';
1612
import type { FlattenOptionData } from './interface';
13+
import type { BaseOptionType, RawValueType } from './Select';
14+
import SelectContext from './SelectContext';
15+
import TransBtn from './TransBtn';
16+
import { isPlatformMac } from './utils/platformUtil';
1717

1818
// export interface OptionListProps<OptionsType extends object[]> {
1919
export type OptionListProps = Record<string, never>;
@@ -32,10 +32,7 @@ function isTitleType(content: any) {
3232
* Using virtual list of option display.
3333
* Will fallback to dom if use customize render.
3434
*/
35-
const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (
36-
_,
37-
ref,
38-
) => {
35+
const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (_, ref) => {
3936
const {
4037
prefixCls,
4138
id,
@@ -245,6 +242,15 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (
245242

246243
const getLabel = (item: Record<string, any>) => item.label;
247244

245+
function getItemAriaProps(item: FlattenOptionData<BaseOptionType>, index: number) {
246+
const { group } = item;
247+
248+
return {
249+
role: group ? 'presentation' : 'option',
250+
id: `${id}_list_${index}`,
251+
};
252+
}
253+
248254
const renderItem = (index: number) => {
249255
const item = memoFlattenOptions[index];
250256
if (!item) return null;
@@ -259,22 +265,28 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (
259265
aria-label={typeof mergedLabel === 'string' && !group ? mergedLabel : null}
260266
{...attrs}
261267
key={index}
262-
role={group ? 'presentation' : 'option'}
263-
id={`${id}_list_${index}`}
268+
{...getItemAriaProps(item, index)}
264269
aria-selected={isSelected(value)}
265270
>
266271
{value}
267272
</div>
268273
) : null;
269274
};
270275

276+
const a11yProps = {
277+
role: 'listbox',
278+
id: `${id}_list`,
279+
};
280+
271281
return (
272282
<>
273-
<div role="listbox" id={`${id}_list`} style={{ height: 0, width: 0, overflow: 'hidden' }}>
274-
{renderItem(activeIndex - 1)}
275-
{renderItem(activeIndex)}
276-
{renderItem(activeIndex + 1)}
277-
</div>
283+
{virtual && (
284+
<div {...a11yProps} style={{ height: 0, width: 0, overflow: 'hidden' }}>
285+
{renderItem(activeIndex - 1)}
286+
{renderItem(activeIndex)}
287+
{renderItem(activeIndex + 1)}
288+
</div>
289+
)}
278290
<List<FlattenOptionData<BaseOptionType>>
279291
itemKey="key"
280292
ref={listRef}
@@ -285,6 +297,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (
285297
onMouseDown={onListMouseDown}
286298
onScroll={onPopupScroll}
287299
virtual={virtual}
300+
innerProps={virtual ? null : a11yProps}
288301
>
289302
{(item, itemIndex) => {
290303
const { group, groupOption, data, label, value } = item;
@@ -334,6 +347,7 @@ const OptionList: React.ForwardRefRenderFunction<RefOptionListProps, {}> = (
334347
return (
335348
<div
336349
{...pickAttrs(passedProps)}
350+
{...(!virtual ? getItemAriaProps(item, itemIndex) : {})}
337351
aria-selected={selected}
338352
className={optionClassName}
339353
title={optionTitle}

tests/OptionList.test.tsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { mount } from 'enzyme';
22
import KeyCode from 'rc-util/lib/KeyCode';
3-
import { act } from 'react-dom/test-utils';
43
import React from 'react';
4+
import { act } from 'react-dom/test-utils';
5+
import { BaseSelectContext } from '../src/hooks/useBaseProps';
56
import type { RefOptionListProps } from '../src/OptionList';
67
import OptionList from '../src/OptionList';
7-
import { injectRunAllTimers } from './utils/common';
8-
import { fillFieldNames, flattenOptions } from '../src/utils/valueUtil';
98
import SelectContext from '../src/SelectContext';
10-
import { BaseSelectContext } from '../src/hooks/useBaseProps';
9+
import { fillFieldNames, flattenOptions } from '../src/utils/valueUtil';
10+
import { injectRunAllTimers } from './utils/common';
1111

1212
jest.mock('../src/utils/platformUtil');
1313

@@ -44,6 +44,7 @@ describe('OptionList', () => {
4444
onActiveValue: () => {},
4545
onSelect: () => {},
4646
rawValues: values || new Set(),
47+
virtual: true,
4748
...props,
4849
}}
4950
>
@@ -55,23 +56,35 @@ describe('OptionList', () => {
5556
);
5657
}
5758

58-
it('renders correctly', () => {
59-
const wrapper = mount(
60-
generateList({
61-
options: [
62-
{
63-
key: 'group1',
64-
options: [{ value: '1', 'aria-label': 'value-1' }],
65-
},
66-
{
67-
key: 'group2',
68-
options: [{ value: '2' }],
69-
},
70-
],
71-
values: new Set(['1']),
72-
}),
73-
);
74-
expect(wrapper.render()).toMatchSnapshot();
59+
describe('renders correctly', () => {
60+
const sharedConfig = {
61+
options: [
62+
{
63+
key: 'group1',
64+
options: [{ value: '1', 'aria-label': 'value-1' }],
65+
},
66+
{
67+
key: 'group2',
68+
options: [{ value: '2' }],
69+
},
70+
],
71+
values: new Set(['1']),
72+
};
73+
74+
it('virtual', () => {
75+
const wrapper = mount(generateList(sharedConfig));
76+
expect(wrapper.render()).toMatchSnapshot();
77+
});
78+
79+
it('without virtual', () => {
80+
const wrapper = mount(
81+
generateList({
82+
...sharedConfig,
83+
virtual: false,
84+
}),
85+
);
86+
expect(wrapper.render()).toMatchSnapshot();
87+
});
7588
});
7689

7790
it('save first active item', () => {

tests/__snapshots__/OptionList.test.tsx.snap

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`OptionList renders correctly 1`] = `
3+
exports[`OptionList renders correctly virtual 1`] = `
44
<div>
55
<div
66
id="undefined_list"
@@ -99,3 +99,85 @@ exports[`OptionList renders correctly 1`] = `
9999
</div>
100100
</div>
101101
`;
102+
103+
exports[`OptionList renders correctly without virtual 1`] = `
104+
<div>
105+
<div
106+
class="rc-virtual-list"
107+
style="position: relative;"
108+
>
109+
<div
110+
class="rc-virtual-list-holder"
111+
>
112+
<div>
113+
<div
114+
class="rc-virtual-list-holder-inner"
115+
id="undefined_list"
116+
role="listbox"
117+
style="display: flex; flex-direction: column;"
118+
>
119+
<div
120+
class="rc-select-item rc-select-item-group"
121+
>
122+
group1
123+
</div>
124+
<div
125+
aria-label="value-1"
126+
aria-selected="true"
127+
class="rc-select-item rc-select-item-option rc-select-item-option-grouped rc-select-item-option-active rc-select-item-option-selected"
128+
id="undefined_list_1"
129+
role="option"
130+
title="1"
131+
>
132+
<div
133+
class="rc-select-item-option-content"
134+
>
135+
1
136+
</div>
137+
<span
138+
aria-hidden="true"
139+
class="rc-select-item-option-state"
140+
style="user-select: none;"
141+
unselectable="on"
142+
>
143+
<span
144+
class="rc-select-item-option-state-icon"
145+
>
146+
147+
</span>
148+
</span>
149+
</div>
150+
<div
151+
class="rc-select-item rc-select-item-group"
152+
>
153+
group2
154+
</div>
155+
<div
156+
aria-selected="false"
157+
class="rc-select-item rc-select-item-option rc-select-item-option-grouped"
158+
id="undefined_list_3"
159+
role="option"
160+
title="2"
161+
>
162+
<div
163+
class="rc-select-item-option-content"
164+
>
165+
2
166+
</div>
167+
<span
168+
aria-hidden="true"
169+
class="rc-select-item-option-state"
170+
style="user-select: none;"
171+
unselectable="on"
172+
>
173+
<span
174+
class="rc-select-item-option-state-icon"
175+
/>
176+
</span>
177+
</div>
178+
</div>
179+
</div>
180+
</div>
181+
</div>
182+
</div>
183+
`;

0 commit comments

Comments
 (0)