Skip to content

Conversation

@michaelkro
Copy link
Contributor

@michaelkro michaelkro commented Dec 4, 2018

BREAKING CHANGE: The reactRoot prop is required

Fixes #561

What: Adds keyboard and screen reader focus trapping

Notes/Resources:

  1. react accessible modal (github.com/reactjs)
  2. a11y dialog
  3. The consumer must pass the react root HTMLElement as a prop.
  4. When the modal is opened, aria-hidden=true is applied to the react root element, hiding it from screen readers.
  5. When the modal is closed, the aria-hidden attribute is removed from the react root element

@coveralls
Copy link

coveralls commented Dec 4, 2018

Pull Request Test Coverage Report for Build 3518

  • 2 of 4 (50.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.03%) to 82.202%

Changes Missing Coverage Covered Lines Changed/Added Lines %
packages/patternfly-4/react-core/src/components/Modal/Modal.js 2 4 50.0%
Totals Coverage Status
Change from base Build 3512: -0.03%
Covered Lines: 3983
Relevant Lines: 4584

💛 - Coveralls

@michaelkro michaelkro force-pushed the 561-modal-keyboard-interaction branch from 3dab5e5 to 9551ff0 Compare December 4, 2018 22:53
@patternfly-build
Copy link
Collaborator

PatternFly-React preview: https://1011-pr-patternfly-react-patternfly.surge.sh

@michaelkro
Copy link
Contributor Author

michaelkro commented Dec 5, 2018

Having trouble writing tests that check that the aria-hidden attribute is applied/removed from the React root.

Using mount, it seems that componentDidUpdate fires at some unknown time. Taking a snapshot of document.body, I see something like the following

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`the React root is hidden from screen readers when the modal is open 1`] = `
<body
  class="pf-c-backdrop__open"
>
  <div />
  <div />
  <div />
  <div />
  <div />
  <div />
</body>
`;

On a side note, I'm not sure why there are so many divs here

Notice that the pf-c-backdrop__open class is being applied. I can comment out the line that applies that style, and when I rerun the tests (with the mounted Modal being open), the snapshot does not error, i.e., the class is still somehow being applied.

Trying to see if componentDidUpdate is actually triggered, I added the following

  const view = mount(<Modal {...props} isOpen />);
  const spy = jest.spyOn(document.body.classList, 'add');

  view.update();

  expect(spy).toHaveBeenCalled();

which results in failure. I tried clearing the jest cache, and sadly that didn't help.

I was hoping to do the something like

test('the React root is hidden from screen readers when the modal is open', () => {
  document.body.appendChild(props.reactRoot);
  const view = mount(<Modal {...props} isOpen />);
  const spy = jest.spyOn(view.prop('reactRoot'), 'setAttribute');

  view.update();

  expect(spy).toHaveBeenCalledWith(stuff);
});

Any ideas/suggestions here?

@jgiardino
Copy link
Contributor

Thanks for working on this! Everything looks good wrt screen readers. I tested in JAWS, NVDA, and VO, and all trap focus, and contents outside the modal are not accessible while the modal is visible.

jgiardino
jgiardino previously approved these changes Dec 6, 2018
Copy link
Contributor

@jgiardino jgiardino left a comment

Choose a reason for hiding this comment

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

Works as expected!

BREAKING CHANGE: The reactRoot prop is required

Fixes patternfly#561
@michaelkro michaelkro force-pushed the 561-modal-keyboard-interaction branch from 304a00d to 764d051 Compare December 10, 2018 11:56
isLarge: PropTypes.bool
isLarge: PropTypes.bool,
/** React application root element */
reactRoot: PropTypes.instanceOf(safeHTMLElement).isRequired
Copy link
Collaborator

Choose a reason for hiding this comment

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

could we default to document.body and make it optional?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree this seems pretty invasive for consumers. I haven't seen this before in others. It looks like the reasoning is for setting aria-hidden? It looks like that has been deprecated/replaced by aria-modal:
https://www.w3.org/TR/wai-aria-practices/#dialog_roles_states_props

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe checking out atlaskits? It seems their focus trap works well, but I can't speak to screen readers: https://atlaskit.atlassian.com/packages/core/modal-dialog

Copy link
Contributor

Choose a reason for hiding this comment

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

I had tested several implementations before to see what worked across the different screen reader / browser combinations we test in. The attributes role="dialog" and aria-modal don't seem to be supported as described by wai-aria. When I test their solution using JAWS / Chrome, focus is not trapped. It also is not trapped using focus-trap-react.

Solutions that did work were:

I did not test atlaskit, but will do that and let you know what the results were.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, bummer maybe aria-modal is not widely supported yet.

Here is another implementation to check: https://ui.reach.tech/dialog

It does not require a root, but sets aria-hidden on root elements.

Code here: https://github.com/reach/reach-ui/blob/master/packages/dialog/src/index.js#L7

Looks like it is just using document.querySelectorAll("body > *") and hides everything but the dialog root.

Copy link
Contributor

Choose a reason for hiding this comment

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

I captured part of this discussion in a separate issue: #1037

Copy link
Contributor

@tlabaj tlabaj left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modal keyboard interaction

7 participants