From 9512686f811e52392155731dcb47c32a3b41ac49 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Fri, 22 May 2026 10:05:42 -0700 Subject: [PATCH] Migrate `ReactShadowNode` interface from Java to Kotlin (#56941) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/56941 Migrate the `ReactShadowNode` interface from Java to Kotlin as part of the ongoing Kotlin migration effort. This is a mechanical conversion of a deprecated legacy architecture interface with no functional changes. The interface is a pure declaration with no default implementations. Changelog: [Android][Changed] - Migrate `ReactShadowNode` interface from Java to Kotlin Differential Revision: D106079764 --- .../react/uimanager/LayoutShadowNode.kt | 54 +- .../react/uimanager/ReactShadowNode.java | 399 --------------- .../react/uimanager/ReactShadowNode.kt | 484 ++++++++++++++++++ .../react/uimanager/ReactShadowNodeImpl.java | 3 +- .../react/uimanager/ShadowNodeRegistry.kt | 4 +- .../uimanager/ViewManagersPropertyCache.kt | 4 +- 6 files changed, 517 insertions(+), 431 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.kt index 77258f28c59b..cf67f6beb0c7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.kt @@ -95,7 +95,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.WIDTH) public open fun setWidth(width: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -113,7 +113,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.MIN_WIDTH) public open fun setMinWidth(minWidth: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -142,7 +142,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.MAX_WIDTH) public open fun setMaxWidth(maxWidth: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -159,7 +159,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.HEIGHT) public open fun setHeight(height: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -177,7 +177,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.MIN_HEIGHT) public open fun setMinHeight(minHeight: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -194,7 +194,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.MAX_HEIGHT) public open fun setMaxHeight(maxHeight: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -212,7 +212,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @Override @ReactProp(name = ViewProps.FLEX, defaultFloat = 0f) public override fun setFlex(flex: Float) { - if (isVirtual) { + if (isVirtual()) { return } super.setFlex(flex) @@ -221,7 +221,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @Override @ReactProp(name = ViewProps.FLEX_GROW, defaultFloat = 0f) public override fun setFlexGrow(flexGrow: Float) { - if (isVirtual) { + if (isVirtual()) { return } super.setFlexGrow(flexGrow) @@ -229,7 +229,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.ROW_GAP) public open fun setRowGap(rowGap: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -247,7 +247,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.COLUMN_GAP) public open fun setColumnGap(columnGap: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -265,7 +265,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.GAP) public open fun setGap(gap: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -284,7 +284,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @Override @ReactProp(name = ViewProps.FLEX_SHRINK, defaultFloat = 0f) public override fun setFlexShrink(flexShrink: Float) { - if (isVirtual) { + if (isVirtual()) { return } super.setFlexShrink(flexShrink) @@ -292,7 +292,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.FLEX_BASIS) public open fun setFlexBasis(flexBasis: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -315,7 +315,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.FLEX_DIRECTION) public open fun setFlexDirection(flexDirection: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -338,7 +338,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.FLEX_WRAP) public open fun setFlexWrap(flexWrap: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -360,7 +360,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.ALIGN_SELF) public open fun setAlignSelf(alignSelf: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -387,7 +387,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.ALIGN_ITEMS) public open fun setAlignItems(alignItems: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -414,7 +414,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.ALIGN_CONTENT) public open fun setAlignContent(alignContent: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -442,7 +442,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.JUSTIFY_CONTENT) public open fun setJustifyContent(justifyContent: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -467,7 +467,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.OVERFLOW) public open fun setOverflow(overflow: String?) { - if (isVirtual) { + if (isVirtual()) { return } if (overflow == null) { @@ -488,7 +488,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.DISPLAY) public open fun setDisplay(display: String?) { - if (isVirtual) { + if (isVirtual()) { return } @@ -575,7 +575,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { ] ) public open fun setMargins(index: Int, margin: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -609,7 +609,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { ] ) public open fun setPaddings(index: Int, padding: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -641,7 +641,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { defaultFloat = Float.NaN, ) public open fun setBorderWidths(index: Int, borderWidth: Float) { - if (isVirtual) { + if (isVirtual()) { return } val spacingType = maybeTransformLeftRightToStartEnd(ViewProps.BORDER_SPACING_TYPES[index]) @@ -660,7 +660,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { ] ) public open fun setPositionValues(index: Int, position: Dynamic) { - if (isVirtual) { + if (isVirtual()) { return } @@ -688,7 +688,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { } private fun maybeTransformLeftRightToStartEnd(spacingType: Int): Int { - if (!I18nUtil.getInstance().doLeftAndRightSwapInRTL(themedContext)) { + if (!I18nUtil.instance.doLeftAndRightSwapInRTL(getThemedContext())) { return spacingType } @@ -701,7 +701,7 @@ public open class LayoutShadowNode : ReactShadowNodeImpl() { @ReactProp(name = ViewProps.POSITION) public open fun setPosition(position: String?) { - if (isVirtual) { + if (isVirtual()) { return } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java deleted file mode 100644 index e145fbce55a8..000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ /dev/null @@ -1,399 +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.uimanager; - -import androidx.annotation.Nullable; -import com.facebook.react.common.annotations.internal.LegacyArchitecture; -import com.facebook.yoga.YogaAlign; -import com.facebook.yoga.YogaBaselineFunction; -import com.facebook.yoga.YogaDirection; -import com.facebook.yoga.YogaDisplay; -import com.facebook.yoga.YogaFlexDirection; -import com.facebook.yoga.YogaJustify; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaNode; -import com.facebook.yoga.YogaOverflow; -import com.facebook.yoga.YogaPositionType; -import com.facebook.yoga.YogaValue; -import com.facebook.yoga.YogaWrap; - -/** - * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily for - * layouting therefore it extends {@link YogaNode} to allow that. They also help with handling - * Common base subclass of {@link YogaNode} for all layout nodes for react-based view. It extends - * {@link YogaNode} by adding additional capabilities. - * - *

Instances of this class receive property updates from JS via @{link UIManagerModule}. - * Subclasses may use {@link #updateShadowNode} to persist some of the updated fields in the node - * instance that corresponds to a particular view type. - * - *

Subclasses of {@link ReactShadowNode} should be created only from {@link ViewManager} that - * corresponds to a certain type of native view. They will be updated and accessed only from JS - * thread. Subclasses of {@link ViewManager} may choose to use base class {@link ReactShadowNode} or - * custom subclass of it if necessary. - * - *

The primary use-case for {@link ReactShadowNode} nodes is to calculate layouting. Although - * this might be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. - * - *

This class allows for the native view hierarchy to not be an exact copy of the hierarchy - * received from JS by keeping track of both JS children (e.g. {@link #getChildCount()} and - * separately native children (e.g. {@link #getNativeChildCount()}). - */ -@LegacyArchitecture -@Deprecated( - since = "This class is part of Legacy Architecture and will be removed in a future release") -public interface ReactShadowNode { - - /** - * Nodes that return {@code true} will be treated as "virtual" nodes. That is, nodes that are not - * mapped into native views or Yoga nodes (e.g. nested text node). By default this method returns - * {@code false}. - */ - boolean isVirtual(); - - /** - * Nodes that return {@code true} will be treated as a root view for the virtual nodes tree. It - * means that all of its descendants will be "virtual" nodes. Good example is {@code InputText} - * view that may have children {@code Text} nodes but this whole hierarchy will be mapped to a - * single android {@link EditText} view. - */ - boolean isVirtualAnchor(); - - /** - * Nodes that return {@code true} will not manage (and and remove) child Yoga nodes. For example - * {@link ReactTextInputShadowNode} or {@link ReactTextShadowNode} have child nodes, which do not - * want Yoga to lay out, so in the eyes of Yoga it is a leaf node. Override this method in - * subclass to enforce this requirement. - */ - boolean isYogaLeafNode(); - - /** - * When constructing the native tree, nodes that return {@code true} will be treated as leaves. - * Instead of adding this view's native children as subviews of it, they will be added as subviews - * of an ancestor. In other words, this view wants to support native children but it cannot host - * them itself (e.g. it isn't a ViewGroup). - */ - boolean hoistNativeChildren(); - - String getViewClass(); - - boolean hasUpdates(); - - void markUpdateSeen(); - - void markUpdated(); - - boolean hasUnseenUpdates(); - - void dirty(); - - boolean isDirty(); - - void addChildAt(T child, int i); - - T removeChildAt(int i); - - int getChildCount(); - - T getChildAt(int i); - - int indexOf(T child); - - void removeAndDisposeAllChildren(); - - /** - * This method will be called by {@link UIManagerModule} once per batch, before calculating - * layout. Will be only called for nodes that are marked as updated with {@link #markUpdated()} or - * require layouting (marked with {@link #dirty()}). - */ - void onBeforeLayout(NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); - - void updateProperties(ReactStylesDiffMap props); - - void onAfterUpdateTransaction(); - - /** - * Called after layout step at the end of the UI batch from {@link UIManagerModule}. May be used - * to enqueue additional ui operations for the native view. Will only be called on nodes marked as - * updated either with {@link #dirty()} or {@link #markUpdated()}. - * - * @param uiViewOperationQueue interface for enqueueing UI operations - */ - void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue); - - /* package */ boolean dispatchUpdatesWillChangeLayout(float absoluteX, float absoluteY); - - /* package */ void dispatchUpdates( - float absoluteX, - float absoluteY, - UIViewOperationQueue uiViewOperationQueue, - NativeViewHierarchyOptimizer nativeViewHierarchyOptimizer); - - int getReactTag(); - - void setReactTag(int reactTag); - - int getRootTag(); - - void setRootTag(int rootTag); - - void setViewClassName(String viewClassName); - - @Nullable - T getParent(); - - // Returns the node that is responsible for laying out this node. - @Nullable - T getLayoutParent(); - - void setLayoutParent(@Nullable T layoutParent); - - /** - * Get the {@link ThemedReactContext} associated with this {@link ReactShadowNode}. This will - * never change during the lifetime of a {@link ReactShadowNode} instance, but different instances - * can have different contexts; don't cache any calculations based on theme values globally. - */ - ThemedReactContext getThemedContext(); - - void setThemedContext(ThemedReactContext themedContext); - - boolean shouldNotifyOnLayout(); - - void calculateLayout(); - - void calculateLayout(float width, float height); - - boolean hasNewLayout(); - - void markLayoutSeen(); - - /** - * Adds a child that the native view hierarchy will have at this index in the native view - * corresponding to this node. - */ - void addNativeChildAt(T child, int nativeIndex); - - T removeNativeChildAt(int i); - - void removeAllNativeChildren(); - - int getNativeChildCount(); - - int indexOfNativeChild(T nativeChild); - - @Nullable - T getNativeParent(); - - /** - * Sets whether this node only contributes to the layout of its children without doing any drawing - * or functionality itself. - */ - void setIsLayoutOnly(boolean isLayoutOnly); - - boolean isLayoutOnly(); - - int getTotalNativeChildren(); - - boolean isDescendantOf(T ancestorNode); - - /** - * @return a {@link String} representation of the Yoga hierarchy of this {@link ReactShadowNode} - */ - String getHierarchyInfo(); - - /* - * In some cases we need a way to specify some environmental data to shadow node - * to improve layout (or do something similar), so {@code localData} serves these needs. - * For example, any stateful embedded native views may benefit from this. - * Have in mind that this data is not supposed to interfere with the state of - * the shadow node. - * Please respect one-directional data flow of React. - * Use {@link UIManagerModule#setViewLocalData} to set this property - * (to provide local/environmental data for a shadow node) from the main thread. - */ - void setLocalData(Object data); - - /** - * Returns the offset within the native children owned by all layout-only nodes in the subtree - * rooted at this node for the given child. Put another way, this returns the number of native - * nodes (nodes not optimized out of the native tree) that are a) to the left (visited before by a - * DFS) of the given child in the subtree rooted at this node and b) do not have a native parent - * in this subtree (which means that the given child will be a sibling of theirs in the final - * native hierarchy since they'll get attached to the same native parent). - * - *

Basically, a view might have children that have been optimized away. Since those children - * will then add their native children to this view, we now have ranges of native children that - * correspond to single unoptimized children. The purpose of this method is to return the index - * within the native children that corresponds to the **start** of the native children that belong - * to the given child. Also, note that all of the children of a view might be optimized away, so - * this could return the same value for multiple different children. - * - *

Example. Native children are represented by (N) where N is the no-opt child they came from. - * If no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) - * - *

In case some children are optimized away, it might look like this: (0) (1) (1) (1) (3) (3) - * (4) - * - *

In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1 - * getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4 - * - *

getNativeOffsetForChild(Node 4) => 6 - */ - int getNativeOffsetForChild(T child); - - float getLayoutX(); - - float getLayoutY(); - - float getLayoutWidth(); - - float getLayoutHeight(); - - /** - * @return the x position of the corresponding view on the screen, rounded to pixels - */ - int getScreenX(); - - /** - * @return the y position of the corresponding view on the screen, rounded to pixels - */ - int getScreenY(); - - /** - * @return width corrected for rounding to pixels. - */ - int getScreenWidth(); - - /** - * @return height corrected for rounding to pixels. - */ - int getScreenHeight(); - - YogaDirection getLayoutDirection(); - - void setLayoutDirection(YogaDirection direction); - - YogaValue getStyleWidth(); - - void setStyleWidth(float widthPx); - - void setStyleWidthPercent(float percent); - - void setStyleWidthAuto(); - - void setStyleMinWidth(float widthPx); - - void setStyleMinWidthPercent(float percent); - - void setStyleMaxWidth(float widthPx); - - void setStyleMaxWidthPercent(float percent); - - YogaValue getStyleHeight(); - - float getFlex(); - - void setStyleHeight(float heightPx); - - void setStyleHeightPercent(float percent); - - void setStyleHeightAuto(); - - void setStyleMinHeight(float widthPx); - - void setStyleMinHeightPercent(float percent); - - void setStyleMaxHeight(float widthPx); - - void setStyleMaxHeightPercent(float percent); - - void setFlex(float flex); - - void setFlexGrow(float flexGrow); - - void setRowGap(float rowGap); - - void setRowGapPercent(float percent); - - void setColumnGap(float columnGap); - - void setColumnGapPercent(float percent); - - void setGap(float gap); - - void setGapPercent(float percent); - - void setFlexShrink(float flexShrink); - - void setFlexBasis(float flexBasis); - - void setFlexBasisAuto(); - - void setFlexBasisPercent(float percent); - - void setStyleAspectRatio(float aspectRatio); - - void setFlexDirection(YogaFlexDirection flexDirection); - - void setFlexWrap(YogaWrap wrap); - - void setAlignSelf(YogaAlign alignSelf); - - void setAlignItems(YogaAlign alignItems); - - void setAlignContent(YogaAlign alignContent); - - void setJustifyContent(YogaJustify justifyContent); - - void setOverflow(YogaOverflow overflow); - - void setDisplay(YogaDisplay display); - - void setMargin(int spacingType, float margin); - - void setMarginPercent(int spacingType, float percent); - - void setMarginAuto(int spacingType); - - float getPadding(int spacingType); - - YogaValue getStylePadding(int spacingType); - - void setDefaultPadding(int spacingType, float padding); - - void setPadding(int spacingType, float padding); - - void setPaddingPercent(int spacingType, float percent); - - void setBorder(int spacingType, float borderWidth); - - void setPosition(int spacingType, float position); - - void setPositionPercent(int spacingType, float percent); - - void setPositionType(YogaPositionType positionType); - - void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout); - - void setBaselineFunction(YogaBaselineFunction baselineFunction); - - void setMeasureFunction(YogaMeasureFunction measureFunction); - - boolean isMeasureDefined(); - - void dispose(); - - void setMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec); - - Integer getWidthMeasureSpec(); - - Integer getHeightMeasureSpec(); - - @Nullable - Iterable calculateLayoutOnChildren(); -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.kt new file mode 100644 index 000000000000..a374e1c63610 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.kt @@ -0,0 +1,484 @@ +/* + * 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.uimanager + +import com.facebook.react.common.annotations.internal.LegacyArchitecture +import com.facebook.yoga.YogaAlign +import com.facebook.yoga.YogaBaselineFunction +import com.facebook.yoga.YogaDirection +import com.facebook.yoga.YogaDisplay +import com.facebook.yoga.YogaFlexDirection +import com.facebook.yoga.YogaJustify +import com.facebook.yoga.YogaMeasureFunction +import com.facebook.yoga.YogaNode +import com.facebook.yoga.YogaOverflow +import com.facebook.yoga.YogaPositionType +import com.facebook.yoga.YogaValue +import com.facebook.yoga.YogaWrap + +/** + * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily for + * layouting therefore it extends [YogaNode] to allow that. They also help with handling Common base + * subclass of [YogaNode] for all layout nodes for react-based view. It extends [YogaNode] by adding + * additional capabilities. + * + * Instances of this class receive property updates from JS via @{link UIManagerModule}. Subclasses + * may use [updateShadowNode] to persist some of the updated fields in the node instance that + * corresponds to a particular view type. + * + * Subclasses of [ReactShadowNode] should be created only from [ViewManager] that corresponds to a + * certain type of native view. They will be updated and accessed only from JS thread. Subclasses of + * [ViewManager] may choose to use base class [ReactShadowNode] or custom subclass of it if + * necessary. + * + * The primary use-case for [ReactShadowNode] nodes is to calculate layouting. Although this might + * be extended. For some examples please refer to ARTGroupYogaNode or ReactTextYogaNode. + * + * This class allows for the native view hierarchy to not be an exact copy of the hierarchy received + * from JS by keeping track of both JS children (e.g. [getChildCount]) and separately native + * children (e.g. [getNativeChildCount]). + */ +@LegacyArchitecture +@Deprecated("This class is part of Legacy Architecture and will be removed in a future release") +public interface ReactShadowNode> { + + /** + * Nodes that return `true` will be treated as "virtual" nodes. That is, nodes that are not mapped + * into native views or Yoga nodes (e.g. nested text node). By default this method returns + * `false`. + */ + public fun isVirtual(): Boolean + + /** + * Nodes that return `true` will be treated as a root view for the virtual nodes tree. It means + * that all of its descendants will be "virtual" nodes. Good example is `InputText` view that may + * have children `Text` nodes but this whole hierarchy will be mapped to a single android + * [EditText] view. + */ + public fun isVirtualAnchor(): Boolean + + /** + * Nodes that return `true` will not manage (and and remove) child Yoga nodes. For example + * [ReactTextInputShadowNode] or [ReactTextShadowNode] have child nodes, which do not want Yoga to + * lay out, so in the eyes of Yoga it is a leaf node. Override this method in subclass to enforce + * this requirement. + */ + public fun isYogaLeafNode(): Boolean + + /** + * When constructing the native tree, nodes that return `true` will be treated as leaves. Instead + * of adding this view's native children as subviews of it, they will be added as subviews of an + * ancestor. In other words, this view wants to support native children but it cannot host them + * itself (e.g. it isn't a ViewGroup). + */ + public fun hoistNativeChildren(): Boolean + + /** Returns the view class name for this shadow node. */ + public fun getViewClass(): String + + /** Returns whether this node has pending updates. */ + public fun hasUpdates(): Boolean + + /** Marks all updates on this node as seen. */ + public fun markUpdateSeen() + + /** Marks this node as having been updated. */ + public fun markUpdated() + + /** Returns whether this node has updates that have not yet been seen. */ + public fun hasUnseenUpdates(): Boolean + + /** Marks this node as dirty, requiring layout recalculation. */ + public fun dirty() + + /** Returns whether this node is currently marked as dirty. */ + public fun isDirty(): Boolean + + /** Adds a child node at the specified index. */ + public fun addChildAt(child: T, i: Int) + + /** Removes and returns the child node at the specified index. */ + public fun removeChildAt(i: Int): T + + /** Returns the number of children this node has. */ + public fun getChildCount(): Int + + /** Returns the child node at the specified index. */ + public fun getChildAt(i: Int): T + + /** Returns the index of the specified child node, or -1 if not found. */ + public fun indexOf(child: T): Int + + /** Removes and disposes all children of this node. */ + public fun removeAndDisposeAllChildren() + + /** + * This method will be called by [UIManagerModule] once per batch, before calculating layout. Will + * be only called for nodes that are marked as updated with [markUpdated] or require layouting + * (marked with [dirty]). + */ + public fun onBeforeLayout(nativeViewHierarchyOptimizer: NativeViewHierarchyOptimizer) + + /** Updates the properties of this shadow node from the given props diff map. */ + public fun updateProperties(props: ReactStylesDiffMap) + + /** Called after a property update transaction has completed. */ + public fun onAfterUpdateTransaction() + + /** + * Called after layout step at the end of the UI batch from [UIManagerModule]. May be used to + * enqueue additional ui operations for the native view. Will only be called on nodes marked as + * updated either with [dirty] or [markUpdated]. + * + * @param uiViewOperationQueue interface for enqueueing UI operations + */ + public fun onCollectExtraUpdates(uiViewOperationQueue: UIViewOperationQueue) + + /** Returns whether dispatching updates will change the layout at the given absolute position. */ + public fun dispatchUpdatesWillChangeLayout(absoluteX: Float, absoluteY: Float): Boolean + + /** Dispatches updates for this node at the given absolute position. */ + public fun dispatchUpdates( + absoluteX: Float, + absoluteY: Float, + uiViewOperationQueue: UIViewOperationQueue, + nativeViewHierarchyOptimizer: NativeViewHierarchyOptimizer, + ) + + /** Returns the React tag identifier for this node. */ + public fun getReactTag(): Int + + /** Sets the React tag identifier for this node. */ + public fun setReactTag(reactTag: Int) + + /** Returns the root tag identifier for this node. */ + public fun getRootTag(): Int + + /** Sets the root tag identifier for this node. */ + public fun setRootTag(rootTag: Int) + + /** Sets the view class name for this shadow node. */ + public fun setViewClassName(viewClassName: String) + + /** Returns the parent node, or null if this is a root node. */ + public fun getParent(): T? + + /** Returns the node that is responsible for laying out this node. */ + public fun getLayoutParent(): T? + + /** Sets the node that is responsible for laying out this node. */ + public fun setLayoutParent(layoutParent: T?) + + /** + * Get the [ThemedReactContext] associated with this [ReactShadowNode]. This will never change + * during the lifetime of a [ReactShadowNode] instance, but different instances can have different + * contexts; don't cache any calculations based on theme values globally. + */ + public fun getThemedContext(): ThemedReactContext + + /** Sets the [ThemedReactContext] associated with this [ReactShadowNode]. */ + public fun setThemedContext(themedContext: ThemedReactContext) + + /** Returns whether this node should notify on layout changes. */ + public fun shouldNotifyOnLayout(): Boolean + + /** Calculates the layout for this node. */ + public fun calculateLayout() + + /** Calculates the layout for this node with the given width and height constraints. */ + public fun calculateLayout(width: Float, height: Float) + + /** Returns whether this node has a new layout that has not yet been seen. */ + public fun hasNewLayout(): Boolean + + /** Marks the current layout as having been seen. */ + public fun markLayoutSeen() + + /** + * Adds a child that the native view hierarchy will have at this index in the native view + * corresponding to this node. + */ + public fun addNativeChildAt(child: T, nativeIndex: Int) + + /** Removes and returns the native child at the specified index. */ + public fun removeNativeChildAt(i: Int): T + + /** Removes all native children from this node. */ + public fun removeAllNativeChildren() + + /** Returns the number of native children this node has. */ + public fun getNativeChildCount(): Int + + /** Returns the index of the specified native child node. */ + public fun indexOfNativeChild(nativeChild: T): Int + + /** Returns the native parent node, or null if there is none. */ + public fun getNativeParent(): T? + + /** + * Sets whether this node only contributes to the layout of its children without doing any drawing + * or functionality itself. + */ + public fun setIsLayoutOnly(isLayoutOnly: Boolean) + + /** Returns whether this node is layout-only. */ + public fun isLayoutOnly(): Boolean + + /** Returns the total number of native children in the subtree rooted at this node. */ + public fun getTotalNativeChildren(): Int + + /** Returns whether this node is a descendant of the given ancestor node. */ + public fun isDescendantOf(ancestorNode: T): Boolean + + /** Returns a [String] representation of the Yoga hierarchy of this [ReactShadowNode]. */ + public fun getHierarchyInfo(): String + + /** + * In some cases we need a way to specify some environmental data to shadow node to improve layout + * (or do something similar), so `localData` serves these needs. For example, any stateful + * embedded native views may benefit from this. Have in mind that this data is not supposed to + * interfere with the state of the shadow node. Please respect one-directional data flow of React. + * Use [UIManagerModule.setViewLocalData] to set this property (to provide local/environmental + * data for a shadow node) from the main thread. + */ + public fun setLocalData(data: Any?) + + /** + * Returns the offset within the native children owned by all layout-only nodes in the subtree + * rooted at this node for the given child. Put another way, this returns the number of native + * nodes (nodes not optimized out of the native tree) that are a) to the left (visited before by a + * DFS) of the given child in the subtree rooted at this node and b) do not have a native parent + * in this subtree (which means that the given child will be a sibling of theirs in the final + * native hierarchy since they'll get attached to the same native parent). + * + * Basically, a view might have children that have been optimized away. Since those children will + * then add their native children to this view, we now have ranges of native children that + * correspond to single unoptimized children. The purpose of this method is to return the index + * within the native children that corresponds to the **start** of the native children that belong + * to the given child. Also, note that all of the children of a view might be optimized away, so + * this could return the same value for multiple different children. + * + * Example. Native children are represented by (N) where N is the no-opt child they came from. If + * no children are optimized away it'd look like this: (0) (1) (2) (3) ... (n) + * + * In case some children are optimized away, it might look like this: (0) (1) (1) (1) (3) (3) (4) + * + * In that case: getNativeOffsetForChild(Node 0) => 0 getNativeOffsetForChild(Node 1) => 1 + * getNativeOffsetForChild(Node 2) => 4 getNativeOffsetForChild(Node 3) => 4 + * + * getNativeOffsetForChild(Node 4) => 6 + */ + public fun getNativeOffsetForChild(child: T): Int + + /** Returns the layout X position of this node. */ + public fun getLayoutX(): Float + + /** Returns the layout Y position of this node. */ + public fun getLayoutY(): Float + + /** Returns the layout width of this node. */ + public fun getLayoutWidth(): Float + + /** Returns the layout height of this node. */ + public fun getLayoutHeight(): Float + + /** Returns the x position of the corresponding view on the screen, rounded to pixels. */ + public fun getScreenX(): Int + + /** Returns the y position of the corresponding view on the screen, rounded to pixels. */ + public fun getScreenY(): Int + + /** Returns the width corrected for rounding to pixels. */ + public fun getScreenWidth(): Int + + /** Returns the height corrected for rounding to pixels. */ + public fun getScreenHeight(): Int + + /** Returns the layout direction for this node. */ + public fun getLayoutDirection(): YogaDirection + + /** Sets the layout direction for this node. */ + public fun setLayoutDirection(direction: YogaDirection) + + /** Returns the style width value for this node. */ + public fun getStyleWidth(): YogaValue + + /** Sets the style width in pixels. */ + public fun setStyleWidth(widthPx: Float) + + /** Sets the style width as a percentage. */ + public fun setStyleWidthPercent(percent: Float) + + /** Sets the style width to auto. */ + public fun setStyleWidthAuto() + + /** Sets the minimum style width in pixels. */ + public fun setStyleMinWidth(widthPx: Float) + + /** Sets the minimum style width as a percentage. */ + public fun setStyleMinWidthPercent(percent: Float) + + /** Sets the maximum style width in pixels. */ + public fun setStyleMaxWidth(widthPx: Float) + + /** Sets the maximum style width as a percentage. */ + public fun setStyleMaxWidthPercent(percent: Float) + + /** Returns the style height value for this node. */ + public fun getStyleHeight(): YogaValue + + /** Returns the flex value for this node. */ + public fun getFlex(): Float + + /** Sets the style height in pixels. */ + public fun setStyleHeight(heightPx: Float) + + /** Sets the style height as a percentage. */ + public fun setStyleHeightPercent(percent: Float) + + /** Sets the style height to auto. */ + public fun setStyleHeightAuto() + + /** Sets the minimum style height in pixels. */ + public fun setStyleMinHeight(widthPx: Float) + + /** Sets the minimum style height as a percentage. */ + public fun setStyleMinHeightPercent(percent: Float) + + /** Sets the maximum style height in pixels. */ + public fun setStyleMaxHeight(widthPx: Float) + + /** Sets the maximum style height as a percentage. */ + public fun setStyleMaxHeightPercent(percent: Float) + + /** Sets the flex value for this node. */ + public fun setFlex(flex: Float) + + /** Sets the flex grow value for this node. */ + public fun setFlexGrow(flexGrow: Float) + + /** Sets the row gap in pixels. */ + public fun setRowGap(rowGap: Float) + + /** Sets the row gap as a percentage. */ + public fun setRowGapPercent(percent: Float) + + /** Sets the column gap in pixels. */ + public fun setColumnGap(columnGap: Float) + + /** Sets the column gap as a percentage. */ + public fun setColumnGapPercent(percent: Float) + + /** Sets the gap in pixels (applies to both row and column). */ + public fun setGap(gap: Float) + + /** Sets the gap as a percentage (applies to both row and column). */ + public fun setGapPercent(percent: Float) + + /** Sets the flex shrink value for this node. */ + public fun setFlexShrink(flexShrink: Float) + + /** Sets the flex basis value in pixels. */ + public fun setFlexBasis(flexBasis: Float) + + /** Sets the flex basis to auto. */ + public fun setFlexBasisAuto() + + /** Sets the flex basis as a percentage. */ + public fun setFlexBasisPercent(percent: Float) + + /** Sets the style aspect ratio for this node. */ + public fun setStyleAspectRatio(aspectRatio: Float) + + /** Sets the flex direction for this node. */ + public fun setFlexDirection(flexDirection: YogaFlexDirection) + + /** Sets the flex wrap mode for this node. */ + public fun setFlexWrap(wrap: YogaWrap) + + /** Sets the align-self value for this node. */ + public fun setAlignSelf(alignSelf: YogaAlign) + + /** Sets the align-items value for this node. */ + public fun setAlignItems(alignItems: YogaAlign) + + /** Sets the align-content value for this node. */ + public fun setAlignContent(alignContent: YogaAlign) + + /** Sets the justify-content value for this node. */ + public fun setJustifyContent(justifyContent: YogaJustify) + + /** Sets the overflow mode for this node. */ + public fun setOverflow(overflow: YogaOverflow) + + /** Sets the display mode for this node. */ + public fun setDisplay(display: YogaDisplay) + + /** Sets the margin for the given spacing type in pixels. */ + public fun setMargin(spacingType: Int, margin: Float) + + /** Sets the margin for the given spacing type as a percentage. */ + public fun setMarginPercent(spacingType: Int, percent: Float) + + /** Sets the margin for the given spacing type to auto. */ + public fun setMarginAuto(spacingType: Int) + + /** Returns the padding for the given spacing type. */ + public fun getPadding(spacingType: Int): Float + + /** Returns the style padding value for the given spacing type. */ + public fun getStylePadding(spacingType: Int): YogaValue + + /** Sets the default padding for the given spacing type in pixels. */ + public fun setDefaultPadding(spacingType: Int, padding: Float) + + /** Sets the padding for the given spacing type in pixels. */ + public fun setPadding(spacingType: Int, padding: Float) + + /** Sets the padding for the given spacing type as a percentage. */ + public fun setPaddingPercent(spacingType: Int, percent: Float) + + /** Sets the border width for the given spacing type. */ + public fun setBorder(spacingType: Int, borderWidth: Float) + + /** Sets the position for the given spacing type in pixels. */ + public fun setPosition(spacingType: Int, position: Float) + + /** Sets the position for the given spacing type as a percentage. */ + public fun setPositionPercent(spacingType: Int, percent: Float) + + /** Sets the position type for this node. */ + public fun setPositionType(positionType: YogaPositionType) + + /** Sets whether this node should notify on layout changes. */ + public fun setShouldNotifyOnLayout(shouldNotifyOnLayout: Boolean) + + /** Sets the baseline function for this node. */ + public fun setBaselineFunction(baselineFunction: YogaBaselineFunction) + + /** Sets the measure function for this node. */ + public fun setMeasureFunction(measureFunction: YogaMeasureFunction) + + /** Returns whether a measure function is defined for this node. */ + public fun isMeasureDefined(): Boolean + + /** Disposes this node and releases associated resources. */ + public fun dispose() + + /** Sets the measure specs for width and height. */ + public fun setMeasureSpecs(widthMeasureSpec: Int, heightMeasureSpec: Int) + + /** Returns the width measure spec, or null if not set. */ + public fun getWidthMeasureSpec(): Int? + + /** Returns the height measure spec, or null if not set. */ + public fun getHeightMeasureSpec(): Int? + + /** Calculates layout on children and returns the iterable of children, or null. */ + public fun calculateLayoutOnChildren(): Iterable<@JvmWildcard ReactShadowNode<*>>? +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java index 766c5e9995b3..540a018fedb0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNodeImpl.java @@ -1077,7 +1077,8 @@ public Integer getHeightMeasureSpec() { } @Override - public @Nullable Iterable calculateLayoutOnChildren() { + @SuppressWarnings("rawtypes") + public @Nullable Iterable calculateLayoutOnChildren() { return isVirtualAnchor() ? // All of the descendants are virtual so none of them are involved in layout. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ShadowNodeRegistry.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ShadowNodeRegistry.kt index 57cdf5085b59..894935f7af42 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ShadowNodeRegistry.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ShadowNodeRegistry.kt @@ -33,7 +33,7 @@ internal class ShadowNodeRegistry { fun addRootNode(node: ReactShadowNode<*>) { threadAsserter.assertNow() - val tag = node.reactTag + val tag = node.getReactTag() tagsToCSSNodes.put(tag, node) rootTags.put(tag, true) } @@ -55,7 +55,7 @@ internal class ShadowNodeRegistry { fun addNode(node: ReactShadowNode<*>) { threadAsserter.assertNow() - tagsToCSSNodes.put(node.reactTag, node) + tagsToCSSNodes.put(node.getReactTag(), node) } fun removeNode(tag: Int) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt index ad0798d673ca..3ebfa0e75363 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.kt @@ -77,7 +77,7 @@ internal object ViewManagersPropertyCache { fun updateShadowNodeProp(nodeToUpdate: ReactShadowNode<*>, value: Any?) { try { - val resolved = getValueOrDefault(value, nodeToUpdate.themedContext) + val resolved = getValueOrDefault(value, nodeToUpdate.getThemedContext()) if (index == null) { setter.invoke(nodeToUpdate, resolved) } else { @@ -86,7 +86,7 @@ internal object ViewManagersPropertyCache { } catch (t: Throwable) { FLog.e(ViewManager::class.java, "Error while updating prop $propName", t) throw JSApplicationIllegalArgumentException( - "Error while updating property '$propName' in shadow node of type: ${nodeToUpdate.viewClass}", + "Error while updating property '$propName' in shadow node of type: ${nodeToUpdate.getViewClass()}", t, ) }