Repository navigation
Conversation
| paddingBottom?: number, | ||
| }; | ||
|
|
||
| function AppendingList(): React.Node { |
There was a problem hiding this comment.
This change was made because cloneElement seemed to remove the zIndex, so we instead render items without cloneElement.
While I was at it, I also changed this to a function component.
This patch should not be needed once react#57959 is merged. When Fabric reorders scroll content children due to z-index, the old computeTargetView logic picked the first child in hierarchy order whose end position exceeded the scroll offset. That could anchor to the wrong item, so height changes kept the bottom edge fixed instead of the top. Scan all children and select the topmost visible anchor instead. Extend the RNTester AppendingList example with negative z-index values and a "Change height at id" control to reproduce the bug. Test plan (Android): 1. Open RNTester → ScrollView → "smooth bi-directional content loading" 2. Add a few items and scroll so there are items above and below the current item 3. Use "Change height at id" to change the height of an item in the middle 4. Verify the top edge of that item stays in place, rather than the bottom edge jumping Co-authored-by: Cursor <cursoragent@cursor.com>
|
Note that I realized this is an issue on iOS too! I have an idea for a more universal fix that also ensures minIndexForVisible works correctly too (it currently doesn't) |
|
I just updated the PR to also cover iOS and added more automated testing. I then manually retested on both Android and iOS. @janicduplessis I know you wrote the previous PR. Let me know what you think of this one! This is also ready for a review by someone from Meta! @Abbondanzo - it looks like you've reviewed some related logic recently (#57294) - would you maybe be able to review this? I appreciate it! |
| })), | ||
| ); | ||
|
|
||
| const renderItem = (item: ItemInfo, horizontal: boolean) => ( |
There was a problem hiding this comment.
Here's an easier to review diff of the component rendering changes:
When maintainVisibleContentPosition is enabled, Fabric can reorder native children due to z-index, so hierarchy order no longer matches layout order. The helper could anchor to the wrong item, keeping the bottom edge of visible content fixed instead of the top. Scan all eligible children and select the topmost visible anchor on iOS and Android, and sort by layout position when minIndexForVisible > 0. https: //github.com/react/pull/57959 Co-authored-by: Cursor <cursoragent@cursor.com>
When maintainVisibleContentPosition is enabled, Fabric can reorder native children due to z-index, so hierarchy order no longer matches layout order. The helper could anchor to the wrong item, keeping the bottom edge of visible content fixed instead of the top. Scan all eligible children and select the topmost visible anchor on iOS and Android, and sort by layout position when minIndexForVisible > 0. https: //github.com/react/pull/57959 Co-authored-by: Cursor <cursoragent@cursor.com>
When maintainVisibleContentPosition is enabled, Fabric can reorder native children due to z-index, so hierarchy order no longer matches layout order. The helper could anchor to the wrong item, keeping the bottom edge of visible content fixed instead of the top. Scan all eligible children and select the topmost visible anchor on iOS and Android, and sort by layout position when minIndexForVisible > 0. https: //github.com/react/pull/57959 Co-authored-by: Cursor <cursoragent@cursor.com>
When maintainVisibleContentPosition is enabled, Fabric can reorder native children due to z-index, so hierarchy order no longer matches layout order. The helper could anchor to the wrong item, keeping the bottom edge of visible content fixed instead of the top. Scan all eligible children and select the topmost visible anchor on iOS and Android, and sort by layout position when minIndexForVisible > 0. https: //github.com/react/pull/57959 Co-authored-by: Cursor <cursoragent@cursor.com>
|
Chris's AI wingmate checking in. 🤖 Expensify's chronological LegendList migration needs this fix. I've added a core-view RNTester reproducer in #59360 and a standalone RN 0.88.0 reproducer. The z-index correction also changes LegendList's zero-sized anchor behavior. With a positioned, zero-sized first child followed by a stationary item wrapper, selecting the smallest visible end position chooses the wrapper. Moving the anchor and rows up 60 leaves the offset at 600 instead of 540, so the visible row shifts. That case already works on RN main. This small follow-up patch preserves the first eligible zero-sized child when its position exceeds the scroll offset. It carries over the corresponding guard from Expensify's patch. Please keep this case covered alongside the z-index correction. |
|
@chrispader thanks for the deeper check here and patch @hsource if you can incorporate Chris' fix and address the merge conflict, happy to pull this in |
When Fabric reorders scroll content children due to z-index, the old computeTargetView logic picked the first child in hierarchy order whose end position exceeded the scroll offset. That could anchor to the wrong item, so height changes kept the bottom edge fixed instead of the top. Scan all children and select the topmost visible anchor instead. Extend the RNTester AppendingList example with negative z-index values and a "Change height at id" control to reproduce the bug. Test plan (Android): 1. Open RNTester → ScrollView → "smooth bi-directional content loading" 2. Add a few items and scroll so there are items above and below the current item 3. Use "Change height at id" to change the height of an item in the middle 4. Verify the top edge of that item stays in place, rather than the bottom edge jumping Co-authored-by: Cursor <cursoragent@cursor.com>
If we encounter a zero-sized view as the first subview (at minIndexForVisible) and it lies past the scroll offset, assume it's the anchor without checking anything else. Applied on both iOS and Android. This is the easiest way to accommodate LegendList without introducing new APIs. LegendList renders a zero-sized, absolutely positioned ScrollAdjust view as the first child of the scroll content, at a large biased offset, and moves it by the amount it wants the scroll position adjusted. The smallest-end scan added for z-index reordering would otherwise pick a stationary sibling instead, so the adjustment was lost. That case works on main, which picks the first child in hierarchy order. See react#57959 (comment) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
I rebased and incorporated the patch! I applied it to iOS as well. It does feel like a bit of a hackish solution to me: any zero-sized view that happens to be the first subview now gets picked as the anchor without checking anything else, whether or not it was meant as one. It feels like we're doing this to accommodate LegendList because no API exists to do exactly what LegendList wants (maintain a specific index as the anchor all the time). That said, it's the easiest way to accommodate LegendList's behavior without introducing new APIs, and it matches what already works on main, so I'm OK with it for this PR. Longer term, I think the ideal API would be a new property on |
| // items, which requires sorting to do. | ||
| children.sortedWith( | ||
| compareBy<View>({ if (horizontal) it.x else it.y }, { contentView.indexOfChild(it) })) | ||
| compareBy<View>({ if (horizontal) it.x else it.y }, { contentView.indexOfChild(it) }) |
There was a problem hiding this comment.
This and the below code was auto-formatted by ktfmt
The zero-size heuristic is there for correctness to what already exists, but I could see the usefulness behind doing something akin to what SwiftUI offers with default scroll anchors rather than making the user keep track of anchor indices. Having to keep track of which index to anchor on and updating it over and over sounds like a potential pain to manage and can fall out of sync since the visibility callbacks are all async. |
|
@Abbondanzo has imported this pull request. If you are a Meta employee, you can view this in D127603701. |
Summary:
Motivation
At Wanderlog, we use a FlatList with negative zIndex so earlier items' bottom shadows can cover later items (by default, later items cover earlier items).
When
maintainVisibleContentPositionis enabled, the native helper selects the scroll anchor by scanning content children and picking the first one whose end position exceeds the current scroll offset. Fabric can reorder native children when views usezIndex, so hierarchy order no longer matches layout order. On both iOS and Android, the helper can then anchor to the wrong item, and scroll corrections keep the bottom edge of the visible content fixed instead of the top.When
minIndexForVisible > 0, skipping early items also needs to follow layout order rather than native hierarchy order.Fix
iOS and Android: Scan all eligible children and select the topmost visible anchor (the smallest end position still below the scroll offset). When
minIndexForVisible > 0, sort children by layout position before applying the skip. Also clear the cached anchor when the helper stops listening.Ported from the approach in #46247, adapted for the current Kotlin helper and Fabric iOS scroll view, and simplified.
Changelog:
[GENERAL] [FIXED] - Fix
maintainVisibleContentPositionanchoring to the wrong child when z-index reorders Fabric childrenTest Plan:
Automated Android tests
Automated Fantom tests
Manual test on iOS and Android using RNTester:
Android
iOS