diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index e349eef13a..82392a25a7 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -170,4 +170,6 @@ export * from './v3/hooks/relations'; export { SingleGestureName } from './v3/types'; +export * from './v3/hooks/gestures'; + initialize(); diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index a55c2fa040..01af531761 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -7,9 +7,9 @@ import { tagMessage } from '../../utils'; import { configureRelations } from './utils'; import { isComposedGesture } from '../hooks/utils/relationUtils'; -export interface NativeDetectorProps { +export interface NativeDetectorProps { children?: React.ReactNode; - gesture: Gesture; + gesture: Gesture; } const AnimatedNativeDetector = @@ -18,7 +18,10 @@ const AnimatedNativeDetector = const ReanimatedNativeDetector = Reanimated?.default.createAnimatedComponent(HostGestureDetector); -export function NativeDetector({ gesture, children }: NativeDetectorProps) { +export function NativeDetector({ + gesture, + children, +}: NativeDetectorProps) { const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents ? AnimatedNativeDetector : gesture.config.shouldUseReanimated @@ -38,25 +41,25 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { return ( ( + gesture: Gesture +) { if (isComposedGesture(gesture)) { const simultaneousHandlers = new Set( gesture.externalSimultaneousHandlers diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts new file mode 100644 index 0000000000..64e175e259 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/index.ts @@ -0,0 +1,26 @@ +export type { TapGestureConfig } from './useTap'; +export { useTap } from './useTap'; + +export type { FlingGestureConfig } from './useFling'; +export { useFling } from './useFling'; + +export type { LongPressGestureConfig } from './useLongPress'; +export { useLongPress } from './useLongPress'; + +export type { PinchGestureConfig } from './usePinch'; +export { usePinch } from './usePinch'; + +export type { RotationGestureConfig } from './useRotation'; +export { useRotation } from './useRotation'; + +export type { HoverGestureConfig } from './useHover'; +export { useHover } from './useHover'; + +export type { ManualGestureConfig } from './useManual'; +export { useManual } from './useManual'; + +export type { NativeViewGestureConfig } from './useNative'; +export { useNative } from './useNative'; + +export type { PanGestureConfig } from './usePan'; +export { usePan } from './usePan'; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts new file mode 100644 index 0000000000..f1e6d2981b --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useFling.ts @@ -0,0 +1,53 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type FlingGestureProperties = { + /** + * Expressed allowed direction of movement. It's possible to pass one or many + * directions in one parameter: + * + * ```js + * direction={Directions.RIGHT | Directions.LEFT} + * ``` + * + * or + * + * ```js + * direction={Directions.DOWN} + * ``` + */ + direction?: number; + + /** + * Determine exact number of points required to handle the fling gesture. + */ + numberOfPointers?: number; +}; + +type FlingHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; +}; + +type FlingGestureInternalConfig = BaseGestureConfig< + FlingHandlerData, + FlingGestureProperties +>; + +export type FlingGestureConfig = + ExcludeInternalConfigProps; + +export function useFling(config: FlingGestureConfig) { + const flingConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Fling, flingConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts new file mode 100644 index 0000000000..cbcf16d4b3 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useHover.ts @@ -0,0 +1,46 @@ +import { StylusData } from '../../../handlers/gestureHandlerCommon'; +import { HoverEffect } from '../../../handlers/gestures/hoverGesture'; +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type HoverGestureProperties = { + /** + * Visual effect applied to the view while the view is hovered. The possible values are: + * + * - `HoverEffect.None` + * - `HoverEffect.Lift` + * - `HoverEffect.Highlight` + * + * Defaults to `HoverEffect.None` + */ + hoverEffect?: HoverEffect; +}; + +type HoverHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + stylusData: StylusData; +}; + +type HoverGestureInternalConfig = BaseGestureConfig< + HoverHandlerData, + HoverGestureProperties +>; + +export type HoverGestureConfig = + ExcludeInternalConfigProps; + +export function useHover(config: HoverGestureConfig) { + const hoverConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Hover, hoverConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts new file mode 100644 index 0000000000..723c9a598c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useLongPress.ts @@ -0,0 +1,80 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig, remapProps } from '../utils'; + +type LongPressGestureProperties = { + /** + * Minimum time, expressed in milliseconds, that a finger must remain pressed on + * the corresponding view. The default value is 500. + */ + minDuration?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel during a long press gesture. If the finger travels + * further than the defined distance and the handler hasn't yet activated, it + * will fail to recognize the gesture. The default value is 10. + */ + maxDistance?: number; + + /** + * Determine exact number of points required to handle the long press gesture. + */ + numberOfPointers?: number; +}; + +type LongPressGestureInternalProperties = { + minDurationMs?: number; + maxDist?: number; + numberOfPointers?: number; +}; + +type LongPressHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + duration: number; +}; + +export type LongPressGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type LongPressGestureInternalConfig = BaseGestureConfig< + LongPressHandlerData, + LongPressGestureInternalProperties +>; + +const LongPressPropsMapping = new Map< + keyof LongPressGestureProperties, + keyof LongPressGestureInternalProperties +>([ + ['minDuration', 'minDurationMs'], + ['maxDistance', 'maxDist'], +]); + +export function useLongPress(config: LongPressGestureConfig) { + const longPressConfig = cloneConfig< + LongPressHandlerData, + LongPressGestureInternalProperties + >(config); + + remapProps( + longPressConfig, + LongPressPropsMapping + ); + + if (longPressConfig.shouldCancelWhenOutside === undefined) { + longPressConfig.shouldCancelWhenOutside = true; + } + + return useGesture( + SingleGestureName.LongPress, + longPressConfig + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts new file mode 100644 index 0000000000..5d32f85e71 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useManual.ts @@ -0,0 +1,26 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type ManualGestureProperties = Record; +type ManualHandlerData = Record; + +type ManualGestureInternalConfig = BaseGestureConfig< + ManualHandlerData, + ManualGestureProperties +>; + +export type ManualGestureConfig = + ExcludeInternalConfigProps; + +export function useManual(config: ManualGestureConfig) { + const manualConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Manual, manualConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts new file mode 100644 index 0000000000..0ed71b4d83 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useNative.ts @@ -0,0 +1,44 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type NativeViewGestureProperties = { + /** + * Android only. + * + * Determines whether the handler should check for an existing touch event on + * instantiation. + */ + shouldActivateOnStart?: boolean; + + /** + * When `true`, cancels all other gesture handlers when this + * `NativeViewGestureHandler` receives an `ACTIVE` state event. + */ + disallowInterruption?: boolean; +}; + +type NativeViewHandlerData = { + pointerInside: boolean; +}; + +type NativeViewGestureInternalConfig = BaseGestureConfig< + NativeViewHandlerData, + NativeViewGestureProperties +>; + +export type NativeViewGestureConfig = + ExcludeInternalConfigProps; + +export function useNative(config: NativeViewGestureConfig) { + const nativeConfig = cloneConfig< + NativeViewHandlerData, + NativeViewGestureProperties + >(config); + + return useGesture(SingleGestureName.Native, nativeConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts new file mode 100644 index 0000000000..c79dc9cb12 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePan.ts @@ -0,0 +1,259 @@ +import { StylusData } from '../../../handlers/gestureHandlerCommon'; +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig, remapProps } from '../utils'; + +type CommonPanGestureProperties = { + /** + * Minimum distance the finger (or multiple finger) need to travel before the + * handler activates. Expressed in points. + */ + minDistance?: number; + + /** + * Android only. + */ + avgTouches?: boolean; + + /** + * Enables two-finger gestures on supported devices, for example iPads with + * trackpads. If not enabled the gesture will require click + drag, with + * enableTrackpadTwoFingerGesture swiping with two fingers will also trigger + * the gesture. + */ + enableTrackpadTwoFingerGesture?: boolean; + + /** + * A number of fingers that is required to be placed before handler can + * activate. Should be a higher or equal to 0 integer. + */ + minPointers?: number; + + /** + * When the given number of fingers is placed on the screen and handler hasn't + * yet activated it will fail recognizing the gesture. Should be a higher or + * equal to 0 integer. + */ + maxPointers?: number; + + minVelocity?: number; + minVelocityX?: number; + minVelocityY?: number; + activateAfterLongPress?: number; +}; + +export type PanGestureProperties = CommonPanGestureProperties & { + /** + * Range along X axis (in points) where fingers travels without activation of + * handler. Moving outside of this range implies activation of handler. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + activeOffsetY?: + | number + | [activeOffsetYStart: number, activeOffsetYEnd: number]; + + /** + * Range along X axis (in points) where fingers travels without activation of + * handler. Moving outside of this range implies activation of handler. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + activeOffsetX?: + | number + | [activeOffsetXStart: number, activeOffsetXEnd: number]; + + /** + * When the finger moves outside this range (in points) along Y axis and + * handler hasn't yet activated it will fail recognizing the gesture. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + failOffsetY?: number | [failOffsetYStart: number, failOffsetYEnd: number]; + + /** + * When the finger moves outside this range (in points) along X axis and + * handler hasn't yet activated it will fail recognizing the gesture. Range + * can be given as an array or a single number. If range is set as an array, + * first value must be lower or equal to 0, a the second one higher or equal + * to 0. If only one number `p` is given a range of `(-inf, p)` will be used + * if `p` is higher or equal to 0 and `(-p, inf)` otherwise. + */ + failOffsetX?: number | [failOffsetXStart: number, failOffsetXEnd: number]; +}; + +type PanGestureInternalProperties = CommonPanGestureProperties & { + minDist?: number; + activeOffsetYStart?: number; + activeOffsetYEnd?: number; + activeOffsetXStart?: number; + activeOffsetXEnd?: number; + failOffsetYStart?: number; + failOffsetYEnd?: number; + failOffsetXStart?: number; + failOffsetXEnd?: number; +}; + +type PanHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; + translationX: number; + translationY: number; + velocityX: number; + velocityY: number; + stylusData: StylusData; +}; + +export type PanGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type PanGestureInternalConfig = BaseGestureConfig< + PanHandlerData, + PanGestureInternalProperties +>; + +const PanPropsMapping = new Map< + keyof PanGestureProperties, + keyof PanGestureInternalProperties +>([['minDistance', 'minDist']]); + +function validatePanConfig(config: PanGestureConfig) { + if ( + Array.isArray(config.activeOffsetX) && + (config.activeOffsetX[0] > 0 || config.activeOffsetX[1] < 0) + ) { + throw new Error( + `First element of activeOffsetX should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.activeOffsetY) && + (config.activeOffsetY[0] > 0 || config.activeOffsetY[1] < 0) + ) { + throw new Error( + `First element of activeOffsetY should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.failOffsetX) && + (config.failOffsetX[0] > 0 || config.failOffsetX[1] < 0) + ) { + throw new Error( + `First element of failOffsetX should be negative, and the second one should be positive` + ); + } + + if ( + Array.isArray(config.failOffsetY) && + (config.failOffsetY[0] > 0 || config.failOffsetY[1] < 0) + ) { + throw new Error( + `First element of failOffsetY should be negative, and the second one should be positive` + ); + } + + if (config.minDistance && (config.failOffsetX || config.failOffsetY)) { + throw new Error( + `It is not supported to use minDistance with failOffsetX or failOffsetY, use activeOffsetX and activeOffsetY instead` + ); + } + + if (config.minDistance && (config.activeOffsetX || config.activeOffsetY)) { + throw new Error( + `It is not supported to use minDistance with activeOffsetX or activeOffsetY` + ); + } +} + +function transformPanProps( + config: PanGestureConfig & PanGestureInternalConfig +) { + if (config.activeOffsetX !== undefined) { + if (Array.isArray(config.activeOffsetX)) { + config.activeOffsetXStart = config.activeOffsetX[0]; + config.activeOffsetXEnd = config.activeOffsetX[1]; + } else if (config.activeOffsetX < 0) { + config.activeOffsetXStart = config.activeOffsetX; + } else { + config.activeOffsetXEnd = config.activeOffsetX; + } + + delete config.activeOffsetX; + } + + if (config.activeOffsetY !== undefined) { + if (Array.isArray(config.activeOffsetY)) { + config.activeOffsetYStart = config.activeOffsetY[0]; + config.activeOffsetYEnd = config.activeOffsetY[1]; + } else if (config.activeOffsetY < 0) { + config.activeOffsetYStart = config.activeOffsetY; + } else { + config.activeOffsetYEnd = config.activeOffsetY; + } + + delete config.activeOffsetY; + } + + if (config.failOffsetX !== undefined) { + if (Array.isArray(config.failOffsetX)) { + config.failOffsetXStart = config.failOffsetX[0]; + config.failOffsetXEnd = config.failOffsetX[1]; + } else if (config.failOffsetX < 0) { + config.failOffsetXStart = config.failOffsetX; + } else { + config.failOffsetXEnd = config.failOffsetX; + } + + delete config.failOffsetX; + } + + if (config.failOffsetY !== undefined) { + if (Array.isArray(config.failOffsetY)) { + config.failOffsetYStart = config.failOffsetY[0]; + config.failOffsetYEnd = config.failOffsetY[1]; + } else if (config.failOffsetY < 0) { + config.failOffsetYStart = config.failOffsetY; + } else { + config.failOffsetYEnd = config.failOffsetY; + } + + delete config.failOffsetY; + } +} + +export function usePan(config: PanGestureConfig) { + if (__DEV__) { + validatePanConfig(config); + } + + const panConfig = cloneConfig( + config + ); + + remapProps( + panConfig, + PanPropsMapping + ); + + transformPanProps(panConfig); + + return useGesture( + SingleGestureName.Pan, + panConfig + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts new file mode 100644 index 0000000000..ed61b0202c --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/usePinch.ts @@ -0,0 +1,32 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type PinchGestureProperties = Record; + +type PinchHandlerData = { + scale: number; + focalX: number; + focalY: number; + velocity: number; +}; + +type PinchGestureInternalConfig = BaseGestureConfig< + PinchHandlerData, + PinchGestureProperties +>; + +export type PinchGestureConfig = + ExcludeInternalConfigProps; + +export function usePinch(config: PinchGestureConfig) { + const pinchConfig = cloneConfig( + config + ); + + return useGesture(SingleGestureName.Pinch, pinchConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts new file mode 100644 index 0000000000..542def7146 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useRotation.ts @@ -0,0 +1,33 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig } from '../utils'; + +type RotationGestureProperties = Record; + +type RotationHandlerData = { + rotation: number; + anchorX: number; + anchorY: number; + velocity: number; +}; + +type RotationGestureInternalConfig = BaseGestureConfig< + RotationHandlerData, + RotationGestureProperties +>; + +export type RotationGestureConfig = + ExcludeInternalConfigProps; + +export function useRotation(config: RotationGestureConfig) { + const rotationConfig = cloneConfig< + RotationHandlerData, + RotationGestureProperties + >(config); + + return useGesture(SingleGestureName.Rotation, rotationConfig); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts new file mode 100644 index 0000000000..d45f8ae99e --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/useTap.ts @@ -0,0 +1,109 @@ +import { + BaseGestureConfig, + ExcludeInternalConfigProps, + SingleGestureName, +} from '../../types'; +import { useGesture } from '../useGesture'; +import { cloneConfig, remapProps } from '../utils'; + +type TapGestureProperties = { + /** + * Minimum number of pointers (fingers) required to be placed before the + * handler activates. Should be a positive integer. + * The default value is 1. + */ + minPointers?: number; + + /** + * Maximum time, expressed in milliseconds, that defines how fast a finger + * must be released after a touch. The default value is 500. + */ + maxDuration?: number; + + /** + * Maximum time, expressed in milliseconds, that can pass before the next tap + * if many taps are required. The default value is 500. + */ + maxDelay?: number; + + /** + * Number of tap gestures required to activate the handler. The default value + * is 1. + */ + numberOfTaps?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel along the X axis during a tap gesture. If the finger + * travels further than the defined distance along the X axis and the handler + * hasn't yet activated, it will fail to recognize the gesture. + */ + maxDeltaX?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel along the Y axis during a tap gesture. If the finger + * travels further than the defined distance along the Y axis and the handler + * hasn't yet activated, it will fail to recognize the gesture. + */ + maxDeltaY?: number; + + /** + * Maximum distance, expressed in points, that defines how far the finger is + * allowed to travel during a tap gesture. If the finger travels further than + * the defined distance and the handler hasn't yet + * activated, it will fail to recognize the gesture. + */ + maxDistance?: number; +}; + +type TapGestureInternalProperties = { + minPointers?: number; + numberOfTaps?: number; + maxDeltaX?: number; + maxDeltaY?: number; + maxDurationMs?: number; + maxDelayMs?: number; + maxDist?: number; +}; + +type TapHandlerData = { + x: number; + y: number; + absoluteX: number; + absoluteY: number; +}; + +export type TapGestureConfig = ExcludeInternalConfigProps< + BaseGestureConfig +>; + +type TapGestureInternalConfig = BaseGestureConfig< + TapHandlerData, + TapGestureInternalProperties +>; + +const TapPropsMapping = new Map< + keyof TapGestureProperties, + keyof TapGestureInternalProperties +>([ + ['maxDistance', 'maxDist'], + ['maxDuration', 'maxDurationMs'], + ['maxDelay', 'maxDelayMs'], +]); + +export function useTap(config: TapGestureConfig) { + const tapConfig = cloneConfig( + config + ); + + remapProps( + tapConfig, + TapPropsMapping + ); + + return useGesture( + SingleGestureName.Tap, + tapConfig + ); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index d90fc773d1..37a6fddee7 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -2,6 +2,7 @@ import { NativeSyntheticEvent } from 'react-native'; import { AnimatedEvent, BaseGestureConfig, + ExcludeInternalConfigProps, GestureHandlerEvent, GestureStateChangeEvent, GestureUpdateEvent, @@ -95,3 +96,28 @@ export function shouldHandleTouchEvents( !!config.onTouchesCancelled ); } + +export function cloneConfig( + config: ExcludeInternalConfigProps> +): BaseGestureConfig { + return { ...config } as BaseGestureConfig; +} + +export function remapProps( + config: TConfig & TInternalConfig, + propsMapping: Map +): TInternalConfig { + type MergedConfig = TConfig & TInternalConfig; + + propsMapping.forEach((internalKey, key) => { + if (key in config) { + config[internalKey as keyof MergedConfig] = + config[key as keyof MergedConfig]; + + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete config[key as keyof MergedConfig]; + } + }); + + return config; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts index 8943c85545..863f312406 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/relationUtils.ts @@ -5,8 +5,8 @@ import { GestureRelations, } from '../../types'; -export function isComposedGesture( - gesture: Gesture +export function isComposedGesture( + gesture: Gesture ): gesture is ComposedGesture { return 'tags' in gesture; } diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 267c83db86..110edade40 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -1,21 +1,37 @@ import { Animated, NativeSyntheticEvent } from 'react-native'; -import { GestureTouchEvent } from '../handlers/gestureHandlerCommon'; +import { + ActiveCursor, + GestureTouchEvent, + HitSlop, + MouseButton, + TouchAction, + UserSelect, +} from '../handlers/gestureHandlerCommon'; +import { PointerType } from '../PointerType'; + import { State } from '../State'; -export interface EventPayload { +interface EventPayload { handlerTag: number; state: State; } -export interface StateChangeEventPayload extends EventPayload { +interface StateChangeEventPayload extends EventPayload { oldState: State; } -export type GestureUpdateEvent = EventPayload & { - handlerData: THandlerData; +export type BaseHandlerData = { + numberOfPointers: number; + pointerType: PointerType; +}; + +export type HandlerData = BaseHandlerData & T; + +export type GestureUpdateEvent = EventPayload & { + handlerData: HandlerData; }; -export type GestureStateChangeEvent = StateChangeEventPayload & { - handlerData: THandlerData; +export type GestureStateChangeEvent = StateChangeEventPayload & { + handlerData: HandlerData; }; export type GestureHandlerEvent = @@ -50,7 +66,8 @@ export enum SingleGestureName { Rotation = 'RotationGestureHandler', Fling = 'FlingGestureHandler', Manual = 'ManualGestureHandler', - Native = 'NativeGestureHandler', + Native = 'NativeViewGestureHandler', + Hover = 'HoverGestureHandler', } export enum ComposedGestureName { @@ -134,12 +151,41 @@ export interface GestureCallbacks { onTouchesCancelled?: (event: GestureTouchEvent) => void; } +export type InternalConfigProps = { + shouldUseReanimated?: boolean; + dispatchesAnimatedEvents?: boolean; + needsPointerData?: boolean; + changeEventCalculator?: ChangeCalculatorType; +}; + export type BaseGestureConfig = ExternalRelations & GestureCallbacks & - TConfig & { + FilterNeverProperties & + InternalConfigProps & { disableReanimated?: boolean; - shouldUseReanimated?: boolean; - dispatchesAnimatedEvents?: boolean; - needsPointerData?: boolean; - changeEventCalculator?: ChangeCalculatorType; + enabled?: boolean; + shouldCancelWhenOutside?: boolean; + hitSlop?: HitSlop; + userSelect?: UserSelect; + activeCursor?: ActiveCursor; + mouseButton?: MouseButton; + enableContextMenu?: boolean; + touchAction?: TouchAction; }; + +export type ExcludeInternalConfigProps = Omit< + T, + keyof InternalConfigProps +>; + +// Some handlers do not have specific properties (e.g. `Pinch`), therefore we mark those prop types as `Record`. +// Doing intersection with those types results in type which cannot have any property. In order to fix that, +// we filter out properties with `never` values. +// +// This piece of magic works like this: +// 1. We iterate over all keys of T using `keyof T` +// 2. We check if the type of property is `never` using conditional types (`T[K] extends never ? never : K`). +// If it is, we replace the key with `never`, i.e. we delete it. Otherwise we keep it as is. +type FilterNeverProperties = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +};