@mmstack/primitives 21.0.22 → 21.0.24

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": "21.0.22",
3
+ "version": "21.0.24",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",
@@ -564,7 +564,30 @@ declare const mapArray: typeof indexArray;
564
564
  * @param mapFn The mapping function. Receives the item and its index as a Signal.
565
565
  * @param options Optional configuration:
566
566
  * - `onDestroy`: A callback invoked when a mapped item is removed from the array.
567
+ * - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
568
+ * when item references change but conceptual identity is preserved.
567
569
  * @returns A `Signal<U[]>` containing the mapped array.
570
+ *
571
+ * @example
572
+ * ```ts
573
+ * const users = signal([
574
+ * { id: 1, name: 'Alice' },
575
+ * { id: 2, name: 'Bob' },
576
+ * ]);
577
+ *
578
+ * const rows = keyArray(
579
+ * users,
580
+ * (user, index) => ({
581
+ * label: computed(() => `#${index()} ${user.name}`),
582
+ * id: user.id,
583
+ * }),
584
+ * { key: (u) => u.id },
585
+ * );
586
+ *
587
+ * // Reordering users() rebuilds index signals only — `rows` entries
588
+ * // are matched by id and reused, not re-created.
589
+ * users.set([users()[1], users()[0]]);
590
+ * ```
568
591
  */
569
592
  declare function keyArray<T, U, K>(source: Signal<T[]> | (() => T[]), mapFn: (v: T, i: Signal<number>) => U, options?: {
570
593
  onDestroy?: (value: U) => void;
@@ -579,12 +602,71 @@ declare function keyArray<T, U, K>(source: Signal<T[]> | (() => T[]), mapFn: (v:
579
602
  type MappedObject<T extends object, U> = {
580
603
  [K in keyof T]: U;
581
604
  };
605
+ /**
606
+ * Reactively maps each property of an object signal into a new object,
607
+ * preserving the same set of keys. For each key, `mapFn` receives a stable
608
+ * per-key signal — outputs for keys that haven't been added or removed are
609
+ * reused on subsequent reads. Sibling to {@link indexArray} / {@link keyArray}
610
+ * but for object records.
611
+ *
612
+ * The type of per-key signal passed into `mapFn` depends on the source:
613
+ * - `MutableSignal<T>` source → `MutableSignal<T[K]>` (in-place mutation)
614
+ * - `WritableSignal<T>` source → `WritableSignal<T[K]>` (two-way binding)
615
+ * - read-only `Signal<T>` or `() => T` source → read-only `Signal<T[K]>`
616
+ *
617
+ * @typeParam T The object type held by the source signal.
618
+ * @typeParam U The type produced for each key by `mapFn`.
619
+ *
620
+ * @param source A `MutableSignal<T>` whose properties are mapped with full
621
+ * in-place mutation capability via the per-key `MutableSignal`.
622
+ * @param mapFn Receives each key and its per-key `MutableSignal<T[K]>`.
623
+ * @param options Optional `onDestroy(value)` callback fired when a key is
624
+ * removed from the source.
625
+ * @returns A read-only signal of the mapped object.
626
+ *
627
+ * @example
628
+ * ```ts
629
+ * const state = mutable({ name: 'Alice', age: 30 });
630
+ * const view = mapObject(state, (key, prop) => ({
631
+ * label: key,
632
+ * current: computed(() => prop()),
633
+ * onInput: (next: any) => prop.set(next),
634
+ * }));
635
+ * view().age.onInput(31);
636
+ * state(); // { name: 'Alice', age: 31 }
637
+ * ```
638
+ */
582
639
  declare function mapObject<T extends object, U>(source: MutableSignal<T>, mapFn: <K extends keyof T>(key: K, value: MutableSignal<T[K]>) => U, options?: {
583
640
  onDestroy?: (value: U) => void;
584
641
  }): Signal<MappedObject<T, U>>;
642
+ /**
643
+ * Reactively maps each property of a `WritableSignal<T>` into a new object.
644
+ * Each key's per-property signal supports `.set` / `.update` for two-way
645
+ * binding back into the parent object.
646
+ *
647
+ * @example
648
+ * ```ts
649
+ * const user = signal({ name: 'Alice', age: 30 });
650
+ * const inputs = mapObject(user, (key, prop) => ({
651
+ * value: prop,
652
+ * setValue: (v: any) => prop.set(v),
653
+ * }));
654
+ * ```
655
+ */
585
656
  declare function mapObject<T extends object, U>(source: WritableSignal<T>, mapFn: <K extends keyof T>(key: K, value: WritableSignal<T[K]>) => U, options?: {
586
657
  onDestroy?: (value: U) => void;
587
658
  }): Signal<MappedObject<T, U>>;
659
+ /**
660
+ * Reactively maps each property of a read-only `Signal<T>` (or plain `() => T`
661
+ * accessor) into a new object. Per-key signals are read-only.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * const config = computed(() => ({ theme: 'dark', density: 'compact' }));
666
+ * const view = mapObject(config, (key, prop) => `${key}: ${prop()}`);
667
+ * view(); // { theme: 'theme: dark', density: 'density: compact' }
668
+ * ```
669
+ */
588
670
  declare function mapObject<T extends object, U>(source: (() => T) | Signal<T>, mapFn: <K extends keyof T>(key: K, value: Signal<T[K]>) => U, options?: {
589
671
  onDestroy?: (value: U) => void;
590
672
  }): Signal<MappedObject<T, U>>;
