Skip to content

Commit 6a7a128

Browse files
authored
Add Text component (#3202)
## Description This PR adds `Text` component to **Gesture Handler**. Upon investigating #3159 we decided that it will be better to add our own `Text` component, instead of forcing users to create their own version of `Text` with `NativeViewGestureHandler`. ## Test plan <details> <summary>New example:</summary> ```jsx import { useState } from 'react'; import { StyleSheet } from 'react-native'; import { Text, GestureHandlerRootView, TouchableOpacity, } from 'react-native-gesture-handler'; export default function NestedText() { const [counter, setCounter] = useState(0); return ( <GestureHandlerRootView style={styles.container}> <Text style={{ fontSize: 30 }}>{`Counter: ${counter}`}</Text> <TouchableOpacity onPress={() => { console.log('Touchable'); setCounter((prev) => prev + 1); }}> <Text style={[styles.textCommon, styles.outerText]} onPress={() => { console.log('Outer text'); setCounter((prev) => prev + 1); }}> {'Outer Text '} <Text style={[styles.textCommon, styles.innerText]} onPress={() => { console.log('Nested text'); setCounter((prev) => prev + 1); }}> {'Nested Text'} </Text> </Text> </TouchableOpacity> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 20, }, textCommon: { padding: 10, color: 'white', }, outerText: { fontSize: 30, borderWidth: 2, backgroundColor: '#131313', }, innerText: { fontSize: 25, backgroundColor: '#F06312', }, }); ``` </details>
1 parent 466d4e5 commit 6a7a128

File tree

6 files changed

+161
-4
lines changed

6 files changed

+161
-4
lines changed

android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.view.ViewGroup
88
import android.widget.ScrollView
99
import com.facebook.react.views.scroll.ReactScrollView
1010
import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout
11+
import com.facebook.react.views.text.ReactTextView
1112
import com.facebook.react.views.textinput.ReactEditText
1213
import com.facebook.react.views.view.ReactViewGroup
1314
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
@@ -45,7 +46,11 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
4546

4647
override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean {
4748
// if the gesture is marked by user as simultaneous with other or the hook return true
48-
if (super.shouldRecognizeSimultaneously(handler) || hook.shouldRecognizeSimultaneously(handler)) {
49+
hook.shouldRecognizeSimultaneously(handler)?.let {
50+
return@shouldRecognizeSimultaneously it
51+
}
52+
53+
if (super.shouldRecognizeSimultaneously(handler)) {
4954
return true
5055
}
5156

@@ -80,6 +85,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
8085
is ReactEditText -> this.hook = EditTextHook(this, view)
8186
is ReactSwipeRefreshLayout -> this.hook = SwipeRefreshLayoutHook(this, view)
8287
is ReactScrollView -> this.hook = ScrollViewHook()
88+
is ReactTextView -> this.hook = TextViewHook()
8389
is ReactViewGroup -> this.hook = ReactViewGroupHook()
8490
}
8591
}
@@ -102,7 +108,8 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
102108
cancel()
103109
} else {
104110
hook.sendTouchEvent(view, event)
105-
if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && view.isPressed) {
111+
112+
if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && hook.canActivate(view)) {
106113
activate()
107114
}
108115

@@ -172,6 +179,11 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
172179
*/
173180
fun canBegin(event: MotionEvent) = true
174181

182+
/**
183+
* Checks whether handler can activate. Used by TextViewHook.
184+
*/
185+
fun canActivate(view: View) = view.isPressed
186+
175187
/**
176188
* Called after the gesture transitions to the END state.
177189
*/
@@ -181,7 +193,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
181193
* @return Boolean value signalling whether the gesture can be recognized simultaneously with
182194
* other (handler). Returning false doesn't necessarily prevent it from happening.
183195
*/
184-
fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) = false
196+
fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean? = null
185197

