Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature]: Add "labelInAriaLive"-property for better accessibility #413

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ lib/*
coverage
yarn.lock
package-lock.json
.npmrc
es/*
# umi
.umi
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ online example: https://tree-select-react-component.vercel.app/
|defaultValue | initial selected treeNode(s) | same as value type | - |
|value | current selected treeNode(s). | normal: String/Array<String>. labelInValue: {value:String,label:React.Node}/Array<{value,label}>. treeCheckStrictly(halfChecked default false): {value:String,label:React.Node, halfChecked}/Array<{value,label,halfChecked}>. | - |
|labelInValue| whether to embed label in value, see above value type | Bool | false |
|labelInAriaLive| whether to use option label instead of value for screenreader | Bool | false |
|onChange | called when select treeNode or input value change | function(value, label(null), extra) | - |
|onSelect | called when select treeNode | function(value, node, extra) | - |
|onSearch | called when input changed | function | - |
Expand Down
18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
{
"name": "rc-tree-select",
"version": "5.4.0",
"description": "tree-select ui component for react",
"name": "@juhovalkonen/rc-tree-select",
"version": "5.5.2",
"description": "custom fork of tree-select ui component for react",
"keywords": [
"react",
"react-component",
"react-tree-select",
"tree-select"
],
"homepage": "https://github.com/react-component/tree-select",
"author": "smith3816@gmail.com",
"homepage": "https://github.com/juhoValkonen/tree-select",
"author": "valkonen.juho@gmail.com",
"repository": {
"type": "git",
"url": "https://github.com/react-component/tree-select.git"
"url": "https://github.com/juhoValkonen/tree-select.git"
},
"bugs": {
"url": "https://github.com/react-component/tree-select/issues"
"url": "https://github.com/juhoValkonen/tree-select/issues"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"files": [
"es",
Expand Down
15 changes: 8 additions & 7 deletions src/OptionList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
import useMemo from 'rc-util/lib/hooks/useMemo';
import type { RefOptionListProps } from 'rc-select/lib/OptionList';
import { useBaseProps } from 'rc-select';
import type { TreeProps } from 'rc-tree';
import type { RefOptionListProps } from 'rc-select/lib/OptionList';
import Tree from 'rc-tree';
import type { TreeProps } from 'rc-tree';
import type { EventDataNode, ScrollTo } from 'rc-tree/lib/interface';
import type { TreeDataNode, Key } from './interface';
import useMemo from 'rc-util/lib/hooks/useMemo';
import KeyCode from 'rc-util/lib/KeyCode';
import * as React from 'react';
import type { Key, TreeDataNode } from './interface';
import LegacyContext from './LegacyContext';
import TreeSelectContext from './TreeSelectContext';
import { getAllKeys, isCheckDisabled } from './utils/valueUtil';
Expand Down Expand Up @@ -42,6 +42,7 @@ const OptionList: React.RefForwardingComponent<ReviseRefOptionListProps> = (_, r
onSelect,
dropdownMatchSelectWidth,
treeExpandAction,
labelInAriaLive,
} = React.useContext(TreeSelectContext);

const {
Expand Down Expand Up @@ -211,7 +212,7 @@ const OptionList: React.RefForwardingComponent<ReviseRefOptionListProps> = (_, r
<div onMouseDown={onListMouseDown}>
{activeEntity && open && (
<span style={HIDDEN_STYLE} aria-live="assertive">
{activeEntity.node.value}
{!labelInAriaLive ? activeEntity.node.value : activeEntity.node.title}
</span>
)}

Expand Down
38 changes: 21 additions & 17 deletions src/TreeSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import * as React from 'react';
import { BaseSelect } from 'rc-select';
import type { IconType } from 'rc-tree/lib/interface';
import type { ExpandAction } from 'rc-tree/lib/Tree';
import type {
BaseSelectRef,
BaseSelectPropsWithoutPrivate,
BaseSelectProps,
BaseSelectPropsWithoutPrivate,
BaseSelectRef,
SelectProps,
} from 'rc-select';
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
import useId from 'rc-select/lib/hooks/useId';
import type { IconType } from 'rc-tree/lib/interface';
import type { ExpandAction } from 'rc-tree/lib/Tree';
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import warning from 'rc-util/lib/warning';
import * as React from 'react';
import useCache from './hooks/useCache';
import useCheckedKeys from './hooks/useCheckedKeys';
import useDataEntities from './hooks/useDataEntities';
import useFilterTreeData from './hooks/useFilterTreeData';
import useRefFunc from './hooks/useRefFunc';
import useTreeData from './hooks/useTreeData';
import LegacyContext from './LegacyContext';
import OptionList from './OptionList';
import TreeNode from './TreeNode';
import { formatStrategyValues, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './utils/strategyUtil';
import type { CheckedStrategy } from './utils/strategyUtil';
import TreeSelectContext from './TreeSelectContext';
import type { TreeSelectContextProps } from './TreeSelectContext';
import LegacyContext from './LegacyContext';
import useTreeData from './hooks/useTreeData';
import { toArray, fillFieldNames, isNil } from './utils/valueUtil';
import useCache from './hooks/useCache';
import useRefFunc from './hooks/useRefFunc';
import useDataEntities from './hooks/useDataEntities';
import { fillAdditionalInfo, fillLegacyProps } from './utils/legacyUtil';
import useCheckedKeys from './hooks/useCheckedKeys';
import useFilterTreeData from './hooks/useFilterTreeData';
import { formatStrategyValues, SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './utils/strategyUtil';
import type { CheckedStrategy } from './utils/strategyUtil';
import { fillFieldNames, isNil, toArray } from './utils/valueUtil';
import warningProps from './utils/warningPropsUtil';
import warning from 'rc-util/lib/warning';

export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void;

Expand Down Expand Up @@ -140,6 +140,7 @@ export interface TreeSelectProps<
treeCheckable?: boolean | React.ReactNode;
treeCheckStrictly?: boolean;
labelInValue?: boolean;
labelInAriaLive?: boolean;

// >>> Data
treeData?: OptionType[];
Expand Down Expand Up @@ -202,6 +203,7 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
treeCheckable,
treeCheckStrictly,
labelInValue,
labelInAriaLive,

// FieldNames
fieldNames,
Expand Down Expand Up @@ -651,6 +653,7 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
fieldNames: mergedFieldNames,
onSelect: onOptionSelect,
treeExpandAction,
labelInAriaLive,
}),
[
virtual,
Expand All @@ -661,6 +664,7 @@ const TreeSelect = React.forwardRef<BaseSelectRef, TreeSelectProps>((props, ref)
mergedFieldNames,
onOptionSelect,
treeExpandAction,
labelInAriaLive,
],
);

Expand Down
3 changes: 2 additions & 1 deletion src/TreeSelectContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import type { ExpandAction } from 'rc-tree/lib/Tree';
import * as React from 'react';
import type { DefaultOptionType, InternalFieldName, OnInternalSelect } from './TreeSelect';

export interface TreeSelectContextProps {
Expand All @@ -11,6 +11,7 @@ export interface TreeSelectContextProps {
fieldNames: InternalFieldName;
onSelect: OnInternalSelect;
treeExpandAction?: ExpandAction;
labelInAriaLive?: boolean;
}

const TreeSelectContext = React.createContext<TreeSelectContextProps>(null as any);
Expand Down
37 changes: 36 additions & 1 deletion tests/Select.props.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable no-undef, react/no-multi-comp, no-console */
import React from 'react';
import { mount } from 'enzyme';
import Tree, { TreeNode } from 'rc-tree';
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import TreeSelect, { SHOW_ALL, SHOW_CHILD, SHOW_PARENT, TreeNode as SelectNode } from '../src';

// Promisify timeout to let jest catch works
Expand Down Expand Up @@ -155,6 +156,40 @@ describe('TreeSelect.props', () => {
);
});

it('labelInAriaLive', () => {
function keyDown(code) {
wrapper.find('input').first().simulate('keyDown', { which: code });
wrapper.update();
}

function keyUp(code) {
wrapper.find('input').first().simulate('keyUp', { which: code });
wrapper.update();
}

const wrapper = mount(
<TreeSelect
labelInAriaLive={true}
treeDefaultExpandAll
treeData={[
{
value: 'parent',
label: 'parent-label',
children: [{ value: 'child', label: 'child-label' }],
},
]}
multiple
/>,
);

wrapper.openSelect();
keyDown(KeyCode.DOWN);
keyUp(KeyCode.DOWN);

const ariaLiveSpan = wrapper.find('[aria-live="assertive"]');
expect(ariaLiveSpan.text()).toEqual('parent-label');
});

it('set illegal value', () => {
const wrapper = mount(
createSelect({
Expand Down