mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Changelog: [General][Added] - Highlight elements on hover while mouse down for React DevTools element inspection. Since there is probably no mouse hover events for RN, this diff implements something that works similar like hover for RN: user keeps the mouse down and moves the cursor around, and the elements under the mouse is highlighted just like Web. Reviewed By: lunaruan Differential Revision: D40369733 fbshipit-source-id: ef223ee0f31f4e0372674fc39dd13bad8c15aa92
190 lines
5.2 KiB
JavaScript
190 lines
5.2 KiB
JavaScript
/**
|
|
* 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 {PressEvent} from '../Types/CoreEventTypes';
|
|
import type {HostRef} from './getInspectorDataForViewAtPoint';
|
|
|
|
import View from '../Components/View/View';
|
|
import StyleSheet from '../StyleSheet/StyleSheet';
|
|
import Dimensions from '../Utilities/Dimensions';
|
|
import ElementBox from './ElementBox';
|
|
import * as React from 'react';
|
|
|
|
const {findNodeHandle} = require('../ReactNative/RendererProxy');
|
|
const getInspectorDataForViewAtPoint = require('./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 | {
|
|
frame: {+height: any, +left: any, +top: any, +width: any},
|
|
}>(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, closestInstance, frame} = viewData;
|
|
if (closestInstance != null || touchedViewTag != null) {
|
|
if (closestInstance != null) {
|
|
// Fabric
|
|
agent.selectNode(closestInstance);
|
|
} else {
|
|
agent.selectNode(findNodeHandle(touchedViewTag));
|
|
}
|
|
setInspected({
|
|
frame,
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
);
|
|
},
|
|
[inspectedView],
|
|
);
|
|
|
|
const onResponderRelease = useCallback(() => {
|
|
const agent = devToolsAgentRef.current;
|
|
if (agent == null) {
|
|
return;
|
|
}
|
|
agent.stopInspectingNative(true);
|
|
setIsInspecting(false);
|
|
setInspected(null);
|
|
}, []);
|
|
|
|
const shouldSetResponser = useCallback(
|
|
(e: PressEvent): boolean => {
|
|
findViewForTouchEvent(e);
|
|
return true;
|
|
},
|
|
[findViewForTouchEvent],
|
|
);
|
|
|
|
let highlight = inspected ? <ElementBox frame={inspected.frame} /> : null;
|
|
if (isInspecting) {
|
|
return (
|
|
<View
|
|
onStartShouldSetResponder={shouldSetResponser}
|
|
onResponderMove={findViewForTouchEvent}
|
|
onResponderRelease={onResponderRelease}
|
|
nativeID="devToolsInspectorOverlay"
|
|
style={[styles.inspector, {height: Dimensions.get('window').height}]}>
|
|
{highlight}
|
|
</View>
|
|
);
|
|
}
|
|
return highlight;
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
inspector: {
|
|
backgroundColor: 'transparent',
|
|
position: 'absolute',
|
|
left: 0,
|
|
top: 0,
|
|
right: 0,
|
|
},
|
|
});
|