@mmstack/primitives 20.7.2 → 20.8.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/index.d.ts +80 -23
- package/package.json +1 -1
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`
|
|
@@ -2915,6 +2915,8 @@ const IDLE = {
|
|
|
2915
2915
|
pointerId: null,
|
|
2916
2916
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2917
2917
|
button: -1,
|
|
2918
|
+
pointerType: '',
|
|
2919
|
+
origin: null,
|
|
2918
2920
|
};
|
|
2919
2921
|
function stateEqual(a, b) {
|
|
2920
2922
|
return (a.active === b.active &&
|
|
@@ -2922,6 +2924,8 @@ function stateEqual(a, b) {
|
|
|
2922
2924
|
a.current.x === b.current.x &&
|
|
2923
2925
|
a.current.y === b.current.y &&
|
|
2924
2926
|
a.button === b.button &&
|
|
2927
|
+
a.pointerType === b.pointerType &&
|
|
2928
|
+
a.origin === b.origin &&
|
|
2925
2929
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2926
2930
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2927
2931
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2955,7 +2959,7 @@ function createPointerDrag(opt) {
|
|
|
2955
2959
|
return base;
|
|
2956
2960
|
}
|
|
2957
2961
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2958
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2962
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2959
2963
|
const resolve = (t) => {
|
|
2960
2964
|
if (!t)
|
|
2961
2965
|
return null;
|
|
@@ -2976,9 +2980,12 @@ function createPointerDrag(opt) {
|
|
|
2976
2980
|
equal: stateEqual,
|
|
2977
2981
|
debugName,
|
|
2978
2982
|
});
|
|
2983
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2979
2984
|
let startPoint = { x: 0, y: 0 };
|
|
2980
2985
|
let activePointerId = null;
|
|
2981
2986
|
let activeButton = -1;
|
|
2987
|
+
let activePointerType = '';
|
|
2988
|
+
let activeOrigin = null;
|
|
2982
2989
|
let activated = false;
|
|
2983
2990
|
let gesture = null;
|
|
2984
2991
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2995,6 +3002,8 @@ function createPointerDrag(opt) {
|
|
|
2995
3002
|
gesture = null;
|
|
2996
3003
|
activePointerId = null;
|
|
2997
3004
|
activeButton = -1;
|
|
3005
|
+
activePointerType = '';
|
|
3006
|
+
activeOrigin = null;
|
|
2998
3007
|
activated = false;
|
|
2999
3008
|
state.set(IDLE);
|
|
3000
3009
|
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
@@ -3004,8 +3013,8 @@ function createPointerDrag(opt) {
|
|
|
3004
3013
|
return;
|
|
3005
3014
|
const current = coord(e);
|
|
3006
3015
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
3007
|
-
if (!activated &&
|
|
3008
|
-
activated = true;
|
|
3016
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3017
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
3009
3018
|
}
|
|
3010
3019
|
state.set({
|
|
3011
3020
|
active: activated,
|
|
@@ -3015,6 +3024,8 @@ function createPointerDrag(opt) {
|
|
|
3015
3024
|
pointerId: activePointerId,
|
|
3016
3025
|
modifiers: mods(e),
|
|
3017
3026
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3027
|
+
pointerType: activePointerType,
|
|
3028
|
+
origin: activeOrigin,
|
|
3018
3029
|
});
|
|
3019
3030
|
};
|
|
3020
3031
|
const onUp = (e) => {
|
|
@@ -3034,11 +3045,17 @@ function createPointerDrag(opt) {
|
|
|
3034
3045
|
return;
|
|
3035
3046
|
if (!buttons.includes(e.button))
|
|
3036
3047
|
return;
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3048
|
+
const matched = handleSelector
|
|
3049
|
+
? e.target?.closest?.(handleSelector)
|
|
3050
|
+
: el;
|
|
3051
|
+
if (!matched)
|
|
3052
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3053
|
+
if (stopPropagation)
|
|
3054
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3040
3055
|
activePointerId = e.pointerId;
|
|
3041
3056
|
activeButton = e.button;
|
|
3057
|
+
activePointerType = e.pointerType;
|
|
3058
|
+
activeOrigin = matched;
|
|
3042
3059
|
activated = false;
|
|
3043
3060
|
startPoint = coord(e);
|
|
3044
3061
|
try {
|
|
@@ -3064,6 +3081,8 @@ function createPointerDrag(opt) {
|
|
|
3064
3081
|
pointerId: e.pointerId,
|
|
3065
3082
|
modifiers: mods(e),
|
|
3066
3083
|
button: e.button,
|
|
3084
|
+
pointerType: activePointerType,
|
|
3085
|
+
origin: activeOrigin,
|
|
3067
3086
|
});
|
|
3068
3087
|
};
|
|
3069
3088
|
const attach = (el) => {
|
|
@@ -3442,17 +3461,14 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3442
3461
|
|
|
3443
3462
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3444
3463
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3464
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3465
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3445
3466
|
/**
|
|
3446
3467
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3447
3468
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3448
3469
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3449
3470
|
*/
|
|
3450
3471
|
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
3472
|
const SIGNAL_FN_PROP = new Set([
|
|
3457
3473
|
'set',
|
|
3458
3474
|
'update',
|
|
@@ -3460,21 +3476,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3460
3476
|
'inline',
|
|
3461
3477
|
'asReadonly',
|
|
3462
3478
|
]);
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
storeCache.delete(prop);
|
|
3479
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3480
|
+
providedIn: 'root',
|
|
3481
|
+
factory: () => new WeakMap(),
|
|
3482
|
+
});
|
|
3483
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3484
|
+
providedIn: 'root',
|
|
3485
|
+
factory: () => {
|
|
3486
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3487
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3488
|
+
const store = cache.get(target);
|
|
3489
|
+
if (store)
|
|
3490
|
+
store.delete(prop);
|
|
3491
|
+
});
|
|
3492
|
+
},
|
|
3478
3493
|
});
|
|
3479
3494
|
/**
|
|
3480
3495
|
* @internal
|
|
@@ -3668,11 +3683,11 @@ function isLeaf(value) {
|
|
|
3668
3683
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3669
3684
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3670
3685
|
*/
|
|
3671
|
-
function getCachedChild(target, prop, build) {
|
|
3672
|
-
let storeCache =
|
|
3686
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3687
|
+
let storeCache = cache.get(target);
|
|
3673
3688
|
if (!storeCache) {
|
|
3674
3689
|
storeCache = new Map();
|
|
3675
|
-
|
|
3690
|
+
cache.set(target, storeCache);
|
|
3676
3691
|
}
|
|
3677
3692
|
const cachedRef = storeCache.get(prop);
|
|
3678
3693
|
if (cachedRef) {
|
|
@@ -3680,12 +3695,12 @@ function getCachedChild(target, prop, build) {
|
|
|
3680
3695
|
if (cached)
|
|
3681
3696
|
return cached;
|
|
3682
3697
|
storeCache.delete(prop);
|
|
3683
|
-
|
|
3698
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3684
3699
|
}
|
|
3685
3700
|
const proxy = build();
|
|
3686
3701
|
const ref = new WeakRef(proxy);
|
|
3687
3702
|
storeCache.set(prop, ref);
|
|
3688
|
-
|
|
3703
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3689
3704
|
return proxy;
|
|
3690
3705
|
}
|
|
3691
3706
|
/**
|
|
@@ -3694,11 +3709,11 @@ function getCachedChild(target, prop, build) {
|
|
|
3694
3709
|
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3695
3710
|
* is constructed.
|
|
3696
3711
|
*/
|
|
3697
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3712
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3698
3713
|
const value = untracked(target);
|
|
3699
3714
|
const valueIsRecord = isRecord(value);
|
|
3700
3715
|
const valueIsArray = Array.isArray(value);
|
|
3701
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3716
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3702
3717
|
const vivifyFn = createVivify(nodeVivify);
|
|
3703
3718
|
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3704
3719
|
isMutableSource &&
|
|
@@ -3713,9 +3728,9 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3713
3728
|
equal: equalFn,
|
|
3714
3729
|
});
|
|
3715
3730
|
const childSample = untracked(computation);
|
|
3716
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3717
|
-
const proxy = toStore(computation,
|
|
3718
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3731
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3732
|
+
const proxy = toStore(computation, options);
|
|
3733
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3719
3734
|
return proxy;
|
|
3720
3735
|
}
|
|
3721
3736
|
/**
|
|
@@ -3733,7 +3748,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3733
3748
|
* const state = store({ user: { name: 'John' } });
|
|
3734
3749
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3735
3750
|
*/
|
|
3736
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3751
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3737
3752
|
if (isStore(source))
|
|
3738
3753
|
return source;
|
|
3739
3754
|
if (!injector)
|
|
@@ -3753,6 +3768,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3753
3768
|
return 'record';
|
|
3754
3769
|
return 'primitive';
|
|
3755
3770
|
}, ...(ngDevMode ? [{ debugName: "kind" }] : []));
|
|
3771
|
+
const STORE_OPTIONS = {
|
|
3772
|
+
injector,
|
|
3773
|
+
vivify,
|
|
3774
|
+
noUnionLeaves,
|
|
3775
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3776
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3777
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3778
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3779
|
+
},
|
|
3780
|
+
};
|
|
3756
3781
|
// built lazily so non-array nodes never allocate it
|
|
3757
3782
|
let length;
|
|
3758
3783
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3806,21 +3831,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3806
3831
|
return { enumerable: true, configurable: true };
|
|
3807
3832
|
},
|
|
3808
3833
|
get(target, prop, receiver) {
|
|
3809
|
-
if (prop ===
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3834
|
+
if (typeof prop === 'symbol') {
|
|
3835
|
+
if (prop === IS_STORE)
|
|
3836
|
+
return true;
|
|
3837
|
+
if (prop === STORE_KIND)
|
|
3838
|
+
return isMutableSource
|
|
3839
|
+
? 'mutable'
|
|
3840
|
+
: isWritableSource
|
|
3841
|
+
? 'writable'
|
|
3842
|
+
: 'readonly';
|
|
3843
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3844
|
+
return STORE_OPTIONS;
|
|
3845
|
+
}
|
|
3819
3846
|
if (prop === 'asReadonlyStore')
|
|
3820
3847
|
return () => {
|
|
3821
3848
|
if (!isWritableSource)
|
|
3822
3849
|
return s;
|
|
3823
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3850
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3824
3851
|
};
|
|
3825
3852
|
const k = untracked(kind);
|
|
3826
3853
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3828,7 +3855,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3828
3855
|
? 'mutable'
|
|
3829
3856
|
: isWritableSource
|
|
3830
3857
|
? 'writable'
|
|
3831
|
-
: 'readonly',
|
|
3858
|
+
: 'readonly', STORE_OPTIONS);
|
|
3832
3859
|
if (k === 'array') {
|
|
3833
3860
|
if (prop === 'length')
|
|
3834
3861
|
return arrayLength();
|
|
@@ -3845,7 +3872,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3845
3872
|
return target[prop];
|
|
3846
3873
|
if (k === 'array' && !isIndexProp(prop))
|
|
3847
3874
|
return Reflect.get(target, prop, receiver);
|
|
3848
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3875
|
+
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
3876
|
},
|
|
3850
3877
|
});
|
|
3851
3878
|
return s;
|
|
@@ -3859,14 +3886,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3859
3886
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3860
3887
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3861
3888
|
*/
|
|
3862
|
-
function scopedStore(parent, seed, kind,
|
|
3889
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3863
3890
|
const local = isSignal(seed)
|
|
3864
|
-
? toStore(seed,
|
|
3891
|
+
? toStore(seed, options)
|
|
3865
3892
|
: kind === 'mutable'
|
|
3866
|
-
? mutableStore(seed,
|
|
3893
|
+
? mutableStore(seed, options)
|
|
3867
3894
|
: kind === 'readonly'
|
|
3868
|
-
? store(seed,
|
|
3869
|
-
: store(seed,
|
|
3895
|
+
? store(seed, options).asReadonlyStore()
|
|
3896
|
+
: store(seed, options);
|
|
3870
3897
|
const localValue = () => untracked(local);
|
|
3871
3898
|
const parentValue = () => untracked(parent);
|
|
3872
3899
|
const view = computed(() => ({
|
|
@@ -3897,18 +3924,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3897
3924
|
}
|
|
3898
3925
|
const scope = new Proxy(base, {
|
|
3899
3926
|
get(target, prop) {
|
|
3900
|
-
if (prop ===
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3927
|
+
if (typeof prop === 'symbol') {
|
|
3928
|
+
if (prop === IS_STORE)
|
|
3929
|
+
return true;
|
|
3930
|
+
if (prop === STORE_KIND)
|
|
3931
|
+
return kind;
|
|
3932
|
+
if (prop === SCOPE_PARENT)
|
|
3933
|
+
return parent;
|
|
3934
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3935
|
+
return options;
|
|
3936
|
+
}
|
|
3908
3937
|
if (prop === 'extend')
|
|
3909
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3938
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3910
3939
|
if (prop === 'asReadonlyStore')
|
|
3911
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3940
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3912
3941
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3913
3942
|
return target[prop];
|
|
3914
3943
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3954,22 +3983,34 @@ function storeKind(s) {
|
|
|
3954
3983
|
* scoped.count.set(1); // writes through to base
|
|
3955
3984
|
* scoped.label.set('x'); // stays local
|
|
3956
3985
|
*/
|
|
3957
|
-
function extendStore(store, source,
|
|
3958
|
-
|
|
3986
|
+
function extendStore(store, source, options) {
|
|
3987
|
+
const opt = {
|
|
3988
|
+
...store[STORE_SHARED_OPTIONS],
|
|
3989
|
+
...options,
|
|
3990
|
+
};
|
|
3991
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3959
3992
|
}
|
|
3960
3993
|
/**
|
|
3961
3994
|
* Creates a WritableSignalStore from a value.
|
|
3962
3995
|
* @see {@link toStore}
|
|
3963
3996
|
*/
|
|
3964
3997
|
function store(value, opt) {
|
|
3965
|
-
return toStore(signal(value, opt),
|
|
3998
|
+
return toStore(signal(value, opt), {
|
|
3999
|
+
vivify: false,
|
|
4000
|
+
noUnionLeaves: false,
|
|
4001
|
+
...opt,
|
|
4002
|
+
});
|
|
3966
4003
|
}
|
|
3967
4004
|
/**
|
|
3968
4005
|
* Creates a MutableSignalStore from a value.
|
|
3969
4006
|
* @see {@link toStore}
|
|
3970
4007
|
*/
|
|
3971
4008
|
function mutableStore(value, opt) {
|
|
3972
|
-
return toStore(mutable(value, opt),
|
|
4009
|
+
return toStore(mutable(value, opt), {
|
|
4010
|
+
vivify: false,
|
|
4011
|
+
noUnionLeaves: false,
|
|
4012
|
+
...opt,
|
|
4013
|
+
});
|
|
3973
4014
|
}
|
|
3974
4015
|
|
|
3975
4016
|
function isPlainRecord(value) {
|
|
@@ -4034,7 +4075,13 @@ function forkStore(base, opt) {
|
|
|
4034
4075
|
source: () => base(),
|
|
4035
4076
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs),
|
|
4036
4077
|
}]));
|
|
4037
|
-
|
|
4078
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4079
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4080
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4081
|
+
const store = toStore(staged, {
|
|
4082
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4083
|
+
...opt,
|
|
4084
|
+
});
|
|
4038
4085
|
return {
|
|
4039
4086
|
store,
|
|
4040
4087
|
commit: () => base.set(untracked(staged)),
|