Skip to content

Commit 5d4a6d8

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents cc11070 + c80a075 commit 5d4a6d8

File tree

14 files changed

+262
-33
lines changed

14 files changed

+262
-33
lines changed

compiler/yarn.lock

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10525,7 +10525,16 @@ string-length@^4.0.1:
1052510525
char-regex "^1.0.2"
1052610526
strip-ansi "^6.0.0"
1052710527

10528-
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
10528+
"string-width-cjs@npm:string-width@^4.2.0":
10529+
version "4.2.3"
10530+
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
10531+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
10532+
dependencies:
10533+
emoji-regex "^8.0.0"
10534+
is-fullwidth-code-point "^3.0.0"
10535+
strip-ansi "^6.0.1"
10536+
10537+
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
1052910538
version "4.2.3"
1053010539
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
1053110540
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10598,7 +10607,14 @@ string_decoder@~1.1.1:
1059810607
dependencies:
1059910608
safe-buffer "~5.1.0"
1060010609

10601-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
10610+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
10611+
version "6.0.1"
10612+
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
10613+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
10614+
dependencies:
10615+
ansi-regex "^5.0.1"
10616+
10617+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1060210618
version "6.0.1"
1060310619
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
1060410620
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11064,9 +11080,9 @@ undici-types@~6.19.2:
1106411080
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
1106511081

1106611082
undici@^6.19.5:
11067-
version "6.21.2"
11068-
resolved "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz"
11069-
integrity sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==
11083+
version "6.23.0"
11084+
resolved "https://registry.yarnpkg.com/undici/-/undici-6.23.0.tgz#7953087744d9095a96f115de3140ca3828aff3a4"
11085+
integrity sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==
1107011086

1107111087
unicode-canonical-property-names-ecmascript@^2.0.0:
1107211088
version "2.0.1"
@@ -11375,7 +11391,7 @@ workerpool@^6.5.1:
1137511391
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz"
1137611392
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
1137711393

11378-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
11394+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
1137911395
version "7.0.0"
1138011396
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
1138111397
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -11393,6 +11409,15 @@ wrap-ansi@^6.2.0:
1139311409
string-width "^4.1.0"
1139411410
strip-ansi "^6.0.0"
1139511411

11412+
wrap-ansi@^7.0.0:
11413+
version "7.0.0"
11414+
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
11415+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
11416+
dependencies:
11417+
ansi-styles "^4.0.0"
11418+
string-width "^4.1.0"
11419+
strip-ansi "^6.0.0"
11420+
1139611421
wrap-ansi@^8.1.0:
1139711422
version "8.1.0"
1139811423
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"

fixtures/packaging/webpack-alias/dev/yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -936,8 +936,8 @@ punycode@^1.2.4, punycode@^1.4.1:
936936
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
937937

938938
qs@~6.4.0:
939-
version "6.4.0"
940-
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
939+
version "6.4.1"
940+
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.1.tgz#2bad97710a5b661c366b378b1e3a44a592ff45e6"
941941

942942
querystring-es3@^0.2.0:
943943
version "0.2.1"

fixtures/packaging/webpack/prod/yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -936,8 +936,8 @@ punycode@^1.2.4, punycode@^1.4.1:
936936
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
937937

938938
qs@~6.4.0:
939-
version "6.4.0"
940-
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
939+
version "6.4.1"
940+
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.1.tgz#2bad97710a5b661c366b378b1e3a44a592ff45e6"
941941

942942
querystring-es3@^0.2.0:
943943
version "0.2.1"

packages/react-client/src/ReactFlightClient.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,8 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
10401040
// Initialize any debug info and block the initializing chunk on any
10411041
// unresolved entries.
10421042
initializeDebugChunk(response, chunk);
1043+
// TODO: The chunk might have transitioned to ERRORED now.
1044+
// Should we return early if that happens?
10431045
}
10441046

