123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- import * as React from 'react';
- import * as PropTypes from 'prop-types';
- import {
- View,
- Platform,
- findNodeHandle,
- TouchableOpacity,
- TouchableHighlight,
- TouchableNativeFeedback,
- TouchableWithoutFeedback,
- GestureResponderEvent,
- NativeTouchEvent,
- NativeSyntheticEvent,
- } from 'react-native';
-
- // Polyfill GestureResponderEvent type with additional `force` property (iOS)
- interface NativeTouchEventWithForce extends NativeTouchEvent {
- force: number;
- }
- interface GestureResponderEventWithForce extends NativeSyntheticEvent<NativeTouchEventWithForce> {}
-
- export interface Props {
- children: React.ReactNode;
- touchableComponent?:
- | TouchableHighlight
- | TouchableOpacity
- | TouchableNativeFeedback
- | TouchableWithoutFeedback
- | React.ReactNode;
- onPress?: () => void;
- onPressIn?: (payload: { reactTag: number | null }) => void;
- onPeekIn?: () => void;
- onPeekOut?: () => void;
- }
-
- const PREVIEW_DELAY = 350;
- const PREVIEW_MIN_FORCE = 0.1;
- const PREVIEW_TIMEOUT = 1250;
-
- export class TouchablePreview extends React.PureComponent<Props> {
- static propTypes = {
- children: PropTypes.node,
- touchableComponent: PropTypes.func,
- onPress: PropTypes.func,
- onPressIn: PropTypes.func,
- onPeekIn: PropTypes.func,
- onPeekOut: PropTypes.func,
- };
-
- static defaultProps = {
- touchableComponent: TouchableWithoutFeedback,
- };
-
- static peeking = false;
-
- private timeout: number | undefined;
- private touchStartedAt: number = 0;
- private onRef = React.createRef<any>();
- onPress = () => {
- const { onPress } = this.props;
-
- if (typeof onPress !== 'function' || TouchablePreview.peeking) {
- return;
- }
-
- return onPress();
- }
-
- onPressIn = () => {
- if (Platform.OS === 'ios') {
- const { onPressIn } = this.props;
-
- if (!onPressIn) {
- return;
- }
-
- const reactTag = findNodeHandle(this.onRef.current);
-
- return onPressIn({ reactTag });
- }
-
- // Other platforms don't support 3D Touch Preview API
- return null;
- }
-
- onTouchStart = (event: GestureResponderEvent) => {
- // Store a timstamp of the initial touch start
- this.touchStartedAt = event.nativeEvent.timestamp;
- }
-
- onTouchMove = (event: GestureResponderEventWithForce) => {
- clearTimeout(this.timeout);
- const { force, timestamp } = event.nativeEvent;
- const diff = timestamp - this.touchStartedAt;
-
- if (force > PREVIEW_MIN_FORCE && diff > PREVIEW_DELAY) {
- TouchablePreview.peeking = true;
-
- if (typeof this.props.onPeekIn === 'function') {
- this.props.onPeekIn();
- }
- }
-
- this.timeout = setTimeout(this.onTouchEnd, PREVIEW_TIMEOUT);
- }
-
- onTouchEnd = () => {
- clearTimeout(this.timeout);
- TouchablePreview.peeking = false;
-
- if (typeof this.props.onPeekOut === 'function') {
- this.props.onPeekOut();
- }
- }
-
- render() {
- const { children, touchableComponent, onPress, onPressIn, ...props } = this.props;
-
- // Default to TouchableWithoutFeedback for iOS if set to TouchableNativeFeedback
- const Touchable = (Platform.OS === 'ios' && touchableComponent instanceof TouchableNativeFeedback) ?
- TouchableWithoutFeedback :
- touchableComponent as typeof React.Component;
-
- // Wrap component with Touchable for handling platform touches
- // and a single react View for detecting force and timing.
- return (
- <Touchable ref={this.onRef} onPress={this.onPress} onPressIn={this.onPressIn} {...props}>
- <View
- onTouchStart={this.onTouchStart}
- onTouchMove={this.onTouchMove as (event: GestureResponderEvent) => void}
- onTouchEnd={this.onTouchEnd}
- >
- {children}
- </View>
- </Touchable>
- );
- }
- }
|