diff --git a/Libraries/Inspector/DevtoolsHighlighter.js b/Libraries/Inspector/DevtoolsHighlighter.js
deleted file mode 100644
index c3d6f1c3a3fbc3..00000000000000
--- a/Libraries/Inspector/DevtoolsHighlighter.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * 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.
- *
- * @format
- * @flow
- */
-
-import ElementBox from './ElementBox';
-import * as React from 'react';
-const {useEffect, useState} = React;
-
-const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
-
-export default function DevtoolsHighlighter(): React.Node {
- const [inspected, setInspected] = useState(null);
- useEffect(() => {
- let devToolsAgent = null;
- let hideTimeoutId = null;
-
- function onAgentHideNativeHighlight() {
- // we wait to actually hide in order to avoid flicker
- clearTimeout(hideTimeoutId);
- hideTimeoutId = setTimeout(() => {
- setInspected(null);
- }, 100);
- }
-
- function onAgentShowNativeHighlight(node: any) {
- clearTimeout(hideTimeoutId);
- // Shape of `node` is different in Fabric.
- const component = node.canonical ?? node;
-
- component.measure((x, y, width, height, left, top) => {
- setInspected({
- frame: {left, top, width, height},
- });
- });
- }
-
- function cleanup() {
- const currentAgent = devToolsAgent;
- if (currentAgent != null) {
- currentAgent.removeListener(
- 'hideNativeHighlight',
- onAgentHideNativeHighlight,
- );
- currentAgent.removeListener(
- 'showNativeHighlight',
- onAgentShowNativeHighlight,
- );
- currentAgent.removeListener('shutdown', cleanup);
- devToolsAgent = null;
- }
- }
-
- function _attachToDevtools(agent: Object) {
- devToolsAgent = agent;
- agent.addListener('hideNativeHighlight', onAgentHideNativeHighlight);
- agent.addListener('showNativeHighlight', onAgentShowNativeHighlight);
- agent.addListener('shutdown', cleanup);
- }
-
- hook.on('react-devtools', _attachToDevtools);
- if (hook.reactDevtoolsAgent) {
- _attachToDevtools(hook.reactDevtoolsAgent);
- }
- return () => {
- hook.off('react-devtools', _attachToDevtools);
- cleanup();
- };
- }, []);
-
- if (inspected != null) {
- return ;
- }
- return null;
-}
diff --git a/Libraries/Inspector/DevtoolsOverlay.js b/Libraries/Inspector/DevtoolsOverlay.js
new file mode 100644
index 00000000000000..6df5593becb5f4
--- /dev/null
+++ b/Libraries/Inspector/DevtoolsOverlay.js
@@ -0,0 +1,167 @@
+/**
+ * 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.
+ *
+ * @format
+ * @flow
+ */
+
+import ElementBox from './ElementBox';
+import * as React from 'react';
+import type {PressEvent} from '../Types/CoreEventTypes';
+import View from '../Components/View/View';
+import StyleSheet from '../StyleSheet/StyleSheet';
+import Dimensions from '../Utilities/Dimensions';
+const getInspectorDataForViewAtPoint = require('./getInspectorDataForViewAtPoint');
+const ReactNative = require('../Renderer/shims/ReactNative');
+
+import type {HostRef} from './getInspectorDataForViewAtPoint';
+
+const {useEffect, useState, useCallback, useRef} = React;
+
+const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+
+export default function DevtoolsOverlay({
+ inspectedView,
+}: {
+ inspectedView: ?HostRef,
+}): React.Node {
+ const [inspected, setInspected] = useState(null);
+ const [isInspecting, setIsInspecting] = useState(false);
+ const devToolsAgentRef = useRef(null);
+
+ useEffect(() => {
+ let devToolsAgent = null;
+ let hideTimeoutId = null;
+
+ function onAgentHideNativeHighlight() {
+ // we wait to actually hide in order to avoid flicker
+ clearTimeout(hideTimeoutId);
+ hideTimeoutId = setTimeout(() => {
+ setInspected(null);
+ }, 100);
+ }
+
+ function onAgentShowNativeHighlight(node: any) {
+ clearTimeout(hideTimeoutId);
+ // Shape of `node` is different in Fabric.
+ const component = node.canonical ?? node;
+
+ component.measure((x, y, width, height, left, top) => {
+ setInspected({
+ frame: {left, top, width, height},
+ });
+ });
+ }
+
+ function cleanup() {
+ const currentAgent = devToolsAgent;
+ if (currentAgent != null) {
+ currentAgent.removeListener(
+ 'hideNativeHighlight',
+ onAgentHideNativeHighlight,
+ );
+ currentAgent.removeListener(
+ 'showNativeHighlight',
+ onAgentShowNativeHighlight,
+ );
+ currentAgent.removeListener('shutdown', cleanup);
+ currentAgent.removeListener(
+ 'startInspectingNative',
+ onStartInspectingNative,
+ );
+ currentAgent.removeListener(
+ 'stopInspectingNative',
+ onStopInspectingNative,
+ );
+ devToolsAgent = null;
+ }
+ devToolsAgentRef.current = null;
+ }
+
+ function onStartInspectingNative() {
+ setIsInspecting(true);
+ }
+
+ function onStopInspectingNative() {
+ setIsInspecting(false);
+ }
+
+ function _attachToDevtools(agent: Object) {
+ devToolsAgent = agent;
+ devToolsAgentRef.current = agent;
+ agent.addListener('hideNativeHighlight', onAgentHideNativeHighlight);
+ agent.addListener('showNativeHighlight', onAgentShowNativeHighlight);
+ agent.addListener('shutdown', cleanup);
+ agent.addListener('startInspectingNative', onStartInspectingNative);
+ agent.addListener('stopInspectingNative', onStopInspectingNative);
+ }
+
+ hook.on('react-devtools', _attachToDevtools);
+ if (hook.reactDevtoolsAgent) {
+ _attachToDevtools(hook.reactDevtoolsAgent);
+ }
+ return () => {
+ hook.off('react-devtools', _attachToDevtools);
+ cleanup();
+ };
+ }, []);
+
+ const findViewForTouchEvent = useCallback(
+ (e: PressEvent) => {
+ const agent = devToolsAgentRef.current;
+ if (agent == null) {
+ return;
+ }
+ const {locationX, locationY} = e.nativeEvent.touches[0];
+ getInspectorDataForViewAtPoint(
+ inspectedView,
+ locationX,
+ locationY,
+ viewData => {
+ const {touchedViewTag} = viewData;
+ if (touchedViewTag != null) {
+ agent.selectNode(ReactNative.findNodeHandle(touchedViewTag));
+ return true;
+ }
+ return false;
+ },
+ );
+ },
+ [inspectedView],
+ );
+
+ const shouldSetResponser = useCallback(
+ (e: PressEvent): boolean => {
+ findViewForTouchEvent(e);
+ return true;
+ },
+ [findViewForTouchEvent],
+ );
+
+ let highlight = inspected ? : null;
+ if (isInspecting) {
+ return (
+
+ {highlight}
+
+ );
+ }
+ return highlight;
+}
+
+const styles = StyleSheet.create({
+ inspector: {
+ backgroundColor: 'transparent',
+ position: 'absolute',
+ left: 0,
+ top: 0,
+ right: 0,
+ },
+});
diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js
index 504c6963f5386a..ff2a2eda91c34d 100644
--- a/Libraries/Inspector/Inspector.js
+++ b/Libraries/Inspector/Inspector.js
@@ -20,70 +20,18 @@ const ReactNative = require('../Renderer/shims/ReactNative');
const StyleSheet = require('../StyleSheet/StyleSheet');
const View = require('../Components/View/View');
const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes');
+const getInspectorDataForViewAtPoint = require('./getInspectorDataForViewAtPoint');
-const invariant = require('invariant');
-
-import type {
- HostComponent,
- TouchedViewDataAtPoint,
-} from '../Renderer/shims/ReactNativeTypes';
-
-type HostRef = React.ElementRef>;
-
-export type ReactRenderer = {
- rendererConfig: {
- getInspectorDataForViewAtPoint: (
- inspectedView: ?HostRef,
- locationX: number,
- locationY: number,
- callback: Function,
- ) => void,
- ...
- },
-};
+import type {TouchedViewDataAtPoint} from '../Renderer/shims/ReactNativeTypes';
+import type {HostRef} from './getInspectorDataForViewAtPoint';
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
-const renderers = findRenderers();
// Required for React DevTools to view/edit React Native styles in Flipper.
// Flipper doesn't inject these values when initializing DevTools.
hook.resolveRNStyle = require('../StyleSheet/flattenStyle');
hook.nativeStyleEditorValidAttributes = Object.keys(ReactNativeStyleAttributes);
-function findRenderers(): $ReadOnlyArray {
- const allRenderers = Array.from(hook.renderers.values());
- invariant(
- allRenderers.length >= 1,
- 'Expected to find at least one React Native renderer on DevTools hook.',
- );
- return allRenderers;
-}
-
-function getInspectorDataForViewAtPoint(
- inspectedView: ?HostRef,
- locationX: number,
- locationY: number,
- callback: (viewData: TouchedViewDataAtPoint) => void,
-) {
- // Check all renderers for inspector data.
- for (let i = 0; i < renderers.length; i++) {
- const renderer = renderers[i];
- if (renderer?.rendererConfig?.getInspectorDataForViewAtPoint != null) {
- renderer.rendererConfig.getInspectorDataForViewAtPoint(
- inspectedView,
- locationX,
- locationY,
- viewData => {
- // Only return with non-empty view data since only one renderer will have this view.
- if (viewData && viewData.hierarchy.length > 0) {
- callback(viewData);
- }
- },
- );
- }
- }
-}
-
class Inspector extends React.Component<
{
inspectedView: ?HostRef,
@@ -221,6 +169,7 @@ class Inspector extends React.Component<
this._setTouchedViewData(viewData);
this._setTouchedViewData = null;
}
+ return false;
},
);
}
diff --git a/Libraries/Inspector/getInspectorDataForViewAtPoint.js b/Libraries/Inspector/getInspectorDataForViewAtPoint.js
new file mode 100644
index 00000000000000..94d2a915e9ebe4
--- /dev/null
+++ b/Libraries/Inspector/getInspectorDataForViewAtPoint.js
@@ -0,0 +1,71 @@
+/**
+ * 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.
+ *
+ * @format
+ * @flow
+ */
+
+import type {
+ HostComponent,
+ TouchedViewDataAtPoint,
+} from '../Renderer/shims/ReactNativeTypes';
+
+export type HostRef = React.ElementRef>;
+export type ReactRenderer = {
+ rendererConfig: {
+ getInspectorDataForViewAtPoint: (
+ inspectedView: ?HostRef,
+ locationX: number,
+ locationY: number,
+ callback: Function,
+ ) => void,
+ ...
+ },
+};
+
+const React = require('react');
+const invariant = require('invariant');
+
+const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
+const renderers = findRenderers();
+
+function findRenderers(): $ReadOnlyArray {
+ const allRenderers = Array.from(hook.renderers.values());
+ invariant(
+ allRenderers.length >= 1,
+ 'Expected to find at least one React Native renderer on DevTools hook.',
+ );
+ return allRenderers;
+}
+
+module.exports = function getInspectorDataForViewAtPoint(
+ inspectedView: ?HostRef,
+ locationX: number,
+ locationY: number,
+ callback: (viewData: TouchedViewDataAtPoint) => boolean,
+) {
+ let shouldBreak = false;
+ // Check all renderers for inspector data.
+ for (let i = 0; i < renderers.length; i++) {
+ if (shouldBreak) {
+ break;
+ }
+ const renderer = renderers[i];
+ if (renderer?.rendererConfig?.getInspectorDataForViewAtPoint != null) {
+ renderer.rendererConfig.getInspectorDataForViewAtPoint(
+ inspectedView,
+ locationX,
+ locationY,
+ viewData => {
+ // Only return with non-empty view data since only one renderer will have this view.
+ if (viewData && viewData.hierarchy.length > 0) {
+ shouldBreak = callback(viewData);
+ }
+ },
+ );
+ }
+ }
+};
diff --git a/Libraries/ReactNative/AppContainer.js b/Libraries/ReactNative/AppContainer.js
index ccf04260152b34..c49cf04ced8520 100644
--- a/Libraries/ReactNative/AppContainer.js
+++ b/Libraries/ReactNative/AppContainer.js
@@ -29,6 +29,7 @@ type Props = $ReadOnly<{|
type State = {|
inspector: ?React.Node,
+ devtoolsOverlay: ?React.Node,
mainKey: number,
hasError: boolean,
|};
@@ -36,6 +37,7 @@ type State = {|
class AppContainer extends React.Component {
state: State = {
inspector: null,
+ devtoolsOverlay: null,
mainKey: 1,
hasError: false,
};
@@ -65,6 +67,14 @@ class AppContainer extends React.Component {
this.setState({inspector});
},
);
+ if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) {
+ const DevtoolsOverlay =
+ require('../Inspector/DevtoolsOverlay').default;
+ const devtoolsOverlay = (
+
+ );
+ this.setState({devtoolsOverlay});
+ }
}
}
}
@@ -77,7 +87,6 @@ class AppContainer extends React.Component {
render(): React.Node {
let logBox = null;
- let devtoolsHighlighter = null;
if (__DEV__) {
if (!global.__RCTProfileIsProfiling) {
if (!this.props.internal_excludeLogBox) {
@@ -85,17 +94,12 @@ class AppContainer extends React.Component {
require('../LogBox/LogBoxNotificationContainer').default;
logBox = ;
}
- if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) {
- const DevtoolsHighlighter =
- require('../Inspector/DevtoolsHighlighter').default;
- devtoolsHighlighter = ;
- }
}
}
let innerView: React.Node = (
{
{!this.state.hasError && innerView}
- {devtoolsHighlighter}
+ {this.state.devtoolsOverlay}
{this.state.inspector}
{logBox}