Skip to content

Commit

Permalink
a11y for Listbox
Browse files Browse the repository at this point in the history
  • Loading branch information
cagataycivici committed Apr 27, 2022
1 parent a161e41 commit 26cf672
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 11 deletions.
6 changes: 6 additions & 0 deletions api-generator/components/listbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ const ListBoxProps = [
default: 'undefined',
description: "Locale to use in filtering. The default locale is the host environment's current locale."
},
{
name: 'filterInputProps',
type: 'object',
default: 'undefined',
description: "Props for the filter input, any prop is passed implicity to the filter input element."
},
{
name: 'tabIndex',
type: 'number',
Expand Down
96 changes: 94 additions & 2 deletions components/doc/listbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,8 @@ const cities = [

<h5>Selection</h5>
<p>Listbox allows selection of either single or multiple items. In single case, model should be a single object reference whereas in multiple case should be an array. Multiple items can either be selected
using metaKey or toggled individually depending on the value of <i>metaKeySelection</i> property value which is true by default. On touch enabled
devices metaKeySelection is turned off automatically.</p>
using metaKey or toggled individually depending on the value of <i>metaKeySelection</i> property value which is false by default. On touch enabled
devices metaKeySelection is turned on automatically even when enabled.</p>

<CodeHighlight>
{`
Expand Down Expand Up @@ -542,6 +542,13 @@ itemTemplate(option) {
`}
</CodeHighlight>

<p>Filter input can be customized with the <i>filterInputProps</i> option that passes any property to the filter input element.</p>
<CodeHighlight>
{`
<ListBox value={city} options={cities} onChange={(e) => setCity(e.value)} filter filterInputProps={{className:'p-3', maxLength: 10}}/>
`}
</CodeHighlight>

<h5>Grouping</h5>
<p>Options groups are specified with the <i>optionGroupLabel</i> and <i>optionGroupChildren</i> properties.</p>
<CodeHighlight>
Expand Down Expand Up @@ -787,6 +794,12 @@ const groupedCities = [
<td>undefined</td>
<td>Locale to use in filtering. The default locale is the host environment's current locale.</td>
</tr>
<tr>
<td>filterInputProps</td>
<td>object</td>
<td>undefined</td>
<td>Props for the filter input, any prop is passed implicity to the filter input element.</td>
</tr>
<tr>
<td>tabIndex</td>
<td>number</td>
Expand Down Expand Up @@ -879,6 +892,85 @@ const groupedCities = [
</table>
</div>

<h5>Accessibility</h5>
<h6>Screen Reader</h6>
<p>Value to describe the component can be provided <i>aria-labelledby</i> or <i>aria-label</i> props. The list element has a <i>listbox</i> role with the <i>aria-multiselectable</i> attribute that sets to true when multiple selection is enabled.
Each list item has an <i>option</i> role with <i>aria-selected</i> and <i>aria-disabled</i> as their attributes.</p>
<p>If filtering is enabled, <i>filterInputProps</i> can be defined to give <i>aria-*</i> props to the input element. Alternatively <i>filterPlaceholder</i> is usually utilized by the screen readers as well.</p>
<CodeHighlight>
{`
<span id="lb">Options</span>
<ListBox aria-labelledby="lb" />
<ListBox aria-label="City" />
`}
</CodeHighlight>
<h6>Keyboard Support</h6>
<div className="doc-tablewrapper">
<table className="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td><i>tab</i></td>
<td>Moves focus to the first selected option, if there is none first option receives the focus.</td>
</tr>
<tr>
<td><i>up arrow</i></td>
<td>Moves focus to the previous option.</td>
</tr>
<tr>
<td><i>down arrow</i></td>
<td>Moves focus to the next option.</td>
</tr>
<tr>
<td><i>enter</i></td>
<td>Toggles the selected state of the focused item.</td>
</tr>
<tr>
<td><i>space</i></td>
<td>Toggles the selected state of the focused item.</td>
</tr>
<tr>
<td><i>home</i></td>
<td>Moves focus to the first option.</td>
</tr>
<tr>
<td><i>end</i></td>
<td>Moves focus to the last option.</td>
</tr>
<tr>
<td><i>shift</i> + <i>down arrow</i></td>
<td>Moves focus to the next option and toggle the selection state.</td>
</tr>
<tr>
<td><i>shift</i> + <i>up arrow</i></td>
<td>Moves focus to the previous option and toggle the selection state.</td>
</tr>
<tr>
<td><i>shift</i> + <i>space</i></td>
<td>Selects the items between the most recently selected option and the focused option.</td>
</tr>
<tr>
<td><i>control</i> + <i>shift</i> + <i>home</i></td>
<td>Selects the focused options and all the options up to the first one.</td>
</tr>
<tr>
<td><i>control</i> + <i>shift</i> + <i>end</i></td>
<td>Selects the focused options and all the options down to the first one.</td>
</tr>
<tr>
<td><i>control</i> + <i>a</i></td>
<td>Selects all options</td>
</tr>
</tbody>
</table>
</div>

<h5>Dependencies</h5>
<p>None.</p>
</TabPanel>
Expand Down
1 change: 1 addition & 0 deletions components/lib/listbox/ListBox.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface ListBoxProps extends Omit<React.DetailedHTMLProps<React.InputHT
filterMatchMode?: string;
filterPlaceholder?: string;
filterLocale?: string;
filterInputProps?: any;
tabIndex?: number;
tooltip?: string;
tooltipOptions?: TooltipOptions;
Expand Down
12 changes: 7 additions & 5 deletions components/lib/listbox/ListBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const ListBox = React.memo(React.forwardRef((props, ref) => {
}

const createHeader = () => {
return props.filter ? <ListBoxHeader filter={filteredValue} onFilter={onFilter} disabled={props.disabled} filterPlaceholder={props.filterPlaceholder} /> : null;
return props.filter ? <ListBoxHeader filter={filteredValue} onFilter={onFilter} disabled={props.disabled} filterPlaceholder={props.filterPlaceholder} filterInputProps={props.filterInputProps} /> : null;
}

const createGroupChildren = (optionGroup) => {
Expand Down Expand Up @@ -240,7 +240,7 @@ export const ListBox = React.memo(React.forwardRef((props, ref) => {

return (
<React.Fragment key={key}>
<li className="p-listbox-item-group">
<li className="p-listbox-item-group" role="group">
{groupContent}
</li>
{groupChildrenContent}
Expand Down Expand Up @@ -275,7 +275,7 @@ export const ListBox = React.memo(React.forwardRef((props, ref) => {
const className = classNames('p-listbox-list', option.className);

return (
<ul ref={option.contentRef} className={className} role="listbox" aria-multiselectable={props.multiple}>
<ul ref={option.contentRef} className={className} role="listbox" aria-multiselectable={props.multiple} aria-labelledby={props['aria-labelledby']} aria-label={props['aria-label']}>
{option.children}
</ul>
)
Expand All @@ -289,7 +289,7 @@ export const ListBox = React.memo(React.forwardRef((props, ref) => {
const items = createItems();

return (
<ul className="p-listbox-list" role="listbox" aria-multiselectable={props.multiple}>
<ul className="p-listbox-list" role="listbox" aria-multiselectable={props.multiple} aria-labelledby={props['aria-labelledby']} aria-label={props['aria-label']}>
{items}
</ul>
)
Expand Down Expand Up @@ -348,10 +348,12 @@ ListBox.defaultProps = {
filterMatchMode: 'contains',
filterPlaceholder: null,
filterLocale: undefined,
filterInputProps: null,
tabIndex: 0,
tooltip: null,
tooltipOptions: null,
ariaLabelledBy: null,
'aria-label': null,
'aria-labelledby': null,
onChange: null,
onFilterValueChange: null
}
2 changes: 1 addition & 1 deletion components/lib/listbox/ListBoxHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const ListBoxHeader = React.memo((props) => {
return (
<div className="p-listbox-header">
<div className="p-listbox-filter-container">
<InputText type="text" value={props.filter} onChange={onFilter} className="p-listbox-filter" disabled={props.disabled} placeholder={props.filterPlaceholder} />
<InputText type="text" value={props.filter} onChange={onFilter} className="p-listbox-filter" disabled={props.disabled} placeholder={props.filterPlaceholder} {...props.filterInputProps} />
<span className="p-listbox-filter-icon pi pi-search"></span>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/lib/listbox/ListBoxItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export const ListBoxItem = React.memo((props) => {
const content = props.template ? ObjectUtils.getJSXElement(props.template, props.option) : props.label;

return (
<li className={className} onClick={onClick} onTouchEnd={onTouchEnd} onKeyDown={onKeyDown} tabIndex={props.tabIndex}
aria-label={props.label} key={props.label} role="option" aria-selected={props.selected}>
<li className={className} onClick={onClick} onTouchEnd={onTouchEnd} onKeyDown={onKeyDown} tabIndex="-1"
aria-label={props.label} key={props.label} role="option" aria-selected={props.selected} aria-disabled={props.disabled}>
{content}
<Ripple />
</li>
Expand Down
2 changes: 1 addition & 1 deletion pages/listbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const ListBoxDemo = () => {
optionGroupTemplate={groupedItemTemplate} style={{ width: '15rem' }} listStyle={{ maxHeight: '250px' }} />

<h5>Advanced with Templating, Filtering and Multiple Selection</h5>
<ListBox value={selectedCountries} options={countries} onChange={(e) => setSelectedCountries(e.value)} multiple filter optionLabel="name"
<ListBox value={selectedCountries} options={countries} onChange={(e) => setSelectedCountries(e.value)} multiple filter optionLabel="name" filterPlaceholder="Search countries"
itemTemplate={countryTemplate} style={{ width: '15rem' }} listStyle={{ maxHeight: '250px' }} />

<h5>Virtual Scroll (100000 Items)</h5>
Expand Down

0 comments on commit 26cf672

Please sign in to comment.