11import React , { PropTypes } from 'react' ;
2+ import ReactDOM from 'react-dom' ;
23import classNames from 'classnames' ;
34import omit from 'lodash.omit' ;
5+ import TetherContent from './TetherContent' ;
6+ import DropdownMenu from './DropdownMenu' ;
7+ import DropdownToggle from './DropdownToggle' ;
48
59const propTypes = {
610 disabled : PropTypes . bool ,
711 dropup : PropTypes . bool ,
812 group : PropTypes . bool ,
9- open : PropTypes . bool ,
10- tag : PropTypes . string
13+ isOpen : PropTypes . bool ,
14+ tag : PropTypes . string ,
15+ tether : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . bool ] ) ,
16+ toggle : PropTypes . func
1117} ;
1218
1319const defaultProps = {
1420 open : false ,
1521 tag : 'div'
1622} ;
1723
24+ const defaultTetherConfig = {
25+ classPrefix : 'bs-tether' ,
26+ classes : { element : 'dropdown' , enabled : 'open' } ,
27+ constraints : [
28+ { to : 'scrollParent' , attachment : 'together none' } ,
29+ { to : 'window' , attachment : 'together none' }
30+ ]
31+ } ;
32+
1833class Dropdown extends React . Component {
1934 constructor ( props ) {
2035 super ( props ) ;
@@ -23,72 +38,113 @@ class Dropdown extends React.Component {
2338 open : props . open
2439 } ;
2540
26- this . openDropdown = this . openDropdown . bind ( this ) ;
27- this . closeDropdown = this . closeDropdown . bind ( this ) ;
41+ this . addEvents = this . addEvents . bind ( this ) ;
42+ this . getTetherConfig = this . getTetherConfig . bind ( this ) ;
2843 this . handleDocumentClick = this . handleDocumentClick . bind ( this ) ;
29- this . handleContainerClick = this . handleContainerClick . bind ( this ) ;
30- this . toggleDropdown = this . toggleDropdown . bind ( this ) ;
44+ this . removeEvents = this . removeEvents . bind ( this ) ;
45+ this . toggle = this . toggle . bind ( this ) ;
3146 }
3247
3348 componentDidMount ( ) {
34- if ( this . state . open ) {
35- this . openDropdown ( ) ;
49+ this . handleProps ( ) ;
50+ }
51+
52+ componentDidUpdate ( prevProps ) {
53+ if ( this . props . isOpen !== prevProps . isOpen ) {
54+ this . handleProps ( ) ;
3655 }
3756 }
3857
3958 componentWillUnmount ( ) {
40- this . closeDropdown ( ) ;
59+ this . removeEvents ( ) ;
60+ }
61+
62+ getTetherConfig ( childProps ) {
63+ const target = ( ) => this . _target ;
64+ let vElementAttach = 'top' ;
65+ let hElementAttach = 'left' ;
66+ let vTargetAttach = 'bottom' ;
67+ let hTargetAttach = 'left' ;
68+
69+ if ( childProps . right ) {
70+ hElementAttach = 'right' ;
71+ hTargetAttach = 'right' ;
72+ }
73+
74+ if ( childProps . dropup ) {
75+ vElementAttach = 'bottom' ;
76+ vTargetAttach = 'top' ;
77+ }
78+
79+ return {
80+ ...defaultTetherConfig ,
81+ attachment : vElementAttach + ' ' + hElementAttach ,
82+ targetAttachment : vTargetAttach + ' ' + hTargetAttach ,
83+ target,
84+ ...this . props . tether
85+ } ;
4186 }
4287
43- handleDocumentClick ( ) {
44- this . closeDropdown ( ) ;
88+ addEvents ( ) {
89+ document . addEventListener ( 'click' , this . handleDocumentClick ) ;
4590 }
4691
47- handleContainerClick ( e ) {
48- if ( e . nativeEvent && e . nativeEvent . stopImmediatePropagation ) {
49- e . nativeEvent . stopImmediatePropagation ( ) ;
92+ removeEvents ( ) {
93+ document . removeEventListener ( 'click' , this . handleDocumentClick ) ;
94+ }
95+
96+ handleDocumentClick ( e ) {
97+ const container = ReactDOM . findDOMNode ( this ) ;
98+
99+ if ( container . contains ( e . target ) && container !== e . target ) {
100+ return ;
50101 }
102+
103+ this . toggle ( ) ;
51104 }
52105
53- toggleDropdown ( e ) {
54- if ( this . props . disabled ) {
55- return e . preventDefault ( ) ;
106+ handleProps ( ) {
107+ if ( this . props . tether ) {
108+ return ;
56109 }
57110
58- if ( this . state . open ) {
59- this . closeDropdown ( ) ;
111+ if ( this . props . isOpen ) {
112+ this . addEvents ( ) ;
60113 } else {
61- this . openDropdown ( ) ;
114+ this . removeEvents ( ) ;
62115 }
63116 }
64117
65- closeDropdown ( ) {
66- this . setState ( {
67- open : false
68- } ) ;
69- document . removeEventListener ( 'click' , this . handleDocumentClick ) ;
70- }
118+ toggle ( e ) {
119+ if ( this . props . disabled ) {
120+ return e && e . preventDefault ( ) ;
121+ }
71122
72- openDropdown ( ) {
73- this . setState ( {
74- open : true
75- } ) ;
76- document . addEventListener ( 'click' , this . handleDocumentClick ) ;
123+ this . props . toggle ( ) ;
77124 }
78125
79126 renderChildren ( ) {
127+ let props = omit ( this . props , [ 'children' , 'className' , 'id' ] ) ;
128+ props . toggle = this . toggle ;
129+
80130 return React . Children . map ( React . Children . toArray ( this . props . children ) , ( child ) => {
81131 if ( React . isValidElement ( child ) ) {
82- return React . cloneElement (
83- child ,
84- {
85- closeDropdown : this . closeDropdown ,
86- handleContainerClick : this . handleContainerClick ,
87- isDropdownOpen : this . state . open ,
88- openDropdown : this . openDropdown ,
89- toggleDropdown : this . toggleDropdown ,
90- }
91- ) ;
132+ if ( child . type === DropdownToggle ) {
133+ return React . cloneElement ( child , {
134+ ...props ,
135+ ref : ( c ) => this . _target = c
136+ } ) ;
137+ } else if ( child . type === DropdownMenu && ! props . isOpen ) {
138+ // don't bother with hidden content
139+ return null ;
140+ } else if ( child . type === DropdownMenu && props . tether ) {
141+ let tetherConfig = this . getTetherConfig ( child . props ) ;
142+
143+ return (
144+ < TetherContent { ...props } tether = { tetherConfig } > { child } </ TetherContent >
145+ ) ;
146+ }
147+ return React . cloneElement ( child , props ) ;
92148 }
93149 return child ;
94150 } ) ;
@@ -101,13 +157,14 @@ class Dropdown extends React.Component {
101157 group,
102158 'tag' : TagName ,
103159 ...attributes
104- } = omit ( this . props , [ 'children' , 'open ' ] ) ;
160+ } = omit ( this . props , [ 'children' , 'isOpen ' ] ) ;
105161
106162 const classes = classNames (
107163 className ,
108- group ? 'btn-group' : 'dropdown' ,
109164 {
110- open : this . state . open ,
165+ 'btn-group' : group ,
166+ dropdown : ! group ,
167+ open : this . props . isOpen ,
111168 dropup : dropup
112169 }
113170 ) ;
0 commit comments