Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 33 additions & 36 deletions packages/react-native/Libraries/Text/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ import flattenStyle from '../StyleSheet/flattenStyle';
import processColor from '../StyleSheet/processColor';
import Platform from '../Utilities/Platform';
import TextAncestorContext from './TextAncestorContext';
import {NativeText, NativeVirtualText} from './TextNativeComponent';
import {
NativeSelectableText,
NativeText,
NativeVirtualText,
} from './TextNativeComponent';
import * as React from 'react';
import {useContext, useMemo, useState} from 'react';

export type {TextProps} from './TextProps';

type TextForwardRef = React.ElementRef<
typeof NativeText | typeof NativeVirtualText,
typeof NativeText | typeof NativeVirtualText | typeof NativeSelectableText,
>;

/**
Expand Down Expand Up @@ -263,7 +267,7 @@ const TextImpl: component(
processedProps.children = children;
if (isPressable) {
return (
<NativePressableVirtualText
<PressableVirtualText
ref={forwardedRef}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
Expand All @@ -283,14 +287,20 @@ const TextImpl: component(

if (isPressable) {
nativeText = (
<NativePressableText
<PressableText
ref={forwardedRef}
selectable={_selectable}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
/>
);
} else {
nativeText = <NativeText {...processedProps} ref={forwardedRef} />;
nativeText =
_selectable === true ? (
<NativeSelectableText {...processedProps} ref={forwardedRef} />
) : (
<NativeText {...processedProps} ref={forwardedRef} />
);
}

if (children == null) {
Expand Down Expand Up @@ -457,28 +467,17 @@ function useTextPressability({
);
}

type NativePressableTextProps = Readonly<{
textProps: NativeTextProps,
textPressabilityProps: TextPressabilityProps,
}>;

/**
* Wrap the NativeVirtualText component and initialize pressability.
*
* This logic is split out from the main Text component to enable the more
* expensive pressability logic to be only initialized when needed.
*/
const NativePressableVirtualText: component(
ref: React.RefSetter<TextForwardRef>,
...props: NativePressableTextProps
) = ({
ref: forwardedRef,
textProps,
textPressabilityProps,
}: {
component PressableVirtualText(
ref?: React.RefSetter<TextForwardRef>,
...NativePressableTextProps,
}) => {
textProps: NativeTextProps,
textPressabilityProps: TextPressabilityProps,
) {
const [isHighlighted, eventHandlersForText] = useTextPressability(
textPressabilityProps,
);
Expand All @@ -489,42 +488,40 @@ const NativePressableVirtualText: component(
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
ref={forwardedRef}
ref={ref}
/>
);
};
}

/**
* Wrap the NativeText component and initialize pressability.
* Wrap a NativeText component and initialize pressability.
*
* This logic is split out from the main Text component to enable the more
* expensive pressability logic to be only initialized when needed.
*/
const NativePressableText: component(
ref: React.RefSetter<TextForwardRef>,
...props: NativePressableTextProps
) = ({
ref: forwardedRef,
textProps,
textPressabilityProps,
}: {
component PressableText(
ref?: React.RefSetter<TextForwardRef>,
...NativePressableTextProps,
}) => {
selectable?: ?boolean,
textProps: NativeTextProps,
textPressabilityProps: TextPressabilityProps,
) {
const [isHighlighted, eventHandlersForText] = useTextPressability(
textPressabilityProps,
);

const NativeComponent =
selectable === true ? NativeSelectableText : NativeText;

return (
<NativeText
<NativeComponent
{...textProps}
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
ref={forwardedRef}
ref={ref}
/>
);
};
}

const userSelectToSelectableMap = {
auto: true,
Expand Down
13 changes: 13 additions & 0 deletions packages/react-native/Libraries/Text/TextNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {ProcessedColorValue} from '../StyleSheet/processColor';
import type {GestureResponderEvent} from '../Types/CoreEventTypes';
import type {TextProps} from './TextProps';

import {enablePreparedTextLayout} from '../../src/private/featureflags/ReactNativeFeatureFlags';
import {createViewConfig} from '../NativeComponent/ViewConfig';
import UIManager from '../ReactNative/UIManager';
import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass';
Expand Down Expand Up @@ -90,3 +91,15 @@ export const NativeVirtualText: HostComponent<NativeTextProps> =
* https://fburl.com/workplace/6291gfvu */
createViewConfig(virtualTextViewConfig),
): any);

export const NativeSelectableText: HostComponent<NativeTextProps> =
enablePreparedTextLayout()
? (createReactNativeComponentClass('RCTSelectableText', () =>
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
createViewConfig({
...textViewConfig,
uiViewClassName: 'RCTSelectableText',
}),
): any)
: NativeText;
10 changes: 9 additions & 1 deletion packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -6112,7 +6112,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
public fun updateView ()V
}