@@ -650,18 +732,137 @@ type PipeableSignal<T, TSig extends Signal<T> = Signal<T>> = TSig & {
650
732
  */
651
733
  type SignalValue<TSig extends Signal<any>> = TSig extends Signal<infer V> ? V : never;
652
734
 
653
- /** Project with optional equality. Pure & sync. */
735
+ /**
736
+ * Synchronous projection of a signal value with optional `CreateSignalOptions`
737
+ * (custom `equal`, `debugName`, etc.). Equivalent to `map` plus the ability to
738
+ * pass signal options through to the underlying `computed()`.
739
+ *
740
+ * @example
741
+ * ```ts
742
+ * const user = piped({ id: 1, name: 'Alice' });
743
+ * const name = user.pipe(select((u) => u.name));
744
+ * name(); // 'Alice'
745
+ * ```
746
+ */
654
747
  declare const select: <I, O>(projector: (v: I) => O, opt?: CreateSignalOptions<O>) => Operator<I, O>;
655
- /** Combine with another signal using a projector. */
748
+ /**
749
+ * Combine the piped signal with another `Signal` using a projector. The result
750
+ * recomputes whenever either source changes.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * const price = piped(10);
755
+ * const quantity = signal(3);
756
+ * const total = price.pipe(combineWith(quantity, (p, q) => p * q));
757
+ * total(); // 30
758
+ * ```
759
+ */
656
760
  declare const combineWith: <A, B, R>(other: Signal<B>, project: (a: A, b: B) => R, opt?: CreateSignalOptions<R>) => Operator<A, R>;
657
- /** Only re-emit when equal(prev, next) is false. */
761
+ /**
762
+ * Suppress emissions while consecutive values are considered equal. The
763
+ * comparator defaults to `Object.is`; pass a custom one for structural or
764
+ * key-based equality (e.g. compare by `id` only).
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * const user = piped({ id: 1, lastSeen: Date.now() });
769
+ * const byId = user.pipe(distinct((a, b) => a.id === b.id));
770
+ * // byId only re-emits when `id` changes, not on every `lastSeen` update
771
+ * ```
772
+ */
658
773
  declare const distinct: <T>(equal?: (a: T, b: T) => boolean) => Operator<T, T>;
659
- /** map to new value */
774
+ /**
775
+ * Pure synchronous transform from input to output. Equivalent to a `computed()`
776
+ * that reads the source and returns `fn(value)`.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * const count = piped(2);
781
+ * const doubled = count.pipe(map((n) => n * 2));
782
+ * doubled(); // 4
783
+ * ```
784
+ */
660
785
  declare const map: <I, O>(fn: (v: I) => O) => Operator<I, O>;
661
- /** filter values, keeping the last value if it was ever available, if first value is filtered will return undefined */
786
+ /**
787
+ * Keep only values that pass the predicate. The result holds the last passing
788
+ * value across emissions; before any value passes, the result is `undefined` —
789
+ * see {@link filterWith} when you need a non-`undefined` seed.
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * const event = piped<MouseEvent | null>(null);
794
+ * const clicks = event.pipe(filter((e): e is MouseEvent => e?.type === 'click'));
795
+ * clicks(); // undefined until a click happens, then the last MouseEvent
796
+ * ```
797
+ */
662
798
  declare const filter: <T>(predicate: (v: T) => boolean) => Operator<T, T | undefined>;
663
- /** tap into the value */
664
- declare const tap: <T>(fn: (v: T) => void) => Operator<T, T>;
799
+ /**
800
+ * Run a side effect on every emission without altering the signal value. Wraps
801
+ * Angular's `effect()`, so it must run in an injection context or receive an
802
+ * explicit `injector`. Use for logging / analytics — not for setting other
803
+ * signals (that's what regular `effect()` is for).
804
+ *
805
+ * @example
806
+ * ```ts
807
+ * const count = piped(0);
808
+ * count.pipe(tap((n) => console.log('count:', n)));
809
+ * count.set(1); // logs 'count: 1'
810
+ * ```
811
+ */
812
+ declare const tap: <T>(fn: (v: T) => void, injector?: Injector) => Operator<T, T>;
813
+ /**
814
+ * Like {@link filter}, but emits `initial` until a value first passes the
815
+ * predicate. Eliminates the `T | undefined` return type at the cost of an
816
+ * explicit seed value.
817
+ *
818
+ * @example
819
+ * ```ts
820
+ * const event = piped<MouseEvent | null>(null);
821
+ * const lastClick = event.pipe(filterWith((e) => e?.type === 'click', null));
822
+ * lastClick(); // null until the first click, then the most recent click event
823
+ * ```
824
+ */
825
+ declare const filterWith: <T>(predicate: (v: T) => boolean, initial: T) => Operator<T, T>;
826
+ /**
827
+ * Emit `initial` on the first read, then mirror the source on every subsequent
828
+ * read. Useful for giving a pipeline a sensible seed value before the source
829
+ * is ready (e.g. loading state).
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * const data = piped<User | null>(null);
834
+ * const view = data.pipe(startWith<User | null, 'loading'>('loading'));
835
+ * view(); // 'loading' on first read, then User | null afterward
836
+ * ```
837
+ */
838
+ declare const startWith: <T, U>(initial: U) => Operator<T, T | U>;
839
+ /**
840
+ * Emit `[prev, curr]` tuples so consumers can react to transitions instead of
841
+ * raw values. On the first emission `prev` is `undefined`.
842
+ *
843
+ * @example
844
+ * ```ts
845
+ * const count = piped(0);
846
+ * const delta = count.pipe(pairwise(), map(([prev, curr]) => curr - (prev ?? 0)));
847
+ * count.set(5);
848
+ * delta(); // 5
849
+ * ```
850
+ */
851
+ declare const pairwise: <T>() => Operator<T, [T | undefined, T]>;
852
+ /**
853
+ * Reduce-like accumulator that folds each emission into a running result.
854
+ * Behaves like `Array.prototype.reduce` but applied over time, with the
855
+ * accumulator persisted across emissions.
856
+ *
857
+ * @example
858
+ * ```ts
859
+ * const delta = piped(0);
860
+ * const total = delta.pipe(scan((acc, n) => acc + n, 0));
861
+ * delta.set(5); // total() === 5
862
+ * delta.set(3); // total() === 8
863
+ * ```
864
+ */
865
+ declare const scan: <T, R>(reducer: (acc: R, curr: T) => R, seed: R) => Operator<T, R>;
665
866
 
666
867
  /**
667
868
  * Decorate any `Signal<T>` with a chainable `.pipe(...)` method.
@@ -807,6 +1008,51 @@ declare function pooledSet<T extends Set<unknown>, U = T>(opt: CreateProvidedPoo
807
1008
  declare function pooledMap<T extends Map<unknown, unknown>, U = T>(computation: Computation<T, U>, opt?: CreateSignalOptions<U>): Signal<U>;
808
1009
  declare function pooledMap<T extends Map<unknown, unknown>, U = T>(opt: CreateProvidedPooledOptions<T, U>): Signal<U>;
809
1010
 
1011
+ type BatteryStatus = {
1012
+ readonly level: number;
1013
+ readonly charging: boolean;
1014
+ readonly chargingTime: number;
1015
+ readonly dischargingTime: number;
1016
+ };
1017
+ /**
1018
+ * Creates a read-only signal that tracks the system battery status using the
1019
+ * Battery Status API. Returns `null` until the underlying `getBattery()`
1020
+ * promise resolves, or permanently when the API is unsupported (Firefox /
1021
+ * Safari at the time of writing). SSR-safe.
1022
+ *
1023
+ * @example
1024
+ * ```ts
1025
+ * const battery = batteryStatus();
1026
+ * effect(() => {
1027
+ * const b = battery();
1028
+ * if (b) console.log(`${Math.round(b.level * 100)}% • charging: ${b.charging}`);
1029
+ * });
1030
+ * ```
1031
+ */
1032
+ declare function batteryStatus(debugName?: string): Signal<BatteryStatus | null>;
1033
+
1034
+ type ClipboardSignal = Signal<string> & {
1035
+ /**
1036
+ * Writes `value` to the system clipboard. Resolves once the write completes;
1037
+ * rejects if the Clipboard API rejects (denied permission, insecure context).
1038
+ */
1039
+ readonly copy: (value: string) => Promise<void>;
1040
+ /** `true` iff the Clipboard API is available in this environment. */
1041
+ readonly isSupported: Signal<boolean>;
1042
+ };
1043
+ /**
1044
+ * Creates a read-only signal mirroring the system clipboard contents.
1045
+ *
1046
+ * The signal value starts empty and updates whenever a `copy` event fires on
1047
+ * the document (or {@link ClipboardSignal.copy} is invoked from this app).
1048
+ * SSR-safe — returns `''` and `isSupported: false` on the server.
1049
+ *
1050
+ * Note: read access requires the Clipboard API and an active permission grant
1051
+ * in browsers that gate it. Errors from `navigator.clipboard.readText` are
1052
+ * swallowed silently to keep the signal value stable.
1053
+ */
1054
+ declare function clipboard(debugName?: string): ClipboardSignal;
1055
+
810
1056
  /**
811
1057
  * Represents the size of an element.
812
1058
  */
@@ -908,6 +1154,90 @@ type ElementVisibilitySignal = Signal<IntersectionObserverEntry | undefined> & {
908
1154
  */
909
1155
  declare function elementVisibility(target?: ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>, opt?: ElementVisibilityOptions): ElementVisibilitySignal;
910
1156
 
1157
+ type FocusWithinTarget = ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
1158
+ /**
1159
+ * Creates a read-only signal that tracks whether the focused element is the
1160
+ * target or a descendant of it. Mirrors the CSS `:focus-within` pseudo-class.
1161
+ *
1162
+ * Defaults `target` to the current `ElementRef` so it can be used inline in a
1163
+ * component's `class` field. SSR-safe — returns a constant `false` signal on
1164
+ * the server.
1165
+ *
1166
+ * @example
1167
+ * ```ts
1168
+ * @Component({ ... })
1169
+ * class MenuComponent {
1170
+ * // Defaults to the host element — flips true when focus is inside.
1171
+ * readonly hasFocus = focusWithin();
1172
+ * }
1173
+ * ```
1174
+ */
1175
+ declare function focusWithin(target?: FocusWithinTarget): Signal<boolean>;
1176
+
1177
+ type GeolocationOptions = PositionOptions & {
1178
+ /**
1179
+ * If `true`, uses `navigator.geolocation.watchPosition` and updates the
1180
+ * signal continuously. Otherwise a single `getCurrentPosition` call is made.
1181
+ * @default false
1182
+ */
1183
+ watch?: boolean;
1184
+ /** Optional debug name for the produced signal. */
1185
+ debugName?: string;
1186
+ };
1187
+ type GeolocationSignal = Signal<GeolocationPosition | null> & {
1188
+ readonly error: Signal<GeolocationPositionError | null>;
1189
+ readonly loading: Signal<boolean>;
1190
+ };
1191
+ /**
1192
+ * Creates a read-only signal that exposes the current geolocation position.
1193
+ *
1194
+ * The returned signal carries `error` and `loading` sub-signals for permission
1195
+ * failures and the in-flight initial fetch respectively. SSR-safe — on the
1196
+ * server the position is `null`, loading is `false`, and no API calls are made.
1197
+ *
1198
+ * @example
1199
+ * ```ts
1200
+ * const where = geolocation({ watch: true, enableHighAccuracy: true });
1201
+ * effect(() => console.log(where()?.coords, where.error()));
1202
+ * ```
1203
+ */
1204
+ declare function geolocation(opt?: GeolocationOptions): GeolocationSignal;
1205
+
1206
+ type IdleOptions = {
1207
+ /**
1208
+ * Milliseconds of user inactivity before the signal flips to `true`.
1209
+ * @default 60_000
1210
+ */
1211
+ ms?: number;
1212
+ /**
1213
+ * Activity events that reset the idle timer.
1214
+ * @default ['mousemove','keydown','touchstart','scroll','visibilitychange']
1215
+ */
1216
+ events?: string[];
1217
+ /** Optional debug name for the produced signal. */
1218
+ debugName?: string;
1219
+ };
1220
+ type IdleSignal = Signal<boolean> & {
1221
+ /** Timestamp of the last idle/active transition. */
1222
+ readonly since: Signal<Date>;
1223
+ };
1224
+ /**
1225
+ * Creates a read-only signal that flips to `true` after a window of user
1226
+ * inactivity. Any of the configured `events` (default: pointer/keyboard/scroll
1227
+ * activity) resets the timer and flips the signal back to `false`.
1228
+ *
1229
+ * SSR-safe — always `false` with a frozen `since` date on the server.
1230
+ *
1231
+ * @example
1232
+ * ```ts
1233
+ * const isAway = idle({ ms: 30_000 });
1234
+ * effect(() => {
1235
+ * if (isAway()) console.log('idle since', isAway.since());
1236
+ * });
1237
+ * ```
1238
+ */
1239
+ declare function idle(opt?: IdleOptions): IdleSignal;
1240
+
911
1241
  /**
912
1242
  * Creates a read-only signal that reactively tracks whether a CSS media query
913
1243
  * string currently matches.
@@ -1098,9 +1428,40 @@ type NetworkStatusSignal = Signal<boolean> & {
1098
1428
  *
1099
1429
  * @param debugName Optional debug name for the signal.
1100
1430
  * @returns A `NetworkStatusSignal` instance.
1431
+ *
1432
+ * @example
1433
+ * ```ts
1434
+ * const online = networkStatus();
1435
+ * effect(() => {
1436
+ * if (!online()) console.log('offline since', online.since());
1437
+ * });
1438
+ * ```
1101
1439
  */
1102
1440
  declare function networkStatus(debugName?: string): NetworkStatusSignal;
1103
1441
 
1442
+ type ScreenOrientation = {
1443
+ /** Angle in degrees relative to the natural orientation. */
1444
+ readonly angle: number;
1445
+ /** One of the four `OrientationType` strings. */
1446
+ readonly type: OrientationType;
1447
+ };
1448
+ /**
1449
+ * Creates a read-only signal that tracks `screen.orientation`.
1450
+ *
1451
+ * SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
1452
+ * and in environments without `screen.orientation` support.
1453
+ *
1454
+ * @example
1455
+ * ```ts
1456
+ * const screenOrientation = orientation();
1457
+ * effect(() => {
1458
+ * const { type, angle } = screenOrientation();
1459
+ * console.log(`${type} at ${angle}°`);
1460
+ * });
1461
+ * ```
1462
+ */
1463
+ declare function orientation(debugName?: string): Signal<ScreenOrientation>;
1464
+
1104
1465
  /**
1105
1466
  * Creates a read-only signal that tracks the page's visibility state.
1106
1467
  *
@@ -1353,6 +1714,39 @@ type SensorTypedOptions = {
1353
1714
  };
1354
1715
  returnType: Signal<boolean>;
1355
1716
  };
1717
+ geolocation: {
1718
+ opt: GeolocationOptions;
1719
+ returnType: GeolocationSignal;
1720
+ };
1721
+ clipboard: {
1722
+ opt: {
1723
+ debugName?: string;
1724
+ };
1725
+ returnType: ClipboardSignal;
1726
+ };
1727
+ orientation: {
1728
+ opt: {
1729
+ debugName?: string;
1730
+ };
1731
+ returnType: Signal<ScreenOrientation>;
1732
+ };
1733
+ batteryStatus: {
1734
+ opt: {
1735
+ debugName?: string;
1736
+ };
1737
+ returnType: Signal<BatteryStatus | null>;
1738
+ };
1739
+ idle: {
1740
+ opt: IdleOptions;
1741
+ returnType: IdleSignal;
1742
+ };
1743
+ focusWithin: {
1744
+ opt: {
1745
+ debugName?: string;
1746
+ target?: ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
1747
+ };
1748
+ returnType: Signal<boolean>;
1749
+ };
1356
1750
  };
1357
1751
  /**
1358
1752
  * Creates a sensor signal that the elements visiblity within the viewport
@@ -1444,14 +1838,106 @@ declare function sensor(type: 'windowSize', options?: SensorTypedOptions['window
1444
1838
  * @example const pageScroll = sensor('scrollPosition', { throttle: 150 });
1445
1839
  */
1446
1840
  declare function sensor(type: 'scrollPosition', options?: SensorTypedOptions['scrollPosition']['opt']): ScrollPositionSignal;
1841
+ /**
1842
+ * Creates a sensor signal exposing the device's current geolocation position.
1843
+ * @see {geolocation}
1844
+ */
1845
+ declare function sensor(type: 'geolocation', options?: SensorTypedOptions['geolocation']['opt']): GeolocationSignal;
1846
+ /**
1847
+ * Creates a sensor signal mirroring the system clipboard contents.
1848
+ * @see {clipboard}
1849
+ */
1850
+ declare function sensor(type: 'clipboard', options?: SensorTypedOptions['clipboard']['opt']): ClipboardSignal;
1851
+ /**
1852
+ * Creates a sensor signal tracking the screen orientation.
1853
+ * @see {orientation}
1854
+ */
1855
+ declare function sensor(type: 'orientation', options?: SensorTypedOptions['orientation']['opt']): Signal<ScreenOrientation>;
1856
+ /**
1857
+ * Creates a sensor signal tracking the system battery status.
1858
+ * @see {batteryStatus}
1859
+ */
1860
+ declare function sensor(type: 'batteryStatus', options?: SensorTypedOptions['batteryStatus']['opt']): Signal<BatteryStatus | null>;
1861
+ /**
1862
+ * Creates a sensor signal that flips to `true` after a window of user inactivity.
1863
+ * @see {idle}
1864
+ */
1865
+ declare function sensor(type: 'idle', options?: SensorTypedOptions['idle']['opt']): IdleSignal;
1866
+ /**
1867
+ * Creates a sensor signal tracking whether focus is within a target subtree.
1868
+ * @see {focusWithin}
1869
+ */
1870
+ declare function sensor(type: 'focusWithin', options?: SensorTypedOptions['focusWithin']['opt']): Signal<boolean>;
1447
1871
  type SensorsOptions<TKey extends keyof SensorTypedOptions> = {
1448
1872
  [K in TKey]: SensorTypedOptions[K]['opt'];
1449
1873
  };
1450
1874
  type Sensors<TKey extends keyof SensorTypedOptions> = {
1451
1875
  [K in TKey]: SensorTypedOptions[K]['returnType'];
1452
1876
  };
1877
+ /**
1878
+ * Bulk sensor factory — creates several sensor signals at once and returns
1879
+ * them keyed by sensor type. Convenient when a single consumer needs to react
1880
+ * to multiple browser signals; for a single sensor prefer {@link sensor}
1881
+ * directly.
1882
+ *
1883
+ * @typeParam TType The union of sensor keys being requested.
1884
+ * @param track Array of sensor type keys to create.
1885
+ * @param opt Optional per-sensor options keyed by sensor type.
1886
+ * @returns A record `{ [key]: <SensorReturnType> }` for each requested key.
1887
+ *
1888
+ * @example
1889
+ * ```ts
1890
+ * const { windowSize, networkStatus } = sensors(
1891
+ * ['windowSize', 'networkStatus'],
1892
+ * { windowSize: { throttle: 200 } },
1893
+ * );
1894
+ *
1895
+ * effect(() => console.log(windowSize(), networkStatus()));
1896
+ * ```
1897
+ */
1453
1898
  declare function sensors<const TType extends keyof SensorTypedOptions>(track: TType[], opt?: SensorsOptions<TType>): Sensors<TType>;
1454
1899
 
1900
+ /**
1901
+ * Options for {@link signalFromEvent}. Extends the native
1902
+ * `AddEventListenerOptions` so callers can opt into `capture`, `passive`, etc.
1903
+ */
1904
+ type SignalFromEventOptions = AddEventListenerOptions & {
1905
+ /** Optional debug name for the produced signal. */
1906
+ debugName?: string;
1907
+ /** Override the DestroyRef used to remove the listener on teardown. */
1908
+ destroyRef?: DestroyRef;
1909
+ /** Override the Injector used to inject dependencies. */
1910
+ injector?: Injector;
1911
+ };
1912
+ type EventTargetLike = EventTarget | ElementRef<EventTarget>;
1913
+ type ResolvableTarget = EventTargetLike | Signal<EventTargetLike | null>;
1914
+ /**
1915
+ * Creates a read-only signal that emits the latest event dispatched on a
1916
+ * target. The target can be a static `EventTarget`, an `ElementRef`, or a
1917
+ * `Signal` that resolves to one (or `null` to detach).
1918
+ *
1919
+ * SSR-safe: on the server the signal returns the provided `initial` value and
1920
+ * no listener is registered.
1921
+ *
1922
+ * @example
1923
+ * ```ts
1924
+ * const click = signalFromEvent(document, 'click', null);
1925
+ * ```
1926
+ *
1927
+ * @example
1928
+ * ```ts
1929
+ * // With a projection — store just the coordinates.
1930
+ * const point = signalFromEvent<MouseEvent, { x: number; y: number }>(
1931
+ * document,
1932
+ * 'mousemove',
1933
+ * { x: 0, y: 0 },
1934
+ * (e) => ({ x: e.clientX, y: e.clientY }),
1935
+ * );
1936
+ * ```
1937
+ */
1938
+ declare function signalFromEvent<TEvent extends Event>(target: ResolvableTarget, eventName: string, initial: TEvent | null, opt?: SignalFromEventOptions): Signal<TEvent | null>;
1939
+ declare function signalFromEvent<TEvent extends Event, U>(target: ResolvableTarget, eventName: string, initial: U, project: (event: TEvent) => U, opt?: SignalFromEventOptions): Signal<U>;
1940
+
1455
1941
  type BaseType = string | number | boolean | symbol | undefined | null | Function | Date | RegExp;
1456
1942
  type Key = string | number;
1457
1943
  type AnyRecord = Record<Key, any>;
@@ -1646,47 +2132,21 @@ type StoredSignal<T> = WritableSignal<T> & {
1646
2132
  */
1647
2133
  declare function stored<T>(fallback: T, { key, store: providedStore, serialize, deserialize, syncTabs, equal, onKeyChange, cleanupOldKey, validate, ...rest }: CreateStoredOptions<T>): StoredSignal<T>;
1648
2134
 
1649
- type SyncSignalOptions = {
2135
+ type LegacySyncSignalOptions = {
1650
2136
  id?: string;
1651
2137
  };
2138
+ type SyncSignalOptions = {
2139
+ id: string;
2140
+ };
1652
2141
  /**
1653
- * Synchronizes a WritableSignal across browser tabs using BroadcastChannel API.
1654
- *
1655
- * Creates a shared signal that automatically syncs its value between all tabs
1656
- * of the same application. When the signal is updated in one tab, all other
1657
- * tabs will receive the new value automatically.
1658
- *
1659
- * @template T - The type of the WritableSignal
1660
- * @param sig - The WritableSignal to synchronize across tabs
1661
- * @param opt - Optional configuration object
1662
- * @param opt.id - Explicit channel ID for synchronization. If not provided,
1663
- * a deterministic ID is generated based on the call site.
1664
- * Use explicit IDs in production for reliability.
1665
- *
1666
- * @returns The same WritableSignal instance, now synchronized across tabs
1667
- *
2142
+ * @example tabSync(signal('dark), {id: 'theme})
2143
+ */
2144
+ declare function tabSync<T extends WritableSignal<any>>(sig: T, opt: SyncSignalOptions | string): T;
2145
+ /**
2146
+ * @deprecated Use `tabSync` with `SyncSignalOptions` instead and pass the options as the second argument
1668
2147
  * @throws {Error} When deterministic ID generation fails and no explicit ID is provided
1669
- *
1670
- * @example
1671
- * ```typescript
1672
- * // Basic usage - auto-generates channel ID from call site
1673
- * const theme = tabSync(signal('dark'));
1674
- *
1675
- * // With explicit ID (recommended for production)
1676
- * const userPrefs = tabSync(signal({ lang: 'en' }), { id: 'user-preferences' });
1677
- *
1678
- * // Changes in one tab will sync to all other tabs
1679
- * theme.set('light'); // All tabs will update to 'light'
1680
- * ```
1681
- *
1682
- * @remarks
1683
- * - Only works in browser environments (returns original signal on server)
1684
- * - Uses a single BroadcastChannel for all synchronized signals
1685
- * - Automatically cleans up listeners when the injection context is destroyed
1686
- * - Initial signal value after sync setup is not broadcasted to prevent loops
1687
- *
1688
2148
  */
1689
- declare function tabSync<T extends WritableSignal<any>>(sig: T, opt?: SyncSignalOptions): T;
2149
+ declare function tabSync<T extends WritableSignal<any>>(sig: T, opt?: LegacySyncSignalOptions): T;
1690
2150
 
1691
2151
  /**
1692
2152
  * Options for creating a throttled writable signal.
@@ -1707,6 +2167,18 @@ type CreateThrottledOptions<T> = CreateSignalOptions<T> & {
1707
2167
  * If it is not provided or injected, the timer will not be cleared automatically...which is usually fine :)
1708
2168
  */
1709
2169
  destroyRef?: DestroyRef;
2170
+ /**
2171
+ * If `true`, the throttled signal emits the first value immediately when a
2172
+ * burst starts, then enforces the cooldown window before the next emission.
2173
+ * @default false
2174
+ */
2175
+ leading?: boolean;
2176
+ /**
2177
+ * If `true`, the throttled signal emits the latest pending value at the end
2178
+ * of each cooldown window (only when at least one write occurred during it).
2179
+ * @default true
2180
+ */
2181
+ trailing?: boolean;
1710
2182
  };
1711
2183
  /**
1712
2184
  * A specialized `WritableSignal` whose publicly readable value updates are throttled.
@@ -1947,7 +2419,7 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
1947
2419
  * console.log('Can undo:', name.canUndo()); // false
1948
2420
  * ```
1949
2421
  */
1950
- declare function withHistory<T>(source: WritableSignal<T>, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
2422
+ declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
1951
2423
 
1952
- export { chunked, combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, indexArray, isDerivation, isMutable, isStore, keyArray, map, mapArray, mapObject, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, pageVisibility, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, scrollPosition, select, sensor, sensors, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
1953
- export type { Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, PipeableSignal, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SignalStore, SignalWithHistory, StoredSignal, ThrottledSignal, UntilOptions, WindowSize, WindowSizeOptions, WindowSizeSignal, WritableSignalStore };
2424
+ export { batteryStatus, chunked, clipboard, combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, filterWith, focusWithin, geolocation, idle, indexArray, isDerivation, isMutable, isStore, keyArray, map, mapArray, mapObject, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, orientation, pageVisibility, pairwise, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
2425
+ export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, PipeableSignal, ScreenOrientation, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SignalFromEventOptions, SignalStore, SignalWithHistory, StoredSignal, ThrottledSignal, UntilOptions, WindowSize, WindowSizeOptions, WindowSizeSignal, WritableSignalStore };