React version: 19.3.1; also reproduced on main at 16453bcd12becf76340caa793d185cec1b80ebef.
Steps To Reproduce
Mount a React root in a shadow root, focus a keyed input, then change the order of the inputs so that React moves the focused DOM node. For example, run this in a React app:
import React from 'react';
import {createRoot} from 'react-dom/client';
import {flushSync} from 'react-dom';
const host = document.body.appendChild(document.createElement('div'));
const shadow = host.attachShadow({mode: 'open'});
const root = createRoot(shadow);
function render(keys) {
flushSync(() => {
root.render(keys.map(key => (
<input key={key} data-key={key} defaultValue={key + ' text'} />
)));
});
}
render(['a', 'b']);
const input = shadow.querySelector('[data-key="a"]');
input.focus();
input.setSelectionRange(1, 4);
render(['b', 'a']);
console.log(shadow.querySelector('[data-key="a"]') === input); // true
console.log(shadow.activeElement === input); // false; expected true
Using a closed shadow root also reproduces the problem. Mounting the same inputs in an ordinary DOM container preserves focus.
The current behavior
The keyed input is reused, but it loses focus after the reorder. This interrupts typing in React forms embedded in web components.
The expected behavior
The same input should remain focused with its text selection preserved, as it does in an ordinary DOM container.
Cause
In , traverses same-origin iframes but stops at shadow hosts, so the commit captures the host instead of the focused input. In addition, uses , which returns false for a connected shadow descendant. Both checks prevent commit-time focus restoration.