@mmstack/primitives 19.5.2 → 19.7.0
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/README.md +30 -3
- package/fesm2022/mmstack-primitives.mjs +217 -114
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/lib/concurrent/start-transition.d.ts +3 -2
- package/lib/concurrent/suspense-boundary.d.ts +1 -2
- package/lib/concurrent/transaction.d.ts +2 -1
- package/lib/concurrent/transition-scope.d.ts +9 -0
- package/lib/sensors/pointer-drag.d.ts +28 -1
- package/lib/store/fork-store.d.ts +8 -18
- package/lib/store/internals.d.ts +8 -11
- package/lib/store/public_api.d.ts +5 -1
- package/lib/store/store.d.ts +39 -10
- package/package.json +1 -1
|
@@ -634,6 +634,38 @@ function provideForwardingTransitionScope() {
|
|
|
634
634
|
function getTransitionScope(injector) {
|
|
635
635
|
return injector.get(TRANSITION_SCOPE, null);
|
|
636
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* @internal Transaction-attributed pending for `startTransition`/`startTransaction`: like
|
|
639
|
+
* `scope.pending`, but loads already in flight when the tracker is created are NOT attributed —
|
|
640
|
+
* a pre-existing background load can neither settle the transaction early nor block its settle
|
|
641
|
+
* forever. A pre-existing flight is excluded only until it first settles; a later re-trigger of
|
|
642
|
+
* the same resource (e.g. the transaction's write changed its request) counts as the
|
|
643
|
+
* transaction's own work.
|
|
644
|
+
*/
|
|
645
|
+
function createAttributedPending(scope) {
|
|
646
|
+
const isInFlight = (ref) => {
|
|
647
|
+
const s = untracked(ref.status);
|
|
648
|
+
return s === ResourceStatus.Loading || s === ResourceStatus.Reloading;
|
|
649
|
+
};
|
|
650
|
+
const preexisting = new Set(untracked(scope.resources).filter(isInFlight));
|
|
651
|
+
return computed(() => {
|
|
652
|
+
let pending = false;
|
|
653
|
+
for (const ref of scope.resources()) {
|
|
654
|
+
const s = ref.status();
|
|
655
|
+
const loading = s === ResourceStatus.Loading || s === ResourceStatus.Reloading;
|
|
656
|
+
if (preexisting.has(ref)) {
|
|
657
|
+
// deletes are monotonic, so this stays sound under re-computation
|
|
658
|
+
if (loading)
|
|
659
|
+
continue;
|
|
660
|
+
preexisting.delete(ref);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (loading)
|
|
664
|
+
pending = true;
|
|
665
|
+
}
|
|
666
|
+
return pending;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
637
669
|
/**
|
|
638
670
|
* Returns a register function bound to the nearest transition scope: it adds a resource
|
|
639
671
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
@@ -671,38 +703,43 @@ function registerResource(res, opt) {
|
|
|
671
703
|
function injectStartTransition() {
|
|
672
704
|
const scope = injectTransitionScope();
|
|
673
705
|
const injector = inject(Injector);
|
|
706
|
+
const destroyRef = inject(DestroyRef);
|
|
674
707
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
675
708
|
return (fn) => {
|
|
709
|
+
// attributed: loads already in flight when the transition starts are not ours —
|
|
710
|
+
// they can neither settle this transition early nor block it forever
|
|
711
|
+
const pending = createAttributedPending(scope);
|
|
676
712
|
untracked(fn);
|
|
677
713
|
let sawPending = false;
|
|
678
714
|
const done = new Promise((resolve) => {
|
|
715
|
+
const settle = () => {
|
|
716
|
+
releaseDestroy();
|
|
717
|
+
watcher.destroy();
|
|
718
|
+
resolve();
|
|
719
|
+
};
|
|
679
720
|
const watcher = effect(() => {
|
|
680
|
-
const p =
|
|
721
|
+
const p = pending();
|
|
681
722
|
if (p)
|
|
682
723
|
sawPending = true;
|
|
683
724
|
// settle: requests went in flight and then drained
|
|
684
|
-
if (sawPending && !p)
|
|
685
|
-
|
|
686
|
-
resolve();
|
|
687
|
-
}
|
|
725
|
+
if (sawPending && !p)
|
|
726
|
+
settle();
|
|
688
727
|
}, { injector });
|
|
728
|
+
// a destroy mid-flight kills the watcher — resolve so awaiters never hang
|
|
729
|
+
const releaseDestroy = destroyRef.onDestroy(settle);
|
|
689
730
|
if (onServer) {
|
|
690
|
-
if (!untracked(
|
|
691
|
-
|
|
692
|
-
resolve();
|
|
693
|
-
}
|
|
731
|
+
if (!untracked(pending))
|
|
732
|
+
settle();
|
|
694
733
|
return;
|
|
695
734
|
}
|
|
696
735
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
697
736
|
// if nothing ever went in flight, the transition is already complete.
|
|
698
737
|
afterNextRender(() => {
|
|
699
|
-
if (!sawPending && !untracked(
|
|
700
|
-
|
|
701
|
-
resolve();
|
|
702
|
-
}
|
|
738
|
+
if (!sawPending && !untracked(pending))
|
|
739
|
+
settle();
|
|
703
740
|
}, { injector });
|
|
704
741
|
});
|
|
705
|
-
return { pending
|
|
742
|
+
return { pending, done };
|
|
706
743
|
};
|
|
707
744
|
}
|
|
708
745
|
|
|
@@ -773,8 +810,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.19", ngImpo
|
|
|
773
810
|
}] });
|
|
774
811
|
/**
|
|
775
812
|
* Unscoped suspense boundary — **reads the ambient scope** instead of providing one. For cases where
|
|
776
|
-
* the resources to coordinate are registered *above* the boundary
|
|
777
|
-
* manifests/connectors register at a higher injector), so the boundary observes that outer scope
|
|
813
|
+
* the resources to coordinate are registered *above* the boundary so the boundary observes that outer scope
|
|
778
814
|
* rather than opening a fresh one. Pair with a `provideTransitionScope()` (or another boundary) in an
|
|
779
815
|
* ancestor.
|
|
780
816
|
*/
|
|
@@ -839,9 +875,13 @@ function runInTransaction(txn, fn) {
|
|
|
839
875
|
function injectStartTransaction() {
|
|
840
876
|
const scope = injectTransitionScope();
|
|
841
877
|
const injector = inject(Injector);
|
|
878
|
+
const destroyRef = inject(DestroyRef);
|
|
842
879
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
843
880
|
return (fn) => {
|
|
844
881
|
const txn = createTransaction();
|
|
882
|
+
// attributed: loads already in flight when the transaction starts are not ours —
|
|
883
|
+
// they can neither commit this transaction early nor block its settle forever
|
|
884
|
+
const pending = createAttributedPending(scope);
|
|
845
885
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
846
886
|
scope.beginHold();
|
|
847
887
|
let finished = false;
|
|
@@ -858,6 +898,7 @@ function injectStartTransaction() {
|
|
|
858
898
|
if (finished)
|
|
859
899
|
return;
|
|
860
900
|
finished = true;
|
|
901
|
+
releaseDestroy();
|
|
861
902
|
watcher?.destroy();
|
|
862
903
|
if (restore)
|
|
863
904
|
txn.restore();
|
|
@@ -866,6 +907,10 @@ function injectStartTransaction() {
|
|
|
866
907
|
scope.endHold();
|
|
867
908
|
resolveDone();
|
|
868
909
|
};
|
|
910
|
+
// The scope may outlive the calling context (a component transacting on an ancestor
|
|
911
|
+
// boundary): a destroy mid-flight kills the settle watcher, so without this the hold
|
|
912
|
+
// would leak and freeze the surviving scope forever. Keep the writes — they landed live.
|
|
913
|
+
const releaseDestroy = destroyRef.onDestroy(() => finish(false));
|
|
869
914
|
try {
|
|
870
915
|
runInTransaction(txn, fn);
|
|
871
916
|
}
|
|
@@ -875,25 +920,25 @@ function injectStartTransaction() {
|
|
|
875
920
|
}
|
|
876
921
|
let sawPending = false;
|
|
877
922
|
watcher = effect(() => {
|
|
878
|
-
const p =
|
|
923
|
+
const p = pending();
|
|
879
924
|
if (p)
|
|
880
925
|
sawPending = true;
|
|
881
926
|
if (sawPending && !p)
|
|
882
927
|
finish(false);
|
|
883
928
|
}, { injector });
|
|
884
929
|
if (onServer) {
|
|
885
|
-
if (!untracked(
|
|
930
|
+
if (!untracked(pending))
|
|
886
931
|
finish(false);
|
|
887
932
|
}
|
|
888
933
|
else {
|
|
889
934
|
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
890
935
|
afterNextRender(() => {
|
|
891
|
-
if (!sawPending && !untracked(
|
|
936
|
+
if (!sawPending && !untracked(pending))
|
|
892
937
|
finish(false);
|
|
893
938
|
}, { injector });
|
|
894
939
|
}
|
|
895
940
|
return {
|
|
896
|
-
pending
|
|
941
|
+
pending,
|
|
897
942
|
done,
|
|
898
943
|
abort: () => finish(true),
|
|
899
944
|
};
|
|
@@ -2905,13 +2950,21 @@ const IDLE = {
|
|
|
2905
2950
|
pointerId: null,
|
|
2906
2951
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2907
2952
|
button: -1,
|
|
2953
|
+
pointerType: '',
|
|
2954
|
+
origin: null,
|
|
2955
|
+
cancelled: false,
|
|
2908
2956
|
};
|
|
2957
|
+
/** Terminal state of an aborted gesture — same idle shape, `cancelled: true`. */
|
|
2958
|
+
const CANCELLED = { ...IDLE, cancelled: true };
|
|
2909
2959
|
function stateEqual(a, b) {
|
|
2910
2960
|
return (a.active === b.active &&
|
|
2961
|
+
a.cancelled === b.cancelled &&
|
|
2911
2962
|
a.pointerId === b.pointerId &&
|
|
2912
2963
|
a.current.x === b.current.x &&
|
|
2913
2964
|
a.current.y === b.current.y &&
|
|
2914
2965
|
a.button === b.button &&
|
|
2966
|
+
a.pointerType === b.pointerType &&
|
|
2967
|
+
a.origin === b.origin &&
|
|
2915
2968
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2916
2969
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2917
2970
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2945,7 +2998,7 @@ function createPointerDrag(opt) {
|
|
|
2945
2998
|
return base;
|
|
2946
2999
|
}
|
|
2947
3000
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2948
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
3001
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2949
3002
|
const resolve = (t) => {
|
|
2950
3003
|
if (!t)
|
|
2951
3004
|
return null;
|
|
@@ -2966,9 +3019,12 @@ function createPointerDrag(opt) {
|
|
|
2966
3019
|
equal: stateEqual,
|
|
2967
3020
|
debugName,
|
|
2968
3021
|
});
|
|
3022
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2969
3023
|
let startPoint = { x: 0, y: 0 };
|
|
2970
3024
|
let activePointerId = null;
|
|
2971
3025
|
let activeButton = -1;
|
|
3026
|
+
let activePointerType = '';
|
|
3027
|
+
let activeOrigin = null;
|
|
2972
3028
|
let activated = false;
|
|
2973
3029
|
let gesture = null;
|
|
2974
3030
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2980,22 +3036,24 @@ function createPointerDrag(opt) {
|
|
|
2980
3036
|
ctrl: e.ctrlKey,
|
|
2981
3037
|
meta: e.metaKey,
|
|
2982
3038
|
});
|
|
2983
|
-
const end = () => {
|
|
3039
|
+
const end = (cancelled = false) => {
|
|
2984
3040
|
gesture?.abort();
|
|
2985
3041
|
gesture = null;
|
|
2986
3042
|
activePointerId = null;
|
|
2987
3043
|
activeButton = -1;
|
|
3044
|
+
activePointerType = '';
|
|
3045
|
+
activeOrigin = null;
|
|
2988
3046
|
activated = false;
|
|
2989
|
-
state.set(IDLE);
|
|
2990
|
-
state.flush(); // terminal transition: reflect
|
|
3047
|
+
state.set(cancelled ? CANCELLED : IDLE);
|
|
3048
|
+
state.flush(); // terminal transition: reflect idle now, not on the trailing edge
|
|
2991
3049
|
};
|
|
2992
3050
|
const onMove = (e) => {
|
|
2993
3051
|
if (e.pointerId !== activePointerId)
|
|
2994
3052
|
return;
|
|
2995
3053
|
const current = coord(e);
|
|
2996
3054
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
2997
|
-
if (!activated &&
|
|
2998
|
-
activated = true;
|
|
3055
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3056
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
2999
3057
|
}
|
|
3000
3058
|
state.set({
|
|
3001
3059
|
active: activated,
|
|
@@ -3005,6 +3063,9 @@ function createPointerDrag(opt) {
|
|
|
3005
3063
|
pointerId: activePointerId,
|
|
3006
3064
|
modifiers: mods(e),
|
|
3007
3065
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3066
|
+
pointerType: activePointerType,
|
|
3067
|
+
origin: activeOrigin,
|
|
3068
|
+
cancelled: false,
|
|
3008
3069
|
});
|
|
3009
3070
|
};
|
|
3010
3071
|
const onUp = (e) => {
|
|
@@ -3013,22 +3074,28 @@ function createPointerDrag(opt) {
|
|
|
3013
3074
|
};
|
|
3014
3075
|
const onCancel = (e) => {
|
|
3015
3076
|
if (e.pointerId === activePointerId)
|
|
3016
|
-
end();
|
|
3077
|
+
end(true);
|
|
3017
3078
|
};
|
|
3018
3079
|
const onKey = (e) => {
|
|
3019
3080
|
if (e.key === 'Escape' && activePointerId !== null)
|
|
3020
|
-
end();
|
|
3081
|
+
end(true);
|
|
3021
3082
|
};
|
|
3022
3083
|
const onDown = (el) => (e) => {
|
|
3023
3084
|
if (activePointerId !== null)
|
|
3024
3085
|
return;
|
|
3025
3086
|
if (!buttons.includes(e.button))
|
|
3026
3087
|
return;
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3088
|
+
const matched = handleSelector
|
|
3089
|
+
? e.target?.closest?.(handleSelector)
|
|
3090
|
+
: el;
|
|
3091
|
+
if (!matched)
|
|
3092
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3093
|
+
if (stopPropagation)
|
|
3094
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3030
3095
|
activePointerId = e.pointerId;
|
|
3031
3096
|
activeButton = e.button;
|
|
3097
|
+
activePointerType = e.pointerType;
|
|
3098
|
+
activeOrigin = matched;
|
|
3032
3099
|
activated = false;
|
|
3033
3100
|
startPoint = coord(e);
|
|
3034
3101
|
try {
|
|
@@ -3054,6 +3121,9 @@ function createPointerDrag(opt) {
|
|
|
3054
3121
|
pointerId: e.pointerId,
|
|
3055
3122
|
modifiers: mods(e),
|
|
3056
3123
|
button: e.button,
|
|
3124
|
+
pointerType: activePointerType,
|
|
3125
|
+
origin: activeOrigin,
|
|
3126
|
+
cancelled: false,
|
|
3057
3127
|
});
|
|
3058
3128
|
};
|
|
3059
3129
|
const attach = (el) => {
|
|
@@ -3063,7 +3133,7 @@ function createPointerDrag(opt) {
|
|
|
3063
3133
|
});
|
|
3064
3134
|
return () => {
|
|
3065
3135
|
controller.abort();
|
|
3066
|
-
end();
|
|
3136
|
+
end(true); // teardown mid-gesture is an abort, not a drop
|
|
3067
3137
|
};
|
|
3068
3138
|
};
|
|
3069
3139
|
if (isSignal(target)) {
|
|
@@ -3081,7 +3151,7 @@ function createPointerDrag(opt) {
|
|
|
3081
3151
|
}
|
|
3082
3152
|
const base = state.asReadonly();
|
|
3083
3153
|
base.unthrottled = state.original;
|
|
3084
|
-
base.cancel = end;
|
|
3154
|
+
base.cancel = () => end(true);
|
|
3085
3155
|
return base;
|
|
3086
3156
|
}
|
|
3087
3157
|
|
|
@@ -3432,17 +3502,14 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3432
3502
|
|
|
3433
3503
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3434
3504
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3505
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3506
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3435
3507
|
/**
|
|
3436
3508
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3437
3509
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3438
3510
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3439
3511
|
*/
|
|
3440
3512
|
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3441
|
-
/**
|
|
3442
|
-
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3443
|
-
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3444
|
-
*/
|
|
3445
|
-
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3446
3513
|
const SIGNAL_FN_PROP = new Set([
|
|
3447
3514
|
'set',
|
|
3448
3515
|
'update',
|
|
@@ -3450,21 +3517,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3450
3517
|
'inline',
|
|
3451
3518
|
'asReadonly',
|
|
3452
3519
|
]);
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
storeCache.delete(prop);
|
|
3520
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3521
|
+
providedIn: 'root',
|
|
3522
|
+
factory: () => new WeakMap(),
|
|
3523
|
+
});
|
|
3524
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3525
|
+
providedIn: 'root',
|
|
3526
|
+
factory: () => {
|
|
3527
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3528
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3529
|
+
const store = cache.get(target);
|
|
3530
|
+
if (store)
|
|
3531
|
+
store.delete(prop);
|
|
3532
|
+
});
|
|
3533
|
+
},
|
|
3468
3534
|
});
|
|
3469
3535
|
/**
|
|
3470
3536
|
* @internal
|
|
@@ -3658,11 +3724,11 @@ function isLeaf(value) {
|
|
|
3658
3724
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3659
3725
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3660
3726
|
*/
|
|
3661
|
-
function getCachedChild(target, prop, build) {
|
|
3662
|
-
let storeCache =
|
|
3727
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3728
|
+
let storeCache = cache.get(target);
|
|
3663
3729
|
if (!storeCache) {
|
|
3664
3730
|
storeCache = new Map();
|
|
3665
|
-
|
|
3731
|
+
cache.set(target, storeCache);
|
|
3666
3732
|
}
|
|
3667
3733
|
const cachedRef = storeCache.get(prop);
|
|
3668
3734
|
if (cachedRef) {
|
|
@@ -3670,42 +3736,47 @@ function getCachedChild(target, prop, build) {
|
|
|
3670
3736
|
if (cached)
|
|
3671
3737
|
return cached;
|
|
3672
3738
|
storeCache.delete(prop);
|
|
3673
|
-
|
|
3739
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3674
3740
|
}
|
|
3675
3741
|
const proxy = build();
|
|
3676
3742
|
const ref = new WeakRef(proxy);
|
|
3677
3743
|
storeCache.set(prop, ref);
|
|
3678
|
-
|
|
3744
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3679
3745
|
return proxy;
|
|
3680
3746
|
}
|
|
3747
|
+
/**
|
|
3748
|
+
* @internal Whether a mutable parent's child value must always re-notify: in-place mutation
|
|
3749
|
+
* keeps an object child's reference stable, so `Object.is` would swallow the change. Decided
|
|
3750
|
+
* per-VALUE (not snapshotted at build) so a union child that becomes an object later still
|
|
3751
|
+
* propagates parent-level mutations.
|
|
3752
|
+
*/
|
|
3753
|
+
function mutableChildEqual(a, b) {
|
|
3754
|
+
if (typeof a === 'object' && a !== null)
|
|
3755
|
+
return false;
|
|
3756
|
+
return Object.is(a, b);
|
|
3757
|
+
}
|
|
3681
3758
|
/**
|
|
3682
3759
|
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3683
|
-
*
|
|
3684
|
-
*
|
|
3685
|
-
* is constructed.
|
|
3760
|
+
* Both the read (`v?.[prop]`) and the write (`createFallbackOnChange` copies by the container's
|
|
3761
|
+
* LIVE shape) are shape-adaptive, so a child cached before an array↔record↔null union flip stays
|
|
3762
|
+
* correct after it. The only place a child node is constructed — shared by every container kind.
|
|
3686
3763
|
*/
|
|
3687
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3764
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3688
3765
|
const value = untracked(target);
|
|
3689
|
-
const
|
|
3690
|
-
const valueIsArray = Array.isArray(value);
|
|
3691
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3766
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3692
3767
|
const vivifyFn = createVivify(nodeVivify);
|
|
3693
|
-
const equalFn = (
|
|
3694
|
-
|
|
3695
|
-
typeof value[prop] === 'object'
|
|
3696
|
-
? () => false
|
|
3768
|
+
const equalFn = isMutableSource && (isRecord(value) || Array.isArray(value))
|
|
3769
|
+
? mutableChildEqual
|
|
3697
3770
|
: undefined;
|
|
3698
|
-
const computation =
|
|
3699
|
-
|
|
3700
|
-
:
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
equal: equalFn,
|
|
3704
|
-
});
|
|
3771
|
+
const computation = derived(target, {
|
|
3772
|
+
from: (v) => v?.[prop],
|
|
3773
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3774
|
+
equal: equalFn,
|
|
3775
|
+
});
|
|
3705
3776
|
const childSample = untracked(computation);
|
|
3706
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3707
|
-
const proxy = toStore(computation,
|
|
3708
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3777
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3778
|
+
const proxy = toStore(computation, options);
|
|
3779
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3709
3780
|
return proxy;
|
|
3710
3781
|
}
|
|
3711
3782
|
/**
|
|
@@ -3723,7 +3794,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3723
3794
|
* const state = store({ user: { name: 'John' } });
|
|
3724
3795
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3725
3796
|
*/
|
|
3726
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3797
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3727
3798
|
if (isStore(source))
|
|
3728
3799
|
return source;
|
|
3729
3800
|
if (!injector)
|
|
@@ -3743,6 +3814,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3743
3814
|
return 'record';
|
|
3744
3815
|
return 'primitive';
|
|
3745
3816
|
});
|
|
3817
|
+
const STORE_OPTIONS = {
|
|
3818
|
+
injector,
|
|
3819
|
+
vivify,
|
|
3820
|
+
noUnionLeaves,
|
|
3821
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3822
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3823
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3824
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3825
|
+
},
|
|
3826
|
+
};
|
|
3746
3827
|
// built lazily so non-array nodes never allocate it
|
|
3747
3828
|
let length;
|
|
3748
3829
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3796,21 +3877,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3796
3877
|
return { enumerable: true, configurable: true };
|
|
3797
3878
|
},
|
|
3798
3879
|
get(target, prop, receiver) {
|
|
3799
|
-
if (prop ===
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3880
|
+
if (typeof prop === 'symbol') {
|
|
3881
|
+
if (prop === IS_STORE)
|
|
3882
|
+
return true;
|
|
3883
|
+
if (prop === STORE_KIND)
|
|
3884
|
+
return isMutableSource
|
|
3885
|
+
? 'mutable'
|
|
3886
|
+
: isWritableSource
|
|
3887
|
+
? 'writable'
|
|
3888
|
+
: 'readonly';
|
|
3889
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3890
|
+
return STORE_OPTIONS;
|
|
3891
|
+
}
|
|
3809
3892
|
if (prop === 'asReadonlyStore')
|
|
3810
3893
|
return () => {
|
|
3811
3894
|
if (!isWritableSource)
|
|
3812
3895
|
return s;
|
|
3813
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3896
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3814
3897
|
};
|
|
3815
3898
|
const k = untracked(kind);
|
|
3816
3899
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3818,7 +3901,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3818
3901
|
? 'mutable'
|
|
3819
3902
|
: isWritableSource
|
|
3820
3903
|
? 'writable'
|
|
3821
|
-
: 'readonly',
|
|
3904
|
+
: 'readonly', STORE_OPTIONS);
|
|
3822
3905
|
if (k === 'array') {
|
|
3823
3906
|
if (prop === 'length')
|
|
3824
3907
|
return arrayLength();
|
|
@@ -3835,7 +3918,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3835
3918
|
return target[prop];
|
|
3836
3919
|
if (k === 'array' && !isIndexProp(prop))
|
|
3837
3920
|
return Reflect.get(target, prop, receiver);
|
|
3838
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3921
|
+
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource, STORE_OPTIONS), STORE_OPTIONS[STORE_SHARED_GLOBALS].cache, STORE_OPTIONS[STORE_SHARED_GLOBALS].registry);
|
|
3839
3922
|
},
|
|
3840
3923
|
});
|
|
3841
3924
|
return s;
|
|
@@ -3849,14 +3932,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3849
3932
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3850
3933
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3851
3934
|
*/
|
|
3852
|
-
function scopedStore(parent, seed, kind,
|
|
3935
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3853
3936
|
const local = isSignal(seed)
|
|
3854
|
-
? toStore(seed,
|
|
3937
|
+
? toStore(seed, options)
|
|
3855
3938
|
: kind === 'mutable'
|
|
3856
|
-
? mutableStore(seed,
|
|
3939
|
+
? mutableStore(seed, options)
|
|
3857
3940
|
: kind === 'readonly'
|
|
3858
|
-
? store(seed,
|
|
3859
|
-
: store(seed,
|
|
3941
|
+
? store(seed, options).asReadonlyStore()
|
|
3942
|
+
: store(seed, options);
|
|
3860
3943
|
const localValue = () => untracked(local);
|
|
3861
3944
|
const parentValue = () => untracked(parent);
|
|
3862
3945
|
const view = computed(() => ({
|
|
@@ -3887,18 +3970,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3887
3970
|
}
|
|
3888
3971
|
const scope = new Proxy(base, {
|
|
3889
3972
|
get(target, prop) {
|
|
3890
|
-
if (prop ===
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3973
|
+
if (typeof prop === 'symbol') {
|
|
3974
|
+
if (prop === IS_STORE)
|
|
3975
|
+
return true;
|
|
3976
|
+
if (prop === STORE_KIND)
|
|
3977
|
+
return kind;
|
|
3978
|
+
if (prop === SCOPE_PARENT)
|
|
3979
|
+
return parent;
|
|
3980
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3981
|
+
return options;
|
|
3982
|
+
}
|
|
3898
3983
|
if (prop === 'extend')
|
|
3899
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3984
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3900
3985
|
if (prop === 'asReadonlyStore')
|
|
3901
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3986
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3902
3987
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3903
3988
|
return target[prop];
|
|
3904
3989
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3944,22 +4029,34 @@ function storeKind(s) {
|
|
|
3944
4029
|
* scoped.count.set(1); // writes through to base
|
|
3945
4030
|
* scoped.label.set('x'); // stays local
|
|
3946
4031
|
*/
|
|
3947
|
-
function extendStore(store, source,
|
|
3948
|
-
|
|
4032
|
+
function extendStore(store, source, options) {
|
|
4033
|
+
const opt = {
|
|
4034
|
+
...store[STORE_SHARED_OPTIONS],
|
|
4035
|
+
...options,
|
|
4036
|
+
};
|
|
4037
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3949
4038
|
}
|
|
3950
4039
|
/**
|
|
3951
4040
|
* Creates a WritableSignalStore from a value.
|
|
3952
4041
|
* @see {@link toStore}
|
|
3953
4042
|
*/
|
|
3954
4043
|
function store(value, opt) {
|
|
3955
|
-
return toStore(signal(value, opt),
|
|
4044
|
+
return toStore(signal(value, opt), {
|
|
4045
|
+
vivify: false,
|
|
4046
|
+
noUnionLeaves: false,
|
|
4047
|
+
...opt,
|
|
4048
|
+
});
|
|
3956
4049
|
}
|
|
3957
4050
|
/**
|
|
3958
4051
|
* Creates a MutableSignalStore from a value.
|
|
3959
4052
|
* @see {@link toStore}
|
|
3960
4053
|
*/
|
|
3961
4054
|
function mutableStore(value, opt) {
|
|
3962
|
-
return toStore(mutable(value, opt),
|
|
4055
|
+
return toStore(mutable(value, opt), {
|
|
4056
|
+
vivify: false,
|
|
4057
|
+
noUnionLeaves: false,
|
|
4058
|
+
...opt,
|
|
4059
|
+
});
|
|
3963
4060
|
}
|
|
3964
4061
|
|
|
3965
4062
|
function isPlainRecord(value) {
|
|
@@ -4023,7 +4120,13 @@ function forkStore(base, opt) {
|
|
|
4023
4120
|
source: () => base(),
|
|
4024
4121
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs),
|
|
4025
4122
|
});
|
|
4026
|
-
|
|
4123
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4124
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4125
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4126
|
+
const store = toStore(staged, {
|
|
4127
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4128
|
+
...opt,
|
|
4129
|
+
});
|
|
4027
4130
|
return {
|
|
4028
4131
|
store,
|
|
4029
4132
|
commit: () => base.set(untracked(staged)),
|
|
@@ -4558,5 +4661,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4558
4661
|
* Generated bundle index. Do not edit.
|
|
4559
4662
|
*/
|
|
4560
4663
|
|
|
4561
|
-
export { MmActivity, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
4664
|
+
export { MmActivity, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createAttributedPending, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
4562
4665
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|