@@ -4,50 +4,163 @@ import React, { PropTypes } from 'react'
44import classNames from 'classnames'
55import enhanceDropdown from './enhanceDropdown'
66
7- function Dropdown ( props ) {
8- const { className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose } = props
9- const ddClasses = classNames ( 'dropdown-wrap' , {
10- [ `${ className } ` ] : true ,
11- [ `${ theme } ` ] : true
12- } )
13- const ndClasses = classNames ( 'Dropdown' , {
14- 'pointer-shadow' : pointerShadow ,
15- 'pointer-hide' : noPointer ,
16- 'pointer-left' : pointerLeft ,
17- 'no-autoclose' : noAutoclose ,
18- hide : ! isOpen
19- } )
20-
21- return (
22- < div className = { ddClasses } onClick = { noAutoclose ? ( ) => { } : handleClick } >
23- {
24- props . children . map ( ( child , index ) => {
25- if ( child . props . className . indexOf ( 'dropdown-menu-header' ) > - 1 )
26- return noAutoclose ? React . cloneElement ( child , {
27- onClick : handleClick ,
28- key : child . props . key || index
29- } ) : child
30- } )
31- }
32-
33- < div className = { ndClasses } >
7+ class Dropdown extends React . Component {
8+ constructor ( props ) {
9+ super ( props )
10+ }
11+
12+ render ( ) {
13+ const props = this . props
14+ const { children, className, pointerShadow, noPointer, pointerLeft, isOpen, handleClick, theme, noAutoclose, handleKeyboardNavigation } = props
15+ const ddClasses = classNames ( 'dropdown-wrap' , {
16+ [ `${ className } ` ] : true ,
17+ [ `${ theme } ` ] : true
18+ } )
19+ const ndClasses = classNames ( 'Dropdown' , {
20+ 'pointer-shadow' : pointerShadow ,
21+ 'pointer-hide' : noPointer ,
22+ 'pointer-left' : pointerLeft ,
23+ 'no-autoclose' : noAutoclose ,
24+ hide : ! isOpen
25+ } )
26+
27+ let childSelectionIndex = - 1
28+ const focusOnNextChild = ( ) => {
29+ const listChild = this . listRef . getElementsByTagName ( 'li' )
30+ if ( listChild . length === 0 ) {
31+ return
32+ }
33+ childSelectionIndex += 1
34+ if ( childSelectionIndex > listChild . length ) {
35+ childSelectionIndex = 0
36+ }
37+ listChild [ childSelectionIndex ] . focus ( )
38+ }
39+ const focusOnPreviousChild = ( ) => {
40+ const listChild = this . listRef . getElementsByTagName ( 'li' )
41+ if ( listChild . length === 0 ) {
42+ return
43+ }
44+ childSelectionIndex -= 1
45+ if ( childSelectionIndex < 0 ) {
46+ childSelectionIndex = listChild . length - 1
47+ }
48+ listChild [ childSelectionIndex ] . focus ( )
49+ }
50+ const focusOnCharacter = ( value ) => {
51+ const listChild = this . listRef . getElementsByTagName ( 'li' )
52+ if ( listChild . length === 0 ) {
53+ return
54+ }
55+ const length = listChild . length
56+ for ( let i = 0 ; i < length ; i ++ ) {
57+ const textContent = listChild [ i ] . textContent
58+ if ( textContent && textContent . length > 0 ) {
59+ const firstChar = textContent . charAt ( 0 )
60+ if ( firstChar === value || firstChar === value . toLowerCase ( ) ) {
61+ listChild [ i ] . focus ( )
62+ return true
63+ }
64+ }
65+ }
66+ return false
67+ }
68+ const onKeydown = ( e ) => {
69+ if ( ! handleKeyboardNavigation ) {
70+ return
71+ }
72+ const keyCode = e . keyCode
73+ if ( keyCode === 32 || keyCode === 38 || keyCode === 40 ) { // space or Up/Down
74+ // open dropdown menu
75+ if ( ! noAutoclose && ! isOpen ) {
76+ e . preventDefault ( )
77+ handleClick ( event )
78+ } else {
79+ if ( keyCode === 40 ) {
80+ focusOnNextChild ( )
81+ } else if ( keyCode === 38 ) {
82+ focusOnPreviousChild ( )
83+ }
84+ e . preventDefault ( )
85+ }
86+ } else if ( isOpen ) {
87+ const value = String . fromCharCode ( e . keyCode )
88+ if ( focusOnCharacter ( value ) ) {
89+ e . preventDefault ( )
90+ }
91+ }
92+ }
93+ const onChildKeydown = ( e ) => {
94+ if ( ! handleKeyboardNavigation ) {
95+ return
96+ }
97+ const keyCode = e . keyCode
98+ if ( keyCode === 38 || keyCode === 40 || keyCode === 13 ) { // Up/Down or enter
99+ if ( keyCode === 40 ) {
100+ focusOnNextChild ( )
101+ } else if ( keyCode === 38 ) {
102+ focusOnPreviousChild ( )
103+ } else if ( keyCode === 13 ) {
104+ const listChild = this . listRef . getElementsByTagName ( 'li' )
105+ if ( listChild . length === 0 ) {
106+ return
107+ }
108+ listChild [ childSelectionIndex ] . click ( )
109+ }
110+ e . preventDefault ( )
111+ } else {
112+ const value = String . fromCharCode ( e . keyCode )
113+ if ( focusOnCharacter ( value ) ) {
114+ e . preventDefault ( )
115+ }
116+ }
117+ }
118+
119+ const setListRef = ( c ) => this . listRef = c
120+
121+ const childrenWithProps = React . Children . map ( children , child =>
122+ React . cloneElement ( child , { onKeyDown : onChildKeydown } )
123+ )
124+ return (
125+ < div className = { ddClasses } onClick = { noAutoclose ? ( ) => { } : handleClick } >
126+ { handleKeyboardNavigation && ( < a onKeyDown = { onKeydown } className = "handle-keyboard" href = "javascript:;" > </ a > ) }
34127 {
35- props . children . map ( ( child ) => {
36- if ( child . props . className . indexOf ( 'dropdown-menu-list' ) > - 1 )
37- return child
128+ childrenWithProps . map ( ( child , index ) => {
129+ if ( child . props . className . indexOf ( 'dropdown-menu-header' ) > - 1 )
130+ return noAutoclose ? React . cloneElement ( child , {
131+ onClick : handleClick ,
132+ key : child . props . key || index
133+ } ) : child
38134 } )
39135 }
136+ < div ref = { setListRef } className = { ndClasses } >
137+ {
138+ childrenWithProps . map ( ( child ) => {
139+ if ( child . props . className . indexOf ( 'dropdown-menu-list' ) > - 1 )
140+ return child
141+ } )
142+ }
143+ </ div >
40144 </ div >
41- </ div >
42- )
145+ )
146+
147+ }
43148}
44149
45150Dropdown . propTypes = {
46151 children : PropTypes . array . isRequired ,
47152 /*
48153 If true, prevents dropdown closing when clicked inside dropdown
49154 */
50- noAutoclose : PropTypes . bool
155+ noAutoclose : PropTypes . bool ,
156+ /*
157+ If true, prevents handle keyboard event
158+ */
159+ handleKeyboardNavigation : PropTypes . bool
160+ }
161+
162+ Dropdown . defaultProps = {
163+ handleKeyboardNavigation : false
51164}
52165
53166export default enhanceDropdown ( Dropdown )
0 commit comments