10451047
try {
@@ -1075,6 +1077,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
10751077
const initializedChunk: InitializedChunk<T> = (chunk: any);
10761078
initializedChunk.status = INITIALIZED;
10771079
initializedChunk.value = value;
1080+
initializedChunk.reason = null;
10781081

10791082
if (__DEV__) {
10801083
processChunkDebugInfo(response, initializedChunk, value);
@@ -1097,6 +1100,7 @@ function initializeModuleChunk<T>(chunk: ResolvedModuleChunk<T>): void {
10971100
const initializedChunk: InitializedChunk<T> = (chunk: any);
10981101
initializedChunk.status = INITIALIZED;
10991102
initializedChunk.value = value;
1103+
initializedChunk.reason = null;
11001104
} catch (error) {
11011105
const erroredChunk: ErroredChunk<T> = (chunk: any);
11021106
erroredChunk.status = ERRORED;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,9 @@ export default function InspectedElementSuspendedBy({
584584
break;
585585
}
586586

587+
if (groups.length === 0) {
588+
return null;
589+
}
587590
return (
588591
<div>
589592
<div className={styles.HeaderRow}>

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4520,18 +4520,30 @@ export function setFocusIfFocusable(
45204520
//
45214521
// We could compare the node to document.activeElement after focus,
45224522
// but this would not handle the case where application code managed focus to automatically blur.
4523+
const element = ((node: any): HTMLElement);
4524+
4525+
// If this element is already the active element, it's focusable and already
4526+
// focused. Calling .focus() on it would be a no-op (no focus event fires),
4527+
// so we short-circuit here.
4528+
if (element.ownerDocument.activeElement === element) {
4529+
return true;
4530+
}
4531+
45234532
let didFocus = false;
45244533
const handleFocus = () => {
45254534
didFocus = true;
45264535
};
45274536

4528-
const element = ((node: any): HTMLElement);
45294537
try {
4530-
element.addEventListener('focus', handleFocus);
4538+
// Listen on the document in the capture phase so we detect focus even when
4539+
// it lands on a different element than the one we called .focus() on. This
4540+
// happens with <label> elements (focus delegates to the associated input)
4541+
// and shadow hosts with delegatesFocus.
4542+
element.ownerDocument.addEventListener('focus', handleFocus, true);
45314543
// $FlowFixMe[method-unbinding]
45324544
(element.focus || HTMLElement.prototype.focus).call(element, focusOptions);
45334545
} finally {
4534-
element.removeEventListener('focus', handleFocus);
4546+
element.ownerDocument.removeEventListener('focus', handleFocus, true);
45354547
}
45364548

45374549
return didFocus;

packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,102 @@ describe('FragmentRefs', () => {
255255
expect(document.activeElement.id).toEqual('child-b');
256256
document.activeElement.blur();
257257
});
258+
259+
// @gate enableFragmentRefs
260+
it('keeps focus on the first focusable child if already focused', async () => {
261+
const fragmentRef = React.createRef();
262+
const root = ReactDOMClient.createRoot(container);
263+
264+
function Test() {
265+
return (
266+
<Fragment ref={fragmentRef}>
267+
<a id="child-a" href="/">
268+
A
269+
</a>
270+
<a id="child-b" href="/">
271+
B
272+
</a>
273+
</Fragment>
274+
);
275+
}
276+
277+
await act(() => {
278+
root.render(<Test />);
279+
});
280+
281+
// Focus the first child manually
282+
document.getElementById('child-a').focus();
283+
expect(document.activeElement.id).toEqual('child-a');
284+
285+
// Calling fragment.focus() should keep focus on child-a,
286+
// not skip to child-b
287+
await act(() => {
288+
fragmentRef.current.focus();
289+
});
290+
expect(document.activeElement.id).toEqual('child-a');
291+
document.activeElement.blur();
292+
});
293+
294+
// @gate enableFragmentRefs
295+
it('keeps focus on a nested child if already focused', async () => {
296+
const fragmentRef = React.createRef();
297+
const root = ReactDOMClient.createRoot(container);
298+
299+
function Test() {
300+
return (
301+
<Fragment ref={fragmentRef}>
302+
<div>
303+
<input id="nested-input" />
304+
</div>
305+
<a id="sibling-link" href="/">
306+
Link
307+
</a>
308+
</Fragment>
309+
);
310+
}
311+
312+
await act(() => {
313+
root.render(<Test />);
314+
});
315+
316+
// Focus the nested input manually
317+
document.getElementById('nested-input').focus();
318+
expect(document.activeElement.id).toEqual('nested-input');
319+
320+
// Calling fragment.focus() should keep focus on nested-input
321+
await act(() => {
322+
fragmentRef.current.focus();
323+
});
324+
expect(document.activeElement.id).toEqual('nested-input');
325+
document.activeElement.blur();
326+
});
327+
328+
// @gate enableFragmentRefs
329+
it('focuses the first focusable child in a fieldset', async () => {
330+
const fragmentRef = React.createRef();
331+
const root = ReactDOMClient.createRoot(container);
332+
333+
function Test() {
334+
return (
335+
<Fragment ref={fragmentRef}>
336+
<fieldset>
337+
<legend>Shipping</legend>
338+
<input id="street" name="street" />
339+
<input id="city" name="city" />
340+
</fieldset>
341+
</Fragment>
342+
);
343+
}
344+
345+
await act(() => {
346+
root.render(<Test />);
347+
});
348+
await act(() => {
349+
fragmentRef.current.focus();
350+
});
351+
expect(document.activeElement.id).toEqual('street');
352+
document.activeElement.blur();
353+
});
258354
});
259355

260356
describe('focusLast()', () => {

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,95 @@ describe('ReactFlightDOM', () => {
14181418
expect(reportedErrors).toEqual([]);
14191419
});
14201420

1421+
it('should not retain stale error reason after reentrant module chunk initialization', async () => {
1422+
function MyComponent() {
1423+
return <div>hello from client component</div>;
1424+
}
1425+
const ClientComponent = clientExports(MyComponent);
1426+
1427+
let resolveAsyncComponent;
1428+
async function AsyncComponent() {
1429+
await new Promise(r => {
1430+
resolveAsyncComponent = r;
1431+
});
1432+
return null;
1433+
}
1434+
1435+
function ServerComponent() {
1436+
return (
1437+
<>
1438+
<ClientComponent />
1439+
<Suspense>
1440+
<AsyncComponent />
1441+
</Suspense>
1442+
</>
1443+
);
1444+
}
1445+
1446+
const {writable: flightWritable, readable: flightReadable} =
1447+
getTestStream();
1448+
const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
1449+
1450+
const {pipe} = await serverAct(() =>
1451+
ReactServerDOMServer.renderToPipeableStream(
1452+
<ServerComponent />,
1453+
webpackMap,
1454+
),
1455+
);
1456+
pipe(flightWritable);
1457+
1458+
let response = null;
1459+
function getResponse() {
1460+
if (response === null) {
1461+
response =
1462+
ReactServerDOMClient.createFromReadableStream(flightReadable);
1463+
}
1464+
return response;
1465+
}
1466+
1467+
// Simulate a module that calls captureOwnerStack() during evaluation.
1468+
// In Fizz SSR, this causes a reentrant readChunk on the same module chunk.
1469+
// The reentrant require throws a TDZ error.
1470+
let evaluatingModuleId = null;
1471+
const origRequire = global.__webpack_require__;
1472+
global.__webpack_require__ = function (id) {
1473+
if (id === evaluatingModuleId) {
1474+
throw new ReferenceError(
1475+
"Cannot access 'MyComponent' before initialization",
1476+
);
1477+
}
1478+
const result = origRequire(id);
1479+
if (result === MyComponent) {
1480+
evaluatingModuleId = id;
1481+
if (__DEV__) {
1482+
React.captureOwnerStack();
1483+
}
1484+
evaluatingModuleId = null;
1485+
}
1486+
return result;
1487+
};
1488+
1489+
function App() {
1490+
return use(getResponse());
1491+
}
1492+
1493+
await serverAct(async () => {
1494+
ReactDOMFizzServer.renderToPipeableStream(<App />).pipe(fizzWritable);
1495+
});
1496+
1497+
global.__webpack_require__ = origRequire;
1498+
1499+
// Resolve the async component so the Flight stream closes after the client
1500+
// module chunk was initialized.
1501+
await serverAct(async () => {
1502+
resolveAsyncComponent();
1503+
});
1504+
1505+
const container = document.createElement('div');
1506+
await readInto(container, fizzReadable);
1507+
expect(container.innerHTML).toContain('hello from client component');
1508+
});
1509+
14211510
it('should be able to recover from a direct reference erroring server-side', async () => {
14221511
const reportedErrors = [];
14231512

packages/react-server/src/ReactFlightReplyServer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ function loadServerReference<A: Iterable<any>, T>(
478478
const initializedPromise: InitializedChunk<T> = (blockedPromise: any);
479479
initializedPromise.status = INITIALIZED;
480480
initializedPromise.value = resolvedValue;
481+
initializedPromise.reason = null;
481482
return resolvedValue;
482483
}
483484
} else if (bound instanceof ReactPromise) {

packages/shared/ReactFeatureFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export const enableInfiniteRenderLoopDetection: boolean = false;
145145

146146
export const enableFragmentRefs: boolean = true;
147147
export const enableFragmentRefsScrollIntoView: boolean = true;
148-
export const enableFragmentRefsInstanceHandles: boolean = false;
148+
export const enableFragmentRefsInstanceHandles: boolean = true;
149149
export const enableFragmentRefsTextNodes: boolean = true;
150150

151151
export const enableInternalInstanceMap: boolean = false;

0 commit comments

Comments
 (0)