Skip to content

Commit f970d5f

Browse files
authored
[DevTools] Highlight the rect when the corresponding timeline bean is hovered (#34881)
Stacked on #34880. In #34861 I removed the highlight of the real view when hovering the timeline since it was disruptive to stepping through the visuals. This makes it so that when we hover the timeline we highlight the rect with the subtle hover effect added in #34880. We can now just use the one shared state for this and don't need the CSS psuedo-selectors. <img width="603" height="813" alt="Screenshot 2025-10-16 at 3 11 17 PM" src="https://github.com/user-attachments/assets/a018b5ce-dd4d-4e77-ad47-b4ea068f1976" />
1 parent 724e7bf commit f970d5f

File tree

4 files changed

+31
-12
lines changed

4 files changed

+31
-12
lines changed

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
background-color: color-mix(in srgb, var(--color-transition) 5%, transparent);
99
}
1010

11-
.SuspenseRectsContainer:hover:not(:has(.SuspenseRectsBoundary:hover))[data-highlighted='false'] {
12-
outline-width: 1px;
11+
.SuspenseRectsContainer[data-hovered='true'] {
12+
background-color: color-mix(in srgb, var(--color-transition) 15%, transparent);
1313
}
1414

1515
.SuspenseRectsContainer[data-highlighted='true'] {
@@ -65,7 +65,7 @@
6565
}
6666

6767
/* highlight this boundary */
68-
.SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect {
68+
.SuspenseRectsBoundary[data-hovered='true'] > .SuspenseRectsRect {
6969
background-color: color-mix(in srgb, var(--color-background) 50%, var(--color-suspense) 50%);
7070
transition: background-color 0.2s ease-out;
7171
}

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function ScaledRect({
3737
visible,
3838
suspended,
3939
selected,
40+
hovered,
4041
adjust,
4142
...props
4243
}: {
@@ -45,6 +46,7 @@ function ScaledRect({
4546
visible: boolean,
4647
suspended: boolean,
4748
selected?: boolean,
49+
hovered?: boolean,
4850
adjust?: boolean,
4951
...
5052
}): React$Node {
@@ -61,6 +63,7 @@ function ScaledRect({
6163
data-visible={visible}
6264
data-suspended={suspended}
6365
data-selected={selected}
66+
data-hovered={hovered}
6467
style={{
6568
// Shrink one pixel so that the bottom outline will line up with the top outline of the next one.
6669
width: adjust ? 'calc(' + width + ' - 1px)' : width,
@@ -80,7 +83,9 @@ function SuspenseRects({
8083
const store = useContext(StoreContext);
8184
const treeDispatch = useContext(TreeDispatcherContext);
8285
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
83-
const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext);
86+
const {uniqueSuspendersOnly, timeline, hoveredTimelineIndex} = useContext(
87+
SuspenseTreeStateContext,
88+
);
8489

8590
const {inspectedElementID} = useContext(TreeStateContext);
8691

@@ -148,6 +153,9 @@ function SuspenseRects({
148153
// TODO: Use the nearest Suspense boundary
149154
const selected = inspectedElementID === suspenseID;
150155

156+
const hovered =
157+
hoveredTimelineIndex > -1 && timeline[hoveredTimelineIndex] === suspenseID;
158+
151159
const boundingBox = getBoundingBox(suspense.rects);
152160

153161
return (
@@ -156,7 +164,8 @@ function SuspenseRects({
156164
className={styles.SuspenseRectsBoundary}
157165
visible={visible}
158166
selected={selected}
159-
suspended={suspense.isSuspended}>
167+
suspended={suspense.isSuspended}
168+
hovered={hovered}>
160169
<ViewBox.Provider value={boundingBox}>
161170
{visible &&
162171
suspense.rects !== null &&
@@ -317,7 +326,7 @@ function SuspenseRectsContainer(): React$Node {
317326
const treeDispatch = useContext(TreeDispatcherContext);
318327
const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
319328
// TODO: This relies on a full re-render of all children when the Suspense tree changes.
320-
const {roots} = useContext(SuspenseTreeStateContext);
329+
const {roots, hoveredTimelineIndex} = useContext(SuspenseTreeStateContext);
321330

322331
// TODO: bbox does not consider uniqueSuspendersOnly filter
323332
const boundingBox = getDocumentBoundingRect(store, roots);
@@ -361,13 +370,15 @@ function SuspenseRectsContainer(): React$Node {
361370
}
362371

363372
const isRootSelected = roots.includes(inspectedElementID);
373+
const isRootHovered = hoveredTimelineIndex === 0;
364374

365375
return (
366376
<div
367377
className={styles.SuspenseRectsContainer}
368378
onClick={handleClick}
369379
onDoubleClick={handleDoubleClick}
370-
data-highlighted={isRootSelected}>
380+
data-highlighted={isRootSelected}
381+
data-hovered={isRootHovered}>
371382
<ViewBox.Provider value={boundingBox}>
372383
<div
373384
className={styles.SuspenseRectsViewBox}

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@
5454
background: var(--color-transition);
5555
}
5656

57-
.SuspenseScrubberStepHighlight > .SuspenseScrubberBead,
58-
.SuspenseScrubberStep:hover > .SuspenseScrubberBead {
57+
.SuspenseScrubberStepHighlight > .SuspenseScrubberBead {
5958
height: 0.75rem;
6059
transition: all 0.3s ease-out;
6160
}

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,19 @@ function SuspenseTimelineInput() {
5353
switchSuspenseNode(timelineIndex);
5454
}
5555

56-
function handleHoverSegment(hoveredValue: number) {
57-
// TODO: Consider highlighting the rect instead.
56+
function handleHoverSegment(hoveredIndex: number) {
57+
const nextSelectedSuspenseID = timeline[hoveredIndex];
58+
suspenseTreeDispatch({
59+
type: 'HOVER_TIMELINE_FOR_ID',
60+
payload: nextSelectedSuspenseID,
61+
});
62+
}
63+
function handleUnhoverSegment() {
64+
suspenseTreeDispatch({
65+
type: 'HOVER_TIMELINE_FOR_ID',
66+
payload: -1,
67+
});
5868
}
59-
function handleUnhoverSegment() {}
6069

6170
function skipPrevious() {
6271
const nextSelectedSuspenseID = timeline[timelineIndex - 1];

0 commit comments

Comments
 (0)