diff --git a/src/Dropdown.js b/src/Dropdown.js index 92b07b1983..88fd672e3b 100644 --- a/src/Dropdown.js +++ b/src/Dropdown.js @@ -6,10 +6,14 @@ import ButtonGroup from './ButtonGroup'; import DropdownToggle from './DropdownToggle'; import DropdownMenu from './DropdownMenu'; import CustomPropTypes from './utils/CustomPropTypes'; +import ValidComponentChildren from './utils/ValidComponentChildren'; import createChainedFunction from './utils/createChainedFunction'; import find from 'lodash/collection/find'; import omit from 'lodash/object/omit'; +import activeElement from 'dom-helpers/activeElement'; +import contains from 'dom-helpers/query/contains'; + const TOGGLE_REF = 'toggle-btn'; export const TOGGLE_ROLE = DropdownToggle.defaultProps.bsRole; @@ -52,11 +56,30 @@ class Dropdown extends React.Component { } } - componentDidUpdate(prevProps, prevState) { + componentWillUpdate(nextProps) { + if (!nextProps.open && this.props.open) { + this._focusInDropdown = contains( + React.findDOMNode(this.refs.menu), + activeElement(document) + ); + } + } + + componentDidUpdate(prevProps) { let menu = this.refs.menu; + if (this.props.open && !prevProps.open && menu.focusNext) { menu.focusNext(); } + + if (!this.props.open && prevProps.open) { + // if focus hasn't already moved from the menu lets return it + // to the toggle + if (this._focusInDropdown) { + this._focusInDropdown = false; + this.focus(); + } + } } render() { @@ -74,6 +97,7 @@ class Dropdown extends React.Component { return ( { children } @@ -84,7 +108,7 @@ class Dropdown extends React.Component { toggleOpen() { let open = !this.props.open; - if (this.props.onToggle){ + if (this.props.onToggle) { this.props.onToggle(open); } } @@ -115,9 +139,7 @@ class Dropdown extends React.Component { break; case keycode.codes.esc: case keycode.codes.tab: - if (this.props.open) { - this.handleClose(event); - } + this.handleClose(event); break; default: } @@ -128,19 +150,13 @@ class Dropdown extends React.Component { return; } - // we need to let the current event finish before closing the menu. - // otherwise the menu may close, shifting focus to document.body, before focus has moved - // to the next focusable input - if (event && event.keyCode === keycode.codes.tab){ - setTimeout(this.toggleOpen); - } else { - this.toggleOpen(); - } + this.toggleOpen(); + } - if (event && event.type === 'keydown' && event.keyCode === keycode.codes.esc) { - let toggle = React.findDOMNode(this.refs[TOGGLE_REF]); - event.preventDefault(); - event.stopPropagation(); + focus(){ + let toggle = React.findDOMNode(this.refs[TOGGLE_REF]); + + if (toggle && toggle.focus) { toggle.focus(); } } @@ -149,7 +165,7 @@ class Dropdown extends React.Component { let open = !!this.props.open; let seen = {}; - return React.Children.map(this.props.children, child => { + return ValidComponentChildren.map(this.props.children, child => { let extractor = find(this.childExtractors, x => x.matches(child)); if (extractor) {