@mmstack/primitives 21.2.2 → 21.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`
|
|
@@ -2893,6 +2893,8 @@ const IDLE = {
|
|
|
2893
2893
|
pointerId: null,
|
|
2894
2894
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2895
2895
|
button: -1,
|
|
2896
|
+
pointerType: '',
|
|
2897
|
+
origin: null,
|
|
2896
2898
|
};
|
|
2897
2899
|
function stateEqual(a, b) {
|
|
2898
2900
|
return (a.active === b.active &&
|
|
@@ -2900,6 +2902,8 @@ function stateEqual(a, b) {
|
|
|
2900
2902
|
a.current.x === b.current.x &&
|
|
2901
2903
|
a.current.y === b.current.y &&
|
|
2902
2904
|
a.button === b.button &&
|
|
2905
|
+
a.pointerType === b.pointerType &&
|
|
2906
|
+
a.origin === b.origin &&
|
|
2903
2907
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2904
2908
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2905
2909
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2933,7 +2937,7 @@ function createPointerDrag(opt) {
|
|
|
2933
2937
|
return base;
|
|
2934
2938
|
}
|
|
2935
2939
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2936
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2940
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2937
2941
|
const resolve = (t) => {
|
|
2938
2942
|
if (!t)
|
|
2939
2943
|
return null;
|
|
@@ -2954,9 +2958,12 @@ function createPointerDrag(opt) {
|
|
|
2954
2958
|
equal: stateEqual,
|
|
2955
2959
|
debugName,
|
|
2956
2960
|
});
|
|
2961
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2957
2962
|
let startPoint = { x: 0, y: 0 };
|
|
2958
2963
|
let activePointerId = null;
|
|
2959
2964
|
let activeButton = -1;
|
|
2965
|
+
let activePointerType = '';
|
|
2966
|
+
let activeOrigin = null;
|
|
2960
2967
|
let activated = false;
|
|
2961
2968
|
let gesture = null;
|
|
2962
2969
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2973,6 +2980,8 @@ function createPointerDrag(opt) {
|
|
|
2973
2980
|
gesture = null;
|
|
2974
2981
|
activePointerId = null;
|
|
2975
2982
|
activeButton = -1;
|
|
2983
|
+
activePointerType = '';
|
|
2984
|
+
activeOrigin = null;
|
|
2976
2985
|
activated = false;
|
|
2977
2986
|
state.set(IDLE);
|
|
2978
2987
|
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
@@ -2982,8 +2991,8 @@ function createPointerDrag(opt) {
|
|
|
2982
2991
|
return;
|
|
2983
2992
|
const current = coord(e);
|
|
2984
2993
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
2985
|
-
if (!activated &&
|
|
2986
|
-
activated = true;
|
|
2994
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
2995
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
2987
2996
|
}
|
|
2988
2997
|
state.set({
|
|
2989
2998
|
active: activated,
|
|
@@ -2993,6 +3002,8 @@ function createPointerDrag(opt) {
|
|
|
2993
3002
|
pointerId: activePointerId,
|
|
2994
3003
|
modifiers: mods(e),
|
|
2995
3004
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3005
|
+
pointerType: activePointerType,
|
|
3006
|
+
origin: activeOrigin,
|
|
2996
3007
|
});
|
|
2997
3008
|
};
|
|
2998
3009
|
const onUp = (e) => {
|
|
@@ -3012,11 +3023,17 @@ function createPointerDrag(opt) {
|
|
|
3012
3023
|
return;
|
|
3013
3024
|
if (!buttons.includes(e.button))
|
|
3014
3025
|
return;
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3026
|
+
const matched = handleSelector
|
|
3027
|
+
? e.target?.closest?.(handleSelector)
|
|
3028
|
+
: el;
|
|
3029
|
+
if (!matched)
|
|
3030
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3031
|
+
if (stopPropagation)
|
|
3032
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3018
3033
|
activePointerId = e.pointerId;
|
|
3019
3034
|
activeButton = e.button;
|
|
3035
|
+
activePointerType = e.pointerType;
|
|
3036
|
+
activeOrigin = matched;
|
|
3020
3037
|
activated = false;
|
|
3021
3038
|
startPoint = coord(e);
|
|
3022
3039
|
try {
|
|
@@ -3042,6 +3059,8 @@ function createPointerDrag(opt) {
|
|
|
3042
3059
|
pointerId: e.pointerId,
|
|
3043
3060
|
modifiers: mods(e),
|
|
3044
3061
|
button: e.button,
|
|
3062
|
+
pointerType: activePointerType,
|
|
3063
|
+
origin: activeOrigin,
|
|
3045
3064
|
});
|
|
3046
3065
|
};
|
|
3047
3066
|
const attach = (el) => {
|
|
@@ -3600,17 +3619,14 @@ function isLeaf(value) {
|
|
|
3600
3619
|
|
|
3601
3620
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3602
3621
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3622
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3623
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3603
3624
|
/**
|
|
3604
3625
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3605
3626
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3606
3627
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3607
3628
|
*/
|
|
3608
3629
|
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
3630
|
const SIGNAL_FN_PROP = new Set([
|
|
3615
3631
|
'set',
|
|
3616
3632
|
'update',
|
|
@@ -3618,21 +3634,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3618
3634
|
'inline',
|
|
3619
3635
|
'asReadonly',
|
|
3620
3636
|
]);
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
storeCache.delete(prop);
|
|
3637
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3638
|
+
providedIn: 'root',
|
|
3639
|
+
factory: () => new WeakMap(),
|
|
3640
|
+
});
|
|
3641
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3642
|
+
providedIn: 'root',
|
|
3643
|
+
factory: () => {
|
|
3644
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3645
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3646
|
+
const store = cache.get(target);
|
|
3647
|
+
if (store)
|
|
3648
|
+
store.delete(prop);
|
|
3649
|
+
});
|
|
3650
|
+
},
|
|
3636
3651
|
});
|
|
3637
3652
|
/**
|
|
3638
3653
|
* @internal
|
|
@@ -3649,11 +3664,11 @@ function isStore(value) {
|
|
|
3649
3664
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3650
3665
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3651
3666
|
*/
|
|
3652
|
-
function getCachedChild(target, prop, build) {
|
|
3653
|
-
let storeCache =
|
|
3667
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3668
|
+
let storeCache = cache.get(target);
|
|
3654
3669
|
if (!storeCache) {
|
|
3655
3670
|
storeCache = new Map();
|
|
3656
|
-
|
|
3671
|
+
cache.set(target, storeCache);
|
|
3657
3672
|
}
|
|
3658
3673
|
const cachedRef = storeCache.get(prop);
|
|
3659
3674
|
if (cachedRef) {
|
|
@@ -3661,12 +3676,12 @@ function getCachedChild(target, prop, build) {
|
|
|
3661
3676
|
if (cached)
|
|
3662
3677
|
return cached;
|
|
3663
3678
|
storeCache.delete(prop);
|
|
3664
|
-
|
|
3679
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3665
3680
|
}
|
|
3666
3681
|
const proxy = build();
|
|
3667
3682
|
const ref = new WeakRef(proxy);
|
|
3668
3683
|
storeCache.set(prop, ref);
|
|
3669
|
-
|
|
3684
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3670
3685
|
return proxy;
|
|
3671
3686
|
}
|
|
3672
3687
|
/**
|
|
@@ -3675,11 +3690,11 @@ function getCachedChild(target, prop, build) {
|
|
|
3675
3690
|
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3676
3691
|
* is constructed.
|
|
3677
3692
|
*/
|
|
3678
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3693
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3679
3694
|
const value = untracked(target);
|
|
3680
3695
|
const valueIsRecord = isRecord(value);
|
|
3681
3696
|
const valueIsArray = Array.isArray(value);
|
|
3682
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3697
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3683
3698
|
const vivifyFn = createVivify(nodeVivify);
|
|
3684
3699
|
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3685
3700
|
isMutableSource &&
|
|
@@ -3694,9 +3709,9 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3694
3709
|
equal: equalFn,
|
|
3695
3710
|
});
|
|
3696
3711
|
const childSample = untracked(computation);
|
|
3697
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3698
|
-
const proxy = toStore(computation,
|
|
3699
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3712
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3713
|
+
const proxy = toStore(computation, options);
|
|
3714
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3700
3715
|
return proxy;
|
|
3701
3716
|
}
|
|
3702
3717
|
/**
|
|
@@ -3714,7 +3729,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3714
3729
|
* const state = store({ user: { name: 'John' } });
|
|
3715
3730
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3716
3731
|
*/
|
|
3717
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3732
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3718
3733
|
if (isStore(source))
|
|
3719
3734
|
return source;
|
|
3720
3735
|
if (!injector)
|
|
@@ -3734,6 +3749,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3734
3749
|
return 'record';
|
|
3735
3750
|
return 'primitive';
|
|
3736
3751
|
}, ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
|
|
3752
|
+
const STORE_OPTIONS = {
|
|
3753
|
+
injector,
|
|
3754
|
+
vivify,
|
|
3755
|
+
noUnionLeaves,
|
|
3756
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3757
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3758
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3759
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3760
|
+
},
|
|
3761
|
+
};
|
|
3737
3762
|
// built lazily so non-array nodes never allocate it
|
|
3738
3763
|
let length;
|
|
3739
3764
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3787,21 +3812,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3787
3812
|
return { enumerable: true, configurable: true };
|
|
3788
3813
|
},
|
|
3789
3814
|
get(target, prop, receiver) {
|
|
3790
|
-
if (prop ===
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3815
|
+
if (typeof prop === 'symbol') {
|
|
3816
|
+
if (prop === IS_STORE)
|
|
3817
|
+
return true;
|
|
3818
|
+
if (prop === STORE_KIND)
|
|
3819
|
+
return isMutableSource
|
|
3820
|
+
? 'mutable'
|
|
3821
|
+
: isWritableSource
|
|
3822
|
+
? 'writable'
|
|
3823
|
+
: 'readonly';
|
|
3824
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3825
|
+
return STORE_OPTIONS;
|
|
3826
|
+
}
|
|
3800
3827
|
if (prop === 'asReadonlyStore')
|
|
3801
3828
|
return () => {
|
|
3802
3829
|
if (!isWritableSource)
|
|
3803
3830
|
return s;
|
|
3804
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3831
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3805
3832
|
};
|
|
3806
3833
|
const k = untracked(kind);
|
|
3807
3834
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3809,7 +3836,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3809
3836
|
? 'mutable'
|
|
3810
3837
|
: isWritableSource
|
|
3811
3838
|
? 'writable'
|
|
3812
|
-
: 'readonly',
|
|
3839
|
+
: 'readonly', STORE_OPTIONS);
|
|
3813
3840
|
if (k === 'array') {
|
|
3814
3841
|
if (prop === 'length')
|
|
3815
3842
|
return arrayLength();
|
|
@@ -3826,7 +3853,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3826
3853
|
return target[prop];
|
|
3827
3854
|
if (k === 'array' && !isIndexProp(prop))
|
|
3828
3855
|
return Reflect.get(target, prop, receiver);
|
|
3829
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3856
|
+
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
3857
|
},
|
|
3831
3858
|
});
|
|
3832
3859
|
return s;
|
|
@@ -3840,14 +3867,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3840
3867
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3841
3868
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3842
3869
|
*/
|
|
3843
|
-
function scopedStore(parent, seed, kind,
|
|
3870
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3844
3871
|
const local = isSignal(seed)
|
|
3845
|
-
? toStore(seed,
|
|
3872
|
+
? toStore(seed, options)
|
|
3846
3873
|
: kind === 'mutable'
|
|
3847
|
-
? mutableStore(seed,
|
|
3874
|
+
? mutableStore(seed, options)
|
|
3848
3875
|
: kind === 'readonly'
|
|
3849
|
-
? store(seed,
|
|
3850
|
-
: store(seed,
|
|
3876
|
+
? store(seed, options).asReadonlyStore()
|
|
3877
|
+
: store(seed, options);
|
|
3851
3878
|
const localValue = () => untracked(local);
|
|
3852
3879
|
const parentValue = () => untracked(parent);
|
|
3853
3880
|
const view = computed(() => ({
|
|
@@ -3878,18 +3905,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3878
3905
|
}
|
|
3879
3906
|
const scope = new Proxy(base, {
|
|
3880
3907
|
get(target, prop) {
|
|
3881
|
-
if (prop ===
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3908
|
+
if (typeof prop === 'symbol') {
|
|
3909
|
+
if (prop === IS_STORE)
|
|
3910
|
+
return true;
|
|
3911
|
+
if (prop === STORE_KIND)
|
|
3912
|
+
return kind;
|
|
3913
|
+
if (prop === SCOPE_PARENT)
|
|
3914
|
+
return parent;
|
|
3915
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3916
|
+
return options;
|
|
3917
|
+
}
|
|
3889
3918
|
if (prop === 'extend')
|
|
3890
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3919
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3891
3920
|
if (prop === 'asReadonlyStore')
|
|
3892
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3921
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3893
3922
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3894
3923
|
return target[prop];
|
|
3895
3924
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3935,22 +3964,34 @@ function storeKind(s) {
|
|
|
3935
3964
|
* scoped.count.set(1); // writes through to base
|
|
3936
3965
|
* scoped.label.set('x'); // stays local
|
|
3937
3966
|
*/
|
|
3938
|
-
function extendStore(store, source,
|
|
3939
|
-
|
|
3967
|
+
function extendStore(store, source, options) {
|
|
3968
|
+
const opt = {
|
|
3969
|
+
...store[STORE_SHARED_OPTIONS],
|
|
3970
|
+
...options,
|
|
3971
|
+
};
|
|
3972
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3940
3973
|
}
|
|
3941
3974
|
/**
|
|
3942
3975
|
* Creates a WritableSignalStore from a value.
|
|
3943
3976
|
* @see {@link toStore}
|
|
3944
3977
|
*/
|
|
3945
3978
|
function store(value, opt) {
|
|
3946
|
-
return toStore(signal(value, opt),
|
|
3979
|
+
return toStore(signal(value, opt), {
|
|
3980
|
+
vivify: false,
|
|
3981
|
+
noUnionLeaves: false,
|
|
3982
|
+
...opt,
|
|
3983
|
+
});
|
|
3947
3984
|
}
|
|
3948
3985
|
/**
|
|
3949
3986
|
* Creates a MutableSignalStore from a value.
|
|
3950
3987
|
* @see {@link toStore}
|
|
3951
3988
|
*/
|
|
3952
3989
|
function mutableStore(value, opt) {
|
|
3953
|
-
return toStore(mutable(value, opt),
|
|
3990
|
+
return toStore(mutable(value, opt), {
|
|
3991
|
+
vivify: false,
|
|
3992
|
+
noUnionLeaves: false,
|
|
3993
|
+
...opt,
|
|
3994
|
+
});
|
|
3954
3995
|
}
|
|
3955
3996
|
|
|
3956
3997
|
function isPlainRecord(value) {
|
|
@@ -4012,7 +4053,13 @@ function forkStore(base, opt) {
|
|
|
4012
4053
|
const merge = reconcile;
|
|
4013
4054
|
const staged = linkedSignal({ ...(ngDevMode ? { debugName: "staged" } : /* istanbul ignore next */ {}), source: () => base(),
|
|
4014
4055
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs) });
|
|
4015
|
-
|
|
4056
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4057
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4058
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4059
|
+
const store = toStore(staged, {
|
|
4060
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4061
|
+
...opt,
|
|
4062
|
+
});
|
|
4016
4063
|
return {
|
|
4017
4064
|
store,
|
|
4018
4065
|
commit: () => base.set(untracked(staged)),
|