From 9419c7496d060ceb944c053653c24545b207b9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?He=CC=81ctor=20Chong?= Date: Mon, 9 Feb 2026 15:30:01 -0500 Subject: [PATCH] fix(VirtualizedList): sync virtualization logic with local direction style Summary: This commit fixes the blank rendering issue (Issue #55433) that occurs when a VirtualizedList is used in an RTL environment but explicitly styled as LTR. The root cause was a mismatch between the JS-level coordinate normalization and the Native-level layout direction. While the native ScrollView rendered as LTR, the JS logic still applied RTL mirror calculations based on the global I18nManager state. Changes: 1. Updated `_orientation()` to prioritize the component's `style.direction` over the global `I18nManager.isRTL` setting. 2. In `render()`, explicitly calculated an `orientationStyle` based on the resolved direction and injected it into the ScrollView's style array. 3. This ensures that `_offsetFromScrollEvent` always uses a coordinate system that is perfectly aligned with the actual rendered layout. Fixes: https://github.com/facebook/react-native/issues/55433 --- .../Lists/VirtualizedList.js | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/virtualized-lists/Lists/VirtualizedList.js b/packages/virtualized-lists/Lists/VirtualizedList.js index 7a48d91be4d0..cd7240851adf 100644 --- a/packages/virtualized-lists/Lists/VirtualizedList.js +++ b/packages/virtualized-lists/Lists/VirtualizedList.js @@ -924,11 +924,13 @@ class VirtualizedList extends StateSafePureComponent< const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} = this.props; const {data, horizontal} = this.props; + const isRTL = this._orientation().rtl const inversionStyle = this.props.inverted ? horizontalOrDefault(this.props.horizontal) - ? styles.horizontallyInverted + ? [styles.horizontallyInverted] : styles.verticallyInverted : null; + const orientationStyle = {direction: isRTL ? 'rtl' : 'ltr'} const cells: Array = []; const stickyIndicesFromProps = new Set(this.props.stickyHeaderIndices); const stickyHeaderIndices = []; @@ -1106,8 +1108,8 @@ class VirtualizedList extends StateSafePureComponent< : this.props.inverted, stickyHeaderIndices, style: inversionStyle - ? [inversionStyle, this.props.style] - : this.props.style, + ? [inversionStyle, this.props.style, orientationStyle] + : [this.props.style, orientationStyle], isInvertedVirtualizedList: this.props.inverted, maintainVisibleContentPosition: this.props.maintainVisibleContentPosition != null @@ -1502,7 +1504,7 @@ class VirtualizedList extends StateSafePureComponent< } _selectLength( - metrics: Readonly<{ + metrics: $ReadOnly<{ height: number, width: number, ... @@ -1517,13 +1519,24 @@ class VirtualizedList extends StateSafePureComponent< return this._orientation().horizontal ? x : y; } - _orientation(): ListOrientation { - return { - horizontal: horizontalOrDefault(this.props.horizontal), - rtl: I18nManager.isRTL, - }; + _orientation(): ListOrientation { + const horizontal = horizontalOrDefault(this.props.horizontal); + const flatStyle = StyleSheet.flatten(this.props.style); + const localDirection = flatStyle?.direction; + + let isRTL = I18nManager.isRTL; + if (localDirection === 'rtl') { + isRTL = true; + } else if (localDirection === 'ltr') { + isRTL = false; } + return { + horizontal, + rtl: isRTL, + }; +} + _maybeCallOnEdgeReached() { const { data,