@mmstack/primitives 19.3.5 → 19.3.7

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.
@@ -15,7 +15,30 @@ import { type Signal } from '@angular/core';
15
15
  * @param mapFn The mapping function. Receives the item and its index as a Signal.
16
16
  * @param options Optional configuration:
17
17
  * - `onDestroy`: A callback invoked when a mapped item is removed from the array.
18
+ * - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
19
+ * when item references change but conceptual identity is preserved.
18
20
  * @returns A `Signal<U[]>` containing the mapped array.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const users = signal([
25
+ * { id: 1, name: 'Alice' },
26
+ * { id: 2, name: 'Bob' },
27
+ * ]);
28
+ *
29
+ * const rows = keyArray(
30
+ * users,
31
+ * (user, index) => ({
32
+ * label: computed(() => `#${index()} ${user.name}`),
33
+ * id: user.id,
34
+ * }),
35
+ * { key: (u) => u.id },
36
+ * );
37
+ *
38
+ * // Reordering users() rebuilds index signals only — `rows` entries
39
+ * // are matched by id and reused, not re-created.
40
+ * users.set([users()[1], users()[0]]);
41
+ * ```
19
42
  */
20
43
  export declare function keyArray<T, U, K>(source: Signal<T[]> | (() => T[]), mapFn: (v: T, i: Signal<number>) => U, options?: {
21
44
  onDestroy?: (value: U) => void;
@@ -3,12 +3,71 @@ import { type MutableSignal } from '../mutable';
3
3
  type MappedObject<T extends object, U> = {
4
4
  [K in keyof T]: U;
5
5
  };
6
+ /**
7
+ * Reactively maps each property of an object signal into a new object,
8
+ * preserving the same set of keys. For each key, `mapFn` receives a stable
9
+ * per-key signal — outputs for keys that haven't been added or removed are
10
+ * reused on subsequent reads. Sibling to {@link indexArray} / {@link keyArray}
11
+ * but for object records.
12
+ *
13
+ * The type of per-key signal passed into `mapFn` depends on the source:
14
+ * - `MutableSignal<T>` source → `MutableSignal<T[K]>` (in-place mutation)
15
+ * - `WritableSignal<T>` source → `WritableSignal<T[K]>` (two-way binding)
16
+ * - read-only `Signal<T>` or `() => T` source → read-only `Signal<T[K]>`
17
+ *
18
+ * @typeParam T The object type held by the source signal.
19
+ * @typeParam U The type produced for each key by `mapFn`.
20
+ *
21
+ * @param source A `MutableSignal<T>` whose properties are mapped with full
22
+ * in-place mutation capability via the per-key `MutableSignal`.
23
+ * @param mapFn Receives each key and its per-key `MutableSignal<T[K]>`.
24
+ * @param options Optional `onDestroy(value)` callback fired when a key is
25
+ * removed from the source.
26
+ * @returns A read-only signal of the mapped object.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const state = mutable({ name: 'Alice', age: 30 });
31
+ * const view = mapObject(state, (key, prop) => ({
32
+ * label: key,
33
+ * current: computed(() => prop()),
34
+ * onInput: (next: any) => prop.set(next),
35
+ * }));
36
+ * view().age.onInput(31);
37
+ * state(); // { name: 'Alice', age: 31 }
38
+ * ```
39
+ */
6
40
  export declare function mapObject<T extends object, U>(source: MutableSignal<T>, mapFn: <K extends keyof T>(key: K, value: MutableSignal<T[K]>) => U, options?: {
7
41
  onDestroy?: (value: U) => void;
8
42
  }): Signal<MappedObject<T, U>>;
43
+ /**
44
+ * Reactively maps each property of a `WritableSignal<T>` into a new object.
45
+ * Each key's per-property signal supports `.set` / `.update` for two-way
46
+ * binding back into the parent object.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const user = signal({ name: 'Alice', age: 30 });
51
+ * const inputs = mapObject(user, (key, prop) => ({
52
+ * value: prop,
53
+ * setValue: (v: any) => prop.set(v),
54
+ * }));
55
+ * ```
56
+ */
9
57
  export declare function mapObject<T extends object, U>(source: WritableSignal<T>, mapFn: <K extends keyof T>(key: K, value: WritableSignal<T[K]>) => U, options?: {
10
58
  onDestroy?: (value: U) => void;
11
59
  }): Signal<MappedObject<T, U>>;
