From 9d69b2f1465396dadef616444a78e00674d9990f Mon Sep 17 00:00:00 2001 From: Aditya Gandhi Date: Thu, 8 Oct 2026 19:55:03 +0530 Subject: [PATCH] fix(ScrollView): forward onCellLayout in ScrollViewStickyHeader --- .../Components/ScrollView/ScrollViewStickyHeader.js | 8 ++++++++ .../Lists/VirtualizedListCellRenderer.js | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js index 6caef6b5066a..f59044340389 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -275,6 +275,13 @@ const ScrollViewStickyHeader: component( if (child.props.onLayout) { child.props.onLayout(event); } + // VirtualizedList cells use `onCellLayout` instead of `onLayout` for + // layout tracking. Forward the sticky wrapper's layout (which is measured + // relative to the ScrollView) so ListMetricsAggregator records the correct + // offset instead of the cell's offset relative to this wrapper (y = 0). + if (child.props.onCellLayout) { + child.props.onCellLayout(event, child.props.cellKey, child.props.index); + } }; const child = React.Children.only<$FlowFixMe>(props.children); @@ -304,6 +311,7 @@ const ScrollViewStickyHeader: component( }> {cloneElement(child, { onLayout: undefined, // we call this manually through our this._onLayout + onCellLayout: undefined, // likewise, forwarded manually above for VirtualizedList cells style: styles.fill, // We transfer the child style to the wrapper. })} diff --git a/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js b/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js index 35573a9d703c..4d298ffa7a8e 100644 --- a/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js +++ b/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js @@ -193,8 +193,9 @@ export default class CellRenderer extends React.PureComponent< index, ); - // NOTE: that when this is a sticky header, `onLayout` will get automatically extracted and - // called explicitly by `ScrollViewStickyHeader`. + // NOTE: that when this is a sticky header, `onCellLayout` will get automatically intercepted + // and called explicitly by `ScrollViewStickyHeader` using the wrapper's ScrollView-relative + // coordinates. `onLayout` is similarly forwarded for non-VirtualizedList sticky children. const itemSeparator: React.Node = isValidElement(ItemSeparatorComponent) ? // $FlowFixMe[incompatible-type] ItemSeparatorComponent