@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
package/src/hydration.ts CHANGED
@@ -226,7 +226,7 @@ export function withHydrationComponentStack(
226
226
  export function reportElementTextMismatch(
227
227
  options: RenderOptions,
228
228
  path: string,
229
- existing: HTMLElement,
229
+ existing: Element,
230
230
  children: ReactCompatNode,
231
231
  ): void {
232
232
  if (
package/src/index.ts CHANGED
@@ -86,11 +86,11 @@ export {
86
86
  cache,
87
87
  cacheSignal,
88
88
  captureOwnerStack,
89
- renderToString,
90
89
  startTransition,
91
90
  unstable_useCacheRefresh,
92
91
  useTransition,
93
92
  version,
94
93
  } from "./hooks.js";
94
+ export { renderToString } from "./server-render.js";
95
95
  export type { StartTransition, TransitionScope } from "./hooks.js";
96
96
  export { default } from "./react-default.js";
@@ -61,12 +61,12 @@ import {
61
61
  cache,
62
62
  cacheSignal,
63
63
  captureOwnerStack,
64
- renderToString,
65
64
  startTransition,
66
65
  unstable_useCacheRefresh,
67
66
  useTransition,
68
67
  version,
69
68
  } from "./hooks.js";
69
+ import { renderToString } from "./server-render.js";
70
70
 
71
71
  const ReactCompat = {
72
72
  Component,
package/src/reconciler.ts CHANGED
@@ -16,12 +16,14 @@ import {
16
16
  type ReactCompatPortal,
17
17
  } from "./element.js";
18
18
  import {
19
+ consumerContext,
19
20
  isReactCompatConsumer,
20
21
  isReactCompatProvider,
21
22
  renderWithContextProvider,
22
23
  useContext,
23
24
  } from "./context.js";
24
25
  import {
26
+ clearRuntimePortalNodes,
25
27
  hasStableExternalStores,
26
28
  restoreRuntimeSnapshot,
27
29
  renderWithProfiler,
@@ -32,8 +34,16 @@ import {
32
34
  } from "./hooks.js";
33
35
  import { commitDevToolsRoot } from "./devtools.js";
34
36
  import { applyPostChildFormProps, applyProps } from "./dom-props.js";
35
- import { syncChildNodes, syncScopedChildNodes } from "./dom-children.js";
37
+ import { syncChildNodes, syncOwnedChildNodes, syncScopedChildNodes } from "./dom-children.js";
36
38
  import { setLogicalEventParent } from "./events.js";
39
+ import {
40
+ createHostElement,
41
+ hostElementMatches,
42
+ isHostElement,
43
+ namespaceForHostChildren,
44
+ namespaceForHostElement,
45
+ type HostNamespace,
46
+ } from "./dom-host-rules.js";
37
47
  import {
38
48
  getHydrationScope,
39
49
  reportElementTextMismatch,
@@ -58,6 +68,7 @@ import { areMemoPropsEqual } from "./prop-comparison.js";
58
68
  import type { ReconcileResult } from "./reconcile-types.js";
59
69
 
60
70
  const nodeKeys = new WeakMap<Node, string>();
71
+ type ReconcileOptions = RenderOptions & { namespace?: HostNamespace };
61
72
 
62
73
  interface MemoRenderState {
63
74
  props: Record<string, unknown>;
@@ -81,10 +92,7 @@ export function renderIntoContainer(
81
92
  let committed = false;
82
93
 
83
94
  try {
84
- for (const portalContainer of runtime.portalContainers) {
85
- portalContainer.replaceChildren();
86
- }
87
- runtime.portalContainers.clear();
95
+ clearRuntimePortalNodes(runtime);
88
96
 
89
97
  const scope = getHydrationScope(container, options.resumeId);
90
98
  const renderOptions = { ...options, eventRoot: container };
@@ -126,7 +134,7 @@ function reconcileNodeList(
126
134
  node: ReactCompatNode,
127
135
  runtime: RootRuntime,
128
136
  path: string,
129
- options: RenderOptions = {},
137
+ options: ReconcileOptions = {},
130
138
  ): Node[] {
131
139
  const result = reconcileNode(parent, previousNodes, node, runtime, path, options);
132
140
  return result.nodes;
@@ -138,7 +146,7 @@ function reconcileNode(
138
146
  node: ReactCompatNode,
139
147
  runtime: RootRuntime,
140
148
  path: string,
141
- options: RenderOptions = {},
149
+ options: ReconcileOptions = {},
142
150
  ): ReconcileResult {
143
151
  if (node === null || node === undefined || typeof node === "boolean") {
144
152
  return { nodes: [], consumed: 0 };
@@ -185,19 +193,21 @@ function reconcilePortal(
185
193
  parent: ParentNode,
186
194
  runtime: RootRuntime,
187
195
  path: string,
188
- options: RenderOptions = {},
196
+ options: ReconcileOptions = {},
189
197
  ): ReconcileResult {
190
198
  runtime.portalContainers.add(portal.container);
191
199
  setLogicalEventParent(portal.container, parent);
200
+ const previousNodes = Array.from(runtime.portalNodes.get(portal.container) ?? []);
192
201
  const nodes = reconcileNodeList(
193
202
  portal.container,
194
- Array.from(portal.container.childNodes),
203
+ previousNodes,
195
204
  portal.children,
196
205
  runtime,
197
206
  `${path}.portal`,
198
207
  { ...options, eventRoot: portal.container },
199
208
  );
200
- syncChildNodes(portal.container, nodes);
209
+ syncOwnedChildNodes(portal.container, previousNodes, nodes);
210
+ runtime.portalNodes.set(portal.container, new Set(nodes));
201
211
  return { nodes: [], consumed: 0 };
202
212
  }
203
213
 
@@ -254,7 +264,7 @@ function reconcileElement(
254
264
  element: ReactCompatElement,
255
265
  runtime: RootRuntime,
256
266
  path: string,
257
- options: RenderOptions = {},
267
+ options: ReconcileOptions = {},
258
268
  ): ReconcileResult {
259
269
  if (element.type === Fragment) {
260
270
  return reconcileNode(
@@ -390,7 +400,7 @@ function reconcileElement(
390
400
  return reconcileNode(
391
401
  parent,
392
402
  previousNodes,
393
- render(useContext(elementType.context)),
403
+ render(useContext(consumerContext(elementType))),
394
404
  runtime,
395
405
  `${path}.consumer`,
396
406
  options,
@@ -407,6 +417,7 @@ function reconcileElement(
407
417
  `${path}.forwardRef`,
408
418
  options,
409
419
  ),
420
+ elementType,
410
421
  );
411
422
  }
412
423
 
@@ -515,6 +526,7 @@ function reconcileElement(
515
526
  `${path}.0`,
516
527
  withHydrationComponentStack(options, getComponentName(functionComponent)),
517
528
  ),
529
+ functionComponent,
518
530
  );
519
531
  }
520
532
 
@@ -522,15 +534,17 @@ function reconcileElement(
522
534
  throw new Error("Invalid react-compat element type.");
523
535
  }
524
536
 
537
+ const elementNamespace = namespaceForHostElement(options.namespace ?? "html", elementType);
538
+ const childNamespace = namespaceForHostChildren(elementNamespace, elementType);
525
539
  const existing = previousNodes[0];
526
540
  if (existing === undefined) {
527
541
  reportMissingHydrationNode(options, path);
528
- } else if (!(existing instanceof HTMLElement)) {
542
+ } else if (!isHostElement(existing)) {
529
543
  reportHydrationNodeTypeMismatch(options, path, `<${elementType}>`, existing);
530
544
  }
531
545
  if (
532
- existing instanceof HTMLElement &&
533
- existing.tagName.toLowerCase() !== elementType
546
+ isHostElement(existing) &&
547
+ !hostElementMatches(existing, elementType, elementNamespace)
534
548
  ) {
535
549
  reportRecoverable(
536
550
  options,
@@ -543,17 +557,17 @@ function reconcileElement(
543
557
  reportElementTextMismatch(options, `${path}.c`, existing, element.props.children);
544
558
  }
545
559
  const domElement =
546
- existing instanceof HTMLElement &&
547
- existing.tagName.toLowerCase() === elementType
560
+ isHostElement(existing) &&
561
+ hostElementMatches(existing, elementType, elementNamespace)
548
562
  ? existing
549
- : document.createElement(elementType);
563
+ : createHostElement(document, elementType, options.namespace ?? "html");
550
564
 
551
565
  applyProps(domElement, element.props, path, {
552
566
  ...options,
553
567
  preserveHydrationAttributes:
554
568
  options.hydration !== undefined &&
555
- existing instanceof HTMLElement &&
556
- existing.tagName.toLowerCase() === elementType,
569
+ isHostElement(existing) &&
570
+ hostElementMatches(existing, elementType, elementNamespace),
557
571
  });
558
572
  const previousChildNodes = Array.from(domElement.childNodes);
559
573
  const childResult = reconcileNode(
@@ -562,7 +576,7 @@ function reconcileElement(
562
576
  element.props.children,
563
577
  runtime,
564
578
  `${path}.c`,
565
- options,
579
+ { ...options, namespace: childNamespace },
566
580
  );
567
581
  reportExtraHydrationNodes(
568
582
  options,
@@ -570,12 +584,37 @@ function reconcileElement(
570
584
  previousChildNodes,
571
585
  childResult.consumed,
572
586
  );
573
- syncChildNodes(domElement, childResult.nodes);
587
+ if (!shouldPreserveContentEditableChildren(domElement, element.props, childResult.nodes)) {
588
+ syncChildNodes(domElement, childResult.nodes);
589
+ }
574
590
  applyPostChildFormProps(domElement, element.props);
575
591
  applyRef(element.ref, domElement);
576
592
  return { nodes: [domElement], consumed: existing === undefined ? 0 : 1 };
577
593
  }
578
594
 
595
+ function shouldPreserveContentEditableChildren(
596
+ element: Element,
597
+ props: Record<string, unknown>,
598
+ childNodes: readonly Node[],
599
+ ): boolean {
600
+ void childNodes;
601
+
602
+ if (
603
+ !element.hasAttribute("contenteditable") ||
604
+ element.getAttribute("contenteditable") === "false"
605
+ ) {
606
+ return false;
607
+ }
608
+
609
+ const children = props.children;
610
+ return (
611
+ children === undefined ||
612
+ children === null ||
613
+ children === false ||
614
+ (Array.isArray(children) && children.length === 0)
615
+ );
616
+ }
617
+
579
618
  function collectKeyedNodes(nodes: readonly Node[]): Map<string, Node> {
580
619
  const keyedNodes = new Map<string, Node>();
581
620
 
package/src/root.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ReactCompatNode } from "./element.js";
2
2
  import {
3
+ clearRuntimePortalNodes,
3
4
  createRootRuntime,
4
5
  flushSyncUpdates,
5
6
  hasStableExternalStores,
@@ -30,7 +31,7 @@ import {
30
31
  createContainerFiberRoot,
31
32
  enqueueRootRender,
32
33
  } from "./fiber-work-loop.js";
33
- import { commitFiberRoot } from "./fiber-commit.js";
34
+ import { commitFiberRoot, detachFiberRefs } from "./fiber-commit.js";
34
35
  import {
35
36
  canRenderHostFiber,
36
37
  commitHydratingHostFiberRoot,
@@ -122,9 +123,10 @@ export function createRoot(
122
123
  unmount() {
123
124
  runtime.currentElement = undefined;
124
125
  runtime.dispose();
126
+ detachFiberRefs(fiberRoot.current);
125
127
  runtime.instances.clear();
126
128
  unmountDevToolsRoot(container);
127
- container.replaceChildren();
129
+ clearElementChildren(container);
128
130
  },
129
131
  };
130
132
  }
@@ -140,10 +142,7 @@ function renderHostFiberIntoContainer(
140
142
  let committed = false;
141
143
 
142
144
  try {
143
- for (const portalContainer of runtime.portalContainers) {
144
- portalContainer.replaceChildren();
145
- }
146
- runtime.portalContainers.clear();
145
+ clearRuntimePortalNodes(runtime);
147
146
 
148
147
  const finishedWork = renderHostFiberRoot(fiberRoot, element, runtime);
149
148
 
@@ -153,6 +152,7 @@ function renderHostFiberIntoContainer(
153
152
 
154
153
  fiberRoot.finishedWork = finishedWork;
155
154
  commitFiberRoot(fiberRoot);
155
+ collectPortalNodes(fiberRoot.current, runtime);
156
156
  commitDevToolsRoot(container, fiberRoot);
157
157
  committed = true;
158
158
  return finishedWork;
@@ -182,10 +182,7 @@ function renderHydratingHostFiberIntoContainer(
182
182
  let committed = false;
183
183
 
184
184
  try {
185
- for (const portalContainer of runtime.portalContainers) {
186
- portalContainer.replaceChildren();
187
- }
188
- runtime.portalContainers.clear();
185
+ clearRuntimePortalNodes(runtime);
189
186
 
190
187
  const scope = getHydrationScope(container, options.resumeId);
191
188
  const finishedWork = renderHydratingHostFiberRoot(
@@ -206,6 +203,7 @@ function renderHydratingHostFiberIntoContainer(
206
203
  fiberRoot.finishedWork = undefined;
207
204
  fiberRoot.workInProgress = undefined;
208
205
  fiberRoot.workInProgressRootRenderLanes = 0;
206
+ collectPortalNodes(fiberRoot.current, runtime);
209
207
  commitDevToolsRoot(container, fiberRoot);
210
208
  committed = true;
211
209
  return finishedWork;
@@ -286,9 +284,10 @@ export function hydrateRoot(
286
284
  unmount() {
287
285
  runtime.currentElement = undefined;
288
286
  runtime.dispose();
287
+ detachFiberRefs(fiberRoot.current);
289
288
  runtime.instances.clear();
290
289
  unmountDevToolsRoot(container);
291
- container.replaceChildren();
290
+ clearElementChildren(container);
292
291
  },
293
292
  };
294
293
 
@@ -411,10 +410,54 @@ export function unmountComponentAtNode(container: Element): boolean {
411
410
  }
412
411
 
413
412
  const hadChildren = container.childNodes.length > 0;
414
- container.replaceChildren();
413
+ clearElementChildren(container);
415
414
  return hadChildren;
416
415
  }
417
416
 
417
+ function clearElementChildren(element: Element): void {
418
+ if (typeof element.replaceChildren === "function") {
419
+ element.replaceChildren();
420
+ return;
421
+ }
422
+
423
+ while (element.firstChild !== null) {
424
+ element.removeChild(element.firstChild);
425
+ }
426
+ }
427
+
428
+ function collectPortalNodes(fiber: Fiber | undefined, runtime: RootRuntime): void {
429
+ if (fiber === undefined || runtime.portalContainers.size === 0) {
430
+ return;
431
+ }
432
+
433
+ const deferredSiblings: Fiber[] = [];
434
+ let cursor: Fiber | undefined = fiber;
435
+
436
+ while (cursor !== undefined) {
437
+ if (cursor.tag === "portal" && cursor.stateNode instanceof Element) {
438
+ const nodes = Array.isArray(cursor.memoizedState)
439
+ ? cursor.memoizedState.filter((node): node is Node => node instanceof Node)
440
+ : [];
441
+ runtime.portalContainers.add(cursor.stateNode);
442
+ const ownedNodes = runtime.portalNodes.get(cursor.stateNode) ?? new Set<Node>();
443
+ for (const node of nodes) {
444
+ ownedNodes.add(node);
445
+ }
446
+ runtime.portalNodes.set(cursor.stateNode, ownedNodes);
447
+ }
448
+
449
+ if (cursor.child !== undefined) {
450
+ if (cursor.sibling !== undefined) {
451
+ deferredSiblings.push(cursor.sibling);
452
+ }
453
+ cursor = cursor.child;
454
+ continue;
455
+ }
456
+
457
+ cursor = cursor.sibling ?? deferredSiblings.pop();
458
+ }
459
+ }
460
+
418
461
  function resolveSelectiveHydrationBoundary(
419
462
  container: Element,
420
463
  event: Event,