60
+ /**
61
+ * Reactively maps each property of a read-only `Signal<T>` (or plain `() => T`
62
+ * accessor) into a new object. Per-key signals are read-only.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const config = computed(() => ({ theme: 'dark', density: 'compact' }));
67
+ * const view = mapObject(config, (key, prop) => `${key}: ${prop()}`);
68
+ * view(); // { theme: 'theme: dark', density: 'density: compact' }
69
+ * ```
70
+ */
12
71
  export declare function mapObject<T extends object, U>(source: (() => T) | Signal<T>, mapFn: <K extends keyof T>(key: K, value: Signal<T[K]>) => U, options?: {
13
72
  onDestroy?: (value: U) => void;
14
73
  }): Signal<MappedObject<T, U>>;
@@ -1,14 +1,133 @@
1
- import { type CreateSignalOptions, type Signal } from '@angular/core';
1
+ import { type CreateSignalOptions, type Injector, type Signal } from '@angular/core';
2
2
  import { type Operator } from './types';
3
- /** Project with optional equality. Pure & sync. */
3
+ /**
4
+ * Synchronous projection of a signal value with optional `CreateSignalOptions`
5
+ * (custom `equal`, `debugName`, etc.). Equivalent to `map` plus the ability to
6
+ * pass signal options through to the underlying `computed()`.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const user = piped({ id: 1, name: 'Alice' });
11
+ * const name = user.pipe(select((u) => u.name));
12
+ * name(); // 'Alice'
13
+ * ```
14
+ */
4
15
  export declare const select: <I, O>(projector: (v: I) => O, opt?: CreateSignalOptions<O>) => Operator<I, O>;
5
- /** Combine with another signal using a projector. */
16
+ /**
17
+ * Combine the piped signal with another `Signal` using a projector. The result
18
+ * recomputes whenever either source changes.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const price = piped(10);
23
+ * const quantity = signal(3);
24
+ * const total = price.pipe(combineWith(quantity, (p, q) => p * q));
25
+ * total(); // 30
26
+ * ```
27
+ */
6
28
  export declare const combineWith: <A, B, R>(other: Signal<B>, project: (a: A, b: B) => R, opt?: CreateSignalOptions<R>) => Operator<A, R>;
7
- /** Only re-emit when equal(prev, next) is false. */
29
+ /**
30
+ * Suppress emissions while consecutive values are considered equal. The
31
+ * comparator defaults to `Object.is`; pass a custom one for structural or
32
+ * key-based equality (e.g. compare by `id` only).
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const user = piped({ id: 1, lastSeen: Date.now() });
37
+ * const byId = user.pipe(distinct((a, b) => a.id === b.id));
38
+ * // byId only re-emits when `id` changes, not on every `lastSeen` update
39
+ * ```
40
+ */
8
41
  export declare const distinct: <T>(equal?: (a: T, b: T) => boolean) => Operator<T, T>;
9
- /** map to new value */
42
+ /**
43
+ * Pure synchronous transform from input to output. Equivalent to a `computed()`
44
+ * that reads the source and returns `fn(value)`.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const count = piped(2);
49
+ * const doubled = count.pipe(map((n) => n * 2));
50
+ * doubled(); // 4
51
+ * ```
52
+ */
10
53
  export declare const map: <I, O>(fn: (v: I) => O) => Operator<I, O>;
11
- /** filter values, keeping the last value if it was ever available, if first value is filtered will return undefined */
54
+ /**
55
+ * Keep only values that pass the predicate. The result holds the last passing
56
+ * value across emissions; before any value passes, the result is `undefined` —
57
+ * see {@link filterWith} when you need a non-`undefined` seed.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const event = piped<MouseEvent | null>(null);
62
+ * const clicks = event.pipe(filter((e): e is MouseEvent => e?.type === 'click'));
63
+ * clicks(); // undefined until a click happens, then the last MouseEvent
64
+ * ```
65
+ */
12
66
  export declare const filter: <T>(predicate: (v: T) => boolean) => Operator<T, T | undefined>;
