Skip to content

Commit 295250f

Browse files
Render the internal SafeAreaView from the safe area insets prop
The core surfaces that cannot depend on `react-native-safe-area-context` — LogBox, the element inspector, `InputAccessoryView` — get their safe area padding from a private component that until now wrapped the native `RCTSafeAreaView`. It applies the prop instead, in JavaScript. The initial insets are seeded from `Dimensions`, so the first frame is already padded, and the synchronous inset event then keeps them correct relative to the view. The seed is only exact for views aligned with the window edges, which these surfaces are. Two consequences, both visible in the updated LogBox snapshots: these surfaces now apply safe area padding on Android too, where they previously fell back to a plain `View`, and they re-render when insets arrive rather than being padded natively. The native implementations are untouched here — the deprecated public `SafeAreaView` still uses them. This only moves the internal component onto the prop, so the two can be compared against each other before the native side is removed.
1 parent a59f60a commit 295250f

6 files changed

Lines changed: 99 additions & 43 deletions

File tree

packages/react-native/Libraries/Components/TextInput/InputAccessoryView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @format
99
*/
1010

11-
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
11+
import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
1212
import StyleSheet, {
1313
type ColorValue,
1414
type ViewStyleProp,

packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooterButton.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @format
99
*/
1010

11-
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
11+
import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
1212
import View from '../../Components/View/View';
1313
import StyleSheet from '../../StyleSheet/StyleSheet';
1414
import Text from '../../Text/Text';

packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import type {ViewProps} from '../../Components/View/ViewPropTypes';
1212
import type {LogLevel} from '../Data/LogBoxLog';
1313

14-
import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
14+
import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
1515
import View from '../../Components/View/View';
1616
import StyleSheet from '../../StyleSheet/StyleSheet';
1717
import Text from '../../Text/Text';

packages/react-native/Libraries/LogBox/__tests__/__snapshots__/LogBoxNotificationContainer-test.js.snap

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`LogBoxNotificationContainer should render both an error and warning notification 1`] = `
4-
<RCTSafeAreaView
4+
<View
5+
experimental_onSafeAreaInsetsChange={[Function]}
56
style={
6-
Object {
7-
"bottom": 20,
8-
"left": 10,
9-
"position": "absolute",
10-
"right": 10,
11-
}
7+
Array [
8+
Object {
9+
"bottom": 20,
10+
"left": 10,
11+
"position": "absolute",
12+
"right": 10,
13+
},
14+
null,
15+
]
1216
}
1317
>
1418
<View
@@ -101,7 +105,7 @@ exports[`LogBoxNotificationContainer should render both an error and warning not
101105
totalLogCount={1}
102106
/>
103107
</View>
104-
</RCTSafeAreaView>
108+
</View>
105109
`;
106110

107111
exports[`LogBoxNotificationContainer should render null with no logs 1`] = `null`;
@@ -113,14 +117,18 @@ exports[`LogBoxNotificationContainer should render selected fatal error even whe
113117
exports[`LogBoxNotificationContainer should render selected syntax error even when disabled 1`] = `null`;
114118

