Skip to content

Commit

Permalink
Fixed #691 - Keyboard Accessibility for MultiSelect
Browse files Browse the repository at this point in the history
  • Loading branch information
cagataycivici committed Dec 19, 2018
1 parent 9faab6a commit 0895b26
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 27 deletions.
72 changes: 63 additions & 9 deletions src/components/multiselect/MultiSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class MultiSelect extends Component {
defaultLabel: 'Choose',
disabled: false,
filter: false,
tabIndex: '0',
dataKey: null,
appendTo: null,
tooltip: null,
Expand All @@ -41,6 +42,7 @@ export class MultiSelect extends Component {
defaultLabel: PropTypes.string,
disabled: PropTypes.bool,
filter: PropTypes.bool,
tabIndex: PropTypes.string,
dataKey: PropTypes.string,
appendTo: PropTypes.object,
tooltip: PropTypes.string,
Expand All @@ -59,6 +61,7 @@ export class MultiSelect extends Component {
this.onClick = this.onClick.bind(this);
this.onPanelClick = this.onPanelClick.bind(this);
this.onOptionClick = this.onOptionClick.bind(this);
this.onOptionKeyDown = this.onOptionKeyDown.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFilter = this.onFilter.bind(this);
Expand All @@ -79,6 +82,59 @@ export class MultiSelect extends Component {
this.updateModel(event.originalEvent, newValue);
}

onOptionKeyDown(event) {
let listItem = event.originalEvent.currentTarget;

switch(event.originalEvent.which) {
//down
case 40:
var nextItem = this.findNextItem(listItem);
if (nextItem) {
nextItem.focus();
}

event.originalEvent.preventDefault();
break;

//up
case 38:
var prevItem = this.findPrevItem(listItem);
if (prevItem) {
prevItem.focus();
}

event.originalEvent.preventDefault();
break;

//enter
case 13:
this.onOptionClick(event);
event.originalEvent.preventDefault();
break;

default:
break;
}
}

findNextItem(item) {
let nextItem = item.nextElementSibling;

if (nextItem)
return !DomHandler.hasClass(nextItem, 'p-multiselect-item') ? this.findNextItem(nextItem) : nextItem;
else
return null;
}

findPrevItem(item) {
let prevItem = item.previousElementSibling;

if (prevItem)
return !DomHandler.hasClass(prevItem, 'p-multiselect-item') ? this.findPrevItem(prevItem) : prevItem;
else
return null;
}

onClick() {
if(this.props.disabled) {
return;
Expand Down Expand Up @@ -364,12 +420,8 @@ export class MultiSelect extends Component {
}

render() {
let className = classNames('p-multiselect p-component', this.props.className, {
'p-disabled': this.props.disabled
});

let className = classNames('p-multiselect p-component', this.props.className, {'p-disabled': this.props.disabled});
let label = this.renderLabel();

let items = this.props.options;

if (items) {
Expand All @@ -380,8 +432,10 @@ export class MultiSelect extends Component {
items = items.map((option, index) => {
let optionLabel = this.getOptionLabel(option);

return <MultiSelectItem key={optionLabel + '_' + index} label={optionLabel} option={option} template={this.props.itemTemplate}
selected={this.isSelected(option)} onClick={this.onOptionClick} />;
return (
<MultiSelectItem key={optionLabel + '_' + index} label={optionLabel} option={option} template={this.props.itemTemplate}
selected={this.isSelected(option)} onClick={this.onOptionClick} onKeyDown={this.onOptionKeyDown} tabIndex={this.props.tabIndex} />
);
});
}

Expand All @@ -390,15 +444,15 @@ export class MultiSelect extends Component {
return (
<div id={this.props.id} className={className} onClick={this.onClick} ref={el => this.container = el} style={this.props.style}>
<div className="p-hidden-accessible">
<input readOnly type="text" onFocus={this.onFocus} onBlur={this.onBlur} ref={(el) => {this.focusInput = el;}}/>
<input readOnly type="text" onFocus={this.onFocus} onBlur={this.onBlur} ref={el => this.focusInput = el} />
</div>
<div className="p-multiselect-label-container" title="Choose">
<label className="p-multiselect-label">{label}</label>
</div>
<div className="p-multiselect-trigger">
<span className="p-multiselect-trigger-icon pi pi-chevron-down p-c"></span>
</div>
<MultiSelectPanel ref={(el) => this.panel = el} header={header} appendTo={this.props.appendTo} onClick={this.onPanelClick}
<MultiSelectPanel ref={el => this.panel = el} header={header} appendTo={this.props.appendTo} onClick={this.onPanelClick}
scrollHeight={this.props.scrollHeight}>
{items}
</MultiSelectPanel>
Expand Down
10 changes: 2 additions & 8 deletions src/components/multiselect/MultiSelectHeader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {InputText} from '../inputtext/InputText';
import {Checkbox} from '../checkbox/Checkbox';
import classNames from 'classnames';

export class MultiSelectHeader extends Component {
Expand Down Expand Up @@ -62,14 +63,7 @@ export class MultiSelectHeader extends Component {

return (
<div className="p-multiselect-header">
<div className="p-checkbox p-component" onClick={this.onToggleAll}>
<div className="p-hidden-accessible">
<input type="checkbox" readOnly={true} />
</div>
<div className={checkboxClassName}>
<span className={checkboxIcon}></span>
</div>
</div>
<Checkbox checked={this.props.allChecked} onChange={this.onToggleAll} />
{filterElement}
<button className="p-multiselect-close p-link" onClick={this.props.onClose}>
<span className="p-multiselect-close-icon pi pi-times"></span>
Expand Down
31 changes: 21 additions & 10 deletions src/components/multiselect/MultiSelectItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,26 @@ export class MultiSelectItem extends Component {
option: null,
label: null,
selected: false,
tabIndex: null,
template: null,
onClick: null
onClick: null,
onKeyDown: null,
};

static propTypes = {
option: PropTypes.object,
label: PropTypes.string,
selected: PropTypes.bool,
tabIndex: PropTypes.string,
template: PropTypes.func,
onClick: PropTypes.func
onClick: PropTypes.func,
onKeyDown: PropTypes.func,
};

constructor() {
super();
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}

onClick(event) {
Expand All @@ -35,19 +40,25 @@ export class MultiSelectItem extends Component {

event.preventDefault();
}

onKeyDown(event) {
if (this.props.onKeyDown) {
this.props.onKeyDown({
originalEvent: event,
option: this.props.option
});
}
}

render() {
let className = classNames('p-multiselect-item', {'p-highlight': this.props.selected});
let checkboxClassName = classNames('p-checkbox-box p-component', {'p-highlight': this.props.selected});
let checkboxIcon = classNames('p-checkbox-icon p-c', {'pi pi-check': this.props.selected});
let content = this.props.template ? this.props.template(this.props.option) : this.props.label;
const className = classNames('p-multiselect-item', {'p-highlight': this.props.selected});
const checkboxClassName = classNames('p-checkbox-box p-component', {'p-highlight': this.props.selected});
const checkboxIcon = classNames('p-checkbox-icon p-c', {'pi pi-check': this.props.selected});
const content = this.props.template ? this.props.template(this.props.option) : this.props.label;

return (
<li className={className} onClick={this.onClick}>
<li className={className} onClick={this.onClick} tabIndex={this.props.tabIndex} onKeyDown={this.onKeyDown}>
<div className="p-checkbox p-component">
<div className="p-hidden-accessible">
<input readOnly="readonly" type="checkbox" />
</div>
<div className={checkboxClassName}>
<span className={checkboxIcon}></span>
</div>
Expand Down

0 comments on commit 0895b26

Please sign in to comment.