react-native-smart-scroll-view
Advanced tools
Comparing version 1.3.5 to 1.3.6
{ | ||
"name": "react-native-smart-scroll-view", | ||
"version": "1.3.5", | ||
"version": "1.3.6", | ||
"description": "Handles keyboard events and auto adjusts content to be visible above keyboard on focus. Further scrolling features available.", | ||
@@ -5,0 +5,0 @@ "main": "SmartScrollView.js", |
import React, { | ||
Component, | ||
PropTypes, | ||
} from 'react'; | ||
import ReactNative, { | ||
View, | ||
StyleSheet, | ||
ScrollView, | ||
DeviceEventEmitter, | ||
Keyboard, | ||
Dimensions, | ||
LayoutAnimation, | ||
PropTypes, | ||
TouchableOpacity | ||
Platform | ||
} from 'react-native'; | ||
import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard'; | ||
import Picker from './Picker.js'; | ||
export { default as PickerInput } from './PickerInput.js'; | ||
const animations = { | ||
const { height: screenHeight } = Dimensions.get('window'); | ||
const animations = { | ||
layout: { | ||
@@ -34,17 +33,2 @@ easeInEaseOut: { | ||
const { height: screenHeight, width: screenWidth } = Dimensions.get('window'); | ||
let keyboardHeight; | ||
switch (screenHeight) { | ||
case 736: | ||
keyboardHeight = 226; | ||
break; | ||
case 1024: | ||
keyboardHeight = 264; | ||
break; | ||
default: | ||
keyboardHeight = 226; | ||
}; | ||
class SmartScrollView extends Component { | ||
@@ -54,26 +38,21 @@ | ||
super(); | ||
this.state = { scrollPosition: 0, pickerOpen: false }; | ||
[ | ||
'_focusNode', | ||
'_keyboardWillHide', | ||
'_keyboardWillShow', | ||
'_updateScrollPosition', | ||
'_dismissPicker', | ||
'_openPicker', | ||
'_dismissKeyboard', | ||
'_generateSmartContent', | ||
'_renderPicker', | ||
'_findHeights' | ||
].forEach((method) => this[method] = this[method].bind(this)) | ||
this.state = { | ||
scrollPosition : 0, | ||
} | ||
this._refCreator = this._refCreator.bind(this); | ||
this._focusNode = this._focusNode.bind(this); | ||
this._keyboardWillHide = this._keyboardWillHide.bind(this); | ||
this._keyboardWillShow = this._keyboardWillShow.bind(this); | ||
this._updateScrollPosition = this._updateScrollPosition.bind(this); | ||
} | ||
componentDidMount() { | ||
setTimeout(this._findHeights, 0); | ||
this._listeners = [ | ||
DeviceEventEmitter.addListener('keyboardWillShow', this._keyboardWillShow), | ||
DeviceEventEmitter.addListener('keyboardWillHide', this._keyboardWillHide), | ||
]; | ||
if (this.props.forceFocusField !== this.state.focusedField){ | ||
this._focusField('input_' + this.props.forceFocusField) | ||
} | ||
this._listeners = [ | ||
Keyboard.addListener(Platform.OS == 'IOS' ? 'keyboardWillShow' : 'keyboardDidShow', this._keyboardWillShow), | ||
Keyboard.addListener(Platform.OS == 'IOS' ? 'keyboardWillHide' : 'keyboardDidHide', this._keyboardWillHide), | ||
]; | ||
} | ||
@@ -88,7 +67,5 @@ | ||
componentWillReceiveProps(props) { | ||
setTimeout(this._findHeights,0); | ||
if ( | ||
this.props.forceFocusField !== props.forceFocusField && | ||
this.props.forceFocusField !== this.state.focusedField | ||
) { this._focusField('input_' + props.forceFocusField, true) } | ||
if (props.forceFocusField !== undefined && props.forceFocusField !== this.state.focusedField){ | ||
this._focusField('input_' + props.forceFocusField) | ||
} | ||
} | ||
@@ -100,117 +77,73 @@ | ||
_findHeights () { | ||
this._container.measureLayout(1, (x, y, w, height) => { | ||
this._findScrollWindowHeight = keyboardHeight => { | ||
const spaceBelow = screenHeight - y - height; | ||
// TODO: fix adjusting of height when there is space below the smart scroll view | ||
return height - Math.max(keyboardHeight, 0); | ||
} | ||
this.setState({ SVDisplacementFromTop: y, originalHeight: height }) | ||
this._smartScroll.refs.InnerScrollView.measure((x,y,w,height) => { | ||
this.setState({contentHeight:height}); | ||
}); | ||
}) | ||
_findScrollWindowHeight(keyboardHeight){ | ||
const {x, y, width, height} = this._layout | ||
const spaceBelow = screenHeight - y - height; | ||
return height - Math.max(keyboardHeight - spaceBelow, 0); | ||
} | ||
_focusField (ref, forced) { | ||
const node = this[ref]; | ||
const { type } = node.props.smartScrollOptions; | ||
const strippedBackRef = ref.slice('input_'.length); | ||
this.setState({focusedField: strippedBackRef}, () => { | ||
switch (type) { | ||
case 'text': | ||
if (forced) { return this[ref].focus() } | ||
else { this._dismissPicker() } | ||
break; | ||
case 'picker': | ||
dismissKeyboard(); | ||
this._openPicker(); | ||
break; | ||
case 'custom': | ||
dismissKeyboard(); | ||
this._dismissPicker(); | ||
this.setState({scrollWindowHeight:this.state.originalHeight}); | ||
break; | ||
this.props.onRefFocus(strippedBackRef); | ||
} | ||
setTimeout(this._focusNode, 1) | ||
}) | ||
} | ||
_focusNode () { | ||
const { | ||
state: { scrollPosition, scrollWindowHeight, contentHeight, focusedField: ref }, | ||
props: { scrollPadding }, | ||
_smartScroll | ||
} = this; | ||
const num = React.findNodeHandle(_smartScroll); | ||
setTimeout(() => { | ||
this['input_'+ref].measureLayout(num, (X,Y,W,H) => { | ||
const py = Y - scrollPosition; | ||
if (py + H > scrollWindowHeight) { | ||
_smartScroll.scrollTo((Y + H) - scrollWindowHeight + scrollPadding) | ||
} else if ( py < 0 ) { | ||
_smartScroll.scrollTo(Y - scrollPadding) | ||
} else if (scrollPosition > (contentHeight - scrollWindowHeight)) { | ||
_smartScroll.scrollTo(contentHeight - scrollWindowHeight); | ||
} | ||
}); | ||
}, 0); | ||
} | ||
_keyboardWillShow(e) { | ||
const scrollWindowHeight = this._findScrollWindowHeight(e.endCoordinates.height) | ||
this.setState({ scrollWindowHeight, keyboardUp: true}); | ||
this.setState({ | ||
scrollWindowHeight, | ||
keyBoardUp: true | ||
}) | ||
} | ||
_keyboardWillHide() { | ||
this._dismissKeyboard(); | ||
this.setState({ | ||
keyBoardUp: false | ||
}); | ||
this._smartScroll && this._smartScroll.scrollTo({y: 0}); | ||
} | ||
_dismissKeyboard () { | ||
this.setState({keyboardUp: false}); | ||
_refCreator () { | ||
const refs = arguments; | ||
return component => Object.keys(refs).forEach(i => this[refs[i]] = component); | ||
} | ||
_dismissPicker () { | ||
this.setState({pickerOpen: false}); | ||
} | ||
_focusField (ref) { | ||
const node = this[ref]; | ||
const {type} = node.props.smartScrollOptions; | ||
_openPicker () { | ||
this.setState({ | ||
pickerOpen: true, | ||
scrollWindowHeight: this._findScrollWindowHeight(keyboardHeight), | ||
}); | ||
switch(type) { | ||
case 'text': | ||
this[ref].focus(); | ||
break; | ||
case 'custom': | ||
this._focusNode(ref); | ||
} | ||
} | ||
_renderPicker () { | ||
_focusNode (ref) { | ||
const { | ||
state: {focusedField, SVDisplacementFromTop}, | ||
_dismissPicker | ||
} = this; | ||
const { props: { | ||
smartScrollOptions: { type, pickerType, pickerProps, pickerTitle, onPickerDone }, | ||
onSubmitEditing | ||
}} = this['input_' + focusedField]; | ||
scrollPosition, | ||
scrollWindowHeight, | ||
} = this.state; | ||
const { | ||
scrollPadding, | ||
onRefFocus | ||
} = this.props; | ||
const num = ReactNative.findNodeHandle(this._smartScroll); | ||
const strippedBackRef = ref.slice('input_'.length); | ||
if (type === 'picker') { | ||
return ( | ||
<View style = {[styles.picker, {top: screenHeight - SVDisplacementFromTop - keyboardHeight}]}> | ||
<Picker | ||
pickerType = { pickerType } | ||
pickerProps = { pickerProps } | ||
pickerTitle = { pickerTitle } | ||
onDone = { () => { | ||
_dismissPicker(); | ||
if ( onSubmitEditing !== undefined ) { onSubmitEditing() } | ||
if ( onPickerDone !== undefined ) { onPickerDone() } | ||
}} | ||
/> | ||
</View> | ||
); | ||
} | ||
setTimeout(() => { | ||
onRefFocus(strippedBackRef); | ||
this.setState({focusedField: strippedBackRef}) | ||
this[ref].measureLayout(num, (X,Y,W,H) => { | ||
const py = Y - scrollPosition; | ||
if ( py + H > scrollWindowHeight ){ | ||
const nextScrollPosition = (Y + H) - scrollWindowHeight + scrollPadding; | ||
this._smartScroll.scrollTo({y: nextScrollPosition}); | ||
this.setState({scrollPosition: nextScrollPosition }) | ||
} else if ( py < 0 ) { | ||
const nextScrollPosition = Y - scrollPadding; | ||
this._smartScroll.scrollTo({y: nextScrollPosition}) | ||
this.setState({ scrollPosition: nextScrollPosition}) | ||
} | ||
}); | ||
}, 0); | ||
} | ||
@@ -222,49 +155,51 @@ | ||
_generateSmartContent (scrollChildren) { | ||
render () { | ||
const { | ||
children: scrollChildren, | ||
contentContainerStyle, | ||
scrollContainerStyle, | ||
zoomScale, | ||
showsVerticalScrollIndicator, | ||
contentInset, | ||
onScroll | ||
} = this.props; | ||
let inputIndex = 0; | ||
const smartClone = (element, i) => { | ||
const { smartScrollOptions } = element.props; | ||
const { type, scrollRef, moveToNext, onSubmitEditing, style, onPress } = smartScrollOptions; | ||
let smartProps = { key: i }; | ||
const { smartScrollOptions } = element.props; | ||
let smartProps = { key: i }; | ||
if (type !== undefined) { | ||
const ref = 'input_' + inputIndex; | ||
smartProps.ref = component => [ref, scrollRef && 'input_' + scrollRef] | ||
.forEach(ref => this[ref] = component); | ||
if (smartScrollOptions.type !== undefined) { | ||
const ref = 'input_' + inputIndex; | ||
if (moveToNext === true) { | ||
const nextRef = 'input_' + (inputIndex+1); | ||
const focusNextField = () => this._focusField(nextRef, true) | ||
smartProps.blurOnSubmit = false; | ||
smartProps.onSubmitEditing = onSubmitEditing ? | ||
() => onSubmitEditing(focusNextField) : | ||
focusNextField | ||
} | ||
if (type === 'text') { | ||
smartProps.ref = this._refCreator(ref, smartScrollOptions.scrollRef && 'input_' + smartScrollOptions.scrollRef); | ||
if (smartScrollOptions.type === 'text') { | ||
smartProps.onFocus = () => { | ||
element.props.onFocus && element.props.onFocus(); | ||
this._focusField(ref) | ||
smartProps.onFocus = element.props.onFocus && element.props.onFocus(); | ||
this._focusNode(ref) | ||
}; | ||
} else if (type === 'picker') { | ||
smartProps.onPress = () => { this._focusField(ref); if (onPress!== undefined) { onPress()} }; | ||
smartProps.smartScrollOptions = smartScrollOptions; | ||
smartProps.style = style; | ||
inputIndex += 1; | ||
return React.cloneElement(<TouchableOpacity/>, smartProps, element); | ||
} else if (type === 'custom') { | ||
smartProps.onPress = () => this._focusField(ref); | ||
smartProps.smartScrollOptions = smartScrollOptions; | ||
smartProps.activeOpacity = 0.95; | ||
inputIndex += 1; | ||
smartProps.style = style; | ||
if (smartScrollOptions.moveToNext === true) { | ||
const nextRef = 'input_' + (inputIndex+1); | ||
const focusNextField = () => this._focusField(nextRef) | ||
return React.cloneElement(<TouchableOpacity/>, smartProps, element.props.children) | ||
if(typeof(element.props.returnKeyType) === 'undefined'){ | ||
smartProps.returnKeyType = 'next' | ||
} | ||
smartProps.blurOnSubmit = false; | ||
smartProps.onSubmitEditing = smartScrollOptions.onSubmitEditing ? | ||
() => smartScrollOptions.onSubmitEditing(focusNextField) : | ||
focusNextField | ||
} | ||
} | ||
inputIndex += 1 | ||
} | ||
return React.cloneElement(element, smartProps); | ||
return React.cloneElement(element, smartProps) | ||
} | ||
const recursivelyCheckAndAdd = (children, i) => { | ||
function recursivelyCheckAndAdd(children, i) { | ||
return React.Children.map(children, (child, j) => { | ||
@@ -280,3 +215,3 @@ if (child && child.props !== undefined) { | ||
} else { | ||
return child; | ||
return child | ||
} | ||
@@ -286,27 +221,4 @@ }) | ||
return recursivelyCheckAndAdd(scrollChildren, '0'); | ||
} | ||
const content = recursivelyCheckAndAdd(scrollChildren, '0'); | ||
render () { | ||
const { | ||
props: { | ||
children: scrollChildren, | ||
contentContainerStyle, | ||
scrollContainerStyle, | ||
zoomScale, | ||
showsVerticalScrollIndicator, | ||
contentInset, | ||
onScroll | ||
}, | ||
state: { | ||
pickerOpen, | ||
scrollWindowHeight, | ||
keyboardUp | ||
}, | ||
_generateSmartContent, | ||
_updateScrollPosition, | ||
_renderPicker | ||
} = this; | ||
const content = _generateSmartContent(scrollChildren); | ||
return ( | ||
@@ -316,4 +228,7 @@ <View | ||
style = {scrollContainerStyle} | ||
onLayout={(e) => this._layout = e.nativeEvent.layout} | ||
> | ||
<View style = {(keyboardUp || pickerOpen) ? { height: scrollWindowHeight } : styles.flex1} > | ||
<View | ||
style = {this.state.keyBoardUp ? { height: this.state.scrollWindowHeight } : styles.flex1} | ||
> | ||
<ScrollView | ||
@@ -325,4 +240,4 @@ ref = { component => this._smartScroll=component } | ||
onScroll = { (event) => { | ||
_updateScrollPosition(event); | ||
onScroll(event); | ||
this._updateScrollPosition(event) | ||
onScroll(event) | ||
}} | ||
@@ -339,3 +254,2 @@ scrollEventThrottle = { 16 } | ||
</ScrollView> | ||
{ pickerOpen && _renderPicker() } | ||
</View> | ||
@@ -350,8 +264,2 @@ </View> | ||
flex: 1 | ||
}, | ||
picker: { | ||
height: keyboardHeight, | ||
position: 'absolute', | ||
width: screenWidth, | ||
alignItems: 'stretch' | ||
} | ||
@@ -361,5 +269,5 @@ }); | ||
SmartScrollView.propTypes = { | ||
forceFocusField: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||
scrollContainerStyle: PropTypes.number, | ||
contentContainerStyle: PropTypes.number, | ||
forceFocusField: PropTypes.oneOf(PropTypes.number, PropTypes.string), | ||
scrollContainerStyle: View.propTypes.style, | ||
contentContainerStyle: View.propTypes.style, | ||
zoomScale: PropTypes.number, | ||
@@ -383,1 +291,18 @@ showsVerticalScrollIndicator: PropTypes.bool, | ||
export default SmartScrollView; | ||
// import dismissKeyboard from 'dismissKeyboard'; | ||
// this._scrollTap = this._scrollTap.bind(this); | ||
// lastTap: 0 | ||
// _scrollTap () { | ||
// const {lastTap} = this.state; | ||
// const currentTap = new Date().getTime(); | ||
// console.log("tap") | ||
// | ||
// if (currentTap - lastTap < 500) { | ||
// dismissKeyboard() | ||
// } | ||
// | ||
// this.setState({ | ||
// lastTap: currentTap | ||
// }) | ||
// } |
2688754
43
793