diff --git a/packages/react-native/Libraries/Components/TextInput/InputAccessoryView.js b/packages/react-native/Libraries/Components/TextInput/InputAccessoryView.js
index 5ded8e707aed..c9e3f4c0b97c 100644
--- a/packages/react-native/Libraries/Components/TextInput/InputAccessoryView.js
+++ b/packages/react-native/Libraries/Components/TextInput/InputAccessoryView.js
@@ -8,7 +8,7 @@
* @format
*/
-import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
+import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import StyleSheet, {
type ColorValue,
type ViewStyleProp,
diff --git a/packages/react-native/Libraries/Components/View/__tests__/ViewSafeAreaInsets-itest.js b/packages/react-native/Libraries/Components/View/__tests__/ViewSafeAreaInsets-itest.js
index bfbd1a24515f..007b51149526 100644
--- a/packages/react-native/Libraries/Components/View/__tests__/ViewSafeAreaInsets-itest.js
+++ b/packages/react-native/Libraries/Components/View/__tests__/ViewSafeAreaInsets-itest.js
@@ -12,6 +12,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
+import SafeAreaView from '../../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
@@ -116,3 +117,41 @@ describe('experimental_onSafeAreaInsetsChange', () => {
).toEqual();
});
});
+
+describe('', () => {
+ it('applies the insets it receives as padding', () => {
+ const root = Fantom.createRoot();
+ const nodeRef = createRef();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ expect(
+ root
+ .getRenderedOutput({
+ props: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
+ })
+ .toJSX(),
+ ).toEqual();
+
+ Fantom.dispatchNativeEvent(nodeRef, 'safeAreaInsetsChange', {
+ insets: INSETS,
+ });
+
+ expect(
+ root
+ .getRenderedOutput({
+ props: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
+ })
+ .toJSX(),
+ ).toEqual(
+ ,
+ );
+ });
+});
diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooterButton.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooterButton.js
index 3db512ed3d43..3a3d5da97792 100644
--- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooterButton.js
+++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorFooterButton.js
@@ -8,7 +8,7 @@
* @format
*/
-import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
+import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import View from '../../Components/View/View';
import StyleSheet from '../../StyleSheet/StyleSheet';
import Text from '../../Text/Text';
diff --git a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js
index a5b5329d440e..06569cab0d4c 100644
--- a/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js
+++ b/packages/react-native/Libraries/LogBox/UI/LogBoxInspectorHeader.js
@@ -8,10 +8,9 @@
* @format
*/
-import type {ViewProps} from '../../Components/View/ViewPropTypes';
import type {LogLevel} from '../Data/LogBoxLog';
-import SafeAreaView from '../../Components/SafeAreaView/SafeAreaView';
+import SafeAreaView from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import View from '../../Components/View/View';
import StyleSheet from '../../StyleSheet/StyleSheet';
import Text from '../../Text/Text';
@@ -27,13 +26,10 @@ type Props = Readonly<{
level: LogLevel,
}>;
-const LogBoxInspectorHeaderSafeArea: React.ComponentType =
- Platform.OS === 'android' ? View : SafeAreaView;
-
export default function LogBoxInspectorHeader(props: Props): React.Node {
if (props.level === 'syntax') {
return (
-
+
-
+
);
}
@@ -56,7 +52,7 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
const titleText = `Log ${props.selectedIndex + 1} of ${props.total}`;
return (
-
+
-
+
);
}
diff --git a/packages/react-native/Libraries/LogBox/__tests__/__snapshots__/LogBoxNotificationContainer-test.js.snap b/packages/react-native/Libraries/LogBox/__tests__/__snapshots__/LogBoxNotificationContainer-test.js.snap
index 6799302e9474..7c65b0a79669 100644
--- a/packages/react-native/Libraries/LogBox/__tests__/__snapshots__/LogBoxNotificationContainer-test.js.snap
+++ b/packages/react-native/Libraries/LogBox/__tests__/__snapshots__/LogBoxNotificationContainer-test.js.snap
@@ -1,16 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxNotificationContainer should render both an error and warning notification 1`] = `
-
-
+
`;
exports[`LogBoxNotificationContainer should render null with no logs 1`] = `null`;
@@ -119,16 +123,20 @@ exports[`LogBoxNotificationContainer should render selected fatal error even whe
exports[`LogBoxNotificationContainer should render selected syntax error even when disabled 1`] = `null`;
exports[`LogBoxNotificationContainer should render the latest error notification 1`] = `
-
-
+
`;
exports[`LogBoxNotificationContainer should render the latest warning notification 1`] = `
-
-
+
`;
diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts
index cb67856cb4b3..7d194d4db35b 100644
--- a/packages/react-native/ReactNativeApi.d.ts
+++ b/packages/react-native/ReactNativeApi.d.ts
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
- * @generated SignedSource<<2ceaa8e472a53fc667994edb63ccd995>>
+ * @generated SignedSource<<1faa2820bf867aa599cdd390c5c90b00>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
@@ -5968,7 +5968,8 @@ export {
RootViewStyleProvider, // d5909148
Runnable, // 594dd93a
Runnables, // 4367c557
- SafeAreaInsetsChangeEvent, // cedb5309
+ SafeAreaInsets, // 64d7a605
+ SafeAreaInsetsChangeEvent, // 4ffd35e5
SafeAreaView, // d917b6ec
SafeAreaViewInstance, // 2b5d4391
ScaledSize, // 07e417c7
diff --git a/packages/react-native/index.js.flow b/packages/react-native/index.js.flow
index 3d015144404b..8dadcac64496 100644
--- a/packages/react-native/index.js.flow
+++ b/packages/react-native/index.js.flow
@@ -485,6 +485,7 @@ export type {
NativeTouchEvent,
NativeUIEvent,
ResponderSyntheticEvent,
+ SafeAreaInsets,
SafeAreaInsetsChangeEvent,
ScrollEvent,
TargetedEvent,
diff --git a/packages/react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE.js b/packages/react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE.js
index e6b5cdd5f522..0ea40f01bbf6 100644
--- a/packages/react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE.js
+++ b/packages/react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE.js
@@ -9,23 +9,62 @@
*/
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
+import type {
+ SafeAreaInsets,
+ SafeAreaInsetsChangeEvent,
+} from '../../../../Libraries/Types/CoreEventTypes';
+import type {HostInstance} from '../../types/HostInstance';
import View from '../../../../Libraries/Components/View/View';
-import UIManager from '../../../../Libraries/ReactNative/UIManager';
-import Platform from '../../../../Libraries/Utilities/Platform';
+import I18nManager from '../../../../Libraries/ReactNative/I18nManager';
import * as React from 'react';
+import {useCallback, useMemo, useState} from 'react';
-const exported: component(
- ref?: React.RefSetter>,
- ...ViewProps
-) = Platform.select({
- ios: require('../../../../src/private/components/safeareaview/specs/RCTSafeAreaViewNativeComponent')
- .default,
- android: UIManager.hasViewManagerConfig('RCTSafeAreaView')
- ? require('../../../../src/private/components/safeareaview/specs/RCTSafeAreaViewNativeComponent')
- .default
- : View,
- default: View,
-});
+/**
+ * Renders its children within the safe area of the device, by applying the part
+ * of the view that is covered by the system UI as padding.
+ */
+component SafeAreaView(
+ ref?: React.RefSetter,
+ ...props: ViewProps
+) {
+ const {style, experimental_onSafeAreaInsetsChange, ...otherProps} = props;
+ const [insets, setInsets] = useState(null);
+
+ const handleSafeAreaInsetsChange = useCallback(
+ (event: SafeAreaInsetsChangeEvent) => {
+ setInsets(event.nativeEvent.insets);
+ experimental_onSafeAreaInsetsChange?.(event);
+ },
+ [experimental_onSafeAreaInsetsChange],
+ );
+
+ const paddingStyle = useMemo(() => {
+ if (insets == null) {
+ return null;
+ }
+ // Insets are physical edges, but Yoga remaps paddingLeft/paddingRight to
+ // start/end when I18nManager's swapLeftAndRightInRTL is on, which would
+ // pad the mirror-image edge in RTL. Swap the values so the physical edge
+ // keeps its inset.
+ const {isRTL, doLeftAndRightSwapInRTL} = I18nManager.getConstants();
+ const swap = isRTL && doLeftAndRightSwapInRTL;
+ return {
+ paddingTop: insets.top,
+ paddingRight: swap ? insets.left : insets.right,
+ paddingBottom: insets.bottom,
+ paddingLeft: swap ? insets.right : insets.left,
+ };
+ }, [insets]);
+
+ return (
+
+ );
+}
-export default exported;
+export default SafeAreaView;
diff --git a/packages/react-native/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-itest.js b/packages/react-native/src/private/components/safeareaview/__tests__/SafeAreaView-itest.js
similarity index 91%
rename from packages/react-native/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-itest.js
rename to packages/react-native/src/private/components/safeareaview/__tests__/SafeAreaView-itest.js
index 0fcae9f2e282..cb84b97d23e7 100644
--- a/packages/react-native/Libraries/Components/SafeAreaView/__tests__/SafeAreaView-itest.js
+++ b/packages/react-native/src/private/components/safeareaview/__tests__/SafeAreaView-itest.js
@@ -10,9 +10,10 @@
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
+import SafeAreaView from '../SafeAreaView_INTERNAL_DO_NOT_USE';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
-import {SafeAreaView, Text} from 'react-native';
+import {Text} from 'react-native';
describe('', () => {
it('renders with children', () => {
diff --git a/packages/react-native/src/private/devsupport/devmenu/elementinspector/InspectorPanel.js b/packages/react-native/src/private/devsupport/devmenu/elementinspector/InspectorPanel.js
index 9e5d04f30038..b4195f431486 100644
--- a/packages/react-native/src/private/devsupport/devmenu/elementinspector/InspectorPanel.js
+++ b/packages/react-native/src/private/devsupport/devmenu/elementinspector/InspectorPanel.js
@@ -12,7 +12,7 @@
import type {ElementsHierarchy, InspectedElement} from './Inspector';
-import SafeAreaView from '../../../../../Libraries/Components/SafeAreaView/SafeAreaView';
+import SafeAreaView from '../../../components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import * as React from 'react';
const ScrollView =
diff --git a/packages/rn-tester/js/examples/Experimental/PlatformTest/RNTesterPlatformTestResultView.js b/packages/rn-tester/js/examples/Experimental/PlatformTest/RNTesterPlatformTestResultView.js
index c0f0e2b06282..6821dca645b7 100644
--- a/packages/rn-tester/js/examples/Experimental/PlatformTest/RNTesterPlatformTestResultView.js
+++ b/packages/rn-tester/js/examples/Experimental/PlatformTest/RNTesterPlatformTestResultView.js
@@ -12,7 +12,12 @@ import type {
PlatformTestResult,
PlatformTestResultStatus,
} from './RNTesterPlatformTestTypes';
-import type {ListRenderItemInfo, TextStyle} from 'react-native';
+import type {
+ ListRenderItemInfo,
+ SafeAreaInsets,
+ SafeAreaInsetsChangeEvent,
+ TextStyle,
+} from 'react-native';
import RNTesterPlatformTestMinimizedResultView from './RNTesterPlatformTestMinimizedResultView';
import RNTesterPlatformTestResultsText from './RNTesterPlatformTestResultsText';
@@ -24,7 +29,6 @@ import {
KeyboardAvoidingView,
Modal,
Platform,
- SafeAreaView,
StyleSheet,
Switch,
Text,
@@ -87,7 +91,7 @@ function FilterModalButton(props: FilterModalProps) {
animationType="fade"
presentationStyle="overFullScreen"
transparent={true}>
-
+
@@ -123,7 +127,7 @@ function FilterModalButton(props: FilterModalProps) {
-
+
>
);
@@ -230,6 +234,13 @@ export default function RNTesterPlatformTestResultView(
);
const [resultsExpanded, setResultsExpanded] = useState(false);
+ const [safeAreaInsets, setSafeAreaInsets] = useState(null);
+ const handleSafeAreaInsetsChange = useCallback(
+ (event: SafeAreaInsetsChangeEvent) => {
+ setSafeAreaInsets(event.nativeEvent.insets);
+ },
+ [],
+ );
const handleReset = useCallback(() => {
setFilterFailStatus(false);
@@ -266,12 +277,21 @@ export default function RNTesterPlatformTestResultView(
animationType="slide"
onRequestClose={handleMaximizedPress}
visible={resultsExpanded}>
-
+
Results
@@ -308,7 +328,7 @@ export default function RNTesterPlatformTestResultView(
-
+
>
);
diff --git a/private/helloworld/App.tsx b/private/helloworld/App.tsx
index e692a7dd84d1..19c0a8358ff8 100644
--- a/private/helloworld/App.tsx
+++ b/private/helloworld/App.tsx
@@ -8,8 +8,9 @@
*/
import * as React from 'react';
+import {useState} from 'react';
import {
- SafeAreaView,
+ type SafeAreaInsets,
ScrollView,
StatusBar,
StyleSheet,
@@ -20,9 +21,21 @@ import {
function App(): React.ReactNode {
const isDarkMode = useColorScheme() === 'dark';
+ const [insets, setInsets] = useState(null);
return (
-
+
+ setInsets(event.nativeEvent.insets)
+ }>
@@ -31,7 +44,7 @@ function App(): React.ReactNode {
-
+
);
}