Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(Accessibility): Added focus handling for List component #256

Merged
merged 48 commits into from
Oct 11, 2018

Conversation

smykhailov
Copy link
Collaborator

Let me introduce focus handling for List component, which is based on the idea of and roving tabindex technique. Also, the idea is based on the current implementation of accessibility behaviors and key handlers. This implementation is based on the research and PoC located in the accessibility repository

Container/AtomicItem hierarchy

A lot of components can be presented as a hierarchical structure as container and item(s), each item may also contain a nested container with its own items. Having this in mind, we can encapsulate focus handling into two components and inject the corresponding functionality into the React component. When container created it can set the first (or any other) item to be focused. The item itself handles the pressed key and fires the corresponding event. Container receives this event and does what is requested as it knows about children, how to handle them and when they have to be updated and how.

Roving tabindex technique

Setting the tabindex of the focused element to "0" ensures that if the user tabs away from the widget and then return, the selected item within the group retains focus. Note that updating the tabindex to "0" requires also updating the previously selected item to tabindex="-1". This technique involves programmatically moving the focus in response to key events and updating the tabindex to reflect the currently focused item.

<radio tabindex="-1">1</radio>
<radio tabindex="-1">2</radio>
<radio tabindex="0">3</radio>
<radio tabindex="-1">4</radio>
<radio tabindex="-1">5</radio>

more details about roving tabindex

@smykhailov smykhailov changed the title Added focus handling for List component (RFC) Added focus handling for List component Sep 20, 2018
@smykhailov smykhailov changed the title (RFC) Added focus handling for List component [RFC] Added focus handling for List component Sep 20, 2018
@@ -90,7 +108,13 @@ class List extends UIComponent<Extendable<IListProps>, any> {
const { items } = this.props
const itemProps = _.pick(this.props, List.itemProps)

return _.map(items, item => ListItem.create(item, { defaultProps: itemProps }))
return _.map(items, (item, idx) => {
itemProps.focusableItemProps = this.focusContainer.assignAtomicItemsProps(idx, items.length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably, prepareItemProps or createItemProps (with Item being a singular form) would be better, due to this method

  • prepares props for single item element
  • there is no assignment for the result props object made there
  • probably, we don't need to repeat Atomic in method's names

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed

import { ComponentVariablesInput, ComponentPartStyle } from '../../../types/theme'
import { Extendable } from '../../../types/utils'

export interface IListItemProps {
accessibility?: Accessibility
as?: any
focusableItemProps?: IFocusableItemProps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for the sake of consistency, lets preserve alphabetical order

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -35,6 +46,7 @@ class ListItem extends UIComponent<Extendable<IListItemProps>, any> {

static propTypes = {
as: customPropTypes.as,
focusableItemProps: PropTypes.object,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe we should place it closer to accessibility prop

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -75,7 +87,17 @@ class ListItem extends UIComponent<Extendable<IListItemProps>, any> {
accessibility: listItemBehavior as Accessibility,
}

state: any = {}
constructor(props: IListItemProps, context: any) {
Copy link
Contributor

@kuzhelov kuzhelov Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context should be omitted as well :) We are not supporting legacy React API. Also, in either case, there is no need to provide this arg here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -85,6 +107,10 @@ class ListItem extends UIComponent<Extendable<IListItemProps>, any> {
this.setState({ isHovering: false })
}

componentDidUpdate() {
this.focusableItem.tryFocus(ReactDOM.findDOMNode(this.itemRef.current!) as HTMLElement)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is better to be rewritten the following way, as we will reduce amount of casts used by one:

ReactDOM.findDOMNode(this.itemRef.current) as HTMLElement

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -11,6 +11,7 @@ const selectableListItemBehavior: Accessibility = (props: any) => ({
root: {
role: 'option',
'aria-selected': !!props['active'],
tabIndex: props.focusableItemProps.isFocused ? '0' : '-1',
Copy link
Contributor

@kuzhelov kuzhelov Oct 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems that we should at least specify type for props here, so that it will be clear from which context focusableItemProps come from. Also, we should think about following thing - what if this behavior will be applied to the component that doesn't have this prop support (for some reason, like some custom component that haven't introduced support for FocusableContainer - to support some custom focus handling logic, for instance)? In that case we shouldn't make tabIndex assignments here - otherwise behavior for selectableListItem will be tightly coupled with the focus handling one.

May suggest to either

  • consider to merge focus handling to be a thing that is defined by accessibility behavior (so that there won't be any code like FocusContainer.create in the component's code - all this will come as accessibility behavior concern). See this option is a preferable one
  • or decouple these two completely - so that focus handling logic will be entirely responsible for handling tabIndex attributes as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the next round of refactoring

@kuzhelov
Copy link
Contributor

kuzhelov commented Oct 9, 2018

agreed with Sergey to address several follow-up points by means of PRs that will come next. For the sake of making the roadmap clear, will file an issue item where all these findings will be summarised - and after that merge the PR 👍

In general, lets scope the area to which this approach is applied to List and ListItem components, till the moment when all the necessary refactoring steps will be made and, as a result, general strategy of applying this functionality will reach its maturity point.

Copy link
Contributor

@kuzhelov kuzhelov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provided functionality raise the following error on switch between Shorthand API and Children API modes of List Selection example. Let sync tomorrow and fix before merging

image

@kuzhelov kuzhelov added needs author changes Author needs to implement changes before merge and removed ready for merge labels Oct 10, 2018
@smykhailov smykhailov added ready for merge and removed needs author changes Author needs to implement changes before merge labels Oct 11, 2018
@smykhailov smykhailov merged commit d0a31a6 into master Oct 11, 2018
@smykhailov smykhailov deleted the smykhailov/focus-navigation-list branch October 11, 2018 08:24
@gopalgoel19 gopalgoel19 mentioned this pull request Oct 16, 2018
11 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants