99
1010import type Store from 'react-devtools-shared/src/devtools/store' ;
1111import type {
12+ Element ,
1213 SuspenseNode ,
1314 Rect ,
1415} from 'react-devtools-shared/src/frontend/types' ;
@@ -18,7 +19,7 @@ import typeof {
1819} from 'react-dom-bindings/src/events/SyntheticEvent' ;
1920
2021import * as React from 'react' ;
21- import { createContext , useContext , useLayoutEffect } from 'react' ;
22+ import { createContext , useContext , useLayoutEffect , useMemo } from 'react' ;
2223import {
2324 TreeDispatcherContext ,
2425 TreeStateContext ,
@@ -426,6 +427,30 @@ function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node {
426427 } ) ;
427428}
428429
430+ function SuspenseRectsInitialPaint ( ) : React$Node {
431+ const { roots } = useContext ( SuspenseTreeStateContext ) ;
432+ return roots . map ( rootID => {
433+ return < SuspenseRectsRoot key = { rootID } rootID = { rootID } /> ;
434+ } ) ;
435+ }
436+
437+ function SuspenseRectsTransition ( { id} : { id : Element [ 'id' ] } ) : React$Node {
438+ const store = useContext ( StoreContext ) ;
439+ const children = useMemo ( ( ) => {
440+ return store . getSuspenseChildren ( id ) ;
441+ } , [ id , store ] ) ;
442+
443+ return children . map ( suspenseID => {
444+ return (
445+ < SuspenseRects
446+ key = { suspenseID }
447+ suspenseID = { suspenseID }
448+ parentRects = { null }
449+ />
450+ ) ;
451+ } ) ;
452+ }
453+
429454const ViewBox = createContext < Rect > ( ( null : any ) ) ;
430455
431456function SuspenseRectsContainer ( {
@@ -434,14 +459,25 @@ function SuspenseRectsContainer({
434459 scaleRef : { current : number } ,
435460} ) : React$Node {
436461 const store = useContext ( StoreContext ) ;
437- const { inspectedElementID} = useContext ( TreeStateContext ) ;
462+ const { activityID , inspectedElementID} = useContext ( TreeStateContext ) ;
438463 const treeDispatch = useContext ( TreeDispatcherContext ) ;
439464 const suspenseTreeDispatch = useContext ( SuspenseTreeDispatcherContext ) ;
440465 // TODO: This relies on a full re-render of all children when the Suspense tree changes.
441466 const { roots, timeline, hoveredTimelineIndex, uniqueSuspendersOnly} =
442467 useContext ( SuspenseTreeStateContext ) ;
443468
444- // TODO: bbox does not consider uniqueSuspendersOnly filter
469+ const activityChildren : $ReadOnlyArray < SuspenseNode [ 'id' ] > | null =
470+ useMemo ( ( ) => {
471+ if ( activityID === null ) {
472+ return null ;
473+ }
474+ return store . getSuspenseChildren ( activityID ) ;
475+ } , [ activityID , store ] ) ;
476+ const transitionChildren =
477+ activityChildren === null ? roots : activityChildren ;
478+
479+ // We're using the bounding box of the entire document to anchor the Transition
480+ // in the actual document.
445481 const boundingBox = getDocumentBoundingRect ( store , roots ) ;
446482
447483 const boundingBoxWidth = boundingBox . width ;
@@ -456,14 +492,18 @@ function SuspenseRectsContainer({
456492 // Already clicked on an inner rect
457493 return ;
458494 }
459- if ( roots . length === 0 ) {
495+ if ( transitionChildren . length === 0 ) {
460496 // Nothing to select
461497 return ;
462498 }
463499 const arbitraryRootID = roots [ 0 ] ;
500+ const transitionRoot = activityID === null ? arbitraryRootID : activityID ;
464501
465502 event . preventDefault ( ) ;
466- treeDispatch ( { type : 'SELECT_ELEMENT_BY_ID' , payload : arbitraryRootID } ) ;
503+ treeDispatch ( {
504+ type : 'SELECT_ELEMENT_BY_ID' ,
505+ payload : transitionRoot ,
506+ } ) ;
467507 suspenseTreeDispatch ( {
468508 type : 'SET_SUSPENSE_LINEAGE' ,
469509 payload : arbitraryRootID ,
@@ -483,7 +523,8 @@ function SuspenseRectsContainer({
483523 }
484524
485525 const isRootSelected = roots . includes ( inspectedElementID ) ;
486- const isRootHovered = hoveredTimelineIndex === 0 ;
526+ // When we're focusing a Transition, the first timeline step will not be a root.
527+ const isRootHovered = activityID === null && hoveredTimelineIndex === 0 ;
487528
488529 let hasRootSuspenders = false ;
489530 if ( ! uniqueSuspendersOnly ) {
@@ -536,7 +577,13 @@ function SuspenseRectsContainer({
536577 < div
537578 className = {
538579 styles . SuspenseRectsContainer +
539- ( hasRootSuspenders ? ' ' + styles . SuspenseRectsRoot : '' ) +
580+ ( hasRootSuspenders &&
581+ // We don't want to draw attention to the root if we're looking at a Transition.
582+ // TODO: Draw bounding rect of Transition and check if the Transition
583+ // has unique suspenders.
584+ activityID === null
585+ ? ' ' + styles . SuspenseRectsRoot
586+ : '' ) +
540587 ( isRootSelected ? ' ' + styles . SuspenseRectsRootOutline : '' ) +
541588 ' ' +
542589 getClassNameForEnvironment ( rootEnvironment )
@@ -548,9 +595,11 @@ function SuspenseRectsContainer({
548595 < div
549596 className = { styles . SuspenseRectsViewBox }
550597 style = { { aspectRatio, width} } >
551- { roots . map ( rootID => {
552- return < SuspenseRectsRoot key = { rootID } rootID = { rootID } /> ;
553- } ) }
598+ { activityID === null ? (
599+ < SuspenseRectsInitialPaint />
600+ ) : (
601+ < SuspenseRectsTransition id = { activityID } />
602+ ) }
554603 { selectedBoundingBox !== null ? (
555604 < ScaledRect
556605 className = {
0 commit comments