diff --git a/packages/react-devtools-extensions/src/main/elementSelection.js b/packages/react-devtools-extensions/src/main/elementSelection.js index 7f9b8ed208527..a315ebb8d58f3 100644 --- a/packages/react-devtools-extensions/src/main/elementSelection.js +++ b/packages/react-devtools-extensions/src/main/elementSelection.js @@ -34,7 +34,7 @@ export function setReactSelectionFromBrowser(bridge) { return; } - // Remember to sync the selection next time we show Components tab. + // Remember to sync the selection next time we show inspected element bridge.send('syncSelectionFromBuiltinElementsPanel'); } }, diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index bc948d1e6d6c9..48d6ed160e387 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -205,6 +205,7 @@ function createBridgeAndStore() { bridge, browserTheme: getBrowserTheme(), componentsPortalContainer, + inspectedElementPortalContainer, profilerPortalContainer, editorPortalContainer, currentSelectedSource, @@ -277,6 +278,46 @@ function createComponentsPanel() { ); } +function createElementsInspectPanel() { + if (inspectedElementPortalContainer) { + // Panel is created and user opened it at least once + ensureInitialHTMLIsCleared(inspectedElementPortalContainer); + render(); + + return; + } + + if (inspectedElementPane) { + // Panel is created, but wasn't opened yet, so no document is present for it + return; + } + + const elementsPanel = chrome.devtools.panels.elements; + if (!elementsPanel || !elementsPanel.createSidebarPane) { + // TODO: Does Firefox support elements panel extensions? + return; + } + + elementsPanel.createSidebarPane('React Element ⚛', createdPane => { + inspectedElementPane = createdPane; + + createdPane.setPage('panel.html'); + createdPane.setHeight('75px'); + + createdPane.onShown.addListener(portal => { + inspectedElementPortalContainer = portal.container; + if (inspectedElementPortalContainer != null && render) { + ensureInitialHTMLIsCleared(inspectedElementPortalContainer); + + render(); + portal.injectStyles(cloneStyleTags); + + logEvent({event_name: 'selected-inspected-element-pane'}); + } + }); + }); +} + function createProfilerPanel() { if (profilerPortalContainer) { // Panel is created and user opened it at least once @@ -507,6 +548,7 @@ function mountReactDevTools() { createComponentsPanel(); createProfilerPanel(); createSourcesEditorPanel(); + createElementsInspectPanel(); // Suspense Tab is created via the hook // TODO(enableSuspenseTab): Create eagerly once Suspense tab is stable } @@ -555,10 +597,12 @@ let componentsPanel = null; let profilerPanel = null; let suspensePanel = null; let editorPane = null; +let inspectedElementPane = null; let componentsPortalContainer = null; let profilerPortalContainer = null; let suspensePortalContainer = null; let editorPortalContainer = null; +let inspectedElementPortalContainer = null; let mostRecentOverrideTab = null; let render = null; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css index 156b28f2cadb6..7b00c0c0b3979 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css @@ -77,3 +77,11 @@ padding: 0.25rem; color: var(--color-console-error-icon); } + +.VRule { + height: 20px; + width: 1px; + flex: 0 0 1px; + margin: 0 0.5rem; + background-color: var(--color-border); +} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index 633a4d382eb76..7ba70678af058 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -34,13 +34,17 @@ import useEditorURL from '../useEditorURL'; import styles from './InspectedElement.css'; import Tooltip from './reach-ui/tooltip'; -export type Props = {}; +export type Props = { + actionButtons?: React.Node, +}; // TODO Make edits and deletes also use transition API! const noSourcePromise = Promise.resolve(null); -export default function InspectedElementWrapper(_: Props): React.Node { +export default function InspectedElementWrapper({ + actionButtons, +}: Props): React.Node { const {inspectedElementID} = useContext(TreeStateContext); const bridge = useContext(BridgeContext); const store = useContext(StoreContext); @@ -305,6 +309,13 @@ export default function InspectedElementWrapper(_: Props): React.Node { symbolicatedSourcePromise={symbolicatedSourcePromise} /> )} + + {actionButtons && ( + <> +
+ {actionButtons} + > + )} {inspectedElement === null && ( diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 6ddacef563769..dc47157497820 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -26,6 +26,7 @@ import Profiler from './Profiler/Profiler'; import SuspenseTab from './SuspenseTab/SuspenseTab'; import TabBar from './TabBar'; import EditorPane from './Editor/EditorPane'; +import InspectedElementPane from './InspectedElement/InspectedElementPane'; import {SettingsContextController} from './Settings/SettingsContext'; import {TreeContextController} from './Components/TreeContext'; import ViewElementSourceContext from './Components/ViewElementSourceContext'; @@ -100,6 +101,7 @@ export type Props = { // The root