[DevTools] Clear element inspection if host element not owned by any renderer is selected (#35504)

This commit is contained in:
Sebastian "Sebbie" Silbermann
2026-01-16 13:20:44 +01:00
committed by GitHub
parent cbc4d40663
commit 01c4d03d84
5 changed files with 28 additions and 14 deletions

View File

@@ -330,6 +330,7 @@ function createElementsInspectPanel() {
inspectedElementPortalContainer = portal.container;
if (inspectedElementPortalContainer != null && render) {
ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
bridge.send('syncSelectionFromBuiltinElementsPanel');
render();
portal.injectStyles(cloneStyleTags);

View File

@@ -938,11 +938,19 @@ export default class Agent extends EventEmitter<{
}
};
selectNode(target: HostInstance): void {
const match = this.getIDForHostInstance(target);
if (match !== null) {
this._bridge.send('selectElement', match.id);
}
selectNode(target: HostInstance | null): void {
const match = target !== null ? this.getIDForHostInstance(target) : null;
this._bridge.send(
'selectElement',
match !== null
? match.id
: // If you click outside a React root in the Elements panel, we want to give
// feedback that no selection is possible so we clear the selection.
// Otherwise clicking outside a React root is indistinguishable from clicking
// a different host node that leads to the same selected React element
// due to Component filters
null,
);
}
registerRendererInterface(
@@ -988,10 +996,7 @@ export default class Agent extends EventEmitter<{
syncSelectionFromBuiltinElementsPanel: () => void = () => {
const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
if (target == null) {
return;
}
this.selectNode(target);
this.selectNode(target == null ? null : target);
};
shutdown: () => void = () => {

View File

@@ -214,7 +214,7 @@ export type BackendEvents = {
profilingStatus: [boolean],
reloadAppForProfiling: [],
saveToClipboard: [string],
selectElement: [number],
selectElement: [number | null],
shutdown: [],
stopInspectingHost: [boolean],
scrollTo: [{left: number, top: number, right: number, bottom: number}],

View File

@@ -147,7 +147,7 @@ export default class Store extends EventEmitter<{
enableSuspenseTab: [],
error: [Error],
hookSettings: [$ReadOnly<DevToolsHookSettings>],
hostInstanceSelected: [Element['id']],
hostInstanceSelected: [Element['id'] | null],
settingsUpdated: [$ReadOnly<DevToolsHookSettings>],
mutated: [
[
@@ -2381,8 +2381,15 @@ export default class Store extends EventEmitter<{
this._bridge.send('getHookSettings'); // Warm up cached hook settings
};
onHostInstanceSelected: (elementId: number) => void = elementId => {
if (this._lastSelectedHostInstanceElementId === elementId) {
onHostInstanceSelected: (elementId: number | null) => void = elementId => {
if (
this._lastSelectedHostInstanceElementId === elementId &&
// Force clear selection e.g. when we inspect an element in the Components panel
// and then switch to the browser's Elements panel.
// We wouldn't want to stay on the inspected element if we're inspecting
// an element not owned by React when switching to the browser's Elements panel.
elementId !== null
) {
return;
}

View File

@@ -967,8 +967,9 @@ function TreeContextController({
// Listen for host element selections.
useEffect(() => {
const handler = (id: Element['id']) =>
const handler = (id: Element['id'] | null) => {
transitionDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
};
store.addListener('hostInstanceSelected', handler);
return () => store.removeListener('hostInstanceSelected', handler);