13
- /** tap into the value */
14
- export declare const tap: <T>(fn: (v: T) => void) => Operator<T, T>;
67
+ /**
68
+ * Run a side effect on every emission without altering the signal value. Wraps
69
+ * Angular's `effect()`, so it must run in an injection context or receive an
70
+ * explicit `injector`. Use for logging / analytics — not for setting other
71
+ * signals (that's what regular `effect()` is for).
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const count = piped(0);
76
+ * count.pipe(tap((n) => console.log('count:', n)));
77
+ * count.set(1); // logs 'count: 1'
78
+ * ```
79
+ */
80
+ export declare const tap: <T>(fn: (v: T) => void, injector?: Injector) => Operator<T, T>;
81
+ /**
82
+ * Like {@link filter}, but emits `initial` until a value first passes the
83
+ * predicate. Eliminates the `T | undefined` return type at the cost of an
84
+ * explicit seed value.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * const event = piped<MouseEvent | null>(null);
89
+ * const lastClick = event.pipe(filterWith((e) => e?.type === 'click', null));
90
+ * lastClick(); // null until the first click, then the most recent click event
91
+ * ```
92
+ */
93
+ export declare const filterWith: <T>(predicate: (v: T) => boolean, initial: T) => Operator<T, T>;
94
+ /**
95
+ * Emit `initial` on the first read, then mirror the source on every subsequent
96
+ * read. Useful for giving a pipeline a sensible seed value before the source
97
+ * is ready (e.g. loading state).
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const data = piped<User | null>(null);
102
+ * const view = data.pipe(startWith<User | null, 'loading'>('loading'));
103
+ * view(); // 'loading' on first read, then User | null afterward
104
+ * ```
105
+ */
106
+ export declare const startWith: <T, U>(initial: U) => Operator<T, T | U>;
107
+ /**
108
+ * Emit `[prev, curr]` tuples so consumers can react to transitions instead of
109
+ * raw values. On the first emission `prev` is `undefined`.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const count = piped(0);
114
+ * const delta = count.pipe(pairwise(), map(([prev, curr]) => curr - (prev ?? 0)));
115
+ * count.set(5);
116
+ * delta(); // 5
117
+ * ```
118
+ */
119
+ export declare const pairwise: <T>() => Operator<T, [T | undefined, T]>;
120
+ /**
121
+ * Reduce-like accumulator that folds each emission into a running result.
122
+ * Behaves like `Array.prototype.reduce` but applied over time, with the
123
+ * accumulator persisted across emissions.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const delta = piped(0);
128
+ * const total = delta.pipe(scan((acc, n) => acc + n, 0));
129
+ * delta.set(5); // total() === 5
130
+ * delta.set(3); // total() === 8
131
+ * ```
132
+ */
133
+ export declare const scan: <T, R>(reducer: (acc: R, curr: T) => R, seed: R) => Operator<T, R>;
@@ -0,0 +1,23 @@
1
+ import { type Signal } from '@angular/core';
2
+ export type BatteryStatus = {
3
+ readonly level: number;
4
+ readonly charging: boolean;
5
+ readonly chargingTime: number;
6
+ readonly dischargingTime: number;
7
+ };
8
+ /**
9
+ * Creates a read-only signal that tracks the system battery status using the
10
+ * Battery Status API. Returns `null` until the underlying `getBattery()`
11
+ * promise resolves, or permanently when the API is unsupported (Firefox /
12
+ * Safari at the time of writing). SSR-safe.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const battery = batteryStatus();
17
+ * effect(() => {
18
+ * const b = battery();
19
+ * if (b) console.log(`${Math.round(b.level * 100)}% • charging: ${b.charging}`);
20
+ * });
21
+ * ```
22
+ */
23
+ export declare function batteryStatus(debugName?: string): Signal<BatteryStatus | null>;
@@ -0,0 +1,22 @@
1
+ import { type Signal } from '@angular/core';
2
+ export type ClipboardSignal = Signal<string> & {
3
+ /**
4
+ * Writes `value` to the system clipboard. Resolves once the write completes;
5
+ * rejects if the Clipboard API rejects (denied permission, insecure context).
6
+ */
7
+ readonly copy: (value: string) => Promise<void>;
8
+ /** `true` iff the Clipboard API is available in this environment. */
9
+ readonly isSupported: Signal<boolean>;
10
+ };
11
+ /**
12
+ * Creates a read-only signal mirroring the system clipboard contents.
13
+ *
14
+ * The signal value starts empty and updates whenever a `copy` event fires on
15
+ * the document (or {@link ClipboardSignal.copy} is invoked from this app).
16
+ * SSR-safe — returns `''` and `isSupported: false` on the server.
17
+ *
18
+ * Note: read access requires the Clipboard API and an active permission grant
19
+ * in browsers that gate it. Errors from `navigator.clipboard.readText` are
20
+ * swallowed silently to keep the signal value stable.
21
+ */
22
+ export declare function clipboard(debugName?: string): ClipboardSignal;
@@ -0,0 +1,21 @@
1
+ import { ElementRef, type Signal } from '@angular/core';
2
+ type FocusWithinTarget = ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
3
+ /**
4
+ * Creates a read-only signal that tracks whether the focused element is the
5
+ * target or a descendant of it. Mirrors the CSS `:focus-within` pseudo-class.
6
+ *
7
+ * Defaults `target` to the current `ElementRef` so it can be used inline in a
8
+ * component's `class` field. SSR-safe — returns a constant `false` signal on
9
+ * the server.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * @Component({ ... })
14
+ * class MenuComponent {
15
+ * // Defaults to the host element — flips true when focus is inside.
16
+ * readonly hasFocus = focusWithin();
17
+ * }
18
+ * ```
19
+ */
20
+ export declare function focusWithin(target?: FocusWithinTarget): Signal<boolean>;
21
+ export {};
@@ -0,0 +1,29 @@
1
+ import { type Signal } from '@angular/core';
2
+ export type GeolocationOptions = PositionOptions & {
3
+ /**
4
+ * If `true`, uses `navigator.geolocation.watchPosition` and updates the
5
+ * signal continuously. Otherwise a single `getCurrentPosition` call is made.
6
+ * @default false
7
+ */
8
+ watch?: boolean;
9
+ /** Optional debug name for the produced signal. */
10
+ debugName?: string;
11
+ };
12
+ export type GeolocationSignal = Signal<GeolocationPosition | null> & {
13
+ readonly error: Signal<GeolocationPositionError | null>;
14
+ readonly loading: Signal<boolean>;
15
+ };
16
+ /**
17
+ * Creates a read-only signal that exposes the current geolocation position.
18
+ *
19
+ * The returned signal carries `error` and `loading` sub-signals for permission
20
+ * failures and the in-flight initial fetch respectively. SSR-safe — on the
21
+ * server the position is `null`, loading is `false`, and no API calls are made.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const where = geolocation({ watch: true, enableHighAccuracy: true });
26
+ * effect(() => console.log(where()?.coords, where.error()));
27
+ * ```
28
+ */
29
+ export declare function geolocation(opt?: GeolocationOptions): GeolocationSignal;
@@ -0,0 +1,35 @@
1
+ import { type Signal } from '@angular/core';
2
+ export type IdleOptions = {
3
+ /**
4
+ * Milliseconds of user inactivity before the signal flips to `true`.
5
+ * @default 60_000
6
+ */
7
+ ms?: number;
8
+ /**
9
+ * Activity events that reset the idle timer.
10
+ * @default ['mousemove','keydown','touchstart','scroll','visibilitychange']
11
+ */
12
+ events?: string[];
13
+ /** Optional debug name for the produced signal. */
14
+ debugName?: string;
15
+ };
16
+ export type IdleSignal = Signal<boolean> & {
17
+ /** Timestamp of the last idle/active transition. */
18
+ readonly since: Signal<Date>;
19
+ };
20
+ /**
21
+ * Creates a read-only signal that flips to `true` after a window of user
22
+ * inactivity. Any of the configured `events` (default: pointer/keyboard/scroll
23
+ * activity) resets the timer and flips the signal back to `false`.
24
+ *
25
+ * SSR-safe — always `false` with a frozen `since` date on the server.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const isAway = idle({ ms: 30_000 });
30
+ * effect(() => {
31
+ * if (isAway()) console.log('idle since', isAway.since());
32
+ * });
33
+ * ```
34
+ */
35
+ export declare function idle(opt?: IdleOptions): IdleSignal;
@@ -1,9 +1,16 @@
1
+ export * from './battery-status';
2
+ export * from './clipboard';
1
3
  export * from './element-size';
