From 48ce5e5f8719f9e39547d7282abfd78e4e75ba97 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 14 Feb 2026 02:55:41 -0800 Subject: [PATCH] Initial support for selectable text with enablePreparedTextLayout (#55552) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/55552 enablePreparedTextLayout replaces ReactTextView (a real TextView) with PreparedLayoutTextView (a ViewGroup that draws a pre-computed Layout). PreparedLayoutTextView does not support native text selection, so selectable text was broken when the flag was on (T222052152). This diff adds support for selectable text by routing it through ReactTextView when enablePreparedTextLayout is enabled. A new JS component NativeSelectableText resolves to native name RCTSelectableText when the flag is on, or falls back to RCTText when it is off. Text.js uses NativeSelectableText whenever text is selectable, and a new SelectableTextViewManager (which extends ReactTextViewManager) is registered as RCTSelectableText in all ReactPackage sites. ReactTextViewManager.updateState() is also updated to handle ReferenceStateWrapper holding PreparedLayout, so that it can process state delivered through the PreparedLayout path. Note that this change relies on https://github.com/facebook/react/pull/35780 to avoid warnings from React Changelog: [General][Changed] - Text Can Conditionally Use "RCTSelectableText" Native Component Reviewed By: mdvacca Differential Revision: D92928315 --- packages/react-native/Libraries/Text/Text.js | 69 +++++++++---------- .../Libraries/Text/TextNativeComponent.js | 13 ++++ .../ReactAndroid/api/ReactAndroid.api | 10 ++- .../mountitems/FabricNameComponentMapping.kt | 1 + .../facebook/react/shell/MainReactPackage.kt | 6 ++ .../text/PreparedLayoutTextViewManager.kt | 5 +- .../react/views/text/ReactTextViewManager.kt | 37 +++++++++- .../views/text/SelectableTextViewManager.kt | 29 ++++++++ .../react/fabric/CoreComponentsRegistry.cpp | 4 ++ .../componentNameByReactViewName.cpp | 3 + .../text/BaseParagraphComponentDescriptor.h | 49 +++++++++++++ .../text/ParagraphComponentDescriptor.cpp | 14 ---- .../text/ParagraphComponentDescriptor.h | 31 +-------- .../components/text/ParagraphShadowNode.h | 2 +- .../SelectableParagraphComponentDescriptor.h | 24 +++++++ .../text/SelectableParagraphShadowNode.h | 32 +++++++++ packages/react-native/ReactNativeApi.d.ts | 10 +-- 17 files changed, 252 insertions(+), 87 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/SelectableTextViewManager.kt create mode 100644 packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphComponentDescriptor.h delete mode 100644 packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.cpp create mode 100644 packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphComponentDescriptor.h create mode 100644 packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphShadowNode.h diff --git a/packages/react-native/Libraries/Text/Text.js b/packages/react-native/Libraries/Text/Text.js index a31b20a918f8..9bfc163f9f5d 100644 --- a/packages/react-native/Libraries/Text/Text.js +++ b/packages/react-native/Libraries/Text/Text.js @@ -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, >; /** @@ -263,7 +267,7 @@ const TextImpl: component( processedProps.children = children; if (isPressable) { return ( - ); } else { - nativeText = ; + nativeText = + _selectable === true ? ( + + ) : ( + + ); } if (children == null) { @@ -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, - ...props: NativePressableTextProps -) = ({ - ref: forwardedRef, - textProps, - textPressabilityProps, -}: { +component PressableVirtualText( ref?: React.RefSetter, - ...NativePressableTextProps, -}) => { + textProps: NativeTextProps, + textPressabilityProps: TextPressabilityProps, +) { const [isHighlighted, eventHandlersForText] = useTextPressability( textPressabilityProps, ); @@ -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, - ...props: NativePressableTextProps -) = ({ - ref: forwardedRef, - textProps, - textPressabilityProps, -}: { +component PressableText( ref?: React.RefSetter, - ...NativePressableTextProps, -}) => { + selectable?: ?boolean, + textProps: NativeTextProps, + textPressabilityProps: TextPressabilityProps, +) { const [isHighlighted, eventHandlersForText] = useTextPressability( textPressabilityProps, ); + const NativeComponent = + selectable === true ? NativeSelectableText : NativeText; + return ( - ); -}; +} const userSelectToSelectableMap = { auto: true, diff --git a/packages/react-native/Libraries/Text/TextNativeComponent.js b/packages/react-native/Libraries/Text/TextNativeComponent.js index 020d5ceea8b6..01b7c690fd81 100644 --- a/packages/react-native/Libraries/Text/TextNativeComponent.js +++ b/packages/react-native/Libraries/Text/TextNativeComponent.js @@ -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'; @@ -90,3 +91,15 @@ export const NativeVirtualText: HostComponent = * https://fburl.com/workplace/6291gfvu */ createViewConfig(virtualTextViewConfig), ): any); + +export const NativeSelectableText: HostComponent = + enablePreparedTextLayout() + ? (createReactNativeComponentClass('RCTSelectableText', () => + /* $FlowFixMe[incompatible-type] Natural Inference rollout. See + * https://fburl.com/workplace/6291gfvu */ + createViewConfig({ + ...textViewConfig, + uiViewClassName: 'RCTSelectableText', + }), + ): any) + : NativeText; diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 80c168cd95d1..18d8f7aafaa8 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 ()V @@ -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 @@ -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 @@ -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 { @@ -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 diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/FabricNameComponentMapping.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/FabricNameComponentMapping.kt index c032448a7a33..31d93c32b695 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/FabricNameComponentMapping.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/FabricNameComponentMapping.kt @@ -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", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt index 9927bf571536..b3baad46a9aa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt @@ -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 @@ -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 @@ -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) : @@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) : ReactTextInputManager(), if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager() else ReactTextViewManager(), + SelectableTextViewManager(), ReactViewManager(), ReactUnimplementedViewManager(), ) @@ -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() }, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt index ede2a678a1fe..66a2bf753135 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextViewManager.kt @@ -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") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt index 2e3669c113d7..e616cf5f6e75 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt @@ -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 @@ -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 @@ -46,7 +48,7 @@ import java.util.HashMap /** View manager for `` nodes. */ @ReactModule(name = ReactTextViewManager.REACT_CLASS) @OptIn(UnstableReactNativeAPI::class) -public class ReactTextViewManager +public open class ReactTextViewManager @JvmOverloads public constructor( protected var reactTextViewManagerCallback: ReactTextViewManagerCallback? = null @@ -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) @@ -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? { val baseEventTypeConstants = super.getExportedCustomDirectEventTypeConstants() val eventTypeConstants = baseEventTypeConstants ?: HashMap() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/SelectableTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/SelectableTextViewManager.kt new file mode 100644 index 000000000000..b1f15c461903 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/SelectableTextViewManager.kt @@ -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" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp index 03d455ee92f4..3b15aa501986 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,9 @@ void addCoreComponents( AndroidHorizontalScrollContentViewComponentDescriptor>()); providerRegistry->add( concreteComponentDescriptorProvider()); + providerRegistry->add( + concreteComponentDescriptorProvider< + SelectableParagraphComponentDescriptor>()); providerRegistry->add( concreteComponentDescriptorProvider< AndroidDrawerLayoutComponentDescriptor>()); diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/componentNameByReactViewName.cpp b/packages/react-native/ReactCommon/react/renderer/componentregistry/componentNameByReactViewName.cpp index 25e3b2cb1072..fe63407de827 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/componentNameByReactViewName.cpp +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/componentNameByReactViewName.cpp @@ -27,6 +27,9 @@ std::string componentNameByReactViewName(std::string viewName) { if (viewName == "Text") { return "Paragraph"; } + if (viewName == "SelectableText") { + return "SelectableParagraph"; + } if (viewName == "VirtualText") { return "Text"; diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphComponentDescriptor.h new file mode 100644 index 000000000000..8c7c8aab798f --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseParagraphComponentDescriptor.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +constexpr const char *const TextLayoutManagerKey = "TextLayoutManager"; + +template +class BaseParagraphComponentDescriptor : public ConcreteComponentDescriptor { + public: + explicit BaseParagraphComponentDescriptor(const ComponentDescriptorParameters ¶meters) + : ConcreteComponentDescriptor(parameters), + textLayoutManager_(getManagerByName(this->contextContainer_, TextLayoutManagerKey)) + { + } + + ComponentName getComponentName() const override + { + return ShadowNodeT::Name(); + } + + protected: + void adopt(ShadowNode &shadowNode) const override + { + ConcreteComponentDescriptor::adopt(shadowNode); + + auto ¶graphShadowNode = static_cast(shadowNode); + + // `ParagraphShadowNode` uses `TextLayoutManager` to measure text content + // and communicate text rendering metrics to mounting layer. + paragraphShadowNode.setTextLayoutManager(textLayoutManager_); + } + + private: + // Every `ParagraphShadowNode` has a reference to a shared `TextLayoutManager` + const std::shared_ptr textLayoutManager_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.cpp deleted file mode 100644 index ee4e014b6564..000000000000 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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. - */ - -#include "ParagraphComponentDescriptor.h" - -namespace facebook::react { - -extern const char TextLayoutManagerKey[] = "TextLayoutManager"; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.h index 3ed1a2a7aefc..3f64c6e8cb7a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphComponentDescriptor.h @@ -7,41 +7,16 @@ #pragma once +#include #include -#include -#include -#include namespace facebook::react { - -extern const char TextLayoutManagerKey[]; - /* * Descriptor for component. */ -class ParagraphComponentDescriptor final : public ConcreteComponentDescriptor { +class ParagraphComponentDescriptor final : public BaseParagraphComponentDescriptor { public: - explicit ParagraphComponentDescriptor(const ComponentDescriptorParameters ¶meters) - : ConcreteComponentDescriptor(parameters), - textLayoutManager_(getManagerByName(contextContainer_, TextLayoutManagerKey)) - { - } - - protected: - void adopt(ShadowNode &shadowNode) const override - { - ConcreteComponentDescriptor::adopt(shadowNode); - - auto ¶graphShadowNode = static_cast(shadowNode); - - // `ParagraphShadowNode` uses `TextLayoutManager` to measure text content - // and communicate text rendering metrics to mounting layer. - paragraphShadowNode.setTextLayoutManager(textLayoutManager_); - } - - private: - // Every `ParagraphShadowNode` has a reference to a shared `TextLayoutManager` - const std::shared_ptr textLayoutManager_; + using BaseParagraphComponentDescriptor::BaseParagraphComponentDescriptor; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h index 91709d035d05..c518ccf4b90b 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.h @@ -26,7 +26,7 @@ extern const char ParagraphComponentName[]; * containing and displaying text. Text content is represented as nested * and components. */ -class ParagraphShadowNode final +class ParagraphShadowNode : public ConcreteViewShadowNode, public BaseTextShadowNode { public: diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphComponentDescriptor.h new file mode 100644 index 000000000000..b008580f5885 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphComponentDescriptor.h @@ -0,0 +1,24 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +namespace facebook::react { +/* + * Descriptor for component, which may render to a + * different native view than . + */ +class SelectableParagraphComponentDescriptor final + : public BaseParagraphComponentDescriptor { + public: + using BaseParagraphComponentDescriptor::BaseParagraphComponentDescriptor; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphShadowNode.h new file mode 100644 index 000000000000..8574dd601abd --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/text/SelectableParagraphShadowNode.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace facebook::react { + +/* + * ShadowNode for selectable Paragraph components, which may map to different native component than Paragraph. + */ +class SelectableParagraphShadowNode : public ParagraphShadowNode { + public: + using ParagraphShadowNode::ParagraphShadowNode; + + static constexpr ComponentName Name() + { + return "SelectableParagraph"; + } + + static ComponentHandle Handle() + { + return ComponentHandle(Name()); + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 9229bb4336ff..3cb8fa700fc8 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<6463b0a4f3acbeb07e6759c345b927d8>> + * @generated SignedSource<> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -322,6 +322,7 @@ declare const NativeModules: typeof NativeModules_default declare let NativeModules_default: { [moduleName: string]: any } +declare const NativeSelectableText: HostComponent declare const NativeText: HostComponent declare const NativeTouchable: | typeof TouchableNativeFeedback @@ -3368,6 +3369,7 @@ declare type NativeScrollVelocity = { readonly x: number readonly y: number } +declare type NativeSelectableText = typeof NativeSelectableText declare type NativeSwitchChangeEvent = { readonly target: Int32 readonly value: boolean @@ -5160,7 +5162,7 @@ declare type TextContentType = | "URL" | "username" declare type TextForwardRef = React.ComponentRef< - typeof NativeText | typeof NativeVirtualText + typeof NativeSelectableText | typeof NativeText | typeof NativeVirtualText > declare type TextInput = typeof TextInput declare type TextInputAndroidProps = { @@ -5995,7 +5997,7 @@ export { AlertOptions, // a0cdac0f AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // ed7eb912 + Animated, // f39d3c6f AppConfig, // ebddad4b AppRegistry, // 6cdee1d6 AppState, // 12012be5 @@ -6215,7 +6217,7 @@ export { TVViewPropsIOS, // 330ce7b5 TargetedEvent, // 16e98910 TaskProvider, // 266dedf2 - Text, // e55ac2e2 + Text, // 0620c789 TextContentType, // 239b3ecc TextInput, // 2e89b91d TextInputAndroidProps, // 3f09ce49