@reckona/mreact-compat 0.0.90 → 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/dist/hooks.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { scheduleCallback } from "./fiber-scheduler.js";
2
- import { Activity, Fragment, FORWARD_REF_TYPE, MEMO_TYPE, Profiler, isReactCompatElement, } from "./element.js";
3
- import { isReactCompatContext, isReactCompatConsumer, isReactCompatProvider, renderWithContextProvider, useContext, } from "./context.js";
2
+ import { removeChildIfPresent } from "./dom-children.js";
3
+ import { isReactCompatContext, readContextValue, useContext, withContextReadObserver, } from "./context.js";
4
4
  import { isThenable } from "./thenable.js";
5
- import { isDangerousHtmlAttribute, isDangerousHtmlOptIn } from "./url-safety.js";
6
- import { escapeHtmlAttribute as escapeHtml } from "@reckona/mreact-shared/html-escape";
7
- let currentRuntime;
8
- let currentInstance;
9
- let currentCacheScope;
5
+ const HOOK_RENDER_STATE_KEY = Symbol.for("modular.react.hook_render_state");
6
+ const hookRenderState = (globalThis[HOOK_RENDER_STATE_KEY] ??= {
7
+ currentRuntime: undefined,
8
+ currentInstance: undefined,
9
+ currentCacheScope: undefined,
10
+ hostCommitDepth: 0,
11
+ queuedHostCommitRerenders: new Set(),
12
+ queuedEffectFlushRerenders: new Set(),
13
+ });
10
14
  const CACHE_SCOPE_SYMBOL = Symbol.for("modular.react.cache_scope");
11
15
  const emptyCacheOwnerStack = [];
12
16
  let syncVersion = 0;
@@ -17,12 +21,64 @@ let transitionRerenderScheduled = false;
17
21
  let eventBatchDepth = 0;
18
22
  let currentEventPriority = "default";
19
23
  let eventRerenderScheduled = false;
24
+ let effectFlushRerenderDepth = 0;
25
+ let strictMemoOwnerId = 0;
26
+ const strictMemoObjectOwnerIds = new WeakMap();
27
+ const strictMemoPrimitiveOwnerIds = new Map();
20
28
  const queuedTransitionRerenders = new Map();
21
29
  const queuedEventRerenders = new Set();
22
30
  export const version = "19.2.6";
23
31
  export function act(callback) {
24
- const result = callback();
25
- return (isThenable(result) ? Promise.resolve(result).then(() => undefined) : undefined);
32
+ const previousPriority = currentEventPriority;
33
+ currentEventPriority = "discrete";
34
+ eventBatchDepth += 1;
35
+ let result;
36
+ try {
37
+ result = callback();
38
+ }
39
+ catch (error) {
40
+ eventBatchDepth -= 1;
41
+ currentEventPriority = previousPriority;
42
+ if (eventBatchDepth === 0) {
43
+ flushEventRerendersForPriority("discrete");
44
+ }
45
+ throw error;
46
+ }
47
+ const finishActScope = () => {
48
+ eventBatchDepth -= 1;
49
+ currentEventPriority = previousPriority;
50
+ if (eventBatchDepth === 0) {
51
+ flushEventRerendersForPriority("discrete");
52
+ }
53
+ };
54
+ if (isThenable(result)) {
55
+ return Promise.resolve(result).then(async () => {
56
+ finishActScope();
57
+ await flushActWorkAsync();
58
+ }, (error) => {
59
+ finishActScope();
60
+ throw error;
61
+ });
62
+ }
63
+ finishActScope();
64
+ flushActWork();
65
+ return undefined;
66
+ }
67
+ async function flushActWorkAsync() {
68
+ for (let attempt = 0; attempt < 10; attempt += 1) {
69
+ await Promise.resolve();
70
+ flushActWork();
71
+ }
72
+ for (let attempt = 0; attempt < 3; attempt += 1) {
73
+ await new Promise((resolve) => setTimeout(resolve, 0));
74
+ flushActWork();
75
+ }
76
+ }
77
+ function flushActWork() {
78
+ flushQueuedEventRerenders("sync");
79
+ flushQueuedTransitionRerenders();
80
+ flushHostCommitRerenders();
81
+ flushEffectFlushRerenders();
26
82
  }
