@reckona/mreact-compat 0.0.170 → 0.0.172
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 +7 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +122 -41
- package/dist/hooks.js.map +1 -1
- package/dist/host-reconciler.d.ts +1 -0
- package/dist/host-reconciler.d.ts.map +1 -1
- package/dist/host-reconciler.js +659 -50
- 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 +3 -1
- 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 +1 -0
- package/src/fiber.ts +4 -0
- package/src/hooks.ts +173 -41
- package/src/host-reconciler.ts +979 -64
- 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 +3 -0
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);
|
|
@@ -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) {
|
|
@@ -516,6 +558,7 @@ export function useState(initial) {
|
|
|
516
558
|
});
|
|
517
559
|
const result = [slot.value, setState];
|
|
518
560
|
result[REACTIVE_TEXT_BINDING_META] = getStateTextBinding(slot);
|
|
561
|
+
result[REACTIVE_STATE_BINDING_META] = getStateBinding(slot);
|
|
519
562
|
return result;
|
|
520
563
|
}
|
|
521
564
|
export function subscribeReactiveTextBinding(binding, node) {
|
|
@@ -554,6 +597,18 @@ function getStateTextBinding(slot) {
|
|
|
554
597
|
slot.textBinding.value = slot.value;
|
|
555
598
|
return slot.textBinding;
|
|
556
599
|
}
|
|
600
|
+
function getStateBinding(slot) {
|
|
601
|
+
slot.stateBinding ??= {
|
|
602
|
+
value: slot.value,
|
|
603
|
+
source: { subscribers: null },
|
|
604
|
+
get() {
|
|
605
|
+
trackSource(this.source);
|
|
606
|
+
return this.value;
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
slot.stateBinding.value = slot.value;
|
|
610
|
+
return slot.stateBinding;
|
|
611
|
+
}
|
|
557
612
|
function optionsAllowDirectTextBinding(value) {
|
|
558
613
|
return typeof value !== "function";
|
|
559
614
|
}
|
|
@@ -577,6 +632,15 @@ function updateDirectTextBinding(binding, value) {
|
|
|
577
632
|
binding.value = value;
|
|
578
633
|
return updated;
|
|
579
634
|
}
|
|
635
|
+
function updateDirectStateBinding(binding, value) {
|
|
636
|
+
if (binding === undefined || binding.source.subscribers === null) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
binding.value = value;
|
|
640
|
+
notifySubscribers(binding.source);
|
|
641
|
+
flushQueuedComputations();
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
580
644
|
function isReactiveTextBinding(value) {
|
|
581
645
|
return (typeof value === "object" &&
|
|
582
646
|
value !== null &&
|
|
@@ -585,7 +649,8 @@ function isReactiveTextBinding(value) {
|
|
|
585
649
|
}
|
|
586
650
|
/** Stores reducer-managed component state and returns the current state with a dispatch function. */
|
|
587
651
|
export function useReducer(reducer, initialArg, init) {
|
|
588
|
-
const
|
|
652
|
+
const stateTuple = runWithoutDevToolsHookTracking(() => useState(() => init === undefined ? initialArg : init(initialArg)));
|
|
653
|
+
const [state, setState] = stateTuple;
|
|
589
654
|
const reducerRef = runWithoutDevToolsHookTracking(() => useRef(reducer));
|
|
590
655
|
const stateRef = runWithoutDevToolsHookTracking(() => useRef(state));
|
|
591
656
|
const dispatchRef = runWithoutDevToolsHookTracking(() => useRef(undefined));
|
|
@@ -602,7 +667,12 @@ export function useReducer(reducer, initialArg, init) {
|
|
|
602
667
|
kind: "reducer",
|
|
603
668
|
value: state,
|
|
604
669
|
});
|
|
605
|
-
|
|
670
|
+
const result = [state, dispatchRef.current];
|
|
671
|
+
const stateBinding = stateTuple[REACTIVE_STATE_BINDING_META];
|
|
672
|
+
if (stateBinding !== undefined) {
|
|
673
|
+
result[REACTIVE_STATE_BINDING_META] = stateBinding;
|
|
674
|
+
}
|
|
675
|
+
return result;
|
|
606
676
|
}
|
|
607
677
|
/** Returns a stable mutable ref object for the component instance. */
|
|
608
678
|
export function useRef(initial) {
|
|
@@ -1433,8 +1503,9 @@ function flushHostCommitRerenders() {
|
|
|
1433
1503
|
if (hostCommitRerenderDepth > 0 ||
|
|
1434
1504
|
hookRenderState.hostCommitDepth > 0 ||
|
|
1435
1505
|
hookRenderState.queuedHostCommitRerenders.size === 0) {
|
|
1436
|
-
return;
|
|
1506
|
+
return false;
|
|
1437
1507
|
}
|
|
1508
|
+
let didRerender = false;
|
|
1438
1509
|
hostCommitRerenderDepth += 1;
|
|
1439
1510
|
try {
|
|
1440
1511
|
for (let attempt = 0; attempt < 3 && hookRenderState.queuedHostCommitRerenders.size > 0; attempt += 1) {
|
|
@@ -1444,6 +1515,7 @@ function flushHostCommitRerenders() {
|
|
|
1444
1515
|
const hasDirtyInstance = Array.from(runtime.instances.values()).some((instance) => instance.dirty);
|
|
1445
1516
|
clearHostCommitStateBaselines(runtime);
|
|
1446
1517
|
if (hasDirtyInstance) {
|
|
1518
|
+
didRerender = true;
|
|
1447
1519
|
runtime.rerender("sync");
|
|
1448
1520
|
}
|
|
1449
1521
|
}
|
|
@@ -1453,6 +1525,18 @@ function flushHostCommitRerenders() {
|
|
|
1453
1525
|
finally {
|
|
1454
1526
|
hostCommitRerenderDepth -= 1;
|
|
1455
1527
|
}
|
|
1528
|
+
return didRerender;
|
|
1529
|
+
}
|
|
1530
|
+
function dedupePendingEffects(queue) {
|
|
1531
|
+
if (queue.length < 2) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
const latestBySlot = new Map();
|
|
1535
|
+
for (const effect of queue) {
|
|
1536
|
+
latestBySlot.set(effect.slot, effect);
|
|
1537
|
+
}
|
|
1538
|
+
queue.length = 0;
|
|
1539
|
+
queue.push(...latestBySlot.values());
|
|
1456
1540
|
}
|
|
1457
1541
|
function flushEffectFlushRerenders() {
|
|
1458
1542
|
if (effectFlushRerenderDepth > 0 ||
|
|
@@ -1616,10 +1700,7 @@ function requireRuntime() {
|
|
|
1616
1700
|
return hookRenderState.currentRuntime;
|
|
1617
1701
|
}
|
|
1618
1702
|
function requireInstance() {
|
|
1619
|
-
|
|
1620
|
-
throw new Error("Hooks can only be called while rendering.");
|
|
1621
|
-
}
|
|
1622
|
-
return hookRenderState.currentInstance;
|
|
1703
|
+
return hookRenderState.currentInstance ?? materializeInstance();
|
|
1623
1704
|
}
|
|
1624
1705
|
function areHookInputsEqual(nextDeps, previousDeps) {
|
|
1625
1706
|
if (nextDeps.length !== previousDeps.length) {
|