@reckona/mreact-compat 0.0.91 → 0.0.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -0
  2. package/dist/class-component.d.ts +20 -6
  3. package/dist/class-component.d.ts.map +1 -1
  4. package/dist/class-component.js +94 -51
  5. package/dist/class-component.js.map +1 -1
  6. package/dist/context.d.ts +19 -3
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/context.js +55 -6
  9. package/dist/context.js.map +1 -1
  10. package/dist/dom-children.d.ts +2 -0
  11. package/dist/dom-children.d.ts.map +1 -1
  12. package/dist/dom-children.js +103 -1
  13. package/dist/dom-children.js.map +1 -1
  14. package/dist/dom-host-rules.d.ts +10 -0
  15. package/dist/dom-host-rules.d.ts.map +1 -0
  16. package/dist/dom-host-rules.js +86 -0
  17. package/dist/dom-host-rules.js.map +1 -0
  18. package/dist/dom-props.d.ts +3 -2
  19. package/dist/dom-props.d.ts.map +1 -1
  20. package/dist/dom-props.js +229 -33
  21. package/dist/dom-props.js.map +1 -1
  22. package/dist/element.d.ts +9 -4
  23. package/dist/element.d.ts.map +1 -1
  24. package/dist/element.js +101 -26
  25. package/dist/element.js.map +1 -1
  26. package/dist/event-listeners.d.ts +4 -4
  27. package/dist/event-listeners.d.ts.map +1 -1
  28. package/dist/event-listeners.js +1 -1
  29. package/dist/event-listeners.js.map +1 -1
  30. package/dist/event-types.d.ts +10 -0
  31. package/dist/event-types.d.ts.map +1 -1
  32. package/dist/event-types.js.map +1 -1
  33. package/dist/events.js +22 -1
  34. package/dist/events.js.map +1 -1
  35. package/dist/fiber-commit.d.ts +2 -1
  36. package/dist/fiber-commit.d.ts.map +1 -1
  37. package/dist/fiber-commit.js +13 -1
  38. package/dist/fiber-commit.js.map +1 -1
  39. package/dist/fiber-reconciler.d.ts.map +1 -1
  40. package/dist/fiber-reconciler.js +28 -7
  41. package/dist/fiber-reconciler.js.map +1 -1
  42. package/dist/fiber-work-loop.d.ts.map +1 -1
  43. package/dist/fiber-work-loop.js +4 -3
  44. package/dist/fiber-work-loop.js.map +1 -1
  45. package/dist/fiber.d.ts +5 -0
  46. package/dist/fiber.d.ts.map +1 -1
  47. package/dist/fiber.js +9 -0
  48. package/dist/fiber.js.map +1 -1
  49. package/dist/hooks-entry.d.ts +3 -0
  50. package/dist/hooks-entry.d.ts.map +1 -0
  51. package/dist/hooks-entry.js +2 -0
  52. package/dist/hooks-entry.js.map +1 -0
  53. package/dist/hooks.d.ts +39 -5
  54. package/dist/hooks.d.ts.map +1 -1
  55. package/dist/hooks.js +373 -326
  56. package/dist/hooks.js.map +1 -1
  57. package/dist/host-reconciler.d.ts +3 -0
  58. package/dist/host-reconciler.d.ts.map +1 -1
  59. package/dist/host-reconciler.js +1152 -64
  60. package/dist/host-reconciler.js.map +1 -1
  61. package/dist/hydration.d.ts +1 -1
  62. package/dist/hydration.d.ts.map +1 -1
  63. package/dist/hydration.js.map +1 -1
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/react-default.d.ts +4 -4
  69. package/dist/react-default.d.ts.map +1 -1
  70. package/dist/react-default.js +2 -1
  71. package/dist/react-default.js.map +1 -1
  72. package/dist/reconciler.d.ts.map +1 -1
  73. package/dist/reconciler.js +38 -22
  74. package/dist/reconciler.js.map +1 -1
  75. package/dist/root.d.ts.map +1 -1
  76. package/dist/root.js +48 -13
  77. package/dist/root.js.map +1 -1
  78. package/dist/server-render.d.ts +6 -0
  79. package/dist/server-render.d.ts.map +1 -0
  80. package/dist/server-render.js +307 -0
  81. package/dist/server-render.js.map +1 -0
  82. package/package.json +6 -2
  83. package/src/class-component.ts +216 -51
  84. package/src/context.ts +108 -9
  85. package/src/dom-children.ts +155 -1
  86. package/src/dom-host-rules.ts +115 -0
  87. package/src/dom-props.ts +297 -46
  88. package/src/element.ts +141 -31
  89. package/src/event-listeners.ts +6 -6
  90. package/src/event-types.ts +10 -0
  91. package/src/events.ts +32 -10
  92. package/src/fiber-commit.ts +16 -1
  93. package/src/fiber-reconciler.ts +39 -6
  94. package/src/fiber-work-loop.ts +4 -3
  95. package/src/fiber.ts +14 -0
  96. package/src/hooks-entry.ts +24 -0
  97. package/src/hooks.ts +482 -479
  98. package/src/host-reconciler.ts +1628 -94
  99. package/src/hydration.ts +1 -1
  100. package/src/index.ts +1 -1
  101. package/src/react-default.ts +1 -1
  102. package/src/reconciler.ts +61 -22
  103. package/src/root.ts +55 -12
  104. package/src/server-render.ts +478 -0
