diff --git a/src/backend/agent.js b/src/backend/agent.js index 3bde89b953..c96b05b0aa 100644 --- a/src/backend/agent.js +++ b/src/backend/agent.js @@ -63,6 +63,10 @@ export default class Agent extends EventEmitter { this._bridge = bridge; bridge.addListener('captureScreenshot', this.captureScreenshot); + bridge.addListener( + 'clearHighlightedElementInDOM', + this.clearHighlightedElementInDOM + ); bridge.addListener('exportProfilingSummary', this.exportProfilingSummary); bridge.addListener('getCommitDetails', this.getCommitDetails); bridge.addListener('getFiberCommits', this.getFiberCommits); @@ -216,14 +220,22 @@ export default class Agent extends EventEmitter { } }; + clearHighlightedElementInDOM = () => { + hideOverlay(); + }; + highlightElementInDOM = ({ displayName, + hideAfterTimeout, id, rendererID, + scrollIntoView, }: { displayName: string, + hideAfterTimeout: boolean, id: number, rendererID: number, + scrollIntoView: boolean, }) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { @@ -236,13 +248,12 @@ export default class Agent extends EventEmitter { } if (node != null) { - if (typeof node.scrollIntoView === 'function') { + if (scrollIntoView && typeof node.scrollIntoView === 'function') { // If the node isn't visible show it before highlighting it. // We may want to reconsider this; it might be a little disruptive. node.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } - - showOverlay(((node: any): HTMLElement), displayName); + showOverlay(((node: any): HTMLElement), displayName, hideAfterTimeout); } else { hideOverlay(); } @@ -466,6 +477,6 @@ export default class Agent extends EventEmitter { // Don't pass the name explicitly. // It will be inferred from DOM tag and Fiber owner. - showOverlay(target); + showOverlay(target, null, false); }; } diff --git a/src/backend/views/Highlighter.js b/src/backend/views/Highlighter.js index fc98d4a134..c5339f6763 100644 --- a/src/backend/views/Highlighter.js +++ b/src/backend/views/Highlighter.js @@ -18,7 +18,8 @@ export function hideOverlay() { export function showOverlay( element: HTMLElement | null, - componentName: string = '' + componentName: string | null, + hideAfterTimeout: boolean ) { if (timeoutID !== null) { clearTimeout(timeoutID); @@ -34,5 +35,7 @@ export function showOverlay( overlay.inspect(element, componentName); - timeoutID = setTimeout(hideOverlay, SHOW_DURATION); + if (hideAfterTimeout) { + timeoutID = setTimeout(hideOverlay, SHOW_DURATION); + } } diff --git a/src/devtools/views/Components/Element.js b/src/devtools/views/Components/Element.js index 14acf8ae2f..421195d5af 100644 --- a/src/devtools/views/Components/Element.js +++ b/src/devtools/views/Components/Element.js @@ -11,6 +11,7 @@ import React, { import { ElementTypeClass, ElementTypeFunction } from 'src/devtools/types'; import { createRegExp } from '../utils'; import { TreeContext } from './TreeContext'; +import { BridgeContext, StoreContext } from '../context'; import type { Element } from './types'; @@ -31,6 +32,9 @@ export default function ElementView({ index, style, data }: Props) { selectedElementID, selectElementByID, } = useContext(TreeContext); + const bridge = useContext(BridgeContext); + const store = useContext(StoreContext); + const element = getElementAtIndex(index); const id = element === null ? null : element.id; @@ -82,6 +86,21 @@ export default function ElementView({ index, style, data }: Props) { [id, selectElementByID] ); + const rendererID = id !== null ? store.getRendererIDForElement(id) : null; + // Individual elements don't have a corresponding leave handler. + // Instead, it's implemented on the tree level. + const handleMouseEnter = useCallback(() => { + if (element !== null && id !== null && rendererID !== null) { + bridge.send('highlightElementInDOM', { + displayName: element.displayName, + hideAfterTimeout: false, + id, + rendererID, + scrollIntoView: false, + }); + } + }, [bridge, element, id, rendererID]); + // Handle elements that are removed from the tree while an async render is in progress. if (element == null) { console.warn(` Could not find element at index ${index}`); @@ -100,6 +119,7 @@ export default function ElementView({ index, style, data }: Props) { return (
{}; } - const rendererID = store.getRendererIDForElement(id) || null; + const rendererID = store.getRendererIDForElement(id); // Update the $r variable. bridge.send('selectElement', { id, rendererID }); diff --git a/src/devtools/views/Components/Tree.js b/src/devtools/views/Components/Tree.js index 4b87eb77d1..0809b7851b 100644 --- a/src/devtools/views/Components/Tree.js +++ b/src/devtools/views/Components/Tree.js @@ -12,6 +12,7 @@ import AutoSizer from 'react-virtualized-auto-sizer'; import { FixedSizeList } from 'react-window'; import { TreeContext } from './TreeContext'; import { SettingsContext } from '../Settings/SettingsContext'; +import { BridgeContext } from '../context'; import Element from './Element'; import InspectHostNodesToggle from './InspectHostNodesToggle'; import OwnersStack from './OwnersStack'; @@ -32,6 +33,7 @@ export default function Tree(props: Props) { selectParentElementInTree, selectPreviousElementInTree, } = useContext(TreeContext); + const bridge = useContext(BridgeContext); const listRef = useRef | null>(null); const treeRef = useRef(null); @@ -112,13 +114,17 @@ export default function Tree(props: Props) { [baseDepth, numElements, getElementAtIndex, lastScrolledIDRef] ); + const handleMouseLeave = useCallback(() => { + bridge.send('clearHighlightedElementInDOM'); + }, [bridge]); + return (
{ownerStack.length > 0 ? : }
-
+
{({ height, width }) => (