@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
66import ReactDOM from 'react-dom' ;
77import { Manager } from 'react-popper' ;
88import classNames from 'classnames' ;
9- import { mapToCssModules , omit } from './utils' ;
9+ import { mapToCssModules , omit , keyCodes } from './utils' ;
1010
1111const propTypes = {
1212 disabled : PropTypes . bool ,
@@ -39,6 +39,7 @@ class Dropdown extends React.Component {
3939
4040 this . addEvents = this . addEvents . bind ( this ) ;
4141 this . handleDocumentClick = this . handleDocumentClick . bind ( this ) ;
42+ this . handleKeyDown = this . handleKeyDown . bind ( this ) ;
4243 this . removeEvents = this . removeEvents . bind ( this ) ;
4344 this . toggle = this . toggle . bind ( this ) ;
4445 }
@@ -65,26 +66,84 @@ class Dropdown extends React.Component {
6566 this . removeEvents ( ) ;
6667 }
6768
69+ getContainer ( ) {
70+ return ReactDOM . findDOMNode ( this ) ;
71+ }
72+
6873 addEvents ( ) {
69- [ 'click' , 'touchstart' ] . forEach ( event =>
74+ [ 'click' , 'touchstart' , 'keyup' ] . forEach ( event =>
7075 document . addEventListener ( event , this . handleDocumentClick , true )
7176 ) ;
7277 }
7378
7479 removeEvents ( ) {
75- [ 'click' , 'touchstart' ] . forEach ( event =>
80+ [ 'click' , 'touchstart' , 'keyup' ] . forEach ( event =>
7681 document . removeEventListener ( event , this . handleDocumentClick , true )
7782 ) ;
7883 }
7984
8085 handleDocumentClick ( e ) {
81- const container = ReactDOM . findDOMNode ( this ) ;
86+ if ( e && ( e . which === 3 || ( e . type === 'keyup' && e . which !== keyCodes . tab ) ) ) return ;
87+ const container = this . getContainer ( ) ;
88+
89+ if ( container . contains ( e . target ) && container !== e . target && ( e . type !== 'keyup' || e . which === keyCodes . tab ) ) {
90+ return ;
91+ }
92+
93+ this . toggle ( e ) ;
94+ }
95+
96+ handleKeyDown ( e ) {
97+ if ( [ keyCodes . esc , keyCodes . up , keyCodes . down , keyCodes . space ] . indexOf ( e . which ) === - 1 ||
98+ ( / b u t t o n / i. test ( e . target . tagName ) && e . which === keyCodes . space ) ||
99+ / i n p u t | t e x t a r e a / i. test ( e . target . tagName ) ) {
100+ return ;
101+ }
102+
103+ e . preventDefault ( ) ;
104+ if ( this . props . disabled ) return ;
105+
106+ const container = this . getContainer ( ) ;
107+
108+ if ( e . which === keyCodes . space && this . props . isOpen && container !== e . target ) {
109+ e . target . click ( ) ;
110+ }
82111
83- if ( container . contains ( e . target ) && container !== e . target ) {
112+ if ( e . which === keyCodes . esc || ! this . props . isOpen ) {
113+ this . toggle ( e ) ;
114+ container . querySelector ( '[aria-expanded]' ) . focus ( ) ;
84115 return ;
85116 }
86117
87- this . toggle ( ) ;
118+ const menuClass = mapToCssModules ( 'dropdown-menu' , this . props . cssModule ) ;
119+ const itemClass = mapToCssModules ( 'dropdown-item' , this . props . cssModule ) ;
120+ const disabledClass = mapToCssModules ( 'disabled' , this . props . cssModule ) ;
121+
122+ const items = container . querySelectorAll ( `.${ menuClass } .${ itemClass } :not(.${ disabledClass } )` ) ;
123+
124+ if ( ! items . length ) return ;
125+
126+ let index = - 1 ;
127+ for ( let i = 0 ; i < items . length ; i += 1 ) {
128+ if ( items [ i ] === e . target ) {
129+ index = i ;
130+ break ;
131+ }
132+ }
133+
134+ if ( e . which === keyCodes . up && index > 0 ) {
135+ index -= 1 ;
136+ }
137+
138+ if ( e . which === keyCodes . down && index < items . length - 1 ) {
139+ index += 1 ;
140+ }
141+
142+ if ( index < 0 ) {
143+ index = 0 ;
144+ }
145+
146+ items [ index ] . focus ( ) ;
88147 }
89148
90149 handleProps ( ) {
@@ -124,7 +183,7 @@ class Dropdown extends React.Component {
124183 dropup : dropup
125184 }
126185 ) , cssModule ) ;
127- return < Manager { ...attrs } className = { classes } /> ;
186+ return < Manager { ...attrs } className = { classes } onKeyDown = { this . handleKeyDown } /> ;
128187 }
129188}
130189
0 commit comments