@@ -87,11 +87,63 @@ export type ScrollIntoViewOptions = ?{
87
87
) => ScrollPosition
88
88
}
89
89
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
+
90
107
function getDisplayName ( WrappedComponent : React$Component ) {
91
108
return WrappedComponent . displayName || WrappedComponent . name || 'Component'
92
109
}
93
110
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
+
95
147
return class extends React . Component <
96
148
KeyboardAwareHOCProps ,
97
149
KeyboardAwareHOCState
@@ -118,21 +170,26 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
118
170
extraHeight : PropTypes . number ,
119
171
extraScrollHeight : PropTypes . number ,
120
172
keyboardOpeningTime : PropTypes . number ,
121
- onScroll : PropTypes . func ,
173
+ onScroll : PropTypes . oneOfType ( [
174
+ PropTypes . func , // Normal listener
175
+ PropTypes . object , // Animated.event listener
176
+ ] ) ,
122
177
update : PropTypes . func ,
123
178
contentContainerStyle : PropTypes . any ,
124
179
enableOnAndroid : PropTypes . bool ,
125
180
innerRef : PropTypes . func ,
126
181
...keyboardEventPropTypes
127
182
}
128
183
184
+ // HOC options are used to init default props, so that these options can be overriden with component props
129
185
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 ,
136
193
}
137
194
138
195
constructor ( props : KeyboardAwareHOCProps ) {
@@ -239,11 +296,11 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
239
296
}
240
297
const responder = this . getScrollResponder ( )
241
298
responder &&
242
- responder . scrollResponderScrollNativeHandleToKeyboard (
243
- reactNode ,
244
- extraHeight ,
245
- true
246
- )
299
+ responder . scrollResponderScrollNativeHandleToKeyboard (
300
+ reactNode ,
301
+ extraHeight ,
302
+ true
303
+ )
247
304
} , keyboardOpeningTime )
248
305
}
249
306
@@ -291,13 +348,13 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
291
348
292
349
// Keyboard actions
293
350
_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 } )
299
351
// Automatically scroll to focused TextInput
300
352
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 } )
301
358
const currentlyFocusedField = TextInput . State . currentlyFocusedField ( )
302
359
const responder = this . getScrollResponder ( )
303
360
if ( ! currentlyFocusedField || ! responder ) {
@@ -343,7 +400,7 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
343
400
) {
344
401
this . scrollForExtraHeightOnAndroid (
345
402
totalExtraHeight -
346
- ( keyboardPosition - textInputBottomPosition )
403
+ ( keyboardPosition - textInputBottomPosition )
347
404
)
348
405
}
349
406
}
@@ -410,11 +467,9 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
410
467
}
411
468
412
469
_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 )
418
473
}
419
474
}
420
475
@@ -437,13 +492,14 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
437
492
if ( Platform . OS === 'android' && enableOnAndroid ) {
438
493
newContentContainerStyle = [ ] . concat ( contentContainerStyle ) . concat ( {
439
494
paddingBottom :
440
- ( ( contentContainerStyle || { } ) . paddingBottom || 0 ) +
441
- this . state . keyboardSpace
495
+ ( ( contentContainerStyle || { } ) . paddingBottom || 0 ) +
496
+ this . state . keyboardSpace
442
497
} )
443
498
}
499
+ const refProps = { [ options . refPropName ] : this . _handleRef }
444
500
return (
445
501
< ScrollableComponent
446
- ref = { this . _handleRef }
502
+ { ... refProps }
447
503
keyboardDismissMode = 'interactive'
448
504
contentInset = { { bottom : this . state . keyboardSpace } }
449
505
automaticallyAdjustContentInsets = { false }
@@ -470,4 +526,16 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) {
470
526
}
471
527
}
472
528
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
+
473
541
export default listenToKeyboardEvents
0 commit comments