Skip to content

Commit bb4418d

Browse files
authored
[DevTools] Linkify Source View (facebook#33954)
This makes it so you can click the source ___location itself to view the source. This is similar styling as the link to jump to function props like events and actions. We're going to need a lot more linkifying to jump to various source locations. Also, I always was trying to click this file anyway. Hover state: <img width="485" height="382" alt="Screenshot 2025-07-21 at 4 36 10 PM" src="https://github.com/user-attachments/assets/1f0f8f8c-6866-4e62-ab84-1fb5ba012986" />
1 parent 074e927 commit bb4418d

File tree

2 files changed

+50
-12
lines changed

2 files changed

+50
-12
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,18 @@
1818
max-width: 100%;
1919
margin-left: 1rem;
2020
}
21+
22+
.Link {
23+
color: var(--color-link);
24+
white-space: pre;
25+
overflow: hidden;
26+
text-overflow: ellipsis;
27+
flex: 1;
28+
cursor: pointer;
29+
border-radius: 0.125rem;
30+
padding: 0px 2px;
31+
}
32+
33+
.Link:hover {
34+
background-color: var(--color-background-hover);
35+
}

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

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import * as React from 'react';
11+
import {useCallback, useContext} from 'react';
1112
import {copy} from 'clipboard-js';
1213
import {toNormalUrl} from 'jsc-safe-url';
1314

@@ -16,6 +17,8 @@ import ButtonIcon from '../ButtonIcon';
1617
import Skeleton from './Skeleton';
1718
import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck';
1819

20+
import ViewElementSourceContext from './ViewElementSourceContext';
21+
1922
import type {Source as InspectedElementSource} from 'react-devtools-shared/src/shared/types';
2023
import styles from './InspectedElementSourcePanel.css';
2124

@@ -87,25 +90,45 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) {
8790

8891
function FormattedSourceString({source, symbolicatedSourcePromise}: Props) {
8992
const symbolicatedSource = React.use(symbolicatedSourcePromise);
90-
if (symbolicatedSource == null) {
91-
const {sourceURL, line} = source;
9293

93-
return (
94-
<div
95-
className={styles.SourceOneLiner}
96-
data-testname="InspectedElementView-FormattedSourceString">
97-
{formatSourceForDisplay(sourceURL, line)}
98-
</div>
99-
);
100-
}
94+
const {canViewElementSourceFunction, viewElementSourceFunction} = useContext(
95+
ViewElementSourceContext,
96+
);
97+
98+
// In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source.
99+
// To detect this case, we defer to an injected helper function (if present).
100+
const linkIsEnabled =
101+
viewElementSourceFunction != null &&
102+
source != null &&
103+
(canViewElementSourceFunction == null ||
104+
canViewElementSourceFunction(source, symbolicatedSource));
105+
106+
const viewSource = useCallback(() => {
107+
if (viewElementSourceFunction != null && source != null) {
108+
viewElementSourceFunction(source, symbolicatedSource);
109+
}
110+
}, [source, symbolicatedSource]);
101111

102-
const {sourceURL, line} = symbolicatedSource;
112+
let sourceURL, line;
113+
if (symbolicatedSource == null) {
114+
sourceURL = source.sourceURL;
115+
line = source.line;
116+
} else {
117+
sourceURL = symbolicatedSource.sourceURL;
118+
line = symbolicatedSource.line;
119+
}
103120

104121
return (
105122
<div
106123
className={styles.SourceOneLiner}
107124
data-testname="InspectedElementView-FormattedSourceString">
108-
{formatSourceForDisplay(sourceURL, line)}
125+
{linkIsEnabled ? (
126+
<span className={styles.Link} onClick={viewSource}>
127+
{formatSourceForDisplay(sourceURL, line)}
128+
</span>
129+
) : (
130+
formatSourceForDisplay(sourceURL, line)
131+
)}
109132
</div>
110133
);
111134
}

0 commit comments

Comments
 (0)