2
4
  export * from './element-visibility';
5
+ export * from './focus-within';
6
+ export * from './geolocation';
7
+ export * from './idle';
3
8
  export * from './media-query';
4
9
  export * from './mouse-position';
5
10
  export * from './network-status';
11
+ export * from './orientation';
6
12
  export * from './page-visibility';
7
13
  export * from './scroll-position';
8
14
  export * from './sensor';
15
+ export * from './signal-from-event';
9
16
  export * from './window-size';
@@ -16,5 +16,13 @@ export type NetworkStatusSignal = Signal<boolean> & {
16
16
  *
17
17
  * @param debugName Optional debug name for the signal.
18
18
  * @returns A `NetworkStatusSignal` instance.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const online = networkStatus();
23
+ * effect(() => {
24
+ * if (!online()) console.log('offline since', online.since());
25
+ * });
26
+ * ```
19
27
  */
20
28
  export declare function networkStatus(debugName?: string): NetworkStatusSignal;
@@ -0,0 +1,23 @@
1
+ import { type Signal } from '@angular/core';
2
+ export type ScreenOrientation = {
3
+ /** Angle in degrees relative to the natural orientation. */
4
+ readonly angle: number;
5
+ /** One of the four `OrientationType` strings. */
6
+ readonly type: OrientationType;
7
+ };
8
+ /**
9
+ * Creates a read-only signal that tracks `screen.orientation`.
10
+ *
11
+ * SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
12
+ * and in environments without `screen.orientation` support.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const screenOrientation = orientation();
17
+ * effect(() => {
18
+ * const { type, angle } = screenOrientation();
19
+ * console.log(`${type} at ${angle}°`);
20
+ * });
21
+ * ```
22
+ */
23
+ export declare function orientation(debugName?: string): Signal<ScreenOrientation>;
@@ -1,8 +1,13 @@
1
1
  import { type ElementRef, type Signal } from '@angular/core';
2
+ import { type BatteryStatus } from './battery-status';
3
+ import { type ClipboardSignal } from './clipboard';
2
4
  import { type ElementSizeOptions as BaseElementSizeOptions, type ElementSizeSignal } from './element-size';
3
5
  import { type ElementVisibilityOptions as BaseElementVisibilityOptions, type ElementVisibilitySignal } from './element-visibility';
6
+ import { type GeolocationOptions, type GeolocationSignal } from './geolocation';
7
+ import { type IdleOptions, type IdleSignal } from './idle';
4
8
  import { type MousePositionOptions, type MousePositionSignal } from './mouse-position';
5
9
  import { type NetworkStatusSignal } from './network-status';
10
+ import { type ScreenOrientation } from './orientation';
6
11
  import { type ScrollPositionOptions, type ScrollPositionSignal } from './scroll-position';
7
12
  import { type WindowSizeOptions, type WindowSizeSignal } from './window-size';
8
13
  type SensorTypedOptions = {
@@ -61,6 +66,39 @@ type SensorTypedOptions = {
61
66
  };
62
67
  returnType: Signal<boolean>;
63
68
  };
69
+ geolocation: {
70
+ opt: GeolocationOptions;
71
+ returnType: GeolocationSignal;
72
+ };
73
+ clipboard: {
74
+ opt: {
75
+ debugName?: string;
76
+ };
77
+ returnType: ClipboardSignal;
78
+ };
79
+ orientation: {
80
+ opt: {
81
+ debugName?: string;
82
+ };
83
+ returnType: Signal<ScreenOrientation>;
84
+ };
85
+ batteryStatus: {
86
+ opt: {
87
+ debugName?: string;
88
+ };
89
+ returnType: Signal<BatteryStatus | null>;
90
+ };
91
+ idle: {
92
+ opt: IdleOptions;
93
+ returnType: IdleSignal;
94
+ };
95
+ focusWithin: {
96
+ opt: {
97
+ debugName?: string;
98
+ target?: ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
99
+ };
100
+ returnType: Signal<boolean>;
101
+ };
64
102
  };
