Show highlight when element is selected in React devtools

Summary:
Changelog:
[General][Changed] - Copied and refactored the current devtools highlighting code from Inspector into its own module and add to the top level `AppContainer`. The effect is that the highlight stills shows without Inspector opened.

This diff copies the current devtools highlighting logic from Inspector into a module and add to the top level `AppContainer`. The effect is without Inspector opened, the highlight will still show.

## Context
This is the first diff for "Component Tab Automatically Highlight Elements". The idea is to replicate the behavior on Web to RN.

## Behavior
on Web:
- highlight shows whenever an element in the component list is hovered
- Selecting an element doesn't keeps the highlight showing.

on RN (before this diff):
- when RN inspector opens: selecting an element keeps the highlight showing
- when RN inspector closes: stop showing highlihgintg.

on RN(this diff)
- selecting an element keeps the highlight showing

## TODO
- See if highlighting event can be sent on hover, instead of when an element is selected.

Reviewed By: lunaruan

Differential Revision: D38568135

fbshipit-source-id: d168874677d08a9c5526a7f896943579da804565
This commit is contained in:
Tianyu Yao
2022-08-16 12:03:17 -07:00
committed by Facebook GitHub Bot
parent 6442543933
commit a63204800a
3 changed files with 93 additions and 45 deletions
@@ -0,0 +1,80 @@
/**
* 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 <ElementBox frame={inspected.frame} />;
}
return null;
}
-38
View File
@@ -144,8 +144,6 @@ class Inspector extends React.Component<
}
_attachToDevtools = (agent: Object) => {
agent.addListener('hideNativeHighlight', this._onAgentHideNativeHighlight);
agent.addListener('showNativeHighlight', this._onAgentShowNativeHighlight);
agent.addListener('shutdown', this._onAgentShutdown);
this.setState({
@@ -153,45 +151,9 @@ class Inspector extends React.Component<
});
};
_onAgentHideNativeHighlight = () => {
if (this.state.inspected === null) {
return;
}
// we wait to actually hide in order to avoid flicker
this._hideTimeoutID = setTimeout(() => {
this.setState({
inspected: null,
});
}, 100);
};
_onAgentShowNativeHighlight = (node: any) => {
clearTimeout(this._hideTimeoutID);
// Shape of `node` is different in Fabric.
const component = node.canonical ?? node;
component.measure((x, y, width, height, left, top) => {
this.setState({
hierarchy: [],
inspected: {
frame: {left, top, width, height},
},
});
});
};
_onAgentShutdown = () => {
const agent = this.state.devtoolsAgent;
if (agent != null) {
agent.removeListener(
'hideNativeHighlight',
this._onAgentHideNativeHighlight,
);
agent.removeListener(
'showNativeHighlight',
this._onAgentShowNativeHighlight,
);
agent.removeListener('shutdown', this._onAgentShutdown);
this.setState({devtoolsAgent: null});
+13 -7
View File
@@ -77,14 +77,19 @@ class AppContainer extends React.Component<Props, State> {
render(): React.Node {
let logBox = null;
let devtoolsHighlighter = null;
if (__DEV__) {
if (
!global.__RCTProfileIsProfiling &&
!this.props.internal_excludeLogBox
) {
const LogBoxNotificationContainer =
require('../LogBox/LogBoxNotificationContainer').default;
logBox = <LogBoxNotificationContainer />;
if (!global.__RCTProfileIsProfiling) {
if (!this.props.internal_excludeLogBox) {
const LogBoxNotificationContainer =
require('../LogBox/LogBoxNotificationContainer').default;
logBox = <LogBoxNotificationContainer />;
}
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__ != null) {
const DevtoolsHighlighter =
require('../Inspector/DevtoolsHighlighter').default;
devtoolsHighlighter = <DevtoolsHighlighter />;
}
}
}
@@ -118,6 +123,7 @@ class AppContainer extends React.Component<Props, State> {
<RootTagContext.Provider value={createRootTag(this.props.rootTag)}>
<View style={styles.appContainer} pointerEvents="box-none">
{!this.state.hasError && innerView}
{devtoolsHighlighter}
{this.state.inspector}
{logBox}
</View>