@samitouri / QOS-React / commits / 6fb7754494

[DevTools] Render selected outline on top of every other rect (#35012)

When rects are close together (or overlapping) the outline can end up being covered up by sibling rects or deeper rects. This renders the selected outline on top of everything so it's always visible. <img width="275" height="730" alt="Screenshot 2025-10-29 at 8 43 28 PM" src="https://github.com/user-attachments/assets/69224883-f548-45ec-ada1-1a04ec17eaf5" /> <img width="266" height="737" alt="Screenshot 2025-10-29 at 8 58 53 PM" src="https://github.com/user-attachments/assets/946f7dde-450d-49fd-9fbd-57487f67f461" /> Additionally, this makes it so that it's not part of the translucent tree when things are hidden by the timeline. That way it's easier to see what is selected inside a hidden tree. <img width="498" height="196" alt="Screenshot 2025-10-29 at 8 45 24 PM" src="https://github.com/user-attachments/assets/723107ab-a92c-42c2-8a7d-a548ac3332d0" /> <img width="571" height="735" alt="Screenshot 2025-10-29 at 8 59 06 PM" src="https://github.com/user-attachments/assets/d653f1a7-4096-45c3-b92a-ef155d4742e6" />

Sebastian Markbåge committed Oct 30, 2025 at 15:26 UTC 6fb77544940d033355c660556dbc48a08d1d6708
2 files changed +38 -14
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
+4 -6
@@ -6,12 +6,6 @@
6 border-radius: 0.25rem;
7 }
8
9 -.SuspenseRectsContainer[data-highlighted='true'] {
10 - outline-color: var(--color-transition);
11 - outline-style: solid;
12 - outline-width: 4px;
13 -}
14 -
9 .SuspenseRectsRoot {
10 cursor: pointer;
11 outline-color: var(--color-transition);
@@ -106,6 +100,10 @@
100 pointer-events: none;
101 }
102
103 +.SuspenseRectOutlineRoot {
104 + outline-color: var(--color-transition);
105 +}
106 +
107 .SuspenseRectsBoundary[data-selected='true'] > .SuspenseRectsRect {
108 box-shadow: none;
109 }
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+34 -8
@@ -236,13 +236,6 @@ function SuspenseRects({
236 <span>{suspense.name}</span>
237 </ScaledRect>
238 ) : null}
239 - {selected && visible ? (
240 - <ScaledRect
241 - className={styles.SuspenseRectOutline}
242 - rect={boundingBox}
243 - adjust={true}
244 - />
245 - ) : null}
239 </ViewBox.Provider>
240 </ScaledRect>
241 );
@@ -514,6 +507,28 @@ function SuspenseRectsContainer({
507 scaleRef.current = boundingBoxWidth;
508 }, [boundingBoxWidth]);
509
510 + let selectedBoundingBox = null;
511 + let selectedEnvironment = null;
512 + if (isRootSelected) {
513 + selectedBoundingBox = boundingBox;
514 + selectedEnvironment = rootEnvironment;
515 + } else if (inspectedElementID !== null) {
516 + const selectedSuspenseNode = store.getSuspenseByID(inspectedElementID);
517 + if (
518 + selectedSuspenseNode !== null &&
519 + (selectedSuspenseNode.hasUniqueSuspenders || !uniqueSuspendersOnly)
520 + ) {
521 + selectedBoundingBox = getBoundingBox(selectedSuspenseNode.rects);
522 + for (let i = 0; i < timeline.length; i++) {
523 + const timelineStep = timeline[i];
524 + if (timelineStep.id === inspectedElementID) {
525 + selectedEnvironment = timelineStep.environment;
526 + break;
527 + }
528 + }
529 + }
530 + }
531 +
532 return (
533 <div
534 className={
@@ -524,7 +539,6 @@ function SuspenseRectsContainer({
539 }
540 onClick={handleClick}
541 onDoubleClick={handleDoubleClick}
527 - data-highlighted={isRootSelected}
542 data-hovered={isRootHovered}>
543 <ViewBox.Provider value={boundingBox}>
544 <div
@@ -533,6 +547,18 @@ function SuspenseRectsContainer({
547 {roots.map(rootID => {
548 return <SuspenseRectsRoot key={rootID} rootID={rootID} />;
549 })}
550 + {selectedBoundingBox !== null ? (
551 + <ScaledRect
552 + className={
553 + styles.SuspenseRectOutline +
554 + (isRootSelected ? ' ' + styles.SuspenseRectOutlineRoot : '') +
555 + ' ' +
556 + getClassNameForEnvironment(selectedEnvironment)
557 + }
558 + rect={selectedBoundingBox}
559 + adjust={true}
560 + />
561 + ) : null}
562 </div>
563 </ViewBox.Provider>
564 </div>