65
103
  /**
66
104
  * Creates a sensor signal that the elements visiblity within the viewport
@@ -152,11 +190,62 @@ export declare function sensor(type: 'windowSize', options?: SensorTypedOptions[
152
190
  * @example const pageScroll = sensor('scrollPosition', { throttle: 150 });
153
191
  */
154
192
  export declare function sensor(type: 'scrollPosition', options?: SensorTypedOptions['scrollPosition']['opt']): ScrollPositionSignal;
193
+ /**
194
+ * Creates a sensor signal exposing the device's current geolocation position.
195
+ * @see {geolocation}
196
+ */
197
+ export declare function sensor(type: 'geolocation', options?: SensorTypedOptions['geolocation']['opt']): GeolocationSignal;
198
+ /**
199
+ * Creates a sensor signal mirroring the system clipboard contents.
200
+ * @see {clipboard}
201
+ */
202
+ export declare function sensor(type: 'clipboard', options?: SensorTypedOptions['clipboard']['opt']): ClipboardSignal;
203
+ /**
204
+ * Creates a sensor signal tracking the screen orientation.
205
+ * @see {orientation}
206
+ */
207
+ export declare function sensor(type: 'orientation', options?: SensorTypedOptions['orientation']['opt']): Signal<ScreenOrientation>;
208
+ /**
209
+ * Creates a sensor signal tracking the system battery status.
210
+ * @see {batteryStatus}
211
+ */
212
+ export declare function sensor(type: 'batteryStatus', options?: SensorTypedOptions['batteryStatus']['opt']): Signal<BatteryStatus | null>;
213
+ /**
214
+ * Creates a sensor signal that flips to `true` after a window of user inactivity.
215
+ * @see {idle}
216
+ */
217
+ export declare function sensor(type: 'idle', options?: SensorTypedOptions['idle']['opt']): IdleSignal;
218
+ /**
219
+ * Creates a sensor signal tracking whether focus is within a target subtree.
220
+ * @see {focusWithin}
221
+ */
222
+ export declare function sensor(type: 'focusWithin', options?: SensorTypedOptions['focusWithin']['opt']): Signal<boolean>;
155
223
  type SensorsOptions<TKey extends keyof SensorTypedOptions> = {
156
224
  [K in TKey]: SensorTypedOptions[K]['opt'];
157
225
  };