@@ -3,6 +3,7 @@ import {
3
3
  ERROR_BOUNDARY_TYPE,
4
4
  FORWARD_REF_TYPE,
5
5
  Fragment,
6
+ HOST_OWN_PROPS_META,
6
7
  LAZY_TYPE,
7
8
  MEMO_TYPE,
8
9
  Profiler,
@@ -16,22 +17,36 @@ import {
16
17
  type ReactCompatNode,
17
18
  } from "./element.js";
18
19
  import {
20
+ consumerContext,
19
21
  isReactCompatConsumer,
20
22
  isReactCompatProvider,
21
23
  renderWithContextProvider,
22
24
  useContext,
23
25
  } from "./context.js";
24
26
  import { applyPostChildFormProps, applyProps } from "./dom-props.js";
25
- import { syncChildNodes, syncScopedChildNodes } from "./dom-children.js";
27
+ import { syncChildNodes, syncOwnedChildNodes, syncScopedChildNodes } from "./dom-children.js";
26
28
  import { setLogicalEventParent } from "./host-event-binder.js";
29
+ import { NoFlags, Placement, Update } from "./fiber-flags.js";
30
+ import {
31
+ createHostElement,
32
+ hostElementMatches,
33
+ isHostElement,
34
+ namespaceForHostChildren,
35
+ namespaceForHostElement,
36
+ type HostNamespace,
37
+ } from "./dom-host-rules.js";
27
38
  import { createFiber, createWorkInProgress, type Fiber, type FiberRoot } from "./fiber.js";
28
39
  import {
29
40
  renderWithRootRuntime,
30
41
  renderWithProfiler,
31
- renderWithStrictMode,
42
+ renderWithStrictModeMemoCapture,
43
+ renderStrictModeReplay,
44
+ runWithHostCommit,
32
45
  restoreRuntimeSnapshot,
33
46
  takeRuntimeSnapshot,
34
47
  getDevToolsHookState,
48
+ hasContextDependency,
49
+ hasChangedContextDependency,
35
50
  type RootRuntime,
36
51
  } from "./hooks.js";
37
52
  import { isThenable } from "./thenable.js";
@@ -40,7 +55,7 @@ import {
40
55
  recoverClassComponentError,
41
56
  renderClassComponentWithRuntime,
42
57
  } from "./class-component.js";
43
- import { areMemoPropsEqual, getPendingProps } from "./prop-comparison.js";
58
+ import { areMemoPropsEqual, getPendingProps, shallowEqual } from "./prop-comparison.js";
44
59
  import {
45
60
  reportElementTextMismatch,
46
61
  reportExtraHydrationNodes,
@@ -58,16 +73,30 @@ interface MemoFiberState {
58
73
  instanceKeys: string[];
59
74
  }
60
75
 
76
+ interface FunctionFiberState {
77
+ element: ReactCompatElement;
78
+ props: Record<string, unknown>;
79
+ instanceKeys: string[];
80
+ hasContextDependencies: boolean;
81
+ }
82
+
61
83
  interface SuspenseFiberState {
62
84
  didSuspend: boolean;
63
85
  }
64
86
 
87
+ const committedPortalContainers = new Set<Element>();
88
+
65
89
  interface FiberHydrationOptions extends RenderOptions {
66
90
  previousNodes?: readonly Node[];
67
91
  resumeId?: string;
68
92
  consumeResumeMarkers?: boolean;
93
+ namespace?: HostNamespace;
94
+ documentRef?: Document;
69
95
  }
70
96
 
97
+ const SKIP_COMMIT_PATH = "\0";
98
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
99
+
71
100
  interface FiberReconcileResult {
72
101
  fiber: Fiber | undefined;
73
102
  consumed: number;
@@ -166,16 +195,18 @@ export function renderHostFiberRoot(
166
195
  options: FiberHydrationOptions = {},
167
196
  ): Fiber {
168
197
  const workInProgress = createWorkInProgress(root.current, { children: element });
198
+ const rootDocument = root.container.ownerDocument;
169
199
  const result = reconcileHostChild(
170
200
  workInProgress,
171
201
  root.current.child,
172
202
  element,
173
203
  runtime,
174
204
  options.previousNodes === undefined ? "0" : "",
175
- options,
205
+ { ...options, documentRef: options.documentRef ?? rootDocument },
176
206
  );
177
207
  workInProgress.child = result.fiber;
178
208
  workInProgress.memoizedProps = { children: element };
209
+ root.refCleanupKnown = true;
179
210
  return workInProgress;
180
211
  }
181
212
 
@@ -204,8 +235,29 @@ export function commitHostFiberRoot(
204
235
  finishedWork: Fiber,
205
236
  options: RenderOptions = {},
206
237
  ): void {
207
- const nodes = commitHostChildren(finishedWork.child, root.container, root.container, "0", options);
208
- syncChildNodes(root.container, nodes);
238
+ runWithHostCommit(() => {
239
+ try {
240
+ committedPortalContainers.clear();
241
+ const commitPath = getRootCommitPath(options);
242
+ if (!hasChildListMutation(finishedWork)) {
243
+ commitHostDirtyChildren(finishedWork.child, root.container, root.container, commitPath, options);
244
+ return;
245
+ }
246
+
247
+ if (
248
+ !finishedWork.childListChanged &&
249
+ finishedWork.subtreeChildListChanged &&
250
+ commitHostKeyedChildListMutation(finishedWork.child, root.container, root.container, commitPath, options)
251
+ ) {
252
+ return;
253
+ }
254
+
255
+ const nodes = commitHostChildren(finishedWork.child, root.container, root.container, commitPath, options);
256
+ syncChildNodes(root.container, nodes);
257
+ } finally {
258
+ committedPortalContainers.clear();
259
+ }
260
+ });
209
261
  }
210
262
 
211
263
  export function commitHydratingHostFiberRoot(
@@ -214,9 +266,16 @@ export function commitHydratingHostFiberRoot(
214
266
  scope: HydrationScope,
215
267
  options: FiberHydrationOptions = {},
216
268
  ): void {
217
- const eventRoot = root.container;
218
- const nodes = commitHostChildren(finishedWork.child, scope.parent, eventRoot, "", options);
219
- syncScopedChildNodes(scope.parent, scope.before, scope.after, nodes);
269
+ runWithHostCommit(() => {
270
+ try {
271
+ committedPortalContainers.clear();
272
+ const eventRoot = root.container;
273
+ const nodes = commitHostChildren(finishedWork.child, scope.parent, eventRoot, "", options);
274
+ syncScopedChildNodes(scope.parent, scope.before, scope.after, nodes);
275
+ } finally {
276
+ committedPortalContainers.clear();
277
+ }
278
+ });
220
279
 
221
280
  if (options.consumeResumeMarkers === true) {
222
281
  scope.before?.parentNode?.removeChild(scope.before);
@@ -232,17 +291,70 @@ function reconcileHostChild(
232
291
  path: string,
233
292
  options: FiberHydrationOptions = {},
234
293
  ): FiberReconcileResult {
235
- const children = normalizeChildren(node);
236
- const existingByKey = collectExistingKeyedFibers(currentFirstChild);
294
+ resetFiberRefSubtree(parent);
295
+ parent.subtreeFlags = NoFlags;
296
+ parent.childListChanged = false;
297
+ parent.subtreeChildListChanged = false;
298
+
299
+ if (node === null || node === undefined || typeof node === "boolean") {
300
+ parent.childListChanged = currentFirstChild !== undefined;
301
+ return { fiber: undefined, consumed: 0 };
302
+ }
303
+
304
+ const children = Array.isArray(node) ? node : undefined;
305
+ const rowResult =
306
+ children === undefined
307
+ ? undefined
308
+ : reconcileKeyedRowHostChildren(parent, currentFirstChild, children, options);
309
+ if (rowResult !== undefined) {
310
+ return rowResult;
311
+ }
312
+
313
+ const childCount = children === undefined ? 1 : children.length;
314
+ const hasKeyedChildren = children !== undefined && hasKeyedChild(children);
315
+ let existingByKey: Map<string, Fiber> | undefined;
316
+ let currentKeyed: Fiber | undefined = currentFirstChild;
237
317
  let currentUnkeyed = currentFirstChild;
238
318
  let first: Fiber | undefined;
239
319
  let previous: Fiber | undefined;
240
320
  let consumed = 0;
321
+ let skipRemainingKeyedLookup = false;
241
322
 
242
- children.forEach((child, index) => {
323
+ for (let index = 0; index < childCount; index += 1) {
324
+ const child = children === undefined ? node : children[index];
243
325
  const key = getNodeKey(child);
244
- const matchedCurrent =
245
- key === undefined ? currentUnkeyed : existingByKey.get(key);
326
+ let matchedCurrent: Fiber | undefined;
327
+
328
+ if (key === undefined) {
329
+ matchedCurrent = currentUnkeyed;
330
+ } else if (skipRemainingKeyedLookup) {
331
+ matchedCurrent = undefined;
332
+ } else if (existingByKey !== undefined) {
333
+ matchedCurrent = existingByKey.get(key);
334
+ } else if (currentKeyed?.key === key) {
335
+ matchedCurrent = currentKeyed;
336
+ currentKeyed = currentKeyed.sibling;
337
+ } else if (
338
+ children !== undefined &&
339
+ currentKeyed?.sibling?.key === key &&
340
+ canSkipSingleDeletedKeyedFiber(children, index, currentKeyed.sibling)
341
+ ) {
342
+ matchedCurrent = currentKeyed.sibling;
343
+ currentKeyed = currentKeyed.sibling.sibling;
344
+ } else {
345
+ if (
346
+ children !== undefined &&
347
+ hasKeyedChildren &&
348
+ canSkipRemainingKeyedLookup(currentKeyed, children, index)
349
+ ) {
350
+ skipRemainingKeyedLookup = true;
351
+ currentKeyed = undefined;
352
+ } else if (hasKeyedChildren) {
353
+ existingByKey = collectExistingKeyedFibers(currentKeyed);
354
+ matchedCurrent = existingByKey.get(key);
355
+ }
356
+ }
357
+
246
358
  const previousNodes =
247
359
  options.previousNodes === undefined
248
360
  ? undefined
@@ -253,13 +365,13 @@ function reconcileHostChild(
253
365
  child,
254
366
  key,
255
367
  runtime,
256
- joinPath(path, getNodePathSegment(child, index)),
368
+ getReconcileChildPath(path, child, index, options),
257
369
  previousNodes === undefined ? options : { ...options, previousNodes },
258
370
  );
259
371
  const fiber = result.fiber;
260
372
 
261
373
  if (fiber === undefined) {
262
- return;
374
+ continue;
263
375
  }
264
376
 
265
377
  if (key === undefined) {
@@ -275,7 +387,7 @@ function reconcileHostChild(
275
387
 
276
388
  fiber.return = parent;
277
389
  fiber.sibling = undefined;
278
- fiber.pendingProps = getPendingProps(child);
390
+ bubbleHostChild(parent, fiber);
279
391
  if (
280
392
  fiber.tag !== "memo" &&
281
393
  fiber.tag !== "function-component" &&
@@ -288,11 +400,479 @@ function reconcileHostChild(
288
400
  fiber.memoizedState = index;
289
401
  }
290
402
  previous = fiber;
291
- });
403
+ }
404
+
405
+ parent.childListChanged = childFiberListShapeChanged(currentFirstChild, first);
292
406
 
293
407
  return { fiber: first, consumed };
294
408
  }
295
409
 
410
+ function reconcileKeyedRowHostChildren(
411
+ parent: Fiber,
412
+ currentFirstChild: Fiber | undefined,
413
+ children: readonly ReactCompatNode[],
414
+ options: FiberHydrationOptions,
415
+ ): FiberReconcileResult | undefined {
416
+ if (
417
+ children.length === 0 ||
418
+ currentFirstChild === undefined ||
419
+ options.previousNodes !== undefined ||
420
+ !shouldUseDirectHostTextChild()
421
+ ) {
422
+ return undefined;
423
+ }
424
+
425
+ let currentKeyed: Fiber | undefined = currentFirstChild;
426
+ let first: Fiber | undefined;
427
+ let previous: Fiber | undefined;
428
+ let listShapeChanged = currentFirstChild === undefined;
429
+ let skipRemainingKeyedLookup = false;
430
+ let subtreeFlags = NoFlags;
431
+ let subtreeChildListChanged = false;
432
+ let hasRefSubtree = false;
433
+ const currentSiblingCount = countFiberSiblings(currentFirstChild);
434
+ const canReuseUnchangedRows =
435
+ currentSiblingCount === children.length ||
436
+ isKeyedAppendOnly(currentFirstChild, children, currentSiblingCount);
437
+ const row = createKeyedRowHostElementScratch();
438
+
439
+ for (let index = 0; index < children.length; index += 1) {
440
+ const child = children[index];
441
+
442
+ if (!readKeyedRowHostElement(child, row)) {
443
+ return undefined;
444
+ }
445
+
446
+ let matchedCurrent: Fiber | undefined;
447
+
448
+ if (skipRemainingKeyedLookup) {
449
+ matchedCurrent = undefined;
450
+ } else if (currentKeyed === undefined) {
451
+ listShapeChanged = true;
452
+ skipRemainingKeyedLookup = true;
453
+ matchedCurrent = undefined;
454
+ } else if (currentKeyed?.key === row.key) {
455
+ matchedCurrent = currentKeyed;
456
+ currentKeyed = currentKeyed.sibling;
457
+ } else if (
458
+ currentKeyed?.sibling?.key === row.key &&
459
+ canSkipSingleDeletedKeyedFiber(children, index, currentKeyed.sibling)
460
+ ) {
461
+ listShapeChanged = true;
462
+ matchedCurrent = currentKeyed.sibling;
463
+ currentKeyed = currentKeyed.sibling.sibling;
464
+ } else if (canSkipRemainingKeyedLookup(currentKeyed, children, index)) {
465
+ listShapeChanged = true;
466
+ skipRemainingKeyedLookup = true;
467
+ currentKeyed = undefined;
468
+ matchedCurrent = undefined;
469
+ } else {
470
+ return undefined;
471
+ }
472
+
473
+ const fiber =
474
+ matchedCurrent === undefined
475
+ ? createKeyedRowHostFiber(parent, undefined, row, options)
476
+ : (canReuseUnchangedRows ? getReusableKeyedRowHostFiber(matchedCurrent, row) : undefined) ??
477
+ createKeyedRowHostFiber(parent, matchedCurrent, row, options);
478
+
479
+ if (first === undefined) {
480
+ first = fiber;
481
+ } else if (previous !== undefined) {
482
+ previous.sibling = fiber;
483
+ }
484
+
485
+ fiber.return = parent;
486
+ fiber.sibling = undefined;
487
+ if (fiber.hasRefSubtree) {
488
+ hasRefSubtree = true;
489
+ }
490
+ subtreeFlags |= fiber.flags | fiber.subtreeFlags;
491
+ subtreeChildListChanged =
492
+ subtreeChildListChanged ||
493
+ fiber.childListChanged ||
494
+ fiber.subtreeChildListChanged;
495
+ if (fiber.memoizedState === undefined) {
496
+ fiber.memoizedState = index;
497
+ }
498
+ previous = fiber;
499
+ }
500
+
501
+ if (currentKeyed !== undefined) {
502
+ listShapeChanged = true;
503
+ }
504
+
505
+ parent.hasRefSubtree = hasRefSubtree;
506
+ parent.subtreeFlags = subtreeFlags;
507
+ parent.subtreeChildListChanged = subtreeChildListChanged;
508
+ parent.childListChanged = listShapeChanged;
509
+ return { fiber: first, consumed: 0 };
510
+ }
511
+
512
+ function countFiberSiblings(first: Fiber): number {
513
+ let count = 0;
514
+ let cursor: Fiber | undefined = first;
515
+
516
+ while (cursor !== undefined) {
517
+ count += 1;
518
+ cursor = cursor.sibling;
519
+ }
520
+
521
+ return count;
522
+ }
523
+
524
+ function isKeyedAppendOnly(
525
+ currentFirstChild: Fiber,
526
+ children: readonly ReactCompatNode[],
527
+ currentSiblingCount: number,
528
+ ): boolean {
529
+ if (children.length <= currentSiblingCount) {
530
+ return false;
531
+ }
532
+
533
+ let current: Fiber | undefined = currentFirstChild;
534
+
535
+ for (let index = 0; index < currentSiblingCount; index += 1) {
536
+ if (current === undefined || current.key !== getNodeKey(children[index])) {
537
+ return false;
538
+ }
539
+ current = current.sibling;
540
+ }
541
+
542
+ return current === undefined;
543
+ }
544
+
545
+ function getReusableKeyedRowHostFiber(
546
+ current: Fiber,
547
+ row: KeyedRowHostElement,
548
+ ): Fiber | undefined {
549
+ if (
550
+ current.tag !== "host-component" ||
551
+ current.type !== row.type ||
552
+ current.hydrateExisting ||
553
+ current.child !== undefined ||
554
+ !isHostElement(current.stateNode)
555
+ ) {
556
+ return undefined;
557
+ }
558
+
559
+ const previousProps = current.memoizedProps ?? current.pendingProps;
560
+
561
+ if (typeof previousProps !== "object" || previousProps === null) {
562
+ return undefined;
563
+ }
564
+
565
+ const previousRecord = previousProps as Record<string, unknown>;
566
+
567
+ if (
568
+ getHostOwnPropsMeta(previousRecord) !== row.meta ||
569
+ getDirectHostTextChild(previousRecord.children) !== row.text
570
+ ) {
571
+ return undefined;
572
+ }
573
+
574
+ current.pendingProps = row.element.props;
575
+ current.flags = NoFlags;
576
+ current.subtreeFlags = NoFlags;
577
+ current.childListChanged = false;
578
+ current.subtreeChildListChanged = false;
579
+ current.hostChildListChanged = false;
580
+ current.hasRefSubtree = false;
581
+ return current;
582
+ }
583
+
584
+ interface KeyedRowHostElement {
585
+ element: ReactCompatElement;
586
+ key: string;
587
+ type: string;
588
+ meta: number;
589
+ text: string;
590
+ }
591
+
592
+ function createKeyedRowHostElementScratch(): KeyedRowHostElement {
593
+ return {
594
+ element: undefined as unknown as ReactCompatElement,
595
+ key: "",
596
+ type: "",
597
+ meta: 0,
598
+ text: "",
599
+ };
600
+ }
601
+
602
+ function readKeyedRowHostElement(
603
+ node: ReactCompatNode,
604
+ row: KeyedRowHostElement,
605
+ ): boolean {
606
+ if (
607
+ !isReactCompatElement(node) ||
608
+ typeof node.type !== "string" ||
609
+ node.key === null ||
610
+ node.ref !== null
611
+ ) {
612
+ return false;
613
+ }
614
+
615
+ const props = node.props as Record<string, unknown>;
616
+ const meta = getHostOwnPropsMeta(props);
617
+ const text = meta === undefined ? undefined : getDirectHostTextChild(props.children);
618
+
619
+ if (meta === undefined || text === undefined) {
620
+ return false;
621
+ }
622
+
623
+ row.element = node;
624
+ row.key = node.key;
625
+ row.type = node.type;
626
+ row.meta = meta;
627
+ row.text = text;
628
+ return true;
629
+ }
630
+
631
+ function createKeyedRowHostFiber(
632
+ parent: Fiber,
633
+ current: Fiber | undefined,
634
+ row: KeyedRowHostElement,
635
+ options: FiberHydrationOptions,
636
+ ): Fiber {
637
+ const node = row.element;
638
+ const elementNamespace = namespaceForHostElement(options.namespace ?? "html", row.type);
639
+ const fiber =
640
+ current?.tag === "host-component" && current.type === row.type
641
+ ? createWorkInProgress(current, node.props)
642
+ : createFiber("host-component", node.props, row.key);
643
+
644
+ fiber.type = row.type;
645
+ fiber.stateNode =
646
+ current?.tag === "host-component" &&
647
+ current.type === row.type &&
648
+ isHostElement(current.stateNode) &&
649
+ hostElementMatches(current.stateNode, row.type, elementNamespace)
650
+ ? current.stateNode
651
+ : createHostElement(getDocumentRef(options), row.type, options.namespace ?? "html");
652
+ fiber.child = undefined;
653
+ fiber.pendingProps = node.props;
654
+ fiber.hostChildListChanged = false;
655
+ fiber.hasRefSubtree = false;
656
+
657
+ if (current === undefined || fiber.alternate !== current) {
658
+ fiber.flags |= Placement;
659
+ fiber.hostChildListChanged = true;
660
+ return fiber;
661
+ }
662
+
663
+ const previousProps = current.memoizedProps ?? current.pendingProps;
664
+ const previousMeta =
665
+ typeof previousProps === "object" && previousProps !== null
666
+ ? getHostOwnPropsMeta(previousProps as Record<string, unknown>)
667
+ : undefined;
668
+ const previousText = getDirectHostTextChild(hostFiberChildrenProp(previousProps));
669
+
670
+ if (previousMeta !== row.meta || previousText !== row.text) {
671
+ fiber.flags |= Update;
672
+ }
673
+
674
+ return fiber;
675
+ }
676
+
677
+ function canReuseStaticHostSubtree(fiber: Fiber | undefined): boolean {
678
+ let cursor = fiber;
679
+
680
+ while (cursor !== undefined) {
681
+ if (
682
+ cursor.tag !== "host-component" &&
683
+ cursor.tag !== "host-text" &&
684
+ cursor.tag !== "fragment" &&
685
+ cursor.tag !== "strict-mode"
686
+ ) {
687
+ return false;
688
+ }
689
+
690
+ if (cursor.child !== undefined && !canReuseStaticHostSubtree(cursor.child)) {
691
+ return false;
692
+ }
693
+
694
+ cursor = cursor.sibling;
695
+ }
696
+
697
+ return true;
698
+ }
699
+
700
+ function canSkipSingleDeletedKeyedFiber(
701
+ children: readonly ReactCompatNode[],
702
+ index: number,
703
+ matched: Fiber,
704
+ ): boolean {
705
+ const nextKey = index + 1 < children.length ? getNodeKey(children[index + 1]) : undefined;
706
+ const afterMatched = matched.sibling;
707
+
708
+ return nextKey === undefined ? afterMatched === undefined : afterMatched?.key === nextKey;
709
+ }
710
+
711
+ function canSkipRemainingKeyedLookup(
712
+ current: Fiber | undefined,
713
+ children: readonly ReactCompatNode[],
714
+ startIndex: number,
715
+ ): boolean {
716
+ const currentRange = readContiguousNumericFiberKeyRange(current);
717
+
718
+ if (currentRange === undefined) {
719
+ return false;
720
+ }
721
+
722
+ const nextRange = readContiguousNumericNodeKeyRange(children, startIndex);
723
+
724
+ if (nextRange === undefined) {
725
+ return false;
726
+ }
727
+
728
+ return (
729
+ currentRange.end < nextRange.start ||
730
+ nextRange.end < currentRange.start
731
+ );
732
+ }
733
+
734
+ function readContiguousNumericFiberKeyRange(
735
+ fiber: Fiber | undefined,
736
+ ): { start: number; end: number } | undefined {
737
+ let cursor = fiber;
738
+ let start: number | undefined;
739
+ let previous: number | undefined;
740
+
741
+ while (cursor !== undefined) {
742
+ const value = parseNumericKey(cursor.key);
743
+
744
+ if (value === undefined || (previous !== undefined && value !== previous + 1)) {
745
+ return undefined;
746
+ }
747
+
748
+ start ??= value;
749
+ previous = value;
750
+ cursor = cursor.sibling;
751
+ }
752
+
753
+ return start === undefined || previous === undefined
754
+ ? undefined
755
+ : { start, end: previous };
756
+ }
757
+
758
+ function readContiguousNumericNodeKeyRange(
759
+ children: readonly ReactCompatNode[],
760
+ startIndex: number,
761
+ ): { start: number; end: number } | undefined {
762
+ let start: number | undefined;
763
+ let previous: number | undefined;
764
+
765
+ for (let index = startIndex; index < children.length; index += 1) {
766
+ const value = parseNumericKey(getNodeKey(children[index]));
767
+
768
+ if (value === undefined || (previous !== undefined && value !== previous + 1)) {
769
+ return undefined;
770
+ }
771
+
772
+ start ??= value;
773
+ previous = value;
774
+ }
775
+
776
+ return start === undefined || previous === undefined
777
+ ? undefined
778
+ : { start, end: previous };
779
+ }
780
+
781
+ function parseNumericKey(key: string | undefined): number | undefined {
782
+ if (key === undefined || key.length === 0) {
783
+ return undefined;
784
+ }
785
+
786
+ const value = Number(key);
787
+
788
+ return Number.isSafeInteger(value) && String(value) === key ? value : undefined;
789
+ }
790
+
791
+ function childFiberListShapeChanged(
792
+ current: Fiber | undefined,
793
+ next: Fiber | undefined,
794
+ ): boolean {
795
+ let currentCursor = current;
796
+ let nextCursor = next;
797
+
798
+ while (currentCursor !== undefined && nextCursor !== undefined) {
799
+ const isSameSlot =
800
+ nextCursor === currentCursor ||
801
+ nextCursor.alternate === currentCursor;
802
+
803
+ if (
804
+ !isSameSlot ||
805
+ currentCursor.tag !== nextCursor.tag ||
806
+ currentCursor.type !== nextCursor.type ||
807
+ currentCursor.key !== nextCursor.key
808
+ ) {
809
+ return true;
810
+ }
811
+
812
+ currentCursor = currentCursor.sibling;
813
+ nextCursor = nextCursor.sibling;
814
+ }
815
+
816
+ return currentCursor !== undefined || nextCursor !== undefined;
817
+ }
818
+
819
+ function bubbleHostChild(parent: Fiber, child: Fiber): void {
820
+ if (child.hasRefSubtree) {
821
+ parent.hasRefSubtree = true;
822
+ }
823
+ parent.subtreeFlags |= child.flags | child.subtreeFlags;
824
+ parent.subtreeChildListChanged =
825
+ parent.subtreeChildListChanged ||
826
+ child.childListChanged ||
827
+ child.subtreeChildListChanged;
828
+ }
829
+
830
+ function resetFiberRefSubtree(fiber: Fiber): void {
831
+ fiber.hasRefSubtree = false;
832
+ }
833
+
834
+ function includeNodeRef(fiber: Fiber, node: ReactCompatNode): void {
835
+ fiber.hasRefSubtree =
836
+ fiber.hasRefSubtree ||
837
+ (isReactCompatElement(node) && node.ref !== null);
838
+ }
839
+
840
+ function markHostFiberEffects(
841
+ fiber: Fiber,
842
+ current: Fiber | undefined,
843
+ node: ReactCompatNode,
844
+ ): void {
845
+ if (current === undefined || fiber.alternate !== current) {
846
+ fiber.flags |= Placement;
847
+ fiber.hostChildListChanged = true;
848
+ return;
849
+ }
850
+
851
+ if (fiber.tag === "host-text") {
852
+ if (!Object.is(current.memoizedProps ?? current.pendingProps, fiber.pendingProps)) {
853
+ fiber.flags |= Update;
854
+ }
855
+ return;
856
+ }
857
+
858
+ if (fiber.tag !== "host-component") {
859
+ return;
860
+ }
861
+
862
+ const previousProps = current.memoizedProps ?? current.pendingProps;
863
+ const nextProps = fiber.pendingProps as Record<string, unknown>;
864
+
865
+ fiber.hostChildListChanged = hostChildListChanged(previousProps, nextProps);
866
+
867
+ if (!hostOwnPropsEqual(previousProps, nextProps) || hostDirectTextChildChanged(previousProps, nextProps)) {
868
+ fiber.flags |= Update;
869
+ }
870
+
871
+ if (isReactCompatElement(node) && node.ref !== null) {
872
+ fiber.flags |= Update;
873
+ }
874
+ }
875
+
296
876
  function createHostFiber(
297
877
  parent: Fiber,
298
878
  current: Fiber | undefined,
@@ -301,6 +881,48 @@ function createHostFiber(
301
881
  runtime: RootRuntime | undefined,
302
882
  path: string,
303
883
  options: FiberHydrationOptions = {},
884
+ ): FiberReconcileResult {
885
+ const result = createHostFiberImpl(parent, current, node, key, runtime, path, options);
886
+
887
+ if (result.fiber !== undefined) {
888
+ if (canFinalizeNewHostFiber(result.fiber, current, node, options)) {
889
+ result.fiber.flags |= Placement;
890
+ result.fiber.hostChildListChanged = true;
891
+ return result;
892
+ }
893
+
894
+ result.fiber.pendingProps = getPendingProps(node);
895
+ includeNodeRef(result.fiber, node);
896
+ markHostFiberEffects(result.fiber, current, node);
897
+ }
898
+
899
+ return result;
900
+ }
901
+
902
+ function canFinalizeNewHostFiber(
903
+ fiber: Fiber,
904
+ current: Fiber | undefined,
905
+ node: ReactCompatNode,
906
+ options: FiberHydrationOptions,
907
+ ): boolean {
908
+ return (
909
+ current === undefined &&
910
+ options.previousNodes === undefined &&
911
+ fiber.tag === "host-component" &&
912
+ isReactCompatElement(node) &&
913
+ node.ref === null &&
914
+ typeof node.type === "string"
915
+ );
916
+ }
917
+
918
+ function createHostFiberImpl(
919
+ parent: Fiber,
920
+ current: Fiber | undefined,
921
+ node: ReactCompatNode,
922
+ key: string | undefined,
923
+ runtime: RootRuntime | undefined,
924
+ path: string,
925
+ options: FiberHydrationOptions = {},
304
926
  ): FiberReconcileResult {
305
927
  if (node === null || node === undefined || typeof node === "boolean") {
306
928
  return { fiber: undefined, consumed: 0 };
@@ -322,7 +944,7 @@ function createHostFiber(
322
944
  ? existing
323
945
  : current?.tag === "host-text" && current.stateNode instanceof Text
324
946
  ? current.stateNode
325
- : document.createTextNode("");
947
+ : getDocumentRef(options).createTextNode("");
326
948
 
327
949
  if (existing instanceof Text && existing.data !== String(node)) {
328
950
  reportRecoverable(
@@ -522,7 +1144,7 @@ function createHostFiber(
522
1144
  const childResult = reconcileHostChild(
523
1145
  fiber,
524
1146
  current?.tag === "context-consumer" ? current.child : undefined,
525
- render(useContext(node.type.context)),
1147
+ render(useContext(consumerContext(node.type))),
526
1148
  runtime,
527
1149
  `${path}.consumer`,
528
1150
  options,
@@ -544,6 +1166,7 @@ function createHostFiber(
544
1166
  fiber.type = forwardRefType;
545
1167
  const rendered = renderWithRootRuntime(runtime, path, () =>
546
1168
  forwardRefType.render(node.props, node.ref),
1169
+ forwardRefType,
547
1170
  );
548
1171
  fiber.memoizedState = getDevToolsHookState(runtime, path);
549
1172
  const childOptions = withHydrationComponentStack(
@@ -582,6 +1205,7 @@ function createHostFiber(
582
1205
  if (
583
1206
  previousMemoState !== undefined &&
584
1207
  !hasDirtyInstance(runtime, previousMemoState.instanceKeys) &&
1208
+ !hasUnflushedMountEffectInstance(runtime, previousMemoState.instanceKeys) &&
585
1209
  areMemoPropsEqual(memoType, previousMemoState.props, node.props)
586
1210
  ) {
587
1211
  markActiveInstanceKeys(runtime, previousMemoState.instanceKeys);
@@ -604,6 +1228,11 @@ function createHostFiber(
604
1228
  options,
605
1229
  );
606
1230
  fiber.child = childResult.fiber;
1231
+ if (fiber.child !== undefined) {
1232
+ fiber.child.return = fiber;
1233
+ fiber.child.sibling = undefined;
1234
+ bubbleHostChild(fiber, fiber.child);
1235
+ }
607
1236
  fiber.memoizedState = {
608
1237
  props: { ...node.props },
609
1238
  instanceKeys: collectInstanceKeys(runtime, memoPath),
@@ -638,6 +1267,11 @@ function createHostFiber(
638
1267
  options,
639
1268
  );
640
1269
  fiber.child = childResult.fiber;
1270
+ if (fiber.child !== undefined) {
1271
+ fiber.child.return = fiber;
1272
+ fiber.child.sibling = undefined;
1273
+ bubbleHostChild(fiber, fiber.child);
1274
+ }
641
1275
  return { fiber, consumed: childResult.consumed };
642
1276
  }
643
1277
 
@@ -680,11 +1314,13 @@ function createHostFiber(
680
1314
  ? createWorkInProgress(current, node.props)
681
1315
  : createFiber("class-component", node.props, key);
682
1316
  fiber.type = classType;
1317
+ const previousClassChildKeys = collectInstanceKeys(runtime, `${path}.class`);
683
1318
  const rendered = renderClassComponentWithRuntime(
684
1319
  classType,
685
1320
  node.props,
686
1321
  runtime,
687
1322
  path,
1323
+ { hasDirtyDescendant: hasDirtyInstance(runtime, previousClassChildKeys) },
688
1324
  );
689
1325
  applyRef(node.ref, rendered.kind === "skip" ? current?.stateNode : rendered.instance);
690
1326
 
@@ -738,13 +1374,44 @@ function createHostFiber(
738
1374
  return { fiber: undefined, consumed: 0 };
739
1375
  }
740
1376
 
1377
+ const previousFunctionState =
1378
+ current?.tag === "function-component"
1379
+ ? (current.stateNode as FunctionFiberState | undefined)
1380
+ : undefined;
741
1381
  const fiber =
742
1382
  current?.tag === "function-component" && current.type === node.type
743
1383
  ? createWorkInProgress(current, node.props)
744
1384
  : createFiber("function-component", node.props, key);
745
1385
  fiber.type = node.type;
1386
+
1387
+ const canReuseSameElement =
1388
+ previousFunctionState !== undefined &&
1389
+ previousFunctionState.hasContextDependencies !== true &&
1390
+ previousFunctionState.element === node;
1391
+ const canReuseExternalStoreSnapshot =
1392
+ previousFunctionState !== undefined &&
1393
+ runtime.externalStoreUpdate &&
1394
+ shallowEqual(previousFunctionState.props, node.props) &&
1395
+ !hasChangedContextDependency(runtime, previousFunctionState.instanceKeys);
1396
+
1397
+ if (
1398
+ runtime.strictReplayDepth === 0 &&
1399
+ previousFunctionState !== undefined &&
1400
+ (canReuseSameElement || canReuseExternalStoreSnapshot) &&
1401
+ !hasDirtyInstance(runtime, previousFunctionState.instanceKeys) &&
1402
+ !hasUnflushedMountEffectInstance(runtime, previousFunctionState.instanceKeys) &&
1403
+ !hasPendingAsyncChild(current?.child)
1404
+ ) {
1405
+ markActiveInstanceKeys(runtime, previousFunctionState.instanceKeys);
1406
+ fiber.child = current?.child;
1407
+ fiber.memoizedState = current?.memoizedState;
1408
+ fiber.stateNode = previousFunctionState;
1409
+ return { fiber, consumed: options.previousNodes?.length ?? 0 };
1410
+ }
1411
+
746
1412
  const rendered = renderWithRootRuntime(runtime, path, () =>
747
1413
  (node.type as (props: Record<string, unknown>) => ReactCompatNode)(node.props),
1414
+ node.type,
748
1415
  );
749
1416
  fiber.memoizedState = getDevToolsHookState(runtime, path);
750
1417
  const childOptions = withHydrationComponentStack(
@@ -760,6 +1427,13 @@ function createHostFiber(
760
1427
  childOptions,
761
1428
  );
762
1429
  fiber.child = childResult.fiber;
1430
+ const instanceKeys = collectInstanceKeys(runtime, path);
1431
+ fiber.stateNode = {
1432
+ element: node,
1433
+ props: { ...node.props },
1434
+ instanceKeys,
1435
+ hasContextDependencies: hasContextDependency(runtime, instanceKeys),
1436
+ } satisfies FunctionFiberState;
763
1437
  return { fiber, consumed: childResult.consumed };
764
1438
  }
765
1439
 
@@ -767,19 +1441,21 @@ function createHostFiber(
767
1441
  return { fiber: undefined, consumed: 0 };
768
1442
  }
769
1443
 
1444
+ const elementNamespace = namespaceForHostElement(options.namespace ?? "html", node.type);
1445
+ const childNamespace = namespaceForHostChildren(elementNamespace, node.type);
770
1446
  const fiber =
771
1447
  current?.tag === "host-component" && current.type === node.type
772
1448
  ? createWorkInProgress(current, node.props)
773
1449
  : createFiber("host-component", node.props, key);
774
1450
  const existing = options.previousNodes?.[0];
775
- const existingElement = existing instanceof HTMLElement ? existing : undefined;
1451
+ const existingElement = isHostElement(existing) ? existing : undefined;
776
1452
  const tagMatches =
777
1453
  existingElement !== undefined &&
778
- existingElement.tagName.toLowerCase() === node.type;
1454
+ hostElementMatches(existingElement, node.type, elementNamespace);
779
1455
 
780
1456
  if (existing === undefined && options.previousNodes !== undefined) {
781
1457
  reportMissingHydrationNode(options, path);
782
- } else if (existing !== undefined && !(existing instanceof HTMLElement)) {
1458
+ } else if (existing !== undefined && !isHostElement(existing)) {
783
1459
  reportHydrationNodeTypeMismatch(options, path, `<${node.type}>`, existing);
784
1460
  }
785
1461
 
@@ -801,62 +1477,474 @@ function createHostFiber(
801
1477
  ? existingElement
802
1478
  : current?.tag === "host-component" &&
803
1479
  current.type === node.type &&
804
- current.stateNode instanceof HTMLElement
1480
+ isHostElement(current.stateNode) &&
1481
+ hostElementMatches(current.stateNode, node.type, elementNamespace)
805
1482
  ? current.stateNode
806
- : document.createElement(node.type);
1483
+ : createHostElement(getDocumentRef(options), node.type, options.namespace ?? "html");
807
1484
  fiber.hydrateExisting = tagMatches && options.previousNodes !== undefined;
808
1485
  const previousChildNodes =
809
1486
  tagMatches && existingElement !== undefined
810
1487
  ? Array.from(existingElement.childNodes)
811
1488
  : undefined;
1489
+ const directTextChild =
1490
+ shouldUseDirectHostTextChild() && previousChildNodes === undefined
1491
+ ? getDirectHostTextChild(node.props.children)
1492
+ : undefined;
1493
+ if (
1494
+ previousChildNodes === undefined &&
1495
+ current?.tag === "host-component" &&
1496
+ current.type === node.type &&
1497
+ Object.is(hostFiberChildrenProp(current.memoizedProps), node.props.children) &&
1498
+ canReuseStaticHostSubtree(current.child)
1499
+ ) {
1500
+ fiber.child = current.child;
1501
+ if (fiber.child !== undefined) {
1502
+ fiber.child.return = fiber;
1503
+ }
1504
+ parent.child ??= fiber;
1505
+ return { fiber, consumed: existing === undefined ? 0 : 1 };
1506
+ }
1507
+
1508
+ if (directTextChild !== undefined) {
1509
+ fiber.child = undefined;
1510
+ parent.child ??= fiber;
1511
+ return { fiber, consumed: existing === undefined ? 0 : 1 };
1512
+ }
1513
+
812
1514
  const childResult = reconcileHostChild(
813
1515
  fiber,
814
1516
  current?.tag === "host-component" ? current.child : undefined,
815
1517
  node.props.children as ReactCompatNode,
816
1518
  runtime,
817
1519
  `${path}.c`,
818
- previousChildNodes === undefined
819
- ? options
820
- : { ...options, previousNodes: previousChildNodes },
1520
+ {
1521
+ ...options,
1522
+ namespace: childNamespace,
1523
+ ...(previousChildNodes === undefined ? {} : { previousNodes: previousChildNodes }),
1524
+ },
821
1525
  );
822
1526
  fiber.child = childResult.fiber;
823
1527
  if (previousChildNodes !== undefined) {
824
1528
  reportExtraHydrationNodes(options, `${path}.c`, previousChildNodes, childResult.consumed);
825
1529
  }
826
- parent.child ??= fiber;
827
- return { fiber, consumed: existing === undefined ? 0 : 1 };
1530
+ parent.child ??= fiber;
1531
+ return { fiber, consumed: existing === undefined ? 0 : 1 };
1532
+ }
1533
+
1534
+ function isFunctionComponentType(value: unknown): value is (
1535
+ props: Record<string, unknown>,
1536
+ ) => ReactCompatNode {
1537
+ return (
1538
+ typeof value === "function" &&
1539
+ typeof (value as { prototype?: { render?: unknown } }).prototype?.render !==
1540
+ "function"
1541
+ );
1542
+ }
1543
+
1544
+ function commitHostChildren(
1545
+ fiber: Fiber | undefined,
1546
+ parent: ParentNode,
1547
+ eventRoot: Element,
1548
+ path: string,
1549
+ options: RenderOptions = {},
1550
+ ): Node[] {
1551
+ const nodes: Node[] = [];
1552
+ let cursor = fiber;
1553
+ let index = 0;
1554
+
1555
+ while (cursor !== undefined) {
1556
+ for (const node of commitHostFiber(cursor, parent, eventRoot, joinCommitPath(path, String(index)), options)) {
1557
+ nodes.push(node);
1558
+ }
1559
+ cursor = cursor.sibling;
1560
+ index += 1;
1561
+ }
1562
+
1563
+ return nodes;
1564
+ }
1565
+
1566
+ function commitHostDirtyChildren(
1567
+ fiber: Fiber | undefined,
1568
+ parent: ParentNode,
1569
+ eventRoot: Element,
1570
+ path: string,
1571
+ options: RenderOptions = {},
1572
+ ): void {
1573
+ let cursor = fiber;
1574
+ let index = 0;
1575
+
1576
+ while (cursor !== undefined) {
1577
+ if (hasHostCommitWork(cursor)) {
1578
+ commitHostDirtyFiber(cursor, parent, eventRoot, joinCommitPath(path, String(index)), options);
1579
+ }
1580
+ cursor = cursor.sibling;
1581
+ index += 1;
1582
+ }
1583
+ }
1584
+
1585
+ function commitHostDirtyFiber(
1586
+ fiber: Fiber,
1587
+ parent: ParentNode,
1588
+ eventRoot: Element,
1589
+ path: string,
1590
+ options: RenderOptions = {},
1591
+ ): void {
1592
+ if (fiber.tag === "host-text") {
1593
+ commitHostFiber(fiber, parent, eventRoot, path, options);
1594
+ return;
1595
+ }
1596
+
1597
+ if (fiber.tag === "host-component") {
1598
+ const element = fiber.stateNode;
1599
+
1600
+ if (!isHostElement(element)) {
1601
+ finishCommittedFiber(fiber);
1602
+ return;
1603
+ }
1604
+
1605
+ const props = fiber.pendingProps as Record<string, unknown>;
1606
+ const propsAreUnchanged =
1607
+ fiber.hydrateExisting !== true &&
1608
+ hostPropsEqual(fiber.memoizedProps, props);
1609
+ const propsAreChildrenOnly =
1610
+ fiber.hydrateExisting !== true &&
1611
+ hostPropsAreChildrenOnly(fiber.memoizedProps) &&
1612
+ hostPropsAreChildrenOnly(props);
1613
+ const textOnlyRowUpdate =
1614
+ fiber.hydrateExisting !== true &&
1615
+ isRowTextOnlyUpdate(fiber.memoizedProps, props);
1616
+
1617
+ if (!propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
1618
+ applyProps(element, props, path, {
1619
+ ...options,
1620
+ eventRoot,
1621
+ preserveHydrationAttributes: fiber.hydrateExisting,
1622
+ });
1623
+ applyRef(props.ref, element);
1624
+ }
1625
+
1626
+ const directTextChild =
1627
+ fiber.child === undefined && fiber.hydrateExisting !== true
1628
+ ? getDirectHostTextChild(props.children)
1629
+ : undefined;
1630
+
1631
+ if (directTextChild !== undefined) {
1632
+ syncDirectHostTextChild(element, directTextChild);
1633
+ } else if (fiber.subtreeFlags !== NoFlags) {
1634
+ commitHostDirtyChildren(fiber.child, element, eventRoot, `${path}.c`, options);
1635
+ }
1636
+
1637
+ if (!propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
1638
+ applyPostChildFormProps(element, props);
1639
+ }
1640
+ fiber.memoizedProps = props;
1641
+ finishCommittedFiber(fiber);
1642
+ return;
1643
+ }
1644
+
1645
+ if (fiber.tag === "portal") {
1646
+ const container = fiber.stateNode;
1647
+
1648
+ if (container instanceof Element) {
1649
+ setLogicalEventParent(container, parent);
1650
+ commitHostDirtyChildren(fiber.child, container, container, `${path}.portal`, options);
1651
+ }
1652
+ fiber.memoizedProps = fiber.pendingProps;
1653
+ finishCommittedFiber(fiber);
1654
+ return;
1655
+ }
1656
+
1657
+ if (fiber.subtreeFlags !== NoFlags) {
1658
+ commitHostDirtyChildren(fiber.child, parent, eventRoot, path, options);
1659
+ }
1660
+ fiber.memoizedProps = fiber.pendingProps;
1661
+ finishCommittedFiber(fiber);
1662
+ }
1663
+
1664
+ function hasHostCommitWork(fiber: Fiber): boolean {
1665
+ return (
1666
+ fiber.flags !== NoFlags ||
1667
+ fiber.subtreeFlags !== NoFlags ||
1668
+ fiber.hostChildListChanged ||
1669
+ fiber.childListChanged ||
1670
+ fiber.subtreeChildListChanged ||
1671
+ fiber.hydrateExisting
1672
+ );
1673
+ }
1674
+
1675
+ function commitHostKeyedChildListMutation(
1676
+ fiber: Fiber | undefined,
1677
+ parent: ParentNode,
1678
+ eventRoot: Element,
1679
+ path: string,
1680
+ options: RenderOptions = {},
1681
+ ): boolean {
1682
+ let cursor = fiber;
1683
+ let index = 0;
1684
+ let committed = false;
1685
+
1686
+ while (cursor !== undefined) {
1687
+ if (!hasHostCommitWork(cursor)) {
1688
+ cursor = cursor.sibling;
1689
+ index += 1;
1690
+ continue;
1691
+ }
1692
+
1693
+ const childPath = joinCommitPath(path, String(index));
1694
+ const didCommit = commitHostKeyedChildListMutationFiber(
1695
+ cursor,
1696
+ parent,
1697
+ eventRoot,
1698
+ childPath,
1699
+ options,
1700
+ );
1701
+
1702
+ if (!didCommit) {
1703
+ return false;
1704
+ }
1705
+
1706
+ committed = true;
1707
+ cursor = cursor.sibling;
1708
+ index += 1;
1709
+ }
1710
+
1711
+ return committed;
1712
+ }
1713
+
1714
+ function commitHostKeyedChildListMutationFiber(
1715
+ fiber: Fiber,
1716
+ parent: ParentNode,
1717
+ eventRoot: Element,
1718
+ path: string,
1719
+ options: RenderOptions = {},
1720
+ ): boolean {
1721
+ if (fiber.childListChanged) {
1722
+ const mutationParent =
1723
+ fiber.tag === "host-component" && isHostElement(fiber.stateNode)
1724
+ ? fiber.stateNode
1725
+ : parent;
1726
+
1727
+ if (fiber.tag === "host-component" && !isHostElement(fiber.stateNode)) {
1728
+ return false;
1729
+ }
1730
+
1731
+ if (commitHostAppendSuffix(fiber, mutationParent, eventRoot, path, options)) {
1732
+ finishHostPassthroughFiber(fiber);
1733
+ return true;
1734
+ }
1735
+
1736
+ if (commitHostSingleRemoval(fiber, mutationParent)) {
1737
+ finishHostPassthroughFiber(fiber);
1738
+ return true;
1739
+ }
1740
+
1741
+ return false;
1742
+ }
1743
+
1744
+ if (fiber.subtreeChildListChanged) {
1745
+ if (fiber.tag === "host-component") {
1746
+ const element = fiber.stateNode;
1747
+
1748
+ if (!isHostElement(element)) {
1749
+ return false;
1750
+ }
1751
+
1752
+ if (!commitHostKeyedChildListMutation(fiber.child, element, eventRoot, `${path}.c`, options)) {
1753
+ return false;
1754
+ }
1755
+ finishHostPassthroughFiber(fiber);
1756
+ return true;
1757
+ }
1758
+
1759
+ if (!commitHostKeyedChildListMutation(fiber.child, parent, eventRoot, path, options)) {
1760
+ return false;
1761
+ }
1762
+ finishHostPassthroughFiber(fiber);
1763
+ return true;
1764
+ }
1765
+
1766
+ commitHostDirtyFiber(fiber, parent, eventRoot, path, options);
1767
+ return true;
1768
+ }
1769
+
1770
+ function commitHostAppendSuffix(
1771
+ fiber: Fiber,
1772
+ parent: ParentNode,
1773
+ eventRoot: Element,
1774
+ path: string,
1775
+ options: RenderOptions,
1776
+ ): boolean {
1777
+ const append = getPlacementAppendSuffix(fiber.child) ?? getAppendSuffix(fiber.alternate?.child, fiber.child);
1778
+
1779
+ if (append === undefined) {
1780
+ return false;
1781
+ }
1782
+
1783
+ let cursor: Fiber | undefined = append.fiber;
1784
+ let index = append.index;
1785
+
1786
+ while (cursor !== undefined) {
1787
+ for (const node of commitHostFiber(cursor, parent, eventRoot, joinCommitPath(path, String(index)), options)) {
1788
+ parent.appendChild(node);
1789
+ }
1790
+ cursor = cursor.sibling;
1791
+ index += 1;
1792
+ }
1793
+
1794
+ return true;
1795
+ }
1796
+
1797
+ function getPlacementAppendSuffix(next: Fiber | undefined): { fiber: Fiber; index: number } | undefined {
1798
+ let nextCursor = next;
1799
+ let index = 0;
1800
+
1801
+ while (nextCursor !== undefined) {
1802
+ if ((nextCursor.flags & Placement) !== NoFlags) {
1803
+ if (index === 0) {
1804
+ return undefined;
1805
+ }
1806
+
1807
+ let appendCursor: Fiber | undefined = nextCursor;
1808
+
1809
+ while (appendCursor !== undefined) {
1810
+ if ((appendCursor.flags & Placement) === NoFlags) {
1811
+ return undefined;
1812
+ }
1813
+ appendCursor = appendCursor.sibling;
1814
+ }
1815
+
1816
+ return { fiber: nextCursor, index };
1817
+ }
1818
+
1819
+ if (hasHostCommitWork(nextCursor)) {
1820
+ return undefined;
1821
+ }
1822
+
1823
+ nextCursor = nextCursor.sibling;
1824
+ index += 1;
1825
+ }
1826
+
1827
+ return undefined;
1828
+ }
1829
+
1830
+ function commitHostSingleRemoval(fiber: Fiber, parent: ParentNode): boolean {
1831
+ const removed = getSingleRemovedFiber(fiber.alternate?.child, fiber.child);
1832
+
1833
+ if (removed === undefined) {
1834
+ return false;
1835
+ }
1836
+
1837
+ let removedAny = false;
1838
+
1839
+ for (const node of collectCommittedHostNodes(removed)) {
1840
+ if (node.parentNode !== parent) {
1841
+ return false;
1842
+ }
1843
+
1844
+ parent.removeChild(node);
1845
+ removedAny = true;
1846
+ }
1847
+
1848
+ return removedAny;
1849
+ }
1850
+
1851
+ function getAppendSuffix(
1852
+ current: Fiber | undefined,
1853
+ next: Fiber | undefined,
1854
+ ): { fiber: Fiber; index: number } | undefined {
1855
+ let currentCursor = current;
1856
+ let nextCursor = next;
1857
+ let index = 0;
1858
+
1859
+ while (currentCursor !== undefined && nextCursor !== undefined) {
1860
+ if (!isSameFiberSlot(currentCursor, nextCursor) || hasHostCommitWork(nextCursor)) {
1861
+ return undefined;
1862
+ }
1863
+
1864
+ currentCursor = currentCursor.sibling;
1865
+ nextCursor = nextCursor.sibling;
1866
+ index += 1;
1867
+ }
1868
+
1869
+ if (currentCursor !== undefined || nextCursor === undefined) {
1870
+ return undefined;
1871
+ }
1872
+
1873
+ return { fiber: nextCursor, index };
1874
+ }
1875
+
1876
+ function getSingleRemovedFiber(
1877
+ current: Fiber | undefined,
1878
+ next: Fiber | undefined,
1879
+ ): Fiber | undefined {
1880
+ let currentCursor = current;
1881
+ let nextCursor = next;
1882
+
1883
+ while (currentCursor !== undefined && nextCursor !== undefined) {
1884
+ if (!isSameFiberSlot(currentCursor, nextCursor)) {
1885
+ break;
1886
+ }
1887
+
1888
+ if (hasHostCommitWork(nextCursor)) {
1889
+ return undefined;
1890
+ }
1891
+
1892
+ currentCursor = currentCursor.sibling;
1893
+ nextCursor = nextCursor.sibling;
1894
+ }
1895
+
1896
+ if (currentCursor === undefined) {
1897
+ return undefined;
1898
+ }
1899
+
1900
+ const removed = currentCursor;
1901
+ currentCursor = currentCursor.sibling;
1902
+
1903
+ while (currentCursor !== undefined && nextCursor !== undefined) {
1904
+ if (!isSameFiberSlot(currentCursor, nextCursor) || hasHostCommitWork(nextCursor)) {
1905
+ return undefined;
1906
+ }
1907
+
1908
+ currentCursor = currentCursor.sibling;
1909
+ nextCursor = nextCursor.sibling;
1910
+ }
1911
+
1912
+ return currentCursor === undefined && nextCursor === undefined ? removed : undefined;
828
1913
  }
829
1914
 
830
- function isFunctionComponentType(value: unknown): value is (
831
- props: Record<string, unknown>,
832
- ) => ReactCompatNode {
1915
+ function isSameFiberSlot(current: Fiber, next: Fiber): boolean {
833
1916
  return (
834
- typeof value === "function" &&
835
- typeof (value as { prototype?: { render?: unknown } }).prototype?.render !==
836
- "function"
1917
+ (next === current || next.alternate === current) &&
1918
+ current.tag === next.tag &&
1919
+ current.type === next.type &&
1920
+ current.key === next.key
837
1921
  );
838
1922
  }
839
1923
 
840
- function commitHostChildren(
841
- fiber: Fiber | undefined,
842
- parent: ParentNode,
843
- eventRoot: Element,
844
- path: string,
845
- options: RenderOptions = {},
846
- ): Node[] {
1924
+ function collectCommittedHostNodes(fiber: Fiber): Node[] {
1925
+ if (
1926
+ (fiber.tag === "host-component" || fiber.tag === "host-text") &&
1927
+ fiber.stateNode instanceof Node
1928
+ ) {
1929
+ return [fiber.stateNode];
1930
+ }
1931
+
847
1932
  const nodes: Node[] = [];
848
- let cursor = fiber;
849
- let index = 0;
1933
+ let child = fiber.child;
850
1934
 
851
- while (cursor !== undefined) {
852
- nodes.push(...commitHostFiber(cursor, parent, eventRoot, joinPath(path, String(index)), options));
853
- cursor = cursor.sibling;
854
- index += 1;
1935
+ while (child !== undefined) {
1936
+ nodes.push(...collectCommittedHostNodes(child));
1937
+ child = child.sibling;
855
1938
  }
856
1939
 
857
1940
  return nodes;
858
1941
  }
859
1942
 
1943
+ function finishHostPassthroughFiber(fiber: Fiber): void {
1944
+ fiber.memoizedProps = fiber.pendingProps;
1945
+ finishCommittedFiber(fiber);
1946
+ }
1947
+
860
1948
  function commitHostFiber(
861
1949
  fiber: Fiber,
862
1950
  parent: ParentNode,
@@ -868,92 +1956,171 @@ function commitHostFiber(
868
1956
  const text = fiber.stateNode;
869
1957
 
870
1958
  if (!(text instanceof Text)) {
1959
+ finishCommittedFiber(fiber);
871
1960
  return [];
872
1961
  }
873
1962
 
874
- text.data = String(fiber.pendingProps);
1963
+ const nextText = String(fiber.pendingProps);
1964
+ if (text.data !== nextText) {
1965
+ text.data = nextText;
1966
+ }
875
1967
  fiber.memoizedProps = fiber.pendingProps;
1968
+ finishCommittedFiber(fiber);
876
1969
  return [text];
877
1970
  }
878
1971
 
879
1972
  if (fiber.tag === "host-component") {
880
1973
  const element = fiber.stateNode;
881
1974
 
882
- if (!(element instanceof HTMLElement)) {
1975
+ if (!isHostElement(element)) {
1976
+ finishCommittedFiber(fiber);
883
1977
  return [];
884
1978
  }
885
1979
 
886
- applyProps(element, fiber.pendingProps as Record<string, unknown>, path, {
887
- ...options,
888
- eventRoot,
889
- preserveHydrationAttributes: fiber.hydrateExisting,
890
- });
891
- applyRef((fiber.pendingProps as { ref?: unknown }).ref, element);
892
- const childNodes = commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
893
- syncChildNodes(element, childNodes);
894
- applyPostChildFormProps(element, fiber.pendingProps as Record<string, unknown>);
895
- fiber.memoizedProps = fiber.pendingProps;
1980
+ if (
1981
+ fiber.hydrateExisting !== true &&
1982
+ fiber.flags === NoFlags &&
1983
+ fiber.subtreeFlags === NoFlags &&
1984
+ fiber.hostChildListChanged !== true
1985
+ ) {
1986
+ fiber.memoizedProps = fiber.pendingProps;
1987
+ return [element];
1988
+ }
1989
+
1990
+ const props = fiber.pendingProps as Record<string, unknown>;
1991
+ const propsAreUnchanged =
1992
+ fiber.hydrateExisting !== true &&
1993
+ hostPropsEqual(fiber.memoizedProps, props);
1994
+ const propsAreChildrenOnly =
1995
+ fiber.hydrateExisting !== true &&
1996
+ hostPropsAreChildrenOnly(fiber.memoizedProps) &&
1997
+ hostPropsAreChildrenOnly(props);
1998
+ const textOnlyRowUpdate =
1999
+ fiber.hydrateExisting !== true &&
2000
+ isRowTextOnlyUpdate(fiber.memoizedProps, props);
2001
+
2002
+ if (!propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
2003
+ applyProps(element, props, path, {
2004
+ ...options,
2005
+ eventRoot,
2006
+ preserveHydrationAttributes: fiber.hydrateExisting,
2007
+ });
2008
+ applyRef(props.ref, element);
2009
+ }
2010
+ const directTextChild =
2011
+ fiber.child === undefined && fiber.hydrateExisting !== true
2012
+ ? getDirectHostTextChild(props.children)
2013
+ : undefined;
2014
+
2015
+ if (directTextChild !== undefined) {
2016
+ syncDirectHostTextChild(element, directTextChild);
2017
+ } else if (
2018
+ fiber.hostChildListChanged ||
2019
+ fiber.childListChanged ||
2020
+ fiber.hydrateExisting === true ||
2021
+ (fiber.subtreeFlags & Placement) !== NoFlags
2022
+ ) {
2023
+ const childNodes = commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
2024
+ if (
2025
+ !(childNodes.length === 0 && committedPortalContainers.has(element)) &&
2026
+ !shouldPreserveContentEditableChildren(element, props, childNodes)
2027
+ ) {
2028
+ syncChildNodes(element, childNodes);
2029
+ }
2030
+ } else if (fiber.subtreeFlags !== NoFlags) {
2031
+ commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
2032
+ }
2033
+
2034
+ if (!propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
2035
+ applyPostChildFormProps(element, props);
2036
+ }
2037
+ fiber.memoizedProps = props;
2038
+ finishCommittedFiber(fiber);
896
2039
  return [element];
897
2040
  }
898
2041
 
899
2042
  if (fiber.tag === "fragment") {
900
2043
  fiber.memoizedProps = fiber.pendingProps;
901
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.f`, options);
2044
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.f`, options);
2045
+ finishCommittedFiber(fiber);
2046
+ return nodes;
902
2047
  }
903
2048
 
904
2049
  if (fiber.tag === "profiler") {
905
2050
  fiber.memoizedProps = fiber.pendingProps;
906
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.profiler`, options);
2051
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.profiler`, options);
2052
+ finishCommittedFiber(fiber);
2053
+ return nodes;
907
2054
  }
908
2055
 
909
2056
  if (fiber.tag === "strict-mode") {
910
2057
  fiber.memoizedProps = fiber.pendingProps;
911
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.strict`, options);
2058
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.strict`, options);
2059
+ finishCommittedFiber(fiber);
2060
+ return nodes;
912
2061
  }
913
2062
 
914
2063
  if (fiber.tag === "suspense") {
915
2064
  fiber.memoizedProps = fiber.pendingProps;
916
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.s`, options);
2065
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.s`, options);
2066
+ finishCommittedFiber(fiber);
2067
+ return nodes;
917
2068
  }
918
2069
 
919
2070
  if (fiber.tag === "suspense-list") {
920
2071
  fiber.memoizedProps = fiber.pendingProps;
921
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.sl`, options);
2072
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.sl`, options);
2073
+ finishCommittedFiber(fiber);
2074
+ return nodes;
922
2075
  }
923
2076
 
924
2077
  if (fiber.tag === "context-provider" || fiber.tag === "context-consumer") {
925
2078
  fiber.memoizedProps = fiber.pendingProps;
926
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.ctx`, options);
2079
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.ctx`, options);
2080
+ finishCommittedFiber(fiber);
2081
+ return nodes;
927
2082
  }
928
2083
 
929
2084
  if (fiber.tag === "function-component") {
930
2085
  fiber.memoizedProps = fiber.pendingProps;
931
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.fc`, options);
2086
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.fc`, options);
2087
+ finishCommittedFiber(fiber);
2088
+ return nodes;
932
2089
  }
933
2090
 
934
2091
  if (fiber.tag === "forward-ref") {
935
2092
  fiber.memoizedProps = fiber.pendingProps;
936
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.fr`, options);
2093
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.fr`, options);
2094
+ finishCommittedFiber(fiber);
2095
+ return nodes;
937
2096
  }
938
2097
 
939
2098
  if (fiber.tag === "memo") {
940
2099
  fiber.memoizedProps = fiber.pendingProps;
941
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.memo`, options);
2100
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.memo`, options);
2101
+ finishCommittedFiber(fiber);
2102
+ return nodes;
942
2103
  }
943
2104
 
944
2105
  if (fiber.tag === "lazy") {
945
2106
  fiber.memoizedProps = fiber.pendingProps;
946
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.lazy`, options);
2107
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.lazy`, options);
2108
+ finishCommittedFiber(fiber);
2109
+ return nodes;
947
2110
  }
948
2111
 
949
2112
  if (fiber.tag === "error-boundary") {
950
2113
  fiber.memoizedProps = fiber.pendingProps;
951
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.eb`, options);
2114
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.eb`, options);
2115
+ finishCommittedFiber(fiber);
2116
+ return nodes;
952
2117
  }
953
2118
 
954
2119
  if (fiber.tag === "class-component") {
955
2120
  fiber.memoizedProps = fiber.pendingProps;
956
- return commitHostChildren(fiber.child, parent, eventRoot, `${path}.class`, options);
2121
+ const nodes = commitHostChildren(fiber.child, parent, eventRoot, `${path}.class`, options);
2122
+ finishCommittedFiber(fiber);
2123
+ return nodes;
957
2124
  }
958
2125
 
959
2126
  if (fiber.tag === "portal") {
@@ -964,21 +2131,281 @@ function commitHostFiber(
964
2131
  }
965
2132
 
966
2133
  setLogicalEventParent(container, parent);
2134
+ committedPortalContainers.add(container);
2135
+ const portalEventRoot =
2136
+ eventRoot !== container && eventRoot.contains(container) ? eventRoot : container;
967
2137
  const childNodes = commitHostChildren(
968
2138
  fiber.child,
969
2139
  container,
970
- container,
2140
+ portalEventRoot,
971
2141
  `${path}.portal`,
972
2142
  options,
973
2143
  );
974
- syncChildNodes(container, childNodes);
2144
+ const previousNodes = Array.isArray(fiber.alternate?.memoizedState)
2145
+ ? fiber.alternate.memoizedState.filter((node): node is Node => node instanceof Node)
2146
+ : [];
2147
+ syncOwnedChildNodes(container, previousNodes, childNodes);
2148
+ fiber.memoizedState = childNodes;
975
2149
  fiber.memoizedProps = fiber.pendingProps;
2150
+ finishCommittedFiber(fiber);
976
2151
  return [];
977
2152
  }
978
2153
 
2154
+ finishCommittedFiber(fiber);
979
2155
  return [];
980
2156
  }
981
2157
 
2158
+ function finishCommittedFiber(fiber: Fiber): void {
2159
+ fiber.flags = NoFlags;
2160
+ fiber.subtreeFlags = NoFlags;
2161
+ fiber.childListChanged = false;
2162
+ fiber.subtreeChildListChanged = false;
2163
+ fiber.hostChildListChanged = false;
2164
+ }
2165
+
2166
+ function hasChildListMutation(fiber: Fiber): boolean {
2167
+ return fiber.childListChanged || fiber.subtreeChildListChanged;
2168
+ }
2169
+
2170
+ function hostPropsEqual(previous: unknown, next: Record<string, unknown>): boolean {
2171
+ if (previous === next) {
2172
+ return true;
2173
+ }
2174
+
2175
+ if (typeof previous !== "object" || previous === null) {
2176
+ return false;
2177
+ }
2178
+
2179
+ const previousProps = previous as Record<string, unknown>;
2180
+ let previousCount = 0;
2181
+ let nextCount = 0;
2182
+
2183
+ for (const key in previousProps) {
2184
+ if (!hasOwnProperty.call(previousProps, key)) {
2185
+ continue;
2186
+ }
2187
+ previousCount += 1;
2188
+ if (!hasOwnProperty.call(next, key)) {
2189
+ return false;
2190
+ }
2191
+
2192
+ if (!Object.is(previousProps[key], next[key])) {
2193
+ return false;
2194
+ }
2195
+ }
2196
+
2197
+ for (const key in next) {
2198
+ if (hasOwnProperty.call(next, key)) {
2199
+ nextCount += 1;
2200
+ }
2201
+ }
2202
+
2203
+ return previousCount === nextCount;
2204
+ }
2205
+
2206
+ function hostOwnPropsEqual(previous: unknown, next: Record<string, unknown>): boolean {
2207
+ if (previous === next) {
2208
+ return true;
2209
+ }
2210
+
2211
+ if (typeof previous !== "object" || previous === null) {
2212
+ return false;
2213
+ }
2214
+
2215
+ const previousProps = previous as Record<string, unknown>;
2216
+ const previousMeta = getHostOwnPropsMeta(previousProps);
2217
+ const nextMeta = getHostOwnPropsMeta(next);
2218
+
2219
+ if (previousMeta !== undefined && nextMeta !== undefined) {
2220
+ return previousMeta === nextMeta;
2221
+ }
2222
+
2223
+ let previousCount = 0;
2224
+ let nextCount = 0;
2225
+
2226
+ for (const key in previousProps) {
2227
+ if (!hasOwnProperty.call(previousProps, key) || key === "children") {
2228
+ continue;
2229
+ }
2230
+ previousCount += 1;
2231
+ if (!hasOwnProperty.call(next, key)) {
2232
+ return false;
2233
+ }
2234
+
2235
+ if (!Object.is(previousProps[key], next[key])) {
2236
+ return false;
2237
+ }
2238
+ }
2239
+
2240
+ for (const key in next) {
2241
+ if (hasOwnProperty.call(next, key) && key !== "children") {
2242
+ nextCount += 1;
2243
+ }
2244
+ }
2245
+
2246
+ return previousCount === nextCount;
2247
+ }
2248
+
2249
+ function getHostOwnPropsMeta(props: Record<string, unknown>): number | undefined {
2250
+ return (props as { [HOST_OWN_PROPS_META]?: number })[HOST_OWN_PROPS_META];
2251
+ }
2252
+
2253
+ function hostDirectTextChildChanged(previous: unknown, next: Record<string, unknown>): boolean {
2254
+ const previousText = getDirectHostTextChild(hostFiberChildrenProp(previous));
2255
+ const nextText = getDirectHostTextChild(next.children);
2256
+
2257
+ return (previousText !== undefined || nextText !== undefined) && previousText !== nextText;
2258
+ }
2259
+
2260
+ function hostChildListChanged(previous: unknown, next: Record<string, unknown>): boolean {
2261
+ const previousChildren = hostFiberChildrenProp(previous);
2262
+ const nextChildren = next.children;
2263
+
2264
+ if (Object.is(previousChildren, nextChildren)) {
2265
+ return false;
2266
+ }
2267
+
2268
+ if (
2269
+ getDirectHostTextChild(previousChildren) !== undefined ||
2270
+ getDirectHostTextChild(nextChildren) !== undefined
2271
+ ) {
2272
+ return false;
2273
+ }
2274
+
2275
+ if (sameSingleHostChild(previousChildren, nextChildren)) {
2276
+ return false;
2277
+ }
2278
+
2279
+ if (sameHostChildList(previousChildren, nextChildren)) {
2280
+ return false;
2281
+ }
2282
+
2283
+ return true;
2284
+ }
2285
+
2286
+ function sameSingleHostChild(previous: unknown, next: unknown): boolean {
2287
+ return (
2288
+ isReactCompatElement(previous) &&
2289
+ isReactCompatElement(next) &&
2290
+ previous.key === next.key &&
2291
+ previous.type === next.type
2292
+ );
2293
+ }
2294
+
2295
+ function sameHostChildList(previous: unknown, next: unknown): boolean {
2296
+ if (!Array.isArray(previous) || !Array.isArray(next) || previous.length !== next.length) {
2297
+ return false;
2298
+ }
2299
+
2300
+ for (let index = 0; index < previous.length; index += 1) {
2301
+ const previousChild = previous[index];
2302
+ const nextChild = next[index];
2303
+
2304
+ if (Object.is(previousChild, nextChild)) {
2305
+ continue;
2306
+ }
2307
+
2308
+ if (!sameSingleHostChild(previousChild, nextChild)) {
2309
+ return false;
2310
+ }
2311
+ }
2312
+
2313
+ return (
2314
+ previous.length > 0 ||
2315
+ (Array.isArray(previous) && Array.isArray(next))
2316
+ );
2317
+ }
2318
+
2319
+ function hostPropsAreChildrenOnly(props: unknown): boolean {
2320
+ if (typeof props !== "object" || props === null) {
2321
+ return false;
2322
+ }
2323
+
2324
+ for (const key in props) {
2325
+ if (
2326
+ Object.prototype.hasOwnProperty.call(props, key) &&
2327
+ key !== "children"
2328
+ ) {
2329
+ return false;
2330
+ }
2331
+ }
2332
+
2333
+ return true;
2334
+ }
2335
+
2336
+ function isRowTextOnlyUpdate(previous: unknown, next: Record<string, unknown>): boolean {
2337
+ if (typeof previous !== "object" || previous === null) {
2338
+ return false;
2339
+ }
2340
+
2341
+ const previousProps = previous as Record<string, unknown>;
2342
+ const previousMeta = getHostOwnPropsMeta(previousProps);
2343
+ const nextMeta = getHostOwnPropsMeta(next);
2344
+
2345
+ if (previousMeta === undefined || previousMeta !== nextMeta) {
2346
+ return false;
2347
+ }
2348
+
2349
+ const previousText = getDirectHostTextChild(previousProps.children);
2350
+ const nextText = getDirectHostTextChild(next.children);
2351
+
2352
+ return previousText !== undefined && nextText !== undefined && previousText !== nextText;
2353
+ }
2354
+
2355
+ function hostFiberChildrenProp(props: unknown): unknown {
2356
+ return typeof props === "object" && props !== null
2357
+ ? (props as { children?: unknown }).children
2358
+ : undefined;
2359
+ }
2360
+
2361
+ function getDirectHostTextChild(children: unknown): string | undefined {
2362
+ return typeof children === "string" || typeof children === "number"
2363
+ ? String(children)
2364
+ : undefined;
2365
+ }
2366
+
2367
+ function shouldUseDirectHostTextChild(): boolean {
2368
+ const globalProcess = (globalThis as { process?: { env?: Record<string, string | undefined> } })
2369
+ .process;
2370
+ return globalProcess?.env?.NODE_ENV === "production";
2371
+ }
2372
+
2373
+ function syncDirectHostTextChild(element: Element, text: string): void {
2374
+ const firstChild = element.firstChild;
2375
+
2376
+ if (firstChild instanceof Text && firstChild.nextSibling === null) {
2377
+ if (firstChild.data !== text) {
2378
+ firstChild.data = text;
2379
+ }
2380
+ return;
2381
+ }
2382
+
2383
+ element.textContent = text;
2384
+ }
2385
+
2386
+ function shouldPreserveContentEditableChildren(
2387
+ element: Element,
2388
+ props: Record<string, unknown>,
2389
+ childNodes: readonly Node[],
2390
+ ): boolean {
2391
+ void childNodes;
2392
+
2393
+ if (
2394
+ !element.hasAttribute("contenteditable") ||
2395
+ element.getAttribute("contenteditable") === "false"
2396
+ ) {
2397
+ return false;
2398
+ }
2399
+
2400
+ const children = props.children;
2401
+ return (
2402
+ children === undefined ||
2403
+ children === null ||
2404
+ children === false ||
2405
+ (Array.isArray(children) && children.length === 0)
2406
+ );
2407
+ }
2408
+
982
2409
  function createSuspenseFiber(
983
2410
  current: Fiber | undefined,
984
2411
  element: ReactCompatElement,
@@ -1078,25 +2505,8 @@ function createStrictModeFiber(
1078
2505
  ? createWorkInProgress(current, element.props)
1079
2506
  : createFiber("strict-mode", element.props, key);
1080
2507
  fiber.type = element.type;
1081
- const snapshot = takeRuntimeSnapshot(runtime);
1082
-
1083
- try {
1084
- createHostFiber(
1085
- fiber,
1086
- undefined,
1087
- element.props.children as ReactCompatNode,
1088
- undefined,
1089
- runtime,
1090
- `${path}.strict.preview`,
1091
- options.previousNodes === undefined
1092
- ? options
1093
- : { ...options, previousNodes: [] },
1094
- );
1095
- } finally {
1096
- restoreRuntimeSnapshot(runtime, snapshot);
1097
- }
1098
2508
 
1099
- const childResult = renderWithStrictMode(
2509
+ const { result: childResult, memoValues, memoValuesByHook } = renderWithStrictModeMemoCapture(
1100
2510
  runtime,
1101
2511
  () =>
1102
2512
  reconcileHostChild(
@@ -1109,6 +2519,29 @@ function createStrictModeFiber(
1109
2519
  ),
1110
2520
  );
1111
2521
  fiber.child = childResult.fiber;
2522
+
2523
+ const snapshot = takeRuntimeSnapshot(runtime);
2524
+ try {
2525
+ renderStrictModeReplay(
2526
+ runtime,
2527
+ memoValues,
2528
+ memoValuesByHook,
2529
+ () =>
2530
+ reconcileHostChild(
2531
+ fiber,
2532
+ childResult.fiber,
2533
+ element.props.children as ReactCompatNode,
2534
+ runtime,
2535
+ `${path}.strict`,
2536
+ options.previousNodes === undefined
2537
+ ? options
2538
+ : { ...options, previousNodes: [] },
2539
+ ),
2540
+ );
2541
+ } finally {
2542
+ restoreRuntimeSnapshot(runtime, snapshot);
2543
+ }
2544
+
1112
2545
  return { fiber, consumed: childResult.consumed };
1113
2546
  }
1114
2547
 
@@ -1220,6 +2653,7 @@ function reconcileSuspenseListForwards(
1220
2653
 
1221
2654
  fiber.return = parent;
1222
2655
  fiber.sibling = undefined;
2656
+ bubbleHostChild(parent, fiber);
1223
2657
 
1224
2658
  if (first === undefined) {
1225
2659
  first = fiber;
@@ -1272,6 +2706,7 @@ function reconcileSuspenseListBackwards(
1272
2706
 
1273
2707
  fiber.return = parent;
1274
2708
  fiber.sibling = undefined;
2709
+ bubbleHostChild(parent, fiber);
1275
2710
  fibers.unshift(fiber);
1276
2711
 
1277
2712
  if (isSuspendedSuspenseFiber(fiber)) {
@@ -1309,6 +2744,32 @@ function hasSuspendedChild(fiber: Fiber | undefined): boolean {
1309
2744
  return false;
1310
2745
  }
1311
2746
 
2747
+ function hasPendingAsyncChild(fiber: Fiber | undefined): boolean {
2748
+ let cursor = fiber;
2749
+
2750
+ while (cursor !== undefined) {
2751
+ if (isPendingLazyFiber(cursor) || isSuspendedSuspenseFiber(cursor)) {
2752
+ return true;
2753
+ }
2754
+
2755
+ if (cursor.child !== undefined && hasPendingAsyncChild(cursor.child)) {
2756
+ return true;
2757
+ }
2758
+
2759
+ cursor = cursor.sibling;
2760
+ }
2761
+
2762
+ return false;
2763
+ }
2764
+
2765
+ function isPendingLazyFiber(fiber: Fiber): boolean {
2766
+ if (fiber.tag !== "lazy" || !isLazyType(fiber.type)) {
2767
+ return false;
2768
+ }
2769
+
2770
+ return fiber.type.status !== "resolved" || fiber.child === undefined;
2771
+ }
2772
+
1312
2773
  function isSuspendedSuspenseFiber(fiber: Fiber): boolean {
1313
2774
  return (
1314
2775
  fiber.tag === "suspense" &&
@@ -1437,7 +2898,7 @@ function createPortalFiber(
1437
2898
  portal.children,
1438
2899
  runtime,
1439
2900
  `${path}.portal`,
1440
- options,
2901
+ { ...options, documentRef: portal.container.ownerDocument },
1441
2902
  );
1442
2903
  fiber.child = childResult.fiber;
1443
2904
  fiber.return = parent;
@@ -1452,6 +2913,10 @@ function normalizeChildren(node: ReactCompatNode): ReactCompatNode[] {
1452
2913
  return Array.isArray(node) ? node : [node];
1453
2914
  }
1454
2915
 
2916
+ function getDocumentRef(options: FiberHydrationOptions): Document {
2917
+ return options.documentRef ?? document;
2918
+ }
2919
+
1455
2920
  function collectExistingKeyedFibers(
1456
2921
  firstChild: Fiber | undefined,
1457
2922
  ): Map<string, Fiber> {
@@ -1473,11 +2938,65 @@ function getNodeKey(node: ReactCompatNode): string | undefined {
1473
2938
  return isReactCompatElement(node) && node.key !== null ? node.key : undefined;
1474
2939
  }
1475
2940
 
2941
+ function hasKeyedChild(children: readonly ReactCompatNode[]): boolean {
2942
+ for (const child of children) {
2943
+ if (getNodeKey(child) !== undefined) {
2944
+ return true;
2945
+ }
2946
+ }
2947
+
2948
+ return false;
2949
+ }
2950
+
1476
2951
  function getNodePathSegment(node: ReactCompatNode, index: number): string {
1477
2952
  const key = getNodeKey(node);
1478
2953
  return key === undefined ? String(index) : `k:${key}`;
1479
2954
  }
1480
2955
 
2956
+ function getReconcileChildPath(
2957
+ path: string,
2958
+ node: ReactCompatNode,
2959
+ index: number,
2960
+ options: FiberHydrationOptions,
2961
+ ): string {
2962
+ if (!shouldTrackReconcilePath(node, options)) {
2963
+ return "";
2964
+ }
2965
+
2966
+ return joinPath(path, getNodePathSegment(node, index));
2967
+ }
2968
+
2969
+ function shouldTrackReconcilePath(
2970
+ node: ReactCompatNode,
2971
+ options: FiberHydrationOptions,
2972
+ ): boolean {
2973
+ if (
2974
+ options.previousNodes !== undefined ||
2975
+ options.hydration?.onRecoverableError !== undefined ||
2976
+ options.resumeId !== undefined
2977
+ ) {
2978
+ return true;
2979
+ }
2980
+
2981
+ return !isHostElementWithDirectTextChild(node);
2982
+ }
2983
+
2984
+ function isHostElementWithDirectTextChild(node: ReactCompatNode): boolean {
2985
+ return (
2986
+ isReactCompatElement(node) &&
2987
+ typeof node.type === "string" &&
2988
+ getDirectHostTextChild(node.props.children) !== undefined
2989
+ );
2990
+ }
2991
+
2992
+ function getRootCommitPath(options: RenderOptions): string {
2993
+ return options.hydration?.onRecoverableError === undefined ? SKIP_COMMIT_PATH : "0";
2994
+ }
2995
+
2996
+ function joinCommitPath(path: string, segment: string): string {
2997
+ return path === SKIP_COMMIT_PATH ? "" : joinPath(path, segment);
2998
+ }
2999
+
1481
3000
  function getComponentName(component: Function): string {
1482
3001
  return component.name === "" ? "Anonymous" : component.name;
1483
3002
  }
@@ -1552,6 +3071,21 @@ function hasDirtyInstance(runtime: RootRuntime, keys: readonly string[]): boolea
1552
3071
  );
1553
3072
  }
1554
3073
 
3074
+ function hasUnflushedMountEffectInstance(runtime: RootRuntime, keys: readonly string[]): boolean {
3075
+ return keys.some((key) => {
3076
+ const instance = runtime.instances.get(key) as
3077
+ | { hooks?: readonly ({ kind?: string; mounted?: boolean; disposed?: boolean } | undefined)[] }
3078
+ | undefined;
3079
+
3080
+ return instance?.hooks?.some(
3081
+ (slot) =>
3082
+ slot?.kind === "effect" &&
3083
+ slot.disposed !== true &&
3084
+ slot.mounted !== true,
3085
+ ) === true;
3086
+ });
3087
+ }
3088
+
1555
3089
  function applyRef(ref: unknown, node: unknown): void {
1556
3090
  if (typeof ref === "function") {
1557
3091
  ref(node);