Skip to content

Commit 29c965a

Browse files
committed
[DevTools] Add React Element pane to browser Elements panel
1 parent 74fa166 commit 29c965a

File tree

8 files changed

+130
-3
lines changed

8 files changed

+130
-3
lines changed

packages/react-devtools-extensions/src/main/elementSelection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function setReactSelectionFromBrowser(bridge) {
3434
return;
3535
}
3636

37-
// Remember to sync the selection next time we show Components tab.
37+
// Remember to sync the selection next time we show inspected element
3838
bridge.send('syncSelectionFromBuiltinElementsPanel');
3939
}
4040
},

packages/react-devtools-extensions/src/main/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ function createBridgeAndStore() {
205205
bridge,
206206
browserTheme: getBrowserTheme(),
207207
componentsPortalContainer,
208+
inspectedElementPortalContainer,
208209
profilerPortalContainer,
209210
editorPortalContainer,
210211
currentSelectedSource,
@@ -277,6 +278,46 @@ function createComponentsPanel() {
277278
);
278279
}
279280

281+
function createElementsInspectPanel() {
282+
if (inspectedElementPortalContainer) {
283+
// Panel is created and user opened it at least once
284+
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
285+
render();
286+
287+
return;
288+
}
289+
290+
if (inspectedElementPane) {
291+
// Panel is created, but wasn't opened yet, so no document is present for it
292+
return;
293+
}
294+
295+
const elementsPanel = chrome.devtools.panels.elements;
296+
if (!elementsPanel || !elementsPanel.createSidebarPane) {
297+
// Firefox doesn't currently support extending the elements panel?
298+
return;
299+
}
300+
301+
elementsPanel.createSidebarPane('React Element ⚛', createdPane => {
302+
inspectedElementPane = createdPane;
303+
304+
createdPane.setPage('panel.html');
305+
createdPane.setHeight('75px');
306+
307+
createdPane.onShown.addListener(portal => {
308+
inspectedElementPortalContainer = portal.container;
309+
if (inspectedElementPortalContainer != null && render) {
310+
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
311+
312+
render();
313+
portal.injectStyles(cloneStyleTags);
314+
315+
logEvent({event_name: 'selected-inspected-element-pane'});
316+
}
317+
});
318+
});
319+
}
320+
280321
function createProfilerPanel() {
281322
if (profilerPortalContainer) {
282323
// Panel is created and user opened it at least once
@@ -507,6 +548,7 @@ function mountReactDevTools() {
507548
createComponentsPanel();
508549
createProfilerPanel();
509550
createSourcesEditorPanel();
551+
createElementsInspectPanel();
510552
// Suspense Tab is created via the hook
511553
// TODO(enableSuspenseTab): Create eagerly once Suspense tab is stable
512554
}
@@ -555,10 +597,12 @@ let componentsPanel = null;
555597
let profilerPanel = null;
556598
let suspensePanel = null;
557599
let editorPane = null;
600+
let inspectedElementPane = null;
558601
let componentsPortalContainer = null;
559602
let profilerPortalContainer = null;
560603
let suspensePortalContainer = null;
561604
let editorPortalContainer = null;
605+
let inspectedElementPortalContainer = null;
562606

563607
let mostRecentOverrideTab = null;
564608
let render = null;

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ export default class Agent extends EventEmitter<{
940940

941941
selectNode(target: HostInstance): void {
942942
const match = this.getIDForHostInstance(target);
943+
console.log({match});
943944
if (match !== null) {
944945
this._bridge.send('selectElement', match.id);
945946
}
@@ -988,6 +989,7 @@ export default class Agent extends EventEmitter<{
988989

989990
syncSelectionFromBuiltinElementsPanel: () => void = () => {
990991
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
992+
console.log(target);
991993
if (target == null) {
992994
return;
993995
}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,11 @@
7777
padding: 0.25rem;
7878
color: var(--color-console-error-icon);
7979
}
80+
81+
.VRule {
82+
height: 20px;
83+
width: 1px;
84+
flex: 0 0 1px;
85+
margin: 0 0.5rem;
86+
background-color: var(--color-border);
87+
}

packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ import useEditorURL from '../useEditorURL';
3434
import styles from './InspectedElement.css';
3535
import Tooltip from './reach-ui/tooltip';
3636

37-
export type Props = {};
37+
export type Props = {
38+
actionButtons?: React.Node,
39+
};
3840

3941
// TODO Make edits and deletes also use transition API!
4042

4143
const noSourcePromise = Promise.resolve(null);
4244

43-
export default function InspectedElementWrapper(_: Props): React.Node {
45+
export default function InspectedElementWrapper({
46+
actionButtons,
47+
}: Props): React.Node {
4448
const {inspectedElementID} = useContext(TreeStateContext);
4549
const bridge = useContext(BridgeContext);
4650
const store = useContext(StoreContext);
@@ -305,6 +309,13 @@ export default function InspectedElementWrapper(_: Props): React.Node {
305309
symbolicatedSourcePromise={symbolicatedSourcePromise}
306310
/>
307311
)}
312+
313+
{actionButtons && (
314+
<>
315+
<div className={styles.VRule} />
316+
{actionButtons}
317+
</>
318+
)}
308319
</div>
309320

310321
{inspectedElement === null && (

packages/react-devtools-shared/src/devtools/views/DevTools.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Profiler from './Profiler/Profiler';
2626
import SuspenseTab from './SuspenseTab/SuspenseTab';
2727
import TabBar from './TabBar';
2828
import EditorPane from './Editor/EditorPane';
29+
import InspectedElementPane from './InspectedElement/InspectedElementPane';
2930
import {SettingsContextController} from './Settings/SettingsContext';
3031
import {TreeContextController} from './Components/TreeContext';
3132
import ViewElementSourceContext from './Components/ViewElementSourceContext';
@@ -100,6 +101,7 @@ export type Props = {
100101
// The root <DevTools> app is rendered in the top-level extension window,
101102
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
102103
componentsPortalContainer?: Element,
104+
inspectedElementPortalContainer?: Element,
103105
profilerPortalContainer?: Element,
104106
suspensePortalContainer?: Element,
105107
editorPortalContainer?: Element,
@@ -155,6 +157,7 @@ export default function DevTools({
155157
canViewElementSourceFunction,
156158
componentsPortalContainer,
157159
editorPortalContainer,
160+
inspectedElementPortalContainer,
158161
profilerPortalContainer,
159162
suspensePortalContainer,
160163
currentSelectedSource,
@@ -379,6 +382,14 @@ export default function DevTools({
379382
portalContainer={editorPortalContainer}
380383
/>
381384
) : null}
385+
{inspectedElementPortalContainer ? (
386+
<InspectedElementPane
387+
selectedSource={currentSelectedSource}
388+
portalContainer={
389+
inspectedElementPortalContainer
390+
}
391+
/>
392+
) : null}
382393
</ThemeProvider>
383394
</SuspenseTreeContextController>
384395
</InspectedElementContextController>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.InspectedElementPane, .InspectedElementPane * {
2+
box-sizing: border-box;
3+
-webkit-font-smoothing: var(--font-smoothing);
4+
}
5+
6+
.InspectedElementPane {
7+
position: relative;
8+
width: 100%;
9+
height: 100%;
10+
display: flex;
11+
flex-direction: row;
12+
background-color: var(--color-background);
13+
color: var(--color-text);
14+
font-family: var(--font-family-sans);
15+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import * as React from 'react';
11+
import {useContext} from 'react';
12+
13+
import portaledContent from 'react-devtools-shared/src/devtools/views/portaledContent';
14+
import {OptionsContext} from 'react-devtools-shared/src/devtools/views/context';
15+
import InspectedElement from 'react-devtools-shared/src/devtools/views/Components/InspectedElement';
16+
import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal';
17+
import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle';
18+
import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext';
19+
import styles from './InspectedElementPane.css';
20+
21+
export type Props = {};
22+
23+
function InspectedElementPane({}: Props) {
24+
const {hideSettings} = useContext(OptionsContext);
25+
return (
26+
<SettingsModalContextController>
27+
<div className={styles.InspectedElementPane}>
28+
<InspectedElement
29+
actionButtons={!hideSettings && <SettingsModalContextToggle />}
30+
/>
31+
<SettingsModal />
32+
</div>
33+
</SettingsModalContextController>
34+
);
35+
}
36+
export default (portaledContent(InspectedElementPane): component());

0 commit comments

Comments
 (0)