@reckona/mreact-compat 0.0.171 → 0.0.173
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.
- package/dist/devtools.js +2 -0
- package/dist/devtools.js.map +1 -1
- package/dist/dom-host-rules.d.ts.map +1 -1
- package/dist/dom-host-rules.js +4 -1
- package/dist/dom-host-rules.js.map +1 -1
- package/dist/dom-props.js +2 -1
- package/dist/dom-props.js.map +1 -1
- package/dist/element.d.ts +13 -1
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +30 -2
- package/dist/element.js.map +1 -1
- package/dist/fiber-commit.js +2 -1
- package/dist/fiber-commit.js.map +1 -1
- package/dist/fiber-host.d.ts +1 -1
- package/dist/fiber-host.d.ts.map +1 -1
- package/dist/fiber-host.js +1 -1
- package/dist/fiber-host.js.map +1 -1
- package/dist/fiber.d.ts +2 -1
- package/dist/fiber.d.ts.map +1 -1
- package/dist/fiber.js +2 -0
- package/dist/fiber.js.map +1 -1
- package/dist/hooks.d.ts +9 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +127 -44
- package/dist/hooks.js.map +1 -1
- package/dist/host-reconciler.d.ts +2 -0
- package/dist/host-reconciler.d.ts.map +1 -1
- package/dist/host-reconciler.js +696 -55
- package/dist/host-reconciler.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +3 -1
- package/dist/jsx-dev-runtime.d.ts.map +1 -1
- package/dist/jsx-dev-runtime.js +3 -1
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +4 -2
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +9 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/reactive-prop-cell.d.ts +13 -0
- package/dist/reactive-prop-cell.d.ts.map +1 -0
- package/dist/reactive-prop-cell.js +36 -0
- package/dist/reactive-prop-cell.js.map +1 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +8 -5
- package/dist/root.js.map +1 -1
- package/package.json +3 -3
- package/src/devtools.ts +2 -0
- package/src/dom-host-rules.ts +4 -1
- package/src/dom-props.ts +2 -1
- package/src/element.ts +60 -3
- package/src/fiber-commit.ts +3 -1
- package/src/fiber-host.ts +2 -0
- package/src/fiber.ts +4 -0
- package/src/hooks.ts +187 -46
- package/src/host-reconciler.ts +1031 -70
- package/src/jsx-dev-runtime.ts +4 -0
- package/src/jsx-runtime.ts +16 -0
- package/src/reactive-prop-cell.ts +67 -0
- package/src/root.ts +14 -4
package/dist/hooks.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { flushPendingComputed, flushQueuedComputations, } from "@reckona/mreact-reactive-core/internal";
|
|
1
|
+
import { flushPendingComputed, flushQueuedComputations, notifySubscribers, trackSource, } from "@reckona/mreact-reactive-core/internal";
|
|
2
2
|
import { scheduleCallback } from "./fiber-scheduler.js";
|
|
3
3
|
import { removeChildIfPresent } from "./dom-children.js";
|
|
4
4
|
import { isReactCompatContext, readContextValue, useContext, withContextReadObserver, } from "./context.js";
|
|
5
|
-
import { REACTIVE_TEXT_BINDING_META } from "./element.js";
|
|
5
|
+
import { REACTIVE_STATE_BINDING_META, REACTIVE_TEXT_BINDING_META, } from "./element.js";
|
|
6
6
|
import { isThenable } from "./thenable.js";
|
|
7
7
|
const HOOK_RENDER_STATE_KEY = Symbol.for("modular.react.hook_render_state");
|
|
8
8
|
const hookRenderState = (globalThis[HOOK_RENDER_STATE_KEY] ??= {
|
|
9
9
|
currentRuntime: undefined,
|
|
10
10
|
currentInstance: undefined,
|
|
11
|
+
pendingInstance: undefined,
|
|
11
12
|
currentCacheScope: undefined,
|
|
12
13
|
hostCommitDepth: 0,
|
|
13
14
|
queuedHostCommitRerenders: new Set(),
|
|
@@ -156,6 +157,9 @@ export function createRootRuntime(rerender, options = {}) {
|
|
|
156
157
|
flushPendingEffects(this.pendingImperativeHandleEffects);
|
|
157
158
|
this.effectFlushPhase = "layout";
|
|
158
159
|
const strictLayoutEffects = flushPendingEffects(this.pendingLayoutEffects);
|
|
160
|
+
if (flushHostCommitRerenders()) {
|
|
161
|
+
dedupePendingEffects(this.pendingEffects);
|
|
162
|
+
}
|
|
159
163
|
this.effectFlushPhase = "normal";
|
|
160
164
|
const strictEffects = flushPendingEffects(this.pendingEffects);
|
|
161
165
|
this.effectFlushPhase = undefined;
|
|
@@ -258,23 +262,53 @@ export function renderWithProfiler(runtime, path, props, render) {
|
|
|
258
262
|
export function renderWithRootRuntime(runtime, path, render, owner) {
|
|
259
263
|
const previousRuntime = hookRenderState.currentRuntime;
|
|
260
264
|
const previousInstance = hookRenderState.currentInstance;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
265
|
+
const previousPending = hookRenderState.pendingInstance;
|
|
266
|
+
let existing = runtime.instances.get(path);
|
|
267
|
+
if (existing !== undefined && owner !== undefined && existing.owner !== owner) {
|
|
268
|
+
cleanupInstance(existing);
|
|
269
|
+
runtime.instances.delete(path);
|
|
270
|
+
removeInstanceKeyFromIndex(runtime, path);
|
|
271
|
+
existing = undefined;
|
|
272
|
+
}
|
|
273
|
+
// Defer instance materialization: hooks / context reads call
|
|
274
|
+
// materializeInstance() lazily. A component that touches neither never
|
|
275
|
+
// allocates or registers an instance.
|
|
276
|
+
hookRenderState.currentRuntime = runtime;
|
|
277
|
+
hookRenderState.currentInstance = undefined;
|
|
278
|
+
hookRenderState.pendingInstance = { runtime, path, owner, existing };
|
|
279
|
+
try {
|
|
280
|
+
return withContextReadObserver(recordContextDependency, render);
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
hookRenderState.currentRuntime = previousRuntime;
|
|
284
|
+
hookRenderState.currentInstance = previousInstance;
|
|
285
|
+
hookRenderState.pendingInstance = previousPending;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Materialize the deferred instance for the rendering component the first time
|
|
289
|
+
// a hook or context read needs it.
|
|
290
|
+
function materializeInstance() {
|
|
291
|
+
const pending = hookRenderState.pendingInstance;
|
|
292
|
+
if (pending === undefined) {
|
|
293
|
+
throw new Error("Hooks can only be called while rendering.");
|
|
294
|
+
}
|
|
295
|
+
const { runtime, path, owner } = pending;
|
|
296
|
+
let instance = pending.existing;
|
|
297
|
+
if (instance === undefined) {
|
|
298
|
+
instance = {
|
|
299
|
+
owner,
|
|
300
|
+
path,
|
|
301
|
+
hooks: [],
|
|
302
|
+
hookIndex: 0,
|
|
303
|
+
dirty: false,
|
|
304
|
+
devToolsHookSuppressionDepth: 0,
|
|
305
|
+
};
|
|
306
|
+
runtime.instances.set(path, instance);
|
|
307
|
+
indexInstanceKey(runtime, path);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
instance.owner = owner;
|
|
311
|
+
}
|
|
278
312
|
runtime.activeInstanceKeys?.add(path);
|
|
279
313
|
instance.hookIndex = 0;
|
|
280
314
|
instance.dirty = false;
|
|
@@ -289,17 +323,13 @@ export function renderWithRootRuntime(runtime, path, render, owner) {
|
|
|
289
323
|
delete instance.devToolsHookTypes;
|
|
290
324
|
}
|
|
291
325
|
instance.devToolsHookSuppressionDepth = 0;
|
|
292
|
-
hookRenderState.currentRuntime = runtime;
|
|
293
326
|
hookRenderState.currentInstance = instance;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
hookRenderState.currentRuntime = previousRuntime;
|
|
301
|
-
hookRenderState.currentInstance = previousInstance;
|
|
302
|
-
}
|
|
327
|
+
hookRenderState.pendingInstance = undefined;
|
|
328
|
+
return instance;
|
|
329
|
+
}
|
|
330
|
+
function recordContextDependency(context, value) {
|
|
331
|
+
const instance = hookRenderState.currentInstance ?? materializeInstance();
|
|
332
|
+
(instance.contextDependencies ??= new Map()).set(context, value);
|
|
303
333
|
}
|
|
304
334
|
export function hasChangedContextDependency(runtime, keys) {
|
|
305
335
|
for (const key of keys) {
|
|
@@ -318,18 +348,22 @@ export function hasChangedContextDependency(runtime, keys) {
|
|
|
318
348
|
export function hasContextDependency(runtime, keys) {
|
|
319
349
|
return keys.some((key) => runtime.instances.get(key)?.contextDependencies !== undefined);
|
|
320
350
|
}
|
|
351
|
+
// Shared read-only empty result so components with no registered instances
|
|
352
|
+
// (e.g. hookless rows under lazy instance materialization) don't each allocate
|
|
353
|
+
// a fresh array. Callers treat instance-key lists as read-only.
|
|
354
|
+
const EMPTY_INSTANCE_KEYS = [];
|
|
321
355
|
export function collectRuntimeInstanceKeys(runtime, prefix) {
|
|
322
356
|
const keys = runtime.instanceKeysByPrefix.get(prefix);
|
|
323
357
|
if (keys === undefined) {
|
|
324
|
-
return
|
|
358
|
+
return EMPTY_INSTANCE_KEYS;
|
|
325
359
|
}
|
|
326
|
-
|
|
360
|
+
let activeKeys;
|
|
327
361
|
for (const key of keys) {
|
|
328
362
|
if (runtime.instances.has(key)) {
|
|
329
|
-
activeKeys.push(key);
|
|
363
|
+
(activeKeys ??= []).push(key);
|
|
330
364
|
}
|
|
331
365
|
}
|
|
332
|
-
return activeKeys;
|
|
366
|
+
return activeKeys ?? EMPTY_INSTANCE_KEYS;
|
|
333
367
|
}
|
|
334
368
|
export function getDevToolsHookState(runtime, path) {
|
|
335
369
|
const instance = runtime.instances.get(path);
|
|
@@ -480,7 +514,7 @@ export function useState(initial) {
|
|
|
480
514
|
if (slot.kind !== "state") {
|
|
481
515
|
throw new Error("Hook order changed between renders.");
|
|
482
516
|
}
|
|
483
|
-
|
|
517
|
+
slot.dispatch ??= (value) => {
|
|
484
518
|
const previousValue = slot.value;
|
|
485
519
|
const nextValue = typeof value === "function"
|
|
486
520
|
? value(slot.value)
|
|
@@ -500,7 +534,15 @@ export function useState(initial) {
|
|
|
500
534
|
transitionDepth === 0 &&
|
|
501
535
|
optionsAllowDirectTextBinding(value) &&
|
|
502
536
|
updateDirectTextBinding(slot.textBinding, nextValue);
|
|
503
|
-
|
|
537
|
+
const canUseDirectStateBinding = hookRenderState.hostCommitDepth === 0 &&
|
|
538
|
+
hookRenderState.currentRuntime !== runtime &&
|
|
539
|
+
hookRenderState.currentInstance !== instance &&
|
|
540
|
+
runtime.effectFlushPhase === undefined &&
|
|
541
|
+
eventBatchDepth === 0 &&
|
|
542
|
+
transitionDepth === 0 &&
|
|
543
|
+
optionsAllowDirectTextBinding(value) &&
|
|
544
|
+
updateDirectStateBinding(slot.stateBinding, nextValue);
|
|
545
|
+
if (canUseDirectTextBinding || canUseDirectStateBinding) {
|
|
504
546
|
return;
|
|
505
547
|
}
|
|
506
548
|
if (hookRenderState.hostCommitDepth > 0) {
|
|
@@ -510,12 +552,14 @@ export function useState(initial) {
|
|
|
510
552
|
}
|
|
511
553
|
scheduleInstanceUpdate(runtime, instance, { deferSync: typeof value === "function" });
|
|
512
554
|
};
|
|
555
|
+
const setState = slot.dispatch;
|
|
513
556
|
recordDevToolsHook("useState", {
|
|
514
557
|
kind: "state",
|
|
515
558
|
value: slot.value,
|
|
516
559
|
});
|
|
517
560
|
const result = [slot.value, setState];
|
|
518
561
|
result[REACTIVE_TEXT_BINDING_META] = getStateTextBinding(slot);
|
|
562
|
+
result[REACTIVE_STATE_BINDING_META] = getStateBinding(slot);
|
|
519
563
|
return result;
|
|
520
564
|
}
|
|
521
565
|
export function subscribeReactiveTextBinding(binding, node) {
|
|
@@ -554,6 +598,18 @@ function getStateTextBinding(slot) {
|
|
|
554
598
|
slot.textBinding.value = slot.value;
|
|
555
599
|
return slot.textBinding;
|
|
556
600
|
}
|
|
601
|
+
function getStateBinding(slot) {
|
|
602
|
+
slot.stateBinding ??= {
|
|
603
|
+
value: slot.value,
|
|
604
|
+
source: { subscribers: null },
|
|
605
|
+
get() {
|
|
606
|
+
trackSource(this.source);
|
|
607
|
+
return this.value;
|
|
608
|
+
},
|
|
609
|
+
};
|
|
610
|
+
slot.stateBinding.value = slot.value;
|
|
611
|
+
return slot.stateBinding;
|
|
612
|
+
}
|
|
557
613
|
function optionsAllowDirectTextBinding(value) {
|
|
558
614
|
return typeof value !== "function";
|
|
559
615
|
}
|
|
@@ -577,6 +633,15 @@ function updateDirectTextBinding(binding, value) {
|
|
|
577
633
|
binding.value = value;
|
|
578
634
|
return updated;
|
|
579
635
|
}
|
|
636
|
+
function updateDirectStateBinding(binding, value) {
|
|
637
|
+
if (binding === undefined || binding.source.subscribers === null) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
binding.value = value;
|
|
641
|
+
notifySubscribers(binding.source);
|
|
642
|
+
flushQueuedComputations();
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
580
645
|
function isReactiveTextBinding(value) {
|
|
581
646
|
return (typeof value === "object" &&
|
|
582
647
|
value !== null &&
|
|
@@ -585,7 +650,8 @@ function isReactiveTextBinding(value) {
|
|
|
585
650
|
}
|
|
586
651
|
/** Stores reducer-managed component state and returns the current state with a dispatch function. */
|
|
587
652
|
export function useReducer(reducer, initialArg, init) {
|
|
588
|
-
const
|
|
653
|
+
const stateTuple = runWithoutDevToolsHookTracking(() => useState(() => init === undefined ? initialArg : init(initialArg)));
|
|
654
|
+
const [state, setState] = stateTuple;
|
|
589
655
|
const reducerRef = runWithoutDevToolsHookTracking(() => useRef(reducer));
|
|
590
656
|
const stateRef = runWithoutDevToolsHookTracking(() => useRef(state));
|
|
591
657
|
const dispatchRef = runWithoutDevToolsHookTracking(() => useRef(undefined));
|
|
@@ -602,7 +668,12 @@ export function useReducer(reducer, initialArg, init) {
|
|
|
602
668
|
kind: "reducer",
|
|
603
669
|
value: state,
|
|
604
670
|
});
|
|
605
|
-
|
|
671
|
+
const result = [state, dispatchRef.current];
|
|
672
|
+
const stateBinding = stateTuple[REACTIVE_STATE_BINDING_META];
|
|
673
|
+
if (stateBinding !== undefined) {
|
|
674
|
+
result[REACTIVE_STATE_BINDING_META] = stateBinding;
|
|
675
|
+
}
|
|
676
|
+
return result;
|
|
606
677
|
}
|
|
607
678
|
/** Returns a stable mutable ref object for the component instance. */
|
|
608
679
|
export function useRef(initial) {
|
|
@@ -865,6 +936,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
|
865
936
|
if (slot.kind !== "store") {
|
|
866
937
|
throw new Error("Hook order changed between renders.");
|
|
867
938
|
}
|
|
939
|
+
slot.getSnapshot = getSnapshot;
|
|
868
940
|
const isHydrationMount = runtime.idMode === "server" && slot.hasMounted !== true && getServerSnapshot !== undefined;
|
|
869
941
|
const currentSnapshot = isHydrationMount ? slot.value : getSnapshot();
|
|
870
942
|
if (!Object.is(slot.value, currentSnapshot)) {
|
|
@@ -878,7 +950,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
|
878
950
|
if (instance.disposed === true) {
|
|
879
951
|
return;
|
|
880
952
|
}
|
|
881
|
-
const nextSnapshot = getSnapshot();
|
|
953
|
+
const nextSnapshot = (slot.getSnapshot ?? getSnapshot)();
|
|
882
954
|
if (!Object.is(slot.value, nextSnapshot)) {
|
|
883
955
|
if (hookRenderState.hostCommitDepth > 0 && !Object.hasOwn(slot, "hostCommitValue")) {
|
|
884
956
|
slot.hostCommitValue = slot.value;
|
|
@@ -910,7 +982,7 @@ export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
|
|
910
982
|
return () => {
|
|
911
983
|
unsubscribe();
|
|
912
984
|
};
|
|
913
|
-
}, [subscribe
|
|
985
|
+
}, [subscribe]));
|
|
914
986
|
recordDevToolsHook("useSyncExternalStore", {
|
|
915
987
|
kind: "store",
|
|
916
988
|
value: slot.value,
|
|
@@ -1433,8 +1505,9 @@ function flushHostCommitRerenders() {
|
|
|
1433
1505
|
if (hostCommitRerenderDepth > 0 ||
|
|
1434
1506
|
hookRenderState.hostCommitDepth > 0 ||
|
|
1435
1507
|
hookRenderState.queuedHostCommitRerenders.size === 0) {
|
|
1436
|
-
return;
|
|
1508
|
+
return false;
|
|
1437
1509
|
}
|
|
1510
|
+
let didRerender = false;
|
|
1438
1511
|
hostCommitRerenderDepth += 1;
|
|
1439
1512
|
try {
|
|
1440
1513
|
for (let attempt = 0; attempt < 3 && hookRenderState.queuedHostCommitRerenders.size > 0; attempt += 1) {
|
|
@@ -1444,6 +1517,7 @@ function flushHostCommitRerenders() {
|
|
|
1444
1517
|
const hasDirtyInstance = Array.from(runtime.instances.values()).some((instance) => instance.dirty);
|
|
1445
1518
|
clearHostCommitStateBaselines(runtime);
|
|
1446
1519
|
if (hasDirtyInstance) {
|
|
1520
|
+
didRerender = true;
|
|
1447
1521
|
runtime.rerender("sync");
|
|
1448
1522
|
}
|
|
1449
1523
|
}
|
|
@@ -1453,6 +1527,18 @@ function flushHostCommitRerenders() {
|
|
|
1453
1527
|
finally {
|
|
1454
1528
|
hostCommitRerenderDepth -= 1;
|
|
1455
1529
|
}
|
|
1530
|
+
return didRerender;
|
|
1531
|
+
}
|
|
1532
|
+
function dedupePendingEffects(queue) {
|
|
1533
|
+
if (queue.length < 2) {
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
const latestBySlot = new Map();
|
|
1537
|
+
for (const effect of queue) {
|
|
1538
|
+
latestBySlot.set(effect.slot, effect);
|
|
1539
|
+
}
|
|
1540
|
+
queue.length = 0;
|
|
1541
|
+
queue.push(...latestBySlot.values());
|
|
1456
1542
|
}
|
|
1457
1543
|
function flushEffectFlushRerenders() {
|
|
1458
1544
|
if (effectFlushRerenderDepth > 0 ||
|
|
@@ -1616,10 +1702,7 @@ function requireRuntime() {
|
|
|
1616
1702
|
return hookRenderState.currentRuntime;
|
|
1617
1703
|
}
|
|
1618
1704
|
function requireInstance() {
|
|
1619
|
-
|
|
1620
|
-
throw new Error("Hooks can only be called while rendering.");
|
|
1621
|
-
}
|
|
1622
|
-
return hookRenderState.currentInstance;
|
|
1705
|
+
return hookRenderState.currentInstance ?? materializeInstance();
|
|
1623
1706
|
}
|
|
1624
1707
|
function areHookInputsEqual(nextDeps, previousDeps) {
|
|
1625
1708
|
if (nextDeps.length !== previousDeps.length) {
|