@mmstack/primitives 22.2.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/primitives",
3
- "version": "22.2.1",
3
+ "version": "22.3.0",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -1,7 +1,94 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ValueEqualityFn, Injector, Signal, Provider, CreateComputedOptions, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, CreateSignalOptions, WritableSignal, ResourceRef, DestroyRef, ElementRef } from '@angular/core';
2
+ import { InjectionToken, Injector, CreateComputedOptions, Signal, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, CreateSignalOptions, WritableSignal, Provider, ValueEqualityFn, ResourceRef, DestroyRef, ElementRef } from '@angular/core';
3
3
  import * as _mmstack_primitives from '@mmstack/primitives';
4
4
 
5
+ /**
6
+ * How a pausable primitive decides whether it is currently paused:
7
+ * - omitted (the default) or `true` — read the ambient {@link PAUSED_CONTEXT} (via `injector`, or the
8
+ * current injection context). Reaching for a `pausable*` primitive means you want it pausable, so
9
+ * this is the default; outside an Activity boundary there's no `PAUSED_CONTEXT`, so the primitive is
10
+ * returned unwrapped (never pauses, zero overhead). On the server it never pauses either.
11
+ * - a predicate `() => boolean` — used directly. A `Signal<boolean>` satisfies this (signals are
12
+ * callable), and a plain function works OUTSIDE an injection context.
13
+ * - `false` — the explicit opt-out: the primitive is returned UNWRAPPED (no `linkedSignal`, no gate),
14
+ * i.e. exactly the plain primitive with zero overhead.
15
+ */
16
+ type PauseOption = boolean | (() => boolean);
17
+ type PausableOptions = {
18
+ /** Pause source — see {@link PauseOption}. Defaults to `true` (read the ambient `PAUSED_CONTEXT`). */
19
+ readonly pause?: PauseOption;
20
+ /**
21
+ * Injector used to resolve {@link PAUSED_CONTEXT} when `pause` is `true`/omitted and the primitive
22
+ * is created outside an injection context. Ignored for the `false` / predicate forms.
23
+ */
24
+ readonly injector?: Injector;
25
+ };
26
+ /**
27
+ * @internal Token carrying an app-wide default {@link PauseOption}, set via
28
+ * {@link providePausableOptions}. {@link resolvePause} consults it when the call site didn't
29
+ * specify `pause`, so users can opt every pausable-aware primitive in (or out) from one place.
30
+ */
31
+ declare const PAUSABLE_OPTIONS: InjectionToken<{
32
+ pause?: PauseOption;
33
+ }>;
34
+ /**
35
+ * Provides an app-wide default {@link PauseOption} for every pausable-aware primitive (the public
36
+ * `pausable*` family plus the opt-in integrations like `stored` / `chunked`). A call-site `pause`
37
+ * always wins; this only fills in when the call didn't specify one.
38
+ *
39
+ * @example
40
+ * // Make everything that can pause honour the ambient Activity boundary by default:
41
+ * providePausableOptions({ pause: true })
42
+ */
43
+ declare function providePausableOptions(opt: {
44
+ /** Default pause source for pausable-aware primitives that don't set their own. */
45
+ pause?: PauseOption;
46
+ }): Provider;
47
+ /**
48
+ * Resolve a {@link PauseOption} into a pause predicate, or `null` meaning "do not pause".
49
+ * `null` tells the caller to return the bare primitive — no wrapper is created.
50
+ *
51
+ * - omitted/`true` → the ambient {@link PAUSED_CONTEXT} if an Activity boundary provides one (via
52
+ * `opt.injector` or the current injection context), else `null` (the bare primitive, no allocation).
53
+ * The default, because an explicit `pausable*` call wants to be pausable. An explicit `pause: true`
54
+ * with no boundary dev-warns; the omitted default stays quiet. SSR → `null`.
55
+ * - a function → returned as-is (covers `Signal<boolean>`; usable outside an injection context).
56
+ * SSR → `null` here too, detected via `opt.injector` if given, else a `globalThis.window` probe.
57
+ * - `false` → `null` (the explicit opt-out).
58
+ *
59
+ * Encapsulating this here keeps every pausable primitive's branching identical and in one place.
60
+ */
61
+ declare function resolvePause(opt?: PausableOptions, defaultPause?: PauseOption): (() => boolean) | null;
62
+ /**
63
+ * Like {@link nestedEffect}, but pausable. While paused the effect does NOT run its body — and,
64
+ * crucially, it reads the pause predicate FIRST, so while paused its dependency set collapses to just
65
+ * the predicate (no churn from the real deps); on resume it re-runs and re-tracks. With no `pause`
66
+ * option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false` makes it a plain `nestedEffect`
67
+ * with zero added overhead.
68
+ */
69
+ declare function pausableEffect(effectFn: (registerCleanup: EffectCleanupRegisterFn) => void, options?: CreateEffectOptions & PausableOptions): EffectRef;
70
+ /**
71
+ * Like `signal`, but pausable. While paused, READS hold the last value; writes still land on the
72
+ * underlying signal and surface on resume. Built on the `keepPrevious`/`hold` shape — a
73
+ * `linkedSignal` gated on the pause predicate, with `set`/`update` forwarded to the source signal.
74
+ * `asReadonly()` returns the held (gated) view, so both views of the signal agree while paused.
75
+ * With no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false`
76
+ * makes it a plain `signal` — no `linkedSignal` is created.
77
+ *
78
+ * NOTE: while paused, `set(x)` followed by a read returns the *held* (pre-pause) value, not `x` — the
79
+ * write lands on the source and surfaces on resume. That is the "freeze the displayed value while
80
+ * hidden" semantics; do not rely on read-after-write while paused.
81
+ */
82
+ declare function pausableSignal<T>(initialValue: T, options?: CreateSignalOptions<T> & PausableOptions): WritableSignal<T>;
83
+ /**
84
+ * Like `computed`, but pausable. While paused it holds its last value AND does not recompute: the
85
+ * computation's dependencies are not read while paused, so a dependency change can't trigger work —
86
+ * on resume it recomputes and re-tracks. The very first read always computes, to seed a value. With
87
+ * no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false` makes it a plain
88
+ * `computed`.
89
+ */
90
+ declare function pausableComputed<T>(computation: () => T, options?: CreateComputedOptions<T> & PausableOptions): Signal<T>;
91
+
5
92
  type CreateChunkedOptions<T> = {
6
93
  /**
7
94
  * The number of items to process in each chunk.
@@ -21,6 +108,12 @@ type CreateChunkedOptions<T> = {
21
108
  * An optional `Injector` to use for the internal effect. This allows the effect to have access to dependency injection if needed.
22
109
  */
23
110
  injector?: Injector;
111
+ /**
112
+ * Opt-in pause: gate the chunk-scheduling effect on an ambient Activity boundary (`true`), a
113
+ * custom predicate, or `false` (default — no pausing). While paused, scheduling stops and resumes
114
+ * from the current chunk on resume. See {@link PauseOption}.
115
+ */
116
+ pause?: PauseOption;
24
117
  };
