Skip to content

Commit

Permalink
fix: Chrome render perf issue (#1045)
Browse files Browse the repository at this point in the history
* fix: chrome render bug

* chore: cut 50
  • Loading branch information
zombieJ committed May 9, 2024
1 parent 51792a5 commit fc5dfe7
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 30 deletions.
32 changes: 32 additions & 0 deletions src/BaseSelect/Polite.tsx
@@ -0,0 +1,32 @@
import * as React from 'react';
import type { DisplayValueType } from '.';

export interface PoliteProps {
visible: boolean;
values: DisplayValueType[];
}

export default function Polite(props: PoliteProps) {
const { visible, values } = props;

if (!visible) {
return null;
}

// Only cut part of values since it's a screen reader
const MAX_COUNT = 50;

return (
<span
aria-live="polite"
style={{ width: 0, height: 0, position: 'absolute', overflow: 'hidden', opacity: 0 }}
>
{/* Merge into one string to make screen reader work as expect */}
{`${values
.slice(0, MAX_COUNT)
.map(({ label, value }) => (['number', 'string'].includes(typeof label) ? label : value))
.join(', ')}`}
{values.length > MAX_COUNT ? ', ...' : null}
</span>
);
}
45 changes: 17 additions & 28 deletions src/BaseSelect.tsx → src/BaseSelect/index.tsx
Expand Up @@ -7,12 +7,12 @@ import KeyCode from 'rc-util/lib/KeyCode';
import { useComposeRef } from 'rc-util/lib/ref';
import type { ScrollConfig, ScrollTo } from 'rc-virtual-list/lib/List';
import * as React from 'react';
import { useAllowClear } from './hooks/useAllowClear';
import { BaseSelectContext } from './hooks/useBaseProps';
import type { BaseSelectContextProps } from './hooks/useBaseProps';
import useDelayReset from './hooks/useDelayReset';
import useLock from './hooks/useLock';
import useSelectTriggerControl from './hooks/useSelectTriggerControl';
import { useAllowClear } from '../hooks/useAllowClear';
import { BaseSelectContext } from '../hooks/useBaseProps';
import type { BaseSelectContextProps } from '../hooks/useBaseProps';
import useDelayReset from '../hooks/useDelayReset';
import useLock from '../hooks/useLock';
import useSelectTriggerControl from '../hooks/useSelectTriggerControl';
import type {
DisplayInfoType,
DisplayValueType,
Expand All @@ -21,15 +21,16 @@ import type {
RawValueType,
RenderDOMFunc,
RenderNode,
} from './interface';
import type { RefSelectorProps } from './Selector';
import Selector from './Selector';
import type { RefTriggerProps } from './SelectTrigger';
import SelectTrigger from './SelectTrigger';
import TransBtn from './TransBtn';
import { getSeparatedContent, isValidCount } from './utils/valueUtil';
import SelectContext from './SelectContext';
import type { SelectContextProps } from './SelectContext';
} from '../interface';
import type { RefSelectorProps } from '../Selector';
import Selector from '../Selector';
import type { RefTriggerProps } from '../SelectTrigger';
import SelectTrigger from '../SelectTrigger';
import TransBtn from '../TransBtn';
import { getSeparatedContent, isValidCount } from '../utils/valueUtil';
import SelectContext from '../SelectContext';
import type { SelectContextProps } from '../SelectContext';
import Polite from './Polite';

export type {
DisplayInfoType,
Expand Down Expand Up @@ -816,19 +817,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
onFocus={onContainerFocus}
onBlur={onContainerBlur}
>
{mockFocused && !mergedOpen && (
<span
aria-live="polite"
style={{ width: 0, height: 0, position: 'absolute', overflow: 'hidden', opacity: 0 }}
>
{/* Merge into one string to make screen reader work as expect */}
{`${displayValues
.map(({ label, value }) =>
['number', 'string'].includes(typeof label) ? label : value,
)
.join(', ')}`}
</span>
)}
<Polite visible={mockFocused && !mergedOpen} values={displayValues} />
{selectorNode}
{arrowNode}
{mergedAllowClear && clearNode}
Expand Down
37 changes: 37 additions & 0 deletions tests/BaseSelect.test.tsx
Expand Up @@ -62,6 +62,43 @@ describe('BaseSelect', () => {
jest.useRealTimers();
});

// https://github.com/ant-design/ant-design/issues/48833
it('a11y not block of Chrome render', () => {
jest.useFakeTimers();

const { container } = render(
<BaseSelect
displayValues={Array.from({ length: 100 }).map((_, index) => ({
key: index,
value: index,
label: index,
}))}
id="test"
prefixCls="rc-select"
onDisplayValuesChange={() => {}}
searchValue=""
onSearch={() => {}}
OptionList={OptionList}
emptyOptions
/>,
);

fireEvent.focus(container.querySelector('div.rc-select'));
act(() => {
jest.runAllTimers();
});

// We cut 50 count as hard code, its safe to adjust if refactor
const contentTxt = container.querySelector('span[aria-live=polite]')?.textContent;
expect(contentTxt).toBe(
`${Array.from({ length: 50 })
.map((_, index) => index)
.join(', ')}, ...`,
);

jest.useRealTimers();
});

it('customize builtinPlacements should override default one', () => {
const { container } = render(
<BaseSelect
Expand Down
10 changes: 8 additions & 2 deletions tests/Select.test.tsx
@@ -1,10 +1,16 @@
import type { LabelInValueType } from '@/Select';
import { createEvent, fireEvent, render, render as testingRender } from '@testing-library/react';
import {
createEvent,
fireEvent,
render,
render as testingRender,
act,
} from '@testing-library/react';
import KeyCode from 'rc-util/lib/KeyCode';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import { resetWarned } from 'rc-util/lib/warning';
import type { ScrollConfig } from 'rc-virtual-list/lib/List';
import React, { act } from 'react';
import React from 'react';
import type { SelectProps } from '../src';
import Select, { OptGroup, Option, useBaseProps } from '../src';
import type { BaseSelectRef } from '../src/BaseSelect';
Expand Down

0 comments on commit fc5dfe7

Please sign in to comment.