@@ -87,11 +87,63 @@ export type ScrollIntoViewOptions = ?{
8787 ) => ScrollPosition
8888}
8989
90+
91+
92+
93+
94+ export type KeyboardAwareHOCOptions = ?{
95+ enableOnAndroid : boolean ,
96+ contentContainerStyle : ?Object ,
97+ enableAutomaticScroll : boolean ,
98+ extraHeight : number ,
99+ extraScrollHeight : number ,
100+ enableResetScrollToCoords : boolean ,
101+ keyboardOpeningTime : number ,
102+ viewIsInsideTabBar : boolean ,
103+ refPropName : string ,
104+ extractNativeRef : Function
105+ }
106+
90107function getDisplayName ( WrappedComponent : React$Component ) {
91108 return WrappedComponent . displayName || WrappedComponent . name || 'Component'
92109}
93110
94- function listenToKeyboardEvents ( ScrollableComponent : React$Component ) {
111+ const ScrollIntoViewDefaultOptions : KeyboardAwareHOCOptions = {
112+ enableOnAndroid : false ,
113+ contentContainerStyle : undefined ,
114+ enableAutomaticScroll : true ,
115+ extraHeight : _KAM_EXTRA_HEIGHT ,
116+ extraScrollHeight : 0 ,
117+ enableResetScrollToCoords : true ,
118+ keyboardOpeningTime : _KAM_KEYBOARD_OPENING_TIME ,
119+ viewIsInsideTabBar : false ,
120+
121+ // The ref prop name that will be passed to the wrapped component to obtain a ref
122+ // If your ScrollView is already wrapped, maybe the wrapper permit to get a ref
123+ // For example, with glamorous-native ScrollView, you should use "innerRef"
124+ refPropName : "ref" ,
125+ // Sometimes the ref you get is a ref to a wrapped view (ex: Animated.ScrollView)
126+ // We need access to the imperative API of a real native ScrollView so we need extraction logic
127+ extractNativeRef : ( ref : Object ) => {
128+ // getNode() permit to support Animated.ScrollView automatically
129+ // see https://github.com/facebook/react-native/issues/19650
130+ // see https://stackoverflow.com/questions/42051368/scrollto-is-undefined-on-animated-scrollview/48786374
131+ if ( ref . getNode ) {
132+ return ref . getNode ( )
133+ }
134+ else {
135+ return ref
136+ }
137+ } ,
138+ }
139+
140+ function KeyboardAwareHOC ( ScrollableComponent : React$Component , userOptions : KeyboardAwareHOCOptions ) {
141+
142+ const options : KeyboardAwareHOCOptions = {
143+ ...ScrollIntoViewDefaultOptions ,
144+ ...userOptions ,
145+ }
146+
95147 return class extends React . Component <
96148 KeyboardAwareHOCProps ,
97149 KeyboardAwareHOCState
@@ -118,21 +170,26 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
118170 extraHeight : PropTypes . number ,
119171 extraScrollHeight : PropTypes . number ,
120172 keyboardOpeningTime : PropTypes . number ,
121- onScroll : PropTypes . func ,
173+ onScroll : PropTypes . oneOfType ( [
174+ PropTypes . func , // Normal listener
175+ PropTypes . object , // Animated.event listener
176+ ] ) ,
122177 update : PropTypes . func ,
123178 contentContainerStyle : PropTypes . any ,
124179 enableOnAndroid : PropTypes . bool ,
125180 innerRef : PropTypes . func ,
126181 ...keyboardEventPropTypes
127182 }
128183
184+ // HOC options are used to init default props, so that these options can be overriden with component props
129185 static defaultProps = {
130- enableAutomaticScroll : true ,
131- extraHeight : _KAM_EXTRA_HEIGHT ,
132- extraScrollHeight : 0 ,
133- enableResetScrollToCoords : true ,
134- keyboardOpeningTime : _KAM_KEYBOARD_OPENING_TIME ,
135- viewIsInsideTabBar : false
186+ enableAutomaticScroll : options . enableAutomaticScroll ,
187+ extraHeight : options . extraHeight ,
188+ extraScrollHeight : options . extraScrollHeight ,
189+ enableResetScrollToCoords : options . enableResetScrollToCoords ,
190+ keyboardOpeningTime : options . keyboardOpeningTime ,
191+ viewIsInsideTabBar : options . viewIsInsideTabBar ,
192+ enableOnAndroid : options . enableOnAndroid ,
136193 }
137194
138195 constructor ( props : KeyboardAwareHOCProps ) {
@@ -239,11 +296,11 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
239296 }
240297 const responder = this . getScrollResponder ( )
241298 responder &&
242- responder . scrollResponderScrollNativeHandleToKeyboard (
243- reactNode ,
244- extraHeight ,
245- true
246- )
299+ responder . scrollResponderScrollNativeHandleToKeyboard (
300+ reactNode ,
301+ extraHeight ,
302+ true
303+ )
247304 } , keyboardOpeningTime )
248305 }
249306
@@ -291,13 +348,13 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
291348
292349 // Keyboard actions
293350 _updateKeyboardSpace = ( frames : Object ) => {
294- let keyboardSpace : number = frames . endCoordinates . height + this . props . extraScrollHeight
295- if ( this . props . viewIsInsideTabBar ) {
296- keyboardSpace -= _KAM_DEFAULT_TAB_BAR_HEIGHT
297- }
298- this . setState ( { keyboardSpace } )
299351 // Automatically scroll to focused TextInput
300352 if ( this . props . enableAutomaticScroll ) {
353+ let keyboardSpace : number = frames . endCoordinates . height + this . props . extraScrollHeight
354+ if ( this . props . viewIsInsideTabBar ) {
355+ keyboardSpace -= _KAM_DEFAULT_TAB_BAR_HEIGHT
356+ }
357+ this . setState ( { keyboardSpace } )
301358 const currentlyFocusedField = TextInput . State . currentlyFocusedField ( )
302359 const responder = this . getScrollResponder ( )
303360 if ( ! currentlyFocusedField || ! responder ) {
@@ -343,7 +400,7 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
343400 ) {
344401 this . scrollForExtraHeightOnAndroid (
345402 totalExtraHeight -
346- ( keyboardPosition - textInputBottomPosition )
403+ ( keyboardPosition - textInputBottomPosition )
347404 )
348405 }
349406 }
@@ -410,11 +467,9 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
410467 }
411468
412469 _handleRef = ( ref : React . Component < * > ) => {
413- if ( ref ) {
414- this . _rnkasv_keyboardView = ref . getNode ? ref . getNode ( ) : ref
415- if ( this . props . innerRef ) {
416- this . props . innerRef ( this . _rnkasv_keyboardView )
417- }
470+ this. _rnkasv_keyboardView = ref ? options . extractNativeRef ( ref ) : ref
471+ if ( this . props . innerRef ) {
472+ this . props . innerRef ( this . _rnkasv_keyboardView )
418473 }
419474 }
420475
@@ -437,13 +492,14 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
437492 if ( Platform . OS === 'android' && enableOnAndroid ) {
438493 newContentContainerStyle = [ ] . concat ( contentContainerStyle ) . concat ( {
439494 paddingBottom :
440- ( ( contentContainerStyle || { } ) . paddingBottom || 0 ) +
441- this . state . keyboardSpace
495+ ( ( contentContainerStyle || { } ) . paddingBottom || 0 ) +
496+ this . state . keyboardSpace
442497 } )
443498 }
499+ const refProps = { [ options . refPropName ] : this . _handleRef }
444500 return (
445501 < ScrollableComponent
446- ref = { this . _handleRef }
502+ { ... refProps }
447503 keyboardDismissMode = 'interactive'
448504 contentInset = { { bottom : this . state . keyboardSpace } }
449505 automaticallyAdjustContentInsets = { false }
@@ -470,4 +526,16 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
470526 }
471527}
472528
529+ // Allow to pass options, without breaking change, and curried for composition
530+ // listenToKeyboardEvents(ScrollView);
531+ // listenToKeyboardEvents(options)(Comp);
532+ const listenToKeyboardEvents = ( configOrComp : any ) = > {
533+ if ( typeof configOrComp === "object" ) {
534+ return ( Comp : Function ) => KeyboardAwareHOC ( Comp , configOrComp )
535+ }
536+ else {
537+ return KeyboardAwareHOC ( configOrComp )
538+ }
539+ }
540+
473541export default listenToKeyboardEvents
0 commit comments