From 178b600214680c543627f7e43a6fda8b58d95022 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 7 Feb 2026 19:42:43 -0800 Subject: [PATCH] (AI Generated) [RN][Android] Merge ReactTextAnchorViewManager into ReactTextViewManager Summary: ReactTextAnchorViewManager was only extended by ReactTextViewManager, and its methods directly assumed the instantiated class was a ReactTextViewManager. This change eliminates the unnecessary inheritance hierarchy by merging ReactTextAnchorViewManager's functionality directly into ReactTextViewManager. ReactTextViewManager now extends BaseViewManager directly and contains all the prop setters (border, text, selection, etc.) that were previously in ReactTextAnchorViewManager. Changelog: [Internal] Differential Revision: D92635681 --- .../ReactAndroid/api/ReactAndroid.api | 19 +- .../views/text/ReactTextAnchorViewManager.kt | 218 ------------------ .../react/views/text/ReactTextViewManager.kt | 200 +++++++++++++++- 3 files changed, 212 insertions(+), 225 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 94e3879b76a608..2a6214f996b8ec 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6196,7 +6196,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi protected fun verifyDrawable (Landroid/graphics/drawable/Drawable;)Z } -public final class com/facebook/react/views/text/ReactTextViewManager : com/facebook/react/views/text/ReactTextAnchorViewManager, com/facebook/react/uimanager/IViewManagerWithChildren, com/facebook/react/views/text/ReactTextViewManagerCallback { +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 static final field Companion Lcom/facebook/react/views/text/ReactTextViewManager$Companion; public static final field REACT_CLASS Ljava/lang/String; public fun ()V @@ -6214,9 +6214,26 @@ public final class com/facebook/react/views/text/ReactTextViewManager : com/face public synthetic fun onAfterUpdateTransaction (Landroid/view/View;)V public fun onPostProcessSpannable (Landroid/text/Spannable;)V public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View; + 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 + public final fun setBorderColor (Lcom/facebook/react/views/text/ReactTextView;ILjava/lang/Integer;)V + public final fun setBorderRadius (Lcom/facebook/react/views/text/ReactTextView;IF)V + public final fun setBorderStyle (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V + public final fun setBorderWidth (Lcom/facebook/react/views/text/ReactTextView;IF)V + public final fun setDataDetectorType (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V + public final fun setDisabled (Lcom/facebook/react/views/text/ReactTextView;Z)V + public final fun setEllipsizeMode (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V + public final fun setFontSize (Lcom/facebook/react/views/text/ReactTextView;F)V + public final fun setIncludeFontPadding (Lcom/facebook/react/views/text/ReactTextView;Z)V + public final fun setLetterSpacing (Lcom/facebook/react/views/text/ReactTextView;F)V + public final fun setNumberOfLines (Lcom/facebook/react/views/text/ReactTextView;I)V 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 + 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 public synthetic fun updateExtraData (Landroid/view/View;Ljava/lang/Object;)V public fun updateExtraData (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/Object;)V public synthetic fun updateState (Landroid/view/View;Lcom/facebook/react/uimanager/ReactStylesDiffMap;Lcom/facebook/react/uimanager/StateWrapper;)Ljava/lang/Object; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.kt deleted file mode 100644 index 12f48e5bc976e7..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.views.text - -import android.text.Layout -import android.text.TextUtils -import android.text.util.Linkify -import android.view.Gravity -import com.facebook.common.logging.FLog -import com.facebook.react.common.ReactConstants -import com.facebook.react.common.annotations.UnstableReactNativeAPI -import com.facebook.react.uimanager.BackgroundStyleApplicator -import com.facebook.react.uimanager.BaseViewManager -import com.facebook.react.uimanager.LengthPercentage -import com.facebook.react.uimanager.LengthPercentageType -import com.facebook.react.uimanager.ViewDefaults -import com.facebook.react.uimanager.ViewProps -import com.facebook.react.uimanager.annotations.ReactProp -import com.facebook.react.uimanager.annotations.ReactPropGroup -import com.facebook.react.uimanager.style.BorderRadiusProp -import com.facebook.react.uimanager.style.BorderStyle.Companion.fromString -import com.facebook.react.uimanager.style.LogicalEdge -import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHighlight - -/** - * Previously a superclass of multiple text view managers. Now only used by [ReactTextViewManager]. - * - * This is a "shadowing" view manager, which means that the - * [com.facebook.react.uimanager.NativeViewHierarchyManager] will NOT manage children of native - * [android.widget.TextView] instances instantiated by this manager. Instead we use - * [ReactBaseTextShadowNode] hierarchy to calculate a [android.text.Spannable] text represented the - * whole text subtree. - */ -@UnstableReactNativeAPI -public abstract class ReactTextAnchorViewManager< - @Suppress("DEPRECATION") - C : ReactBaseTextShadowNode? -> : BaseViewManager() { - - @ReactProp(name = "accessible") - internal fun setAccessible(view: ReactTextView, accessible: Boolean) { - view.isFocusable = accessible - } - - // maxLines can only be set in master view (block), doesn't really make sense to set in a span - @ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES) - internal fun setNumberOfLines(view: ReactTextView, numberOfLines: Int) { - view.setNumberOfLines(numberOfLines) - } - - @ReactProp(name = ViewProps.ELLIPSIZE_MODE) - internal fun setEllipsizeMode(view: ReactTextView, ellipsizeMode: String?) { - when (ellipsizeMode) { - null, - "tail" -> view.setEllipsizeLocation(TextUtils.TruncateAt.END) - "head" -> view.setEllipsizeLocation(TextUtils.TruncateAt.START) - "middle" -> view.setEllipsizeLocation(TextUtils.TruncateAt.MIDDLE) - "clip" -> view.setEllipsizeLocation(null) - else -> { - FLog.w(ReactConstants.TAG, "Invalid ellipsizeMode: $ellipsizeMode") - view.setEllipsizeLocation(TextUtils.TruncateAt.END) - } - } - } - - @ReactProp(name = ViewProps.ADJUSTS_FONT_SIZE_TO_FIT) - internal fun setAdjustFontSizeToFit(view: ReactTextView, adjustsFontSizeToFit: Boolean) { - view.setAdjustFontSizeToFit(adjustsFontSizeToFit) - } - - @ReactProp(name = ViewProps.FONT_SIZE) - internal fun setFontSize(view: ReactTextView, fontSize: Float) { - view.setFontSize(fontSize) - } - - @ReactProp(name = ViewProps.LETTER_SPACING, defaultFloat = 0f) - internal fun setLetterSpacing(view: ReactTextView, letterSpacing: Float) { - view.letterSpacing = letterSpacing - } - - @ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL) - internal fun setTextAlignVertical(view: ReactTextView, textAlignVertical: String?) { - when (textAlignVertical) { - null, - "auto" -> view.setGravityVertical(Gravity.NO_GRAVITY) - "top" -> view.setGravityVertical(Gravity.TOP) - "bottom" -> view.setGravityVertical(Gravity.BOTTOM) - "center" -> view.setGravityVertical(Gravity.CENTER_VERTICAL) - else -> { - FLog.w(ReactConstants.TAG, "Invalid textAlignVertical: $textAlignVertical") - view.setGravityVertical(Gravity.NO_GRAVITY) - } - } - } - - @ReactProp(name = "selectable") - internal fun setSelectable(view: ReactTextView, isSelectable: Boolean) { - view.setTextIsSelectable(isSelectable) - } - - @ReactProp(name = "selectionColor", customType = "Color") - internal fun setSelectionColor(view: ReactTextView, color: Int?) { - view.highlightColor = color ?: getDefaultTextColorHighlight(view.context) - } - - @ReactProp(name = "android_hyphenationFrequency") - internal fun setAndroidHyphenationFrequency(view: ReactTextView, frequency: String?) { - when (frequency) { - null, - "none" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE - "full" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_FULL - "normal" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL - else -> { - FLog.w(ReactConstants.TAG, "Invalid android_hyphenationFrequency: $frequency") - view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE - } - } - } - - @ReactPropGroup( - names = - [ - ViewProps.BORDER_RADIUS, - ViewProps.BORDER_TOP_LEFT_RADIUS, - ViewProps.BORDER_TOP_RIGHT_RADIUS, - ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, - ViewProps.BORDER_BOTTOM_LEFT_RADIUS, - ], - defaultFloat = Float.NaN, - ) - internal fun setBorderRadius(view: ReactTextView, index: Int, borderRadius: Float) { - val radius = - if (borderRadius.isNaN()) { - null - } else { - LengthPercentage(borderRadius, LengthPercentageType.POINT) - } - BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], radius) - } - - @ReactProp(name = "borderStyle") - internal fun setBorderStyle(view: ReactTextView, borderStyle: String?) { - val parsedBorderStyle = if (borderStyle == null) null else fromString(borderStyle) - BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle) - } - - @ReactPropGroup( - names = - [ - ViewProps.BORDER_WIDTH, - ViewProps.BORDER_LEFT_WIDTH, - ViewProps.BORDER_RIGHT_WIDTH, - ViewProps.BORDER_TOP_WIDTH, - ViewProps.BORDER_BOTTOM_WIDTH, - ViewProps.BORDER_START_WIDTH, - ViewProps.BORDER_END_WIDTH, - ], - defaultFloat = Float.NaN, - ) - internal fun setBorderWidth(view: ReactTextView, index: Int, width: Float) { - BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width) - } - - @ReactPropGroup( - names = - [ - "borderColor", - "borderLeftColor", - "borderRightColor", - "borderTopColor", - "borderBottomColor", - ], - customType = "Color", - ) - internal fun setBorderColor(view: ReactTextView, index: Int, color: Int?) { - BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color) - } - - @ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true) - internal fun setIncludeFontPadding(view: ReactTextView, includepad: Boolean) { - view.includeFontPadding = includepad - } - - @ReactProp(name = "disabled", defaultBoolean = false) - internal fun setDisabled(view: ReactTextView, disabled: Boolean) { - view.isEnabled = !disabled - } - - @ReactProp(name = "dataDetectorType") - internal fun setDataDetectorType(view: ReactTextView, type: String?) { - when (type) { - "phoneNumber" -> { - view.setLinkifyMask(Linkify.PHONE_NUMBERS) - return - } - "link" -> { - view.setLinkifyMask(Linkify.WEB_URLS) - return - } - "email" -> { - view.setLinkifyMask(Linkify.EMAIL_ADDRESSES) - return - } - "all" -> { - @Suppress("DEPRECATION") view.setLinkifyMask(Linkify.ALL) - return - } - } - - // "none" case, default, and null type are equivalent. - view.setLinkifyMask(0) - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt index 180f1059463114..a80fcd93566622 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.kt @@ -8,25 +8,39 @@ package com.facebook.react.views.text import android.os.Build +import android.text.Layout import android.text.Spannable +import android.text.TextUtils +import android.text.util.Linkify +import android.view.Gravity +import com.facebook.common.logging.FLog import com.facebook.react.R +import com.facebook.react.common.ReactConstants import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.mapbuffer.MapBuffer import com.facebook.react.internal.SystraceSection import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.BackgroundStyleApplicator +import com.facebook.react.uimanager.BaseViewManager import com.facebook.react.uimanager.IViewManagerWithChildren +import com.facebook.react.uimanager.LengthPercentage +import com.facebook.react.uimanager.LengthPercentageType import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewDefaults +import com.facebook.react.uimanager.ViewProps import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.uimanager.annotations.ReactPropGroup +import com.facebook.react.uimanager.style.BorderRadiusProp +import com.facebook.react.uimanager.style.BorderStyle.Companion.fromString +import com.facebook.react.uimanager.style.LogicalEdge +import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHighlight import com.facebook.react.views.text.internal.span.TextInlineImageSpan import java.util.HashMap -/** - * Concrete class for [ReactTextAnchorViewManager] which represents view managers of anchor `` - * nodes. - */ +/** View manager for `` nodes. */ @Suppress("DEPRECATION") @ReactModule(name = ReactTextViewManager.REACT_CLASS) @OptIn(UnstableReactNativeAPI::class) @@ -35,7 +49,7 @@ public class ReactTextViewManager public constructor( protected var reactTextViewManagerCallback: ReactTextViewManagerCallback? = null ) : - ReactTextAnchorViewManager(), + BaseViewManager(), IViewManagerWithChildren, ReactTextViewManagerCallback { init { @@ -53,7 +67,7 @@ public constructor( if (preparedView != null) { // Resets background and borders preparedView.recycleView() - // Defaults from ReactTextAnchorViewManager + // Reset selection color to default setSelectionColor(preparedView, null) } return preparedView @@ -186,6 +200,180 @@ public constructor( view.setOverflow(overflow) } + @ReactProp(name = "accessible") + public fun setAccessible(view: ReactTextView, accessible: Boolean) { + view.isFocusable = accessible + } + + // maxLines can only be set in master view (block), doesn't really make sense to set in a span + @ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES) + public fun setNumberOfLines(view: ReactTextView, numberOfLines: Int) { + view.setNumberOfLines(numberOfLines) + } + + @ReactProp(name = ViewProps.ELLIPSIZE_MODE) + public fun setEllipsizeMode(view: ReactTextView, ellipsizeMode: String?) { + when (ellipsizeMode) { + null, + "tail" -> view.setEllipsizeLocation(TextUtils.TruncateAt.END) + "head" -> view.setEllipsizeLocation(TextUtils.TruncateAt.START) + "middle" -> view.setEllipsizeLocation(TextUtils.TruncateAt.MIDDLE) + "clip" -> view.setEllipsizeLocation(null) + else -> { + FLog.w(ReactConstants.TAG, "Invalid ellipsizeMode: $ellipsizeMode") + view.setEllipsizeLocation(TextUtils.TruncateAt.END) + } + } + } + + @ReactProp(name = ViewProps.ADJUSTS_FONT_SIZE_TO_FIT) + public fun setAdjustFontSizeToFit(view: ReactTextView, adjustsFontSizeToFit: Boolean) { + view.setAdjustFontSizeToFit(adjustsFontSizeToFit) + } + + @ReactProp(name = ViewProps.FONT_SIZE) + public fun setFontSize(view: ReactTextView, fontSize: Float) { + view.setFontSize(fontSize) + } + + @ReactProp(name = ViewProps.LETTER_SPACING, defaultFloat = 0f) + public fun setLetterSpacing(view: ReactTextView, letterSpacing: Float) { + view.letterSpacing = letterSpacing + } + + @ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL) + public fun setTextAlignVertical(view: ReactTextView, textAlignVertical: String?) { + when (textAlignVertical) { + null, + "auto" -> view.setGravityVertical(Gravity.NO_GRAVITY) + "top" -> view.setGravityVertical(Gravity.TOP) + "bottom" -> view.setGravityVertical(Gravity.BOTTOM) + "center" -> view.setGravityVertical(Gravity.CENTER_VERTICAL) + else -> { + FLog.w(ReactConstants.TAG, "Invalid textAlignVertical: $textAlignVertical") + view.setGravityVertical(Gravity.NO_GRAVITY) + } + } + } + + @ReactProp(name = "selectable") + public fun setSelectable(view: ReactTextView, isSelectable: Boolean) { + view.setTextIsSelectable(isSelectable) + } + + @ReactProp(name = "selectionColor", customType = "Color") + public fun setSelectionColor(view: ReactTextView, color: Int?) { + view.highlightColor = color ?: getDefaultTextColorHighlight(view.context) + } + + @ReactProp(name = "android_hyphenationFrequency") + public fun setAndroidHyphenationFrequency(view: ReactTextView, frequency: String?) { + when (frequency) { + null, + "none" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + "full" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_FULL + "normal" -> view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL + else -> { + FLog.w(ReactConstants.TAG, "Invalid android_hyphenationFrequency: $frequency") + view.hyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE + } + } + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_RADIUS, + ViewProps.BORDER_TOP_LEFT_RADIUS, + ViewProps.BORDER_TOP_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, + ViewProps.BORDER_BOTTOM_LEFT_RADIUS, + ], + defaultFloat = Float.NaN, + ) + public fun setBorderRadius(view: ReactTextView, index: Int, borderRadius: Float) { + val radius = + if (borderRadius.isNaN()) { + null + } else { + LengthPercentage(borderRadius, LengthPercentageType.POINT) + } + BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], radius) + } + + @ReactProp(name = "borderStyle") + public fun setBorderStyle(view: ReactTextView, borderStyle: String?) { + val parsedBorderStyle = if (borderStyle == null) null else fromString(borderStyle) + BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle) + } + + @ReactPropGroup( + names = + [ + ViewProps.BORDER_WIDTH, + ViewProps.BORDER_LEFT_WIDTH, + ViewProps.BORDER_RIGHT_WIDTH, + ViewProps.BORDER_TOP_WIDTH, + ViewProps.BORDER_BOTTOM_WIDTH, + ViewProps.BORDER_START_WIDTH, + ViewProps.BORDER_END_WIDTH, + ], + defaultFloat = Float.NaN, + ) + public fun setBorderWidth(view: ReactTextView, index: Int, width: Float) { + BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width) + } + + @ReactPropGroup( + names = + [ + "borderColor", + "borderLeftColor", + "borderRightColor", + "borderTopColor", + "borderBottomColor", + ], + customType = "Color", + ) + public fun setBorderColor(view: ReactTextView, index: Int, color: Int?) { + BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.values()[index], color) + } + + @ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true) + public fun setIncludeFontPadding(view: ReactTextView, includepad: Boolean) { + view.includeFontPadding = includepad + } + + @ReactProp(name = "disabled", defaultBoolean = false) + public fun setDisabled(view: ReactTextView, disabled: Boolean) { + view.isEnabled = !disabled + } + + @ReactProp(name = "dataDetectorType") + public fun setDataDetectorType(view: ReactTextView, type: String?) { + when (type) { + "phoneNumber" -> { + view.setLinkifyMask(Linkify.PHONE_NUMBERS) + return + } + "link" -> { + view.setLinkifyMask(Linkify.WEB_URLS) + return + } + "email" -> { + view.setLinkifyMask(Linkify.EMAIL_ADDRESSES) + return + } + "all" -> { + @Suppress("DEPRECATION") view.setLinkifyMask(Linkify.ALL) + return + } + } + + // "none" case, default, and null type are equivalent. + view.setLinkifyMask(0) + } + public companion object { private const val TX_STATE_KEY_ATTRIBUTED_STRING: Short = 0 private const val TX_STATE_KEY_PARAGRAPH_ATTRIBUTES: Short = 1