Fix zIndex being ignored on Android ScrollView/FlatList with a RefreshControl - #58104
Open
kulkarni-rohan wants to merge 1 commit into
Open
Fix zIndex being ignored on Android ScrollView/FlatList with a RefreshControl#58104kulkarni-rohan wants to merge 1 commit into
kulkarni-rohan wants to merge 1 commit into
Conversation
…hControl On Android a ScrollView with a RefreshControl is wrapped in an AndroidSwipeRefreshLayout, and its style is split across the two nodes by splitLayoutProps(). zIndex was not in the outer list, so it landed on the inner NativeScrollView -- an only child, which can never be reordered -- and z-ordering silently stopped working as soon as onRefresh was passed. Route zIndex to the outer node alongside position, so the wrapper (the node the parent actually orders) carries it. Fixes react#31083
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Fixes #31083.
On Android, a
ScrollViewthat has aRefreshControlis not one view.ScrollView.render()wraps itin an
AndroidSwipeRefreshLayoutand splits the user'sstyleacross the two nodes withsplitLayoutProps()— layout props to the wrapper, painting props to the scroll view(
Libraries/Components/ScrollView/ScrollView.js:1939-1948).splitLayoutPropsroutesposition,top,left,right,bottomandtransformto the outernode, but never listed
zIndex. SozIndexfell through to the innerNativeScrollView— whichis an only child of the wrapper, and therefore can never be reordered against anything. The user's
zIndexsilently stops working the momentonRefreshis added, becauseonRefreshis exactly whatmakes
VirtualizedListsynthesize aRefreshControl(
packages/virtualized-lists/Lists/VirtualizedList.js:1308-1317).That is why removing a single
onRefreshprop fixes the stacking, and why the workaround in the issuethread — wrapping the list in
<View style={{zIndex: 1}}>— works: it reintroduces a node that theparent can order.
On Android z-order is not a view property.
BaseViewManager.setZIndexis an explicit no-op("Z-order is managed at the C++ layer in Fabric");
ConcreteViewShadowNodecopieszIndexintoorderIndex_, andsliceChildShadowNodeViewPairsstable-sorts a parent's children by it to assignmount indices. The parent does the ordering, reading each child's own
zIndex— so the prop has tobe on the wrapper. Correspondingly,
sliceChildShadowNodeViewPairsreturns early for a single child,which is why the current placement is inert rather than merely wrong.
This is a regression, not a long-standing gap: the wrapper split and this omission both arrived in
d9a8ac5 (#24411), first shipping in 0.60.0.
Prior art for this exact shape of fix: #26611 added
transformto this same outer list for the samereason, and #24411 itself created the list. Both landed.
Both consumers already apply the split correctly and need no change —
ScrollView.js:1939andLibraries/Animated/components/AnimatedScrollView.js:94.splitLayoutPropsis internal (not exportedfrom
index.js, absent fromReactNativeApi.d.ts) and the diff adds only acaselabel, so nogenerated artifact changes and no
yarn build-typesrun is needed.Scoped deliberately to
zIndex.elevationis routed toinnertoo and has related symptoms, butmoving it is not equivalent — it would put the elevation on a background-less wrapper and change the
drop shadow — so it belongs in a separate change.
Note that because Android z-order is implemented by reordering mount indices, this also affects touch
and accessibility order, not just paint order — which is the correct, consistent behaviour, and
matches what a list without
onRefreshalready does.Changelog:
[ANDROID] [FIXED] - Fix
zIndexbeing ignored on aScrollView/FlatListthat has aRefreshControl(e.g. whenonRefreshis set)Test Plan:
Added
Libraries/Components/ScrollView/__tests__/ScrollView-refreshControl-test.js, which renders areal
ScrollViewwith aRefreshControlon Android and asserts where the style lands — this pins theactual reported behaviour, not just the helper:
Verified it is a real regression guard — with the one-line fix reverted, it fails:
The rendered trees, before and after:
Also extended
Libraries/StyleSheet/__tests__/splitLayoutProps-itest.jswith a unit case covering therouting directly.
Static checks, against
main@ ab2ea64:Flow 0.329.0 matching the
.flowconfigpin; ESLint 8.57.0 and Prettier 3.9.4, both the versionsdeclared in
package.json.Not run locally: the Fantom itest (
splitLayoutProps-itest.js) — the Fantom tester needs a JDKplus cmake/ninja, which I don't have set up. Its assertions were verified by exercising the
transpiled module directly, and
toEqualis used widely in existing itests. Also no on-device run;the before/after above is from the rendered element tree, not a device.