Skip to content

Commit

Permalink
[changed] Maintain focus inside of tray
Browse files Browse the repository at this point in the history
This adds a new prop `maintainFocus` which defaults to `true`.
This makes the tray "modal" in that it will not allow focus to escape
from within it until it is explicitly closed via close button, escape key, or
clicking in the overlay area.

Previous behavior can be achieved by setting the `maintainFocus` prop
to `false`.  This defaults to `true` so that it can achieve a greater level of
accessibility support which I felt was valuable to everyone so it is opt-out
rather than opt-in.
  • Loading branch information
claydiffrient committed Jul 7, 2016
1 parent b052164 commit 720c1ce
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
6 changes: 4 additions & 2 deletions lib/components/Tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ export default React.createClass({
isOpen: React.PropTypes.bool,
onBlur: React.PropTypes.func,
closeTimeoutMS: React.PropTypes.number,
closeOnBlur: React.PropTypes.bool
closeOnBlur: React.PropTypes.bool,
maintainFocus: React.PropTypes.bool
},

getDefaultProps() {
return {
isOpen: false,
closeTimeoutMS: 0,
closeOnBlur: true
closeOnBlur: true,
maintainFocus: false
};
},

Expand Down
13 changes: 12 additions & 1 deletion lib/components/TrayPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { PropTypes } from 'react';
import cx from 'classnames';
import focusManager from '../helpers/focusManager';
import isLeavingNode from '../helpers/isLeavingNode';
import findTabbable from '../helpers/tabbable';

const styles = {
overlay: {
Expand Down Expand Up @@ -55,7 +56,8 @@ export default React.createClass({
onBlur: PropTypes.func,
closeOnBlur: PropTypes.bool,
closeTimeoutMS: PropTypes.number,
children: PropTypes.any
children: PropTypes.any,
maintainFocus: PropTypes.bool
},

getInitialState() {
Expand Down Expand Up @@ -108,6 +110,15 @@ export default React.createClass({
this.props.onBlur();
}

// Keep focus inside the tray if maintainFocus is true
if (e.keyCode === 9 && this.props.maintainFocus && isLeavingNode(this.refs.content, e)) {
e.preventDefault();
const tabbable = findTabbable(this.refs.content);
const target = tabbable[e.shiftKey ? tabbable.length - 1 : 0];
target.focus();
return;
}

// Treat tabbing away from content as blur/close if closeOnBlur
if (e.keyCode === 9 && this.props.closeOnBlur && isLeavingNode(this.refs.content, e)) {
e.preventDefault();
Expand Down
31 changes: 31 additions & 0 deletions lib/components/__tests__/Tray-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,35 @@ describe('react-tray', function() {
equal(document.querySelectorAll('.ReactTray__Content').length, 1);
}, 0);
});

describe('maintainFocus prop', function() {
this.timeout(0);
beforeEach(function(done) {
const props = {isOpen: true, onBlur: function() {}, closeTimeoutMS: 0, maintainFocus: true};
const children = (
<div>
<a href="#" id="one">One</a>
<a href="#" id="two">Two</a>
<a href="#" id="three">Three</a>
</div>
);
renderTray(props, children, () => done());
});

it('sends focus to the first item if tabbing away from the last element', function() {
const firstItem = document.querySelector('#one');
const lastItem = document.querySelector('#three');
lastItem.focus();
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {keyCode: 9});
equal(document.activeElement, firstItem);
});

it('sends focus to the last item if shift + tabbing from the first item', function() {
const firstItem = document.querySelector('#one');
const lastItem = document.querySelector('#three');
firstItem.focus();
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {keyCode: 9, shiftKey: true});
equal(document.activeElement, lastItem);
});
});
});

0 comments on commit 720c1ce

Please sign in to comment.