158
226
  type Sensors<TKey extends keyof SensorTypedOptions> = {
159
227
  [K in TKey]: SensorTypedOptions[K]['returnType'];
160
228
  };
229
+ /**
230
+ * Bulk sensor factory — creates several sensor signals at once and returns
231
+ * them keyed by sensor type. Convenient when a single consumer needs to react
232
+ * to multiple browser signals; for a single sensor prefer {@link sensor}
233
+ * directly.
234
+ *
235
+ * @typeParam TType The union of sensor keys being requested.
236
+ * @param track Array of sensor type keys to create.
237
+ * @param opt Optional per-sensor options keyed by sensor type.
238
+ * @returns A record `{ [key]: <SensorReturnType> }` for each requested key.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * const { windowSize, networkStatus } = sensors(
243
+ * ['windowSize', 'networkStatus'],
244
+ * { windowSize: { throttle: 200 } },
245
+ * );
246
+ *
247
+ * effect(() => console.log(windowSize(), networkStatus()));
248
+ * ```
249
+ */
161
250
  export declare function sensors<const TType extends keyof SensorTypedOptions>(track: TType[], opt?: SensorsOptions<TType>): Sensors<TType>;
162
251
  export {};
@@ -0,0 +1,42 @@
1
+ import { DestroyRef, ElementRef, Injector, type Signal } from '@angular/core';
2
+ /**
3
+ * Options for {@link signalFromEvent}. Extends the native
4
+ * `AddEventListenerOptions` so callers can opt into `capture`, `passive`, etc.
5
+ */
6
+ export type SignalFromEventOptions = AddEventListenerOptions & {
7
+ /** Optional debug name for the produced signal. */
8
+ debugName?: string;
9
+ /** Override the DestroyRef used to remove the listener on teardown. */
10
+ destroyRef?: DestroyRef;
11
+ /** Override the Injector used to inject dependencies. */
12
+ injector?: Injector;
13
+ };
14
+ type EventTargetLike = EventTarget | ElementRef<EventTarget>;
15
+ type ResolvableTarget = EventTargetLike | Signal<EventTargetLike | null>;
16
+ /**
17
+ * Creates a read-only signal that emits the latest event dispatched on a
18
+ * target. The target can be a static `EventTarget`, an `ElementRef`, or a
19
+ * `Signal` that resolves to one (or `null` to detach).
20
+ *
21
+ * SSR-safe: on the server the signal returns the provided `initial` value and
22
+ * no listener is registered.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const click = signalFromEvent(document, 'click', null);
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // With a projection — store just the coordinates.
32
+ * const point = signalFromEvent<MouseEvent, { x: number; y: number }>(
33
+ * document,
34
+ * 'mousemove',
35
+ * { x: 0, y: 0 },
36
+ * (e) => ({ x: e.clientX, y: e.clientY }),
37
+ * );
38
+ * ```
39
+ */
40
+ export declare function signalFromEvent<TEvent extends Event>(target: ResolvableTarget, eventName: string, initial: TEvent | null, opt?: SignalFromEventOptions): Signal<TEvent | null>;
41
+ export declare function signalFromEvent<TEvent extends Event, U>(target: ResolvableTarget, eventName: string, initial: U, project: (event: TEvent) => U, opt?: SignalFromEventOptions): Signal<U>;
42
+ export {};
package/lib/tabSync.d.ts CHANGED
@@ -12,45 +12,19 @@ export declare class MessageBus {
12
12
  static ɵprov: i0.ɵɵInjectableDeclaration<MessageBus>;
13
13
  }