115119
exports[`LogBoxNotificationContainer should render the latest error notification 1`] = `
116-
<RCTSafeAreaView
120+
<View
121+
experimental_onSafeAreaInsetsChange={[Function]}
117122
style={
118-
Object {
119-
"bottom": 20,
120-
"left": 10,
121-
"position": "absolute",
122-
"right": 10,
123-
}
123+
Array [
124+
Object {
125+
"bottom": 20,
126+
"left": 10,
127+
"position": "absolute",
128+
"right": 10,
129+
},
130+
null,
131+
]
124132
}
125133
>
126134
<View
@@ -168,18 +176,22 @@ exports[`LogBoxNotificationContainer should render the latest error notification
168176
totalLogCount={2}
169177
/>
170178
</View>
171-
</RCTSafeAreaView>
179+
</View>
172180
`;
173181

174182
exports[`LogBoxNotificationContainer should render the latest warning notification 1`] = `
175-
<RCTSafeAreaView
183+
<View
184+
experimental_onSafeAreaInsetsChange={[Function]}
176185
style={
177-
Object {
178-
"bottom": 20,
179-
"left": 10,
180-
"position": "absolute",
181-
"right": 10,
182-
}
186+
Array [
187+
Object {
188+
"bottom": 20,
189+
"left": 10,
190+
"position": "absolute",
191+
"right": 10,
192+
},
193+
null,
194+
]
183195
}
184196
>
185197
<View
@@ -227,5 +239,5 @@ exports[`LogBoxNotificationContainer should render the latest warning notificati
227239
totalLogCount={2}
228240
/>
229241
</View>
230-
</RCTSafeAreaView>
242+
</View>
231243
`;

packages/react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE.js

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,67 @@
99
*/
1010

1111
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
12+
import type {
13+
SafeAreaInsets,
14+
SafeAreaInsetsChangeEvent,
15+
} from '../../../../Libraries/Types/CoreEventTypes';
16+
import type {HostInstance} from '../../types/HostInstance';
1217

1318
import View from '../../../../Libraries/Components/View/View';
14-
import UIManager from '../../../../Libraries/ReactNative/UIManager';
15-
import Platform from '../../../../Libraries/Utilities/Platform';
19+
import Dimensions from '../../../../Libraries/Utilities/Dimensions';
1620
import * as React from 'react';
21+
import {useCallback, useMemo, useState} from 'react';
1722

18-
const exported: component(
19-
ref?: React.RefSetter<React.ElementRef<typeof View>>,
20-
...ViewProps
21-
) = Platform.select({
22-
ios: require('../../../../src/private/components/safeareaview/specs/RCTSafeAreaViewNativeComponent')
23-
.default,
24-
android: UIManager.hasViewManagerConfig('RCTSafeAreaView')
25-
? require('../../../../src/private/components/safeareaview/specs/RCTSafeAreaViewNativeComponent')
26-
.default
27-
: View,
28-
default: View,
29-
});
23+
/**
24+
* Renders its children within the safe area of the device, by applying the part
25+
* of the view that is covered by the system UI as padding.
26+
*
27+
* This is the internal counterpart of `react-native-safe-area-context`, for the
28+
* few surfaces React Native renders itself (LogBox, the element inspector, ...)
29+
* which cannot take a dependency on it. Everything else should use the library.
30+
*/
31+
component SafeAreaView(
32+
ref?: React.RefSetter<HostInstance>,
33+
...props: ViewProps
34+
) {
35+
const {style, experimental_onSafeAreaInsetsChange, ...otherProps} = props;
36+
// Seeded with the window insets so the first frame is already padded; the
37+
// synchronous event then keeps them correct relative to this view. The seed
38+
// is only exact for views aligned with the window edges, which the internal
39+
// surfaces using this component (LogBox, the element inspector) are.
40+
const [insets, setInsets] = useState<?SafeAreaInsets>(
41+
() => Dimensions.get('window').experimental_safeAreaInsets,
42+
);
43+
44+
const handleSafeAreaInsetsChange = useCallback(
45+
(event: SafeAreaInsetsChangeEvent) => {
46+
setInsets(event.nativeEvent.insets);
47+
experimental_onSafeAreaInsetsChange?.(event);
48+
},
49+
[experimental_onSafeAreaInsetsChange],
50+
);
51+
52+
const paddingStyle = useMemo(
53+
() =>
54+
insets == null
55+
? null
56+
: {
57+
paddingTop: insets.top,
58+
paddingRight: insets.right,
59+
paddingBottom: insets.bottom,
60+
paddingLeft: insets.left,
61+
},
62+
[insets],
63+
);
64+
65+
return (
66+
<View
67+
{...otherProps}
68+
ref={ref}
69+
experimental_onSafeAreaInsetsChange={handleSafeAreaInsetsChange}
70+
style={[style, paddingStyle]}
71+
/>
72+
);
73+
}
3074

31-
export default exported;
75+
export default SafeAreaView;

packages/react-native/src/private/devsupport/devmenu/elementinspector/InspectorPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import type {ElementsHierarchy, InspectedElement} from './Inspector';
1414

15-
import SafeAreaView from '../../../../../Libraries/Components/SafeAreaView/SafeAreaView';
15+
import SafeAreaView from '../../../components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
1616
import * as React from 'react';
1717

1818
const ScrollView =

0 commit comments

Comments
 (0)