25
118
  /**
26
119
  * Creates a new `Signal` that processes an array of items in time-sliced chunks. This is useful for handling large lists without blocking the main thread.
@@ -95,72 +188,6 @@ declare function providePaused(source: Signal<boolean>): Provider;
95
188
  */
96
189
  declare function holdUntilReady<T>(target: Signal<T>, ready: () => boolean): Signal<T>;
97
190
 
98
- /**
99
- * How a pausable primitive decides whether it is currently paused:
100
- * - omitted (the default) or `true` — read the ambient {@link PAUSED_CONTEXT} (via `injector`, or the
101
- * current injection context). Reaching for a `pausable*` primitive means you want it pausable, so
102
- * this is the default; outside an Activity boundary there's no `PAUSED_CONTEXT`, so the primitive is
103
- * returned unwrapped (never pauses, zero overhead). On the server it never pauses either.
104
- * - a predicate `() => boolean` — used directly. A `Signal<boolean>` satisfies this (signals are
105
- * callable), and a plain function works OUTSIDE an injection context.
106
- * - `false` — the explicit opt-out: the primitive is returned UNWRAPPED (no `linkedSignal`, no gate),
107
- * i.e. exactly the plain primitive with zero overhead.
108
- */
109
- type PauseOption = boolean | (() => boolean);
110
- type PausableOptions = {
111
- /** Pause source — see {@link PauseOption}. Defaults to `true` (read the ambient `PAUSED_CONTEXT`). */
112
- readonly pause?: PauseOption;
113
- /**
114
- * Injector used to resolve {@link PAUSED_CONTEXT} when `pause` is `true`/omitted and the primitive
115
- * is created outside an injection context. Ignored for the `false` / predicate forms.
116
- */
117
- readonly injector?: Injector;
118
- };
119
- /**
120
- * Resolve a {@link PauseOption} into a pause predicate, or `null` meaning "do not pause".
121
- * `null` tells the caller to return the bare primitive — no wrapper is created.
122
- *
123
- * - omitted/`true` → the ambient {@link PAUSED_CONTEXT} if an Activity boundary provides one (via
124
- * `opt.injector` or the current injection context), else `null` (the bare primitive, no allocation).
125
- * The default, because an explicit `pausable*` call wants to be pausable. An explicit `pause: true`
126
- * with no boundary dev-warns; the omitted default stays quiet. SSR → `null`.
127
- * - a function → returned as-is (covers `Signal<boolean>`; usable outside an injection context).
128
- * SSR → `null` here too, detected via `opt.injector` if given, else a `globalThis.window` probe.
129
- * - `false` → `null` (the explicit opt-out).
130
- *
131
- * Encapsulating this here keeps every pausable primitive's branching identical and in one place.
132
- */
133
- declare function resolvePause(opt?: PausableOptions): (() => boolean) | null;
134
- /**
135
- * Like {@link nestedEffect}, but pausable. While paused the effect does NOT run its body — and,
136
- * crucially, it reads the pause predicate FIRST, so while paused its dependency set collapses to just
137
- * the predicate (no churn from the real deps); on resume it re-runs and re-tracks. With no `pause`
138
- * option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false` makes it a plain `nestedEffect`
139
- * with zero added overhead.
140
- */
141
- declare function pausableEffect(effectFn: (registerCleanup: EffectCleanupRegisterFn) => void, options?: CreateEffectOptions & PausableOptions): EffectRef;
142
- /**
143
- * Like `signal`, but pausable. While paused, READS hold the last value; writes still land on the
144
- * underlying signal and surface on resume. Built on the `keepPrevious`/`hold` shape — a
145
- * `linkedSignal` gated on the pause predicate, with `set`/`update` forwarded to the source signal.
146
- * `asReadonly()` returns the held (gated) view, so both views of the signal agree while paused.
147
- * With no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false`
148
- * makes it a plain `signal` — no `linkedSignal` is created.
149
- *
150
- * NOTE: while paused, `set(x)` followed by a read returns the *held* (pre-pause) value, not `x` — the
151
- * write lands on the source and surfaces on resume. That is the "freeze the displayed value while
152
- * hidden" semantics; do not rely on read-after-write while paused.
153
- */
154
- declare function pausableSignal<T>(initialValue: T, options?: CreateSignalOptions<T> & PausableOptions): WritableSignal<T>;
155
- /**
156
- * Like `computed`, but pausable. While paused it holds its last value AND does not recompute: the
157
- * computation's dependencies are not read while paused, so a dependency change can't trigger work —
158
- * on resume it recomputes and re-tracks. The very first read always computes, to seed a value. With
159
- * no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false` makes it a plain
160
- * `computed`.
161
- */
162
- declare function pausableComputed<T>(computation: () => T, options?: CreateComputedOptions<T> & PausableOptions): Signal<T>;
163
-
164
191
  /**
165
192
  * Handle for an in-progress transition: a `pending` signal (true while the transition's
166
193
  * resources are in flight) and a `done` promise that resolves once they all settle.
@@ -288,6 +315,12 @@ declare function registerResource<T extends ResourceRef<any>>(res: T, opt?: Regi
288
315
  *
289
316
  * `type` selects what "not ready" means: `'value'` (default) suspends only until a first value lands
290
317
  * then holds through reloads; `'loading'` suspends on every in-flight load (strict suspense).
318
+ *
319
+ * SSR: the server serializes whatever the scope reports at stabilization, so a registered resource
320
+ * must keep the app unstable until it settles or the placeholder is what gets serialized (then
321
+ * flashes/mismatches on hydration). HttpClient-backed resources, httpResource & all of `@mmstack/resource`
322
+ * do this automatically via the HTTP layer's `PendingTasks` + transfer cache. A custom loader (raw
323
+ * `fetch`/promise/timer) must opt in itself: wrap it with `inject(PendingTasks).run(() => promise)`.
291
324
  */
