Select DOM nodes on hover
Dan Abramov committed
Apr 8, 2019 at 14:26 UTC
39223239bbcea68f801d2d3607c5326099e22e91
5 files changed
+47
-7
src/backend/agent.js
+15
-4
@@ -63,6 +63,10 @@ export default class Agent extends EventEmitter {
63
this._bridge = bridge;
64
65
bridge.addListener('captureScreenshot', this.captureScreenshot);
66
+ bridge.addListener(
67
+ 'clearHighlightedElementInDOM',
68
+ this.clearHighlightedElementInDOM
69
+ );
70
bridge.addListener('exportProfilingSummary', this.exportProfilingSummary);
71
bridge.addListener('getCommitDetails', this.getCommitDetails);
72
bridge.addListener('getFiberCommits', this.getFiberCommits);
@@ -216,14 +220,22 @@ export default class Agent extends EventEmitter {
220
}
221
};
222
223
+ clearHighlightedElementInDOM = () => {
224
+ hideOverlay();
225
+ };
226
+
227
highlightElementInDOM = ({
228
displayName,
229
id,
230
+ isSticky,
231
rendererID,
232
+ scrollIntoView,
233
}: {
234
displayName: string,
235
id: number,
236
+ isSticky: boolean,
237
rendererID: number,
238
+ scrollIntoView: boolean,
239
}) => {
240
const renderer = this._rendererInterfaces[rendererID];
241
if (renderer == null) {
@@ -236,13 +248,12 @@ export default class Agent extends EventEmitter {
248
}
249
250
if (node != null) {
239
- if (typeof node.scrollIntoView === 'function') {
251
+ if (scrollIntoView && typeof node.scrollIntoView === 'function') {
252
// If the node isn't visible show it before highlighting it.
253
// We may want to reconsider this; it might be a little disruptive.
254
node.scrollIntoView({ block: 'nearest', inline: 'nearest' });
255
}
244
-
245
- showOverlay(((node: any): HTMLElement), displayName);
256
+ showOverlay(((node: any): HTMLElement), displayName, isSticky);
257
} else {
258
hideOverlay();
259
}
@@ -466,6 +477,6 @@ export default class Agent extends EventEmitter {
477
478
// Don't pass the name explicitly.
479
// It will be inferred from DOM tag and Fiber owner.
469
- showOverlay(target);
480
+ showOverlay(target, null, true);
481
};
482
}
src/backend/views/Highlighter.js
+5
-2
@@ -18,7 +18,8 @@ export function hideOverlay() {
18
19
export function showOverlay(
20
element: HTMLElement | null,
21
- componentName: string = ''
21
+ componentName: string | null,
22
+ isSticky: boolean
23
) {
24
if (timeoutID !== null) {
25
clearTimeout(timeoutID);
@@ -34,5 +35,7 @@ export function showOverlay(
35
36
overlay.inspect(element, componentName);
37
37
- timeoutID = setTimeout(hideOverlay, SHOW_DURATION);
38
+ if (!isSticky) {
39
+ timeoutID = setTimeout(hideOverlay, SHOW_DURATION);
40
+ }
41
}
src/devtools/views/Components/Element.js
+18
@@ -11,6 +11,7 @@ import React, {
11
import { ElementTypeClass, ElementTypeFunction } from 'src/devtools/types';
12
import { createRegExp } from '../utils';
13
import { TreeContext } from './TreeContext';
14
+import { BridgeContext, StoreContext } from '../context';
15
16
import type { Element } from './types';
17
@@ -31,6 +32,9 @@ export default function ElementView({ index, style, data }: Props) {
32
selectedElementID,
33
selectElementByID,
34
} = useContext(TreeContext);
35
+ const bridge = useContext(BridgeContext);
36
+ const store = useContext(StoreContext);
37
+
38
const element = getElementAtIndex(index);
39
40
const id = element === null ? null : element.id;
@@ -82,6 +86,19 @@ export default function ElementView({ index, style, data }: Props) {
86
[id, selectElementByID]
87
);
88
89
+ const rendererID = store.getRendererIDForElement(element.id) || null;
90
+ const handleMouseEnter = useCallback(() => {
91
+ if (rendererID !== null) {
92
+ bridge.send('highlightElementInDOM', {
93
+ displayName: element.displayName,
94
+ id: element.id,
95
+ rendererID,
96
+ scrollIntoView: false,
97
+ isSticky: true,
98
+ });
99
+ }
100
+ }, [bridge, element, rendererID]);
101
+
102
// Handle elements that are removed from the tree while an async render is in progress.
103
if (element == null) {
104
console.warn(`<ElementView> Could not find element at index ${index}`);
@@ -100,6 +117,7 @@ export default function ElementView({ index, style, data }: Props) {
117
return (
118
<div
119
className={isSelected ? styles.SelectedElement : styles.Element}
120
+ onMouseEnter={handleMouseEnter}
121
onMouseDown={handleMouseDown}
122
onDoubleClick={handleDoubleClick}
123
style={{
src/devtools/views/Components/SelectedElement.js
+2
@@ -47,6 +47,8 @@ export default function SelectedElement(_: Props) {
47
displayName: element.displayName,
48
id: selectedElementID,
49
rendererID,
50
+ scrollIntoView: true,
51
+ isSticky: false,
52
});
53
}
54
}
src/devtools/views/Components/Tree.js
+7
-1
@@ -12,6 +12,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
12
import { FixedSizeList } from 'react-window';
13
import { TreeContext } from './TreeContext';
14
import { SettingsContext } from '../Settings/SettingsContext';
15
+import { BridgeContext } from '../context';
16
import Element from './Element';
17
import InspectHostNodesToggle from './InspectHostNodesToggle';
18
import OwnersStack from './OwnersStack';
@@ -32,6 +33,7 @@ export default function Tree(props: Props) {
33
selectParentElementInTree,
34
selectPreviousElementInTree,
35
} = useContext(TreeContext);
36
+ const bridge = useContext(BridgeContext);
37
const listRef = useRef<FixedSizeList<any> | null>(null);
38
const treeRef = useRef<HTMLDivElement | null>(null);
39
@@ -112,13 +114,17 @@ export default function Tree(props: Props) {
114
[baseDepth, numElements, getElementAtIndex, lastScrolledIDRef]
115
);
116
117
+ const handleMouseLeave = useCallback(() => {
118
+ bridge.send('clearHighlightedElementInDOM');
119
+ }, [bridge]);
120
+
121
return (
122
<div className={styles.Tree} ref={treeRef}>
123
<div className={styles.SearchInput}>
124
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
125
<InspectHostNodesToggle />
126
</div>
121
- <div className={styles.AutoSizerWrapper}>
127
+ <div className={styles.AutoSizerWrapper} onMouseLeave={handleMouseLeave}>
128
<AutoSizer>
129
{({ height, width }) => (
130
<FixedSizeList