1212 BackAndroid,
1313 BackHandler,
1414 Platform,
15- Modal
15+ Modal,
16+ Keyboard
1617} = require ( 'react-native' ) ;
1718
1819var BackButton = BackHandler || BackAndroid ;
@@ -60,6 +61,7 @@ var ModalBox = React.createClass({
6061 backButtonClose : React . PropTypes . bool ,
6162 easing : React . PropTypes . func ,
6263 coverScreen : React . PropTypes . bool ,
64+ keyboardTopOffset : React . PropTypes . number ,
6365
6466 onClosed : React . PropTypes . func ,
6567 onOpened : React . PropTypes . func ,
@@ -81,6 +83,7 @@ var ModalBox = React.createClass({
8183 backButtonClose : false ,
8284 easing : Easing . elastic ( 0.8 ) ,
8385 coverScreen : false ,
86+ keyboardTopOffset : Platform . OS == 'ios' ? 22 : 0
8487 } ;
8588 } ,
8689
@@ -97,19 +100,30 @@ var ModalBox = React.createClass({
97100 width : screen . width ,
98101 containerHeight : screen . height ,
99102 containerWidth : screen . width ,
100- isInitialized : false
103+ isInitialized : false ,
104+ keyboardOffset : 0
101105 } ;
102106 } ,
103107
104108 onBackPress ( ) {
105- this . close ( )
106- return true
109+ this . close ( )
110+ return true
107111 } ,
108112
109113 componentWillMount : function ( ) {
110114 this . createPanResponder ( ) ;
111115 this . handleOpenning ( this . props ) ;
116+ // Needed for IOS because the keyboard covers the screen
117+ if ( Platform . OS === 'ios' ) {
118+ this . subscriptions = [
119+ Keyboard . addListener ( 'keyboardWillChangeFrame' , this . onKeyboardChange ) ,
120+ Keyboard . addListener ( 'keyboardDidHide' , this . onKeyboardHide )
121+ ] ;
122+ }
123+ } ,
112124
125+ componentWillUnmount : function ( ) {
126+ if ( this . subscriptions ) this . subscriptions . forEach ( ( sub ) => sub . remove ( ) ) ;
113127 } ,
114128
115129 componentWillReceiveProps : function ( props ) {
@@ -128,6 +142,26 @@ var ModalBox = React.createClass({
128142
129143 /****************** ANIMATIONS **********************/
130144
145+ /*
146+ * The keyboard is hidden (IOS only)
147+ */
148+ onKeyboardHide : function ( evt ) {
149+ this . state . keyboardOffset = 0 ;
150+ } ,
151+
152+ /*
153+ * The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only)
154+ */
155+ onKeyboardChange : function ( evt ) {
156+ if ( ! evt ) return ;
157+ if ( ! this . state . isOpen ) return ;
158+ var keyboardFrame = evt . endCoordinates ;
159+ var keyboardHeight = this . state . containerHeight - keyboardFrame . screenY ;
160+
161+ this . state . keyboardOffset = keyboardHeight ;
162+ this . animateOpen ( ) ;
163+ } ,
164+
131165 /*
132166 * Open animation for the backdrop, will fade in
133167 */
@@ -196,8 +230,10 @@ var ModalBox = React.createClass({
196230
197231 requestAnimationFrame ( ( ) => {
198232 // Detecting modal position
199- this . state . positionDest = this . calculateModalPosition ( this . state . containerHeight , this . state . containerWidth ) ;
200-
233+ this . state . positionDest = this . calculateModalPosition ( this . state . containerHeight - this . state . keyboardOffset , this . state . containerWidth ) ;
234+ if ( this . state . keyboardOffset && ( this . state . positionDest < this . props . keyboardTopOffset ) ) {
235+ this . state . positionDest = this . props . keyboardTopOffset ;
236+ }
201237 this . state . animOpen = Animated . timing (
202238 this . state . position ,
203239 {
@@ -207,9 +243,9 @@ var ModalBox = React.createClass({
207243 }
208244 ) ;
209245 this . state . animOpen . start ( ( ) => {
246+ if ( ! this . state . isOpen && this . props . onOpened ) this . props . onOpened ( ) ;
210247 this . state . isAnimateOpen = false ;
211248 this . state . isOpen = true ;
212- if ( this . props . onOpened ) this . props . onOpened ( ) ;
213249 } ) ;
214250 } )
215251
@@ -244,6 +280,7 @@ var ModalBox = React.createClass({
244280 }
245281 ) ;
246282 this . state . animClose . start ( ( ) => {
283+ Keyboard . dismiss ( ) ;
247284 this . state . isAnimateClose = false ;
248285 this . state . isOpen = false ;
249286 this . setState ( { } ) ;
@@ -344,43 +381,28 @@ var ModalBox = React.createClass({
344381 return ;
345382 }
346383
347- var modalPosition = this . calculateModalPosition ( height , width ) ;
348- var coords = { } ;
349-
350- // Fixing the position if the modal was already open or an animation was in progress
351- if ( this . state . isInitialized && ( this . state . isOpen || this . state . isAnimateOpen || this . state . isAnimateClose ) ) {
352- var position = this . state . isOpen ? modalPosition : this . state . containerHeight ;
353-
354- // Checking if a animation was in progress
355- if ( this . state . isAnimateOpen ) {
356- position = modalPosition ;
357- this . stopAnimateOpen ( ) ;
358- } else if ( this . state . isAnimateClose ) {
359- position = this . state . containerHeight ;
360- this . stopAnimateClose ( ) ;
361- }
362- this . state . position . setValue ( position ) ;
363- coords = { positionDest : position } ;
384+ if ( this . state . isOpen || this . state . isAnimateOpen ) {
385+ this . animateOpen ( ) ;
364386 }
365387
388+ if ( this . props . onLayout ) this . props . onLayout ( evt ) ;
366389 this . setState ( {
367390 isInitialized : true ,
368391 containerHeight : height ,
369- containerWidth : width ,
370- ...coords
392+ containerWidth : width
371393 } ) ;
372394 } ,
373395
374396 /*
375397 * Render the backdrop element
376398 */
377- renderBackdrop : function ( size ) {
378- var backdrop = [ ] ;
399+ renderBackdrop : function ( ) {
400+ var backdrop = null ;
379401
380402 if ( this . props . backdrop ) {
381403 backdrop = (
382404 < TouchableWithoutFeedback onPress = { this . props . backdropPressToClose ? this . close : null } >
383- < Animated . View style = { [ styles . absolute , size , { opacity : this . state . backdropOpacity } ] } >
405+ < Animated . View style = { [ styles . absolute , { opacity : this . state . backdropOpacity } ] } >
384406 < View style = { [ styles . absolute , { backgroundColor :this . props . backdropColor , opacity : this . props . backdropOpacity } ] } />
385407 { this . props . backdropContent || [ ] }
386408 </ Animated . View >
@@ -391,45 +413,43 @@ var ModalBox = React.createClass({
391413 return backdrop ;
392414 } ,
393415
416+ renderContent ( ) {
417+ var size = { height : this . state . containerHeight , width : this . state . containerWidth } ;
418+ var offsetX = ( this . state . containerWidth - this . state . width ) / 2 ;
419+
420+ return (
421+ < Animated . View
422+ onLayout = { this . onViewLayout }
423+ style = { [ styles . wrapper , size , this . props . style , { transform : [ { translateY : this . state . position } , { translateX : offsetX } ] } ] }
424+ { ...this . state . pan . panHandlers } >
425+ { this . props . children }
426+ </ Animated . View >
427+ )
428+ } ,
429+
394430 /*
395431 * Render the component
396432 */
397433 render : function ( ) {
398- var visible = this . state . isOpen || this . state . isAnimateOpen || this . state . isAnimateClose ;
399- var size = { height : this . state . containerHeight , width : this . state . containerWidth } ;
400- var offsetX = ( this . state . containerWidth - this . state . width ) / 2 ;
401- var backdrop = this . renderBackdrop ( size ) ;
402- var coverScreen = this . props . coverScreen ;
403- var modalContainer = [ ] ;
404-
405- modalContainer = (
406- < View style = { { flex : 1 } } pointerEvents = { 'box-none' } onLayout = { this . onContainerLayout } >
407- { backdrop }
408- < Animated . View
409- onLayout = { this . onViewLayout }
410- style = { [ styles . wrapper , size , this . props . style , { transform : [ { translateY : this . state . position } , { translateX : offsetX } ] } ] }
411- { ...this . state . pan . panHandlers } >
412- { this . props . children }
413- </ Animated . View >
414- </ View >
415- ) ;
416-
434+ var visible = this . state . isOpen || this . state . isAnimateOpen || this . state . isAnimateClose ;
435+
417436 if ( ! visible ) return < View />
418-
419- if ( coverScreen ) {
420- return (
421- < Modal onRequestClose = { ( ) => this . close ( ) } supportedOrientations = { [ 'landscape' , 'portrait' ] } transparent visible = { visible } >
422- < View style = { [ styles . transparent , styles . absolute ] } >
423- { modalContainer }
424- </ View >
425- </ Modal >
426- ) ;
427- }
437+
438+ var content = (
439+ < View style = { [ styles . transparent , styles . absolute ] } pointerEvents = { 'box-none' } >
440+ < View style = { { flex : 1 } } onLayout = { this . onContainerLayout } >
441+ { visible && this . renderBackdrop ( ) }
442+ { visible && this . renderContent ( ) }
443+ </ View >
444+ </ View >
445+ )
446+
447+ if ( ! this . props . coverScreen ) return content ;
428448
429449 return (
430- < View style = { [ styles . transparent , styles . absolute ] } >
431- { modalContainer }
432- </ View >
450+ < Modal onRequestClose = { ( ) => this . close ( ) } supportedOrientations = { [ 'landscape' , 'portrait' ] } transparent visible = { visible } >
451+ { content }
452+ </ Modal >
433453 ) ;
434454 } ,
435455
@@ -459,4 +479,4 @@ var ModalBox = React.createClass({
459479
460480} ) ;
461481
462- module . exports = ModalBox ;
482+ module . exports = ModalBox ;
0 commit comments