Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,19 @@
*/

import type {HostInstance} from '../../../src/private/types/HostInstance';
import type {ViewProps} from '../View/ViewPropTypes';

import Platform from '../../Utilities/Platform';
import View from '../View/View';
import * as React from 'react';
import SafeAreaView_INTERNAL_DO_NOT_USE from '../../../src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';

export type SafeAreaViewInstance = HostInstance;

/**
* Renders content within the safe area boundaries of a device. Currently only applicable to iOS devices with iOS version 11 or later. Automatically applies padding to reflect the portion of the view not covered by navigation bars, tab bars, toolbars, and other ancestor views.
* Renders content within the safe area boundaries of a device, by applying the
* part of the view that is covered by the system UI as padding.
*
* @see https://reactnative.dev/docs/safeareaview
* @deprecated Use `react-native-safe-area-context` instead.
* @platform ios
*/
const SafeAreaView: component(
ref?: React.RefSetter<SafeAreaViewInstance>,
...props: ViewProps
) = Platform.select({
ios: require('./RCTSafeAreaViewNativeComponent').default,
default: View,
});
const SafeAreaView: typeof SafeAreaView_INTERNAL_DO_NOT_USE =
SafeAreaView_INTERNAL_DO_NOT_USE;

export default SafeAreaView;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions packages/react-native/Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
LayoutRectangle,
MouseEvent,
PointerEvent,
SafeAreaInsetsChangeEvent,
} from '../../Types/CoreEventTypes';
import type {
AccessibilityActionEvent,
Expand Down Expand Up @@ -63,6 +64,26 @@ type DirectEventProps = Readonly<{
*/
onLayout?: ?(event: LayoutChangeEvent) => unknown,

/**
* Invoked when the part of this view that is covered by the system UI
* (status bar, navigation bar, home indicator, display cutouts, ...) or the
* position of this view in the window changes, with:
*
* `{nativeEvent: {insets: {top, right, bottom, left}, frame: {x, y, width, height}}}`
*
* `insets` are relative to this view: an inset is only non-zero for the part
* of the view that actually overlaps the system UI.
*
* The event is dispatched synchronously, so the rendering it schedules is
* applied in the same frame the insets changed in.
*
* Setting this prop makes the view observe safe area changes; views without
* it are unaffected.
*
* @experimental
*/
onSafeAreaInsetsChange?: ?(event: SafeAreaInsetsChangeEvent) => unknown,

/**
* When `accessible` is `true`, the system will invoke this function when the
* user performs the magic tap gesture.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import type {HostInstance} from 'react-native/src/private/types/HostInstance';

import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {View} from 'react-native';
import SafeAreaView from 'react-native/src/private/components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';

const INSETS = {top: 44, right: 0, bottom: 34, left: 0};
const FRAME = {x: 0, y: 0, width: 390, height: 844};

describe('onSafeAreaInsetsChange', () => {
it('delivers the insets and the frame of the view', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();
const onSafeAreaInsetsChange = jest.fn();

Fantom.runTask(() => {
root.render(
<View
collapsable={false}
ref={nodeRef}
onSafeAreaInsetsChange={event => {
onSafeAreaInsetsChange(event.nativeEvent);
}}
/>,
);
});

Fantom.dispatchNativeEvent(nodeRef, 'safeAreaInsetsChange', {
insets: INSETS,
frame: FRAME,
});

expect(onSafeAreaInsetsChange).toHaveBeenCalledTimes(1);
const [event] = onSafeAreaInsetsChange.mock.lastCall;
expect(event.insets).toEqual(INSETS);
expect(event.frame).toEqual(FRAME);
});

it('is not delivered to views that did not opt in', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();

Fantom.runTask(() => {
root.render(<View collapsable={false} ref={nodeRef} />);
});

// The prop is what makes the view observe the safe area, so a view without
// it is never the target of the event.
expect(
root.getRenderedOutput({props: ['onSafeAreaInsetsChange']}).toJSX(),
).toEqual(<rn-view />);
});

it('is reflected in the props of the view when set', () => {
const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(
<View collapsable={false} onSafeAreaInsetsChange={() => {}} />,
);
});

expect(
root.getRenderedOutput({props: ['onSafeAreaInsetsChange']}).toJSX(),
).toEqual(<rn-view onSafeAreaInsetsChange="true" />);
});
});

describe('<SafeAreaView>', () => {
it('applies the insets it receives as padding', () => {
const root = Fantom.createRoot();
const nodeRef = createRef<HostInstance>();

Fantom.runTask(() => {
root.render(<SafeAreaView collapsable={false} ref={nodeRef} />);
});

expect(
root
.getRenderedOutput({
props: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
})
.toJSX(),
).toEqual(<rn-view />);

Fantom.dispatchNativeEvent(nodeRef, 'safeAreaInsetsChange', {
insets: INSETS,
frame: FRAME,
});

expect(
root
.getRenderedOutput({
props: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
})
.toJSX(),
).toEqual(
<rn-view
paddingBottom="34"
paddingLeft="0"
paddingRight="0"
paddingTop="44"
/>,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -27,13 +26,10 @@ type Props = Readonly<{
level: LogLevel,
}>;

const LogBoxInspectorHeaderSafeArea: React.ComponentType<ViewProps> =
Platform.OS === 'android' ? View : SafeAreaView;

export default function LogBoxInspectorHeader(props: Props): React.Node {
if (props.level === 'syntax') {
return (
<LogBoxInspectorHeaderSafeArea style={styles[props.level]}>
<SafeAreaView style={styles[props.level]}>
<View style={styles.header}>
<View style={styles.title}>
<Text
Expand All @@ -44,7 +40,7 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
</Text>
</View>
</View>
</LogBoxInspectorHeaderSafeArea>
</SafeAreaView>
);
}

Expand All @@ -56,7 +52,7 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
const titleText = `Log ${props.selectedIndex + 1} of ${props.total}`;

return (
<LogBoxInspectorHeaderSafeArea style={styles[props.level]}>
<SafeAreaView style={styles[props.level]}>
<View style={styles.header}>
<LogBoxInspectorHeaderButton
id="logbox_header_button_prev"
Expand All @@ -81,7 +77,7 @@ export default function LogBoxInspectorHeader(props: Props): React.Node {
onPress={() => props.onSelectIndex(nextIndex)}
/>
</View>
</LogBoxInspectorHeaderSafeArea>
</SafeAreaView>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`LogBoxNotificationContainer should render both an error and warning notification 1`] = `
<RCTSafeAreaView
<View
onSafeAreaInsetsChange={[Function]}
style={
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
}
Array [
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
},
null,
]
}
>
<View
Expand Down Expand Up @@ -101,7 +105,7 @@ exports[`LogBoxNotificationContainer should render both an error and warning not
totalLogCount={1}
/>
</View>
</RCTSafeAreaView>
</View>
`;

exports[`LogBoxNotificationContainer should render null with no logs 1`] = `null`;
Expand All @@ -113,14 +117,18 @@ 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`] = `
<RCTSafeAreaView
<View
onSafeAreaInsetsChange={[Function]}
style={
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
}
Array [
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
},
null,
]
}
>
<View
Expand Down Expand Up @@ -168,18 +176,22 @@ exports[`LogBoxNotificationContainer should render the latest error notification
totalLogCount={2}
/>
</View>
</RCTSafeAreaView>
</View>
`;

exports[`LogBoxNotificationContainer should render the latest warning notification 1`] = `
<RCTSafeAreaView
<View
onSafeAreaInsetsChange={[Function]}
style={
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
}
Array [
Object {
"bottom": 20,
"left": 10,
"position": "absolute",
"right": 10,
},
null,
]
}
>
<View
Expand Down Expand Up @@ -227,5 +239,5 @@ exports[`LogBoxNotificationContainer should render the latest warning notificati
totalLogCount={2}
/>
</View>
</RCTSafeAreaView>
</View>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ const directEventTypes = {
topLayout: {
registrationName: 'onLayout',
},
topSafeAreaInsetsChange: {
registrationName: 'onSafeAreaInsetsChange',
},
};

const validAttributesForNonEventProps = {
Expand Down Expand Up @@ -402,6 +405,7 @@ const validAttributesForNonEventProps = {
// Props for bubbling and direct events
const validAttributesForEventProps = {
onLayout: true,
onSafeAreaInsetsChange: true,

// PanResponder handlers
onMoveShouldSetResponder: true,
Expand Down
Loading
Loading