Skip to content

Commit

Permalink
[Autocomplete] Improve accessibility (mui#18204)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari authored Nov 6, 2019
1 parent 283dd00 commit 7f09ea4
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 117 deletions.
8 changes: 5 additions & 3 deletions docs/pages/api/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">disabled</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the input will be disabled. |
| <span class="prop-name">disableListWrap</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the list box in the popup will not wrap focus. |
| <span class="prop-name">disableOpenOnFocus</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the popup won't open on input focus. |
| <span class="prop-name">disablePortal</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Disable the portal behavior. The children stay within it's parent DOM hierarchy. |
| <span class="prop-name">filterOptions</span> | <span class="prop-type">func</span> | | A filter function that determines the options that are eligible.<br><br>**Signature:**<br>`function(options: undefined, state: object) => undefined`<br>*options:* The options to render.<br>*state:* The state of the component. |
| <span class="prop-name">filterSelectedOptions</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, hide the selected options from the list box. |
| <span class="prop-name">freeSolo</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the Autocomplete is free solo, meaning that the user input is not bound to provided options. |
Expand All @@ -55,12 +56,12 @@ You can learn more about the difference by [reading this guide](/guides/minimizi
| <span class="prop-name">open</span> | <span class="prop-type">bool</span> | | Control the popup` open state. |
| <span class="prop-name">options</span> | <span class="prop-type">array</span> | <span class="prop-default">[]</span> | Array of options. |
| <span class="prop-name">PaperComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">Paper</span> | The component used to render the body of the popup. |
| <span class="prop-name">PopupComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">Popper</span> | The component used to render the popup. |
| <span class="prop-name">PopperComponent</span> | <span class="prop-type">elementType</span> | <span class="prop-default">Popper</span> | The component used to position the popup. |
| <span class="prop-name">renderGroup</span> | <span class="prop-type">func</span> | | Render the group.<br><br>**Signature:**<br>`function(option: any) => ReactNode`<br>*option:* The group to render. |
| <span class="prop-name required">renderInput&nbsp;*</span> | <span class="prop-type">func</span> | | Render the input.<br><br>**Signature:**<br>`function(params: object) => ReactNode`<br>*params:* null |
| <span class="prop-name">renderOption</span> | <span class="prop-type">func</span> | | Render the option, use `getOptionLabel` by default.<br><br>**Signature:**<br>`function(option: any, state: object) => ReactNode`<br>*option:* The option to render.<br>*state:* The state of the component. |
| <span class="prop-name">renderTags</span> | <span class="prop-type">func</span> | | Render the selected value.<br><br>**Signature:**<br>`function(value: any) => ReactNode`<br>*value:* The `value` provided to the component. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | The input value. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | The value of the autocomplete. |

The `ref` is forwarded to the root element.

Expand All @@ -83,7 +84,8 @@ Any other props supplied will be provided to the root element (native element).
| <span class="prop-name">clearIndicatorDirty</span> | <span class="prop-name">.MuiAutocomplete-clearIndicatorDirty</span> | Styles applied to the clear indictator if the input is dirty.
| <span class="prop-name">popupIndicator</span> | <span class="prop-name">.MuiAutocomplete-popupIndicator</span> | Styles applied to the popup indictator.
| <span class="prop-name">popupIndicatorOpen</span> | <span class="prop-name">.MuiAutocomplete-popupIndicatorOpen</span> | Styles applied to the popup indictator if the popup is open.
| <span class="prop-name">popup</span> | <span class="prop-name">.MuiAutocomplete-popup</span> | Styles applied to the popup element.
| <span class="prop-name">popper</span> | <span class="prop-name">.MuiAutocomplete-popper</span> | Styles applied to the popper element.
| <span class="prop-name">popperDisablePortal</span> | <span class="prop-name">.MuiAutocomplete-popperDisablePortal</span> | Styles applied to the popper element if `disablePortal={true}`.
| <span class="prop-name">paper</span> | <span class="prop-name">.MuiAutocomplete-paper</span> | Styles applied to the `Paper` component.
| <span class="prop-name">listbox</span> | <span class="prop-name">.MuiAutocomplete-listbox</span> | Styles applied to the `listbox` component.
| <span class="prop-name">loading</span> | <span class="prop-name">.MuiAutocomplete-loading</span> | Styles applied to the loading wrapper.
Expand Down
20 changes: 1 addition & 19 deletions docs/src/pages/components/autocomplete/GitHubLabel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable no-use-before-define */
import React from 'react';
import PropTypes from 'prop-types';
import { useTheme, fade, makeStyles } from '@material-ui/core/styles';
import Popper from '@material-ui/core/Popper';
import SettingsIcon from '@material-ui/icons/Settings';
Expand All @@ -10,23 +9,6 @@ import Autocomplete from '@material-ui/lab/Autocomplete';
import ButtonBase from '@material-ui/core/ButtonBase';
import InputBase from '@material-ui/core/InputBase';

function Popup(props) {
const { popperRef, anchorEl, open, ...other } = props;
return <div {...other} />;
}

Popup.propTypes = {
anchorEl: PropTypes.object,
open: PropTypes.bool.isRequired,
popperRef: PropTypes.oneOfType([
PropTypes.oneOf([null]),
PropTypes.func,
PropTypes.shape({
current: PropTypes.any.isRequired,
}),
]).isRequired,
};

const useStyles = makeStyles(theme => ({
root: {
width: 221,
Expand Down Expand Up @@ -201,7 +183,7 @@ export default function GitHubLabel() {
setPendingValue(newValue);
}}
disableCloseOnSelect
PopupComponent={Popup}
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option, { selected }) => (
Expand Down
9 changes: 2 additions & 7 deletions docs/src/pages/components/autocomplete/GitHubLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@ import Popper from '@material-ui/core/Popper';
import SettingsIcon from '@material-ui/icons/Settings';
import CloseIcon from '@material-ui/icons/Close';
import DoneIcon from '@material-ui/icons/Done';
import Autocomplete, { PopupProps } from '@material-ui/lab/Autocomplete';
import Autocomplete from '@material-ui/lab/Autocomplete';
import ButtonBase from '@material-ui/core/ButtonBase';
import InputBase from '@material-ui/core/InputBase';

function Popup(props: PopupProps) {
const { popperRef, anchorEl, open, ...other } = props;
return <div {...other} />;
}

const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
Expand Down Expand Up @@ -190,7 +185,7 @@ export default function GitHubLabel() {
setPendingValue(newValue);
}}
disableCloseOnSelect
PopupComponent={Popup}
disablePortal
renderTags={() => null}
noOptionsText="No labels"
renderOption={(option: LabelType, { selected }) => (
Expand Down
7 changes: 7 additions & 0 deletions docs/src/pages/components/autocomplete/Playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ export default function Playground() {
disabled
renderInput={params => <TextField {...params} label="disabled" margin="normal" fullWidth />}
/>
<Autocomplete
{...defaultProps}
disablePortal
renderInput={params => (
<TextField {...params} label="disablePortal" margin="normal" fullWidth />
)}
/>
</div>
);
}
Expand Down
7 changes: 7 additions & 0 deletions docs/src/pages/components/autocomplete/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export default function Playground() {
disabled
renderInput={params => <TextField {...params} label="disabled" margin="normal" fullWidth />}
/>
<Autocomplete
{...defaultProps}
disablePortal
renderInput={params => (
<TextField {...params} label="disablePortal" margin="normal" fullWidth />
)}
/>
</div>
);
}
Expand Down
15 changes: 10 additions & 5 deletions docs/src/pages/components/autocomplete/UseAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import useAutocomplete from '@material-ui/lab/useAutocomplete';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles(theme => ({
root: {
position: 'relative',
},
input: {
width: 200,
},
Expand Down Expand Up @@ -32,7 +35,7 @@ const useStyles = makeStyles(theme => ({
export default function UseAutocomplete() {
const classes = useStyles();
const {
getRootProps,
getComboboxProps,
getInputProps,
getInputLabelProps,
getListboxProps,
Expand All @@ -44,10 +47,12 @@ export default function UseAutocomplete() {
});

return (
<div {...getRootProps()}>
<label {...getInputLabelProps()}>useAutocomplete</label>
<br />
<input className={classes.input} {...getInputProps()} />
<div className={classes.root}>
<div {...getComboboxProps()}>
<label {...getInputLabelProps()}>useAutocomplete</label>
<br />
<input className={classes.input} {...getInputProps()} />
</div>
{groupedOptions.length > 0 ? (
<ul className={classes.listbox} {...getListboxProps()}>
{groupedOptions.map((option, index) => (
Expand Down
15 changes: 10 additions & 5 deletions docs/src/pages/components/autocomplete/UseAutocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
position: 'relative',
},
input: {
width: 200,
},
Expand Down Expand Up @@ -34,7 +37,7 @@ const useStyles = makeStyles((theme: Theme) =>
export default function UseAutocomplete() {
const classes = useStyles();
const {
getRootProps,
getComboboxProps,
getInputProps,
getInputLabelProps,
getListboxProps,
Expand All @@ -46,10 +49,12 @@ export default function UseAutocomplete() {
});

return (
<div {...getRootProps()}>
<label {...getInputLabelProps()}>useAutocomplete</label>
<br />
<input className={classes.input} {...getInputProps()} />
<div className={classes.root}>
<div {...getComboboxProps()}>
<label {...getInputLabelProps()}>useAutocomplete</label>
<br />
<input className={classes.input} {...getInputProps()} />
</div>
{groupedOptions.length > 0 ? (
<ul className={classes.listbox} {...getListboxProps()}>
{groupedOptions.map((option, index) => (
Expand Down
7 changes: 7 additions & 0 deletions docs/src/pages/components/autocomplete/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ Search within 10,000 randomly generated options. The list is virtualized thanks

{{"demo": "pages/components/autocomplete/Virtualize.js"}}

## Limitations

### iOS VoiceOver

VoiceOver on iOS Safari doesn't support the `aria-owns` attribute very well.
You can work around the issue with the `disablePortal` prop.

## Accessibility

(WAI-ARIA: https://www.w3.org/TR/wai-aria-practices/#combobox)
Expand Down
11 changes: 8 additions & 3 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { StandardProps } from '@material-ui/core';
import { UseAutocompleteProps, CreateFilterOptions } from '../useAutocomplete';

export interface PopupProps extends React.HTMLAttributes<HTMLElement> {
export interface PopperProps extends React.HTMLAttributes<HTMLElement> {
anchorEl?: HTMLElement;
open: boolean;
popperRef: React.Ref<unknown>;
Expand Down Expand Up @@ -48,6 +48,11 @@ export interface AutocompleteProps
* If `true`, the input will be disabled.
*/
disabled?: boolean;
/**
* Disable the portal behavior.
* The children stay within it's parent DOM hierarchy.
*/
disablePortal?: boolean;
/**
* The component used to render the listbox.
*/
Expand All @@ -69,9 +74,9 @@ export interface AutocompleteProps
*/
PaperComponent?: React.ComponentType<React.HTMLAttributes<HTMLElement>>;
/**
* The component used to render the popup.
* The component used to position the popup.
*/
PopupComponent?: React.ComponentType<PopupProps>;
PopperComponent?: React.ComponentType<PopperProps>;
/**
* Render the group.
*
Expand Down
Loading

0 comments on commit 7f09ea4

Please sign in to comment.