diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
index 3c33c659e72..6e4c661b78f 100644
--- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
+++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-viewCulling-itest.js
@@ -2556,3 +2556,56 @@ describe('culling inside ScrollView with overflow visible', () => {
);
});
});
+
+describe('horizontal ScrollView in RTL script', () => {
+ it('renders item 1', () => {
+ const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
+
+ Fantom.runTask(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
+
+ expect(root.takeMountingManagerLogs()).toEqual([
+ 'Update {type: "RootView", nativeID: (root)}',
+ 'Create {type: "ScrollView", nativeID: (N/A)}',
+ 'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
+ 'Create {type: "View", nativeID: "item1"}',
+ 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item1"}',
+ 'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
+ 'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
+ ]);
+ });
+
+ it('takes contentOffset into account', () => {
+ const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
+
+ Fantom.runTask(() => {
+ root.render(
+
+
+
+ ,
+ );
+ });
+
+ expect(root.takeMountingManagerLogs()).toEqual([
+ 'Update {type: "RootView", nativeID: (root)}',
+ 'Create {type: "ScrollView", nativeID: (N/A)}',
+ 'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
+ 'Create {type: "View", nativeID: "item2"}',
+ 'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item2"}',
+ 'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
+ 'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
+ ]);
+ });
+});
diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp
index b061451ac0f..52c539965de 100644
--- a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp
@@ -25,12 +25,24 @@ CullingContext CullingContext::adjustCullingContextIfNeeded(
dynamic_cast(pair.shadowNode)) {
if (scrollViewShadowNode->getConcreteProps().yogaStyle.overflow() !=
yoga::Overflow::Visible) {
+ auto layoutMetrics = scrollViewShadowNode->getLayoutMetrics();
cullingContext.frame.origin =
-scrollViewShadowNode->getContentOriginOffset(
/* includeTransform */ true);
cullingContext.frame.size =
scrollViewShadowNode->getLayoutMetrics().frame.size;
cullingContext.transform = Transform::Identity();
+
+ if (layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) {
+ // In RTL, content offset is flipped horizontally.
+ // We need to flip the culling context frame to match.
+ // See:
+ // https://github.com/facebook/react-native/blob/c2f39cfdd87c32b9a59efe8a788b8a03f02b0ea0/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm#L579
+ auto stateData = scrollViewShadowNode->getStateData();
+ cullingContext.frame.origin.x =
+ stateData.contentBoundingRect.size.width -
+ layoutMetrics.frame.size.width - cullingContext.frame.origin.x;
+ }
} else {
cullingContext = {};
}