@mmstack/primitives 21.2.2 → 21.4.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/package.json +1 -1
- package/types/mmstack-primitives.d.ts +103 -29
|
@@ -628,6 +628,38 @@ function provideForwardingTransitionScope() {
|
|
|
628
628
|
function getTransitionScope(injector) {
|
|
629
629
|
return injector.get(TRANSITION_SCOPE, null);
|
|
630
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* @internal Transaction-attributed pending for `startTransition`/`startTransaction`: like
|
|
633
|
+
* `scope.pending`, but loads already in flight when the tracker is created are NOT attributed —
|
|
634
|
+
* a pre-existing background load can neither settle the transaction early nor block its settle
|
|
635
|
+
* forever. A pre-existing flight is excluded only until it first settles; a later re-trigger of
|
|
636
|
+
* the same resource (e.g. the transaction's write changed its request) counts as the
|
|
637
|
+
* transaction's own work.
|
|
638
|
+
*/
|
|
639
|
+
function createAttributedPending(scope) {
|
|
640
|
+
const isInFlight = (ref) => {
|
|
641
|
+
const s = untracked(ref.status);
|
|
642
|
+
return s === 'loading' || s === 'reloading';
|
|
643
|
+
};
|
|
644
|
+
const preexisting = new Set(untracked(scope.resources).filter(isInFlight));
|
|
645
|
+
return computed(() => {
|
|
646
|
+
let pending = false;
|
|
647
|
+
for (const ref of scope.resources()) {
|
|
648
|
+
const s = ref.status();
|
|
649
|
+
const loading = s === 'loading' || s === 'reloading';
|
|
650
|
+
if (preexisting.has(ref)) {
|
|
651
|
+
// deletes are monotonic, so this stays sound under re-computation
|
|
652
|
+
if (loading)
|
|
653
|
+
continue;
|
|
654
|
+
preexisting.delete(ref);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (loading)
|
|
658
|
+
pending = true;
|
|
659
|
+
}
|
|
660
|
+
return pending;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
631
663
|
/**
|
|
632
664
|
* Returns a register function bound to the nearest transition scope: it adds a resource
|
|
633
665
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
@@ -665,38 +697,43 @@ function registerResource(res, opt) {
|
|
|
665
697
|
function injectStartTransition() {
|
|
666
698
|
const scope = injectTransitionScope();
|
|
667
699
|
const injector = inject(Injector);
|
|
700
|
+
const destroyRef = inject(DestroyRef);
|
|
668
701
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
669
702
|
return (fn) => {
|
|
703
|
+
// attributed: loads already in flight when the transition starts are not ours —
|
|
704
|
+
// they can neither settle this transition early nor block it forever
|
|
705
|
+
const pending = createAttributedPending(scope);
|
|
670
706
|
untracked(fn);
|
|
671
707
|
let sawPending = false;
|
|
672
708
|
const done = new Promise((resolve) => {
|
|
709
|
+
const settle = () => {
|
|
710
|
+
releaseDestroy();
|
|
711
|
+
watcher.destroy();
|
|
712
|
+
resolve();
|
|
713
|
+
};
|
|
673
714
|
const watcher = effect(() => {
|
|
674
|
-
const p =
|
|
715
|
+
const p = pending();
|
|
675
716
|
if (p)
|
|
676
717
|
sawPending = true;
|
|
677
718
|
// settle: requests went in flight and then drained
|
|
678
|
-
if (sawPending && !p)
|
|
679
|
-
|
|
680
|
-
resolve();
|
|
681
|
-
}
|
|
719
|
+
if (sawPending && !p)
|
|
720
|
+
settle();
|
|
682
721
|
}, { ...(ngDevMode ? { debugName: "watcher" } : /* istanbul ignore next */ {}), injector });
|
|
722
|
+
// a destroy mid-flight kills the watcher — resolve so awaiters never hang
|
|
723
|
+
const releaseDestroy = destroyRef.onDestroy(settle);
|
|
683
724
|
if (onServer) {
|
|
684
|
-
if (!untracked(
|
|
685
|
-
|
|
686
|
-
resolve();
|
|
687
|
-
}
|
|
725
|
+
if (!untracked(pending))
|
|
726
|
+
settle();
|
|
688
727
|
return;
|
|
689
728
|
}
|
|
690
729
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
691
730
|
// if nothing ever went in flight, the transition is already complete.
|
|
692
731
|
afterNextRender(() => {
|
|
693
|
-
if (!sawPending && !untracked(
|
|
694
|
-
|
|
695
|
-
resolve();
|
|
696
|
-
}
|
|
732
|
+
if (!sawPending && !untracked(pending))
|
|
733
|
+
settle();
|
|
697
734
|
}, { injector });
|
|
698
735
|
});
|
|
699
|
-
return { pending
|
|
736
|
+
return { pending, done };
|
|
700
737
|
};
|
|
701
738
|
}
|
|
702
739
|
|
|
@@ -767,8 +804,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
767
804
|
}] });
|
|
768
805
|
/**
|
|
769
806
|
* Unscoped suspense boundary — **reads the ambient scope** instead of providing one. For cases where
|
|
770
|
-
* the resources to coordinate are registered *above* the boundary
|
|
771
|
-
* manifests/connectors register at a higher injector), so the boundary observes that outer scope
|
|
807
|
+
* the resources to coordinate are registered *above* the boundary so the boundary observes that outer scope
|
|
772
808
|
* rather than opening a fresh one. Pair with a `provideTransitionScope()` (or another boundary) in an
|
|
773
809
|
* ancestor.
|
|
774
810
|
*/
|
|
@@ -833,9 +869,13 @@ function runInTransaction(txn, fn) {
|
|
|
833
869
|
function injectStartTransaction() {
|
|
834
870
|
const scope = injectTransitionScope();
|
|
835
871
|
const injector = inject(Injector);
|
|
872
|
+
const destroyRef = inject(DestroyRef);
|
|
836
873
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
837
874
|
return (fn) => {
|
|
838
875
|
const txn = createTransaction();
|
|
876
|
+
// attributed: loads already in flight when the transaction starts are not ours —
|
|
877
|
+
// they can neither commit this transaction early nor block its settle forever
|
|
878
|
+
const pending = createAttributedPending(scope);
|
|
839
879
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
840
880
|
scope.beginHold();
|
|
841
881
|
let finished = false;
|
|
@@ -852,6 +892,7 @@ function injectStartTransaction() {
|
|
|
852
892
|
if (finished)
|
|
853
893
|
return;
|
|
854
894
|
finished = true;
|
|
895
|
+
releaseDestroy();
|
|
855
896
|
watcher?.destroy();
|
|
856
897
|
if (restore)
|
|
857
898
|
txn.restore();
|
|
@@ -860,6 +901,10 @@ function injectStartTransaction() {
|
|
|
860
901
|
scope.endHold();
|
|
861
902
|
resolveDone();
|
|
862
903
|
};
|
|
904
|
+
// The scope may outlive the calling context (a component transacting on an ancestor
|
|
905
|
+
// boundary): a destroy mid-flight kills the settle watcher, so without this the hold
|
|
906
|
+
// would leak and freeze the surviving scope forever. Keep the writes — they landed live.
|
|
907
|
+
const releaseDestroy = destroyRef.onDestroy(() => finish(false));
|
|
863
908
|
try {
|
|
864
909
|
runInTransaction(txn, fn);
|
|
865
910
|
}
|
|
@@ -869,25 +914,25 @@ function injectStartTransaction() {
|
|
|
869
914
|
}
|
|
870
915
|
let sawPending = false;
|
|
871
916
|
watcher = effect(() => {
|
|
872
|
-
const p =
|
|
917
|
+
const p = pending();
|
|
873
918
|
if (p)
|
|
874
919
|
sawPending = true;
|
|
875
920
|
if (sawPending && !p)
|
|
876
921
|
finish(false);
|
|
877
922
|
}, { injector });
|
|
878
923
|
if (onServer) {
|
|
879
|
-
if (!untracked(
|
|
924
|
+
if (!untracked(pending))
|
|
880
925
|
finish(false);
|
|
881
926
|
}
|
|
882
927
|
else {
|
|
883
928
|
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
884
929
|
afterNextRender(() => {
|
|
885
|
-
if (!sawPending && !untracked(
|
|
930
|
+
if (!sawPending && !untracked(pending))
|
|
886
931
|
finish(false);
|
|
887
932
|
}, { injector });
|
|
888
933
|
}
|
|
889
934
|
return {
|
|
890
|
-
pending
|
|
935
|
+
pending,
|
|
891
936
|
done,
|
|
892
937
|
abort: () => finish(true),
|
|
893
938
|
};
|
|
@@ -2893,13 +2938,21 @@ const IDLE = {
|
|
|
2893
2938
|
pointerId: null,
|
|
2894
2939
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2895
2940
|
button: -1,
|
|
2941
|
+
pointerType: '',
|
|
2942
|
+
origin: null,
|
|
2943
|
+
cancelled: false,
|
|
2896
2944
|
};
|
|
2945
|
+
/** Terminal state of an aborted gesture — same idle shape, `cancelled: true`. */
|
|
2946
|
+
const CANCELLED = { ...IDLE, cancelled: true };
|
|
2897
2947
|
function stateEqual(a, b) {
|
|
2898
2948
|
return (a.active === b.active &&
|
|
2949
|
+
a.cancelled === b.cancelled &&
|
|
2899
2950
|
a.pointerId === b.pointerId &&
|
|
2900
2951
|
a.current.x === b.current.x &&
|
|
2901
2952
|
a.current.y === b.current.y &&
|
|
2902
2953
|
a.button === b.button &&
|
|
2954
|
+
a.pointerType === b.pointerType &&
|
|
2955
|
+
a.origin === b.origin &&
|
|
2903
2956
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2904
2957
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2905
2958
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2933,7 +2986,7 @@ function createPointerDrag(opt) {
|
|
|
2933
2986
|
return base;
|
|
2934
2987
|
}
|
|
2935
2988
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2936
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2989
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2937
2990
|
const resolve = (t) => {
|
|
2938
2991
|
if (!t)
|
|
2939
2992
|
return null;
|
|
@@ -2954,9 +3007,12 @@ function createPointerDrag(opt) {
|
|
|
2954
3007
|
equal: stateEqual,
|
|
2955
3008
|
debugName,
|
|
2956
3009
|
});
|
|
3010
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2957
3011
|
let startPoint = { x: 0, y: 0 };
|
|
2958
3012
|
let activePointerId = null;
|
|
2959
3013
|
let activeButton = -1;
|
|
3014
|
+
let activePointerType = '';
|
|
3015
|
+
let activeOrigin = null;
|
|
2960
3016
|
let activated = false;
|
|
2961
3017
|
let gesture = null;
|
|
2962
3018
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2968,22 +3024,24 @@ function createPointerDrag(opt) {
|
|
|
2968
3024
|
ctrl: e.ctrlKey,
|
|
2969
3025
|
meta: e.metaKey,
|
|
2970
3026
|
});
|
|
2971
|
-
const end = () => {
|
|
3027
|
+
const end = (cancelled = false) => {
|
|
2972
3028
|
gesture?.abort();
|
|
2973
3029
|
gesture = null;
|
|
2974
3030
|
activePointerId = null;
|
|
2975
3031
|
activeButton = -1;
|
|
3032
|
+
activePointerType = '';
|
|
3033
|
+
activeOrigin = null;
|
|
2976
3034
|
activated = false;
|
|
2977
|
-
state.set(IDLE);
|
|
2978
|
-
state.flush(); // terminal transition: reflect
|
|
3035
|
+
state.set(cancelled ? CANCELLED : IDLE);
|
|
3036
|
+
state.flush(); // terminal transition: reflect idle now, not on the trailing edge
|
|
2979
3037
|
};
|
|
2980
3038
|
const onMove = (e) => {
|
|
2981
3039
|
if (e.pointerId !== activePointerId)
|
|
2982
3040
|
return;
|
|
2983
3041
|
const current = coord(e);
|
|
2984
3042
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
2985
|
-
if (!activated &&
|
|
2986
|
-
activated = true;
|
|
3043
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3044
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
2987
3045
|
}
|
|
2988
3046
|
state.set({
|
|
2989
3047
|
active: activated,
|
|
@@ -2993,6 +3051,9 @@ function createPointerDrag(opt) {
|
|
|
2993
3051
|
pointerId: activePointerId,
|
|
2994
3052
|
modifiers: mods(e),
|
|
2995
3053
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3054
|
+
pointerType: activePointerType,
|
|
3055
|
+
origin: activeOrigin,
|
|
3056
|
+
cancelled: false,
|
|
2996
3057
|
});
|
|
2997
3058
|
};
|
|
2998
3059
|
const onUp = (e) => {
|
|
@@ -3001,22 +3062,28 @@ function createPointerDrag(opt) {
|
|
|
3001
3062
|
};
|
|
3002
3063
|
const onCancel = (e) => {
|
|
3003
3064
|
if (e.pointerId === activePointerId)
|
|
3004
|
-
end();
|
|
3065
|
+
end(true);
|
|
3005
3066
|
};
|
|
3006
3067
|
const onKey = (e) => {
|
|
3007
3068
|
if (e.key === 'Escape' && activePointerId !== null)
|
|
3008
|
-
end();
|
|
3069
|
+
end(true);
|
|
3009
3070
|
};
|
|
3010
3071
|
const onDown = (el) => (e) => {
|
|
3011
3072
|
if (activePointerId !== null)
|
|
3012
3073
|
return;
|
|
3013
3074
|
if (!buttons.includes(e.button))
|
|
3014
3075
|
return;
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3076
|
+
const matched = handleSelector
|
|
3077
|
+
? e.target?.closest?.(handleSelector)
|
|
3078
|
+
: el;
|
|
3079
|
+
if (!matched)
|
|
3080
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3081
|
+
if (stopPropagation)
|
|
3082
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3018
3083
|
activePointerId = e.pointerId;
|
|
3019
3084
|
activeButton = e.button;
|
|
3085
|
+
activePointerType = e.pointerType;
|
|
3086
|
+
activeOrigin = matched;
|
|
3020
3087
|
activated = false;
|
|
3021
3088
|
startPoint = coord(e);
|
|
3022
3089
|
try {
|
|
@@ -3042,6 +3109,9 @@ function createPointerDrag(opt) {
|
|
|
3042
3109
|
pointerId: e.pointerId,
|
|
3043
3110
|
modifiers: mods(e),
|
|
3044
3111
|
button: e.button,
|
|
3112
|
+
pointerType: activePointerType,
|
|
3113
|
+
origin: activeOrigin,
|
|
3114
|
+
cancelled: false,
|
|
3045
3115
|
});
|
|
3046
3116
|
};
|
|
3047
3117
|
const attach = (el) => {
|
|
@@ -3051,7 +3121,7 @@ function createPointerDrag(opt) {
|
|
|
3051
3121
|
});
|
|
3052
3122
|
return () => {
|
|
3053
3123
|
controller.abort();
|
|
3054
|
-
end();
|
|
3124
|
+
end(true); // teardown mid-gesture is an abort, not a drop
|
|
3055
3125
|
};
|
|
3056
3126
|
};
|
|
3057
3127
|
if (isSignal(target)) {
|
|
@@ -3069,7 +3139,7 @@ function createPointerDrag(opt) {
|
|
|
3069
3139
|
}
|
|
3070
3140
|
const base = state.asReadonly();
|
|
3071
3141
|
base.unthrottled = state.original;
|
|
3072
|
-
base.cancel = end;
|
|
3142
|
+
base.cancel = () => end(true);
|
|
3073
3143
|
return base;
|
|
3074
3144
|
}
|
|
3075
3145
|
|
|
@@ -3600,17 +3670,14 @@ function isLeaf(value) {
|
|
|
3600
3670
|
|
|
3601
3671
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3602
3672
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3673
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3674
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3603
3675
|
/**
|
|
3604
3676
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3605
3677
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3606
3678
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3607
3679
|
*/
|
|
3608
3680
|
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3609
|
-
/**
|
|
3610
|
-
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3611
|
-
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3612
|
-
*/
|
|
3613
|
-
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3614
3681
|
const SIGNAL_FN_PROP = new Set([
|
|
3615
3682
|
'set',
|
|
3616
3683
|
'update',
|
|
@@ -3618,21 +3685,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3618
3685
|
'inline',
|
|
3619
3686
|
'asReadonly',
|
|
3620
3687
|
]);
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
storeCache.delete(prop);
|
|
3688
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3689
|
+
providedIn: 'root',
|
|
3690
|
+
factory: () => new WeakMap(),
|
|
3691
|
+
});
|
|
3692
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3693
|
+
providedIn: 'root',
|
|
3694
|
+
factory: () => {
|
|
3695
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3696
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3697
|
+
const store = cache.get(target);
|
|
3698
|
+
if (store)
|
|
3699
|
+
store.delete(prop);
|
|
3700
|
+
});
|
|
3701
|
+
},
|
|
3636
3702
|
});
|
|
3637
3703
|
/**
|
|
3638
3704
|
* @internal
|
|
@@ -3649,11 +3715,11 @@ function isStore(value) {
|
|
|
3649
3715
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3650
3716
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3651
3717
|
*/
|
|
3652
|
-
function getCachedChild(target, prop, build) {
|
|
3653
|
-
let storeCache =
|
|
3718
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3719
|
+
let storeCache = cache.get(target);
|
|
3654
3720
|
if (!storeCache) {
|
|
3655
3721
|
storeCache = new Map();
|
|
3656
|
-
|
|
3722
|
+
cache.set(target, storeCache);
|
|
3657
3723
|
}
|
|
3658
3724
|
const cachedRef = storeCache.get(prop);
|
|
3659
3725
|
if (cachedRef) {
|
|
@@ -3661,42 +3727,47 @@ function getCachedChild(target, prop, build) {
|
|
|
3661
3727
|
if (cached)
|
|
3662
3728
|
return cached;
|
|
3663
3729
|
storeCache.delete(prop);
|
|
3664
|
-
|
|
3730
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3665
3731
|
}
|
|
3666
3732
|
const proxy = build();
|
|
3667
3733
|
const ref = new WeakRef(proxy);
|
|
3668
3734
|
storeCache.set(prop, ref);
|
|
3669
|
-
|
|
3735
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3670
3736
|
return proxy;
|
|
3671
3737
|
}
|
|
3738
|
+
/**
|
|
3739
|
+
* @internal Whether a mutable parent's child value must always re-notify: in-place mutation
|
|
3740
|
+
* keeps an object child's reference stable, so `Object.is` would swallow the change. Decided
|
|
3741
|
+
* per-VALUE (not snapshotted at build) so a union child that becomes an object later still
|
|
3742
|
+
* propagates parent-level mutations.
|
|
3743
|
+
*/
|
|
3744
|
+
function mutableChildEqual(a, b) {
|
|
3745
|
+
if (typeof a === 'object' && a !== null)
|
|
3746
|
+
return false;
|
|
3747
|
+
return Object.is(a, b);
|
|
3748
|
+
}
|
|
3672
3749
|
/**
|
|
3673
3750
|
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3674
|
-
*
|
|
3675
|
-
*
|
|
3676
|
-
* is constructed.
|
|
3751
|
+
* Both the read (`v?.[prop]`) and the write (`createFallbackOnChange` copies by the container's
|
|
3752
|
+
* LIVE shape) are shape-adaptive, so a child cached before an array↔record↔null union flip stays
|
|
3753
|
+
* correct after it. The only place a child node is constructed — shared by every container kind.
|
|
3677
3754
|
*/
|
|
3678
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3755
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3679
3756
|
const value = untracked(target);
|
|
3680
|
-
const
|
|
3681
|
-
const valueIsArray = Array.isArray(value);
|
|
3682
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3757
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3683
3758
|
const vivifyFn = createVivify(nodeVivify);
|
|
3684
|
-
const equalFn = (
|
|
3685
|
-
|
|
3686
|
-
typeof value[prop] === 'object'
|
|
3687
|
-
? () => false
|
|
3759
|
+
const equalFn = isMutableSource && (isRecord(value) || Array.isArray(value))
|
|
3760
|
+
? mutableChildEqual
|
|
3688
3761
|
: undefined;
|
|
3689
|
-
const computation =
|
|
3690
|
-
|
|
3691
|
-
:
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
equal: equalFn,
|
|
3695
|
-
});
|
|
3762
|
+
const computation = derived(target, {
|
|
3763
|
+
from: (v) => v?.[prop],
|
|
3764
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3765
|
+
equal: equalFn,
|
|
3766
|
+
});
|
|
3696
3767
|
const childSample = untracked(computation);
|
|
3697
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3698
|
-
const proxy = toStore(computation,
|
|
3699
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3768
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3769
|
+
const proxy = toStore(computation, options);
|
|
3770
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3700
3771
|
return proxy;
|
|
3701
3772
|
}
|
|
3702
3773
|
/**
|
|
@@ -3714,7 +3785,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3714
3785
|
* const state = store({ user: { name: 'John' } });
|
|
3715
3786
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3716
3787
|
*/
|
|
3717
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3788
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3718
3789
|
if (isStore(source))
|
|
3719
3790
|
return source;
|
|
3720
3791
|
if (!injector)
|
|
@@ -3734,6 +3805,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3734
3805
|
return 'record';
|
|
3735
3806
|
return 'primitive';
|
|
3736
3807
|
}, ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3808
|
+
const STORE_OPTIONS = {
|
|
3809
|
+
injector,
|
|
3810
|
+
vivify,
|
|
3811
|
+
noUnionLeaves,
|
|
3812
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3813
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3814
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3815
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3816
|
+
},
|
|
3817
|
+
};
|
|
3737
3818
|
// built lazily so non-array nodes never allocate it
|
|
3738
3819
|
let length;
|
|
3739
3820
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3787,21 +3868,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3787
3868
|
return { enumerable: true, configurable: true };
|
|
3788
3869
|
},
|
|
3789
3870
|
get(target, prop, receiver) {
|
|
3790
|
-
if (prop ===
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3871
|
+
if (typeof prop === 'symbol') {
|
|
3872
|
+
if (prop === IS_STORE)
|
|
3873
|
+
return true;
|
|
3874
|
+
if (prop === STORE_KIND)
|
|
3875
|
+
return isMutableSource
|
|
3876
|
+
? 'mutable'
|
|
3877
|
+
: isWritableSource
|
|
3878
|
+
? 'writable'
|
|
3879
|
+
: 'readonly';
|
|
3880
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3881
|
+
return STORE_OPTIONS;
|
|
3882
|
+
}
|
|
3800
3883
|
if (prop === 'asReadonlyStore')
|
|
3801
3884
|
return () => {
|
|
3802
3885
|
if (!isWritableSource)
|
|
3803
3886
|
return s;
|
|
3804
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3887
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3805
3888
|
};
|
|
3806
3889
|
const k = untracked(kind);
|
|
3807
3890
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3809,7 +3892,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3809
3892
|
? 'mutable'
|
|
3810
3893
|
: isWritableSource
|
|
3811
3894
|
? 'writable'
|
|
3812
|
-
: 'readonly',
|
|
3895
|
+
: 'readonly', STORE_OPTIONS);
|
|
3813
3896
|
if (k === 'array') {
|
|
3814
3897
|
if (prop === 'length')
|
|
3815
3898
|
return arrayLength();
|
|
@@ -3826,7 +3909,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3826
3909
|
return target[prop];
|
|
3827
3910
|
if (k === 'array' && !isIndexProp(prop))
|
|
3828
3911
|
return Reflect.get(target, prop, receiver);
|
|
3829
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3912
|
+
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);
|
|
3830
3913
|
},
|
|
3831
3914
|
});
|
|
3832
3915
|
return s;
|
|
@@ -3840,14 +3923,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3840
3923
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3841
3924
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3842
3925
|
*/
|
|
3843
|
-
function scopedStore(parent, seed, kind,
|
|
3926
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3844
3927
|
const local = isSignal(seed)
|
|
3845
|
-
? toStore(seed,
|
|
3928
|
+
? toStore(seed, options)
|
|
3846
3929
|
: kind === 'mutable'
|
|
3847
|
-
? mutableStore(seed,
|
|
3930
|
+
? mutableStore(seed, options)
|
|
3848
3931
|
: kind === 'readonly'
|
|
3849
|
-
? store(seed,
|
|
3850
|
-
: store(seed,
|
|
3932
|
+
? store(seed, options).asReadonlyStore()
|
|
3933
|
+
: store(seed, options);
|
|
3851
3934
|
const localValue = () => untracked(local);
|
|
3852
3935
|
const parentValue = () => untracked(parent);
|
|
3853
3936
|
const view = computed(() => ({
|
|
@@ -3878,18 +3961,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3878
3961
|
}
|
|
3879
3962
|
const scope = new Proxy(base, {
|
|
3880
3963
|
get(target, prop) {
|
|
3881
|
-
if (prop ===
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3964
|
+
if (typeof prop === 'symbol') {
|
|
3965
|
+
if (prop === IS_STORE)
|
|
3966
|
+
return true;
|
|
3967
|
+
if (prop === STORE_KIND)
|
|
3968
|
+
return kind;
|
|
3969
|
+
if (prop === SCOPE_PARENT)
|
|
3970
|
+
return parent;
|
|
3971
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3972
|
+
return options;
|
|
3973
|
+
}
|
|
3889
3974
|
if (prop === 'extend')
|
|
3890
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3975
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3891
3976
|
if (prop === 'asReadonlyStore')
|
|
3892
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3977
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3893
3978
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3894
3979
|
return target[prop];
|
|
3895
3980
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3935,22 +4020,34 @@ function storeKind(s) {
|
|
|
3935
4020
|
* scoped.count.set(1); // writes through to base
|
|
3936
4021
|
* scoped.label.set('x'); // stays local
|
|
3937
4022
|
*/
|
|
3938
|
-
function extendStore(store, source,
|
|
3939
|
-
|
|
4023
|
+
function extendStore(store, source, options) {
|
|
4024
|
+
const opt = {
|
|
4025
|
+
...store[STORE_SHARED_OPTIONS],
|
|
4026
|
+
...options,
|
|
4027
|
+
};
|
|
4028
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3940
4029
|
}
|
|
3941
4030
|
/**
|
|
3942
4031
|
* Creates a WritableSignalStore from a value.
|
|
3943
4032
|
* @see {@link toStore}
|
|
3944
4033
|
*/
|
|
3945
4034
|
function store(value, opt) {
|
|
3946
|
-
return toStore(signal(value, opt),
|
|
4035
|
+
return toStore(signal(value, opt), {
|
|
4036
|
+
vivify: false,
|
|
4037
|
+
noUnionLeaves: false,
|
|
4038
|
+
...opt,
|
|
4039
|
+
});
|
|
3947
4040
|
}
|
|
3948
4041
|
/**
|
|
3949
4042
|
* Creates a MutableSignalStore from a value.
|
|
3950
4043
|
* @see {@link toStore}
|
|
3951
4044
|
*/
|
|
3952
4045
|
function mutableStore(value, opt) {
|
|
3953
|
-
return toStore(mutable(value, opt),
|
|
4046
|
+
return toStore(mutable(value, opt), {
|
|
4047
|
+
vivify: false,
|
|
4048
|
+
noUnionLeaves: false,
|
|
4049
|
+
...opt,
|
|
4050
|
+
});
|
|
3954
4051
|
}
|
|
3955
4052
|
|
|
3956
4053
|
function isPlainRecord(value) {
|
|
@@ -4012,7 +4109,13 @@ function forkStore(base, opt) {
|
|
|
4012
4109
|
const merge = reconcile;
|
|
4013
4110
|
const staged = linkedSignal({ ...(ngDevMode ? { debugName: "staged" } : /* istanbul ignore next */ {}), source: () => base(),
|
|
4014
4111
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs) });
|
|
4015
|
-
|
|
4112
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4113
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4114
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4115
|
+
const store = toStore(staged, {
|
|
4116
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4117
|
+
...opt,
|
|
4118
|
+
});
|
|
4016
4119
|
return {
|
|
4017
4120
|
store,
|
|
4018
4121
|
commit: () => base.set(untracked(staged)),
|
|
@@ -4545,5 +4648,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4545
4648
|
* Generated bundle index. Do not edit.
|
|
4546
4649
|
*/
|
|
4547
4650
|
|
|
4548
|
-
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 };
|
|
4651
|
+
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 };
|
|
4549
4652
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|