Skip to content

Commit

Permalink
Keyboard support enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
maxs15 committed Jun 28, 2017
1 parent 096da14 commit 000446c
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 63 deletions.
10 changes: 9 additions & 1 deletion Example/index.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
StyleSheet,
ScrollView,
View,
Dimensions
Dimensions,
TextInput
} from 'react-native';

var screen = Dimensions.get('window');
Expand Down Expand Up @@ -59,6 +60,7 @@ class Example extends React.Component {
<Button onPress={() => this.refs.modal4.open()} style={styles.btn}>Position bottom + backdrop + slider</Button>
<Button onPress={() => this.setState({isOpen: true})} style={styles.btn}>Backdrop + backdropContent</Button>
<Button onPress={() => this.refs.modal6.open()} style={styles.btn}>Position bottom + ScrollView</Button>
<Button onPress={() => this.refs.modal7.open()} style={styles.btn}>Modal with keyboard support</Button>

<Modal
style={[styles.modal, styles.modal1]}
Expand Down Expand Up @@ -96,6 +98,12 @@ class Example extends React.Component {
</View>
</ScrollView>
</Modal>

<Modal ref={"modal7"} style={[styles.modal, styles.modal4]} position={"center"}>
<View>
<TextInput style={{height: 50, width: 200, backgroundColor: '#DDDDDD'}}/>
</View>
</Modal>
</View>
);
}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Check [index.js](https://github.com/maxs15/react-native-modalbox/blob/master/Exa
| backButtonClose | false | `bool` | (Android only) Close modal when receiving back button event
| startOpen | false | `bool` | Allow modal to appear open without animation upon first mount
| coverScreen | false | `bool` | Will use RN `Modal` component to cover the entire screen wherever the modal is mounted in the component hierarchy
| keyboardTopOffset | ios:22, android:0 | `number` | This property prevent the modal to cover the ios status bar when the modal is scrolling up because the keyboard is opening

## Events

Expand Down
144 changes: 82 additions & 62 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ var {
BackAndroid,
BackHandler,
Platform,
Modal
Modal,
Keyboard
} = require('react-native');

var BackButton = BackHandler || BackAndroid;
Expand Down Expand Up @@ -60,6 +61,7 @@ var ModalBox = React.createClass({
backButtonClose: React.PropTypes.bool,
easing: React.PropTypes.func,
coverScreen: React.PropTypes.bool,
keyboardTopOffset: React.PropTypes.number,

onClosed: React.PropTypes.func,
onOpened: React.PropTypes.func,
Expand All @@ -81,6 +83,7 @@ var ModalBox = React.createClass({
backButtonClose: false,
easing: Easing.elastic(0.8),
coverScreen: false,
keyboardTopOffset: Platform.OS == 'ios' ? 22 : 0
};
},

Expand All @@ -97,19 +100,30 @@ var ModalBox = React.createClass({
width: screen.width,
containerHeight: screen.height,
containerWidth: screen.width,
isInitialized: false
isInitialized: false,
keyboardOffset: 0
};
},

onBackPress () {
this.close()
return true
this.close()
return true
},

componentWillMount: function() {
this.createPanResponder();
this.handleOpenning(this.props);
// Needed for IOS because the keyboard covers the screen
if (Platform.OS === 'ios') {
this.subscriptions = [
Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange),
Keyboard.addListener('keyboardDidHide', this.onKeyboardHide)
];
}
},

componentWillUnmount: function() {
if (this.subscriptions) this.subscriptions.forEach((sub) => sub.remove());
},

componentWillReceiveProps: function(props) {
Expand All @@ -128,6 +142,26 @@ var ModalBox = React.createClass({

/****************** ANIMATIONS **********************/

/*
* The keyboard is hidden (IOS only)
*/
onKeyboardHide: function(evt) {
this.state.keyboardOffset = 0;
},

/*
* The keyboard frame changed, used to detect when the keyboard open, faster than keyboardDidShow (IOS only)
*/
onKeyboardChange: function(evt) {
if (!evt) return;
if (!this.state.isOpen) return;
var keyboardFrame = evt.endCoordinates;
var keyboardHeight = this.state.containerHeight - keyboardFrame.screenY;

this.state.keyboardOffset = keyboardHeight;
this.animateOpen();
},

/*
* Open animation for the backdrop, will fade in
*/
Expand Down Expand Up @@ -196,8 +230,10 @@ var ModalBox = React.createClass({

requestAnimationFrame(() => {
// Detecting modal position
this.state.positionDest = this.calculateModalPosition(this.state.containerHeight, this.state.containerWidth);

this.state.positionDest = this.calculateModalPosition(this.state.containerHeight - this.state.keyboardOffset, this.state.containerWidth);
if (this.state.keyboardOffset && (this.state.positionDest < this.props.keyboardTopOffset)) {
this.state.positionDest = this.props.keyboardTopOffset;
}
this.state.animOpen = Animated.timing(
this.state.position,
{
Expand All @@ -207,9 +243,9 @@ var ModalBox = React.createClass({
}
);
this.state.animOpen.start(() => {
if (!this.state.isOpen && this.props.onOpened) this.props.onOpened();
this.state.isAnimateOpen = false;
this.state.isOpen = true;
if (this.props.onOpened) this.props.onOpened();
});
})

Expand Down Expand Up @@ -244,6 +280,7 @@ var ModalBox = React.createClass({
}
);
this.state.animClose.start(() => {
Keyboard.dismiss();
this.state.isAnimateClose = false;
this.state.isOpen = false;
this.setState({});
Expand Down Expand Up @@ -344,43 +381,28 @@ var ModalBox = React.createClass({
return;
}

var modalPosition = this.calculateModalPosition(height, width);
var coords = {};

// Fixing the position if the modal was already open or an animation was in progress
if (this.state.isInitialized && (this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose)) {
var position = this.state.isOpen ? modalPosition : this.state.containerHeight;

// Checking if a animation was in progress
if (this.state.isAnimateOpen) {
position = modalPosition;
this.stopAnimateOpen();
} else if (this.state.isAnimateClose) {
position = this.state.containerHeight;
this.stopAnimateClose();
}
this.state.position.setValue(position);
coords = {positionDest: position};
if (this.state.isOpen || this.state.isAnimateOpen) {
this.animateOpen();
}

if (this.props.onLayout) this.props.onLayout(evt);
this.setState({
isInitialized: true,
containerHeight: height,
containerWidth: width,
...coords
containerWidth: width
});
},

/*
* Render the backdrop element
*/
renderBackdrop: function(size) {
var backdrop = [];
renderBackdrop: function() {
var backdrop = null;

if (this.props.backdrop) {
backdrop = (
<TouchableWithoutFeedback onPress={this.props.backdropPressToClose ? this.close : null}>
<Animated.View style={[styles.absolute, size, {opacity: this.state.backdropOpacity}]}>
<Animated.View style={[styles.absolute, {opacity: this.state.backdropOpacity}]}>
<View style={[styles.absolute, {backgroundColor:this.props.backdropColor, opacity: this.props.backdropOpacity}]}/>
{this.props.backdropContent || []}
</Animated.View>
Expand All @@ -391,45 +413,43 @@ var ModalBox = React.createClass({
return backdrop;
},

renderContent() {
var size = {height: this.state.containerHeight, width: this.state.containerWidth};
var offsetX = (this.state.containerWidth - this.state.width) / 2;

return (
<Animated.View
onLayout={this.onViewLayout}
style={[styles.wrapper, size, this.props.style, {transform: [{translateY: this.state.position}, {translateX: offsetX}]} ]}
{...this.state.pan.panHandlers}>
{this.props.children}
</Animated.View>
)
},

/*
* Render the component
*/
render: function() {
var visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;
var size = {height: this.state.containerHeight, width: this.state.containerWidth};
var offsetX = (this.state.containerWidth - this.state.width) / 2;
var backdrop = this.renderBackdrop(size);
var coverScreen = this.props.coverScreen;
var modalContainer = [];

modalContainer = (
<View style={{ flex: 1 }} pointerEvents={'box-none'} onLayout={this.onContainerLayout}>
{backdrop}
<Animated.View
onLayout={this.onViewLayout}
style={[styles.wrapper, size, this.props.style, {transform: [{translateY: this.state.position}, {translateX: offsetX}]} ]}
{...this.state.pan.panHandlers}>
{this.props.children}
</Animated.View>
</View>
);

var visible = this.state.isOpen || this.state.isAnimateOpen || this.state.isAnimateClose;

if (!visible) return <View/>

if (coverScreen) {
return (
<Modal onRequestClose={() => this.close()} supportedOrientations={['landscape', 'portrait']} transparent visible={visible}>
<View style={[styles.transparent, styles.absolute]}>
{modalContainer}
</View>
</Modal>
);
}

var content = (
<View style={[styles.transparent, styles.absolute]} pointerEvents={'box-none'}>
<View style={{ flex: 1 }} onLayout={this.onContainerLayout}>
{visible && this.renderBackdrop()}
{visible && this.renderContent()}
</View>
</View>
)

if (!this.props.coverScreen) return content;

return (
<View style={[styles.transparent, styles.absolute]}>
{modalContainer}
</View>
<Modal onRequestClose={() => this.close()} supportedOrientations={['landscape', 'portrait']} transparent visible={visible}>
{content}
</Modal>
);
},

Expand Down Expand Up @@ -459,4 +479,4 @@ var ModalBox = React.createClass({

});

module.exports = ModalBox;
module.exports = ModalBox;

0 comments on commit 000446c

Please sign in to comment.