27
83
  export function createRootRuntime(rerender, options = {}) {
28
84
  return {
@@ -37,11 +93,19 @@ export function createRootRuntime(rerender, options = {}) {
37
93
  pendingEffects: [],
38
94
  externalStoreChecks: [],
39
95
  portalContainers: new Set(),
96
+ portalNodes: new Map(),
40
97
  idCounter: 0,
41
98
  identifierPrefix: options.identifierPrefix ?? "",
42
99
  idMode: options.idMode ?? "client",
43
100
  strictModeDepth: 0,
101
+ strictReplayDepth: 0,
102
+ strictMemoCapture: undefined,
103
+ strictMemoCaptureByHook: undefined,
104
+ strictMemoReplay: undefined,
105
+ strictMemoReplayByHook: undefined,
44
106
  profilerFlushDepth: 0,
107
+ effectFlushPhase: undefined,
108
+ externalStoreUpdate: false,
45
109
  rerender,
46
110
  beginRender() {
47
111
  this.activeInstanceKeys = new Set();
@@ -65,24 +129,34 @@ export function createRootRuntime(rerender, options = {}) {
65
129
  }
66
130
  this.activeInstanceKeys = undefined;
67
131
  this.activeProfilerPaths = undefined;
68
- currentRuntime = undefined;
69
- currentInstance = undefined;
132
+ hookRenderState.currentRuntime = undefined;
133
+ hookRenderState.currentInstance = undefined;
70
134
  if (committed) {
71
135
  flushProfilerCommits(this, profilerCommits);
136
+ flushHostCommitRerenders();
137
+ this.externalStoreUpdate = false;
72
138
  }
73
139
  },
74
140
  flushEffects() {
75
141
  this.profilerFlushDepth += 1;
76
142
  try {
143
+ this.effectFlushPhase = "insertion";
77
144
  flushPendingEffects(this.pendingInsertionEffects);
145
+ this.effectFlushPhase = "layout";
78
146
  const strictLayoutEffects = flushPendingEffects(this.pendingLayoutEffects);
147
+ this.effectFlushPhase = "normal";
79
148
  const strictEffects = flushPendingEffects(this.pendingEffects);
149
+ this.effectFlushPhase = undefined;
80
150
  const strictReplayEffects = [...strictLayoutEffects, ...strictEffects];
81
151
  cleanupStrictEffects(strictReplayEffects);
82
152
  replayStrictEffects(strictReplayEffects);
83
153
  }
84
154
  finally {
155
+ this.effectFlushPhase = undefined;
85
156
  this.profilerFlushDepth -= 1;
157
+ if (this.profilerFlushDepth === 0) {
158
+ flushEffectFlushRerenders();
159
+ }
86
160
  }
87
161
  },
88
162
  dispose() {
@@ -96,10 +170,7 @@ export function createRootRuntime(rerender, options = {}) {
96
170
  this.activeProfilerPaths = undefined;
97
171
  this.mountedProfilerPaths.clear();
98
172
  this.profilerBaseDurations.clear();
99
- for (const container of this.portalContainers) {
100
- container.replaceChildren();
101
- }
102
- this.portalContainers.clear();
173
+ clearRuntimePortalNodes(this);
103
174
  },
104
175
  };
105
176
  }
@@ -116,31 +187,31 @@ export function refreshCacheScope(scope) {
116
187
  scope.controller = new AbortController();
117
188
  }
118
189
  export function runWithCacheScope(scope, callback) {
119
- const previousScope = currentCacheScope;
190
+ const previousScope = hookRenderState.currentCacheScope;
120
191
  const previousGlobalScope = getGlobalCacheScope();
121
- currentCacheScope = scope;
192
+ hookRenderState.currentCacheScope = scope;
122
193
  setGlobalCacheScope(scope);
123
194
  try {
124
195
  const result = callback();
125
196
  if (isThenable(result)) {
126
197
  return Promise.resolve(result).finally(() => {
127
- currentCacheScope = previousScope;
198
+ hookRenderState.currentCacheScope = previousScope;
128
199
  setGlobalCacheScope(previousGlobalScope);
129
200
  });
130
201
  }
131
- currentCacheScope = previousScope;
202
+ hookRenderState.currentCacheScope = previousScope;
132
203
  setGlobalCacheScope(previousGlobalScope);
133
204
  return result;
134
205
  }
135
206
  catch (error) {
136
- currentCacheScope = previousScope;
207
+ hookRenderState.currentCacheScope = previousScope;
137
208
  setGlobalCacheScope(previousGlobalScope);
138
209
  throw error;
139
210
  }
140
211
  }
141
212
  export function renderWithProfiler(runtime, path, props, render) {
142
213
  const startTime = getCurrentTime();
143
- const phase = runtime.profilerFlushDepth > 0
214
+ const phase = runtime.profilerFlushDepth > 0 || effectFlushRerenderDepth > 0
144
215
  ? "nested-update"
145
216
  : runtime.mountedProfilerPaths.has(path)
146
217
  ? "update"
@@ -167,10 +238,17 @@ export function renderWithProfiler(runtime, path, props, render) {
167
238
  }
168
239
  }
169
240
  }
170
- export function renderWithRootRuntime(runtime, path, render) {
171
- const previousRuntime = currentRuntime;
172
- const previousInstance = currentInstance;
173
- const instance = runtime.instances.get(path) ?? {
241
+ export function renderWithRootRuntime(runtime, path, render, owner) {
242
+ const previousRuntime = hookRenderState.currentRuntime;
243
+ const previousInstance = hookRenderState.currentInstance;
244
+ let instance = runtime.instances.get(path);
245
+ if (instance !== undefined && owner !== undefined && instance.owner !== owner) {
246
+ cleanupInstance(instance);
247
+ instance = undefined;
248
+ }
249
+ instance ??= {
250
+ owner,
251
+ path,
174
252
  hooks: [],
175
253
  hookIndex: 0,
176
254
  dirty: false,
@@ -178,23 +256,46 @@ export function renderWithRootRuntime(runtime, path, render) {
178
256
  devToolsHookTypes: [],
179
257
  devToolsHookSuppressionDepth: 0,
180
258
  };
259
+ instance.owner = owner;
260
+ instance.path = path;
181
261
  runtime.instances.set(path, instance);
182
262
  runtime.activeInstanceKeys?.add(path);
183
263
  instance.hookIndex = 0;
184
264
  instance.dirty = false;
265
+ instance.disposed = false;
266
+ delete instance.contextDependencies;
185
267
  instance.devToolsHooks = [];
186
268
  instance.devToolsHookTypes = [];
187
269
  instance.devToolsHookSuppressionDepth = 0;
188
- currentRuntime = runtime;
189
- currentInstance = instance;
270
+ hookRenderState.currentRuntime = runtime;
271
+ hookRenderState.currentInstance = instance;
190
272
  try {
191
- return render();
273
+ return withContextReadObserver((context, value) => {
274
+ (instance.contextDependencies ??= new Map()).set(context, value);
275
+ }, render);
192
276
  }
193
277
  finally {
194
- currentRuntime = previousRuntime;
195
- currentInstance = previousInstance;
278
+ hookRenderState.currentRuntime = previousRuntime;
279
+ hookRenderState.currentInstance = previousInstance;
196
280
  }
197
281
  }
282
+ export function hasChangedContextDependency(runtime, keys) {
283
+ for (const key of keys) {
284
+ const dependencies = runtime.instances.get(key)?.contextDependencies;
285
+ if (dependencies === undefined) {
286
+ continue;
287
+ }
288
+ for (const [context, value] of dependencies) {
289
+ if (!Object.is(readContextValue(context), value)) {
290
+ return true;
291
+ }
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+ export function hasContextDependency(runtime, keys) {
297
+ return keys.some((key) => runtime.instances.get(key)?.contextDependencies !== undefined);
298
+ }
198
299
  export function getDevToolsHookState(runtime, path) {
199
300
  const instance = runtime.instances.get(path);
200
301
  if (instance === undefined) {
@@ -214,10 +315,44 @@ export function renderWithStrictMode(runtime, render) {
214
315
  runtime.strictModeDepth -= 1;
215
316
  }
216
317
  }
318
+ export function renderWithStrictModeMemoCapture(runtime, render) {
319
+ const previousCapture = runtime.strictMemoCapture;
320
+ const previousCaptureByHook = runtime.strictMemoCaptureByHook;
321
+ runtime.strictMemoCapture = [];
322
+ runtime.strictMemoCaptureByHook = new Map();
323
+ try {
324
+ const result = renderWithStrictMode(runtime, render);
325
+ return {
326
+ result,
327
+ memoValues: runtime.strictMemoCapture,
328
+ memoValuesByHook: runtime.strictMemoCaptureByHook,
329
+ };
330
+ }
331
+ finally {
332
+ runtime.strictMemoCapture = previousCapture;
333
+ runtime.strictMemoCaptureByHook = previousCaptureByHook;
334
+ }
335
+ }
336
+ export function renderStrictModeReplay(runtime, memoValues, memoValuesByHook, render) {
337
+ const previousReplay = runtime.strictMemoReplay;
338
+ const previousReplayByHook = runtime.strictMemoReplayByHook;
339
+ runtime.strictReplayDepth += 1;
340
+ runtime.strictMemoReplay = { values: memoValues, index: 0 };
341
+ runtime.strictMemoReplayByHook = memoValuesByHook;
342
+ try {
343
+ return render();
344
+ }
345
+ finally {
346
+ runtime.strictMemoReplay = previousReplay;
347
+ runtime.strictMemoReplayByHook = previousReplayByHook;
348
+ runtime.strictReplayDepth -= 1;
349
+ }
350
+ }
217
351
  export function takeRuntimeSnapshot(runtime) {
218
352
  return {
219
353
  instanceKeys: new Set(runtime.instances.keys()),
220
354
  portalContainers: new Set(runtime.portalContainers),
355
+ portalNodes: clonePortalNodes(runtime.portalNodes),
221
356
  pendingInsertionEffectsLength: runtime.pendingInsertionEffects.length,
222
357
  pendingLayoutEffectsLength: runtime.pendingLayoutEffects.length,
223
358
  pendingEffectsLength: runtime.pendingEffects.length,
@@ -227,6 +362,11 @@ export function takeRuntimeSnapshot(runtime) {
227
362
  identifierPrefix: runtime.identifierPrefix,
228
363
  idMode: runtime.idMode,
229
364
  strictModeDepth: runtime.strictModeDepth,
365
+ strictReplayDepth: runtime.strictReplayDepth,
366
+ strictMemoCapture: runtime.strictMemoCapture,
367
+ strictMemoCaptureByHook: runtime.strictMemoCaptureByHook,
368
+ strictMemoReplay: runtime.strictMemoReplay,
369
+ strictMemoReplayByHook: runtime.strictMemoReplayByHook,
230
370
  profilerFlushDepth: runtime.profilerFlushDepth,
231
371
  };
232
372
  }
@@ -240,21 +380,49 @@ export function restoreRuntimeSnapshot(runtime, snapshot) {
240
380
  runtime.identifierPrefix = snapshot.identifierPrefix;
241
381
  runtime.idMode = snapshot.idMode;
242
382
  runtime.strictModeDepth = snapshot.strictModeDepth;
383
+ runtime.strictReplayDepth = snapshot.strictReplayDepth;
384
+ runtime.strictMemoCapture = snapshot.strictMemoCapture;
385
+ runtime.strictMemoCaptureByHook = snapshot.strictMemoCaptureByHook;
386
+ runtime.strictMemoReplay = snapshot.strictMemoReplay;
387
+ runtime.strictMemoReplayByHook = snapshot.strictMemoReplayByHook;
243
388
  runtime.profilerFlushDepth = snapshot.profilerFlushDepth;
244
389
  for (const key of runtime.instances.keys()) {
245
390
  if (!snapshot.instanceKeys.has(key)) {
246
391
  runtime.instances.delete(key);
247
392
  }
248
393
  }
249
- for (const container of runtime.portalContainers) {
250
- if (!snapshot.portalContainers.has(container)) {
251
- container.replaceChildren();
252
- }
253
- }
394
+ clearRuntimePortalNodesExcept(runtime, snapshot.portalNodes);
254
395
  runtime.portalContainers.clear();
255
396
  for (const container of snapshot.portalContainers) {
256
397
  runtime.portalContainers.add(container);
257
398
  }
399
+ runtime.portalNodes = clonePortalNodes(snapshot.portalNodes);
400
+ }
401
+ export function clearRuntimePortalNodes(runtime) {
402
+ for (const [container, nodes] of runtime.portalNodes) {
403
+ for (const node of nodes) {
404
+ removeChildIfPresent(container, node);
405
+ }
406
+ }
407
+ runtime.portalNodes.clear();
408
+ runtime.portalContainers.clear();
409
+ }
410
+ function clearRuntimePortalNodesExcept(runtime, preserved) {
411
+ for (const [container, nodes] of runtime.portalNodes) {
412
+ const preservedNodes = preserved.get(container);
413
+ for (const node of nodes) {
414
+ if (preservedNodes?.has(node) !== true) {
415
+ removeChildIfPresent(container, node);
416
+ }
417
+ }
418
+ }
419
+ }
420
+ function clonePortalNodes(source) {
421
+ const clone = new Map();
422
+ for (const [container, nodes] of source) {
423
+ clone.set(container, new Set(nodes));
424
+ }
425
+ return clone;
258
426
  }
259
427
  export function useState(initial) {
260
428
  const runtime = requireRuntime();
@@ -273,13 +441,22 @@ export function useState(initial) {
273
441
  throw new Error("Hook order changed between renders.");
274
442
  }
275
443
  const setState = (value) => {
444
+ const previousValue = slot.value;
276
445
  const nextValue = typeof value === "function"
277
446
  ? value(slot.value)
278
447
  : value;
279
448
  if (Object.is(slot.value, nextValue)) {
280
449
  return;
281
450
  }
451
+ if (hookRenderState.hostCommitDepth > 0 && !Object.hasOwn(slot, "hostCommitValue")) {
452
+ slot.hostCommitValue = previousValue;
453
+ }
282
454
  slot.value = nextValue;
455
+ if (hookRenderState.hostCommitDepth > 0) {
456
+ updateHostCommitDirtyState(instance);
457
+ hookRenderState.queuedHostCommitRerenders.add(runtime);
458
+ return;
459
+ }
283
460
  scheduleInstanceUpdate(runtime, instance);
284
461
  };
285
462
  recordDevToolsHook("useState", {
@@ -349,6 +526,7 @@ export function useImperativeHandle(ref, create, deps) {
349
526
  : { kind: "imperative-handle", deps });
350
527
  }
351
528
  export function useMemo(factory, deps) {
529
+ const runtime = requireRuntime();
352
530
  const instance = requireInstance();
353
531
  const index = instance.hookIndex;
354
532
  instance.hookIndex += 1;
@@ -356,21 +534,72 @@ export function useMemo(factory, deps) {
356
534
  if (slot !== undefined && slot.kind !== "memo") {
357
535
  throw new Error("Hook order changed between renders.");
358
536
  }
359
- if (slot === undefined ||
537
+ let value;
538
+ const shouldRecompute = slot === undefined ||
360
539
  deps === undefined ||
361
540
  slot.deps === undefined ||
362
- !areHookInputsEqual(deps, slot.deps)) {
363
- const value = factory();
541
+ !areHookInputsEqual(deps, slot.deps);
542
+ if (runtime.strictReplayDepth > 0) {
543
+ const replayValue = factory();
544
+ if (slot === undefined) {
545
+ slot =
546
+ deps === undefined
547
+ ? { kind: "memo", value: replayValue }
548
+ : { kind: "memo", value: replayValue, deps };
549
+ instance.hooks[index] = slot;
550
+ }
551
+ const hookKey = getStrictMemoHookKey(instance, index);
552
+ const replayByHook = runtime.strictMemoReplayByHook;
553
+ const replay = runtime.strictMemoReplay;
554
+ value = replayByHook?.has(hookKey) === true
555
+ ? replayByHook.get(hookKey)
556
+ : replay === undefined || replay.index >= replay.values.length
557
+ ? slot.value
558
+ : replay.values[replay.index++];
559
+ }
560
+ else if (shouldRecompute) {
561
+ value = factory();
364
562
  slot =
365
563
  deps === undefined
366
564
  ? { kind: "memo", value }
367
565
  : { kind: "memo", value, deps };
368
566
  instance.hooks[index] = slot;
369
567
  }
370
- recordDevToolsHook("useMemo", slot.deps === undefined
371
- ? { kind: "memo", value: slot.value }
372
- : { kind: "memo", value: slot.value, deps: slot.deps });
373
- return slot.value;
568
+ else {
569
+ value = slot.value;
570
+ }
571
+ if (runtime.strictModeDepth > 0 && runtime.strictReplayDepth === 0) {
572
+ runtime.strictMemoCapture?.push(value);
573
+ runtime.strictMemoCaptureByHook?.set(getStrictMemoHookKey(instance, index), value);
574
+ }
575
+ const memoSlot = slot;
576
+ if (memoSlot === undefined) {
577
+ throw new Error("Hook order changed between renders.");
578
+ }
579
+ recordDevToolsHook("useMemo", memoSlot.deps === undefined
580
+ ? { kind: "memo", value }
581
+ : { kind: "memo", value, deps: memoSlot.deps });
582
+ return value;
583
+ }
584
+ function getStrictMemoHookKey(instance, index) {
585
+ return `${instance.path}:${getStrictMemoOwnerId(instance.owner)}:${index}`;
586
+ }
587
+ function getStrictMemoOwnerId(owner) {
588
+ if ((typeof owner === "object" && owner !== null) || typeof owner === "function") {
589
+ const objectOwner = owner;
590
+ let ownerId = strictMemoObjectOwnerIds.get(objectOwner);
591
+ if (ownerId === undefined) {
592
+ ownerId = strictMemoOwnerId++;
593
+ strictMemoObjectOwnerIds.set(objectOwner, ownerId);
594
+ }
595
+ return ownerId;
596
+ }
597
+ let ownerId = strictMemoPrimitiveOwnerIds.get(owner);
598
+ if (ownerId === undefined) {
599
+ ownerId = strictMemoOwnerId++;
600
+ strictMemoPrimitiveOwnerIds.set(owner, ownerId);
601
+ }
602
+ return ownerId;
374
603
  }
375
604
  function assignRef(ref, value) {
376
605
  if (typeof ref === "function") {
@@ -382,7 +611,7 @@ function assignRef(ref, value) {
382
611
  }
383
612
  }
384
613
  function recordDevToolsHook(type, value) {
385
- const instance = currentInstance;
614
+ const instance = hookRenderState.currentInstance;
386
615
  if (instance === undefined || instance.devToolsHookSuppressionDepth > 0) {
387
616
  return;
388
617
  }
@@ -479,14 +708,29 @@ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot =
479
708
  recordExternalStoreCheck(getSnapshot, currentSnapshot);
480
709
  runWithoutDevToolsHookTracking(() => useEffect(() => {
481
710
  const checkForUpdates = () => {
711
+ if (instance.disposed === true) {
712
+ return;
713
+ }
482
714
  const nextSnapshot = getSnapshot();
483
715
  if (!Object.is(slot.value, nextSnapshot)) {
484
716
  slot.value = nextSnapshot;
717
+ instance.dirty = true;
718
+ runtime.externalStoreUpdate = true;
719
+ if (runtime.profilerFlushDepth > 0) {
720
+ hookRenderState.queuedEffectFlushRerenders.add(runtime);
721
+ return;
722
+ }
485
723
  runtime.rerender("sync");
486
724
  }
487
725
  };
488
- checkForUpdates();
489
- return subscribe(checkForUpdates);
726
+ if (slot.hasMounted !== true) {
727
+ checkForUpdates();
728
+ }
729
+ const unsubscribe = subscribe(checkForUpdates);
730
+ slot.hasMounted = true;
731
+ return () => {
732
+ unsubscribe();
733
+ };
490
734
  }, [subscribe, getSnapshot]));
491
735
  recordDevToolsHook("useSyncExternalStore", {
492
736
  kind: "store",
@@ -617,280 +861,6 @@ export function unstable_useCacheRefresh() {
617
861
  export function hasStableExternalStores(runtime) {
618
862
  return runtime.externalStoreChecks.every((check) => Object.is(check.getSnapshot(), check.value));
619
863
  }
620
- export function renderToString(component, props, options = {}) {
621
- const runtime = createRootRuntime(() => undefined, {
622
- ...options,
623
- idMode: "server",
624
- });
625
- return runWithCacheScope(createCacheScope(), () => {
626
- try {
627
- const rendered = renderWithRootRuntime(runtime, "0", () => {
628
- if (isClassComponentType(component)) {
629
- const instance = new component(props);
630
- return instance.render();
631
- }
632
- return component(props);
633
- });
634
- return typeof rendered === "string"
635
- ? rendered
636
- : renderNodeToString(rendered, runtime, "0");
637
- }
638
- finally {
639
- runtime.dispose();
640
- }
641
- });
642
- }
643
- function renderNodeToString(node, runtime, path) {
644
- if (node === null || node === undefined || typeof node === "boolean") {
645
- return "";
646
- }
647
- if (typeof node === "string" || typeof node === "number") {
648
- return escapeHtml(node);
649
- }
650
- if (Array.isArray(node)) {
651
- return node.map((child, index) => renderNodeToString(child, runtime, `${path}.${index}`)).join("");
652
- }
653
- if (!isReactCompatElement(node)) {
654
- return "";
655
- }
656
- return renderElementToString(node, runtime, path);
657
- }
658
- function renderElementToString(element, runtime, path) {
659
- if (typeof element.type === "string") {
660
- if (element.type === "textarea") {
661
- return renderTextareaToString(element, runtime, path);
662
- }
663
- if (element.type === "select") {
664
- return renderSelectToString(element, runtime, path);
665
- }
666
- const attributes = element.type === "input"
667
- ? renderInputAttributesToString(element.props)
668
- : Object.entries(element.props)
669
- .map(([name, value]) => renderHtmlAttribute(name, value))
670
- .filter((attribute) => attribute !== "")
671
- .join("");
672
- if (voidHtmlElements.has(element.type)) {
673
- return `<${element.type}${attributes}/>`;
674
- }
675
- return `<${element.type}${attributes}>${renderNodeToString(element.props.children, runtime, `${path}.children`)}</${element.type}>`;
676
- }
677
- if (element.type === Fragment) {
678
- return renderNodeToString(element.props.children, runtime, `${path}.fragment`);
679
- }
680
- if (element.type === Activity) {
681
- if (element.props.mode === "hidden") {
682
- return "";
683
- }
684
- return `<!--&-->${renderNodeToString(element.props.children, runtime, `${path}.activity`)}<!--/&-->`;
685
- }
686
- if (element.type === Profiler) {
687
- return renderNodeToString(element.props.children, runtime, `${path}.profiler`);
688
- }
689
- if (isReactCompatProvider(element.type)) {
690
- return renderWithContextProvider(element.type, element.props.value, () => renderNodeToString(element.props.children, runtime, `${path}.provider`));
691
- }
692
- if (isReactCompatConsumer(element.type)) {
693
- const children = element.props.children;
694
- if (typeof children === "function") {
695
- return renderNodeToString(children(useContext(element.type.context)), runtime, `${path}.consumer`);
696
- }
697
- return "";
698
- }
699
- if (isForwardRefType(element.type)) {
700
- const forwardRefType = element.type;
701
- return renderNodeToString(renderWithRootRuntime(runtime, `${path}.forwardRef`, () => forwardRefType.render(element.props, element.ref)), runtime, `${path}.forwardRef`);
702
- }
703
- if (isMemoType(element.type)) {
704
- return renderNodeToString({
705
- ...element,
706
- type: element.type.type,
707
- }, runtime, `${path}.memo`);
708
- }
709
- if (isClassComponentType(element.type)) {
710
- const instance = new element.type(element.props);
711
- return renderNodeToString(renderWithRootRuntime(runtime, path, () => instance.render()), runtime, path);
712
- }
713
- if (typeof element.type === "function") {
714
- const component = element.type;
715
- return renderNodeToString(renderWithRootRuntime(runtime, path, () => component(element.props)), runtime, path);
716
- }
717
- return "";
718
- }
719
- function isClassComponentType(value) {
720
- return (typeof value === "function" &&
721
- typeof value.prototype?.render === "function");
722
- }
723
- function renderTextareaToString(element, runtime, path) {
724
- const value = element.props.value ??
725
- element.props.defaultValue ??
726
- element.props.children;
727
- const attributes = Object.entries(element.props)
728
- .filter(([name]) => name !== "value" && name !== "defaultValue")
729
- .map(([name, child]) => renderHtmlAttribute(name, child))
730
- .filter((attribute) => attribute !== "")
731
- .join("");
732
- return `<textarea${attributes}>${renderNodeToString(value, runtime, `${path}.textarea`)}</textarea>`;
733
- }
734
- function renderSelectToString(element, runtime, path) {
735
- const selectedValue = element.props.value ??
736
- element.props.defaultValue;
737
- const attributes = Object.entries(element.props)
738
- .filter(([name]) => name !== "value" && name !== "defaultValue")
739
- .map(([name, child]) => renderHtmlAttribute(name, child))
740
- .filter((attribute) => attribute !== "")
741
- .join("");
742
- return `<select${attributes}>${renderSelectChildrenToString(element.props.children, selectedValue, runtime, `${path}.select`)}</select>`;
743
- }
744
- function renderSelectChildrenToString(children, selectedValue, runtime, path) {
745
- const childArray = Array.isArray(children) ? children : [children];
746
- return childArray.map((child, index) => {
747
- if (!isReactCompatElement(child) || child.type !== "option") {
748
- return renderNodeToString(child, runtime, `${path}.${index}`);
749
- }
750
- const optionValue = child.props.value ?? child.props.children;
751
- const selected = selectedValue !== undefined && String(optionValue) === String(selectedValue);
752
- const props = selected
753
- ? { ...child.props, selected: true }
754
- : child.props;
755
- return renderElementToString({ ...child, props }, runtime, `${path}.${index}`);
756
- }).join("");
757
- }
758
- function renderInputAttributesToString(props) {
759
- const hasValue = props.value !== undefined;
760
- const hasChecked = props.checked !== undefined;
761
- return Object.entries(props)
762
- .filter(([name]) => !((name === "defaultValue" && hasValue) || (name === "defaultChecked" && hasChecked)))
763
- .sort(([leftName], [rightName]) => Number(isInputValueAttribute(leftName)) - Number(isInputValueAttribute(rightName)))
764
- .map(([name, value]) => renderHtmlAttribute(toInputHtmlAttributeName(name), value))
765
- .filter((attribute) => attribute !== "")
766
- .join("");
767
- }
768
- function renderHtmlAttribute(name, value) {
769
- if (name === "children" ||
770
- name === "key" ||
771
- name === "ref" ||
772
- /^on[A-Z]/.test(name) ||
773
- value === null ||
774
- value === undefined ||
775
- value === false ||
776
- typeof value === "function") {
777
- return "";
778
- }
779
- if (name === "style") {
780
- const style = renderStyleAttribute(value);
781
- return style === "" ? "" : ` style="${escapeHtml(style)}"`;
782
- }
783
- const attributeName = toHtmlAttributeName(name);
784
- if (isDangerousHtmlAttribute(attributeName)) {
785
- return isDangerousHtmlOptIn(value)
786
- ? ` ${attributeName}="${escapeHtml(value.__html)}"`
787
- : "";
788
- }
789
- if (typeof value === "object") {
790
- return "";
791
- }
792
- if (value === true) {
793
- return ` ${attributeName}=""`;
794
- }
795
- return ` ${attributeName}="${escapeHtml(value)}"`;
796
- }
797
- function isInputValueAttribute(name) {
798
- return name === "value" || name === "defaultValue";
799
- }
800
- function toInputHtmlAttributeName(name) {
801
- if (name === "defaultValue") {
802
- return "value";
803
- }
804
- if (name === "defaultChecked") {
805
- return "checked";
806
- }
807
- return name;
808
- }
809
- function toHtmlAttributeName(name) {
810
- return HTML_ATTRIBUTE_ALIASES[name] ?? name;
811
- }
812
- const HTML_ATTRIBUTE_ALIASES = {
813
- acceptCharset: "accept-charset",
814
- autoFocus: "autofocus",
815
- autoPlay: "autoplay",
816
- charSet: "charset",
817
- className: "class",
818
- colSpan: "colspan",
819
- contentEditable: "contenteditable",
820
- crossOrigin: "crossorigin",
821
- encType: "enctype",
822
- formAction: "formaction",
823
- frameBorder: "frameborder",
824
- htmlFor: "for",
825
- httpEquiv: "http-equiv",
826
- maxLength: "maxlength",
827
- minLength: "minlength",
828
- noValidate: "novalidate",
829
- playsInline: "playsinline",
830
- readOnly: "readOnly",
831
- rowSpan: "rowspan",
832
- spellCheck: "spellcheck",
833
- srcDoc: "srcdoc",
834
- srcSet: "srcset",
835
- tabIndex: "tabindex",
836
- useMap: "usemap",
837
- };
838
- function renderStyleAttribute(value) {
839
- if (typeof value !== "object" || value === null) {
840
- return "";
841
- }
842
- return Object.entries(value)
843
- .filter(([, propertyValue]) => propertyValue !== null &&
844
- propertyValue !== undefined &&
845
- typeof propertyValue !== "boolean" &&
846
- propertyValue !== "")
847
- .map(([name, propertyValue]) => `${toKebabCase(name)}:${renderCssValue(name, propertyValue)}`)
848
- .join(";");
849
- }
850
- function renderCssValue(name, value) {
851
- if (typeof value !== "number" || value === 0 || isUnitlessCssProperty(name)) {
852
- return String(value);
853
- }
854
- return `${value}px`;
855
- }
856
- function toKebabCase(value) {
857
- return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
858
- }
859
- function isUnitlessCssProperty(name) {
860
- return (name === "flex" ||
861
- name === "fontWeight" ||
862
- name === "lineHeight" ||
863
- name === "opacity" ||
864
- name === "order" ||
865
- name === "zIndex" ||
866
- name === "zoom");
867
- }
868
- const voidHtmlElements = new Set([
869
- "area",
870
- "base",
871
- "br",
872
- "col",
873
- "embed",
874
- "hr",
875
- "img",
876
- "input",
877
- "link",
878
- "meta",
879
- "param",
880
- "source",
881
- "track",
882
- "wbr",
883
- ]);
884
- function isForwardRefType(value) {
885
- return (typeof value === "object" &&
886
- value !== null &&
887
- value.$$typeof === FORWARD_REF_TYPE);
888
- }
889
- function isMemoType(value) {
890
- return (typeof value === "object" &&
891
- value !== null &&
892
- value.$$typeof === MEMO_TYPE);
893
- }
894
864
  function readThenable(thenable) {
895
865
  const record = thenable;
896
866
  if (record.status === "fulfilled") {
@@ -957,6 +927,15 @@ export function flushSyncUpdates(callback) {
957
927
  currentEventPriority = previousEventPriority;
958
928
  }
959
929
  }
930
+ export function runWithHostCommit(callback) {
931
+ hookRenderState.hostCommitDepth += 1;
932
+ try {
933
+ return callback();
934
+ }
935
+ finally {
936
+ hookRenderState.hostCommitDepth -= 1;
937
+ }
938
+ }
960
939
  export function useTransition() {
961
940
  const [pending, setPending] = runWithoutDevToolsHookTracking(() => useState(false));
962
941
  const startTransitionWithPending = (scope) => {
@@ -1070,6 +1049,7 @@ function useEffectImpl(effectKind, callback, deps) {
1070
1049
  throw new Error("Hook order changed between renders.");
1071
1050
  }
1072
1051
  const shouldRun = slot === undefined ||
1052
+ slot.mounted !== true ||
1073
1053
  deps === undefined ||
1074
1054
  slot.deps === undefined ||
1075
1055
  !areHookInputsEqual(deps, slot.deps);
@@ -1092,7 +1072,8 @@ function useEffectImpl(effectKind, callback, deps) {
1092
1072
  }
1093
1073
  }
1094
1074
  slot.strictReplay =
1095
- runtime.strictModeDepth > 0 && effectKind !== "insertion";
1075
+ (runtime.strictModeDepth > 0 || runtime.strictReplayDepth > 0) &&
1076
+ effectKind !== "insertion";
1096
1077
  if (shouldRun) {
1097
1078
  const queue = effectKind === "insertion"
1098
1079
  ? runtime.pendingInsertionEffects
@@ -1103,7 +1084,7 @@ function useEffectImpl(effectKind, callback, deps) {
1103
1084
  }
1104
1085
  }
1105
1086
  function recordExternalStoreCheck(getSnapshot, value) {
1106
- currentRuntime?.externalStoreChecks.push({ getSnapshot, value });
1087
+ hookRenderState.currentRuntime?.externalStoreChecks.push({ getSnapshot, value });
1107
1088
  }
1108
1089
  function flushPendingEffects(queue) {
1109
1090
  const pending = queue.splice(0);
@@ -1121,6 +1102,7 @@ function flushPendingEffects(queue) {
1121
1102
  else {
1122
1103
  delete slot.cleanup;
1123
1104
  }
1105
+ slot.mounted = true;
1124
1106
  if (shouldReplay) {
1125
1107
  strictReplay.push({ slot });
1126
1108
  }
@@ -1184,9 +1166,20 @@ function runActionStateDispatch(slot, runtime, instance, payload) {
1184
1166
  });
1185
1167
  }
1186
1168
  function scheduleInstanceUpdate(runtime, instance) {
1169
+ if (instance.disposed === true) {
1170
+ return;
1171
+ }
1187
1172
  instance.dirty = true;
1188
1173
  if (transitionDepth === 0) {
1189
1174
  syncVersion += 1;
1175
+ if (hookRenderState.hostCommitDepth > 0) {
1176
+ hookRenderState.queuedHostCommitRerenders.add(runtime);
1177
+ return;
1178
+ }
1179
+ if (runtime.effectFlushPhase !== undefined) {
1180
+ hookRenderState.queuedEffectFlushRerenders.add(runtime);
1181
+ return;
1182
+ }
1190
1183
  if (eventBatchDepth > 0) {
1191
1184
  queueEventRerender(runtime);
1192
1185
  return;
@@ -1198,6 +1191,58 @@ function scheduleInstanceUpdate(runtime, instance) {
1198
1191
  queueTransitionRerender(runtime, currentTransitionContext);
1199
1192
  }
1200
1193
  }
1194
+ function flushHostCommitRerenders() {
1195
+ if (hookRenderState.hostCommitDepth > 0 ||
1196
+ hookRenderState.queuedHostCommitRerenders.size === 0) {
1197
+ return;
1198
+ }
1199
+ const runtimes = [...hookRenderState.queuedHostCommitRerenders];
1200
+ hookRenderState.queuedHostCommitRerenders.clear();
1201
+ for (const runtime of runtimes) {
1202
+ const hasDirtyInstance = Array.from(runtime.instances.values()).some((instance) => instance.dirty);
1203
+ clearHostCommitStateBaselines(runtime);
1204
+ if (hasDirtyInstance) {
1205
+ runtime.rerender("sync");
1206
+ }
1207
+ }
1208
+ }
1209
+ function flushEffectFlushRerenders() {
1210
+ if (effectFlushRerenderDepth > 0 ||
1211
+ hookRenderState.queuedEffectFlushRerenders.size === 0) {
1212
+ return;
1213
+ }
1214
+ effectFlushRerenderDepth += 1;
1215
+ try {
1216
+ for (let attempt = 0; attempt < 3 && hookRenderState.queuedEffectFlushRerenders.size > 0; attempt += 1) {
1217
+ const runtimes = [...hookRenderState.queuedEffectFlushRerenders];
1218
+ hookRenderState.queuedEffectFlushRerenders.clear();
1219
+ for (const runtime of runtimes) {
1220
+ const hasDirtyInstance = Array.from(runtime.instances.values()).some((instance) => instance.dirty);
1221
+ if (hasDirtyInstance) {
1222
+ runtime.rerender("sync");
1223
+ }
1224
+ }
1225
+ }
1226
+ hookRenderState.queuedEffectFlushRerenders.clear();
1227
+ }
1228
+ finally {
1229
+ effectFlushRerenderDepth -= 1;
1230
+ }
1231
+ }
1232
+ function updateHostCommitDirtyState(instance) {
1233
+ instance.dirty = instance.hooks.some((slot) => slot.kind === "state" &&
1234
+ Object.hasOwn(slot, "hostCommitValue") &&
1235
+ !Object.is(slot.hostCommitValue, slot.value));
1236
+ }
1237
+ function clearHostCommitStateBaselines(runtime) {
1238
+ for (const instance of runtime.instances.values()) {
1239
+ for (const slot of instance.hooks) {
1240
+ if (slot.kind === "state") {
1241
+ delete slot.hostCommitValue;
1242
+ }
1243
+ }
1244
+ }
1245
+ }
1201
1246
  function getCacheLeaf(scope, callback, args) {
1202
1247
  let node = scope.functionCaches.get(callback);
1203
1248
  if (node === undefined) {
@@ -1233,7 +1278,7 @@ function createCacheTrieNode() {
1233
1278
  };
1234
1279
  }
1235
1280
  function getCurrentCacheScope() {
1236
- return currentCacheScope ?? getGlobalCacheScope();
1281
+ return hookRenderState.currentCacheScope ?? getGlobalCacheScope();
1237
1282
  }
1238
1283
  function getGlobalCacheScope() {
1239
1284
  return globalThis[CACHE_SCOPE_SYMBOL];
@@ -1265,25 +1310,27 @@ function cleanupInactiveInstances(runtime) {
1265
1310
  }
1266
1311
  }
1267
1312
  function cleanupInstance(instance) {
1313
+ instance.disposed = true;
1268
1314
  for (const slot of instance.hooks) {
1269
1315
  if (slot?.kind === "effect") {
1270
1316
  slot.disposed = true;
1317
+ slot.mounted = false;
1271
1318
  slot.cleanup?.();
1272
1319
  delete slot.cleanup;
1273
1320
  }
1274
1321
  }
1275
1322
  }
1276
1323
  function requireRuntime() {
1277
- if (currentRuntime === undefined) {
1324
+ if (hookRenderState.currentRuntime === undefined) {
1278
1325
  throw new Error("Hooks can only be called while rendering.");
1279
1326
  }
1280
- return currentRuntime;
1327
+ return hookRenderState.currentRuntime;
1281
1328
  }
1282
1329
  function requireInstance() {
1283
- if (currentInstance === undefined) {
1330
+ if (hookRenderState.currentInstance === undefined) {
1284
1331
  throw new Error("Hooks can only be called while rendering.");
1285
1332
  }
1286
- return currentInstance;
1333
+ return hookRenderState.currentInstance;
1287
1334
  }
1288
1335
  function areHookInputsEqual(nextDeps, previousDeps) {
1289
1336
  if (nextDeps.length !== previousDeps.length) {