[DevTools] - Highlight rendered by elements on hover. (#18479)
* [DevTools] - Highlight rendered by elements on hover. * Fixed formatting issue. * DevTools - Extracted highlight logic to custom hook. Added highlight support for rendered by elements. * Removed unnecessary padding style * Removed unnecessary wrapper function. Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
Hristo Kanchev committed
Apr 3, 2020 at 19:49 UTC
7785a5263e0bbbdcb997a84a840c9c039fcf6e5d
4 files changed
+74
-32
packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.css
+14
-4
@@ -82,7 +82,6 @@
82
.Owner {
83
border-radius: 0.25rem;
84
padding: 0.125rem 0.25rem;
85
- cursor: pointer;
85
background: none;
86
border: none;
87
display: block;
@@ -107,12 +106,23 @@
106
}
107
108
.OwnerButton {
109
+ cursor: pointer;
110
+ width: 100%;
111
+ padding: 0;
112
+}
113
+
114
+.OwnerContent {
115
display: flex;
116
align-items: center;
112
- margin-left: 0.5rem;
113
- padding: 0;
117
+ padding-left: 1rem;
118
+ width: 100%;
119
+ border-radius: 0.25rem;
120
+}
121
+
122
+.OwnerContent:hover {
123
+ background-color: var(--color-background-hover);
124
}
125
126
.ContextMenuIcon {
127
margin-right: 0.5rem;
118
-}
\ No newline at end of file
128
+}
packages/react-devtools-shared/src/devtools/views/Components/SelectedElement.js
+19
-6
@@ -26,6 +26,7 @@ import ViewElementSourceContext from './ViewElementSourceContext';
26
import NativeStyleEditor from './NativeStyleEditor';
27
import Toggle from '../Toggle';
28
import Badge from './Badge';
29
+import {useHighlightNativeElement} from '../hooks';
30
import {
31
ComponentFilterElementType,
32
ElementTypeClass,
@@ -522,6 +523,10 @@ function OwnerView({
523
type,
524
}: OwnerViewProps) {
525
const dispatch = useContext(TreeDispatcherContext);
526
+ const {
527
+ highlightNativeElement,
528
+ clearHighlightNativeElement,
529
+ } = useHighlightNativeElement();
530
531
const handleClick = useCallback(
532
() =>
@@ -532,18 +537,26 @@ function OwnerView({
537
[dispatch, id],
538
);
539
540
+ const onMouseEnter = () => highlightNativeElement(id);
541
+
542
+ const onMouseLeave = clearHighlightNativeElement;
543
+
544
return (
545
<Button
546
key={id}
547
className={styles.OwnerButton}
548
disabled={!isInStore}
540
- onClick={handleClick}>
541
- <span
542
- className={`${styles.Owner} ${isInStore ? '' : styles.NotInStore}`}
543
- title={displayName}>
544
- {displayName}
549
+ onClick={handleClick}
550
+ onMouseEnter={onMouseEnter}
551
+ onMouseLeave={onMouseLeave}>
552
+ <span className={styles.OwnerContent}>
553
+ <span
554
+ className={`${styles.Owner} ${isInStore ? '' : styles.NotInStore}`}
555
+ title={displayName}>
556
+ {displayName}
557
+ </span>
558
+ <Badge hocDisplayNames={hocDisplayNames} type={type} />
559
</span>
546
- <Badge hocDisplayNames={hocDisplayNames} type={type} />
560
</Button>
561
);
562
}
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+7
-22
@@ -30,6 +30,7 @@ import SearchInput from './SearchInput';
30
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
31
import SelectedTreeHighlight from './SelectedTreeHighlight';
32
import TreeFocusedContext from './TreeFocusedContext';
33
+import {useHighlightNativeElement} from '../hooks';
34
35
import styles from './Tree.css';
36
@@ -61,6 +62,10 @@ export default function Tree(props: Props) {
62
const [isNavigatingWithKeyboard, setIsNavigatingWithKeyboard] = useState(
63
false,
64
);
65
+ const {
66
+ highlightNativeElement,
67
+ clearHighlightNativeElement,
68
+ } = useHighlightNativeElement();
69
const treeRef = useRef<HTMLDivElement | null>(null);
70
const focusTargetRef = useRef<HTMLDivElement | null>(null);
71
@@ -205,24 +210,6 @@ export default function Tree(props: Props) {
210
[dispatch, selectedElementID],
211
);
212
208
- const highlightNativeElement = useCallback(
209
- (id: number) => {
210
- const element = store.getElementByID(id);
211
- const rendererID = store.getRendererIDForElement(id);
212
- if (element !== null && rendererID !== null) {
213
- bridge.send('highlightNativeElement', {
214
- displayName: element.displayName,
215
- hideAfterTimeout: false,
216
- id,
217
- openNativeElementsPanel: false,
218
- rendererID,
219
- scrollIntoView: false,
220
- });
221
- }
222
- },
223
- [store, bridge],
224
- );
225
-
213
// If we switch the selected element while using the keyboard,
214
// start highlighting it in the DOM instead of the last hovered node.
215
const searchRef = useRef({searchIndex, searchResults});
@@ -240,7 +227,7 @@ export default function Tree(props: Props) {
227
if (selectedElementID !== null) {
228
highlightNativeElement(selectedElementID);
229
} else {
243
- bridge.send('clearNativeElementHighlight');
230
+ clearHighlightNativeElement();
231
}
232
}
233
}, [
@@ -270,9 +257,7 @@ export default function Tree(props: Props) {
257
setIsNavigatingWithKeyboard(false);
258
}, []);
259
273
- const handleMouseLeave = useCallback(() => {
274
- bridge.send('clearNativeElementHighlight');
275
- }, [bridge]);
260
+ const handleMouseLeave = clearHighlightNativeElement;
261
262
// Let react-window know to re-render any time the underlying tree data changes.
263
// This includes the owner context, since it controls a filtered view of the tree.
packages/react-devtools-shared/src/devtools/views/hooks.js
+34
@@ -14,11 +14,13 @@ import {
14
useLayoutEffect,
15
useReducer,
16
useState,
17
+ useContext,
18
} from 'react';
19
import {
20
localStorageGetItem,
21
localStorageSetItem,
22
} from 'react-devtools-shared/src/storage';
23
+import {StoreContext, BridgeContext} from './context';
24
import {sanitizeForParse, smartParse, smartStringify} from '../utils';
25
26
type ACTION_RESET = {|
@@ -301,3 +303,35 @@ export function useSubscription<Value>({
303
304
return state.value;
305
}
306
+
307
+export function useHighlightNativeElement() {
308
+ const bridge = useContext(BridgeContext);
309
+ const store = useContext(StoreContext);
310
+
311
+ const highlightNativeElement = useCallback(
312
+ (id: number) => {
313
+ const element = store.getElementByID(id);
314
+ const rendererID = store.getRendererIDForElement(id);
315
+ if (element !== null && rendererID !== null) {
316
+ bridge.send('highlightNativeElement', {
317
+ displayName: element.displayName,
318
+ hideAfterTimeout: false,
319
+ id,
320
+ openNativeElementsPanel: false,
321
+ rendererID,
322
+ scrollIntoView: false,
323
+ });
324
+ }
325
+ },
326
+ [store, bridge],
327
+ );
328
+
329
+ const clearHighlightNativeElement = useCallback(() => {
330
+ bridge.send('clearNativeElementHighlight');
331
+ }, [bridge]);
332
+
333
+ return {
334
+ highlightNativeElement,
335
+ clearHighlightNativeElement,
336
+ };
337
+}