@samitouri / QOS-React-2 / commits / 4f273bd364

fix(devtools): clear highlight when mouse leaves DevTools panel (#36177)

Fixes #17855 When hovering a component in the DevTools Components inspector, a highlight overlay appears on the inspected page. The highlight is cleared via `onMouseLeave` on the tree container `div`. But this React synthetic event only fires when the pointer transitions between elements _within the same document_. When the user moves their mouse out of the DevTools panel window entirely (e.g. to the browser viewport), no element in the React tree receives `mouseleave`, so `clearHostInstanceHighlight` is never sent over the bridge and the overlay persists on the page. The fix adds a native `mouseleave` listener on the DevTools panel's `ownerDocument` in `Tree.js`. When the pointer exits the panel viewport, it fires `clearHighlightHostInstance` and removes the overlay. Using `ownerDocument` (rather than document) is consistent with the existing pattern in `Tree.js` for browser extension compatibility. How did you test this change? Tested manually using the Chrome extension: 1. Opened React DevTools → Components tab on a React app 2. Hovered a component in the tree — highlight appeared on the page ✓ 3. Moved the mouse out of the DevTools panel into the browser viewport — highlight cleared immediately ✓ (previously it persisted) 4. Moved the mouse back into the panel and hovered a component — highlighting still works normally ✓ 5. Unhovered within the panel — highlight still clears correctly ✓ Ran the DevTools test suite: yarn test --no-watchman ReactDevTools — all tests pass.

petertdinh committed May 2, 2026 at 01:50 UTC 4f273bd36493cd3e818a15a5da5f82ba6af7f812
1 file changed +17
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+17
@@ -384,6 +384,23 @@ export default function Tree(): React.Node {
384
385 const handleMouseLeave = clearHighlightHostInstance;
386
387 + // The synthetic onMouseLeave on the tree div only fires within the document,
388 + // so we need a native listener on the document itself.
389 + useEffect(() => {
390 + const container = focusTargetRef.current;
391 + if (container == null) {
392 + return;
393 + }
394 + const ownerDocument = container.ownerDocument;
395 + ownerDocument.addEventListener('mouseleave', clearHighlightHostInstance);
396 + return () => {
397 + ownerDocument.removeEventListener(
398 + 'mouseleave',
399 + clearHighlightHostInstance,
400 + );
401 + };
402 + }, [clearHighlightHostInstance]);
403 +
404 // Let react-window know to re-render any time the underlying tree data changes.
405 // This includes the owner context, since it controls a filtered view of the tree.
406 const itemData = useMemo<ItemData>(