diff --git a/packages/react-devtools-shared/src/devtools/constants.js b/packages/react-devtools-shared/src/devtools/constants.js index ee13e5b1630..cfb73713b08 100644 --- a/packages/react-devtools-shared/src/devtools/constants.js +++ b/packages/react-devtools-shared/src/devtools/constants.js @@ -163,6 +163,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = { '--color-scroll-track': '#fafafa', '--color-tooltip-background': 'rgba(0, 0, 0, 0.9)', '--color-tooltip-text': '#ffffff', + + '--elevation-4': + '0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12)', }, dark: { '--color-attribute-name': '#9d87d2', @@ -315,6 +318,9 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = { '--color-scroll-track': '#313640', '--color-tooltip-background': 'rgba(255, 255, 255, 0.95)', '--color-tooltip-text': '#000000', + + '--elevation-4': + '0 2px 8px 0 rgba(0,0,0,0.32),0 4px 12px 0 rgba(0,0,0,0.24),0 1px 10px 0 rgba(0,0,0,0.18)', }, compact: { '--font-size-monospace-small': '9px', diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css index d9ecf2cad75..7e74f21eef0 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css @@ -2,14 +2,40 @@ padding: .25rem; } -.SuspenseRect { - fill: transparent; - stroke: var(--color-background-selected); - stroke-width: 1px; - vector-effect: non-scaling-stroke; - paint-order: stroke; +.SuspenseRectsViewBox { + position: relative; } -[data-highlighted='true'] > .SuspenseRect { - fill: var(--color-selected-tree-highlight-active); +.SuspenseRectsBoundary { + pointer-events: all; +} + +.SuspenseRectsBoundaryChildren { + pointer-events: none; + /** + * So that the shadow of Boundaries within is clipped off. + * Otherwise it would look like this boundary is further elevated. + */ + overflow: hidden; +} + +.SuspenseRectsRect { + box-shadow: var(--elevation-4); + pointer-events: all; + outline-style: solid; + outline-width: 1px; +} + +.SuspenseRectsScaledRect { + position: absolute; + outline-color: var(--color-background-selected); +} + +/* highlight this boundary */ +.SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect { + background-color: var(--color-background-hover); +} + +.SuspenseRectsRect[data-highlighted='true'] { + background-color: var(--color-selected-tree-highlight-active); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index a03439c07d9..39c6f1c4925 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -12,9 +12,13 @@ import type { SuspenseNode, Rect, } from 'react-devtools-shared/src/frontend/types'; +import typeof { + SyntheticMouseEvent, + SyntheticPointerEvent, +} from 'react-dom-bindings/src/events/SyntheticEvent'; import * as React from 'react'; -import {useContext} from 'react'; +import {createContext, useContext} from 'react'; import { TreeDispatcherContext, TreeStateContext, @@ -26,19 +30,32 @@ import { SuspenseTreeStateContext, SuspenseTreeDispatcherContext, } from './SuspenseTreeContext'; -import typeof { - SyntheticMouseEvent, - SyntheticPointerEvent, -} from 'react-dom-bindings/src/events/SyntheticEvent'; -function SuspenseRect({rect}: {rect: Rect}): React$Node { +function ScaledRect({ + className, + rect, + ...props +}: { + className: string, + rect: Rect, + ... +}): React$Node { + const viewBox = useContext(ViewBox); + const width = (rect.width / viewBox.width) * 100 + '%'; + const height = (rect.height / viewBox.height) * 100 + '%'; + const x = ((rect.x - viewBox.x) / viewBox.width) * 100 + '%'; + const y = ((rect.y - viewBox.y) / viewBox.height) * 100 + '%'; + return ( - ); } @@ -97,24 +114,67 @@ function SuspenseRects({ // TODO: Use the nearest Suspense boundary const selected = inspectedElementID === suspenseID; + const boundingBox = getBoundingBox(suspense.rects); + return ( - - {suspense.name} - {suspense.rects !== null && - suspense.rects.map((rect, index) => { - return ; - })} - {suspense.children.map(childID => { - return ; - })} - + + + {suspense.rects !== null && + suspense.rects.map((rect, index) => { + return ( + + ); + })} + {suspense.children.length > 0 && ( + + {suspense.children.map(childID => { + return ; + })} + + )} + + ); } +function getBoundingBox(rects: $ReadOnlyArray | null): Rect { + if (rects === null || rects.length === 0) { + return {x: 0, y: 0, width: 0, height: 0}; + } + + let minX = Number.POSITIVE_INFINITY; + let minY = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + let maxY = Number.NEGATIVE_INFINITY; + + for (let i = 0; i < rects.length; i++) { + const rect = rects[i]; + minX = Math.min(minX, rect.x); + minY = Math.min(minY, rect.y); + maxX = Math.max(maxX, rect.x + rect.width); + maxY = Math.max(maxY, rect.y + rect.height); + } + + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; +} + function getDocumentBoundingRect( store: Store, roots: $ReadOnlyArray, @@ -169,42 +229,42 @@ function SuspenseRectsShell({ const store = useContext(StoreContext); const root = store.getSuspenseByID(rootID); if (root === null) { - console.warn(` Could not find suspense node id ${rootID}`); + // getSuspenseByID will have already warned return null; } - return ( - - {root.children.map(childID => { - return ; - })} - - ); + return root.children.map(childID => { + return ; + }); } +const ViewBox = createContext((null: any)); + function SuspenseRectsContainer(): React$Node { const store = useContext(StoreContext); // TODO: This relies on a full re-render of all children when the Suspense tree changes. const {roots} = useContext(SuspenseTreeStateContext); - const boundingRect = getDocumentBoundingRect(store, roots); + const boundingBox = getDocumentBoundingRect(store, roots); + const boundingBoxWidth = boundingBox.width; + const heightScale = + boundingBoxWidth === 0 ? 1 : boundingBox.height / boundingBoxWidth; + // Scales the inspected document to fit into the available width const width = '100%'; - const boundingRectWidth = boundingRect.width; - const height = - (boundingRectWidth === 0 ? 0 : boundingRect.height / boundingRect.width) * - 100 + - '%'; + const aspectRatio = `1 / ${heightScale}`; return ( - - {roots.map(rootID => { - return ; - })} - + + + {roots.map(rootID => { + return ; + })} + + ); }