@@ -12,9 +12,13 @@ import type {
1212 SuspenseNode ,
1313 Rect ,
1414} from 'react-devtools-shared/src/frontend/types' ;
15+ import typeof {
16+ SyntheticMouseEvent ,
17+ SyntheticPointerEvent ,
18+ } from 'react-dom-bindings/src/events/SyntheticEvent' ;
1519
1620import * as React from 'react' ;
17- import { useContext } from 'react' ;
21+ import { createContext , useContext } from 'react' ;
1822import {
1923 TreeDispatcherContext ,
2024 TreeStateContext ,
@@ -26,19 +30,32 @@ import {
2630 SuspenseTreeStateContext ,
2731 SuspenseTreeDispatcherContext ,
2832} from './SuspenseTreeContext' ;
29- import typeof {
30- SyntheticMouseEvent ,
31- SyntheticPointerEvent ,
32- } from 'react-dom-bindings/src/events/SyntheticEvent' ;
3333
34- function SuspenseRect ( { rect} : { rect : Rect } ) : React$Node {
34+ function ScaledRect ( {
35+ className,
36+ rect,
37+ ...props
38+ } : {
39+ className : string ,
40+ rect : Rect ,
41+ ...
42+ } ) : React$Node {
43+ const viewBox = useContext ( ViewBox ) ;
44+ const width = ( rect . width / viewBox . width ) * 100 + '%' ;
45+ const height = ( rect . height / viewBox . height ) * 100 + '%' ;
46+ const x = ( ( rect . x - viewBox . x ) / viewBox . width ) * 100 + '%' ;
47+ const y = ( ( rect . y - viewBox . y ) / viewBox . height ) * 100 + '%' ;
48+
3549 return (
36- < rect
37- className = { styles . SuspenseRect }
38- x = { rect . x }
39- y = { rect . y }
40- width = { rect . width }
41- height = { rect . height }
50+ < div
51+ { ...props }
52+ className = { styles . SuspenseRectsScaledRect + ' ' + className }
53+ style = { {
54+ width,
55+ height,
56+ top : y ,
57+ left : x ,
58+ } }
4259 />
4360 ) ;
4461}
@@ -97,24 +114,67 @@ function SuspenseRects({
97114 // TODO: Use the nearest Suspense boundary
98115 const selected = inspectedElementID === suspenseID ;
99116
117+ const boundingBox = getBoundingBox ( suspense . rects ) ;
118+
100119 return (
101- < g
102- data-highlighted = { selected }
103- onClick = { handleClick }
104- onPointerOver = { handlePointerOver }
105- onPointerLeave = { handlePointerLeave } >
106- < title > { suspense . name } </ title >
107- { suspense . rects !== null &&
108- suspense . rects . map ( ( rect , index ) => {
109- return < SuspenseRect key = { index } rect = { rect } /> ;
110- } ) }
111- { suspense . children . map ( childID => {
112- return < SuspenseRects key = { childID } suspenseID = { childID } /> ;
113- } ) }
114- </ g >
120+ < >
121+ < ScaledRect
122+ rect = { boundingBox }
123+ className = { styles . SuspenseRectsBoundary }
124+ data-highlighted = { selected }
125+ onClick = { handleClick }
126+ onPointerOver = { handlePointerOver }
127+ onPointerLeave = { handlePointerLeave }
128+ // Reach-UI tooltip will go out of bounds of parent scroll container.
129+ // Native title will change position for each Client Rect.
130+ // changing position is better than going oob.
131+ title = { suspense . name } >
132+ < ViewBox . Provider value = { boundingBox } >
133+ { suspense . rects !== null &&
134+ suspense . rects . map ( ( rect , index ) => {
135+ return (
136+ < ScaledRect
137+ key = { index }
138+ className = { styles . SuspenseRectsRect }
139+ rect = { rect }
140+ />
141+ ) ;
142+ } ) }
143+ { suspense . children . map ( childID => {
144+ return < SuspenseRects key = { childID } suspenseID = { childID } /> ;
145+ } ) }
146+ </ ViewBox . Provider >
147+ </ ScaledRect >
148+ </ >
115149 ) ;
116150}
117151
152+ function getBoundingBox ( rects : $ReadOnlyArray < Rect > | null ) : Rect {
153+ if ( rects === null || rects . length === 0 ) {
154+ return { x : 0 , y : 0 , width : 0 , height : 0 } ;
155+ }
156+
157+ let minX = Number . POSITIVE_INFINITY ;
158+ let minY = Number . POSITIVE_INFINITY ;
159+ let maxX = Number . NEGATIVE_INFINITY ;
160+ let maxY = Number . NEGATIVE_INFINITY ;
161+
162+ for ( let i = 0 ; i < rects . length ; i ++ ) {
163+ const rect = rects [ i ] ;
164+ minX = Math . min ( minX , rect . x ) ;
165+ minY = Math . min ( minY , rect . y ) ;
166+ maxX = Math . max ( maxX , rect . x + rect . width ) ;
167+ maxY = Math . max ( maxY , rect . y + rect . height ) ;
168+ }
169+
170+ return {
171+ x : minX ,
172+ y : minY ,
173+ width : maxX - minX ,
174+ height : maxY - minY ,
175+ } ;
176+ }
177+
118178function getDocumentBoundingRect (
119179 store : Store ,
120180 roots : $ReadOnlyArray < SuspenseNode [ 'id' ] > ,
@@ -161,29 +221,33 @@ function getDocumentBoundingRect(
161221 } ;
162222}
163223
224+ const ViewBox = createContext < Rect > ( ( null : any ) ) ;
225+
164226function SuspenseRectsContainer ( ) : React$Node {
165227 const store = useContext ( StoreContext ) ;
166228 // TODO: This relies on a full re-render of all children when the Suspense tree changes.
167229 const { roots} = useContext ( SuspenseTreeStateContext ) ;
168230
169- const boundingRect = getDocumentBoundingRect ( store , roots ) ;
231+ const boundingBox = getDocumentBoundingRect ( store , roots ) ;
170232
233+ const boundingBoxWidth = boundingBox . width ;
234+ const heightScale =
235+ boundingBoxWidth === 0 ? 1 : boundingBox . height / boundingBoxWidth ;
236+ // Scales the inspected document to fit into the available width
171237 const width = '100%' ;
172- const boundingRectWidth = boundingRect . width ;
173- const height =
174- ( boundingRectWidth === 0 ? 0 : boundingRect . height / boundingRect . width ) *
175- 100 +
176- '%' ;
238+ const aspectRatio = `1 / ${ heightScale } ` ;
177239
178240 return (
179241 < div className = { styles . SuspenseRectsContainer } >
180- < svg
181- style = { { width, height} }
182- viewBox = { `${ boundingRect . x } ${ boundingRect . y } ${ boundingRect . width } ${ boundingRect . height } ` } >
183- { roots . map ( rootID => {
184- return < SuspenseRects key = { rootID } suspenseID = { rootID } /> ;
185- } ) }
186- </ svg >
242+ < ViewBox . Provider value = { boundingBox } >
243+ < div
244+ className = { styles . SuspenseRectsViewBox }
245+ style = { { aspectRatio, width} } >
246+ { roots . map ( rootID => {
247+ return < SuspenseRects key = { rootID } suspenseID = { rootID } /> ;
248+ } ) }
249+ </ div >
250+ </ ViewBox . Provider >
187251 </ div >
188252 ) ;
189253}
0 commit comments