diff --git a/Libraries/ReactNative/AppRegistry.js b/Libraries/ReactNative/AppRegistry.js index aaef5ac184de8b..c18b042fba675f 100644 --- a/Libraries/ReactNative/AppRegistry.js +++ b/Libraries/ReactNative/AppRegistry.js @@ -112,6 +112,7 @@ const AppRegistry = { ), appParameters.initialProps, appParameters.rootTag, + appParameters.initialLayoutContext, wrapperComponentProvider && wrapperComponentProvider(appParameters), appParameters.fabric, false, diff --git a/Libraries/ReactNative/RootViewLayout.js b/Libraries/ReactNative/RootViewLayout.js new file mode 100644 index 00000000000000..8a1f17bdcadb68 --- /dev/null +++ b/Libraries/ReactNative/RootViewLayout.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow local-strict + * @format + */ + +const React = require('React'); +const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); + +import type {Layout} from 'CoreEventTypes'; + +export type LayoutContext = $ReadOnly<{| + layout: Layout, + safeAreaInsets: $ReadOnly<{| + top: number, + right: number, + bottom: number, + left: number, + |}>, +|}>; + +/** + * Context used to provide layout metrics from the root view the component + * tree is being rendered in. This is useful when sync measurements are + * required. + */ +const Context: React.Context = React.createContext({ + layout: {x: 0, y: 0, width: 0, height: 0}, + safeAreaInsets: {top: 0, right: 0, bottom: 0, left: 0}, +}); + +type Props = {| + children: React.Node, + initialLayoutContext: LayoutContext, + rootTag: number, +|}; + +function RootViewLayoutManager({ + children, + initialLayoutContext, + rootTag, +}: Props) { + const [layoutContext, setLayoutContext] = React.useState( + initialLayoutContext, + ); + React.useLayoutEffect(() => { + const subscription = RCTDeviceEventEmitter.addListener( + 'didUpdateLayoutContext', + event => { + if (rootTag === event.rootTag) { + setLayoutContext(event.layoutContext); + } + }, + ); + return () => { + subscription.remove(); + }; + }, [rootTag]); + + return {children}; +} + +module.exports = { + Context, + Manager: RootViewLayoutManager, +}; diff --git a/Libraries/ReactNative/renderApplication.js b/Libraries/ReactNative/renderApplication.js index 3fa9deff700ec5..5604f39b6b9d15 100644 --- a/Libraries/ReactNative/renderApplication.js +++ b/Libraries/ReactNative/renderApplication.js @@ -16,9 +16,12 @@ import type {IPerformanceLogger} from 'createPerformanceLogger'; import PerformanceLoggerContext from 'PerformanceLoggerContext'; const React = require('React'); const ReactFabricIndicator = require('ReactFabricIndicator'); +const RootViewLayout = require('RootViewLayout'); const invariant = require('invariant'); +import type {LayoutContext} from 'RootViewLayout'; + // require BackHandler so it sets the default handler that exits the app if no listeners respond require('BackHandler'); @@ -26,6 +29,7 @@ function renderApplication( RootComponent: React.ComponentType, initialProps: Props, rootTag: any, + initialLayoutContext: LayoutContext, WrapperComponent?: ?React.ComponentType<*>, fabric?: boolean, showFabricIndicator?: boolean, @@ -36,12 +40,16 @@ function renderApplication( let renderable = ( - - - {fabric === true && showFabricIndicator === true ? ( - - ) : null} - + + + + {fabric === true && showFabricIndicator === true ? ( + + ) : null} + + ); diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index a1efcb1ec7913b..ba30409e432955 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -71,6 +71,9 @@ module.exports = { get KeyboardAvoidingView() { return require('KeyboardAvoidingView'); }, + get LayoutContext() { + return require('RootViewLayout').Context.Consumer; + }, get MaskedViewIOS() { warnOnce( 'maskedviewios-moved', diff --git a/RNTester/js/LayoutContextExample.js b/RNTester/js/LayoutContextExample.js new file mode 100644 index 00000000000000..b5c81437e59e68 --- /dev/null +++ b/RNTester/js/LayoutContextExample.js @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +const React = require('react'); +const { + LayoutContext, + StyleSheet, + View, + Text, + StatusBar, + Button, + Platform, +} = require('react-native'); + +const BORDER_WIDTH = 2; + +type Props = { + onExampleExit: () => void, +}; + +type State = { + hidden: boolean, + translucent: boolean, +}; + +class LayoutContextExample extends React.Component { + static title = ''; + static description = + 'LayoutContext allows getting layout metrics for the current root view.'; + static external = true; + + state = { + hidden: false, + translucent: false, + }; + + render() { + return ( + + {ctx => { + return ( + <> +