14
14
  export declare function generateDeterministicID(): string;
15
- type SyncSignalOptions = {
15
+ type LegacySyncSignalOptions = {
16
16
  id?: string;
17
17
  };
18
+ export type SyncSignalOptions = {
19
+ id: string;
20
+ };
21
+ /**
22
+ * @example tabSync(signal('dark), {id: 'theme})
23
+ */
24
+ export declare function tabSync<T extends WritableSignal<any>>(sig: T, opt: SyncSignalOptions | string): T;
18
25
  /**
19
- * Synchronizes a WritableSignal across browser tabs using BroadcastChannel API.
20
- *
21
- * Creates a shared signal that automatically syncs its value between all tabs
22
- * of the same application. When the signal is updated in one tab, all other
23
- * tabs will receive the new value automatically.
24
- *
25
- * @template T - The type of the WritableSignal
26
- * @param sig - The WritableSignal to synchronize across tabs
27
- * @param opt - Optional configuration object
28
- * @param opt.id - Explicit channel ID for synchronization. If not provided,
29
- * a deterministic ID is generated based on the call site.
30
- * Use explicit IDs in production for reliability.
31
- *
32
- * @returns The same WritableSignal instance, now synchronized across tabs
33
- *
26
+ * @deprecated Use `tabSync` with `SyncSignalOptions` instead and pass the options as the second argument
34
27
  * @throws {Error} When deterministic ID generation fails and no explicit ID is provided
35
- *
36
- * @example
37
- * ```typescript
38
- * // Basic usage - auto-generates channel ID from call site
39
- * const theme = tabSync(signal('dark'));
40
- *
41
- * // With explicit ID (recommended for production)
42
- * const userPrefs = tabSync(signal({ lang: 'en' }), { id: 'user-preferences' });
43
- *
44
- * // Changes in one tab will sync to all other tabs
45
- * theme.set('light'); // All tabs will update to 'light'
46
- * ```
47
- *
48
- * @remarks
49
- * - Only works in browser environments (returns original signal on server)
50
- * - Uses a single BroadcastChannel for all synchronized signals
51
- * - Automatically cleans up listeners when the injection context is destroyed
52
- * - Initial signal value after sync setup is not broadcasted to prevent loops
53
- *
54
28
  */
55
- export declare function tabSync<T extends WritableSignal<any>>(sig: T, opt?: SyncSignalOptions): T;
29
+ export declare function tabSync<T extends WritableSignal<any>>(sig: T, opt?: LegacySyncSignalOptions): T;
56
30
  export {};
@@ -19,6 +19,18 @@ export type CreateThrottledOptions<T> = CreateSignalOptions<T> & {
19
19
  * If it is not provided or injected, the timer will not be cleared automatically...which is usually fine :)
20
20
  */
21
21
  destroyRef?: DestroyRef;
22
+ /**
23
+ * If `true`, the throttled signal emits the first value immediately when a
24
+ * burst starts, then enforces the cooldown window before the next emission.
25
+ * @default false
26
+ */
27
+ leading?: boolean;
28
+ /**
29
+ * If `true`, the throttled signal emits the latest pending value at the end
30
+ * of each cooldown window (only when at least one write occurred during it).
31
+ * @default true
32
+ */
33
+ trailing?: boolean;
22
34
  };
23
35
  /**
24
36
  * A specialized `WritableSignal` whose publicly readable value updates are throttled.