/* @flow */
import * as React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, StyleSheet } from 'react-native';
import { PagerRendererPropType } from './PropTypes';
import type { PagerRendererProps } from './TypeDefinitions';
type ScrollEvent = {
nativeEvent: {
contentOffset: {
x: number,
y: number,
},
contentSize: {
height: number,
width: number,
},
},
};
type State = {|
initialOffset: {| x: number, y: number |},
|};
type Props<T> = PagerRendererProps<T> & {
bounces: boolean,
};
export default class PagerScroll<T: *> extends React.Component<
Props<T>,
State
> {
static propTypes = {
...PagerRendererPropType,
bounces: PropTypes.bool.isRequired,
};
static defaultProps = {
canJumpToTab: () => true,
bounces: false,
};
constructor(props: Props<T>) {
super(props);
const { navigationState, layout } = this.props;
this.state = {
initialOffset: {
x: navigationState.index * layout.width,
y: 0,
},
};
}
componentDidMount() {
this._setInitialPage();
}
componentDidUpdate(prevProps: Props<T>) {
const amount = this.props.navigationState.index * this.props.layout.width;
if (
prevProps.navigationState.routes.length !==
this.props.navigationState.routes.length ||
prevProps.layout.width !== this.props.layout.width
) {
this._scrollTo(amount, false);
} else if (
prevProps.navigationState.index !== this.props.navigationState.index
) {
this._scrollTo(amount);
}
}
_scrollView: ?ScrollView;
_idleCallback: any;
_isIdle: boolean = true;
_isInitial: boolean = true;
_setInitialPage = () => {
if (this.props.layout.width) {
this._isInitial = true;
this._scrollTo(
this.props.navigationState.index * this.props.layout.width,
false
);
}
setTimeout(() => {
this._isInitial = false;
}, 50);
};
_scrollTo = (x: number, animated = true) => {
if (this._isIdle && this._scrollView) {
this._scrollView.scrollTo({
x,
animated: animated && this.props.animationEnabled !== false,
});
}
};
_handleMomentumScrollEnd = (e: ScrollEvent) => {
let nextIndex = Math.round(
e.nativeEvent.contentOffset.x / this.props.layout.width
);
const nextRoute = this.props.navigationState.routes[nextIndex];
if (this.props.canJumpToTab({ route: nextRoute })) {
this.props.jumpTo(nextRoute.key);
this.props.onAnimationEnd && this.props.onAnimationEnd();
} else {
global.requestAnimationFrame(() => {
this._scrollTo(
this.props.navigationState.index * this.props.layout.width
);
});
}
};
_handleScroll = (e: ScrollEvent) => {
if (this._isInitial || e.nativeEvent.contentSize.width === 0) {
return;
}
const { navigationState, layout } = this.props;
const offset = navigationState.index * layout.width;
this.props.offsetX.setValue(-offset);
this.props.panX.setValue(offset - e.nativeEvent.contentOffset.x);
global.cancelAnimationFrame(this._idleCallback);
this._isIdle = false;
this._idleCallback = global.requestAnimationFrame(() => {
this._isIdle = true;
});
};
render() {
const {
children,
layout,
navigationState,
onSwipeStart,
onSwipeEnd,
bounces,
} = this.props;
return (
<ScrollView
horizontal
pagingEnabled
directionalLockEnabled
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
overScrollMode="never"
scrollToOverflowEnabled
scrollEnabled={this.props.swipeEnabled}
automaticallyAdjustContentInsets={false}
bounces={bounces}
alwaysBounceHorizontal={bounces}
scrollsToTop={false}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={1}
onScroll={this._handleScroll}
onScrollBeginDrag={onSwipeStart}
onScrollEndDrag={onSwipeEnd}
onMomentumScrollEnd={this._handleMomentumScrollEnd}
contentOffset={this.state.initialOffset}
style={styles.container}
contentContainerStyle={layout.width ? null : styles.container}
ref={el => (this._scrollView = el)}
>
{React.Children.map(children, (child, i) => {
const route = navigationState.routes[i];
const focused = i === navigationState.index;
return (
<View
key={route.key}
testID={this.props.getTestID({ route })}
accessibilityElementsHidden={!focused}
importantForAccessibility={
focused ? 'auto' : 'no-hide-descendants'
}
style={
layout.width
? { width: layout.width, overflow: 'hidden' }
: focused
? styles.page
: null
}
>
{focused || layout.width ? child : null}
</View>
);
})}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
page: {
flex: 1,
overflow: 'hidden',
},
});