public final class com/facebook/react/views/text/ReactTextViewManager : com/facebook/react/uimanager/BaseViewManager, com/facebook/react/uimanager/IViewManagerWithChildren, com/facebook/react/views/text/ReactTextViewManagerCallback {
public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/react/uimanager/BaseViewManager, com/facebook/react/uimanager/IViewManagerWithChildren, com/facebook/react/views/text/ReactTextViewManagerCallback {
public static final field Companion Lcom/facebook/react/views/text/ReactTextViewManager$Companion;
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
Expand All @@ -6125,11 +6125,14 @@ public final class com/facebook/react/views/text/ReactTextViewManager : com/face
public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/text/ReactTextView;
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
public fun getName ()Ljava/lang/String;
protected final fun getReactTextViewManagerCallback ()Lcom/facebook/react/views/text/ReactTextViewManagerCallback;
public fun getShadowNodeClass ()Ljava/lang/Class;
public fun needsCustomLayoutForChildren ()Z
public synthetic fun onAfterUpdateTransaction (Landroid/view/View;)V
protected fun onAfterUpdateTransaction (Lcom/facebook/react/views/text/ReactTextView;)V
public fun onPostProcessSpannable (Landroid/text/Spannable;)V
public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/text/ReactTextView;)Lcom/facebook/react/views/text/ReactTextView;
public final fun setAccessible (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setAdjustFontSizeToFit (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setAndroidHyphenationFrequency (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
Expand All @@ -6147,6 +6150,7 @@ public final class com/facebook/react/views/text/ReactTextViewManager : com/face
public final fun setOverflow (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
public synthetic fun setPadding (Landroid/view/View;IIII)V
public fun setPadding (Lcom/facebook/react/views/text/ReactTextView;IIII)V
protected final fun setReactTextViewManagerCallback (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
public final fun setSelectable (Lcom/facebook/react/views/text/ReactTextView;Z)V
public final fun setSelectionColor (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/Integer;)V
public final fun setTextAlignVertical (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
Expand All @@ -6155,6 +6159,7 @@ public final class com/facebook/react/views/text/ReactTextViewManager : com/face
public synthetic fun updateState (Landroid/view/View;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object;
public fun updateState (Lcom/facebook/react/views/text/ReactTextView;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object;
public synthetic fun updateViewAccessibility (Landroid/view/View;)V
protected fun updateViewAccessibility (Lcom/facebook/react/views/text/ReactTextView;)V
}

public final class com/facebook/react/views/text/ReactTextViewManager$Companion {
Expand All @@ -6172,6 +6177,9 @@ public final class com/facebook/react/views/text/ReactTypefaceUtils {
public static final fun parseFontWeight (Ljava/lang/String;)I
}

public final class com/facebook/react/views/text/SelectableTextViewManager$Companion {
}

public final class com/facebook/react/views/text/TextAttributeProps {
public static final field Companion Lcom/facebook/react/views/text/TextAttributeProps$Companion;
public static final field TA_KEY_ACCESSIBILITY_ROLE I
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal object FabricNameComponentMapping {
"Slider" to "RCTSlider",
"ModalHostView" to "RCTModalHostView",
"Paragraph" to "RCTText",
"SelectableParagraph" to "RCTSelectableText",
"Text" to "RCTText",
"RawText" to "RCTRawText",
"ActivityIndicatorView" to "AndroidProgressBar",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.facebook.react.bridge.ModuleSpec
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.common.ClassFinder
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.module.annotations.ReactModuleList
Expand Down Expand Up @@ -58,6 +59,7 @@ import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager
import com.facebook.react.views.switchview.ReactSwitchManager
import com.facebook.react.views.text.PreparedLayoutTextViewManager
import com.facebook.react.views.text.ReactTextViewManager
import com.facebook.react.views.text.SelectableTextViewManager
import com.facebook.react.views.textinput.ReactTextInputManager
import com.facebook.react.views.unimplementedview.ReactUnimplementedViewManager
import com.facebook.react.views.view.ReactViewManager
Expand Down Expand Up @@ -96,6 +98,7 @@ import com.facebook.react.views.view.ReactViewManager
WebSocketModule::class,
]
)
@OptIn(UnstableReactNativeAPI::class)
public class MainReactPackage
@JvmOverloads
constructor(private val config: MainPackageConfig? = null) :
Expand Down Expand Up @@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand Down Expand Up @@ -192,6 +196,8 @@ constructor(private val config: MainPackageConfig? = null) :
PreparedLayoutTextViewManager()
else ReactTextViewManager()
},
SelectableTextViewManager.REACT_CLASS to
ModuleSpec.viewManagerSpec { SelectableTextViewManager() },
ReactViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactViewManager() },
ReactUnimplementedViewManager.REACT_CLASS to
ModuleSpec.viewManagerSpec { ReactUnimplementedViewManager() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ internal class PreparedLayoutTextViewManager :

@ReactProp(name = "selectable", defaultBoolean = false)
fun setSelectable(view: PreparedLayoutTextView, isSelectable: Boolean): Unit {
// T222052152: Implement fine-grained text selection for PreparedLayoutTextView
// view.setTextIsSelectable(isSelectable);
check(!isSelectable) {
"selectable Text should use SelectableTextViewManager instead of PreparedLayoutViewManager"
}
}

@ReactProp(name = "selectionColor", customType = "Color")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.internal.SystraceSection;
import com.facebook.react.uimanager.BackgroundStyleApplicator;
Expand All @@ -52,6 +53,7 @@
import com.facebook.react.uimanager.style.BorderStyle;
import com.facebook.react.uimanager.style.LogicalEdge;
import com.facebook.react.uimanager.style.Overflow;
import com.facebook.react.views.text.internal.span.ReactFragmentIndexSpan;
import com.facebook.react.views.text.internal.span.ReactTagSpan;
import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan;
import com.facebook.yoga.YogaMeasureMode;
Expand All @@ -77,6 +79,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
private Overflow mOverflow = Overflow.VISIBLE;

private @Nullable Spannable mSpanned;
private @Nullable PreparedLayout mPreparedLayout;

public ReactTextView(Context context) {
super(context);
Expand All @@ -100,6 +103,7 @@ private void initView() {
mLetterSpacing = 0.f;
mOverflow = Overflow.VISIBLE;
mSpanned = null;
mPreparedLayout = null;
}

/* package */ void recycleView() {
Expand Down Expand Up @@ -444,16 +448,33 @@ public int reactTagForTouch(float touchX, float touchY) {
// if no such span can be found we will send the textview's react id as a touch handler
// In case when there are more than one spans with same length we choose the last one
// from the spans[] array, since it correspond to the most inner react element
ReactTagSpan[] spans = spannedText.getSpans(index, index, ReactTagSpan.class);

if (spans != null) {
int targetSpanTextLength = text.length();
for (int i = 0; i < spans.length; i++) {
int spanStart = spannedText.getSpanStart(spans[i]);
int spanEnd = spannedText.getSpanEnd(spans[i]);
if (spanEnd >= index && (spanEnd - spanStart) <= targetSpanTextLength) {
target = spans[i].getReactTag();
targetSpanTextLength = (spanEnd - spanStart);
if (mPreparedLayout != null) {
ReactFragmentIndexSpan[] fragmentSpans =
spannedText.getSpans(index, index, ReactFragmentIndexSpan.class);

if (fragmentSpans != null) {
int targetSpanTextLength = text.length();
for (int i = 0; i < fragmentSpans.length; i++) {
int spanStart = spannedText.getSpanStart(fragmentSpans[i]);
int spanEnd = spannedText.getSpanEnd(fragmentSpans[i]);
if (spanEnd >= index && (spanEnd - spanStart) <= targetSpanTextLength) {
target = mPreparedLayout.getReactTags()[fragmentSpans[i].getFragmentIndex()];
targetSpanTextLength = (spanEnd - spanStart);
}
}
}
} else {
ReactTagSpan[] spans = spannedText.getSpans(index, index, ReactTagSpan.class);

if (spans != null) {
int targetSpanTextLength = text.length();
for (int i = 0; i < spans.length; i++) {
int spanStart = spannedText.getSpanStart(spans[i]);
int spanEnd = spannedText.getSpanEnd(spans[i]);
if (spanEnd >= index && (spanEnd - spanStart) <= targetSpanTextLength) {
target = spans[i].getReactTag();
targetSpanTextLength = (spanEnd - spanStart);
}
}
}
}
Expand Down Expand Up @@ -623,6 +644,22 @@ public void setSpanned(Spannable spanned) {
return mSpanned;
}

/**
* Get the PreparedLayout originally generated by the Fabric renderer, if using {@code
* enablePreparedTextLayout()}
*
* <p>TODO: Should be made internal when ReactTextView is converted to Kotlin
*/
@UnstableReactNativeAPI
@Nullable
public PreparedLayout getPreparedLayout() {
return mPreparedLayout;
}

/* package */ void setPreparedLayout(@Nullable PreparedLayout preparedLayout) {
mPreparedLayout = preparedLayout;
}

public void setLinkifyMask(int mask) {
mLinkifyMaskType = mask;
}
Expand Down
Loading
Loading