Skip to content

Commit

Permalink
feat(CascaderSelect): support a11y
Browse files Browse the repository at this point in the history
  • Loading branch information
jinli.lyy committed Jan 24, 2019
1 parent b18c7e0 commit 4a40b2a
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 13 deletions.
41 changes: 39 additions & 2 deletions src/cascader-select/cascader-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import Select from '../select';
import Cascader from '../cascader';
import Menu from '../menu';
import { func, obj, dom } from '../util';
import { func, obj, dom, KEYCODE } from '../util';

const { bindCtx } = func;
const { pickOthers } = obj;
Expand Down Expand Up @@ -253,7 +253,7 @@ export default class CascaderSelect extends Component {

bindCtx(this, [
'handleVisibleChange', 'handleAfterOpen', 'handleChange', 'handleClear', 'handleRemove',
'handleSearch', 'getPopup'
'handleSearch', 'getPopup', 'saveSelectRef', 'saveCascaderRef', 'handleKeyDown'
]);
}

Expand Down Expand Up @@ -412,6 +412,14 @@ export default class CascaderSelect extends Component {
return indeterminate;
}

saveSelectRef(ref) {
this.select = ref;
}

saveCascaderRef(ref) {
this.cascader = ref;
}

completeValue(value) {
const newValue = [];

Expand Down Expand Up @@ -443,9 +451,35 @@ export default class CascaderSelect extends Component {
});
}

if (['fromCascader', 'keyboard'].indexOf(type) !== -1 && !visible) {
this.select.focusInput();
}

this.props.onVisibleChange(visible, type);
}

handleKeyDown(e) {
const { onKeyDown } = this.props;
const { visible } = this.state;

if (onKeyDown) {
onKeyDown(e);
}

if (!visible) {
return;
}

switch (e.keyCode) {
case KEYCODE.UP:
case KEYCODE.DOWN:
this.cascader.setFocusValue();
e.preventDefault();
break;
default: break;
}
}

