Skip to content
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

Add Text component #3202

Merged
merged 36 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c75899e
Add Text component
m-bert Nov 8, 2024
5109ee6
Add TextHook
m-bert Nov 8, 2024
3887041
simplify hook
m-bert Nov 8, 2024
c9b00a2
Fix Text activation
m-bert Nov 8, 2024
6665ddb
Add eslint no-redeclare
m-bert Nov 8, 2024
0a0da87
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 8, 2024
1852e67
Change early return
m-bert Nov 8, 2024
5697078
Change to TextViewHook
m-bert Nov 8, 2024
45fa273
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 13, 2024
9d02ed8
Move to separate file
m-bert Nov 13, 2024
a5ef09f
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 15, 2024
6b80044
Fix Text on web
m-bert Nov 15, 2024
03f6289
Remove disallowInterruption flag
m-bert Nov 15, 2024
8a0e06f
Switch to new API
m-bert Nov 18, 2024
7470f98
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 21, 2024
414e507
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 21, 2024
5a4bc5f
Add Text on iOS
m-bert Nov 21, 2024
0447e40
onPress
m-bert Nov 21, 2024
8f1eb1d
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 21, 2024
7e66b08
Macos
m-bert Nov 21, 2024
2d90e76
Add example
m-bert Nov 21, 2024
6477492
Fix example
m-bert Nov 21, 2024
7ae6e39
Remove newline
m-bert Nov 21, 2024
185b92d
Merge branch 'main' into @mbert/add-GH-Text
m-bert Nov 28, 2024
31aba7a
Move to lowercased convention
m-bert Nov 28, 2024
40e5322
Add hook method
m-bert Nov 28, 2024
0ade4c0
Change ref to refHandler
m-bert Nov 28, 2024
dd02ac1
Extract check to method
m-bert Nov 28, 2024
9c4cea4
Merge branch 'main' into @mbert/add-GH-Text
m-bert Dec 2, 2024
280d62e
Remove explicit children
m-bert Dec 4, 2024
da90c04
Merge branch 'main' into @mbert/add-GH-Text
m-bert Dec 4, 2024
9ada0d6
Mergele main
m-bert Dec 5, 2024
df1c2aa
Change hook
m-bert Dec 6, 2024
dbce3f8
Use cast
m-bert Dec 6, 2024
65ea0f9
Remove text from recognizer
m-bert Dec 6, 2024
794ef90
Fix macos build
m-bert Dec 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ScrollView
import com.facebook.react.views.scroll.ReactScrollView
import com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout
import com.facebook.react.views.text.ReactTextView
import com.facebook.react.views.textinput.ReactEditText
import com.facebook.react.views.view.ReactViewGroup
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
Expand Down Expand Up @@ -45,7 +46,11 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {

override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean {
// if the gesture is marked by user as simultaneous with other or the hook return true
if (super.shouldRecognizeSimultaneously(handler) || hook.shouldRecognizeSimultaneously(handler)) {
hook.shouldRecognizeSimultaneously(handler)?.let {
return@shouldRecognizeSimultaneously it
}
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved

if (super.shouldRecognizeSimultaneously(handler)) {
return true
}

Expand Down Expand Up @@ -80,6 +85,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
is ReactEditText -> this.hook = EditTextHook(this, view)
is ReactSwipeRefreshLayout -> this.hook = SwipeRefreshLayoutHook(this, view)
is ReactScrollView -> this.hook = ScrollViewHook()
is ReactTextView -> this.hook = TextViewHook()
is ReactViewGroup -> this.hook = ReactViewGroupHook()
}
}
Expand All @@ -102,7 +108,8 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
cancel()
} else {
hook.sendTouchEvent(view, event)
if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && view.isPressed) {

if ((state == STATE_UNDETERMINED || state == STATE_BEGAN) && hook.canActivate(view)) {
activate()
}

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

/**
* Checks whether handler can activate. Used by TextViewHook.
*/
fun canActivate(view: View) = true
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved

/**
* Called after the gesture transitions to the END state.
*/
Expand All @@ -181,7 +193,7 @@ class NativeViewGestureHandler : GestureHandler<NativeViewGestureHandler>() {
* @return Boolean value signalling whether the gesture can be recognized simultaneously with
* other (handler). Returning false doesn't necessarily prevent it from happening.
*/
fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) = false
fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean? = null

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

private class TextViewHook() : NativeViewGestureHandlerHook {
override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>) = false
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved

// We have to explicitly check for ReactTextView, since its `isPressed` flag is not set to `true`,
// in contrast to e.g. Touchable
override fun canActivate(view: View) = view.isPressed || view is ReactTextView
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
}