186198
/**
187199
* shouldActivateOnStart and tryIntercept have priority over this method
@@ -208,6 +220,14 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
208220
fun sendTouchEvent(view: View?, event: MotionEvent) = view?.onTouchEvent(event)
209221
}
210222

223+
private class TextViewHook() : NativeViewGestureHandlerHook {
224+
override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) = false
225+
226+
// We have to explicitly check for ReactTextView, since its `isPressed` flag is not set to `true`,
227+
// in contrast to e.g. Touchable
228+
override fun canActivate(view: View) = view is ReactTextView
229+
}
230+
211231
private class EditTextHook(
212232
private val handler: NativeViewGestureHandler,
213233
private val editText: ReactEditText

example/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import PointerType from './src/release_tests/pointerType';
4040
import SwipeableReanimation from './src/release_tests/swipeableReanimation';
4141
import NestedGestureHandlerRootViewWithModal from './src/release_tests/nestedGHRootViewWithModal';
4242
import TwoFingerPan from './src/release_tests/twoFingerPan';
43+
import NestedText from './src/release_tests/nestedText';
4344
import { PinchableBox } from './src/recipes/scaleAndRotate';
4445
import PanAndScroll from './src/recipes/panAndScroll';
4546
import { BottomSheet } from './src/showcase/bottomSheet';
@@ -217,6 +218,11 @@ const EXAMPLES: ExamplesSection[] = [
217218
component: TwoFingerPan,
218219
unsupportedPlatforms: new Set(['android', 'macos']),
219220
},
221+
{
222+
name: 'Nested Text',
223+
component: NestedText,
224+
unsupportedPlatforms: new Set(['macos']),
225+
},
220226
],
221227
},
222228
{
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useState } from 'react';
2+
import { StyleSheet } from 'react-native';
3+
import {
4+
Text,
5+
GestureHandlerRootView,
6+
TouchableOpacity,
7+
} from 'react-native-gesture-handler';
8+
9+
export default function NestedText() {
10+
const [counter, setCounter] = useState(0);
11+
12+
return (
13+
<GestureHandlerRootView style={styles.container}>
14+
<Text style={{ fontSize: 30 }}>{`Counter: ${counter}`}</Text>
15+
16+
<TouchableOpacity
17+
onPress={() => {
18+
console.log('Touchable');
19+
setCounter((prev) => prev + 1);
20+
}}>
21+
<Text
22+
style={[styles.textCommon, styles.outerText]}
23+
onPress={() => {
24+
console.log('Outer text');
25+
setCounter((prev) => prev + 1);
26+
}}>
27+
{'Outer Text '}
28+
<Text
29+
style={[styles.textCommon, styles.innerText]}
30+
onPress={() => {
31+
console.log('Nested text');
32+
setCounter((prev) => prev + 1);
33+
}}>
34+
{'Nested Text'}
35+
</Text>
36+
</Text>
37+
</TouchableOpacity>
38+
</GestureHandlerRootView>
39+
);
40+
}
41+
42+
const styles = StyleSheet.create({
43+
container: {
44+
flex: 1,
45+
alignItems: 'center',
46+
justifyContent: 'center',
47+
48+
gap: 20,
49+
},
50+
51+
textCommon: {
52+
padding: 10,
53+
color: 'white',
54+
},
55+
56+
outerText: {
57+
fontSize: 30,
58+
borderWidth: 2,
59+
backgroundColor: '#131313',
60+
},
61+
62+
innerText: {
63+
fontSize: 25,
64+
backgroundColor: '#F06312',
65+
},
66+
});

src/components/Text.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, {
2+
ForwardedRef,
3+
forwardRef,
4+
RefObject,
5+
useEffect,
6+
useRef,
7+
} from 'react';
8+
import {
9+
Platform,
10+
Text as RNText,
11+
TextProps as RNTextProps,
12+
} from 'react-native';
13+
14+
import { Gesture, GestureDetector } from '../';
15+
16+
export const Text = forwardRef(
17+
(props: RNTextProps, ref: ForwardedRef<RNText>) => {
18+
const { onPress, ...rest } = props;
19+
const textRef = useRef<RNText | null>(null);
20+
const native = Gesture.Native().runOnJS(true);
21+
22+
const refHandler = (node: any) => {
23+
textRef.current = node;
24+
25+
if (ref === null) {
26+
return;
27+
}
28+
29+
if (typeof ref === 'function') {
30+
ref(node);
31+
} else {
32+
ref.current = node;
33+
}
34+
};
35+
36+
useEffect(() => {
37+
if (Platform.OS !== 'web') {
38+
return;
39+
}
40+
41+
const textElement = ref
42+
? (ref as RefObject<RNText>).current
43+
: textRef.current;
44+
45+
// At this point we are sure that textElement is div in HTML tree
46+
(textElement as unknown as HTMLDivElement)?.setAttribute(
47+
'rnghtext',
48+
'true'
49+
);
50+
}, []);
51+
52+
return (
53+
<GestureDetector gesture={native}>
54+
<RNText onPress={onPress} ref={refHandler} {...rest} />
55+
</GestureDetector>
56+
);
57+
}
58+
);
59+
// eslint-disable-next-line @typescript-eslint/no-redeclare
60+
export type Text = typeof Text & RNText;

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export {
102102
FlatList,
103103
RefreshControl,
104104
} from './components/GestureComponents';
105+
export { Text } from './components/Text';
105106
export { HoverEffect } from './handlers/gestures/hoverGesture';
106107
export type {
107108
// Events

src/web/handlers/NativeViewGestureHandler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ export default class NativeViewGestureHandler extends GestureHandler {
7979
}
8080

8181
this.begin();
82-
if (this.buttonRole) {
82+
83+
const view = this.delegate.getView() as HTMLElement;
84+
const isRNGHText = view.hasAttribute('rnghtext');
85+
86+
if (this.buttonRole || isRNGHText) {
8387
this.activate();
8488
}
8589
}

0 commit comments

Comments
 (0)