getPopup(ref) {
this.popup = ref;
if (typeof this.props.popupProps.ref === 'function') {
Expand Down Expand Up @@ -640,6 +674,7 @@ export default class CascaderSelect extends Component {
canOnlyCheckLeaf,
defaultExpandedValue,
expandTriggerType,
ref: this.saveCascaderRef,
onExpand,
listStyle,
listClassName,
Expand Down Expand Up @@ -711,6 +746,7 @@ export default class CascaderSelect extends Component {
hasClear,
label,
readOnly,
ref: this.saveSelectRef,
autoWidth: false,
mode: multiple ? 'multiple' : 'single',
value: multiple ? this.getMultipleData(value) : this.getSignleData(value),
Expand All @@ -721,6 +757,7 @@ export default class CascaderSelect extends Component {
showSearch,
searchValue,
onSearch: this.handleSearch,
onKeyDown: this.handleKeyDown,
popupContent,
popupStyle,
popupClassName,
Expand Down
53 changes: 52 additions & 1 deletion src/cascader/cascader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,50 @@ export default class Cascader extends Component {
}
}

getFirstFocusKeyByDataSource(dataSource) {
if (!dataSource || dataSource.length === 0) {
return '';
}

for (let i = 0; i < dataSource.length; i++) {
if (dataSource[i] && !dataSource[i].disabled) {
return dataSource[i].value;
}
}

return '';
}

getFirstFocusKeyByFilteredPaths(filteredPaths) {
if (!filteredPaths || filteredPaths.length === 0) {
return '';
}

for (let i = 0; i < filteredPaths.length; i++) {
const path = filteredPaths[i];
if (!path.some(item => item.disabled)) {
const lastItem = path[path.length - 1];
return lastItem.value;
}
}

return '';
}

getFirstFocusKey () {
const { dataSource, searchValue, filteredPaths } = this.props;

return !searchValue ?
this.getFirstFocusKeyByDataSource(dataSource) :
this.getFirstFocusKeyByFilteredPaths(filteredPaths);
}

setFocusValue() {
this.setState({
focusedValue: this.getFirstFocusKey()
});
}

handleFocus(focusedValue) {
this.setState({
focusedValue
Expand Down Expand Up @@ -593,6 +637,7 @@ export default class Cascader extends Component {
useVirtual={useVirtual}
className={listClassName}
style={listStyle}
ref={this.saveMenuRef}
focusedKey={focusedValue}
onItemFocus={this.handleFocus}
onBlur={this.onBlur}
Expand Down Expand Up @@ -694,8 +739,14 @@ export default class Cascader extends Component {

renderFilteredList() {
const { prefix, filteredListStyle, filteredPaths } = this.props;
const { focusedValue } = this.state;
return (
<Menu className={`${prefix}cascader-filtered-list`} style={filteredListStyle}>
<Menu
focusedKey={focusedValue}
onItemFocus={this.handleFocus}
className={`${prefix}cascader-filtered-list`}
style={filteredListStyle}
>
{filteredPaths.map(path => this.renderFilteredItem(path))}
</Menu>
);
Expand Down
3 changes: 2 additions & 1 deletion src/cascader/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export default ConfigProvider.config(Cascader, {
}

return props;
}
},
exportNames: ['setFocusValue']
});
16 changes: 12 additions & 4 deletions src/cascader/item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class CascaderMenuItem extends Component {
onSelect: PropTypes.func,
expanded: PropTypes.bool,
canExpand: PropTypes.bool,
menu: PropTypes.any,
expandTriggerType: PropTypes.oneOf(['click', 'hover']),
onExpand: PropTypes.func,
onFold: PropTypes.func,
Expand Down Expand Up @@ -74,7 +75,7 @@ export default class CascaderMenuItem extends Component {

handleKeyDown(e) {
if (!this.props.disabled) {
if (e.keyCode === KEYCODE.RIGHT || e.keyCODE === KEYCODE.ENTER) {
if (e.keyCode === KEYCODE.RIGHT || e.keyCode === KEYCODE.ENTER) {
if (this.props.canExpand) {
this.handleExpand(true);
}
Expand All @@ -87,7 +88,7 @@ export default class CascaderMenuItem extends Component {
}

render() {
const { prefix, className, disabled, selected, onSelect, expanded, canExpand, expandTriggerType,
const { prefix, className, menu, disabled, selected, onSelect, expanded, canExpand, expandTriggerType,
checkable, checked, indeterminate, checkboxDisabled, onCheck, children } = this.props;
const others = pickOthers(Object.keys(CascaderMenuItem.propTypes), this.props);
const { loading } = this.state;
Expand All @@ -99,6 +100,7 @@ export default class CascaderMenuItem extends Component {
[className]: !!className
}),
disabled,
menu,
onKeyDown: this.handleKeyDown,
role: 'option',
'aria-expanded': expanded,
Expand Down Expand Up @@ -130,8 +132,14 @@ export default class CascaderMenuItem extends Component {
{children}
{canExpand ? (
loading ?
<Icon className={`${prefix}cascader-menu-icon-right ${prefix}cascader-menu-icon-loading`} type="loading" /> :
<Icon className={`${prefix}cascader-menu-icon-right ${prefix}cascader-menu-icon-expand`} type="arrow-right" />) :
<Icon
className={`${prefix}cascader-menu-icon-right ${prefix}cascader-menu-icon-loading`}
type="loading"
/> :
<Icon
className={`${prefix}cascader-menu-icon-right ${prefix}cascader-menu-icon-expand`}
type="arrow-right"
/>) :
null}
</Item>
);
Expand Down
24 changes: 21 additions & 3 deletions src/cascader/menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export default class CascaderMenu extends Component {
if (!children || children.length === 0) {
return;
}
const selectedIndex = children.findIndex(item => !!item.props.checked || !!item.props.selected || !!item.props.expanded);
const selectedIndex = children.findIndex(
item => !!item.props.checked ||
!!item.props.selected ||
!!item.props.expanded
);

if (selectedIndex === -1) {
return;
Expand All @@ -35,13 +39,27 @@ export default class CascaderMenu extends Component {
const menu = findDOMNode(this.refs.menu);
const targetItem = menu.querySelectorAll(itemSelector)[selectedIndex];
if (targetItem) {
menu.scrollTop = targetItem.offsetTop - Math.floor((menu.clientHeight / targetItem.clientHeight - 1) / 2) * targetItem.clientHeight;
menu.scrollTop = targetItem.offsetTop - Math.floor(
(menu.clientHeight / targetItem.clientHeight - 1) / 2
) * targetItem.clientHeight;
}
}
}

renderMenu (items, ref, props) {
return <Menu ref={ref} role="listbox" {...props}>{items}</Menu>;
return (
<Menu ref={ref} role="listbox" {...props}>{
items.map(node => {
if (React.isValidElement(node) && node.type.menuChildType === 'item') {
return React.cloneElement(node, {
menu: this
});
}

return node;
})
}</Menu>
);
}

render() {
Expand Down
7 changes: 5 additions & 2 deletions src/menu/view/item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class Item extends Component {
level: PropTypes.number,
groupIndent: PropTypes.number,
root: PropTypes.object,
menu: PropTypes.any,
parent: PropTypes.object,
parentMode: PropTypes.oneOf(['inline', 'popup']),
type: PropTypes.oneOf(['submenu', 'item']),
Expand Down Expand Up @@ -41,8 +42,10 @@ export default class Item extends Component {
componentDidMount() {
this.itemNode = findDOMNode(this);

const { parentMode, root } = this.props;
if (parentMode === 'popup') {
const { parentMode, root, menu } = this.props;
if (menu) {
this.menuNode = findDOMNode(menu);
} else if (parentMode === 'popup') {
this.menuNode = this.itemNode.parentNode;
} else {
this.menuNode = findDOMNode(root);
Expand Down

0 comments on commit 4a40b2a

Please sign in to comment.