@mmstack/primitives 19.5.2 → 19.6.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/lib/sensors/pointer-drag.d.ts +21 -1
- package/lib/store/fork-store.d.ts +8 -18
- package/lib/store/internals.d.ts +8 -11
- package/lib/store/public_api.d.ts +5 -1
- package/lib/store/store.d.ts +39 -10
- 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`
|
|
@@ -2905,6 +2905,8 @@ const IDLE = {
|
|
|
2905
2905
|
pointerId: null,
|
|
2906
2906
|
modifiers: { shift: false, alt: false, ctrl: false, meta: false },
|
|
2907
2907
|
button: -1,
|
|
2908
|
+
pointerType: '',
|
|
2909
|
+
origin: null,
|
|
2908
2910
|
};
|
|
2909
2911
|
function stateEqual(a, b) {
|
|
2910
2912
|
return (a.active === b.active &&
|
|
@@ -2912,6 +2914,8 @@ function stateEqual(a, b) {
|
|
|
2912
2914
|
a.current.x === b.current.x &&
|
|
2913
2915
|
a.current.y === b.current.y &&
|
|
2914
2916
|
a.button === b.button &&
|
|
2917
|
+
a.pointerType === b.pointerType &&
|
|
2918
|
+
a.origin === b.origin &&
|
|
2915
2919
|
a.modifiers.shift === b.modifiers.shift &&
|
|
2916
2920
|
a.modifiers.alt === b.modifiers.alt &&
|
|
2917
2921
|
a.modifiers.ctrl === b.modifiers.ctrl &&
|
|
@@ -2945,7 +2949,7 @@ function createPointerDrag(opt) {
|
|
|
2945
2949
|
return base;
|
|
2946
2950
|
}
|
|
2947
2951
|
const hostRef = inject((ElementRef), { optional: true });
|
|
2948
|
-
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], debugName = 'pointerDrag', } = opt ?? {};
|
|
2952
|
+
const { target = hostRef?.nativeElement, coordinateSpace = 'client', activationThreshold = 3, throttle = 16, handleSelector, buttons = [0], stopPropagation = false, debugName = 'pointerDrag', } = opt ?? {};
|
|
2949
2953
|
const resolve = (t) => {
|
|
2950
2954
|
if (!t)
|
|
2951
2955
|
return null;
|
|
@@ -2966,9 +2970,12 @@ function createPointerDrag(opt) {
|
|
|
2966
2970
|
equal: stateEqual,
|
|
2967
2971
|
debugName,
|
|
2968
2972
|
});
|
|
2973
|
+
const threshold2 = activationThreshold * activationThreshold;
|
|
2969
2974
|
let startPoint = { x: 0, y: 0 };
|
|
2970
2975
|
let activePointerId = null;
|
|
2971
2976
|
let activeButton = -1;
|
|
2977
|
+
let activePointerType = '';
|
|
2978
|
+
let activeOrigin = null;
|
|
2972
2979
|
let activated = false;
|
|
2973
2980
|
let gesture = null;
|
|
2974
2981
|
const coord = (e) => coordinateSpace === 'page'
|
|
@@ -2985,6 +2992,8 @@ function createPointerDrag(opt) {
|
|
|
2985
2992
|
gesture = null;
|
|
2986
2993
|
activePointerId = null;
|
|
2987
2994
|
activeButton = -1;
|
|
2995
|
+
activePointerType = '';
|
|
2996
|
+
activeOrigin = null;
|
|
2988
2997
|
activated = false;
|
|
2989
2998
|
state.set(IDLE);
|
|
2990
2999
|
state.flush(); // terminal transition: reflect IDLE now, not on the trailing edge
|
|
@@ -2994,8 +3003,8 @@ function createPointerDrag(opt) {
|
|
|
2994
3003
|
return;
|
|
2995
3004
|
const current = coord(e);
|
|
2996
3005
|
const delta = { x: current.x - startPoint.x, y: current.y - startPoint.y };
|
|
2997
|
-
if (!activated &&
|
|
2998
|
-
activated = true;
|
|
3006
|
+
if (!activated && delta.x * delta.x + delta.y * delta.y >= threshold2) {
|
|
3007
|
+
activated = true; // squared compare — no sqrt on the pre-activation path
|
|
2999
3008
|
}
|
|
3000
3009
|
state.set({
|
|
3001
3010
|
active: activated,
|
|
@@ -3005,6 +3014,8 @@ function createPointerDrag(opt) {
|
|
|
3005
3014
|
pointerId: activePointerId,
|
|
3006
3015
|
modifiers: mods(e),
|
|
3007
3016
|
button: activeButton, // pointermove button is -1; keep the down-button
|
|
3017
|
+
pointerType: activePointerType,
|
|
3018
|
+
origin: activeOrigin,
|
|
3008
3019
|
});
|
|
3009
3020
|
};
|
|
3010
3021
|
const onUp = (e) => {
|
|
@@ -3024,11 +3035,17 @@ function createPointerDrag(opt) {
|
|
|
3024
3035
|
return;
|
|
3025
3036
|
if (!buttons.includes(e.button))
|
|
3026
3037
|
return;
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3038
|
+
const matched = handleSelector
|
|
3039
|
+
? e.target?.closest?.(handleSelector)
|
|
3040
|
+
: el;
|
|
3041
|
+
if (!matched)
|
|
3042
|
+
return; // handleSelector set but pointerdown landed outside a handle
|
|
3043
|
+
if (stopPropagation)
|
|
3044
|
+
e.stopPropagation(); // claim it: an outer sensor won't also start
|
|
3030
3045
|
activePointerId = e.pointerId;
|
|
3031
3046
|
activeButton = e.button;
|
|
3047
|
+
activePointerType = e.pointerType;
|
|
3048
|
+
activeOrigin = matched;
|
|
3032
3049
|
activated = false;
|
|
3033
3050
|
startPoint = coord(e);
|
|
3034
3051
|
try {
|
|
@@ -3054,6 +3071,8 @@ function createPointerDrag(opt) {
|
|
|
3054
3071
|
pointerId: e.pointerId,
|
|
3055
3072
|
modifiers: mods(e),
|
|
3056
3073
|
button: e.button,
|
|
3074
|
+
pointerType: activePointerType,
|
|
3075
|
+
origin: activeOrigin,
|
|
3057
3076
|
});
|
|
3058
3077
|
};
|
|
3059
3078
|
const attach = (el) => {
|
|
@@ -3432,17 +3451,14 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
|
3432
3451
|
|
|
3433
3452
|
const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
|
|
3434
3453
|
const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
|
|
3454
|
+
const STORE_SHARED_GLOBALS = Symbol('@mmstack/primitives::store/STORE_SHARED_GLOBALS');
|
|
3455
|
+
const STORE_SHARED_OPTIONS = Symbol('@mmstack/primitives::store/STORE_SHARED_OPTIONS');
|
|
3435
3456
|
/**
|
|
3436
3457
|
* @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
|
|
3437
3458
|
* on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
|
|
3438
3459
|
* which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
|
|
3439
3460
|
*/
|
|
3440
3461
|
const STORE_KIND = Symbol('@mmstack/primitives::store/STORE_KIND');
|
|
3441
|
-
/**
|
|
3442
|
-
* @internal Brand exposing the injector a store was built with, so `extendStore` inherits it the
|
|
3443
|
-
* same way `store.extend(...)` does (via closure) — no injection context needed at the call site.
|
|
3444
|
-
*/
|
|
3445
|
-
const STORE_INJECTOR = Symbol('@mmstack/primitives::store/STORE_INJECTOR');
|
|
3446
3462
|
const SIGNAL_FN_PROP = new Set([
|
|
3447
3463
|
'set',
|
|
3448
3464
|
'update',
|
|
@@ -3450,21 +3466,20 @@ const SIGNAL_FN_PROP = new Set([
|
|
|
3450
3466
|
'inline',
|
|
3451
3467
|
'asReadonly',
|
|
3452
3468
|
]);
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
storeCache.delete(prop);
|
|
3469
|
+
const PROXY_CACHE_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cache', {
|
|
3470
|
+
providedIn: 'root',
|
|
3471
|
+
factory: () => new WeakMap(),
|
|
3472
|
+
});
|
|
3473
|
+
const PROXY_CLEANUP_TOKEN = new InjectionToken('@mmstack/primitives:store-proxy-cleanup', {
|
|
3474
|
+
providedIn: 'root',
|
|
3475
|
+
factory: () => {
|
|
3476
|
+
const cache = inject(PROXY_CACHE_TOKEN);
|
|
3477
|
+
return new FinalizationRegistry(({ target, prop }) => {
|
|
3478
|
+
const store = cache.get(target);
|
|
3479
|
+
if (store)
|
|
3480
|
+
store.delete(prop);
|
|
3481
|
+
});
|
|
3482
|
+
},
|
|
3468
3483
|
});
|
|
3469
3484
|
/**
|
|
3470
3485
|
* @internal
|
|
@@ -3658,11 +3673,11 @@ function isLeaf(value) {
|
|
|
3658
3673
|
* holding it via a `WeakRef` and registering it for finalizer-driven cache pruning. The cache
|
|
3659
3674
|
* is keyed per backing signal, so child identity is stable across repeat reads.
|
|
3660
3675
|
*/
|
|
3661
|
-
function getCachedChild(target, prop, build) {
|
|
3662
|
-
let storeCache =
|
|
3676
|
+
function getCachedChild(target, prop, build, cache, cleanupRegistry) {
|
|
3677
|
+
let storeCache = cache.get(target);
|
|
3663
3678
|
if (!storeCache) {
|
|
3664
3679
|
storeCache = new Map();
|
|
3665
|
-
|
|
3680
|
+
cache.set(target, storeCache);
|
|
3666
3681
|
}
|
|
3667
3682
|
const cachedRef = storeCache.get(prop);
|
|
3668
3683
|
if (cachedRef) {
|
|
@@ -3670,12 +3685,12 @@ function getCachedChild(target, prop, build) {
|
|
|
3670
3685
|
if (cached)
|
|
3671
3686
|
return cached;
|
|
3672
3687
|
storeCache.delete(prop);
|
|
3673
|
-
|
|
3688
|
+
cleanupRegistry.unregister(cachedRef);
|
|
3674
3689
|
}
|
|
3675
3690
|
const proxy = build();
|
|
3676
3691
|
const ref = new WeakRef(proxy);
|
|
3677
3692
|
storeCache.set(prop, ref);
|
|
3678
|
-
|
|
3693
|
+
cleanupRegistry.register(proxy, { target, prop }, ref);
|
|
3679
3694
|
return proxy;
|
|
3680
3695
|
}
|
|
3681
3696
|
/**
|
|
@@ -3684,11 +3699,11 @@ function getCachedChild(target, prop, build) {
|
|
|
3684
3699
|
* `onChange` path. Shared verbatim by the array and object proxies — the only place a child node
|
|
3685
3700
|
* is constructed.
|
|
3686
3701
|
*/
|
|
3687
|
-
function buildChildNode(target, prop, isMutableSource,
|
|
3702
|
+
function buildChildNode(target, prop, isMutableSource, options) {
|
|
3688
3703
|
const value = untracked(target);
|
|
3689
3704
|
const valueIsRecord = isRecord(value);
|
|
3690
3705
|
const valueIsArray = Array.isArray(value);
|
|
3691
|
-
const nodeVivify = resolveVivify(value, vivify);
|
|
3706
|
+
const nodeVivify = resolveVivify(value, options.vivify);
|
|
3692
3707
|
const vivifyFn = createVivify(nodeVivify);
|
|
3693
3708
|
const equalFn = (valueIsRecord || valueIsArray) &&
|
|
3694
3709
|
isMutableSource &&
|
|
@@ -3703,9 +3718,9 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3703
3718
|
equal: equalFn,
|
|
3704
3719
|
});
|
|
3705
3720
|
const childSample = untracked(computation);
|
|
3706
|
-
const childVivify = resolveVivify(childSample, vivify);
|
|
3707
|
-
const proxy = toStore(computation,
|
|
3708
|
-
markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
|
|
3721
|
+
const childVivify = resolveVivify(childSample, options.vivify);
|
|
3722
|
+
const proxy = toStore(computation, options);
|
|
3723
|
+
markAsLeaf(proxy, computation, childVivify !== false, options.noUnionLeaves);
|
|
3709
3724
|
return proxy;
|
|
3710
3725
|
}
|
|
3711
3726
|
/**
|
|
@@ -3723,7 +3738,7 @@ function buildChildNode(target, prop, isMutableSource, injector, vivify, noUnion
|
|
|
3723
3738
|
* const state = store({ user: { name: 'John' } });
|
|
3724
3739
|
* const nameSignal = state.user.name; // WritableSignal<string>
|
|
3725
3740
|
*/
|
|
3726
|
-
function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
3741
|
+
function toStore(source, { injector, vivify = false, noUnionLeaves = false, ...rest } = {}) {
|
|
3727
3742
|
if (isStore(source))
|
|
3728
3743
|
return source;
|
|
3729
3744
|
if (!injector)
|
|
@@ -3743,6 +3758,16 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3743
3758
|
return 'record';
|
|
3744
3759
|
return 'primitive';
|
|
3745
3760
|
});
|
|
3761
|
+
const STORE_OPTIONS = {
|
|
3762
|
+
injector,
|
|
3763
|
+
vivify,
|
|
3764
|
+
noUnionLeaves,
|
|
3765
|
+
[STORE_SHARED_GLOBALS]: {
|
|
3766
|
+
cache: rest[STORE_SHARED_GLOBALS]?.cache ?? injector.get(PROXY_CACHE_TOKEN),
|
|
3767
|
+
registry: rest[STORE_SHARED_GLOBALS]?.registry ??
|
|
3768
|
+
injector.get(PROXY_CLEANUP_TOKEN),
|
|
3769
|
+
},
|
|
3770
|
+
};
|
|
3746
3771
|
// built lazily so non-array nodes never allocate it
|
|
3747
3772
|
let length;
|
|
3748
3773
|
const arrayLength = () => (length ??= computed(() => {
|
|
@@ -3796,21 +3821,23 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3796
3821
|
return { enumerable: true, configurable: true };
|
|
3797
3822
|
},
|
|
3798
3823
|
get(target, prop, receiver) {
|
|
3799
|
-
if (prop ===
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3824
|
+
if (typeof prop === 'symbol') {
|
|
3825
|
+
if (prop === IS_STORE)
|
|
3826
|
+
return true;
|
|
3827
|
+
if (prop === STORE_KIND)
|
|
3828
|
+
return isMutableSource
|
|
3829
|
+
? 'mutable'
|
|
3830
|
+
: isWritableSource
|
|
3831
|
+
? 'writable'
|
|
3832
|
+
: 'readonly';
|
|
3833
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3834
|
+
return STORE_OPTIONS;
|
|
3835
|
+
}
|
|
3809
3836
|
if (prop === 'asReadonlyStore')
|
|
3810
3837
|
return () => {
|
|
3811
3838
|
if (!isWritableSource)
|
|
3812
3839
|
return s;
|
|
3813
|
-
return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
|
|
3840
|
+
return untracked(() => toStore(source.asReadonly(), { injector, vivify, noUnionLeaves }));
|
|
3814
3841
|
};
|
|
3815
3842
|
const k = untracked(kind);
|
|
3816
3843
|
if (prop === 'extend' && k !== 'array')
|
|
@@ -3818,7 +3845,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3818
3845
|
? 'mutable'
|
|
3819
3846
|
: isWritableSource
|
|
3820
3847
|
? 'writable'
|
|
3821
|
-
: 'readonly',
|
|
3848
|
+
: 'readonly', STORE_OPTIONS);
|
|
3822
3849
|
if (k === 'array') {
|
|
3823
3850
|
if (prop === 'length')
|
|
3824
3851
|
return arrayLength();
|
|
@@ -3835,7 +3862,7 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3835
3862
|
return target[prop];
|
|
3836
3863
|
if (k === 'array' && !isIndexProp(prop))
|
|
3837
3864
|
return Reflect.get(target, prop, receiver);
|
|
3838
|
-
return getCachedChild(target, prop, () => buildChildNode(target, k === 'array' ? +prop : prop, isMutableSource,
|
|
3865
|
+
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);
|
|
3839
3866
|
},
|
|
3840
3867
|
});
|
|
3841
3868
|
return s;
|
|
@@ -3849,14 +3876,14 @@ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
|
|
|
3849
3876
|
* identity + two-way), while local keys never propagate upward. A merged `computed` is derived
|
|
3850
3877
|
* only for whole-object reads / `has` / iteration — never for routing.
|
|
3851
3878
|
*/
|
|
3852
|
-
function scopedStore(parent, seed, kind,
|
|
3879
|
+
function scopedStore(parent, seed, kind, options) {
|
|
3853
3880
|
const local = isSignal(seed)
|
|
3854
|
-
? toStore(seed,
|
|
3881
|
+
? toStore(seed, options)
|
|
3855
3882
|
: kind === 'mutable'
|
|
3856
|
-
? mutableStore(seed,
|
|
3883
|
+
? mutableStore(seed, options)
|
|
3857
3884
|
: kind === 'readonly'
|
|
3858
|
-
? store(seed,
|
|
3859
|
-
: store(seed,
|
|
3885
|
+
? store(seed, options).asReadonlyStore()
|
|
3886
|
+
: store(seed, options);
|
|
3860
3887
|
const localValue = () => untracked(local);
|
|
3861
3888
|
const parentValue = () => untracked(parent);
|
|
3862
3889
|
const view = computed(() => ({
|
|
@@ -3887,18 +3914,20 @@ function scopedStore(parent, seed, kind, injector) {
|
|
|
3887
3914
|
}
|
|
3888
3915
|
const scope = new Proxy(base, {
|
|
3889
3916
|
get(target, prop) {
|
|
3890
|
-
if (prop ===
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3917
|
+
if (typeof prop === 'symbol') {
|
|
3918
|
+
if (prop === IS_STORE)
|
|
3919
|
+
return true;
|
|
3920
|
+
if (prop === STORE_KIND)
|
|
3921
|
+
return kind;
|
|
3922
|
+
if (prop === SCOPE_PARENT)
|
|
3923
|
+
return parent;
|
|
3924
|
+
if (prop === STORE_SHARED_OPTIONS)
|
|
3925
|
+
return options;
|
|
3926
|
+
}
|
|
3898
3927
|
if (prop === 'extend')
|
|
3899
|
-
return (childSeed) => scopedStore(scope, childSeed, kind,
|
|
3928
|
+
return (childSeed) => scopedStore(scope, childSeed, kind, options);
|
|
3900
3929
|
if (prop === 'asReadonlyStore')
|
|
3901
|
-
return () => toStore(computed(() => ({ ...parent(), ...local() })),
|
|
3930
|
+
return () => toStore(computed(() => ({ ...parent(), ...local() })), options);
|
|
3902
3931
|
if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
|
|
3903
3932
|
return target[prop];
|
|
3904
3933
|
// Route by consulting both signals: local first, then parent, else local (new → local).
|
|
@@ -3944,22 +3973,34 @@ function storeKind(s) {
|
|
|
3944
3973
|
* scoped.count.set(1); // writes through to base
|
|
3945
3974
|
* scoped.label.set('x'); // stays local
|
|
3946
3975
|
*/
|
|
3947
|
-
function extendStore(store, source,
|
|
3948
|
-
|
|
3976
|
+
function extendStore(store, source, options) {
|
|
3977
|
+
const opt = {
|
|
3978
|
+
...store[STORE_SHARED_OPTIONS],
|
|
3979
|
+
...options,
|
|
3980
|
+
};
|
|
3981
|
+
return scopedStore(store, source, storeKind(store), opt);
|
|
3949
3982
|
}
|
|
3950
3983
|
/**
|
|
3951
3984
|
* Creates a WritableSignalStore from a value.
|
|
3952
3985
|
* @see {@link toStore}
|
|
3953
3986
|
*/
|
|
3954
3987
|
function store(value, opt) {
|
|
3955
|
-
return toStore(signal(value, opt),
|
|
3988
|
+
return toStore(signal(value, opt), {
|
|
3989
|
+
vivify: false,
|
|
3990
|
+
noUnionLeaves: false,
|
|
3991
|
+
...opt,
|
|
3992
|
+
});
|
|
3956
3993
|
}
|
|
3957
3994
|
/**
|
|
3958
3995
|
* Creates a MutableSignalStore from a value.
|
|
3959
3996
|
* @see {@link toStore}
|
|
3960
3997
|
*/
|
|
3961
3998
|
function mutableStore(value, opt) {
|
|
3962
|
-
return toStore(mutable(value, opt),
|
|
3999
|
+
return toStore(mutable(value, opt), {
|
|
4000
|
+
vivify: false,
|
|
4001
|
+
noUnionLeaves: false,
|
|
4002
|
+
...opt,
|
|
4003
|
+
});
|
|
3963
4004
|
}
|
|
3964
4005
|
|
|
3965
4006
|
function isPlainRecord(value) {
|
|
@@ -4023,7 +4064,13 @@ function forkStore(base, opt) {
|
|
|
4023
4064
|
source: () => base(),
|
|
4024
4065
|
computation: (theirs, prev) => prev === undefined ? theirs : merge(prev.source, prev.value, theirs),
|
|
4025
4066
|
});
|
|
4026
|
-
|
|
4067
|
+
// Inherit the base's shared options (injector, vivify, noUnionLeaves + the
|
|
4068
|
+
// proxy cache/registry), same as extendStore — a fork should vivify like its
|
|
4069
|
+
// base and share its injector-scoped cache. `opt` overrides (advanced use).
|
|
4070
|
+
const store = toStore(staged, {
|
|
4071
|
+
...base[STORE_SHARED_OPTIONS],
|
|
4072
|
+
...opt,
|
|
4073
|
+
});
|
|
4027
4074
|
return {
|
|
4028
4075
|
store,
|
|
4029
4076
|
commit: () => base.set(untracked(staged)),
|