Skip to content

[Web] Change shouldBeCancelledByOther to respect blocksExternalGesture #3429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 17, 2025

Conversation

m-bert
Copy link
Contributor

@m-bert m-bert commented Mar 3, 2025

Description

While checking possibilities of blocking FlatList with gestures using blocksExternalHandler, I've found out that even though FlatList doesn't scroll, Tap fails. Turns out that shouldBeCancelledByOther method doesn't respect blocking relation. I think that if handler explicitly blocks other handler, it shouldn't be possible for it to be blocked by handler that it should block.

Test plan

Tested on the following code:
import React from 'react';
import { StyleSheet, Text, View, FlatList, ScrollView } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const data = [
  { id: '1', title: 'Item 1' },
  { id: '2', title: 'Item 2' },
  { id: '3', title: 'Item 3' },
  { id: '4', title: 'Item 4' },
  { id: '5', title: 'Item 5' },
  { id: '6', title: 'Item 6' },
  { id: '7', title: 'Item 7' },
  { id: '8', title: 'Item 8' },
  { id: '9', title: 'Item 9' },
  { id: '10', title: 'Item 10' },
  { id: '11', title: 'Item 11' },
  { id: '12', title: 'Item 12' },
  { id: '13', title: 'Item 13' },
  { id: '14', title: 'Item 14' },
  { id: '15', title: 'Item 15' },
  { id: '16', title: 'Item 16' },
];

const SimpleFlatList = () => {
  const native = Gesture.Native();

  const renderItem = ({ item }) => {
    const g = Gesture.Tap()
      .onEnd(() => {
        console.log(`Tapped ${item.id}`);
      })
      .onFinalize((e, s) => console.log(e, s))
      .maxDistance(1000)
      .maxDuration(1000)
      .shouldCancelWhenOutside(false)
      .blocksExternalGesture(native);

    return (
      <GestureDetector gesture={g}>
        <View style={styles.itemContainer}>
          <Text style={styles.itemText}>{item.title}</Text>
        </View>
      </GestureDetector>
    );
  };

  const CustomScrollComponent = React.forwardRef((props, ref) => {
    return (
      <GestureDetector gesture={native}>
        <ScrollView {...props} ref={ref} />
      </GestureDetector>
    );
  });

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      renderScrollComponent={(props) => <CustomScrollComponent {...props} />}
      keyExtractor={(item) => item.id}
    />
  );
};

export default function EmptyExample() {
  return (
    <View style={styles.container}>
      <SimpleFlatList />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },

  itemContainer: {
    padding: 10,
    marginVertical: 8,
    backgroundColor: '#f9c2ff',
  },
  itemText: {
    fontSize: 18,
  },
});

@m-bert m-bert requested review from j-piasecki and latekvo March 3, 2025 10:43
Comment on lines 114 to 120
if (
this.blocksHandlersRelations
.get(handler.handlerTag)
?.includes(otherHandler.handlerTag)
) {
return false;
}
Copy link
Member

Choose a reason for hiding this comment

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

Does this allow for the situation where both of the gestures are active?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, depends how do you understand active. If by having state set to 4 then yes, it is possible due to current Gesture Handler implementation.

But looking at the example code, NativeViewGestureHandler won't send event in active state since tryActivate for this handler will be called only once and it will be marked as awaitingHandler

Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to do the reverse? I.e. when a handler tries to activate, check if there's already an active one that is blocking the current one. If so, cancel/fail instead of activating.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I recall correctly there were some complications with this approach, but I'll double-check that

@m-bert
Copy link
Contributor Author

m-bert commented Mar 14, 2025

I've changed the approach and when checking isActive now I use handler.active instead of comparing to STATE.ACTIVE, as the state can be set to 4, but handler may be awaiting.

I've also checked when these changes were introduced and tracked it down to #2788. I've checked the example and ReanimatedSwipeable is still scrollable, so it should be fine.

@m-bert m-bert requested a review from j-piasecki March 14, 2025 11:21
@m-bert m-bert merged commit c80aee4 into main Mar 17, 2025
1 check passed
@m-bert m-bert deleted the @mbert/web-blocks-external-gesture branch March 17, 2025 11:38
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.

2 participants