diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index bce648a5dba..76b320f1a66 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -330,6 +330,7 @@ function createElementsInspectPanel() { inspectedElementPortalContainer = portal.container; if (inspectedElementPortalContainer != null && render) { ensureInitialHTMLIsCleared(inspectedElementPortalContainer); + bridge.send('syncSelectionFromBuiltinElementsPanel'); render(); portal.injectStyles(cloneStyleTags); diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 42fbbc9648a..a7c237be721 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -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 = () => { diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index af98cb82989..bc2669fda51 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -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}], diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 6ddaedb7981..c48d5b42fb6 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -147,7 +147,7 @@ export default class Store extends EventEmitter<{ enableSuspenseTab: [], error: [Error], hookSettings: [$ReadOnly], - hostInstanceSelected: [Element['id']], + hostInstanceSelected: [Element['id'] | null], settingsUpdated: [$ReadOnly], 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; } diff --git a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js index 2a1477ddb57..80b1e9dce3e 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js @@ -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);