@mmstack/primitives 20.7.2 → 20.9.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/index.d.ts +103 -29
- package/package.json +1 -1
|
@@ -640,6 +640,38 @@ function provideForwardingTransitionScope() {
|
|
|
640
640
|
function getTransitionScope(injector) {
|
|
641
641
|
return injector.get(TRANSITION_SCOPE, null);
|
|
642
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* @internal Transaction-attributed pending for `startTransition`/`startTransaction`: like
|
|
645
|
+
* `scope.pending`, but loads already in flight when the tracker is created are NOT attributed —
|
|
646
|
+
* a pre-existing background load can neither settle the transaction early nor block its settle
|
|
647
|
+
* forever. A pre-existing flight is excluded only until it first settles; a later re-trigger of
|
|
648
|
+
* the same resource (e.g. the transaction's write changed its request) counts as the
|
|
649
|
+
* transaction's own work.
|
|
650
|
+
*/
|
|
651
|
+
function createAttributedPending(scope) {
|
|
652
|
+
const isInFlight = (ref) => {
|
|
653
|
+
const s = untracked(ref.status);
|
|
654
|
+
return s === 'loading' || s === 'reloading';
|
|
655
|
+
};
|
|
656
|
+
const preexisting = new Set(untracked(scope.resources).filter(isInFlight));
|
|
657
|
+
return computed(() => {
|
|
658
|
+
let pending = false;
|
|
659
|
+
for (const ref of scope.resources()) {
|
|
660
|
+
const s = ref.status();
|
|
661
|
+
const loading = s === 'loading' || s === 'reloading';
|
|
662
|
+
if (preexisting.has(ref)) {
|
|
663
|
+
// deletes are monotonic, so this stays sound under re-computation
|
|
664
|
+
if (loading)
|
|
665
|
+
continue;
|
|
666
|
+
preexisting.delete(ref);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (loading)
|
|
670
|
+
pending = true;
|
|
671
|
+
}
|
|
672
|
+
return pending;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
643
675
|
/**
|
|
644
676
|
* Returns a register function bound to the nearest transition scope: it adds a resource
|
|
645
677
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
@@ -677,38 +709,43 @@ function registerResource(res, opt) {
|
|
|
677
709
|
function injectStartTransition() {
|
|
678
710
|
const scope = injectTransitionScope();
|
|
679
711
|
const injector = inject(Injector);
|
|
712
|
+
const destroyRef = inject(DestroyRef);
|
|
680
713
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
681
714
|
return (fn) => {
|
|
715
|
+
// attributed: loads already in flight when the transition starts are not ours —
|
|
716
|
+
// they can neither settle this transition early nor block it forever
|
|
717
|
+
const pending = createAttributedPending(scope);
|
|
682
718
|
untracked(fn);
|
|
683
719
|
let sawPending = false;
|
|
684
720
|
const done = new Promise((resolve) => {
|
|
721
|
+
const settle = () => {
|
|
722
|
+
releaseDestroy();
|
|
723
|
+
watcher.destroy();
|
|
724
|
+
resolve();
|
|
725
|
+
};
|
|
685
726
|
const watcher = effect(() => {
|
|
686
|
-
const p =
|
|
727
|
+
const p = pending();
|
|
687
728
|
if (p)
|
|
688
729
|
sawPending = true;
|
|
689
730
|
// settle: requests went in flight and then drained
|
|
690
|
-
if (sawPending && !p)
|
|
691
|
-
|
|
692
|
-
resolve();
|
|
693
|
-
}
|
|
731
|
+
if (sawPending && !p)
|
|
732
|
+
settle();
|
|
694
733
|
}, ...(ngDevMode ? [{ debugName: "watcher", injector }] : [{ injector }]));
|
|
734
|
+
// a destroy mid-flight kills the watcher — resolve so awaiters never hang
|
|
735
|
+
const releaseDestroy = destroyRef.onDestroy(settle);
|
|
695
736
|
if (onServer) {
|
|
696
|
-
if (!untracked(
|
|
697
|
-
|
|
698
|
-
resolve();
|
|
699
|
-
}
|
|
737
|
+
if (!untracked(pending))
|
|
738
|
+
settle();
|
|
700
739
|
return;
|
|
701
740
|
}
|
|
702
741
|
// no-async fallback: once the reactive system has processed the writes (afterNextRender),
|
|
703
742
|
// if nothing ever went in flight, the transition is already complete.
|
|
704
743
|
afterNextRender(() => {
|
|
705
|
-
if (!sawPending && !untracked(
|
|
706
|
-
|
|
707
|
-
resolve();
|
|
708
|
-
}
|
|
744
|
+
if (!sawPending && !untracked(pending))
|
|
745
|
+
settle();
|
|
709
746
|
}, { injector });
|
|
710
747
|
});
|
|
711
|
-
return { pending
|
|
748
|
+
return { pending, done };
|
|
712
749
|
};
|
|
713
750
|
}
|
|
714
751
|
|
|
@@ -779,8 +816,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
779
816
|
}] });
|
|
780
817
|
/**
|
|
781
818
|
* Unscoped suspense boundary — **reads the ambient scope** instead of providing one. For cases where
|
|
782
|
-
* the resources to coordinate are registered *above* the boundary
|
|
783
|
-
* manifests/connectors register at a higher injector), so the boundary observes that outer scope
|
|
819
|
+
* the resources to coordinate are registered *above* the boundary so the boundary observes that outer scope
|
|
784
820
|
* rather than opening a fresh one. Pair with a `provideTransitionScope()` (or another boundary) in an
|
|
785
821
|
* ancestor.
|
|
786
822
|
*/
|
|
@@ -845,9 +881,13 @@ function runInTransaction(txn, fn) {
|
|
|
845
881
|
function injectStartTransaction() {
|
|
846
882
|
const scope = injectTransitionScope();
|
|
847
883
|
const injector = inject(Injector);
|
|
884
|
+
const destroyRef = inject(DestroyRef);
|
|
848
885
|
const onServer = isPlatformServer(inject(PLATFORM_ID, { optional: true }) ?? 'browser');
|
|
849
886
|
return (fn) => {
|
|
850
887
|
const txn = createTransaction();
|
|
888
|
+
// attributed: loads already in flight when the transaction starts are not ours —
|
|
889
|
+
// they can neither commit this transaction early nor block its settle forever
|
|
890
|
+
const pending = createAttributedPending(scope);
|
|
851
891
|
// Hold BEFORE the writes, so the display freezes at pre-transaction values.
|
|
852
892
|
scope.beginHold();
|
|
853
893
|
let finished = false;
|
|
@@ -864,6 +904,7 @@ function injectStartTransaction() {
|
|
|
864
904
|
if (finished)
|
|
865
905
|
return;
|
|
866
906
|
finished = true;
|
|
907
|
+
releaseDestroy();
|
|
867
908
|
watcher?.destroy();
|
|
868
909
|
if (restore)
|
|
869
910
|
txn.restore();
|
|
@@ -872,6 +913,10 @@ function injectStartTransaction() {
|
|
|
872
913
|
scope.endHold();
|
|
873
914
|
resolveDone();
|
|
874
915
|
};
|
|
916
|
+
// The scope may outlive the calling context (a component transacting on an ancestor
|
|
917
|
+
// boundary): a destroy mid-flight kills the settle watcher, so without this the hold
|
|
918
|
+
// would leak and freeze the surviving scope forever. Keep the writes — they landed live.
|
|
919
|
+
const releaseDestroy = destroyRef.onDestroy(() => finish(false));
|
|
875
920
|
try {
|
|
876
921
|
runInTransaction(txn, fn);
|
|
877
922
|
}
|
|
@@ -881,25 +926,25 @@ function injectStartTransaction() {
|
|
|
881
926
|
}
|
|
882
927
|
let sawPending = false;
|
|
883
928
|
watcher = effect(() => {
|
|
884
|
-
const p =
|
|
929
|
+
const p = pending();
|
|
885
930
|
if (p)
|
|
886
931
|
sawPending = true;
|
|
887
932
|
if (sawPending && !p)
|
|
888
933
|
finish(false);
|
|
889
934
|
}, { injector });
|
|
890
935
|
if (onServer) {
|
|
891
|
-
if (!untracked(
|
|
936
|
+
if (!untracked(pending))
|
|
892
937
|
finish(false);
|
|
893
938
|
}
|
|
894
939
|
else {
|
|
895
940
|
// no-async fallback: if nothing ever went in flight, settle once the writes are processed.
|
|
896
941
|
afterNextRender(() => {
|
|
897
|
-
if (!sawPending && !untracked(
|
|
942
|
+
if (!sawPending && !untracked(pending))
|
|
898
943
|
finish(false);
|
|
899
944
|
}, { injector });
|
|
900
945
|
}
|
|
901
946
|
return {
|
|
902
|
-
pending
|
|
947
|
+
pending,
|
|
903
948
|
done,
|
|
904
949
|
abort: () => finish(true),
|
|
905
950
|
};
|
|
@@ -2915,13 +2960,21 @@ const IDLE = {
|
|
|
2915
2960
|
pointerId: null,
|
|
2916
2961
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2917
2962
|
button: -1,
|
|
2963
|
+
pointerType: '',
|
|
2964
|
+
origin: null,
|
|
2965
|
+
cancelled: false,
|
|
2918
2966
|
};
|
|
2967
|
+
/** Terminal state of an aborted gesture — same idle shape, `cancelled: true`. */
|
|
2968
|
+
const CANCELLED = { ...IDLE, cancelled: true };
|
|
2919
2969
|
function stateEqual(a, b) {
|
|
2920
2970
|
return (a.active === b.active &&
|
|
2971
|
+
a.cancelled === b.cancelled &&
|
|
2921
2972
|
a.pointerId === b.pointerId &&
|
|
2922
2973
|
a.current.x === b.current.x &&
|
|
2923
2974
|
a.current.y === b.current.y &&
|
|
2924
2975
|
a.button === b.button &&
|
|
2976
|
+
a.pointerType === b.pointerType &&
|
|
2977
|
+
a.origin === b.origin &&
|
|
2925
2978
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2926
2979
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2927
2980
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2955,7 +3008,7 @@ function createPointerDrag(opt) {
|
|
|
2955
3008
|
return base;
|
|
2956
3009
|
}
|
|
2957
3010
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2958
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
3011
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2959
3012
|
const resolve = (t) => {
|
|
2960
3013
|
if (!t)
|
|
2961
3014
|
return null;
|
|
@@ -2976,9 +3029,12 @@ function createPointerDrag(opt) {
|
|
|
2976
3029
|
equal: stateEqual,
|
|
2977
3030
|
debugName,
|
|
2978
3031
|
});
|
|
3032
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2979
3033
|
let startPoint = { x: 0, y: 0 };
|
|
2980
3034
|
let activePointerId = null;
|
|
2981
3035
|
let activeButton = -1;
|
|
3036
|
+
let activePointerType = '';
|
|
3037
|
+
let activeOrigin = null;
|
|
2982
3038
|
let activated = false;
|
|
2983
3039
|
let gesture = null;
|
|
2984
3040
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2990,22 +3046,24 @@ function createPointerDrag(opt) {
|
|
|
2990
3046
|
ctrl: e.ctrlKey,
|
|
2991
3047
|
meta: e.metaKey,
|
|
2992
3048
|
});
|
|
2993
|
-
const end = () => {
|
|
3049
|
+
const end = (cancelled = false) => {
|
|
2994
3050
|
gesture?.abort();
|
|
2995
3051
|
gesture = null;
|
|
2996
3052
|
activePointerId = null;
|
|
2997
3053
|
activeButton = -1;
|
|
3054
|
+
activePointerType = '';
|
|
3055
|
+
activeOrigin = null;
|
|
2998
3056
|
activated = false;
|
|
2999
|
-
state.set(IDLE);
|
|
3000
|
-
state.flush(); // terminal transition: reflect
|
|
3057
|
+
state.set(cancelled ? CANCELLED : IDLE);
|
|
3058
|
+
state.flush(); // terminal transition: reflect idle now, not on the trailing edge
|
|
3001
3059
|
};
|
|
3002
3060
|
const onMove = (e) => {
|
|
3003
3061
|
if (e.pointerId !== activePointerId)
|
|
3004
3062
|
return;
|
|
3005
3063
|
const current = coord(e);
|
|
3006
3064
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
3007
|
-
if (!activated &&
|
|
3008
|
-
activated = true;
|
|
3065
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3066
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
3009
3067
|
}
|
|
3010
3068
|
state.set({
|
|
3011
3069
|
active: activated,
|
|
@@ -3015,6 +3073,9 @@ function createPointerDrag(opt) {
|
|
|
3015
3073
|
pointerId: activePointerId,
|
|
3016
3074
|
modifiers: mods(e),
|
|
3017
3075
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3076
|
+
pointerType: activePointerType,
|
|
3077
|
+
origin: activeOrigin,
|
|
3078
|
+
cancelled: false,
|
|
3018
3079
|
});
|
|
3019
3080
|
};
|
|
3020
3081
|
const onUp = (e) => {
|
|
@@ -3023,22 +3084,28 @@ function createPointerDrag(opt) {
|
|
|
3023
3084
|
};
|
|
3024
3085
|
const onCancel = (e) => {
|
|
3025
3086
|
if (e.pointerId === activePointerId)
|
|
3026
|
-
end();
|
|
3087
|
+
end(true);
|
|
3027
3088
|
};
|
|
3028
3089
|
const onKey = (e) => {
|
|
3029
3090
|
if (e.key === 'Escape' && activePointerId !== null)
|
|
3030
|
-
end();
|
|
3091
|
+
end(true);
|
|
3031
3092
|
};
|
|
3032
3093
|
const onDown = (el) => (e) => {
|
|
3033
3094
|
if (activePointerId !== null)
|
|
3034
3095
|
return;
|
|
3035
3096
|
if (!buttons.includes(e.button))
|
|
3036
3097
|
return;
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3098
|
+
const matched = handleSelector
|
|
3099
|
+
? e.target?.closest?.(handleSelector)
|
|
3100
|
+
: el;
|
|
3101
|
+
if (!matched)
|
|
3102
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3103
|
+
if (stopPropagation)
|
|
3104
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3040
3105
|
activePointerId = e.pointerId;
|
|
3041
3106
|
activeButton = e.button;
|
|
3107
|
+
activePointerType = e.pointerType;
|
|
3108
|
+
activeOrigin = matched;
|
|
3042
3109
|
activated = false;
|
|
3043
3110
|
startPoint = coord(e);
|
|
3044
3111
|
try {
|
|
@@ -3064,6 +3131,9 @@ function createPointerDrag(opt) {
|
|
|
3064
3131
|
pointerId: e.pointerId,
|
|
3065
3132
|
modifiers: mods(e),
|
|
3066
3133
|
button: e.button,
|
|
3134
|
+
pointerType: activePointerType,
|
|
3135
|
+
origin: activeOrigin,
|
|
3136
|
+
cancelled: false,
|
|
3067
3137
|
});
|
|
3068
3138
|
};
|
|
3069
3139
|
const attach = (el) => {
|
|
@@ -3073,7 +3143,7 @@ function createPointerDrag(opt) {
|
|
|
3073
3143
|
});
|
|
3074
3144
|
return () => {
|
|
3075
3145
|
controller.abort();
|
|
3076
|
-
end();
|
|
3146
|
+
end(true); // teardown mid-gesture is an abort, not a drop
|
|
3077
3147
|
};
|
|
3078
3148
|
};
|
|
3079
3149
|
if (isSignal(target)) {
|
|
@@ -3091,7 +3161,7 @@ function createPointerDrag(opt) {
|
|
|
3091
3161
|
}
|
|
3092
3162
|
const base = state.asReadonly();
|
|
3093
3163
|
base.unthrottled = state.original;
|
|
3094
|
-
base.cancel = end;
|
|
3164
|
+
base.cancel = () => end(true);
|
|
3095
3165
|
return base;
|
|
3096
3166
|
}
|
|
3097
3167
|
|
|
@@ -3442,17 +3512,14 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3442
3512
|
|
|
3443
3513
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3444
3514
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3515
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3516
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3445
3517
|
/**
|
|
3446
3518
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3447
3519
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3448
3520
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3449
3521
|
*/
|
|
3450
3522
|
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3451
|
-
/**
|
|
3452
|
-
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3453
|
-
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3454
|
-
*/
|
|
3455
|
-
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3456
3523
|
const SIGNAL_FN_PROP = new Set([
|
|
3457
3524
|
'set',
|
|
3458
3525
|
'update',
|
|
@@ -3460,21 +3527,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3460
3527
|
'inline',
|
|
3461
3528
|
'asReadonly',
|
|
3462
3529
|
]);
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
storeCache.delete(prop);
|
|
3530
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3531
|
+
providedIn: 'root',
|
|
3532
|
+
factory: () => new WeakMap(),
|
|
3533
|
+
});
|
|
3534
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3535
|
+
providedIn: 'root',
|
|
3536
|
+
factory: () => {
|
|
3537
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3538
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3539
|
+
const store = cache.get(target);
|
|
3540
|
+
if (store)
|
|
3541
|
+
store.delete(prop);
|
|
3542
|
+
});
|
|
3543
|
+
},
|
|
3478
3544
|
});
|
|
3479
3545
|
/**
|
|
3480
3546
|
* @internal
|
|
@@ -3668,11 +3734,11 @@ function isLeaf(value) {
|
|
|
3668
3734
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3669
3735
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3670
3736
|
*/
|
|
3671
|
-
function getCachedChild(target, prop, build) {
|
|
3672
|
-
let storeCache =
|
|
3737
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3738
|
+
let storeCache = cache.get(target);
|
|
3673
3739
|
if (!storeCache) {
|
|
3674
3740
|
storeCache = new Map();
|
|
3675
|
-
|
|
3741
|
+
cache.set(target, storeCache);
|
|
3676
3742
|
}
|
|
3677
3743
|
const cachedRef = storeCache.get(prop);
|
|
3678
3744
|
if (cachedRef) {
|
|
@@ -3680,42 +3746,47 @@ function getCachedChild(target, prop, build) {
|
|
|
3680
3746
|
if (cached)
|
|
3681
3747
|
return cached;
|
|
3682
3748
|
storeCache.delete(prop);
|
|
3683
|
-
|
|
3749
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3684
3750
|
}
|
|
3685
3751
|
const proxy = build();
|
|
3686
3752
|
const ref = new WeakRef(proxy);
|
|
3687
3753
|
storeCache.set(prop, ref);
|
|
3688
|
-
|
|
3754
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3689
3755
|
return proxy;
|
|
3690
3756
|
}
|
|
3757
|
+
/**
|
|
3758
|
+
* @internal Whether a mutable parent's child value must always re-notify: in-place mutation
|
|
3759
|
+
* keeps an object child's reference stable, so `Object.is` would swallow the change. Decided
|
|
3760
|
+
* per-VALUE (not snapshotted at build) so a union child that becomes an object later still
|
|
3761
|
+
* propagates parent-level mutations.
|
|
3762
|
+
*/
|
|
3763
|
+
function mutableChildEqual(a, b) {
|
|
3764
|
+
if (typeof a === 'object' && a !== null)
|
|
3765
|
+
return false;
|
|
3766
|
+
return Object.is(a, b);
|
|
3767
|
+
}
|
|
3691
3768
|
/**
|
|
3692
3769
|
* @internal Builds the derived child signal for `prop` and wraps it as an array/object substore.
|
|
3693
|
-
*
|
|
3694
|
-
*
|
|
3695
|
-
* is constructed.
|
|
3770
|
+
* Both the read (`v?.[prop]`) and the write (`createFallbackOnChange` copies by the container's
|
|
3771
|
+
* LIVE shape) are shape-adaptive, so a child cached before an array↔record↔null union flip stays
|
|
3772
|
+
* correct after it. The only place a child node is constructed — shared by every container kind.
|
|
3696
3773
|
*/
|
|
3697
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3774
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3698
3775
|
const value = untracked(target);
|
|
3699
|
-
const
|
|
3700
|
-
const valueIsArray = Array.isArray(value);
|
|
3701
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3776
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3702
3777
|
const vivifyFn = createVivify(nodeVivify);
|
|
3703
|
-
const equalFn = (
|
|
3704
|
-
|
|
3705
|
-
typeof value[prop] === 'object'
|
|
3706
|
-
? () => false
|
|
3778
|
+
const equalFn = isMutableSource && (isRecord(value) || Array.isArray(value))
|
|
3779
|
+
? mutableChildEqual
|
|
3707
3780
|
: undefined;
|
|
3708
|
-
const computation =
|
|
3709
|
-
|
|
3710
|
-
:
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
equal: equalFn,
|
|
3714
|
-
});
|
|
3781
|
+
const computation = derived(target, {
|
|
3782
|
+
from: (v) => v?.[prop],
|
|
3783
|
+
onChange: createFallbackOnChange(target, prop, vivifyFn, isMutableSource),
|
|
3784
|
+
equal: equalFn,
|
|
3785
|
+
});
|
|
3715
3786
|
const childSample = untracked(computation);
|
|
3716
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3717
|
-
const proxy = toStore(computation,
|
|
3718
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3787
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3788
|
+
const proxy = toStore(computation, options);
|
|
3789
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3719
3790
|
return proxy;
|
|
3720
3791
|
}
|
|
3721
3792
|
/**
|
|
@@ -3733,7 +3804,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3733
3804
|
* const state = store({ user: { name: 'John' } });
|
|
3734
3805
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3735
3806
|
*/
|
|
3736
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3807
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3737
3808
|
if (isStore(source))
|
|
3738
3809
|
return source;
|
|
3739
3810
|
if (!injector)
|
|
@@ -3753,6 +3824,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3753
3824
|
return 'record';
|
|
3754
3825
|
return 'primitive';
|
|
3755
3826
|
}, ...(ngDevMode ? [{ debugName: "kind" }] : []));
|
|
3827
|
+
const STORE_OPTIONS = {
|
|
3828
|
+
injector,
|
|
3829
|
+
vivify,
|
|
3830
|
+
noUnionLeaves,
|
|
3831
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3832
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3833
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3834
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3835
|
+
},
|
|
3836
|
+
};
|
|
3756
3837
|
// built lazily so non-array nodes never allocate it
|
|
3757
3838
|
let length;
|
|
3758
3839
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3806,21 +3887,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3806
3887
|
return { enumerable: true, configurable: true };
|
|
3807
3888
|
},
|
|
3808
3889
|
get(target, prop, receiver) {
|
|
3809
|
-
if (prop ===
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3890
|
+
if (typeof prop === 'symbol') {
|
|
3891
|
+
if (prop === IS_STORE)
|
|
3892
|
+
return true;
|
|
3893
|
+
if (prop === STORE_KIND)
|
|
3894
|
+
return isMutableSource
|
|
3895
|
+
? 'mutable'
|
|
3896
|
+
: isWritableSource
|
|
3897
|
+
? 'writable'
|
|
3898
|
+
: 'readonly';
|
|
3899
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3900
|
+
return STORE_OPTIONS;
|
|
3901
|
+
}
|
|
3819
3902
|
if (prop === 'asReadonlyStore')
|
|
3820
3903
|
return () => {
|
|
3821
3904
|
if (!isWritableSource)
|
|
3822
3905
|
return s;
|
|
3823
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3906
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3824
3907
|
};
|
|
3825
3908
|
const k = untracked(kind);
|
|
3826
3909
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3828,7 +3911,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3828
3911
|
? 'mutable'
|
|
3829
3912
|
: isWritableSource
|
|
3830
3913
|
? 'writable'
|
|
3831
|
-
: 'readonly',
|
|
3914
|
+
: 'readonly', STORE_OPTIONS);
|
|
3832
3915
|
if (k === 'array') {
|
|
3833
3916
|
if (prop === 'length')
|
|
3834
3917
|
return arrayLength();
|
|
@@ -3845,7 +3928,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3845
3928
|
return target[prop];
|
|
3846
3929
|
if (k === 'array' && !isIndexProp(prop))
|
|
3847
3930
|
return Reflect.get(target, prop, receiver);
|
|
3848
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3931
|
+
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);
|
|
3849
3932
|
},
|
|
3850
3933
|
});
|
|
3851
3934
|
return s;
|
|
@@ -3859,14 +3942,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3859
3942
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3860
3943
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3861
3944
|
*/
|
|
3862
|
-
function scopedStore(parent, seed, kind,
|
|
3945
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3863
3946
|
const local = isSignal(seed)
|
|
3864
|
-
? toStore(seed,
|
|
3947
|
+
? toStore(seed, options)
|
|
3865
3948
|
: kind === 'mutable'
|
|
3866
|
-
? mutableStore(seed,
|
|
3949
|
+
? mutableStore(seed, options)
|
|
3867
3950
|
: kind === 'readonly'
|
|
3868
|
-
? store(seed,
|
|
3869
|
-
: store(seed,
|
|
3951
|
+
? store(seed, options).asReadonlyStore()
|
|
3952
|
+
: store(seed, options);
|
|
3870
3953
|
const localValue = () => untracked(local);
|
|
3871
3954
|
const parentValue = () => untracked(parent);
|
|
3872
3955
|
const view = computed(() => ({
|
|
@@ -3897,18 +3980,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3897
3980
|
}
|
|
3898
3981
|
const scope = new Proxy(base, {
|
|
3899
3982
|
get(target, prop) {
|
|
3900
|
-
if (prop ===
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3983
|
+
if (typeof prop === 'symbol') {
|
|
3984
|
+
if (prop === IS_STORE)
|
|
3985
|
+
return true;
|
|
3986
|
+
if (prop === STORE_KIND)
|
|
3987
|
+
return kind;
|
|
3988
|
+
if (prop === SCOPE_PARENT)
|
|
3989
|
+
return parent;
|
|
3990
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3991
|
+
return options;
|
|
3992
|
+
}
|
|
3908
3993
|
if (prop === 'extend')
|
|
3909
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3994
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3910
3995
|
if (prop === 'asReadonlyStore')
|
|
3911
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3996
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3912
3997
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3913
3998
|
return target[prop];
|
|
3914
3999
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3954,22 +4039,34 @@ function storeKind(s) {
|
|
|
3954
4039
|
* scoped.count.set(1); // writes through to base
|
|
3955
4040
|
* scoped.label.set('x'); // stays local
|
|
3956
4041
|
*/
|
|
3957
|
-
function extendStore(store, source,
|
|
3958
|
-
|
|
4042
|
+
function extendStore(store, source, options) {
|
|
4043
|
+
const opt = {
|
|
4044
|
+
...store[STORE_SHARED_OPTIONS],
|
|
4045
|
+
...options,
|
|
4046
|
+
};
|
|
4047
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3959
4048
|
}
|
|
3960
4049
|
/**
|
|
3961
4050
|
* Creates a WritableSignalStore from a value.
|
|
3962
4051
|
* @see {@link toStore}
|
|
3963
4052
|
*/
|
|
3964
4053
|
function store(value, opt) {
|
|
3965
|
-
return toStore(signal(value, opt),
|
|
4054
|
+
return toStore(signal(value, opt), {
|
|
4055
|
+
vivify: false,
|
|
4056
|
+
noUnionLeaves: false,
|
|
4057
|
+
...opt,
|
|
4058
|
+
});
|
|
3966
4059
|
}
|
|
3967
4060
|
/**
|
|
3968
4061
|
* Creates a MutableSignalStore from a value.
|
|
3969
4062
|
* @see {@link toStore}
|
|
3970
4063
|
*/
|
|
3971
4064
|
function mutableStore(value, opt) {
|
|
3972
|
-
return toStore(mutable(value, opt),
|
|
4065
|
+
return toStore(mutable(value, opt), {
|
|
4066
|
+
vivify: false,
|
|
4067
|
+
noUnionLeaves: false,
|
|
4068
|
+
...opt,
|
|
4069
|
+
});
|
|
3973
4070
|
}
|
|
3974
4071
|
|
|
3975
4072
|
function isPlainRecord(value) {
|
|
@@ -4034,7 +4131,13 @@ function forkStore(base, opt) {
|
|
|
4034
4131
|
source: () => base(),
|
|
4035
4132
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs),
|
|
4036
4133
|
}]));
|
|
4037
|
-
|
|
4134
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4135
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4136
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4137
|
+
const store = toStore(staged, {
|
|
4138
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4139
|
+
...opt,
|
|
4140
|
+
});
|
|
4038
4141
|
return {
|
|
4039
4142
|
store,
|
|
4040
4143
|
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
|