@@ -4,50 +4,183 @@ 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 -= 1
36+ } else {
37+ listChild [ childSelectionIndex ] . focus ( )
38+ }
39+ }
40+ const focusOnPreviousChild = ( ) => {
41+ const listChild = this . listRef . getElementsByTagName ( 'li' )
42+ if ( listChild . length === 0 ) {
43+ return
44+ }
45+ childSelectionIndex -= 1
46+ if ( childSelectionIndex < 0 ) {
47+ childSelectionIndex = 0
48+ } else {
49+ listChild [ childSelectionIndex ] . focus ( )
50+ }
51+ }
52+ let searchKey = ''
53+ let timer
54+ const focusOnCharacter = ( value ) => {
55+ searchKey += value
56+ if ( timer ) {
57+ clearTimeout ( timer )
58+ }
59+ timer = setTimeout ( ( ) => { searchKey = '' } , 500 )
60+ const listChild = this . listRef . getElementsByTagName ( 'li' )
61+ if ( listChild . length === 0 ) {
62+ return
63+ }
64+ const length = listChild . length
65+ for ( let i = 0 ; i < length ; i ++ ) {
66+ let textContent = listChild [ i ] . textContent
67+ if ( textContent && textContent . length > 0 ) {
68+ textContent = textContent . toLowerCase ( )
69+ const search = searchKey . toLowerCase ( )
70+ if ( textContent . startsWith ( search ) ) {
71+ childSelectionIndex = i
72+ listChild [ i ] . focus ( )
73+ return true
74+ }
75+ }
76+ }
77+ return false
78+ }
79+ const onFocus = ( ) => {
80+ this . containerRef . classList . add ( 'focused' )
81+ }
82+ const onBlur = ( ) => {
83+ this . containerRef . classList . remove ( 'focused' )
84+ }
85+ const onKeydown = ( e ) => {
86+ if ( ! handleKeyboardNavigation ) {
87+ return
88+ }
89+ const keyCode = e . keyCode
90+ if ( keyCode === 32 || keyCode === 38 || keyCode === 40 ) { // space or Up/Down
91+ // open dropdown menu
92+ if ( ! noAutoclose && ! isOpen ) {
93+ e . preventDefault ( )
94+ handleClick ( event )
95+ } else {
96+ if ( keyCode === 40 ) {
97+ focusOnNextChild ( )
98+ } else if ( keyCode === 38 ) {
99+ focusOnPreviousChild ( )
100+ }
101+ e . preventDefault ( )
102+ }
103+ } else if ( isOpen ) {
104+ const value = String . fromCharCode ( e . keyCode )
105+ if ( focusOnCharacter ( value ) ) {
106+ e . preventDefault ( )
107+ }
108+ }
109+ }
110+ const onChildKeydown = ( e ) => {
111+ if ( ! handleKeyboardNavigation ) {
112+ return
113+ }
114+ const keyCode = e . keyCode
115+ if ( keyCode === 38 || keyCode === 40 || keyCode === 13 ) { // Up/Down or enter
116+ if ( keyCode === 40 ) {
117+ focusOnNextChild ( )
118+ } else if ( keyCode === 38 ) {
119+ focusOnPreviousChild ( )
120+ } else if ( keyCode === 13 ) { // enter
121+ const listChild = this . listRef . getElementsByTagName ( 'li' )
122+ if ( listChild . length === 0 ) {
123+ return
124+ }
125+ listChild [ childSelectionIndex ] . click ( )
126+ this . handleKeyboardRef . focus ( )
127+ }
128+ e . preventDefault ( )
129+ } else {
130+ const value = String . fromCharCode ( e . keyCode )
131+ if ( focusOnCharacter ( value ) ) {
132+ e . preventDefault ( )
133+ }
134+ }
135+ }
136+
137+ const setListRef = ( c ) => this . listRef = c
138+ const setContainerRef = ( c ) => this . containerRef = c
139+ const setHandleKeyboardRef = ( c ) => this . handleKeyboardRef = c
140+
141+ const childrenWithProps = React . Children . map ( children , child =>
142+ React . cloneElement ( child , { onKeyDown : onChildKeydown } )
143+ )
144+ return (
145+ < div ref = { setContainerRef } className = { ddClasses } onClick = { noAutoclose ? ( ) => { } : handleClick } >
146+ { handleKeyboardNavigation && ( < a ref = { setHandleKeyboardRef } tabIndex = "0" onFocus = { onFocus } onBlur = { onBlur } onKeyDown = { onKeydown } className = "handle-keyboard" href = "javascript:;" > </ a > ) }
34147 {
35- props . children . map ( ( child ) => {
36- if ( child . props . className . indexOf ( 'dropdown-menu-list' ) > - 1 )
37- return child
148+ childrenWithProps . map ( ( child , index ) => {
149+ if ( child . props . className . indexOf ( 'dropdown-menu-header' ) > - 1 )
150+ return noAutoclose ? React . cloneElement ( child , {
151+ onClick : handleClick ,
152+ key : child . props . key || index
153+ } ) : child
38154 } )
39155 }
156+ < div ref = { setListRef } className = { ndClasses } >
157+ {
158+ childrenWithProps . map ( ( child ) => {
159+ if ( child . props . className . indexOf ( 'dropdown-menu-list' ) > - 1 )
160+ return child
161+ } )
162+ }
163+ </ div >
40164 </ div >
41- </ div >
42- )
165+ )
166+
167+ }
43168}
44169
45170Dropdown . propTypes = {
46171 children : PropTypes . array . isRequired ,
47172 /*
48173 If true, prevents dropdown closing when clicked inside dropdown
49174 */
50- noAutoclose : PropTypes . bool
175+ noAutoclose : PropTypes . bool ,
176+ /*
177+ If true, prevents handle keyboard event
178+ */
179+ handleKeyboardNavigation : PropTypes . bool
180+ }
181+
182+ Dropdown . defaultProps = {
183+ handleKeyboardNavigation : false
51184}
52185
53186export default enhanceDropdown ( Dropdown )
0 commit comments