diff --git a/lib/components/Tray.js b/lib/components/Tray.js index 041ca0f..d0e5002 100644 --- a/lib/components/Tray.js +++ b/lib/components/Tray.js @@ -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 }; }, diff --git a/lib/components/TrayPortal.js b/lib/components/TrayPortal.js index 151ddde..c4b93d5 100644 --- a/lib/components/TrayPortal.js +++ b/lib/components/TrayPortal.js @@ -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: { @@ -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() { @@ -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(); diff --git a/lib/components/__tests__/Tray-test.js b/lib/components/__tests__/Tray-test.js index ffa8d3e..c9ae247 100644 --- a/lib/components/__tests__/Tray-test.js +++ b/lib/components/__tests__/Tray-test.js @@ -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 = ( +
+ ); + 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); + }); + }); });