Skip to content

Commit 12d6bcf

Browse files
committed
HOC options API
1 parent 3f747ea commit 12d6bcf

File tree

1 file changed

+95
-27
lines changed

1 file changed

+95
-27
lines changed

lib/KeyboardAwareHOC.js

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
90107
function 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+
473541
export default listenToKeyboardEvents

0 commit comments

Comments
 (0)