import * as React from 'react'; import type { HostComponent, ViewStyle } from 'react-native'; import { Animated, Platform, StyleSheet } from 'react-native'; import createNativeWrapper from '../handlers/createNativeWrapper'; import type { GestureEvent, HandlerStateChangeEvent, } from '../handlers/gestureHandlerCommon'; import type { NativeViewGestureHandlerPayload } from '../handlers/GestureHandlerEventPayload'; import { State } from '../State'; import type { BaseButtonWithRefProps, BorderlessButtonWithRefProps, LegacyBaseButtonProps, LegacyBorderlessButtonProps, LegacyRawButtonProps, LegacyRectButtonProps, RectButtonWithRefProps, } from './GestureButtonsProps'; import GestureHandlerButton, { type ButtonProps } from './GestureHandlerButton'; type LegacyRawButtonInnerProps = LegacyRawButtonProps & { needsOffscreenAlphaCompositing?: boolean; }; const LegacyRawButtonInner = createNativeWrapper( GestureHandlerButton as unknown as HostComponent, { shouldCancelWhenOutside: false, shouldActivateOnStart: Platform.OS === 'web', } ); /** * @deprecated use `RawButton` instead */ export const LegacyRawButton = ( props: Omit< React.ComponentProps, 'needsOffscreenAlphaCompositing' > ) => ; class InnerBaseButton extends React.Component { static defaultProps = { delayLongPress: 600, }; private lastIsPressed: boolean; private longPressTimeout: ReturnType | undefined; private longPressDetected: boolean; constructor(props: BaseButtonWithRefProps) { super(props); this.lastIsPressed = false; this.longPressDetected = false; } private handleEvent = ({ nativeEvent, }: HandlerStateChangeEvent) => { const { state, oldState, pointerInside } = nativeEvent; const isPressed = pointerInside && (state === State.BEGAN || state === State.ACTIVE); if (isPressed !== this.lastIsPressed && this.props.onActiveStateChange) { this.props.onActiveStateChange(isPressed); } if ( !this.longPressDetected && oldState === State.ACTIVE && state !== State.CANCELLED && this.lastIsPressed && this.props.onPress ) { this.props.onPress(pointerInside); } if (!this.lastIsPressed && state === 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.lastIsPressed = isPressed; }; private 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"). private onHandlerStateChange = ( e: HandlerStateChangeEvent ) => { this.props.onHandlerStateChange?.(e); this.handleEvent(e); }; private onGestureEvent = ( e: GestureEvent ) => { this.props.onGestureEvent?.(e); this.handleEvent( e as HandlerStateChangeEvent ); // TODO: maybe it is not correct }; override render() { const { rippleColor, ...rest } = this.props; return ( ); } } const AnimatedInnerBaseButton = Animated.createAnimatedComponent(InnerBaseButton); /** * @deprecated use `BaseButton` instead */ export const LegacyBaseButton = ({ ref, ...props }: Omit & { ref?: React.Ref> | undefined; }) => ; const AnimatedBaseButton = ({ ref, ...props }: Animated.AnimatedProps & { ref?: React.Ref> | undefined; }) => ; 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', }; private opacity: Animated.Value; constructor(props: RectButtonWithRefProps) { super(props); this.opacity = new Animated.Value(0); } private onActiveStateChange = (active: boolean) => { if (Platform.OS !== 'android') { this.opacity.setValue(active ? this.props.activeOpacity! : 0); } this.props.onActiveStateChange?.(active); }; override render() { // Move activeOpacity out of the rest props to avoid passing it to the native component // eslint-disable-next-line @typescript-eslint/no-unused-vars const { children, style, activeOpacity, ...rest } = this.props; const resolvedStyle = StyleSheet.flatten(style) ?? {}; return ( {children} ); } } /** * @deprecated use `RectButton` instead */ export const LegacyRectButton = ({ ref, ...props }: Omit & { ref?: React.Ref> | undefined; }) => ; class InnerBorderlessButton extends React.Component { static defaultProps = { activeOpacity: 0.3, borderless: true, }; private opacity: Animated.Value; constructor(props: BorderlessButtonWithRefProps) { super(props); this.opacity = new Animated.Value(1); } private onActiveStateChange = (active: boolean) => { if (Platform.OS !== 'android') { this.opacity.setValue(active ? this.props.activeOpacity! : 1); } this.props.onActiveStateChange?.(active); }; override render() { // Move activeOpacity out of the rest props to avoid passing it to the native component // eslint-disable-next-line @typescript-eslint/no-unused-vars const { children, style, innerRef, activeOpacity, ...rest } = this.props; return ( {children} ); } } /** * @deprecated use `BorderlessButton` instead */ export const LegacyBorderlessButton = ({ ref, ...props }: Omit & { ref?: React.Ref> | undefined; }) => ; /** * @deprecated use `PureNativeButton` instead */ export const LegacyPureNativeButton = ({ ref, ...props }: Omit) => ( >} needsOffscreenAlphaCompositing /> );