292
325
  declare abstract class SuspenseBoundaryBase {
293
326
  protected readonly scope: _mmstack_primitives.TransitionScope;
@@ -1879,6 +1912,93 @@ type ScreenOrientation = ScreenOrientationState;
1879
1912
  */
1880
1913
  declare function orientation(opt?: string | SensorRunOptions): Signal<ScreenOrientationState>;
1881
1914
 
1915
+ type PointerPoint = {
1916
+ x: number;
1917
+ y: number;
1918
+ };
1919
+ type PointerModifiers = {
1920
+ shift: boolean;
1921
+ alt: boolean;
1922
+ ctrl: boolean;
1923
+ meta: boolean;
1924
+ };
1925
+ type PointerDragState = {
1926
+ /** A gesture is past the activation threshold (distinguishes a drag from a click). */
1927
+ active: boolean;
1928
+ /** Pointer position at the pointerdown that began the gesture. */
1929
+ start: PointerPoint;
1930
+ /** Latest pointer position. */
1931
+ current: PointerPoint;
1932
+ /** `current - start`, computed on the same update as `current` (never torn). */
1933
+ delta: PointerPoint;
1934
+ /** The captured pointer id, or `null` when idle. */
1935
+ pointerId: number | null;
1936
+ /** Modifier keys at the latest update. */
1937
+ modifiers: PointerModifiers;
1938
+ /** Mouse button that started the gesture (`-1` when idle). */
1939
+ button: number;
1940
+ /** The pointing device: `'mouse' | 'touch' | 'pen'` (`''` when idle). */
1941
+ pointerType: string;
1942
+ /**
1943
+ * The element the gesture started on: the `handleSelector` match when one is
1944
+ * set (so a single delegated listener can tell which child started the drag),
1945
+ * otherwise the listener's element. `null` when idle.
1946
+ */
1947
+ origin: HTMLElement | null;
1948
+ };
1949
+ type PointerDragOptions = SensorRunOptions & {
1950
+ /**
1951
+ * Element that receives `pointerdown`. An `HTMLElement`, `ElementRef`, or a
1952
+ * `Signal` of one (listeners re-attach when it changes). Defaults to the host
1953
+ * `ElementRef`.
1954
+ */
1955
+ target?: HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | null | undefined>;
1956
+ /** `'client'` (viewport) or `'page'` coordinates. @default 'client' */
1957
+ coordinateSpace?: 'client' | 'page';
1958
+ /** Pixels the pointer must travel before `active` flips true. @default 3 */
1959
+ activationThreshold?: number;
1960
+ /**
1961
+ * Throttle (ms) for `current`/`delta` updates. @default 16
1962
+ *
1963
+ * Note: a final sub-throttle move right before `pointerup` may not surface on
1964
+ * the throttled view (it coalesces into the terminal idle). Logic that must act
1965
+ * on the *exact* release position should read {@link PointerDragSignal.unthrottled}.
1966
+ */
1967
+ throttle?: number;
1968
+ /** Only start when the pointerdown target matches this selector (delegated handles). */
1969
+ handleSelector?: string;
1970
+ /** Mouse buttons that may start a gesture. @default [0] (primary) */
1971
+ buttons?: number[];
1972
+ /**
1973
+ * Stop the `pointerdown` from propagating once this sensor claims it. Lets an
1974
+ * inner sensor win over an outer one on the same element tree (e.g. a nested
1975
+ * sortable inside another). @default false
1976
+ */
1977
+ stopPropagation?: boolean;
1978
+ };
1979
+ /** A gesture signal with an `unthrottled` view and an imperative `cancel()`. */
1980
+ type PointerDragSignal = Signal<PointerDragState> & {
1981
+ readonly unthrottled: Signal<PointerDragState>;
1982
+ /** Abort the current gesture and reset to idle (e.g. on Escape / programmatically). */
1983
+ cancel: () => void;
1984
+ };
1985
+ /**
1986
+ * Tracks a pointer *gesture* (pointerdown → capture → move → up) as a signal —
1987
+ * the foundation for pointer-based drag/move/resize/marquee on a canvas. Unlike
1988
+ * native HTML5 drag, pointer events fire continuously and coordinates are
1989
+ * reliable. SSR-safe; cleans up its listeners automatically.
1990
+ *
1991
+ * @example
1992
+ * ```ts
1993
+ * const drag = pointerDrag({ activationThreshold: 4 });
1994
+ * const position = computed(() => {
1995
+ * const d = drag();
1996
+ * return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
1997
+ * });
1998
+ * ```
1999
+ */
2000
+ declare function pointerDrag(opt?: PointerDragOptions): PointerDragSignal;
2001
+
1882
2002
  /**
1883
2003
  * Creates a read-only signal that tracks the page's visibility state.
1884
2004
  *
@@ -2080,6 +2200,10 @@ type SensorTypedOptions = {
2080
2200
  opt: MousePositionOptions;
2081
2201
  returnType: MousePositionSignal;
2082
2202
  };
2203
+ pointerDrag: {
2204
+ opt: PointerDragOptions;
2205
+ returnType: PointerDragSignal;
2206
+ };
2083
2207
  networkStatus: {
2084
2208
  opt: SensorRunOptions;
2085
2209
  returnType: NetworkStatusSignal;
@@ -2164,6 +2288,15 @@ declare function sensor(type: 'elementSize', options?: SensorTypedOptions['eleme
2164
2288
  * @example const pos = sensor('mousePosition', { coordinateSpace: 'page', throttle: 50 });
2165
2289
  */
2166
2290
  declare function sensor(type: 'mousePosition', options?: SensorTypedOptions['mousePosition']['opt']): MousePositionSignal;
2291
+ /**
2292
+ * Creates a sensor signal tracking a pointer drag gesture (down → move → up).
2293
+ * @param type Must be `'pointerDrag'`.
2294
+ * @param options Optional configuration (target, activationThreshold, throttle, …).
2295
+ * @returns A `PointerDragSignal` with `active`/`start`/`current`/`delta`, plus `unthrottled` and `cancel()`.
2296
+ * @see {pointerDrag} for detailed documentation and examples.
2297
+ * @example const drag = sensor('pointerDrag', { activationThreshold: 4 });
2298
+ */
2299
+ declare function sensor(type: 'pointerDrag', options?: SensorTypedOptions['pointerDrag']['opt']): PointerDragSignal;
2167
2300
  /**
2168
2301
  * Creates a sensor signal that tracks the browser's online/offline status.
2169
2302
  * @param type Must be `'networkStatus'`.
@@ -2327,6 +2460,32 @@ type ResolvableTarget = EventTargetLike | Signal<EventTargetLike | null>;
2327
2460
  declare function signalFromEvent<TEvent extends Event>(target: ResolvableTarget, eventName: string, initial: TEvent | null, opt?: SignalFromEventOptions): Signal<TEvent | null>;
2328
2461
  declare function signalFromEvent<TEvent extends Event, U>(target: ResolvableTarget, eventName: string, initial: U, project: (event: TEvent) => U, opt?: SignalFromEventOptions): Signal<U>;
2329
2462
 
2463
+ /**
2464
+ * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
2465
+ * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
2466
+ * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
2467
+ */
2468
+ declare const LEAF: unique symbol;
2469
+ /**
2470
+ * Reports whether a store node is currently a **leaf** — a terminal value the store does not
2471
+ * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
2472
+ * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
2473
+ *
2474
+ * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
2475
+ * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
2476
+ *
2477
+ * @internal Exposed for advanced/niche interop only — not part of the supported public surface
2478
+ * and may change without a major version bump.
2479
+ *
2480
+ * @example
2481
+ * const s = store({ name: 'Ada', address: { city: 'London' } });
2482
+ * isLeaf(s.name); // true
2483
+ * isLeaf(s.address); // false — a substore
2484
+ */
2485
+ declare function isLeaf<T = unknown>(value: unknown): value is Signal<T> & {
2486
+ readonly [LEAF]: () => boolean;
2487
+ };
2488
+
2330
2489
  /**
2331
2490
  * Runtime marker + compile-time brand for an opaque value. A `const`-declared `Symbol`
2332
2491
  * has a `unique symbol` type, so the same symbol serves as both the property key written
@@ -2360,31 +2519,6 @@ declare function opaque<T extends object>(value: T): Opaque<T>;
2360
2519
  * }
2361
2520
  */
2362
2521
  declare function isOpaque<T = object>(value: unknown): value is Opaque<T>;
2363
- /**
2364
- * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
2365
- * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
2366
- * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
2367
- */
2368
- declare const LEAF: unique symbol;
2369
- /**
2370
- * Reports whether a store node is currently a **leaf** — a terminal value the store does not
2371
- * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
2372
- * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
2373
- *
2374
- * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
2375
- * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
2376
- *
2377
- * @internal Exposed for advanced/niche interop only — not part of the supported public surface
2378
- * and may change without a major version bump.
2379
- *
2380
- * @example
2381
- * const s = store({ name: 'Ada', address: { city: 'London' } });
2382
- * isLeaf(s.name); // true
2383
- * isLeaf(s.address); // false — a substore
2384
- */
2385
- declare function isLeaf<T = unknown>(value: unknown): value is Signal<T> & {
2386
- readonly [LEAF]: () => boolean;
2387
- };
2388
2522
  /**
2389
2523
  * An object marked via {@link opaque} — the store treats it as an indivisible leaf
2390
2524
  * (like a `Date`), returning it whole instead of deep-proxying its keys.
@@ -2396,16 +2530,27 @@ type Opaque<T> = T & {
2396
2530
  type UnwrapOpaque<T> = T extends {
2397
2531
  readonly [OPAQUE]: true;
2398
2532
  } ? Omit<T, typeof OPAQUE> : T;
2533
+
2399
2534
  type BaseType = string | number | boolean | symbol | bigint | undefined | null | Function | Date | RegExp | {
2400
2535
  readonly [OPAQUE]: true;
2401
2536
  };
2402
2537
  type Key = string | number;
2403
2538
  type AnyRecord = Record<Key, any>;
2404
2539
  /**
2405
- * @internal
2406
- * Validates whether a value is a Signal Store.
2540
+ * @internal Resolves to `true` only for `any`. In a conditional type, `any` distributes across
2541
+ * *both* branches (`unknown | object`), and `unknown | X` collapses to `unknown` — which would
2542
+ * erase a store's property access and `extend`. Guarding on this routes an `any`-typed store to
2543
+ * the full object shape instead.
2407
2544
  */
2408
- declare function isStore<T>(value: unknown): value is SignalStore<T>;
2545
+ type IsAny<T> = 0 extends 1 & T ? true : false;
2546
+ /**
2547
+ * @internal Flattens an intersection (`A & B & C`) into a single object literal so editor
2548
+ * tooltips show the resolved members instead of the raw intersection chain. Display-only —
2549
+ * structurally identical to its input.
2550
+ */
2551
+ type Simplify<T> = {
2552
+ [K in keyof T]: T[K];
2553
+ } & {};
2409
2554
  type SignalArrayStore<T extends any[]> = Signal<T> & {
2410
2555
  readonly [index: number]: SignalStore<T[number]>;
2411
2556
  readonly length: Signal<number>;
@@ -2423,25 +2568,11 @@ type MutableArrayStore<T extends any[]> = MutableSignal<T> & {
2423
2568
  readonly length: Signal<number>;
2424
2569
  [Symbol.iterator](): Iterator<MutableSignalStore<T[number]>>;
2425
2570
  };
2426
- /**
2427
- * @internal Resolves to `true` only for `any`. In a conditional type, `any` distributes across
2428
- * *both* branches (`unknown | object`), and `unknown | X` collapses to `unknown` — which would
2429
- * erase a store's property access and `extend`. Guarding on this routes an `any`-typed store to
2430
- * the full object shape instead.
2431
- */
2432
- type IsAny<T> = 0 extends 1 & T ? true : false;
2433
- /**
2434
- * @internal Flattens an intersection (`A & B & C`) into a single object literal so editor
2435
- * tooltips show the resolved members instead of the raw intersection chain. Display-only —
2436
- * structurally identical to its input.
2437
- */
2438
- type Simplify<T> = {
2439
- [K in keyof T]: T[K];
2440
- } & {};
2441
2571
  /** @internal The object shape of a readonly store: a child store per key, plus `extend`. */
2442
2572
  type SignalStoreObject<T> = Simplify<Readonly<{
2443
2573
  [K in keyof Required<T>]: SignalStore<NonNullable<T>[K]>;
2444
2574
  }> & {
2575
+ /** @deprecated Use the standalone `extendStore(store, …)`; the `extend` key is removed next minor. */
2445
2576
  readonly extend: {
2446
2577
  <L extends AnyRecord>(source: Signal<L>): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2447
2578
  <L extends AnyRecord>(props: L): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
@@ -2451,6 +2582,7 @@ type SignalStoreObject<T> = Simplify<Readonly<{
2451
2582
  type WritableSignalStoreObject<T> = Simplify<Readonly<{
2452
2583
  [K in keyof Required<T>]: WritableSignalStore<NonNullable<T>[K]>;
2453
2584
  }> & {
2585
+ /** @deprecated Use the standalone `extendStore(store, …)`; the `extend` key is removed next minor. */
2454
2586
  readonly extend: {
2455
2587
  <L extends AnyRecord>(source: WritableSignal<L>): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2456
2588
  <L extends AnyRecord>(props: L): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
@@ -2460,6 +2592,7 @@ type WritableSignalStoreObject<T> = Simplify<Readonly<{
2460
2592
  type MutableSignalStoreObject<T> = Simplify<Readonly<{
2461
2593
  [K in keyof Required<T>]: MutableSignalStore<NonNullable<T>[K]>;
2462
2594
  }> & {
2595
+ /** @deprecated Use the standalone `extendStore(store, …)`; the `extend` key is removed next minor. */
2463
2596
  readonly extend: {
2464
2597
  <L extends AnyRecord>(source: MutableSignal<L>): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2465
2598
  <L extends AnyRecord>(props: L): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
@@ -2478,9 +2611,65 @@ type MutableSignalStore<T> = MutableSignal<UnwrapOpaque<T>> & {
2478
2611
  } & (IsAny<T> extends true ? MutableSignalStoreObject<T> : NonNullable<T> extends BaseType ? {
2479
2612
  readonly [LEAF]: () => boolean;
2480
2613
  } : NonNullable<T> extends any[] ? MutableArrayStore<NonNullable<T>> : MutableSignalStoreObject<T>);
2481
- declare function toStore<T extends AnyRecord>(source: MutableSignal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): MutableSignalStore<T>;
2482
- declare function toStore<T extends AnyRecord>(source: WritableSignal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): WritableSignalStore<T>;
2483
- declare function toStore<T extends AnyRecord>(source: Signal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): SignalStore<T>;
2614
+
2615
+ declare const STORE_SHARED_GLOBALS: unique symbol;
2616
+ /**
2617
+ * @internal
2618
+ * Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
2619
+ */
2620
+ type ProxyCache = WeakMap<object, Map<PropertyKey, WeakRef<Signal<any>>>>;
2621
+ /**
2622
+ * @internal
2623
+ * Prunes a cache entry once its proxy is reclaimed by the GC.
2624
+ */
2625
+ type ProxyCleanupRegistry = FinalizationRegistry<{
2626
+ target: object;
2627
+ prop: PropertyKey;
2628
+ }>;
2629
+ /**
2630
+ * @internal
2631
+ * Validates whether a value is a Signal Store.
2632
+ */
2633
+ declare function isStore<T>(value: unknown): value is SignalStore<T>;
2634
+
2635
+ type toStoreOptions = {
2636
+ injector?: Injector;
2637
+ /**
2638
+ * Opt-in autovivification: when writing through a `null`/`undefined` path, create the
2639
+ * missing intermediate containers instead of dropping the write. Off by default.
2640
+ *
2641
+ * Levels whose current value is a known object/array re-vivify as that same shape — the
2642
+ * knowledge is captured when the path is first accessed and cached, so it holds even after
2643
+ * the value is later nulled. This option governs only genuinely-unknown (currently
2644
+ * `null`/`undefined`) levels: `'auto'` (an array for index keys, an object otherwise), an
2645
+ * explicit `'object'`/`'array'`, or a `() => container` factory. See {@link Vivify}.
2646
+ */
2647
+ vivify?: Vivify;
2648
+ /**
2649
+ * Performance opt-in: promise that no node ever switches between leaf and substore (i.e. no
2650
+ * unions mixing a primitive with an object/array). With this on, each node's leaf-ness is
2651
+ * resolved once on the first {@link isLeaf} probe and cached as a constant, skipping the
2652
+ * reactive `computed`. If a node's shape does change anyway, {@link isLeaf} keeps its first
2653
+ * answer. Off by default.
2654
+ */
2655
+ noUnionLeaves?: boolean;
2656
+ /**
2657
+ * @internal
2658
+ * Shared cleanup singletons, they get injected/passed automatically
2659
+ */
2660
+ [STORE_SHARED_GLOBALS]?: {
2661
+ cache: ProxyCache;
2662
+ registry: ProxyCleanupRegistry;
2663
+ };
2664
+ };
2665
+ type StoreOptions<T> = CreateSignalOptions<T> & toStoreOptions;
2666
+ declare function toStore<T extends AnyRecord>(source: MutableSignal<T>, options?: toStoreOptions): MutableSignalStore<T>;
2667
+ declare function toStore<T extends AnyRecord>(source: WritableSignal<T>, options?: toStoreOptions): WritableSignalStore<T>;
2668
+ declare function toStore<T extends AnyRecord>(source: Signal<T>, options?: toStoreOptions): SignalStore<T>;
2669
+ type ExtendStoreOptions = Omit<toStoreOptions, 'vivify' | 'noUnionLeaves' | typeof STORE_SHARED_GLOBALS>;
2670
+ declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: MutableSignalStore<T>, source: MutableSignal<L> | L, options?: ExtendStoreOptions): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2671
+ declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: WritableSignalStore<T>, source: WritableSignal<L> | L, options?: ExtendStoreOptions): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2672
+ declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: SignalStore<T>, source: Signal<L> | L, options?: ExtendStoreOptions): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
2484
2673
  /**
2485
2674
  * Creates a WritableSignalStore from a value.
2486
2675
  * @see {@link toStore}
@@ -2594,22 +2783,13 @@ type Fork<T> = {
2594
2783
  * leaves compare by value, so equal primitives are correctly seen as unchanged.
2595
2784
  */
2596
2785
  declare function merge3<T>(ancestor: T, mine: T, theirs: T): T;
2597
- declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: {
2786
+ /**
2787
+ * ForkStoreOptions, vivify/noUnionLeaves should remain the same & are automatically inherited, override carefully (advanced usecases)
2788
+ */
2789
+ type ForkStoreOptions<T> = toStoreOptions & {
2598
2790
  strategy?: ForkStrategy<T>;
2599
- injector?: Injector;
2600
- /**
2601
- * Store config for the FORK's store — NOT inherited from `base` (it's closed over inside
2602
- * the base's `toStore` and can't be read back). If the base was created with these, pass
2603
- * the same values or the fork's write semantics will differ:
2604
- * - `vivify`: without it, a write through a `null`/`undefined` path is silently dropped on
2605
- * the fork even though the base would have created the container. Match the base.
2606
- * - `noUnionLeaves`: a perf promise; off just means the slower reactive leaf-probe. NOTE it
2607
- * is a whole-store guarantee — a fork that flips a node's type (leaf↔substore) violates it,
2608
- * and on `commit` the base receives the flipped value with stale cached leaf-ness.
2609
- */
2610
- vivify?: Vivify;
2611
- noUnionLeaves?: boolean;
2612
- }): Fork<T>;
2791
+ };
2792
+ declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: ForkStoreOptions<T>): Fork<T>;
2613
2793
 
2614
2794
  /**
2615
2795
  * Interface for storage mechanisms compatible with the `stored` signal.
@@ -2677,6 +2857,18 @@ type CreateStoredOptions<T> = CreateSignalOptions<T> & {
2677
2857
  * Optional validator, which is called on load of value. Store will be set to fallback if value is false
2678
2858
  */
2679
2859
  validate?: (value: T) => boolean;
2860
+ /**
2861
+ * Opt-in pause: gate the persistence effect on an ambient Activity boundary (`true`), a custom
2862
+ * predicate, or `false` (default — no pausing). While paused the value stays live; persistence is
2863
+ * skipped and flushes the latest value on resume. The inbound `storage` listener stays live.
2864
+ * See {@link PauseOption}.
2865
+ */
2866
+ pause?: PauseOption;
2867
+ /**
2868
+ * Injector used when `stored` is created outside an injection context, and to resolve the ambient
2869
+ * pause boundary when `pause` is `true`.
2870
+ */
2871
+ injector?: Injector;
2680
2872
  };
2681
2873
  /**
2682
2874
  * A specialized `WritableSignal` returned by the `stored()` function.
@@ -2748,13 +2940,21 @@ type StoredSignal<T> = WritableSignal<T> & {
2748
2940
  * }
2749
2941
  * ```
2750
2942
  */
2751
- declare function stored<T>(fallback: T, { key, store: providedStore, serialize, deserialize, syncTabs, equal, onKeyChange, cleanupOldKey, validate, ...rest }: CreateStoredOptions<T>): StoredSignal<T>;
2943
+ declare function stored<T>(fallback: T, { key, store: providedStore, serialize, deserialize, syncTabs, equal, onKeyChange, cleanupOldKey, validate, pause, injector: providedInjector, ...rest }: CreateStoredOptions<T>): StoredSignal<T>;
2752
2944
 
2753
2945
  type LegacySyncSignalOptions = {
2754
2946
  id?: string;
2755
2947
  };
2756
2948
  type SyncSignalOptions = {
2757
2949
  id: string;
2950
+ /**
2951
+ * Injector used when `tabSync` is called outside an injection context.
2952
+ *
2953
+ * NOTE: `tabSync` is intentionally NOT pausable. Pausing the outbound broadcast would let its
2954
+ * mount-time echo guard swallow a value changed while hidden, so other tabs would silently miss
2955
+ * it — a cross-tab consistency gap not worth the negligible saving. The channel stays live.
2956
+ */
2957
+ injector?: Injector;
2758
2958
  };
2759
2959
  /**
2760
2960
  * @example tabSync(signal('dark'), { id: 'theme' })
@@ -2803,12 +3003,16 @@ type CreateThrottledOptions<T> = CreateSignalOptions<T> & {
2803
3003
  /**
2804
3004
  * A specialized `WritableSignal` whose publicly readable value updates are throttled.
2805
3005
  *
2806
- * It provides access to the underlying, non-throttled signal via the `original` property.
3006
+ * Provides access to the underlying, non-throttled signal via `original`, and a
3007
+ * `flush()` that emits the current value immediately (clearing any open window) —
3008
+ * useful for terminal transitions that shouldn't wait for the trailing edge.
2807
3009
  *
2808
3010
  * @template T The type of value held by the signal.
2809
- * @see {DebouncedSignal} as the output type has the same structure.
2810
3011
  */
2811
- type ThrottledSignal<T> = DebouncedSignal<T>;
3012
+ type ThrottledSignal<T> = DebouncedSignal<T> & {
3013
+ /** Emit the latest value now, bypassing the remaining throttle window. */
3014
+ flush: () => void;
3015
+ };
2812
3016
  /**
2813
3017
  * A convenience function that creates and throttles a new `WritableSignal` in one step.
2814
3018
  *
@@ -2829,7 +3033,7 @@ type ThrottledSignal<T> = DebouncedSignal<T>;
2829
3033
  * // With a trailing-edge throttle, the final value 'c' would be set
2830
3034
  * // after the 500ms cooldown.
2831
3035
  */
2832
- declare function throttled<T>(initial: T, opt?: CreateThrottledOptions<T>): DebouncedSignal<T>;
3036
+ declare function throttled<T>(initial: T, opt?: CreateThrottledOptions<T>): ThrottledSignal<T>;
2833
3037
  /**
2834
3038
  * Wraps an existing `WritableSignal` to create a new one whose readable value is throttled.
2835
3039
  *
@@ -3045,5 +3249,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
3045
3249
  */
3046
3250
  declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
3047
3251
 
3048
- export { MmActivity, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, 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, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
3049
- export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, Fork, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, Opaque, PausableOptions, PauseOption, PipeableSignal, ReconcileFn, RegisterOptions, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoredSignal, SuspendType, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore };
3252
+ 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 };
3253
+ export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, Opaque, PausableOptions, PauseOption, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ReconcileFn, RegisterOptions, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreOptions, StoredSignal, SuspendType, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };