mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #92 from gaearon/highlight-on-hover
Select DOM nodes on hover
This commit is contained in:
+15
-4
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(`<ElementView> Could not find element at index ${index}`);
|
||||
@@ -100,6 +119,7 @@ export default function ElementView({ index, style, data }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={isSelected ? styles.SelectedElement : styles.Element}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseDown={handleMouseDown}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
style={{
|
||||
|
||||
@@ -45,8 +45,10 @@ export default function SelectedElement(_: Props) {
|
||||
if (rendererID !== null) {
|
||||
bridge.send('highlightElementInDOM', {
|
||||
displayName: element.displayName,
|
||||
hideAfterTimeout: true,
|
||||
id: selectedElementID,
|
||||
rendererID,
|
||||
scrollIntoView: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -269,7 +271,7 @@ function useInspectedElement(id: number | null): InspectedElement | null {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const rendererID = store.getRendererIDForElement(id) || null;
|
||||
const rendererID = store.getRendererIDForElement(id);
|
||||
|
||||
// Update the $r variable.
|
||||
bridge.send('selectElement', { id, rendererID });
|
||||
|
||||
@@ -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<FixedSizeList<any> | null>(null);
|
||||
const treeRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@@ -112,13 +114,17 @@ export default function Tree(props: Props) {
|
||||
[baseDepth, numElements, getElementAtIndex, lastScrolledIDRef]
|
||||
);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
bridge.send('clearHighlightedElementInDOM');
|
||||
}, [bridge]);
|
||||
|
||||
return (
|
||||
<div className={styles.Tree} ref={treeRef}>
|
||||
<div className={styles.SearchInput}>
|
||||
{ownerStack.length > 0 ? <OwnersStack /> : <SearchInput />}
|
||||
<InspectHostNodesToggle />
|
||||
</div>
|
||||
<div className={styles.AutoSizerWrapper}>
|
||||
<div className={styles.AutoSizerWrapper} onMouseLeave={handleMouseLeave}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
|
||||
Reference in New Issue
Block a user