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 @@ -12,6 +12,7 @@ package com.facebook.react.views.text
import android.os.Build
import android.text.Layout
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.TextUtils
import android.text.util.Linkify
Expand All @@ -31,6 +32,7 @@ import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.LengthPercentage
import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.ReferenceStateWrapper
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewDefaults
Expand All @@ -46,7 +48,7 @@ import java.util.HashMap
/** View manager for `<Text>` nodes. */
@ReactModule(name = ReactTextViewManager.REACT_CLASS)
@OptIn(UnstableReactNativeAPI::class)
public class ReactTextViewManager
public open class ReactTextViewManager
@JvmOverloads
public constructor(
protected var reactTextViewManagerCallback: ReactTextViewManagerCallback? = null
Expand Down Expand Up @@ -131,6 +133,11 @@ public constructor(
stateWrapper: StateWrapper,
): Any? {
SystraceSection("ReactTextViewManager.updateState").use { s ->
val refState = (stateWrapper as? ReferenceStateWrapper)?.stateDataReference
if (refState is PreparedLayout) {
return getReactTextUpdateFromPreparedLayout(view, refState)
}

val stateMapBuffer = stateWrapper.stateDataMapBuffer
return if (stateMapBuffer != null) {
getReactTextUpdate(view, props, stateMapBuffer)
Expand Down Expand Up @@ -176,6 +183,34 @@ public constructor(
)
}

/**
* Constructs a [ReactTextUpdate] from a [PreparedLayout] received via [ReferenceStateWrapper].
*/
private fun getReactTextUpdateFromPreparedLayout(
view: ReactTextView,
preparedLayout: PreparedLayout,
): ReactTextUpdate {
val layout = preparedLayout.layout
val text = layout.text
val spanned = if (text is Spannable) text else SpannableString(text)
view.setSpanned(spanned)

val textAlign =
when (layout.alignment) {
Layout.Alignment.ALIGN_CENTER -> Gravity.CENTER_HORIZONTAL
Layout.Alignment.ALIGN_OPPOSITE -> Gravity.END
else -> Gravity.START
}

return ReactTextUpdate(
spanned,
-1,
textAlign,
Layout.BREAK_STRATEGY_HIGH_QUALITY,
0,
)
}

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
val baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants()
val eventTypeConstants = baseEventTypeConstants ?: HashMap()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.text

import com.facebook.react.common.annotations.UnstableReactNativeAPI

/**
* A [ReactTextViewManager] registered under the name "RCTSelectableText". Used to route selectable
* text through [ReactTextView] (a real [android.widget.TextView]) instead of
* [PreparedLayoutTextView] when enablePreparedTextLayout is on, since [PreparedLayoutTextView] does
* not support native text selection.
*/
@UnstableReactNativeAPI
public class SelectableTextViewManager
@JvmOverloads
public constructor(reactTextViewManagerCallback: ReactTextViewManagerCallback? = null) :
ReactTextViewManager(reactTextViewManagerCallback) {

override fun getName(): String = REACT_CLASS

public companion object {
public const val REACT_CLASS: String = "RCTSelectableText"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <react/renderer/components/scrollview/ScrollViewComponentDescriptor.h>
#include <react/renderer/components/text/ParagraphComponentDescriptor.h>
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
#include <react/renderer/components/text/SelectableParagraphComponentDescriptor.h>
#include <react/renderer/components/text/TextComponentDescriptor.h>
#include <react/renderer/components/view/LayoutConformanceComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>
Expand Down Expand Up @@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading