react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
166 lines (165 loc) • 6.69 kB
JavaScript
import * as React from 'react';
import { Animated, Platform, processColor, StyleSheet } from 'react-native';
import createNativeWrapper from '../handlers/createNativeWrapper';
import GestureHandlerButton from './GestureHandlerButton';
import { State } from '../State';
import { isFabric } from '../utils';
export const RawButton = createNativeWrapper(GestureHandlerButton, {
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
});
let IS_FABRIC = null;
class InnerBaseButton extends React.Component {
static defaultProps = {
delayLongPress: 600,
};
lastActive;
longPressTimeout;
longPressDetected;
constructor(props) {
super(props);
this.lastActive = false;
this.longPressDetected = false;
}
handleEvent = ({ nativeEvent, }) => {
const { state, oldState, pointerInside } = nativeEvent;
const active = pointerInside && state === State.ACTIVE;
if (active !== this.lastActive && this.props.onActiveStateChange) {
this.props.onActiveStateChange(active);
}
if (!this.longPressDetected &&
oldState === State.ACTIVE &&
state !== State.CANCELLED &&
this.lastActive &&
this.props.onPress) {
this.props.onPress(pointerInside);
}
if (!this.lastActive &&
// NativeViewGestureHandler sends different events based on platform
state === (Platform.OS !== 'android' ? State.ACTIVE : State.BEGAN) &&
pointerInside) {
this.longPressDetected = false;
if (this.props.onLongPress) {
this.longPressTimeout = setTimeout(this.onLongPress, this.props.delayLongPress);
}
}
else if (
// Cancel longpress timeout if it's set and the finger moved out of the view
state === State.ACTIVE &&
!pointerInside &&
this.longPressTimeout !== undefined) {
clearTimeout(this.longPressTimeout);
this.longPressTimeout = undefined;
}
else if (
// Cancel longpress timeout if it's set and the gesture has finished
this.longPressTimeout !== undefined &&
(state === State.END ||
state === State.CANCELLED ||
state === State.FAILED)) {
clearTimeout(this.longPressTimeout);
this.longPressTimeout = undefined;
}
this.lastActive = active;
};
onLongPress = () => {
this.longPressDetected = true;
this.props.onLongPress?.();
};
// Normally, the parent would execute it's handler first, then forward the
// event to listeners. However, here our handler is virtually only forwarding
// events to listeners, so we reverse the order to keep the proper order of
// the callbacks (from "raw" ones to "processed").
onHandlerStateChange = (e) => {
this.props.onHandlerStateChange?.(e);
this.handleEvent(e);
};
onGestureEvent = (e) => {
this.props.onGestureEvent?.(e);
this.handleEvent(e); // TODO: maybe it is not correct
};
render() {
const { rippleColor: unprocessedRippleColor, style, ...rest } = this.props;
if (IS_FABRIC === null) {
IS_FABRIC = isFabric();
}
const rippleColor = IS_FABRIC
? unprocessedRippleColor
: processColor(unprocessedRippleColor ?? undefined);
return (<RawButton ref={this.props.innerRef} rippleColor={rippleColor} style={[style, Platform.OS === 'ios' && { cursor: undefined }]} {...rest} onGestureEvent={this.onGestureEvent} onHandlerStateChange={this.onHandlerStateChange}/>);
}
}
const AnimatedInnerBaseButton = Animated.createAnimatedComponent(InnerBaseButton);
export const BaseButton = React.forwardRef((props, ref) => <InnerBaseButton innerRef={ref} {...props}/>);
const AnimatedBaseButton = React.forwardRef((props, ref) => <AnimatedInnerBaseButton innerRef={ref} {...props}/>);
const btnStyles = StyleSheet.create({
underlay: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
},
});
class InnerRectButton extends React.Component {
static defaultProps = {
activeOpacity: 0.105,
underlayColor: 'black',
};
opacity;
constructor(props) {
super(props);
this.opacity = new Animated.Value(0);
}
onActiveStateChange = (active) => {
if (Platform.OS !== 'android') {
this.opacity.setValue(active ? this.props.activeOpacity : 0);
}
this.props.onActiveStateChange?.(active);
};
render() {
const { children, style, ...rest } = this.props;
const resolvedStyle = StyleSheet.flatten(style) ?? {};
return (<BaseButton {...rest} ref={this.props.innerRef} style={resolvedStyle} onActiveStateChange={this.onActiveStateChange}>
<Animated.View style={[
btnStyles.underlay,
{
opacity: this.opacity,
backgroundColor: this.props.underlayColor,
borderRadius: resolvedStyle.borderRadius,
borderTopLeftRadius: resolvedStyle.borderTopLeftRadius,
borderTopRightRadius: resolvedStyle.borderTopRightRadius,
borderBottomLeftRadius: resolvedStyle.borderBottomLeftRadius,
borderBottomRightRadius: resolvedStyle.borderBottomRightRadius,
},
]}/>
{children}
</BaseButton>);
}
}
export const RectButton = React.forwardRef((props, ref) => <InnerRectButton innerRef={ref} {...props}/>);
class InnerBorderlessButton extends React.Component {
static defaultProps = {
activeOpacity: 0.3,
borderless: true,
};
opacity;
constructor(props) {
super(props);
this.opacity = new Animated.Value(1);
}
onActiveStateChange = (active) => {
if (Platform.OS !== 'android') {
this.opacity.setValue(active ? this.props.activeOpacity : 1);
}
this.props.onActiveStateChange?.(active);
};
render() {
const { children, style, innerRef, ...rest } = this.props;
return (<AnimatedBaseButton {...rest} innerRef={innerRef} onActiveStateChange={this.onActiveStateChange} style={[style, Platform.OS === 'ios' && { opacity: this.opacity }]}>
{children}
</AnimatedBaseButton>);
}
}
export const BorderlessButton = React.forwardRef((props, ref) => <InnerBorderlessButton innerRef={ref} {...props}/>);
export { default as PureNativeButton } from './GestureHandlerButton';