react-native-ui-lib
Version:
<p align="center"> <img src="https://user-images.githubusercontent.com/1780255/105469025-56759000-5ca0-11eb-993d-3568c1fd54f4.png" height="250px" style="display:block"/> </p> <p align="center">UI Toolset & Components Library for React Native</p> <p a
266 lines (242 loc) • 9.07 kB
JavaScript
import _pt from "prop-types";
import _ from 'lodash';
import React, { PureComponent } from 'react';
import { PanResponder } from 'react-native';
import asPanViewConsumer from "./asPanViewConsumer";
import PanningProvider from "./panningProvider";
import View from "../view";
const DEFAULT_DIRECTIONS = [PanningProvider.Directions.UP, PanningProvider.Directions.DOWN, PanningProvider.Directions.LEFT, PanningProvider.Directions.RIGHT];
const DEFAULT_PAN_SENSITIVITY = 5;
const DEFAULT_SWIPE_VELOCITY = 1.8;
/**
* @description: PanListenerView component created to making listening to swipe and drag events easier
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/PanListenerScreen.tsx
* @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/PanListenerView/PanListenerView.gif?raw=true
*/
class PanListenerView extends PureComponent {
static propTypes = {
/**
* This is were you will get notified when a drag occurs
* onDrag = ({directions, deltas}) => {...}
* directions - array of directions
* deltas - array of deltas (same length and order as directions)
* Both arrays will have {x, y} - if no x or y drag has occurred this value will be undefined
*/
onDrag: _pt.func,
/**
* This is were you will get notified when a swipe occurs
* onSwipe = ({directions, velocities}) => {...}
* directions - array of directions
* velocities - array of velocities (same length and order as directions)
* Both arrays will have {x, y} - if no x or y swipe has occurred this value will be undefined
*/
onSwipe: _pt.func,
/**
* This is were you will get notified when the pan starts
*/
onPanStart: _pt.func,
/**
* This is were you will get notified when the pan ends
* The user has released all touches while this view is the responder.
* This typically means a gesture has succeeded
*/
onPanRelease: _pt.func,
/**
* This is were you will get notified when the pan ends
* Another component has become the responder,
* so this gesture should be cancelled
*/
onPanTerminated: _pt.func,
/**
* The directions of the allowed pan (default allows all directions)
* Types: UP, DOWN, LEFT and RIGHT (using PanningProvider.Directions.###)
*/
directions: _pt.array,
/**
* The sensitivity beyond which a pan is no longer considered a single click (default is 5)
*/
panSensitivity: _pt.number,
/**
* The sensitivity beyond which a pan is no longer considered a drag, but a swipe (default is 1.8)
* Note: a pan would have to occur (i.e. the panSensitivity has already been surpassed)
*/
swipeVelocitySensitivity: _pt.number,
/**
* Is there a view that is clickable (has onPress etc.) in the PanListenerView.
* This can affect the panability of this component.
*/
isClickable: _pt.bool,
context: _pt.shape({
/**
* This is were you will get notified when a drag occurs
* onDrag = ({directions, deltas}) => {...}
* directions - array of directions
* deltas - array of deltas (same length and order as directions)
* Both arrays will have {x, y} - if no x or y drag has occurred this value will be undefined
*/
onDrag: _pt.func,
/**
* This is were you will get notified when a swipe occurs
* onSwipe = ({directions, velocities}) => {...}
* directions - array of directions
* velocities - array of velocities (same length and order as directions)
* Both arrays will have {x, y} - if no x or y swipe has occurred this value will be undefined
*/
onSwipe: _pt.func,
/**
* This is were you will get notified when the pan starts
*/
onPanStart: _pt.func,
/**
* This is were you will get notified when the pan ends
* The user has released all touches while this view is the responder.
* This typically means a gesture has succeeded
*/
onPanRelease: _pt.func,
/**
* This is were you will get notified when the pan ends
* Another component has become the responder,
* so this gesture should be cancelled
*/
onPanTerminated: _pt.func
})
};
static displayName = 'PanListenerView';
static defaultProps = {
directions: DEFAULT_DIRECTIONS,
panSensitivity: DEFAULT_PAN_SENSITIVITY,
swipeVelocitySensitivity: DEFAULT_SWIPE_VELOCITY
};
constructor(props) {
super(props);
const {
isClickable
} = props;
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: isClickable ? this.shouldPan : this.yes,
onMoveShouldSetPanResponder: this.shouldPan,
onStartShouldSetPanResponderCapture: this.no,
onMoveShouldSetPanResponderCapture: this.no,
onPanResponderGrant: this.handlePanStart,
onPanResponderMove: this.handlePanMove,
onPanResponderRelease: this.handlePanRelease,
onPanResponderTerminate: this.handlePanTerminate
});
}
yes = () => {
return true;
};
no = () => {
return false;
};
shouldPan = (_e, gestureState) => {
// return true if user is swiping, return false if it's a single click
const {
dy,
dx
} = gestureState;
const {
directions,
panSensitivity = DEFAULT_PAN_SENSITIVITY
} = this.props;
return Boolean(directions && (directions.includes(PanningProvider.Directions.UP) && dy < -panSensitivity || directions.includes(PanningProvider.Directions.DOWN) && dy > panSensitivity || directions.includes(PanningProvider.Directions.LEFT) && dx < -panSensitivity || directions.includes(PanningProvider.Directions.RIGHT) && dx > panSensitivity));
};
handlePanStart = () => {
this.props.onPanStart?.();
this.props.context?.onPanStart?.();
};
getSwipeDirection = ({
vx,
vy
}) => {
const {
swipeVelocitySensitivity = DEFAULT_SWIPE_VELOCITY
} = this.props;
return this.getDirectionsOverSensitivity(vx, vy, swipeVelocitySensitivity);
};
getDragDirection = ({
dx,
dy
}) => {
return this.getDirectionsOverSensitivity(dx, dy, 0);
};
getDirectionsOverSensitivity = (x, y, sensitivity) => {
const {
directions = DEFAULT_DIRECTIONS
} = this.props;
const selectedDirections = {};
const selectedAmounts = {};
if (directions.includes(PanningProvider.Directions.LEFT) && x < -sensitivity) {
selectedDirections.x = PanningProvider.Directions.LEFT;
selectedAmounts.x = x;
} else if (directions.includes(PanningProvider.Directions.RIGHT) && x > sensitivity) {
selectedDirections.x = PanningProvider.Directions.RIGHT;
selectedAmounts.x = x;
}
if (directions.includes(PanningProvider.Directions.UP) && y < -sensitivity) {
selectedDirections.y = PanningProvider.Directions.UP;
selectedAmounts.y = y;
} else if (directions.includes(PanningProvider.Directions.DOWN) && y > sensitivity) {
selectedDirections.y = PanningProvider.Directions.DOWN;
selectedAmounts.y = y;
}
return {
selectedDirections,
selectedAmounts
};
};
panResultHasValue = panResult => {
return Boolean(panResult && (panResult.selectedDirections.x || panResult.selectedDirections.y));
};
handlePanMove = (_e, gestureState) => {
const {
onSwipe,
onDrag,
context
} = this.props;
const hasSwipe = !_.isUndefined(onSwipe);
const hasDrag = !_.isUndefined(onDrag);
const hasContext = !_.isUndefined(context);
let panResult;
if (hasSwipe || hasContext) {
panResult = this.getSwipeDirection(gestureState);
}
if (this.panResultHasValue(panResult)) {
// @ts-ignore
const data = {
directions: panResult.selectedDirections,
velocities: panResult.selectedAmounts
};
this.props.onSwipe?.(data);
context?.onSwipe?.(data);
} else if (hasDrag || hasContext) {
panResult = this.getDragDirection(gestureState);
if (this.panResultHasValue(panResult)) {
const data = {
directions: panResult.selectedDirections,
deltas: panResult.selectedAmounts
};
this.props.onDrag?.(data);
context?.onDrag?.(data);
}
}
};
handlePanRelease = () => {
this.props.onPanRelease?.();
this.props.context?.onPanRelease?.();
};
handlePanTerminate = () => {
this.props.onPanTerminated?.();
this.props.context?.onPanTerminated?.();
};
render() {
const {
children,
...others
} = this.props;
return <View {...others} {...this.panResponder.panHandlers}>
{children}
</View>;
}
}
export default asPanViewConsumer(PanListenerView);