From 386d521958845428334551d67dd17063a0738f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 15 Oct 2025 17:15:05 +0200 Subject: [PATCH] split types --- .../src/v3/LogicDetector.tsx | 10 +- .../src/v3/NativeDetector/NativeDetector.tsx | 26 +- .../v3/NativeDetector/useDetectorContext.ts | 4 +- .../v3/hooks/relations/useComposedGesture.ts | 25 +- .../src/v3/hooks/useGesture.ts | 2 +- .../src/v3/types.ts | 277 ------------------ .../src/v3/types/ConfigTypes.ts | 61 ++++ .../src/v3/types/DetectorTypes.ts | 27 ++ .../src/v3/types/EventTypes.ts | 66 +++++ .../src/v3/types/GestureTypes.ts | 67 +++++ .../src/v3/types/ReanimatedTypes.ts | 47 +++ .../src/v3/types/UtilityTypes.ts | 32 ++ .../src/v3/types/index.ts | 6 + 13 files changed, 340 insertions(+), 310 deletions(-) delete mode 100644 packages/react-native-gesture-handler/src/v3/types.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/DetectorTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/EventTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/ReanimatedTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/UtilityTypes.ts create mode 100644 packages/react-native-gesture-handler/src/v3/types/index.ts diff --git a/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx b/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx index c24bdb9b00..a280c3de60 100644 --- a/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/LogicDetector.tsx @@ -4,7 +4,7 @@ import { findNodeHandle, Platform } from 'react-native'; import { useDetectorContext } from './NativeDetector/useDetectorContext'; import { NativeDetectorProps } from './NativeDetector/NativeDetector'; import { isComposedGesture } from './hooks/utils/relationUtils'; -import { GestureEvents } from './types'; +import { DetectorCallbacks } from './types'; export function LogicDetector( props: NativeDetectorProps @@ -12,7 +12,7 @@ export function LogicDetector( const { register, unregister } = useDetectorContext(); const viewRef = useRef(null); const [viewTag, setViewTag] = useState(-1); - const logicMethods = useRef(props.gesture.gestureEvents); + const logicMethods = useRef(props.gesture.detectorCallbacks); const handleRef = useCallback((node: any) => { viewRef.current = node; @@ -31,8 +31,8 @@ export function LogicDetector( }, []); useEffect(() => { - logicMethods.current = props.gesture.gestureEvents; - }, [props.gesture.gestureEvents]); + logicMethods.current = props.gesture.detectorCallbacks; + }, [props.gesture.detectorCallbacks]); useEffect(() => { if (viewTag === -1) { @@ -60,7 +60,7 @@ export function LogicDetector( Object.assign(logicProps, { viewRef }); } - register(logicProps, logicMethods as RefObject>); + register(logicProps, logicMethods as RefObject>); return () => { unregister(viewTag); 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 ea2f73e40b..321dfeecc1 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -5,7 +5,7 @@ import { tagMessage } from '../../utils'; import { LogicChildren, Gesture, - GestureEvents, + DetectorCallbacks, GestureHandlerEvent, } from '../types'; import { DetectorContext } from './useDetectorContext'; @@ -29,9 +29,9 @@ export function NativeDetector({ children, }: NativeDetectorProps) { const [logicChildren, setLogicChildren] = useState([]); - const logicMethods = useRef>>>( - new Map() - ); + const logicMethods = useRef< + Map>> + >(new Map()); const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents ? AnimatedNativeDetector @@ -40,7 +40,7 @@ export function NativeDetector({ : HostGestureDetector; const register = useCallback( - (child: LogicChildren, methods: RefObject>) => { + (child: LogicChildren, methods: RefObject>) => { setLogicChildren((prev) => { const index = prev.findIndex((c) => c.viewTag === child.viewTag); if (index !== -1) { @@ -74,10 +74,10 @@ export function NativeDetector({ configureRelations(gesture); - const handleGestureEvent = (key: keyof GestureEvents) => { + const handleGestureEvent = (key: keyof DetectorCallbacks) => { return (e: GestureHandlerEvent) => { - if (gesture.gestureEvents[key]) { - gesture.gestureEvents[key](e); + if (gesture.detectorCallbacks[key]) { + gesture.detectorCallbacks[key](e); } logicMethods.current.forEach((ref) => { @@ -90,12 +90,12 @@ export function NativeDetector({ }; const getHandlers = useCallback( - (key: keyof GestureEvents) => { + (key: keyof DetectorCallbacks) => { const handlers: ((e: GestureHandlerEvent) => void)[] = []; - if (gesture.gestureEvents[key]) { + if (gesture.detectorCallbacks[key]) { handlers.push( - gesture.gestureEvents[key] as ( + gesture.detectorCallbacks[key] as ( e: GestureHandlerEvent ) => void ); @@ -112,7 +112,7 @@ export function NativeDetector({ return handlers; }, - [logicChildren, gesture.gestureEvents] + [logicChildren, gesture.detectorCallbacks] ); const reanimatedEventHandler = Reanimated?.useComposedEventHandler( @@ -136,7 +136,7 @@ export function NativeDetector({ onGestureHandlerEvent={handleGestureEvent('onGestureHandlerEvent')} // @ts-ignore This is a type mismatch between RNGH types and RN Codegen types onGestureHandlerAnimatedEvent={ - gesture.gestureEvents.onGestureHandlerAnimatedEvent + gesture.detectorCallbacks.onGestureHandlerAnimatedEvent } // @ts-ignore This is a type mismatch between RNGH types and RN Codegen types onGestureHandlerTouchEvent={handleGestureEvent( diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts index 97c2c90dec..6fe54c11e4 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/useDetectorContext.ts @@ -1,10 +1,10 @@ import { createContext, RefObject, useContext } from 'react'; -import { GestureEvents, LogicChildren } from '../types'; +import { DetectorCallbacks, LogicChildren } from '../types'; type DetectorContextType = { register: ( child: LogicChildren, - methods: RefObject> + methods: RefObject> ) => void; unregister: (child: number) => void; }; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts index f942b51e28..4f2776bb32 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -46,43 +46,43 @@ export function useComposedGesture( const onGestureHandlerStateChange = (event: StateChangeEvent) => { for (const gesture of gestures) { - if (gesture.gestureEvents.onGestureHandlerStateChange) { - gesture.gestureEvents.onGestureHandlerStateChange(event); + if (gesture.detectorCallbacks.onGestureHandlerStateChange) { + gesture.detectorCallbacks.onGestureHandlerStateChange(event); } } }; const onGestureHandlerEvent = (event: UpdateEvent) => { for (const gesture of gestures) { - if (gesture.gestureEvents.onGestureHandlerEvent) { - gesture.gestureEvents.onGestureHandlerEvent(event); + if (gesture.detectorCallbacks.onGestureHandlerEvent) { + gesture.detectorCallbacks.onGestureHandlerEvent(event); } } }; const onGestureHandlerTouchEvent = (event: TouchEvent) => { for (const gesture of gestures) { - if (gesture.gestureEvents.onGestureHandlerTouchEvent) { - gesture.gestureEvents.onGestureHandlerTouchEvent(event); + if (gesture.detectorCallbacks.onGestureHandlerTouchEvent) { + gesture.detectorCallbacks.onGestureHandlerTouchEvent(event); } } }; const onReanimatedStateChange = Reanimated?.useComposedEventHandler( gestures.map( - (gesture) => gesture.gestureEvents.onReanimatedStateChange || null + (gesture) => gesture.detectorCallbacks.onReanimatedStateChange || null ) ); const onReanimatedUpdateEvent = Reanimated?.useComposedEventHandler( gestures.map( - (gesture) => gesture.gestureEvents.onReanimatedUpdateEvent || null + (gesture) => gesture.detectorCallbacks.onReanimatedUpdateEvent || null ) ); const onReanimatedTouchEvent = Reanimated?.useComposedEventHandler( gestures.map( - (gesture) => gesture.gestureEvents.onReanimatedTouchEvent || null + (gesture) => gesture.detectorCallbacks.onReanimatedTouchEvent || null ) ); @@ -90,12 +90,13 @@ export function useComposedGesture( const gesturesWithAnimatedEvent = gestures.filter( (gesture) => - gesture.gestureEvents.onGestureHandlerAnimatedEvent !== undefined + gesture.detectorCallbacks.onGestureHandlerAnimatedEvent !== undefined ); if (gesturesWithAnimatedEvent.length > 0) { onGestureHandlerAnimatedEvent = - gesturesWithAnimatedEvent[0].gestureEvents.onGestureHandlerAnimatedEvent; + gesturesWithAnimatedEvent[0].detectorCallbacks + .onGestureHandlerAnimatedEvent; if (__DEV__ && gesturesWithAnimatedEvent.length > 1) { console.warn( @@ -110,7 +111,7 @@ export function useComposedGesture( tags, type, config, - gestureEvents: { + detectorCallbacks: { onGestureHandlerStateChange, onGestureHandlerEvent, onGestureHandlerTouchEvent, diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 0d994c7a36..c98d0ba7d0 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -107,7 +107,7 @@ export function useGesture( tag, type, config, - gestureEvents: { + detectorCallbacks: { onGestureHandlerStateChange, onGestureHandlerEvent, onGestureHandlerTouchEvent, diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts deleted file mode 100644 index 67814ab4ab..0000000000 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { Animated, NativeSyntheticEvent } from 'react-native'; -import { - ActiveCursor, - GestureTouchEvent, - HitSlop, - MouseButton, - TouchAction, - UserSelect, -} from '../handlers/gestureHandlerCommon'; -import { PointerType } from '../PointerType'; - -import { State } from '../State'; -import { PanGestureNativeProperties } from './hooks/gestures/pan/PanProperties'; -import { FlingGestureNativeProperties } from './hooks/gestures/fling/FlingProperties'; -import { HoverGestureNativeProperties } from './hooks/gestures/hover/HoverProperties'; -import { LongPressGestureNativeProperties } from './hooks/gestures/longPress/LongPressProperties'; -import { NativeGestureNativeProperties } from './hooks/gestures/native/NativeProperties'; -import { TapGestureNativeConfig } from './hooks/gestures/tap/TapProperties'; - -interface EventPayload { - handlerTag: number; - state: State; -} -interface StateChangeEventPayload extends EventPayload { - oldState: State; -} - -export type BaseHandlerData = { - numberOfPointers: number; - pointerType: PointerType; -}; - -export type HandlerData = BaseHandlerData & T; - -export type GestureUpdateEvent = EventPayload & { - handlerData: HandlerData; -}; - -export type GestureStateChangeEvent = StateChangeEventPayload & { - handlerData: HandlerData; -}; - -export type GestureHandlerEvent = - | UpdateEvent - | StateChangeEvent - | TouchEvent; - -export type UnpackedGestureHandlerEvent = - | GestureUpdateEvent - | GestureStateChangeEvent - | GestureTouchEvent; - -export type UpdateEvent = - | GestureUpdateEvent - | NativeSyntheticEvent>; - -export type StateChangeEvent = - | GestureStateChangeEvent - | NativeSyntheticEvent>; - -export type TouchEvent = - | GestureTouchEvent - | NativeSyntheticEvent; - -// This is almost how Animated.event is typed in React Native. We add _argMapping in order to: -// 1. Distinguish it from a regular function, -// 2. Have access to the _argMapping property to check for usage of `change*` callbacks. -export type AnimatedEvent = ((...args: any[]) => void) & { - _argMapping: (Animated.Mapping | null)[]; -}; - -export interface LogicChildren { - viewTag: number; - handlerTags: number[]; -} - -export enum SingleGestureName { - Tap = 'TapGestureHandler', - LongPress = 'LongPressGestureHandler', - Pan = 'PanGestureHandler', - Pinch = 'PinchGestureHandler', - Rotation = 'RotationGestureHandler', - Fling = 'FlingGestureHandler', - Manual = 'ManualGestureHandler', - Native = 'NativeViewGestureHandler', - Hover = 'HoverGestureHandler', -} - -export enum ComposedGestureName { - Simultaneous = 'SimultaneousGesture', - Exclusive = 'ExclusiveGesture', - Race = 'RaceGesture', -} - -export type GestureEvents = { - onGestureHandlerStateChange: (event: StateChangeEvent) => void; - onGestureHandlerEvent: - | undefined - | ((event: UpdateEvent) => void); - onGestureHandlerTouchEvent: (event: TouchEvent) => void; - onReanimatedStateChange: - | undefined - | ((event: StateChangeEvent) => void); - onReanimatedUpdateEvent: - | undefined - | ((event: UpdateEvent) => void); - onReanimatedTouchEvent: undefined | ((event: TouchEvent) => void); - onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; -}; - -export type GestureRelations = { - simultaneousHandlers: number[]; - waitFor: number[]; - blocksHandlers: number[]; -}; - -export type SingleGesture = { - tag: number; - type: SingleGestureName; - config: BaseGestureConfig; - gestureEvents: GestureEvents; - gestureRelations: GestureRelations; -}; - -export type ComposedGesture = { - tags: number[]; - type: ComposedGestureName; - config: { - shouldUseReanimated: boolean; - dispatchesAnimatedEvents: boolean; - }; - gestureEvents: GestureEvents; - externalSimultaneousHandlers: number[]; - gestures: Gesture[]; -}; - -export type ChangeCalculatorType = ( - current: GestureUpdateEvent, - previous?: GestureUpdateEvent -) => GestureUpdateEvent; - -export type DiffCalculatorType = ( - current: HandlerData, - previous: HandlerData | null -) => Partial>; - -export type Gesture = - | SingleGesture - | ComposedGesture; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type AnyGesture = Gesture; - -interface ExternalRelations { - simultaneousWithExternalGesture?: Gesture | Gesture[]; - requireExternalGestureToFail?: Gesture | Gesture[]; - blocksExternalGesture?: Gesture | Gesture[]; -} - -export interface GestureCallbacks { - onBegin?: (event: GestureStateChangeEvent) => void; - onStart?: (event: GestureStateChangeEvent) => void; - onEnd?: ( - event: GestureStateChangeEvent, - success: boolean - ) => void; - onFinalize?: ( - event: GestureStateChangeEvent, - success: boolean - ) => void; - onUpdate?: (event: GestureUpdateEvent) => void | AnimatedEvent; - onTouchesDown?: (event: GestureTouchEvent) => void; - onTouchesMove?: (event: GestureTouchEvent) => void; - onTouchesUp?: (event: GestureTouchEvent) => void; - onTouchesCancelled?: (event: GestureTouchEvent) => void; -} - -export type InternalConfigProps = { - shouldUseReanimated?: boolean; - dispatchesAnimatedEvents?: boolean; - needsPointerData?: boolean; - changeEventCalculator?: ChangeCalculatorType; -}; - -export type CommonGestureConfig = WithSharedValue< - { - disableReanimated?: boolean; - enabled?: boolean; - shouldCancelWhenOutside?: boolean; - hitSlop?: HitSlop; - userSelect?: UserSelect; - activeCursor?: ActiveCursor; - mouseButton?: MouseButton; - enableContextMenu?: boolean; - touchAction?: TouchAction; - }, - HitSlop | UserSelect | ActiveCursor | MouseButton | TouchAction ->; - -export type BaseGestureConfig = ExternalRelations & - GestureCallbacks & - FilterNeverProperties & - InternalConfigProps & - CommonGestureConfig; - -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]; -}; - -export interface SharedValue { - value: Value; - get(): Value; - set(value: Value | ((value: Value) => Value)): void; - addListener: (listenerID: number, listener: (value: Value) => void) => void; - removeListener: (listenerID: number) => void; - modify: ( - modifier?: (value: T) => T, - forceUpdate?: boolean - ) => void; -} - -// Apply SharedValue recursively. P is used to make sure that composed types won't be expanded. -// For example, if we pass `HoverEffect` as P, then resulting type will have HoverEffect | SharedValue, -// not HoverEffect, SharedValue, ... -type WithSharedValueRecursive = { - [K in keyof T]: Exclude extends P - ? Simplify> - : // Special case for boolean as passing `boolean` as P doesn't look ok. - boolean extends T[K] - ? boolean | SharedValue - : // Special handling for tuples [number, number]. - T[K] extends [number, number] - ? [WithSharedValue, WithSharedValue] - : // Default case: apply the MaybeWithSharedValue logic recursively or as a direct SharedValue wrap. - WithSharedValue; -}; - -export type SharedValueOrT = T | SharedValue>; - -// Utility type that decides whether to recurse for objects or apply SharedValue directly. -export type WithSharedValue = T extends object - ? WithSharedValueRecursive - : Simplify>; - -// Simplifies types for end users. -// For example, changes TOrSharedValue into number | SharedValue. -type Simplify = - T extends SharedValue - ? never - : // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends SharedValue - ? T - : { - // For a generic object, retain the original structure while forcing an object type - [K in keyof T]: T[K]; - } & NonNullable; - -export type HandlersPropsWhiteList = - | Set - | Set - | Set - | Set - | Set - | Set; diff --git a/packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts b/packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts new file mode 100644 index 0000000000..88604915a9 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts @@ -0,0 +1,61 @@ +import { + ActiveCursor, + GestureTouchEvent, + HitSlop, + MouseButton, + TouchAction, + UserSelect, +} from '../../handlers/gestureHandlerCommon'; +import { + AnimatedEvent, + ChangeCalculatorType, + GestureStateChangeEvent, + GestureUpdateEvent, +} from './EventTypes'; +import { WithSharedValue } from './ReanimatedTypes'; + +export type GestureCallbacks = { + onBegin?: (event: GestureStateChangeEvent) => void; + onStart?: (event: GestureStateChangeEvent) => void; + onEnd?: ( + event: GestureStateChangeEvent, + success: boolean + ) => void; + onFinalize?: ( + event: GestureStateChangeEvent, + success: boolean + ) => void; + onUpdate?: (event: GestureUpdateEvent) => void | AnimatedEvent; + onTouchesDown?: (event: GestureTouchEvent) => void; + onTouchesMove?: (event: GestureTouchEvent) => void; + onTouchesUp?: (event: GestureTouchEvent) => void; + onTouchesCancelled?: (event: GestureTouchEvent) => void; +}; + +export type GestureRelations = { + simultaneousHandlers: number[]; + waitFor: number[]; + blocksHandlers: number[]; +}; + +export type InternalConfigProps = { + shouldUseReanimated?: boolean; + dispatchesAnimatedEvents?: boolean; + needsPointerData?: boolean; + changeEventCalculator?: ChangeCalculatorType; +}; + +export type CommonGestureConfig = WithSharedValue< + { + disableReanimated?: boolean; + enabled?: boolean; + shouldCancelWhenOutside?: boolean; + hitSlop?: HitSlop; + userSelect?: UserSelect; + activeCursor?: ActiveCursor; + mouseButton?: MouseButton; + enableContextMenu?: boolean; + touchAction?: TouchAction; + }, + HitSlop | UserSelect | ActiveCursor | MouseButton | TouchAction +>; diff --git a/packages/react-native-gesture-handler/src/v3/types/DetectorTypes.ts b/packages/react-native-gesture-handler/src/v3/types/DetectorTypes.ts new file mode 100644 index 0000000000..c9368b778b --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/DetectorTypes.ts @@ -0,0 +1,27 @@ +import { + AnimatedEvent, + StateChangeEvent, + UpdateEvent, + TouchEvent, +} from './EventTypes'; + +export type DetectorCallbacks = { + onGestureHandlerStateChange: (event: StateChangeEvent) => void; + onGestureHandlerEvent: + | undefined + | ((event: UpdateEvent) => void); + onGestureHandlerTouchEvent: (event: TouchEvent) => void; + onReanimatedStateChange: + | undefined + | ((event: StateChangeEvent) => void); + onReanimatedUpdateEvent: + | undefined + | ((event: UpdateEvent) => void); + onReanimatedTouchEvent: undefined | ((event: TouchEvent) => void); + onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; +}; + +export type LogicChildren = { + viewTag: number; + handlerTags: number[]; +}; diff --git a/packages/react-native-gesture-handler/src/v3/types/EventTypes.ts b/packages/react-native-gesture-handler/src/v3/types/EventTypes.ts new file mode 100644 index 0000000000..62e95994be --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/EventTypes.ts @@ -0,0 +1,66 @@ +import { Animated, NativeSyntheticEvent } from 'react-native'; +import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; +import { PointerType } from '../../PointerType'; +import { State } from '../../State'; + +type EventPayload = { + handlerTag: number; + state: State; +}; +type StateChangeEventPayload = EventPayload & { + oldState: State; +}; + +export type BaseHandlerData = { + numberOfPointers: number; + pointerType: PointerType; +}; + +export type HandlerData = BaseHandlerData & T; + +export type GestureUpdateEvent = EventPayload & { + handlerData: HandlerData; +}; + +export type GestureStateChangeEvent = StateChangeEventPayload & { + handlerData: HandlerData; +}; + +export type GestureHandlerEvent = + | UpdateEvent + | StateChangeEvent + | TouchEvent; + +export type UnpackedGestureHandlerEvent = + | GestureUpdateEvent + | GestureStateChangeEvent + | GestureTouchEvent; + +export type UpdateEvent = + | GestureUpdateEvent + | NativeSyntheticEvent>; + +export type StateChangeEvent = + | GestureStateChangeEvent + | NativeSyntheticEvent>; + +export type TouchEvent = + | GestureTouchEvent + | NativeSyntheticEvent; + +// This is almost how Animated.event is typed in React Native. We add _argMapping in order to: +// 1. Distinguish it from a regular function, +// 2. Have access to the _argMapping property to check for usage of `change*` callbacks. +export type AnimatedEvent = ((...args: any[]) => void) & { + _argMapping: (Animated.Mapping | null)[]; +}; + +export type ChangeCalculatorType = ( + current: GestureUpdateEvent, + previous?: GestureUpdateEvent +) => GestureUpdateEvent; + +export type DiffCalculatorType = ( + current: HandlerData, + previous: HandlerData | null +) => Partial>; diff --git a/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts b/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts new file mode 100644 index 0000000000..bce8656195 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts @@ -0,0 +1,67 @@ +import { DetectorCallbacks } from './DetectorTypes'; +import { + CommonGestureConfig, + GestureCallbacks, + GestureRelations, + InternalConfigProps, +} from './ConfigTypes'; +import { FilterNeverProperties } from './UtilityTypes'; + +// Unfortunately, this type cannot be moved into ConfigTypes.ts because of circular dependency +type ExternalRelations = { + simultaneousWithExternalGesture?: Gesture | Gesture[]; + requireExternalGestureToFail?: Gesture | Gesture[]; + blocksExternalGesture?: Gesture | Gesture[]; +}; + +// Similarly, this type cannot be moved into ConfigTypes.ts because it depends on `ExternalRelations` +export type BaseGestureConfig = ExternalRelations & + GestureCallbacks & + FilterNeverProperties & + InternalConfigProps & + CommonGestureConfig; + +export type SingleGesture = { + tag: number; + type: SingleGestureName; + config: BaseGestureConfig; + detectorCallbacks: DetectorCallbacks; + gestureRelations: GestureRelations; +}; + +export type ComposedGesture = { + tags: number[]; + type: ComposedGestureName; + config: { + shouldUseReanimated: boolean; + dispatchesAnimatedEvents: boolean; + }; + detectorCallbacks: DetectorCallbacks; + externalSimultaneousHandlers: number[]; + gestures: Gesture[]; +}; + +export type Gesture = + | SingleGesture + | ComposedGesture; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyGesture = Gesture; + +export enum SingleGestureName { + Tap = 'TapGestureHandler', + LongPress = 'LongPressGestureHandler', + Pan = 'PanGestureHandler', + Pinch = 'PinchGestureHandler', + Rotation = 'RotationGestureHandler', + Fling = 'FlingGestureHandler', + Manual = 'ManualGestureHandler', + Native = 'NativeViewGestureHandler', + Hover = 'HoverGestureHandler', +} + +export enum ComposedGestureName { + Simultaneous = 'SimultaneousGesture', + Exclusive = 'ExclusiveGesture', + Race = 'RaceGesture', +} diff --git a/packages/react-native-gesture-handler/src/v3/types/ReanimatedTypes.ts b/packages/react-native-gesture-handler/src/v3/types/ReanimatedTypes.ts new file mode 100644 index 0000000000..e8e0b3fba7 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/ReanimatedTypes.ts @@ -0,0 +1,47 @@ +export type SharedValue = { + value: Value; + get(): Value; + set(value: Value | ((value: Value) => Value)): void; + addListener: (listenerID: number, listener: (value: Value) => void) => void; + removeListener: (listenerID: number) => void; + modify: ( + modifier?: (value: T) => T, + forceUpdate?: boolean + ) => void; +}; + +export type SharedValueOrT = T | SharedValue>; + +// Utility type that decides whether to recurse for objects or apply SharedValue directly. +export type WithSharedValue = T extends object + ? WithSharedValueRecursive + : Simplify>; + +// Apply SharedValue recursively. P is used to make sure that composed types won't be expanded. +// For example, if we pass `HoverEffect` as P, then resulting type will have HoverEffect | SharedValue, +// not HoverEffect, SharedValue, ... +type WithSharedValueRecursive = { + [K in keyof T]: Exclude extends P + ? Simplify> + : // Special case for boolean as passing `boolean` as P doesn't look ok. + boolean extends T[K] + ? boolean | SharedValue + : // Special handling for tuples [number, number]. + T[K] extends [number, number] + ? [WithSharedValue, WithSharedValue] + : // Default case: apply the MaybeWithSharedValue logic recursively or as a direct SharedValue wrap. + WithSharedValue; +}; + +// Simplifies types for end users. +// For example, changes SharedValueOrT into number | SharedValue. +type Simplify = + T extends SharedValue + ? never + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends SharedValue + ? T + : { + // For a generic object, retain the original structure while forcing an object type + [K in keyof T]: T[K]; + } & NonNullable; diff --git a/packages/react-native-gesture-handler/src/v3/types/UtilityTypes.ts b/packages/react-native-gesture-handler/src/v3/types/UtilityTypes.ts new file mode 100644 index 0000000000..56c6fedaa7 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/UtilityTypes.ts @@ -0,0 +1,32 @@ +import { PanGestureNativeProperties } from '../hooks/gestures/pan/PanProperties'; +import { FlingGestureNativeProperties } from '../hooks/gestures/fling/FlingProperties'; +import { HoverGestureNativeProperties } from '../hooks/gestures/hover/HoverProperties'; +import { LongPressGestureNativeProperties } from '../hooks/gestures/longPress/LongPressProperties'; +import { NativeGestureNativeProperties } from '../hooks/gestures/native/NativeProperties'; +import { TapGestureNativeConfig } from '../hooks/gestures/tap/TapProperties'; +import { InternalConfigProps } from './ConfigTypes'; + +export type HandlersPropsWhiteList = + | Set + | Set + | Set + | Set + | Set + | Set; + +// 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. +export type FilterNeverProperties = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +}; + +export type ExcludeInternalConfigProps = Omit< + T, + keyof InternalConfigProps +>; diff --git a/packages/react-native-gesture-handler/src/v3/types/index.ts b/packages/react-native-gesture-handler/src/v3/types/index.ts new file mode 100644 index 0000000000..6fd85f24c9 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/types/index.ts @@ -0,0 +1,6 @@ +export * from './ReanimatedTypes'; +export * from './ConfigTypes'; +export * from './EventTypes'; +export * from './DetectorTypes'; +export * from './GestureTypes'; +export * from './UtilityTypes';