diff --git a/src/Dropdown.js b/src/Dropdown.js index 5b157f59f4..f664f5691a 100644 --- a/src/Dropdown.js +++ b/src/Dropdown.js @@ -46,13 +46,13 @@ class Dropdown extends React.Component { }]; this.state = {}; + + this.lastOpenEventType = null; + this.isKeyboardClick = false; } componentDidMount() { - let menu = this.refs.menu; - if (this.props.open && menu.focusNext) { - menu.focusNext(); - } + this.focusNextOnOpen(); } componentWillUpdate(nextProps) { @@ -65,10 +65,8 @@ class Dropdown extends React.Component { } componentDidUpdate(prevProps) { - let menu = this.refs.menu; - - if (this.props.open && !prevProps.open && menu.focusNext) { - menu.focusNext(); + if (this.props.open && !prevProps.open) { + this.focusNextOnOpen(); } if (!this.props.open && prevProps.open) { @@ -105,9 +103,13 @@ class Dropdown extends React.Component { ); } - toggleOpen() { + toggleOpen(eventType = null) { let open = !this.props.open; + if (open) { + this.lastOpenEventType = eventType; + } + if (this.props.onToggle) { this.props.onToggle(open); } @@ -118,22 +120,21 @@ class Dropdown extends React.Component { return; } - this.toggleOpen(); + this.toggleOpen(this.isKeyboardClick ? 'keydown' : 'click'); + this.isKeyboardClick = false; } handleKeyDown(event) { - let focusNext = () => { - if (this.refs.menu.focusNext) { - this.refs.menu.focusNext(); - } - }; + if (this.props.disabled) { + return; + } switch (event.keyCode) { case keycode.codes.down: if (!this.props.open) { - this.toggleOpen(); - } else { - focusNext(); + this.toggleOpen('keydown'); + } else if (this.refs.menu.focusNext) { + this.refs.menu.focusNext(); } event.preventDefault(); break; @@ -141,6 +142,10 @@ class Dropdown extends React.Component { case keycode.codes.tab: this.handleClose(event); break; + case keycode.codes.space: + case keycode.codes.enter: + this.isKeyboardClick = true; + break; default: } } @@ -153,6 +158,20 @@ class Dropdown extends React.Component { this.toggleOpen(); } + focusNextOnOpen() { + const {menu} = this.refs; + if (!menu.focusNext) { + return; + } + + if ( + this.lastOpenEventType === 'keydown' || + this.props.alwaysFocusNextOnOpen + ) { + menu.focusNext(); + } + } + focus() { let toggle = React.findDOMNode(this.refs[TOGGLE_REF]); @@ -232,7 +251,8 @@ Dropdown.TOGGLE_ROLE = TOGGLE_ROLE; Dropdown.MENU_ROLE = MENU_ROLE; Dropdown.defaultProps = { - componentClass: ButtonGroup + componentClass: ButtonGroup, + alwaysFocusNextOnOpen: false }; Dropdown.propTypes = { @@ -270,7 +290,7 @@ Dropdown.propTypes = { disabled: React.PropTypes.bool, /** - * Align the menu to the right side of the Dropdown toggle + * Align the menu to the right side of the Dropdown toggle */ pullRight: React.PropTypes.bool, @@ -304,7 +324,12 @@ Dropdown.propTypes = { * function(Object event, Any eventKey) * ``` */ - onSelect: React.PropTypes.func + onSelect: React.PropTypes.func, + + /** + * Focus first menu item on menu open on all events, not just keydown events. + */ + alwaysFocusNextOnOpen: React.PropTypes.bool }; Dropdown = uncontrollable(Dropdown, { open: 'onToggle' }); diff --git a/test/DropdownSpec.js b/test/DropdownSpec.js index a50a654b4c..089b8b8503 100644 --- a/test/DropdownSpec.js +++ b/test/DropdownSpec.js @@ -416,7 +416,7 @@ describe('Dropdown', () => { // I am fairly confident that the failure is due to a test specific conflict and not an actual bug. it('when open and the key "esc" is pressed the menu is closed and focus is returned to the button', () => { const instance = React.render( - + {dropdownChildren} , focusableContainer);