@mmstack/primitives 22.2.2 → 22.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
|
@@ -633,6 +633,38 @@ function provideForwardingTransitionScope() {
|
|
|
633
633
|
function getTransitionScope(injector) {
|
|
634
634
|
return injector.get(TRANSITION_SCOPE, null);
|
|
635
635
|
}
|
|
636
|
+
/**
|
|
637
|
+
* @internal Transaction-attributed pending for `startTransition`/`startTransaction`: like
|
|
638
|
+
* `scope.pending`, but loads already in flight when the tracker is created are NOT attributed —
|
|
639
|
+
* a pre-existing background load can neither settle the transaction early nor block its settle
|
|
640
|
+
* forever. A pre-existing flight is excluded only until it first settles; a later re-trigger of
|
|
641
|
+
* the same resource (e.g. the transaction's write changed its request) counts as the
|
|
642
|
+
* transaction's own work.
|
|
643
|
+
*/
|
|
644
|
+
function createAttributedPending(scope) {
|
|
645
|
+
const isInFlight = (ref) => {
|
|
646
|
+
const s = untracked(ref.status);
|
|
647
|
+
return s === 'loading' || s === 'reloading';
|
|
648
|
+
};
|
|
649
|
+
const preexisting = new Set(untracked(scope.resources).filter(isInFlight));
|
|
650
|
+
return computed(() => {
|
|
651
|
+
let pending = false;
|
|
652
|
+
for (const ref of scope.resources()) {
|
|
653
|
+
const s = ref.status();
|
|
654
|
+
const loading = s === 'loading' || s === 'reloading';
|
|
655
|
+
if (preexisting.has(ref)) {
|
|
656
|
+
// deletes are monotonic, so this stays sound under re-computation
|
|
657
|
+
if (loading)
|
|
658
|
+
continue;
|
|
659
|
+
preexisting.delete(ref);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (loading)
|
|
663
|
+
pending = true;
|
|
664
|
+
}
|
|
665
|
+
return pending;
|
|
666
|
+
});
|
|
667
|
+
}
|
|
636
668
|
/**
|
|
637
669
|
* Returns a register function bound to the nearest transition scope: it adds a resource
|
|
638
670
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
@@ -670,38 +702,43 @@ function registerResource(res, opt) {
|
|
|
670
702
|
function injectStartTransition() {
|
|
671
703
|
const scope = injectTransitionScope();
|
|
672
704
|
const injector = inject(Injector);
|
|
705
|
+
const destroyRef = inject(DestroyRef);
|
|
673
706
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
674
707
|
return (fn) => {
|
|
708
|
+
// attributed: loads already in flight when the transition starts are not ours —
|
|
709
|
+
// they can neither settle this transition early nor block it forever
|
|
710
|
+
const pending = createAttributedPending(scope);
|
|
675
711
|
untracked(fn);
|
|
676
712
|
let sawPending = false;
|
|
677
713
|
const done = new Promise((resolve) => {
|
|
714
|
+
const settle = () => {
|
|
715
|
+
releaseDestroy();
|
|
716
|
+
watcher.destroy();
|
|
717
|
+
resolve();
|
|
718
|
+
};
|
|
678
719
|
const watcher = effect(() => {
|
|
679
|
-
const p =
|
|
720
|
+
const p = pending();
|
|
680
721
|
if (p)
|
|
681
722
|
sawPending = true;
|
|
682
723
|
// settle: requests went in flight and then drained
|
|
683
|
-
if (sawPending && !p)
|
|
684
|
-
|
|
685
|
-
resolve();
|
|
686
|
-
}
|
|
724
|
+
if (sawPending && !p)
|
|
725
|
+
settle();
|
|
687
726
|
}, { ...(ngDevMode ? { debugName: "watcher" } : /* istanbul ignore next */ {}), injector });
|
|
727
|
+
// a destroy mid-flight kills the watcher — resolve so awaiters never hang
|
|
728
|
+
const releaseDestroy = destroyRef.onDestroy(settle);
|
|
688
729
|
if (onServer) {
|
|
689
|
-
if (!untracked(
|
|
690
|
-
|
|
691
|
-
resolve();
|
|
692
|
-
}
|
|
730
|
+
if (!untracked(pending))
|
|
731
|
+
settle();
|
|
693
732
|
return;
|
|
694
733
|
}
|
|
695
734
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
696
735
|
// if nothing ever went in flight, the transition is already complete.
|
|
697
736
|
afterNextRender(() => {
|
|
698
|
-
if (!sawPending && !untracked(
|
|
699
|
-
|
|
700
|
-
resolve();
|
|
701
|
-
}
|
|
737
|
+
if (!sawPending && !untracked(pending))
|
|
738
|
+
settle();
|
|
702
739
|
}, { injector });
|
|
703
740
|
});
|
|
704
|
-
return { pending
|
|
741
|
+
return { pending, done };
|
|
705
742
|
};
|
|
706
743
|
}
|
|
707
744
|
|
|
@@ -774,8 +811,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
774
811
|
}] });
|
|
775
812
|
/**
|
|
776
813
|
* Unscoped suspense boundary — **reads the ambient scope** instead of providing one. For cases where
|
|
777
|
-
* the resources to coordinate are registered *above* the boundary
|
|
778
|
-
* manifests/connectors register at a higher injector), so the boundary observes that outer scope
|
|
814
|
+
* the resources to coordinate are registered *above* the boundary so the boundary observes that outer scope
|
|
779
815
|
* rather than opening a fresh one. Pair with a `provideTransitionScope()` (or another boundary) in an
|
|
780
816
|
* ancestor.
|
|
781
817
|
*/
|
|
@@ -840,9 +876,13 @@ function runInTransaction(txn, fn) {
|
|
|
840
876
|
function injectStartTransaction() {
|
|
841
877
|
const scope = injectTransitionScope();
|
|
842
878
|
const injector = inject(Injector);
|
|
879
|
+
const destroyRef = inject(DestroyRef);
|
|
843
880
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
844
881
|
return (fn) => {
|
|
845
882
|
const txn = createTransaction();
|
|
883
|
+
// attributed: loads already in flight when the transaction starts are not ours —
|
|
884
|
+
// they can neither commit this transaction early nor block its settle forever
|
|
885
|
+
const pending = createAttributedPending(scope);
|
|
846
886
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
847
887
|
scope.beginHold();
|
|
848
888
|
let finished = false;
|
|
@@ -859,6 +899,7 @@ function injectStartTransaction() {
|
|
|
859
899
|
if (finished)
|
|
860
900
|
return;
|
|
861
901
|
finished = true;
|
|
902
|
+
releaseDestroy();
|
|
862
903
|
watcher?.destroy();
|
|
863
904
|
if (restore)
|
|
864
905
|
txn.restore();
|
|
@@ -867,6 +908,10 @@ function injectStartTransaction() {
|
|
|
867
908
|
scope.endHold();
|
|
868
909
|
resolveDone();
|
|
869
910
|
};
|
|
911
|
+
// The scope may outlive the calling context (a component transacting on an ancestor
|
|
912
|
+
// boundary): a destroy mid-flight kills the settle watcher, so without this the hold
|
|
913
|
+
// would leak and freeze the surviving scope forever. Keep the writes — they landed live.
|
|
914
|
+
const releaseDestroy = destroyRef.onDestroy(() => finish(false));
|
|
870
915
|
try {
|
|
871
916
|
runInTransaction(txn, fn);
|
|
872
917
|
}
|
|
@@ -876,25 +921,25 @@ function injectStartTransaction() {
|
|
|
876
921
|
}
|
|
877
922
|
let sawPending = false;
|
|
878
923
|
watcher = effect(() => {
|
|
879
|
-
const p =
|
|
924
|
+
const p = pending();
|
|
880
925
|
if (p)
|
|
881
926
|
sawPending = true;
|
|
882
927
|
if (sawPending && !p)
|
|
883
928
|
finish(false);
|
|
884
929
|
}, { injector });
|
|
885
930
|
if (onServer) {
|
|
886
|
-
if (!untracked(
|
|
931
|
+
if (!untracked(pending))
|
|
887
932
|
finish(false);
|
|
888
933
|
}
|
|
889
934
|
else {
|
|
890
935
|
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
891
936
|
afterNextRender(() => {
|
|
892
|
-
if (!sawPending && !untracked(
|
|
937
|
+
if (!sawPending && !untracked(pending))
|
|
893
938
|
finish(false);
|
|
894
939
|
}, { injector });
|
|
895
940
|
}
|
|
896
941
|
return {
|
|
897
|
-
pending
|
|
942
|
+
pending,
|
|
898
943
|
done,
|
|
899
944
|
abort: () => finish(true),
|
|
900
945
|
};
|
|
@@ -2919,13 +2964,21 @@ const IDLE = {
|
|
|
2919
2964
|
pointerId: null,
|
|
2920
2965
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2921
2966
|
button: -1,
|
|
2967
|
+
pointerType: '',
|
|
2968
|
+
origin: null,
|
|
2969
|
+
cancelled: false,
|
|
2922
2970
|
};
|
|
2971
|
+
/** Terminal state of an aborted gesture — same idle shape, `cancelled: true`. */
|
|
2972
|
+
const CANCELLED = { ...IDLE, cancelled: true };
|
|
2923
2973
|
function stateEqual(a, b) {
|
|
2924
2974
|
return (a.active === b.active &&
|
|
2975
|
+
a.cancelled === b.cancelled &&
|
|
2925
2976
|
a.pointerId === b.pointerId &&
|
|
2926
2977
|
a.current.x === b.current.x &&
|
|
2927
2978
|
a.current.y === b.current.y &&
|
|
2928
2979
|
a.button === b.button &&
|
|
2980
|
+
a.pointerType === b.pointerType &&
|
|
2981
|
+
a.origin === b.origin &&
|
|
2929
2982
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2930
2983
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2931
2984
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2959,7 +3012,7 @@ function createPointerDrag(opt) {
|
|
|
2959
3012
|
return base;
|
|
2960
3013
|
}
|
|
2961
3014
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2962
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
3015
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2963
3016
|
const resolve = (t) => {
|
|
2964
3017
|
if (!t)
|
|
2965
3018
|
return null;
|
|
@@ -2980,9 +3033,12 @@ function createPointerDrag(opt) {
|
|
|
2980
3033
|
equal: stateEqual,
|
|
2981
3034
|
debugName,
|
|
2982
3035
|
});
|
|
3036
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2983
3037
|
let startPoint = { x: 0, y: 0 };
|
|
2984
3038
|
let activePointerId = null;
|
|
2985
3039
|
let activeButton = -1;
|
|
3040
|
+
let activePointerType = '';
|
|
3041
|
+
let activeOrigin = null;
|
|
2986
3042
|
let activated = false;
|
|
2987
3043
|
let gesture = null;
|
|
2988
3044
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2994,22 +3050,24 @@ function createPointerDrag(opt) {
|
|
|
2994
3050
|
ctrl: e.ctrlKey,
|
|
2995
3051
|
meta: e.metaKey,
|
|
2996
3052
|
});
|
|
2997
|
-
const end = () => {
|
|
3053
|
+
const end = (cancelled = false) => {
|
|
2998
3054
|
gesture?.abort();
|
|
2999
3055
|
gesture = null;
|
|
3000
3056
|
activePointerId = null;
|
|
3001
3057
|
activeButton = -1;
|
|
3058
|
+
activePointerType = '';
|
|
3059
|
+
activeOrigin = null;
|
|
3002
3060
|
activated = false;
|
|
3003
|
-
state.set(IDLE);
|
|
3004
|
-
state.flush(); // terminal transition: reflect
|
|
3061
|
+
state.set(cancelled ? CANCELLED : IDLE);
|
|
3062
|
+
state.flush(); // terminal transition: reflect idle now, not on the trailing edge
|
|
3005
3063
|
};
|
|
3006
3064
|
const onMove = (e) => {
|
|
3007
3065
|
if (e.pointerId !== activePointerId)
|
|
3008
3066
|
return;
|
|
3009
3067
|
const current = coord(e);
|
|
3010
3068
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
3011
|
-
if (!activated &&
|
|
3012
|
-
activated = true;
|
|
3069
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3070
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
3013
3071
|
}
|
|
3014
3072
|
state.set({
|
|
3015
3073
|
active: activated,
|
|
@@ -3019,6 +3077,9 @@ function createPointerDrag(opt) {
|
|
|
3019
3077
|
pointerId: activePointerId,
|
|
3020
3078
|
modifiers: mods(e),
|
|
3021
3079
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3080
|
+
pointerType: activePointerType,
|
|
3081
|
+
origin: activeOrigin,
|
|
3082
|
+
cancelled: false,
|
|
3022
3083
|
});
|
|
3023
3084
|
};
|
|
3024
3085
|
const onUp = (e) => {
|
|
@@ -3027,22 +3088,28 @@ function createPointerDrag(opt) {
|
|
|
3027
3088
|
};
|
|
3028
3089
|
const onCancel = (e) => {
|
|
3029
3090
|
if (e.pointerId === activePointerId)
|
|
3030
|
-
end();
|
|
3091
|
+
end(true);
|
|
3031
3092
|
};
|
|
3032
3093
|
const onKey = (e) => {
|
|
3033
3094
|
if (e.key === 'Escape' && activePointerId !== null)
|
|
3034
|
-
end();
|
|
3095
|
+
end(true);
|
|
3035
3096
|
};
|
|
3036
3097
|
const onDown = (el) => (e) => {
|
|
3037
3098
|
if (activePointerId !== null)
|
|
3038
3099
|
return;
|
|
3039
3100
|
if (!buttons.includes(e.button))
|
|
3040
3101
|
return;
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3102
|
+
const matched = handleSelector
|
|
3103
|
+
? e.target?.closest?.(handleSelector)
|
|
3104
|
+
: el;
|
|
3105
|
+
if (!matched)
|
|
3106
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3107
|
+
if (stopPropagation)
|
|
3108
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3044
3109
|
activePointerId = e.pointerId;
|
|
3045
3110
|
activeButton = e.button;
|
|
3111
|
+
activePointerType = e.pointerType;
|
|
3112
|
+
activeOrigin = matched;
|
|
3046
3113
|
activated = false;
|
|
3047
3114
|
startPoint = coord(e);
|
|
3048
3115
|
try {
|
|
@@ -3068,6 +3135,9 @@ function createPointerDrag(opt) {
|
|
|
3068
3135
|
pointerId: e.pointerId,
|
|
3069
3136
|
modifiers: mods(e),
|
|
3070
3137
|
button: e.button,
|
|
3138
|
+
pointerType: activePointerType,
|
|
3139
|
+
origin: activeOrigin,
|
|
3140
|
+
cancelled: false,
|
|
3071
3141
|
});
|
|
3072
3142
|
};
|
|
3073
3143
|
const attach = (el) => {
|
|
@@ -3077,7 +3147,7 @@ function createPointerDrag(opt) {
|
|
|
3077
3147
|
});
|
|
3078
3148
|
return () => {
|
|
3079
3149
|
controller.abort();
|
|
3080
|
-
end();
|
|
3150
|
+
end(true); // teardown mid-gesture is an abort, not a drop
|
|
3081
3151
|
};
|
|
3082
3152
|
};
|
|
3083
3153
|
if (isSignal(target)) {
|
|
@@ -3095,7 +3165,7 @@ function createPointerDrag(opt) {
|
|
|
3095
3165
|
}
|
|
3096
3166
|
const base = state.asReadonly();
|
|
3097
3167
|
base.unthrottled = state.original;
|
|
3098
|
-
base.cancel = end;
|
|
3168
|
+
base.cancel = () => end(true);
|
|
3099
3169
|
return base;
|
|
3100
3170
|
}
|
|
3101
3171
|
|
|
@@ -3626,17 +3696,14 @@ function isLeaf(value) {
|
|
|
3626
3696
|
|
|
3627
3697
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3628
3698
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3699
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3700
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3629
3701
|
/**
|
|
3630
3702
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3631
3703
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3632
3704
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3633
3705
|
*/
|
|
3634
3706
|
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3635
|
-
/**
|
|
3636
|
-
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3637
|
-
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3638
|
-
*/
|
|
3639
|
-
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3640
3707
|
const SIGNAL_FN_PROP = new Set([
|
|
3641
3708
|
'set',
|
|
3642
3709
|
'update',
|
|
@@ -3644,21 +3711,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3644
3711
|
'inline',
|
|
3645
3712
|
'asReadonly',
|
|
3646
3713
|
]);
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
storeCache.delete(prop);
|
|
3714
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3715
|
+
providedIn: 'root',
|
|
3716
|
+
factory: () => new WeakMap(),
|
|
3717
|
+
});
|
|
3718
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3719
|
+
providedIn: 'root',
|
|
3720
|
+
factory: () => {
|
|
3721
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3722
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3723
|
+
const store = cache.get(target);
|
|
3724
|
+
if (store)
|
|
3725
|
+
store.delete(prop);
|
|
3726
|
+
});
|
|
3727
|
+
},
|
|
3662
3728
|
});
|
|
3663
3729
|
/**
|
|
3664
3730
|
* @internal
|
|
@@ -3675,11 +3741,11 @@ function isStore(value) {
|
|
|
3675
3741
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3676
3742
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3677
3743
|
*/
|
|
3678
|
-
function getCachedChild(target, prop, build) {
|
|
3679
|
-
let storeCache =
|
|
3744
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3745
|
+
let storeCache = cache.get(target);
|
|
3680
3746
|
if (!storeCache) {
|
|
3681
3747
|
storeCache = new Map();
|
|
3682
|
-
|
|
3748
|
+
cache.set(target, storeCache);
|
|
3683
3749
|
}
|
|
3684
3750
|
const cachedRef = storeCache.get(prop);
|
|
3685
3751
|
if (cachedRef) {
|
|
@@ -3687,42 +3753,47 @@ function getCachedChild(target, prop, build) {
|
|
|
3687
3753
|
if (cached)
|
|
3688
3754
|
return cached;
|
|
3689
3755
|
storeCache.delete(prop);
|
|
3690
|
-
|
|
3756
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3691
3757
|
}
|
|
3692
3758
|
const proxy = build();
|
|
3693
3759
|
const ref = new WeakRef(proxy);
|
|
3694
3760
|
storeCache.set(prop, ref);
|
|
3695
|
-
|
|
3761
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3696
3762
|
return proxy;
|
|
3697
3763
|
}
|
|
3764
|
+
/**
|
|
3765
|
+
* @internal Whether a mutable parent's child value must always re-notify: in-place mutation
|
|
3766
|
+
* keeps an object child's reference stable, so `Object.is` would swallow the change. Decided
|
|
3767
|
+
* per-VALUE (not snapshotted at build) so a union child that becomes an object later still
|
|
3768
|
+
* propagates parent-level mutations.
|
|
3769
|
+
*/
|
|
3770
|
+
function mutableChildEqual(a, b) {
|
|
3771
|
+
if (typeof a === 'object' && a !== null)
|
|
3772
|
+
return false;
|
|
3773
|
+
return Object.is(a, b);
|
|
3774
|
+
}
|
|
3698
3775
|
/**
|
|
3699
3776
|
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3700
|
-
*
|
|
3701
|
-
*
|
|
3702
|
-
* is constructed.
|
|
3777
|
+
* Both the read (`v?.[prop]`) and the write (`createFallbackOnChange` copies by the container's
|
|
3778
|
+
* LIVE shape) are shape-adaptive, so a child cached before an array↔record↔null union flip stays
|
|
3779
|
+
* correct after it. The only place a child node is constructed — shared by every container kind.
|
|
3703
3780
|
*/
|
|
3704
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3781
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3705
3782
|
const value = untracked(target);
|
|
3706
|
-
const
|
|
3707
|
-
const valueIsArray = Array.isArray(value);
|
|
3708
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3783
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3709
3784
|
const vivifyFn = createVivify(nodeVivify);
|
|
3710
|
-
const equalFn = (
|
|
3711
|
-
|
|
3712
|
-
typeof value[prop] === 'object'
|
|
3713
|
-
? () => false
|
|
3785
|
+
const equalFn = isMutableSource && (isRecord(value) || Array.isArray(value))
|
|
3786
|
+
? mutableChildEqual
|
|
3714
3787
|
: undefined;
|
|
3715
|
-
const computation =
|
|
3716
|
-
|
|
3717
|
-
:
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
equal: equalFn,
|
|
3721
|
-
});
|
|
3788
|
+
const computation = derived(target, {
|
|
3789
|
+
from: (v) => v?.[prop],
|
|
3790
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3791
|
+
equal: equalFn,
|
|
3792
|
+
});
|
|
3722
3793
|
const childSample = untracked(computation);
|
|
3723
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3724
|
-
const proxy = toStore(computation,
|
|
3725
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3794
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3795
|
+
const proxy = toStore(computation, options);
|
|
3796
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3726
3797
|
return proxy;
|
|
3727
3798
|
}
|
|
3728
3799
|
/**
|
|
@@ -3740,7 +3811,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3740
3811
|
* const state = store({ user: { name: 'John' } });
|
|
3741
3812
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3742
3813
|
*/
|
|
3743
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3814
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3744
3815
|
if (isStore(source))
|
|
3745
3816
|
return source;
|
|
3746
3817
|
if (!injector)
|
|
@@ -3761,6 +3832,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3761
3832
|
return 'primitive';
|
|
3762
3833
|
}, /* @ts-ignore */
|
|
3763
3834
|
...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3835
|
+
const STORE_OPTIONS = {
|
|
3836
|
+
injector,
|
|
3837
|
+
vivify,
|
|
3838
|
+
noUnionLeaves,
|
|
3839
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3840
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3841
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3842
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3843
|
+
},
|
|
3844
|
+
};
|
|
3764
3845
|
// built lazily so non-array nodes never allocate it
|
|
3765
3846
|
let length;
|
|
3766
3847
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3814,21 +3895,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3814
3895
|
return { enumerable: true, configurable: true };
|
|
3815
3896
|
},
|
|
3816
3897
|
get(target, prop, receiver) {
|
|
3817
|
-
if (prop ===
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3898
|
+
if (typeof prop === 'symbol') {
|
|
3899
|
+
if (prop === IS_STORE)
|
|
3900
|
+
return true;
|
|
3901
|
+
if (prop === STORE_KIND)
|
|
3902
|
+
return isMutableSource
|
|
3903
|
+
? 'mutable'
|
|
3904
|
+
: isWritableSource
|
|
3905
|
+
? 'writable'
|
|
3906
|
+
: 'readonly';
|
|
3907
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3908
|
+
return STORE_OPTIONS;
|
|
3909
|
+
}
|
|
3827
3910
|
if (prop === 'asReadonlyStore')
|
|
3828
3911
|
return () => {
|
|
3829
3912
|
if (!isWritableSource)
|
|
3830
3913
|
return s;
|
|
3831
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3914
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3832
3915
|
};
|
|
3833
3916
|
const k = untracked(kind);
|
|
3834
3917
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3836,7 +3919,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3836
3919
|
? 'mutable'
|
|
3837
3920
|
: isWritableSource
|
|
3838
3921
|
? 'writable'
|
|
3839
|
-
: 'readonly',
|
|
3922
|
+
: 'readonly', STORE_OPTIONS);
|
|
3840
3923
|
if (k === 'array') {
|
|
3841
3924
|
if (prop === 'length')
|
|
3842
3925
|
return arrayLength();
|
|
@@ -3853,7 +3936,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3853
3936
|
return target[prop];
|
|
3854
3937
|
if (k === 'array' && !isIndexProp(prop))
|
|
3855
3938
|
return Reflect.get(target, prop, receiver);
|
|
3856
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3939
|
+
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);
|
|
3857
3940
|
},
|
|
3858
3941
|
});
|
|
3859
3942
|
return s;
|
|
@@ -3867,14 +3950,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3867
3950
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3868
3951
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3869
3952
|
*/
|
|
3870
|
-
function scopedStore(parent, seed, kind,
|
|
3953
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3871
3954
|
const local = isSignal(seed)
|
|
3872
|
-
? toStore(seed,
|
|
3955
|
+
? toStore(seed, options)
|
|
3873
3956
|
: kind === 'mutable'
|
|
3874
|
-
? mutableStore(seed,
|
|
3957
|
+
? mutableStore(seed, options)
|
|
3875
3958
|
: kind === 'readonly'
|
|
3876
|
-
? store(seed,
|
|
3877
|
-
: store(seed,
|
|
3959
|
+
? store(seed, options).asReadonlyStore()
|
|
3960
|
+
: store(seed, options);
|
|
3878
3961
|
const localValue = () => untracked(local);
|
|
3879
3962
|
const parentValue = () => untracked(parent);
|
|
3880
3963
|
const view = computed(() => ({
|
|
@@ -3906,18 +3989,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3906
3989
|
}
|
|
3907
3990
|
const scope = new Proxy(base, {
|
|
3908
3991
|
get(target, prop) {
|
|
3909
|
-
if (prop ===
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3992
|
+
if (typeof prop === 'symbol') {
|
|
3993
|
+
if (prop === IS_STORE)
|
|
3994
|
+
return true;
|
|
3995
|
+
if (prop === STORE_KIND)
|
|
3996
|
+
return kind;
|
|
3997
|
+
if (prop === SCOPE_PARENT)
|
|
3998
|
+
return parent;
|
|
3999
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
4000
|
+
return options;
|
|
4001
|
+
}
|
|
3917
4002
|
if (prop === 'extend')
|
|
3918
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
4003
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3919
4004
|
if (prop === 'asReadonlyStore')
|
|
3920
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
4005
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3921
4006
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3922
4007
|
return target[prop];
|
|
3923
4008
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3963,22 +4048,34 @@ function storeKind(s) {
|
|
|
3963
4048
|
* scoped.count.set(1); // writes through to base
|
|
3964
4049
|
* scoped.label.set('x'); // stays local
|
|
3965
4050
|
*/
|
|
3966
|
-
function extendStore(store, source,
|
|
3967
|
-
|
|
4051
|
+
function extendStore(store, source, options) {
|
|
4052
|
+
const opt = {
|
|
4053
|
+
...store[STORE_SHARED_OPTIONS],
|
|
4054
|
+
...options,
|
|
4055
|
+
};
|
|
4056
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3968
4057
|
}
|
|
3969
4058
|
/**
|
|
3970
4059
|
* Creates a WritableSignalStore from a value.
|
|
3971
4060
|
* @see {@link toStore}
|
|
3972
4061
|
*/
|
|
3973
4062
|
function store(value, opt) {
|
|
3974
|
-
return toStore(signal(value, opt),
|
|
4063
|
+
return toStore(signal(value, opt), {
|
|
4064
|
+
vivify: false,
|
|
4065
|
+
noUnionLeaves: false,
|
|
4066
|
+
...opt,
|
|
4067
|
+
});
|
|
3975
4068
|
}
|
|
3976
4069
|
/**
|
|
3977
4070
|
* Creates a MutableSignalStore from a value.
|
|
3978
4071
|
* @see {@link toStore}
|
|
3979
4072
|
*/
|
|
3980
4073
|
function mutableStore(value, opt) {
|
|
3981
|
-
return toStore(mutable(value, opt),
|
|
4074
|
+
return toStore(mutable(value, opt), {
|
|
4075
|
+
vivify: false,
|
|
4076
|
+
noUnionLeaves: false,
|
|
4077
|
+
...opt,
|
|
4078
|
+
});
|
|
3982
4079
|
}
|
|
3983
4080
|
|
|
3984
4081
|
function isPlainRecord(value) {
|
|
@@ -4040,7 +4137,13 @@ function forkStore(base, opt) {
|
|
|
4040
4137
|
const merge = reconcile;
|
|
4041
4138
|
const staged = linkedSignal({ ...(ngDevMode ? { debugName: "staged" } : /* istanbul ignore next */ {}), source: () => base(),
|
|
4042
4139
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs) });
|
|
4043
|
-
|
|
4140
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4141
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4142
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4143
|
+
const store = toStore(staged, {
|
|
4144
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4145
|
+
...opt,
|
|
4146
|
+
});
|
|
4044
4147
|
return {
|
|
4045
4148
|
store,
|
|
4046
4149
|
commit: () => base.set(untracked(staged)),
|
|
@@ -4576,5 +4679,5 @@ function withHistory(sourceOrValue, opt) {
|
|
|
4576
4679
|
* Generated bundle index. Do not edit.
|
|
4577
4680
|
*/
|
|
4578
4681
|
|
|
4579
|
-
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 };
|
|
4682
|
+
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 };
|
|
4580
4683
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|