private class EditTextHook(
private val handler: NativeViewGestureHandler,
private val editText: ReactEditText
Expand Down
21 changes: 19 additions & 2 deletions apple/Handlers/RNNativeViewHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
#import <React/UIView+React.h>

#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTParagraphComponentView.h>
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
#import <React/RCTScrollViewComponentView.h>
#else
#import <React/RCTScrollView.h>
#import <React/RCTTextView.h>
#endif // RCT_NEW_ARCH_ENABLED

#pragma mark RNDummyGestureRecognizer
Expand All @@ -35,11 +37,24 @@ - (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler
return self;
}

- (BOOL)isHandlerAttachedToTextView
{
#if RCT_NEW_ARCH_ENABLED
return [self.view.superview isKindOfClass:[RCTParagraphComponentView class]];
#else
return [self.view isKindOfClass:[RCTTextView class]];
#endif
}

#if !TARGET_OS_OSX
- (void)touchesBegan:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler setCurrentPointerType:event];
[_gestureHandler.pointerTracker touchesBegan:touches withEvent:event];

if ([self isHandlerAttachedToTextView]) {
self.state = UIGestureRecognizerStatePossible;
}
}

- (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
Expand All @@ -51,7 +66,8 @@ - (void)touchesMoved:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet<RNGHUITouch *> *)touches withEvent:(UIEvent *)event
{
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;

self.state = [self isHandlerAttachedToTextView] ? UIGestureRecognizerStateEnded : UIGestureRecognizerStateFailed;

// For now, we are handling only the scroll view case.
// If more views need special treatment, then we can switch to a delegate pattern
Expand All @@ -72,7 +88,8 @@ - (void)mouseDown:(NSEvent *)event
{
[_gestureHandler setCurrentPointerTypeToMouse];

self.state = NSGestureRecognizerStateBegan;
self.state = [self isHandlerAttachedToTextView] ? NSGestureRecognizerStatePossible : NSGestureRecognizerStateBegan;

[_gestureHandler.pointerTracker touchesBegan:[NSSet setWithObject:event] withEvent:event];
}

Expand Down
6 changes: 6 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import SwipeableReanimation from './src/release_tests/swipeableReanimation';
import NestedGestureHandlerRootViewWithModal from './src/release_tests/nestedGHRootViewWithModal';
import TwoFingerPan from './src/release_tests/twoFingerPan';
import NestedText from './src/release_tests/nestedText';
import { PinchableBox } from './src/recipes/scaleAndRotate';
import PanAndScroll from './src/recipes/panAndScroll';
import { BottomSheet } from './src/showcase/bottomSheet';
Expand Down Expand Up @@ -217,6 +218,11 @@
component: TwoFingerPan,
unsupportedPlatforms: new Set(['android', 'macos']),
},
{
name: 'Nested Text',
component: NestedText,
unsupportedPlatforms: new Set(['macos']),
},
],
},
{
Expand Down Expand Up @@ -321,7 +327,7 @@
renderSectionHeader={({ section: { sectionTitle } }) => (
<Text style={styles.sectionTitle}>{sectionTitle}</Text>
)}
ItemSeparatorComponent={() => <View style={styles.separator} />}

Check warning on line 330 in example/App.tsx

View workflow job for this annotation

GitHub Actions / check (example)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MainScreen” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
/>
</SafeAreaView>
);
Expand Down
66 changes: 66 additions & 0 deletions example/src/release_tests/nestedText/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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',
},
});
57 changes: 57 additions & 0 deletions src/components/Text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, {
ForwardedRef,
forwardRef,
RefObject,
useEffect,
useRef,
} from 'react';
import {
Platform,
Text as RNText,
TextProps as RNTextProps,
} from 'react-native';

import { Gesture, GestureDetector } from '../';

export const Text = forwardRef(
(props: RNTextProps, ref: ForwardedRef<RNText>) => {
const { onPress, ...rest } = props;
const textRef = useRef<RNText | null>(null);
const native = Gesture.Native().runOnJS(true);

const refHandler = (node: any) => {
textRef.current = node;

if (ref === null) {
return;
}

if (typeof ref === 'function') {
ref(node);
} else {
ref.current = node;
}
};

useEffect(() => {
if (Platform.OS !== 'web') {
return;
}

const textElement = ref
? (ref as RefObject<RNText>).current
: textRef.current;

// @ts-ignore at this point we are sure that text is div
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
textElement?.setAttribute('rnghtext', 'true');
}, []);

return (
<GestureDetector gesture={native}>
<RNText onPress={onPress} ref={refHandler} {...rest} />
</GestureDetector>
);
}
);
// eslint-disable-next-line @typescript-eslint/no-redeclare
export type Text = typeof Text & RNText;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export {
FlatList,
RefreshControl,
} from './components/GestureComponents';
export { Text } from './components/Text';
export { HoverEffect } from './handlers/gestures/hoverGesture';
export type {
// Events
Expand Down
6 changes: 5 additions & 1 deletion src/web/handlers/NativeViewGestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ export default class NativeViewGestureHandler extends GestureHandler {
}

this.begin();
if (this.buttonRole) {

const view = this.delegate.getView() as HTMLElement;
const isRNGHText = view.hasAttribute('rnghtext');
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved

if (this.buttonRole || isRNGHText) {
this.activate();
}
}
Expand Down
Loading