@mmstack/primitives 22.2.2 → 22.3.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 +12 -3
- package/fesm2022/mmstack-primitives.mjs +117 -70
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +80 -23
package/README.md
CHANGED
|
@@ -117,6 +117,8 @@ Each level's shape is resolved from what's known: a value that is currently an o
|
|
|
117
117
|
|
|
118
118
|
Top-level array support isn't exposed yet — use `indexArray` / `keyArray` for those.
|
|
119
119
|
|
|
120
|
+
**Union leaves (perf opt-in).** `noUnionLeaves: true` promises no node ever flips between a leaf and a sub-store, so each node's leaf-ness is resolved once on first access and cached instead of staying reactive. Off by default — leave it off if a value can switch between a primitive and an object/array.
|
|
121
|
+
|
|
120
122
|
### `extendStore` (scoped overlay)
|
|
121
123
|
|
|
122
124
|
`extendStore(store, seed)` (on any store kind) creates a **scoped overlay** — a child store that **shares** the parent's signals for inherited keys (the same `WritableSignal`: writes go through to the parent and parent changes flow down) while keeping the seed and any new keys in a **local layer** that never propagates upward. No diffing, no syncing — local keys simply aren't wired to the parent.
|
|
@@ -145,7 +147,7 @@ const scope = extendStore(app, draft); // writes to scope.title flow out to `dra
|
|
|
145
147
|
|
|
146
148
|
A few release notes:
|
|
147
149
|
|
|
148
|
-
- The
|
|
150
|
+
- The scope inherits the parent's config (`vivify` / `noUnionLeaves`) and its injector-scoped proxy cache, so **both** inherited and local paths vivify when the parent was created with `vivify`. `extendStore` doesn't accept `vivify` / `noUnionLeaves` — they always come from the parent.
|
|
149
151
|
- Reserved names — `asReadonlyStore` and the signal methods (`set` / `update` / `mutate` / `inline` / `asReadonly`) — shadow same-named data keys, as on any store.
|
|
150
152
|
- `scope.asReadonlyStore()` returns a read-only **snapshot view** of the merge (reactive reads, no writes); it does not share sub-store identity.
|
|
151
153
|
|
|
@@ -173,7 +175,7 @@ The fork is a full store (`draft.store.user.name(...)`, `extendStore`, deep read
|
|
|
173
175
|
- **`'coarse'`** — any base change resets the whole fork. Cheapest; correct when the base is held for the fork's lifetime (e.g. a transition). The default for a mutable base.
|
|
174
176
|
- **a `ReconcileFn<T>`** — `(ancestor, mine, theirs) => merged`, for bring-your-own merge (array-by-id, Immer patches, CRDT-ish).
|
|
175
177
|
|
|
176
|
-
>
|
|
178
|
+
> The fork inherits the base's `vivify` / `noUnionLeaves` and its injector-scoped proxy cache automatically, so its write semantics match the base. Pass them explicitly only to override (advanced).
|
|
177
179
|
|
|
178
180
|
### `toWritable`
|
|
179
181
|
|
|
@@ -668,6 +670,13 @@ once the pointer travels past `activationThreshold`, so the same element stays
|
|
|
668
670
|
clickable. Uses `setPointerCapture`, supports a delegated `handleSelector`, and
|
|
669
671
|
cancels on Escape or via `.cancel()`.
|
|
670
672
|
|
|
673
|
+
A delegated `handleSelector` reports which child actually started the drag via
|
|
674
|
+
`drag().origin` (so one listener on a container can serve many handles), and
|
|
675
|
+
`stopPropagation: true` lets an inner sensor claim the `pointerdown` over an
|
|
676
|
+
outer one on the same tree (e.g. a nested sortable). Reads are throttled
|
|
677
|
+
(`throttle`, default 16ms); `drag.unthrottled()` exposes the un-throttled view
|
|
678
|
+
for logic that needs the exact release position.
|
|
679
|
+
|
|
671
680
|
```typescript
|
|
672
681
|
import { sensor } from '@mmstack/primitives';
|
|
673
682
|
|
|
@@ -678,7 +687,7 @@ const position = computed(() => {
|
|
|
678
687
|
const d = drag();
|
|
679
688
|
return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
|
|
680
689
|
});
|
|
681
|
-
// drag().modifiers.shift → e.g. constrain axis · drag.cancel() → revert
|
|
690
|
+
// drag().modifiers.shift → e.g. constrain axis · drag().origin → the handle · drag.cancel() → revert
|
|
682
691
|
```
|
|
683
692
|
|
|
684
693
|
### `signalFromEvent`
|
|
@@ -2919,6 +2919,8 @@ const IDLE = {
|
|
|
2919
2919
|
pointerId: null,
|
|
2920
2920
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2921
2921
|
button: -1,
|
|
2922
|
+
pointerType: '',
|
|
2923
|
+
origin: null,
|
|
2922
2924
|
};
|
|
2923
2925
|
function stateEqual(a, b) {
|
|
2924
2926
|
return (a.active === b.active &&
|
|
@@ -2926,6 +2928,8 @@ function stateEqual(a, b) {
|
|
|
2926
2928
|
a.current.x === b.current.x &&
|
|
2927
2929
|
a.current.y === b.current.y &&
|
|
2928
2930
|
a.button === b.button &&
|
|
2931
|
+
a.pointerType === b.pointerType &&
|
|
2932
|
+
a.origin === b.origin &&
|
|
2929
2933
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2930
2934
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2931
2935
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2959,7 +2963,7 @@ function createPointerDrag(opt) {
|
|
|
2959
2963
|
return base;
|
|
2960
2964
|
}
|
|
2961
2965
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2962
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2966
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2963
2967
|
const resolve = (t) => {
|
|
2964
2968
|
if (!t)
|
|
2965
2969
|
return null;
|
|
@@ -2980,9 +2984,12 @@ function createPointerDrag(opt) {
|
|
|
2980
2984
|
equal: stateEqual,
|
|
2981
2985
|
debugName,
|
|
2982
2986
|
});
|
|
2987
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2983
2988
|
let startPoint = { x: 0, y: 0 };
|
|
2984
2989
|
let activePointerId = null;
|
|
2985
2990
|
let activeButton = -1;
|
|
2991
|
+
let activePointerType = '';
|
|
2992
|
+
let activeOrigin = null;
|
|
2986
2993
|
let activated = false;
|
|
2987
2994
|
let gesture = null;
|
|
2988
2995
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2999,6 +3006,8 @@ function createPointerDrag(opt) {
|
|
|
2999
3006
|
gesture = null;
|
|
3000
3007
|
activePointerId = null;
|
|
3001
3008
|
activeButton = -1;
|
|
3009
|
+
activePointerType = '';
|
|
3010
|
+
activeOrigin = null;
|
|
3002
3011
|
activated = false;
|
|
3003
3012
|
state.set(IDLE);
|
|
3004
3013
|
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
@@ -3008,8 +3017,8 @@ function createPointerDrag(opt) {
|
|
|
3008
3017
|
return;
|
|
3009
3018
|
const current = coord(e);
|
|
3010
3019
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
3011
|
-
if (!activated &&
|
|
3012
|
-
activated = true;
|
|
3020
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3021
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
3013
3022
|
}
|
|
3014
3023
|
state.set({
|
|
3015
3024
|
active: activated,
|
|
@@ -3019,6 +3028,8 @@ function createPointerDrag(opt) {
|
|
|
3019
3028
|
pointerId: activePointerId,
|
|
3020
3029
|
modifiers: mods(e),
|
|
3021
3030
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3031
|
+
pointerType: activePointerType,
|
|
3032
|
+
origin: activeOrigin,
|
|
3022
3033
|
});
|
|
3023
3034
|
};
|
|
3024
3035
|
const onUp = (e) => {
|
|
@@ -3038,11 +3049,17 @@ function createPointerDrag(opt) {
|
|
|
3038
3049
|
return;
|
|
3039
3050
|
if (!buttons.includes(e.button))
|
|
3040
3051
|
return;
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3052
|
+
const matched = handleSelector
|
|
3053
|
+
? e.target?.closest?.(handleSelector)
|
|
3054
|
+
: el;
|
|
3055
|
+
if (!matched)
|
|
3056
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3057
|
+
if (stopPropagation)
|
|
3058
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3044
3059
|
activePointerId = e.pointerId;
|
|
3045
3060
|
activeButton = e.button;
|
|
3061
|
+
activePointerType = e.pointerType;
|
|
3062
|
+
activeOrigin = matched;
|
|
3046
3063
|
activated = false;
|
|
3047
3064
|
startPoint = coord(e);
|
|
3048
3065
|
try {
|
|
@@ -3068,6 +3085,8 @@ function createPointerDrag(opt) {
|
|
|
3068
3085
|
pointerId: e.pointerId,
|
|
3069
3086
|
modifiers: mods(e),
|
|
3070
3087
|
button: e.button,
|
|
3088
|
+
pointerType: activePointerType,
|
|
3089
|
+
origin: activeOrigin,
|
|
3071
3090
|
});
|
|
3072
3091
|
};
|
|
3073
3092
|
const attach = (el) => {
|
|
@@ -3626,17 +3645,14 @@ function isLeaf(value) {
|
|
|
3626
3645
|
|
|
3627
3646
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3628
3647
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3648
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3649
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3629
3650
|
/**
|
|
3630
3651
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3631
3652
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3632
3653
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3633
3654
|
*/
|
|
3634
3655
|
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
3656
|
const SIGNAL_FN_PROP = new Set([
|
|
3641
3657
|
'set',
|
|
3642
3658
|
'update',
|
|
@@ -3644,21 +3660,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3644
3660
|
'inline',
|
|
3645
3661
|
'asReadonly',
|
|
3646
3662
|
]);
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
storeCache.delete(prop);
|
|
3663
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3664
|
+
providedIn: 'root',
|
|
3665
|
+
factory: () => new WeakMap(),
|
|
3666
|
+
});
|
|
3667
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3668
|
+
providedIn: 'root',
|
|
3669
|
+
factory: () => {
|
|
3670
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3671
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3672
|
+
const store = cache.get(target);
|
|
3673
|
+
if (store)
|
|
3674
|
+
store.delete(prop);
|
|
3675
|
+
});
|
|
3676
|
+
},
|
|
3662
3677
|
});
|
|
3663
3678
|
/**
|
|
3664
3679
|
* @internal
|
|
@@ -3675,11 +3690,11 @@ function isStore(value) {
|
|
|
3675
3690
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3676
3691
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3677
3692
|
*/
|
|
3678
|
-
function getCachedChild(target, prop, build) {
|
|
3679
|
-
let storeCache =
|
|
3693
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3694
|
+
let storeCache = cache.get(target);
|
|
3680
3695
|
if (!storeCache) {
|
|
3681
3696
|
storeCache = new Map();
|
|
3682
|
-
|
|
3697
|
+
cache.set(target, storeCache);
|
|
3683
3698
|
}
|
|
3684
3699
|
const cachedRef = storeCache.get(prop);
|
|
3685
3700
|
if (cachedRef) {
|
|
@@ -3687,12 +3702,12 @@ function getCachedChild(target, prop, build) {
|
|
|
3687
3702
|
if (cached)
|
|
3688
3703
|
return cached;
|
|
3689
3704
|
storeCache.delete(prop);
|
|
3690
|
-
|
|
3705
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3691
3706
|
}
|
|
3692
3707
|
const proxy = build();
|
|
3693
3708
|
const ref = new WeakRef(proxy);
|
|
3694
3709
|
storeCache.set(prop, ref);
|
|
3695
|
-
|
|
3710
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3696
3711
|
return proxy;
|
|
3697
3712
|
}
|
|
3698
3713
|
/**
|
|
@@ -3701,11 +3716,11 @@ function getCachedChild(target, prop, build) {
|
|
|
3701
3716
|
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3702
3717
|
* is constructed.
|
|
3703
3718
|
*/
|
|
3704
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3719
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3705
3720
|
const value = untracked(target);
|
|
3706
3721
|
const valueIsRecord = isRecord(value);
|
|
3707
3722
|
const valueIsArray = Array.isArray(value);
|
|
3708
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3723
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3709
3724
|
const vivifyFn = createVivify(nodeVivify);
|
|
3710
3725
|
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3711
3726
|
isMutableSource &&
|
|
@@ -3720,9 +3735,9 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3720
3735
|
equal: equalFn,
|
|
3721
3736
|
});
|
|
3722
3737
|
const childSample = untracked(computation);
|
|
3723
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3724
|
-
const proxy = toStore(computation,
|
|
3725
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3738
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3739
|
+
const proxy = toStore(computation, options);
|
|
3740
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3726
3741
|
return proxy;
|
|
3727
3742
|
}
|
|
3728
3743
|
/**
|
|
@@ -3740,7 +3755,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3740
3755
|
* const state = store({ user: { name: 'John' } });
|
|
3741
3756
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3742
3757
|
*/
|
|
3743
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3758
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3744
3759
|
if (isStore(source))
|
|
3745
3760
|
return source;
|
|
3746
3761
|
if (!injector)
|
|
@@ -3761,6 +3776,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3761
3776
|
return 'primitive';
|
|
3762
3777
|
}, /* @ts-ignore */
|
|
3763
3778
|
...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3779
|
+
const STORE_OPTIONS = {
|
|
3780
|
+
injector,
|
|
3781
|
+
vivify,
|
|
3782
|
+
noUnionLeaves,
|
|
3783
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3784
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3785
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3786
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3787
|
+
},
|
|
3788
|
+
};
|
|
3764
3789
|
// built lazily so non-array nodes never allocate it
|
|
3765
3790
|
let length;
|
|
3766
3791
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3814,21 +3839,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3814
3839
|
return { enumerable: true, configurable: true };
|
|
3815
3840
|
},
|
|
3816
3841
|
get(target, prop, receiver) {
|
|
3817
|
-
if (prop ===
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3842
|
+
if (typeof prop === 'symbol') {
|
|
3843
|
+
if (prop === IS_STORE)
|
|
3844
|
+
return true;
|
|
3845
|
+
if (prop === STORE_KIND)
|
|
3846
|
+
return isMutableSource
|
|
3847
|
+
? 'mutable'
|
|
3848
|
+
: isWritableSource
|
|
3849
|
+
? 'writable'
|
|
3850
|
+
: 'readonly';
|
|
3851
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3852
|
+
return STORE_OPTIONS;
|
|
3853
|
+
}
|
|
3827
3854
|
if (prop === 'asReadonlyStore')
|
|
3828
3855
|
return () => {
|
|
3829
3856
|
if (!isWritableSource)
|
|
3830
3857
|
return s;
|
|
3831
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3858
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3832
3859
|
};
|
|
3833
3860
|
const k = untracked(kind);
|
|
3834
3861
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3836,7 +3863,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3836
3863
|
? 'mutable'
|
|
3837
3864
|
: isWritableSource
|
|
3838
3865
|
? 'writable'
|
|
3839
|
-
: 'readonly',
|
|
3866
|
+
: 'readonly', STORE_OPTIONS);
|
|
3840
3867
|
if (k === 'array') {
|
|
3841
3868
|
if (prop === 'length')
|
|
3842
3869
|
return arrayLength();
|
|
@@ -3853,7 +3880,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3853
3880
|
return target[prop];
|
|
3854
3881
|
if (k === 'array' && !isIndexProp(prop))
|
|
3855
3882
|
return Reflect.get(target, prop, receiver);
|
|
3856
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3883
|
+
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
3884
|
},
|
|
3858
3885
|
});
|
|
3859
3886
|
return s;
|
|
@@ -3867,14 +3894,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3867
3894
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3868
3895
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3869
3896
|
*/
|
|
3870
|
-
function scopedStore(parent, seed, kind,
|
|
3897
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3871
3898
|
const local = isSignal(seed)
|
|
3872
|
-
? toStore(seed,
|
|
3899
|
+
? toStore(seed, options)
|
|
3873
3900
|
: kind === 'mutable'
|
|
3874
|
-
? mutableStore(seed,
|
|
3901
|
+
? mutableStore(seed, options)
|
|
3875
3902
|
: kind === 'readonly'
|
|
3876
|
-
? store(seed,
|
|
3877
|
-
: store(seed,
|
|
3903
|
+
? store(seed, options).asReadonlyStore()
|
|
3904
|
+
: store(seed, options);
|
|
3878
3905
|
const localValue = () => untracked(local);
|
|
3879
3906
|
const parentValue = () => untracked(parent);
|
|
3880
3907
|
const view = computed(() => ({
|
|
@@ -3906,18 +3933,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3906
3933
|
}
|
|
3907
3934
|
const scope = new Proxy(base, {
|
|
3908
3935
|
get(target, prop) {
|
|
3909
|
-
if (prop ===
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3936
|
+
if (typeof prop === 'symbol') {
|
|
3937
|
+
if (prop === IS_STORE)
|
|
3938
|
+
return true;
|
|
3939
|
+
if (prop === STORE_KIND)
|
|
3940
|
+
return kind;
|
|
3941
|
+
if (prop === SCOPE_PARENT)
|
|
3942
|
+
return parent;
|
|
3943
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3944
|
+
return options;
|
|
3945
|
+
}
|
|
3917
3946
|
if (prop === 'extend')
|
|
3918
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3947
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3919
3948
|
if (prop === 'asReadonlyStore')
|
|
3920
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3949
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3921
3950
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3922
3951
|
return target[prop];
|
|
3923
3952
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3963,22 +3992,34 @@ function storeKind(s) {
|
|
|
3963
3992
|
* scoped.count.set(1); // writes through to base
|
|
3964
3993
|
* scoped.label.set('x'); // stays local
|
|
3965
3994
|
*/
|
|
3966
|
-
function extendStore(store, source,
|
|
3967
|
-
|
|
3995
|
+
function extendStore(store, source, options) {
|
|
3996
|
+
const opt = {
|
|
3997
|
+
...store[STORE_SHARED_OPTIONS],
|
|
3998
|
+
...options,
|
|
3999
|
+
};
|
|
4000
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3968
4001
|
}
|
|
3969
4002
|
/**
|
|
3970
4003
|
* Creates a WritableSignalStore from a value.
|
|
3971
4004
|
* @see {@link toStore}
|
|
3972
4005
|
*/
|
|
3973
4006
|
function store(value, opt) {
|
|
3974
|
-
return toStore(signal(value, opt),
|
|
4007
|
+
return toStore(signal(value, opt), {
|
|
4008
|
+
vivify: false,
|
|
4009
|
+
noUnionLeaves: false,
|
|
4010
|
+
...opt,
|
|
4011
|
+
});
|
|
3975
4012
|
}
|
|
3976
4013
|
/**
|
|
3977
4014
|
* Creates a MutableSignalStore from a value.
|
|
3978
4015
|
* @see {@link toStore}
|
|
3979
4016
|
*/
|
|
3980
4017
|
function mutableStore(value, opt) {
|
|
3981
|
-
return toStore(mutable(value, opt),
|
|
4018
|
+
return toStore(mutable(value, opt), {
|
|
4019
|
+
vivify: false,
|
|
4020
|
+
noUnionLeaves: false,
|
|
4021
|
+
...opt,
|
|
4022
|
+
});
|
|
3982
4023
|
}
|
|
3983
4024
|
|
|
3984
4025
|
function isPlainRecord(value) {
|
|
@@ -4040,7 +4081,13 @@ function forkStore(base, opt) {
|
|
|
4040
4081
|
const merge = reconcile;
|
|
4041
4082
|
const staged = linkedSignal({ ...(ngDevMode ? { debugName: "staged" } : /* istanbul ignore next */ {}), source: () => base(),
|
|
4042
4083
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs) });
|
|
4043
|
-
|
|
4084
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4085
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4086
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4087
|
+
const store = toStore(staged, {
|
|
4088
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4089
|
+
...opt,
|
|
4090
|
+
});
|
|
4044
4091
|
return {
|
|
4045
4092
|
store,
|
|
4046
4093
|
commit: () => base.set(untracked(staged)),
|