react-native-gesture-handler icon indicating copy to clipboard operation
react-native-gesture-handler copied to clipboard

Set cursor to the grabbing hand when gesture is active

Open EvanBacon opened this issue 6 years ago • 1 comments

😎

cursor: grab;

EvanBacon avatar Jul 24 '19 04:07 EvanBacon

Added in 2.6.1 😎

Note that this change is available in new web implementation. To enable it, call enableExperimentalWebImplementation() in the root of your project.

m-bert avatar Sep 21 '22 11:09 m-bert

Why is this desirable? I just encountered the behavior and I don't want it, and not seeing a way to turn it off.

TomSwift avatar Dec 14 '22 20:12 TomSwift

@TomSwift I agree, and I've come up with a workaround to turn it off:

  • Find the view that your GestureDetector wraps and get a ref to it.
  • In every gesture's onBegin, set viewRef.current.style.cursor = 'auto';

For example:

const viewRef = useRef<Animated.View>(null);

/* ... */

const panGesture = Gesture.Pan()
  .onBegin(() => {
    // Web fix: don't use the grabby hand when drawing.
    if (Platform.OS === 'web' && viewRef.current !== null) {
      (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
    }
  })
  /* ...more config */;

/* ... */

<GestureDetector gesture={panGesture}>
  <Animated.View ref={viewRef} /* ...more props */>

This works because they set the cursor to 'grab' right after calling activate(), and I think that's always followed by the state being transitioned to BEGIN.

Edit: hrm, actually I'm not sure if that's enough. I actually was doing something a bit more complicated:

const panGesture = Gesture.Exclusive(
  Gesture.Pan()
    .onBegin(() => {
      // Web fix: don't use the grabby hand when drawing.
      if (Platform.OS === 'web' && viewRef.current !== null) {
        (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
      }
    })
    .minDistance(0)
    /* ...more config */,
  Gesture.Tap()
    .onBegin(() => {
      // Web fix: don't use the grabby hand when drawing.
      if (Platform.OS === 'web' && viewRef.current !== null) {
        (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
      }
    })
);

This works. I seem to need the Tap - even if it does nothing at all. I also seem to need the minDistance(0), which you might not want. I'm afraid I haven't been able to distill it down to exactly the right fix to undo their change - maybe you'll have more luck.

Edit 2:

It appears that adding in the extra (unused) Gesture.Tap, at lower priority to your main gesture (via Gesture.Exclusive) seems to be the magic fix. I also needed the fix in the onUpdate part of my Gesture.Pan, but if your gesture wasn't a Gesture.Pan I'm sure you can do something similar. To be clear, I don't understand why this works.

let panGesture: GestureType | ComposedGesture = Gesture.Pan()
  .onUpdate(event => {
    // Web fix: don't use grabby hand (part 1)
    if (Platform.OS === 'web' && viewRef.current !== null) {
      (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
    }
    /* ...more code */
  })
  /* ...more config */;

// Web fix: don't use grabby hand (part 2)
if (Platform.OS === 'web') {
  panGesture = Gesture.Exclusive(
    panGesture,
    Gesture.Tap().onBegin(() => {
      if (viewRef.current !== null) (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
    })
  );
}

Edit 3:

If you are using manual activation, it's super simple: just put this code immediately after calling activate(). E.g.

const gesture = Gesture.Manual()
  .onTouchesDown((event, manager) => {
    /* ... logic */
    manager.activate();
    if (viewRef.current !== null) (viewRef.current as unknown as HTMLElement).style.cursor = 'auto';
  };

All the hacks in the rest of my comment are to do with trying to find where each gesture calls activate() under the covers.

chriscoomber avatar Feb 14 '23 23:02 chriscoomber

Hi @TomSwift and @chriscoomber! We understand that this change may not be desirable and not everyone likes the idea. With that in mind I've prepared this PR, which adds activeCursor prop. This way cursor keeps default appearance and if you would like to change it, you can use new prop to give it any css cursor value you'd like. I hope this will help!

m-bert avatar Jul 19 '23 08:07 m-bert