Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand Up@@ -150,6 +153,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactTextInputManager(),
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) PreparedLayoutTextViewManager()
else ReactTextViewManager(),
SelectableTextViewManager(),
ReactViewManager(),
ReactUnimplementedViewManager(),
)
Expand DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand Up@@ -71,6 +72,9 @@ void addCoreComponents(
AndroidHorizontalScrollContentViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
SelectableParagraphComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider<
AndroidDrawerLayoutComponentDescriptor>());
Expand Down
Loading
Loading