11import React from 'react' ;
2- import ReactDOM from 'react-dom' ;
2+ import ReactDOM , { findDOMNode } from 'react-dom' ;
33import PropTypes from 'prop-types' ;
44import Popup from 'react-widget-popup' ;
5+ import Portal from 'react-widget-portal' ;
56import getPlacement from 'bplokjs-placement' ;
67import Deferred from 'bplokjs-deferred' ;
8+ import { listen } from 'bplokjs-dom-utils/events' ;
9+ // import classnames from 'classnames';
10+ // import omit from 'omit.js';
11+ const contains = require ( 'bplokjs-dom-utils/contains' )
12+
13+ import PopupRootComponent from './PopupRootComponent' ;
14+
15+ const isMobile = typeof navigator !== 'undefined' && ! ! navigator . userAgent . match (
16+ / ( A n d r o i d | i P h o n e | i P a d | i P o d | i O S | U C W E B ) / i
17+ ) ;
718
819const propTypes = {
20+ children : PropTypes . any ,
921 offset : PropTypes . oneOfType ( [ PropTypes . number , PropTypes . array ] ) ,
1022 action : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . arrayOf ( PropTypes . string ) ] ) ,
1123 showAction : PropTypes . any ,
@@ -16,6 +28,21 @@ const propTypes = {
1628 PropTypes . number ,
1729 PropTypes . object ,
1830 ] ) ,
31+ getPopupContainer : PropTypes . func ,
32+ getDocument : PropTypes . func ,
33+ prefixCls : PropTypes . string ,
34+ popupClassName : PropTypes . string ,
35+ popupMaskClassName : PropTypes . string ,
36+ //maskProps: PropTypes.object,
37+ defaultPopupVisible : PropTypes . bool ,
38+ popupProps : PropTypes . object ,
39+ mask : PropTypes . bool ,
40+ maskClosable : PropTypes . bool ,
41+ popupRootComponent : PropTypes . any ,
42+ destroyPopupOnHide : PropTypes . bool ,
43+ popupStyle : PropTypes . object ,
44+ popupMaskStyle : PropTypes . object ,
45+ zIndex : PropTypes . number ,
1946}
2047
2148function noop ( ) { }
@@ -34,6 +61,16 @@ export default class Trigger extends React.Component {
3461 hideAction : [ ] ,
3562 delay : 0 ,
3663 onPopupVisibleChange : noop ,
64+ getDocument : ( ) => window . document ,
65+ prefixCls : "rw-trigger" ,
66+ mask : false ,
67+ maskClosable : true ,
68+ destroyPopupOnHide : true ,
69+ popupProps : { } ,
70+ popupRootComponent : PopupRootComponent ,
71+ popupStyle : { } ,
72+ popupMaskStyle : { } ,
73+ zIndex : null ,
3774 }
3875
3976 static getDerivedStateFromProps ( props , state ) {
@@ -46,16 +83,98 @@ export default class Trigger extends React.Component {
4683 popupVisible : this . props . defaultPopupVisible ,
4784 }
4885
86+ _popup = null
87+
4988 delayTimer = null
5089
5190 promise = null
5291
5392 componentDidMount ( ) {
54- this . resolvePopupDOM ( ) ;
93+ if ( this . state . popupVisible ) {
94+ this . resolvePopupDOM ( ) ;
95+ }
96+
97+ this . togglePopupCloseEvents ( ) ;
5598 }
5699
57100 componentDidUpdate ( ) {
58- this . resolvePopupDOM ( ) ;
101+ if ( this . state . popupVisible ) {
102+ this . resolvePopupDOM ( ) ;
103+ }
104+
105+ this . togglePopupCloseEvents ( ) ;
106+ }
107+
108+ componentWillUnmount ( ) {
109+ this . clearDelayTimer ( ) ;
110+ }
111+
112+ togglePopupCloseEvents ( ) {
113+ const { getDocument } = this . props ;
114+ const { popupVisible } = this . state ;
115+
116+ if ( popupVisible ) {
117+ const currentDocument = getDocument ( ) ;
118+
119+ if ( ! this . clickOutsideHandler && ( this . isClickToHide ( ) || this . isContextMenuToShow ( ) ) ) {
120+ this . clickOutsideHandler = listen ( currentDocument ,
121+ 'mousedown' , this . onDocumentClick ) ;
122+ }
123+
124+ if ( ! this . touchOutsideHandler && isMobile ) {
125+ this . touchOutsideHandler = listen ( currentDocument ,
126+ 'click' , this . onDocumentClick ) ;
127+ }
128+
129+ // close popup when trigger type contains 'onContextMenu' and document is scrolling.
130+ if ( ! this . contextMenuOutsideHandler1 && this . isContextMenuToShow ( ) ) {
131+ this . contextMenuOutsideHandler1 = listen ( currentDocument ,
132+ 'scroll' , this . onContextMenuClose ) ;
133+ }
134+ // close popup when trigger type contains 'onContextMenu' and window is blur.
135+ if ( ! this . contextMenuOutsideHandler2 && this . isContextMenuToShow ( ) ) {
136+ this . contextMenuOutsideHandler2 = listen ( window ,
137+ 'blur' , this . onContextMenuClose ) ;
138+ }
139+ } else {
140+ this . clearOutsideHandler ( ) ;
141+ }
142+ }
143+
144+ onDocumentClick = ( event ) => {
145+ if ( this . props . mask && ! this . props . maskClosable ) {
146+ return ;
147+ }
148+
149+ const target = event . target ;
150+ const root = findDOMNode ( this ) ;
151+ const popupNode = this . getPopupDomNode ( ) ;
152+
153+ if ( ! contains ( root , target ) && ! contains ( popupNode , target ) ) {
154+ this . close ( ) ;
155+ }
156+ }
157+
158+ clearOutsideHandler ( ) {
159+ if ( this . clickOutsideHandler ) {
160+ this . clickOutsideHandler ( ) ;
161+ this . clickOutsideHandler = null ;
162+ }
163+
164+ if ( this . contextMenuOutsideHandler1 ) {
165+ this . contextMenuOutsideHandler1 ( ) ;
166+ this . contextMenuOutsideHandler1 = null ;
167+ }
168+
169+ if ( this . contextMenuOutsideHandler2 ) {
170+ this . contextMenuOutsideHandler2 ( ) ;
171+ this . contextMenuOutsideHandler2 = null ;
172+ }
173+
174+ if ( this . touchOutsideHandler ) {
175+ this . touchOutsideHandler ( ) ;
176+ this . touchOutsideHandler = null ;
177+ }
59178 }
60179
61180 resolvePopupDOM ( ) {
@@ -91,13 +210,13 @@ export default class Trigger extends React.Component {
91210
92211 if ( this . promise ) {
93212 this . promise . resolve ( {
94- of : ReactDOM . findDOMNode ( this ) ,
213+ of : findDOMNode ( this ) ,
95214 ...getPlacement ( placement , pOffset )
96215 } ) ;
97216 }
98217 }
99218
100- setPopupVisible ( popupVisible ) {
219+ _setPopupVisible ( popupVisible ) {
101220
102221 if ( ! ( 'popupVisible' in this . props ) ) {
103222 this . setState ( {
@@ -108,6 +227,10 @@ export default class Trigger extends React.Component {
108227 this . props . onPopupVisibleChange ( popupVisible ) ;
109228 }
110229
230+ close ( ) {
231+ this . delaySetPopupVisible ( false ) ;
232+ }
233+
111234 clearDelayTimer ( ) {
112235 if ( this . delayTimer ) {
113236 clearTimeout ( this . delayTimer ) ;
@@ -136,11 +259,11 @@ export default class Trigger extends React.Component {
136259
137260 if ( delay ) {
138261 this . delayTimer = setTimeout ( ( ) => {
139- this . setPopupVisible ( visible ) ;
262+ this . _setPopupVisible ( visible ) ;
140263 this . delayTimer = null ;
141264 } , delay ) ;
142265 } else {
143- this . setPopupVisible ( visible ) ;
266+ this . _setPopupVisible ( visible ) ;
144267 }
145268 }
146269
@@ -212,13 +335,94 @@ export default class Trigger extends React.Component {
212335 }
213336 }
214337
215- render ( ) {
216- const { popupVisible } = this . state ;
338+ onContextMenuClose = ( ) => {
339+ if ( this . isContextMenuToShow ( ) ) {
340+ this . close ( ) ;
341+ }
342+ }
343+
344+ savePopup = ( popup ) => {
345+ this . _popup = popup ;
346+ }
347+
348+ getPopup ( ) {
349+ return this . _popup ;
350+ }
351+
352+ getPopupDomNode ( ) {
353+ if ( this . _popup ) {
354+ return this . _popup ? this . _popup . getPopupDOM ( ) : null ;
355+ }
356+ return null ;
357+ }
358+
359+ getPopupMaskDomNode ( ) {
360+ if ( this . _popup ) {
361+ return this . _popup ? this . _popup . getPopupMaskDOM ( ) : null ;
362+ }
363+ return null ;
364+ }
365+
366+ getPopupComponent ( ) {
217367 const {
218- children,
219368 popup,
369+ prefixCls,
370+ popupClassName,
371+ popupMaskClassName,
372+ popupProps,
373+ mask,
374+ popupStyle,
375+ popupMaskStyle,
376+ popupRootComponent : PopupRootComponent ,
377+ destroyPopupOnHide,
378+ zIndex,
379+ popupTransitionClassNames,
380+ popupMaskTransitionClassNames,
220381 } = this . props ;
221- const child = React . Children . only ( children ) ;
382+ const { popupVisible } = this . state ;
383+
384+ this . promise = Deferred ( ) ;
385+
386+ const maskProps = popupProps . maskProps || { } ;
387+
388+ const newPopupStyle = { ...popupStyle } ;
389+ const newPopupMaskStyle = { ...popupMaskStyle } ;
390+
391+ if ( zIndex != null ) {
392+ newPopupStyle . zIndex = zIndex ;
393+ newPopupMaskStyle . zIndex = zIndex ;
394+ }
395+
396+ return (
397+ < Popup
398+ prefixCls = { prefixCls }
399+ placement = { this . promise }
400+ unmountOnExit = { destroyPopupOnHide }
401+ style = { newPopupStyle }
402+ className = { popupClassName }
403+ { ...popupProps }
404+ rootComponent = { PopupRootComponent }
405+ mask = { mask }
406+ visible = { popupVisible }
407+ ref = { this . savePopup }
408+ maskProps = { {
409+ style : newPopupMaskStyle ,
410+ className : popupMaskClassName ,
411+ ...maskProps ,
412+ } }
413+ >
414+ {
415+ typeof popup === 'function' ? popup ( ) : popup
416+ }
417+ </ Popup >
418+ ) ;
419+ }
420+
421+ render ( ) {
422+ const { getPopupContainer } = this . props ;
423+ const { popupVisible } = this . state ;
424+
425+ const child = React . Children . only ( this . props . children ) ;
222426
223427 const newChildProps = { } ;
224428
@@ -291,23 +495,23 @@ export default class Trigger extends React.Component {
291495 } ;
292496 }
293497
294- this . promise = Deferred ( ) ;
295-
296- const popupChildren = typeof popup === 'function' ? popup ( ) : popup ;
297-
298498 const trigger = React . cloneElement ( child , newChildProps ) ;
499+ let portal ;
500+
501+ if ( popupVisible || this . _popup ) {
502+ portal = (
503+ < Portal
504+ getContainer = { getPopupContainer }
505+ >
506+ { this . getPopupComponent ( ) }
507+ </ Portal >
508+ ) ;
509+ }
299510
300511 return (
301512 < >
302513 { trigger }
303- < Popup
304- placement = { this . promise }
305- visible = { ! ! popupVisible }
306- >
307- {
308- popupChildren
309- }
310- </ Popup >
514+ { portal }
311515 </ >
312516 ) ;
313517 }
0 commit comments