Skip to content

Commit 720c1ce

Browse files
committed
[changed] Maintain focus inside of tray
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.
1 parent b052164 commit 720c1ce

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

lib/components/Tray.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ export default React.createClass({
1010
isOpen: React.PropTypes.bool,
1111
onBlur: React.PropTypes.func,
1212
closeTimeoutMS: React.PropTypes.number,
13-
closeOnBlur: React.PropTypes.bool
13+
closeOnBlur: React.PropTypes.bool,
14+
maintainFocus: React.PropTypes.bool
1415
},
1516

1617
getDefaultProps() {
1718
return {
1819
isOpen: false,
1920
closeTimeoutMS: 0,
20-
closeOnBlur: true
21+
closeOnBlur: true,
22+
maintainFocus: false
2123
};
2224
},
2325

lib/components/TrayPortal.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { PropTypes } from 'react';
22
import cx from 'classnames';
33
import focusManager from '../helpers/focusManager';
44
import isLeavingNode from '../helpers/isLeavingNode';
5+
import findTabbable from '../helpers/tabbable';
56

67
const styles = {
78
overlay: {
@@ -55,7 +56,8 @@ export default React.createClass({
5556
onBlur: PropTypes.func,
5657
closeOnBlur: PropTypes.bool,
5758
closeTimeoutMS: PropTypes.number,
58-
children: PropTypes.any
59+
children: PropTypes.any,
60+
maintainFocus: PropTypes.bool
5961
},
6062

6163
getInitialState() {
@@ -108,6 +110,15 @@ export default React.createClass({
108110
this.props.onBlur();
109111
}
110112

113+
// Keep focus inside the tray if maintainFocus is true
114+
if (e.keyCode === 9 && this.props.maintainFocus && isLeavingNode(this.refs.content, e)) {
115+
e.preventDefault();
116+
const tabbable = findTabbable(this.refs.content);
117+
const target = tabbable[e.shiftKey ? tabbable.length - 1 : 0];
118+
target.focus();
119+
return;
120+
}
121+
111122
// Treat tabbing away from content as blur/close if closeOnBlur
112123
if (e.keyCode === 9 && this.props.closeOnBlur && isLeavingNode(this.refs.content, e)) {
113124
e.preventDefault();

lib/components/__tests__/Tray-test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,35 @@ describe('react-tray', function() {
7979
equal(document.querySelectorAll('.ReactTray__Content').length, 1);
8080
}, 0);
8181
});
82+
83+
describe('maintainFocus prop', function() {
84+
this.timeout(0);
85+
beforeEach(function(done) {
86+
const props = {isOpen: true, onBlur: function() {}, closeTimeoutMS: 0, maintainFocus: true};
87+
const children = (
88+
<div>
89+
<a href="#" id="one">One</a>
90+
<a href="#" id="two">Two</a>
91+
<a href="#" id="three">Three</a>
92+
</div>
93+
);
94+
renderTray(props, children, () => done());
95+
});
96+
97+
it('sends focus to the first item if tabbing away from the last element', function() {
98+
const firstItem = document.querySelector('#one');
99+
const lastItem = document.querySelector('#three');
100+
lastItem.focus();
101+
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {keyCode: 9});
102+
equal(document.activeElement, firstItem);
103+
});
104+
105+
it('sends focus to the last item if shift + tabbing from the first item', function() {
106+
const firstItem = document.querySelector('#one');
107+
const lastItem = document.querySelector('#three');
108+
firstItem.focus();
109+
TestUtils.Simulate.keyDown(document.querySelector('.ReactTray__Content'), {keyCode: 9, shiftKey: true});
110+
equal(document.activeElement, lastItem);
111+
});
112+
});
82113
});

0 commit comments

Comments
 (0)