Skip to content

Conversation

@m-bert
Copy link
Contributor

@m-bert m-bert commented Feb 3, 2026

Description

Though we allowed HitSlop to be SharedValue in gesture config, we excluded it from being split. Therefore if someone did something like:

const sv = useSharedValue(10);

const pan = usePanGesture({
  hitSlop: sv;
})

this wouldn't work as SharedValue<number> couldn't be assigned to SharedValue<HitSlop>.

This PR changes behavior of SharedValueOrT<T> type, so that it uses Distributive Conditional Types to split union into separate types and then apply SharedValue.

Test plan

yarn ts-check and yarn lint-js

Tested on the following code:
import { useSharedValue } from 'react-native-reanimated';

import {
  ActiveCursor,
  MouseButton,
  useHoverGesture,
  usePanGesture,
  HoverEffect,
} from 'react-native-gesture-handler';

export function App() {
  const runOnJS_SV = useSharedValue(true);
  const enabled_SV = useSharedValue(false);
  const shouldCancelWhenOutside_SV = useSharedValue(true);
  const hitSlop_SV = useSharedValue(10);
  const activeCursor_SV = useSharedValue<ActiveCursor>('pointer');
  const mouseButton_SV = useSharedValue(MouseButton.LEFT);
  const cancelsTouchesInView_SV = useSharedValue(true);
  const manualActivation_SV = useSharedValue(false);

  const minDistance_SV = useSharedValue(5);
  const offsetStart_SV = useSharedValue(0);
  const hoverEffect_SV = useSharedValue(HoverEffect.LIFT);

  const pan1 = usePanGesture({
    runOnJS: false,
    enabled: true,
    shouldCancelWhenOutside: true,
    hitSlop: 10,
    activeCursor: 'pointer',
    mouseButton: MouseButton.LEFT,
    cancelsTouchesInView: true,
    manualActivation: false,

    minDistance: 20,
    activeOffsetX: [10, 20],
  });

  const pan2 = usePanGesture({
    runOnJS: runOnJS_SV,
    enabled: enabled_SV,
    shouldCancelWhenOutside: shouldCancelWhenOutside_SV,
    hitSlop: hitSlop_SV,
    activeCursor: activeCursor_SV,
    mouseButton: mouseButton_SV,
    cancelsTouchesInView: cancelsTouchesInView_SV,
    manualActivation: manualActivation_SV,

    minDistance: minDistance_SV,
    activeOffsetX: [offsetStart_SV, 20],
  });

  const hover1 = useHoverGesture({
    effect: HoverEffect.LIFT,
  });

  const hover2 = useHoverGesture({
    effect: hoverEffect_SV,
  });

  console.log(pan1, pan2, hover1, hover2);
}

Copilot AI review requested due to automatic review settings February 3, 2026 10:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the typing of HitSlop in the v3 gesture config so that SharedValue can be used with HitSlop (e.g. useSharedValue(10)) without type errors and tightens the typing around config value unwrapping.

Changes:

  • Refactors SharedValueOrT/WithSharedValue to use distributive conditional types and a P parameter to selectively prevent union splitting, then removes HitSlop from the non-splittable set, allowing SharedValue<number> etc. to be assignable where HitSlop is expected.
  • Narrows maybeUnpackValue’s input type using SharedValueOrT<T, boolean> | undefined and updates call sites to specify the expected value type (e.g. <number>, <boolean>), improving type inference and safety when unwrapping config values.
  • Exposes the ActiveCursor type from the package root index for consumers using the new v3 gesture config APIs.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/react-native-gesture-handler/src/v3/types/ReanimatedTypes.ts Introduces the new SharedValueOrT<T, P> implementation and wires it into WithSharedValue/WithSharedValueRecursive to support union splitting with an escape hatch via P.
packages/react-native-gesture-handler/src/v3/types/ConfigTypes.ts Updates CommonGestureConfig’s WithSharedValue parameter to only treat ActiveCursor and MouseButton as non-splittable, so HitSlop now benefits from the new union-splitting behavior.
packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts Refines maybeUnpackValue’s signature to use SharedValueOrT<T, boolean> and return T, ensuring correct handling of boolean config flags and shared values.
packages/react-native-gesture-handler/src/v3/hooks/gestures/pan/usePanGesture.ts Adds explicit type parameters to maybeUnpackValue calls for pan offsets, ensuring numbers are correctly inferred when validating and transforming offset props.
packages/react-native-gesture-handler/src/jestUtils/jestUtils.ts Adjusts use of maybeUnpackValue for enabled in test utilities to specify <boolean>, aligning with the new generic signature.
packages/react-native-gesture-handler/src/index.ts Re-exports the ActiveCursor type from gestureHandlerCommon, making it available to library consumers at the package root.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@tjzel tjzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Complicated, but LGTM

@m-bert m-bert merged commit 8de3322 into next Feb 4, 2026
2 checks passed
@m-bert m-bert deleted the @mbert/hitslop-type branch February 4, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants