Add an onSafeAreaInsetsChange view prop - #57967
Draft
janicduplessis wants to merge 4 commits into
Draft
Conversation
Reports the part of a view that is covered by the system UI, dispatched synchronously so that layout depending on the insets lands in the frame the insets changed in. Replaces every use of the deprecated SafeAreaView inside core (LogBox, the element inspector, InputAccessoryView) with a JS implementation built on the prop.
|
Warning JavaScript API change detected This PR commits an update to
This change was flagged as: |
Triggers now mark the view as needing layout instead of emitting inline, so the synchronous React render never re-enters from inside the mounting transaction (updateProps / didMoveToWindow). The layout pass runs before the frame is displayed, so the same-frame guarantee is unchanged.
Dimensions.get('window').safeAreaInsets exposes the part of the window
covered by the system UI, available synchronously at startup and updated
through the existing change event. Uses the same native inset
computation as the onSafeAreaInsetsChange view prop.
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:
Prototype, opened for discussion rather than for landing as-is.
SafeAreaViewis deprecated in favour ofreact-native-safe-area-context(per react-native-community/discussions-and-proposals#827), but core surfaces like LogBox and the element inspector cannot depend on the library, so core keeps a private copy of the deprecated component alive. The smallest primitive that would let both sides go away is native code reporting inset values to JS — today the library'sRNCSafeAreaProvidercomponent. This adds that primitive as a view prop instead:The payload is deliberately identical to the library's
onInsetsChange, soSafeAreaProvidercan swap its native component for a plainViewwith no API change on its side. Insets are relative to the view: a view laid out inside the safe area reports zeros, which is what makes it composable and what stops nested providers from double-padding. The inset math follows the library's (UIView.safeAreaInsetson iOS; root windowsystemBars() | displayCutout()insets clipped to the view's rect on Android) so the semantics match.Window insets in
Dimensions.Dimensions.get('window').safeAreaInsets(anduseWindowDimensions) reports the safe area insets of the window, using the same native inset computation as the prop — available synchronously at startup and updated through the existingchangeevent. This is what letsreact-native-safe-area-contextdrop its last native module (initialWindowMetrics); the library-side prototype consuming all of this is appandflow/react-native-safe-area-context#752.Every use of the deprecated
SafeAreaViewinside core is replaced with a JSSafeAreaViewbuilt on the prop. Two behaviour changes fall out of that:InputAccessoryViewnow apply safe area padding on Android too — they previously fell back to a plainView, since the nativeSafeAreaViewwas iOS-only. Relative insets mean this can't double-pad a surface that is already inside the safe area.Synchronous dispatch. The event goes out through
EventEmitter::experimental_flushSyncas aDiscreteevent, the same mechanismVirtualViewuses. The UI and JS threads block until React has re-rendered, so the layout that depends on the insets is mounted in the frame the insets changed in. That is the part the library cannot do today: rotating the device currently shows one frame with the old padding.Cost when unused. The prop is a
boolinBaseViewProps(likeonLayout), and native only observes the safe area when it is set — aUIViewthat doesn't set it never computes insets, and an Android view never gets a pre-draw listener. iOS pays for one ivar check inlayoutSubviews/didMoveToWindow, which are now overridden onRCTViewComponentView; that's the only unconditional cost I could not avoid, and it's worth a look from someone who profiles this path.Open questions I'd like input on:
onSafeAreaInsetsChangeright, and should it ship prefixed (experimental_/unstable_) first?framebe in the payload at all? The library needs it forSafeAreaFrameContext, but it's derivable withmeasureInWindow.Changelog:
[GENERAL] [ADDED] - Add an
onSafeAreaInsetsChangeview prop andDimensions.get('window').safeAreaInsets, reporting the part of a view / the window covered by the system UITest Plan:
RNTester, new "Safe area insets" example, on an iPhone 17 Pro simulator and an Android 16 emulator.
A view laid out inside the safe area reports zero insets and its real frame in window coordinates (iOS left, Android right):
A full screen view padding itself by its own insets — the unpadded (pink) area lines up exactly with the status bar / home indicator / gesture bar on both platforms, and on iOS the padding follows rotation:
The LogBox notification container, one of the converted call sites, still clears the home indicator:
Dimensions.get('window').safeAreaInsetsreports the same values as the prop on both platforms:Synchronous rendering — frame-by-frame validation. Screen recordings of the modal presenting and (on iOS) rotating, decomposed with ffmpeg and inspected frame by frame: on both platforms the modal's first visible frames already carry the inset padding while it is still animating in, and on iOS the mid-rotation animation frames already show the new orientation's insets. No frame anywhere shows content with stale insets — which is the artifact this dispatch mode exists to eliminate.
New Fantom tests — the event payload reaching JS, the prop reaching C++ props, the internal
SafeAreaViewturning insets into padding, and the physical-pixel scaling of theDimensionsinsets.Not exercised: rotation on Android (the RNTester activity kept its orientation on my emulator) — the same pre-draw listener drives it, but I have not seen it happen.