diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js index 07c779d758b26..957bb7f6fc1d2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js @@ -30,7 +30,7 @@ import SearchInput from './SearchInput'; import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle'; import SelectedTreeHighlight from './SelectedTreeHighlight'; import TreeFocusedContext from './TreeFocusedContext'; - +import {useNativeElementHighlighter} from '../hooks'; import styles from './Tree.css'; // Never indent more than this number of pixels (even if we have the room). @@ -67,6 +67,10 @@ export default function Tree(props: Props) { const [treeFocused, setTreeFocused] = useState(false); const {lineHeight} = useContext(SettingsContext); + const { + highlightNativeElement, + clearNativeElementHighlight, + } = useNativeElementHighlighter(); // Make sure a newly selected element is visible in the list. // This is helpful for things like the owners list and search. @@ -205,24 +209,6 @@ export default function Tree(props: Props) { [dispatch, selectedElementID], ); - const highlightNativeElement = useCallback( - (id: number) => { - const element = store.getElementByID(id); - const rendererID = store.getRendererIDForElement(id); - if (element !== null && rendererID !== null) { - bridge.send('highlightNativeElement', { - displayName: element.displayName, - hideAfterTimeout: false, - id, - openNativeElementsPanel: false, - rendererID, - scrollIntoView: false, - }); - } - }, - [store, bridge], - ); - // If we switch the selected element while using the keyboard, // start highlighting it in the DOM instead of the last hovered node. const searchRef = useRef({searchIndex, searchResults}); @@ -240,11 +226,12 @@ export default function Tree(props: Props) { if (selectedElementID !== null) { highlightNativeElement(selectedElementID); } else { - bridge.send('clearNativeElementHighlight'); + clearNativeElementHighlight(); } } }, [ bridge, + clearNativeElementHighlight, isNavigatingWithKeyboard, highlightNativeElement, searchIndex, @@ -270,9 +257,7 @@ export default function Tree(props: Props) { setIsNavigatingWithKeyboard(false); }, []); - const handleMouseLeave = useCallback(() => { - bridge.send('clearNativeElementHighlight'); - }, [bridge]); + const handleMouseLeave = clearNativeElementHighlight; // Let react-window know to re-render any time the underlying tree data changes. // This includes the owner context, since it controls a filtered view of the tree. diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.js b/packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.js index 82c58e86da460..1ad37dca79410 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/ChartNode.js @@ -15,6 +15,7 @@ type Props = {| color: string, height: number, isDimmed?: boolean, + isHovered?: boolean, label: string, onClick: (event: SyntheticMouseEvent<*>) => mixed, onDoubleClick?: (event: SyntheticMouseEvent<*>) => mixed, @@ -33,16 +34,24 @@ export default function ChartNode({ color, height, isDimmed = false, + isHovered = false, label, onClick, + onDoubleClick, onMouseEnter, onMouseLeave, - onDoubleClick, textStyle, width, x, y, }: Props) { + let opacity = 1; + if (isHovered) { + opacity = 0.75; + } else if (isDimmed) { + opacity = 0.5; + } + return ( {width >= minWidthToDisplay && ( diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js index f86573037e12c..d592d3a50cdaa 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraph.js @@ -16,6 +16,7 @@ import NoCommitData from './NoCommitData'; import CommitFlamegraphListItem from './CommitFlamegraphListItem'; import HoveredFiberInfo from './HoveredFiberInfo'; import {scale} from './utils'; +import {useNativeElementHighlighter} from '../hooks'; import {StoreContext} from '../context'; import {SettingsContext} from '../Settings/SettingsContext'; import Tooltip from './Tooltip'; @@ -28,7 +29,9 @@ import type {CommitTree} from './types'; export type ItemData = {| chartData: ChartData, - hoverFiber: (fiberData: TooltipFiberData | null) => void, + isHovered: boolean, + onElementMouseEnter: (fiberData: TooltipFiberData) => void, + onElementMouseLeave: () => void, scaleX: (value: number, fallbackValue: number) => number, selectedChartNode: ChartNode | null, selectedChartNodeIndex: number, @@ -96,9 +99,13 @@ type Props = {| |}; function CommitFlamegraph({chartData, commitTree, height, width}: Props) { - const [hoveredFiberData, hoverFiber] = useState(null); + const [hoveredFiberData, setHoveredFiberData] = useState(null); const {lineHeight} = useContext(SettingsContext); const {selectFiber, selectedFiberID} = useContext(ProfilerContext); + const { + highlightNativeElement, + clearNativeElementHighlight, + } = useNativeElementHighlighter(); const selectedChartNodeIndex = useMemo(() => { if (selectedFiberID === null) { @@ -121,10 +128,22 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) { return null; }, [chartData, selectedFiberID, selectedChartNodeIndex]); + const handleElementMouseEnter = ({id, name}) => { + highlightNativeElement(id); // Highlight last hovered element. + setHoveredFiberData({id, name}); // Set hovered fiber data for tooltip + }; + + const handleElementMouseLeave = () => { + clearNativeElementHighlight(); // clear highlighting of element on mouse leave + setHoveredFiberData(null); // clear hovered fiber data for tooltip + }; + const itemData = useMemo( () => ({ chartData, - hoverFiber, + isHovered: hoveredFiberData && hoveredFiberData.id, + onElementMouseEnter: handleElementMouseEnter, + onElementMouseLeave: handleElementMouseLeave, scaleX: scale( 0, selectedChartNode !== null @@ -140,7 +159,7 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) { }), [ chartData, - hoverFiber, + setHoveredFiberData, selectedChartNode, selectedChartNodeIndex, selectFiber, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js index c998998cb4f45..699b6a8f27ae2 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitFlamegraphListItem.js @@ -28,13 +28,16 @@ type Props = { function CommitFlamegraphListItem({data, index, style}: Props) { const { chartData, - hoverFiber, + isHovered, + onElementMouseEnter, + onElementMouseLeave, scaleX, selectedChartNode, selectedChartNodeIndex, selectFiber, width, } = data; + const {renderPathNodes, maxSelfDuration, rows} = chartData; const {lineHeight} = useContext(SettingsContext); @@ -49,11 +52,11 @@ function CommitFlamegraphListItem({data, index, style}: Props) { const handleMouseEnter = (nodeData: ChartNodeType) => { const {id, name} = nodeData; - hoverFiber({id, name}); + onElementMouseEnter({id, name}); }; const handleMouseLeave = () => { - hoverFiber(null); + onElementMouseLeave(); }; // List items are absolutely positioned using the CSS "top" attribute. @@ -114,6 +117,7 @@ function CommitFlamegraphListItem({data, index, style}: Props) { color={color} height={lineHeight} isDimmed={index < selectedChartNodeIndex} + isHovered={isHovered} key={id} label={label} onClick={event => handleClick(event, id, name)} diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js index e20692fd4df94..577c794bcb428 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js @@ -18,6 +18,7 @@ import HoveredFiberInfo from './HoveredFiberInfo'; import {scale} from './utils'; import {StoreContext} from '../context'; import {SettingsContext} from '../Settings/SettingsContext'; +import {useNativeElementHighlighter} from '../hooks'; import Tooltip from './Tooltip'; import styles from './CommitRanked.css'; @@ -28,7 +29,9 @@ import type {CommitTree} from './types'; export type ItemData = {| chartData: ChartData, - hoverFiber: (fiberData: TooltipFiberData | null) => void, + isHovered: boolean, + onElementMouseEnter: (fiberData: TooltipFiberData) => void, + onElementMouseLeave: () => void, scaleX: (value: number, fallbackValue: number) => number, selectedFiberID: number | null, selectedFiberIndex: number, @@ -94,19 +97,35 @@ type Props = {| |}; function CommitRanked({chartData, commitTree, height, width}: Props) { - const [hoveredFiberData, hoverFiber] = useState(null); + const [hoveredFiberData, setHoveredFiberData] = useState(null); const {lineHeight} = useContext(SettingsContext); const {selectedFiberID, selectFiber} = useContext(ProfilerContext); + const { + highlightNativeElement, + clearNativeElementHighlight, + } = useNativeElementHighlighter(); const selectedFiberIndex = useMemo( () => getNodeIndex(chartData, selectedFiberID), [chartData, selectedFiberID], ); + const handleElementMouseEnter = ({id, name}) => { + highlightNativeElement(id); // Highlight last hovered element. + setHoveredFiberData({id, name}); // Set hovered fiber data for tooltip + }; + + const handleElementMouseLeave = () => { + clearNativeElementHighlight(); // clear highlighting of element on mouse leave + setHoveredFiberData(null); // clear hovered fiber data for tooltip + }; + const itemData = useMemo( () => ({ chartData, - hoverFiber, + isHovered: hoveredFiberData && hoveredFiberData.id, + onElementMouseEnter: handleElementMouseEnter, + onElementMouseLeave: handleElementMouseLeave, scaleX: scale(0, chartData.nodes[selectedFiberIndex].value, 0, width), selectedFiberID, selectedFiberIndex, diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js index 314c928cc50e9..b9974705b1698 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitRankedListItem.js @@ -27,7 +27,9 @@ type Props = { function CommitRankedListItem({data, index, style}: Props) { const { chartData, - hoverFiber, + isHovered, + onElementMouseEnter, + onElementMouseLeave, scaleX, selectedFiberIndex, selectFiber, @@ -49,11 +51,11 @@ function CommitRankedListItem({data, index, style}: Props) { const handleMouseEnter = () => { const {id, name} = node; - hoverFiber({id, name}); + onElementMouseEnter({id, name}); }; const handleMouseLeave = () => { - hoverFiber(null); + onElementMouseLeave(); }; // List items are absolutely positioned using the CSS "top" attribute. @@ -67,6 +69,7 @@ function CommitRankedListItem({data, index, style}: Props) { color={getGradientColor(node.value / chartData.maxValue)} height={lineHeight} isDimmed={index < selectedFiberIndex} + isHovered={isHovered} key={node.id} label={node.label} onClick={handleClick} diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index a8fb43dd61a12..3253624fd8939 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -14,12 +14,14 @@ import { useLayoutEffect, useReducer, useState, + useContext, } from 'react'; import { localStorageGetItem, localStorageSetItem, } from 'react-devtools-shared/src/storage'; import {sanitizeForParse, smartParse, smartStringify} from '../utils'; +import {BridgeContext, StoreContext} from './context'; type ACTION_RESET = {| type: 'RESET', @@ -300,3 +302,29 @@ export function useSubscription({ return state.value; } + +// provides highlighting and clearing highlights of elements based on ID +export function useNativeElementHighlighter() { + const store = useContext(StoreContext); + const bridge = useContext(BridgeContext); + + const highlightNativeElement = (id: number) => { + const element = store.getElementByID(id); + const rendererID = store.getRendererIDForElement(id); + if (element !== null && rendererID !== null) { + bridge.send('highlightNativeElement', { + displayName: element.displayName, + hideAfterTimeout: false, + id, + openNativeElementsPanel: false, + rendererID, + scrollIntoView: false, + }); + } + }; + + const clearNativeElementHighlight = () => { + bridge.send('clearNativeElementHighlight'); + }; + return {highlightNativeElement, clearNativeElementHighlight}; +}