@mmstack/primitives 19.3.6 → 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,25 +1,133 @@
1
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 */
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
+ */
14
80
  export declare const tap: <T>(fn: (v: T) => void, injector?: Injector) => Operator<T, T>;
15
81
  /**
16
- * Like {@link filter}, but emits `initial` until a value passes the predicate
17
- * for the first time. Avoids the `T | undefined` return type.
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
+ * ```
18
92
  */
19
93
  export declare const filterWith: <T>(predicate: (v: T) => boolean, initial: T) => Operator<T, T>;
20
- /** Emits `initial` first, then mirrors source. */
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
+ */
21
106
  export declare const startWith: <T, U>(initial: U) => Operator<T, T | U>;
22
- /** Emits `[prev, curr]` pairs. The first emission has prev = undefined. */
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
+ */
23
119
  export declare const pairwise: <T>() => Operator<T, [T | undefined, T]>;
24
- /** Reduce-like accumulator across emissions. */
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
+ */
25
133
  export declare const scan: <T, R>(reducer: (acc: R, curr: T) => R, seed: R) => Operator<T, R>;
@@ -10,5 +10,14 @@ export type BatteryStatus = {
10
10
  * Battery Status API. Returns `null` until the underlying `getBattery()`
11
11
  * promise resolves, or permanently when the API is unsupported (Firefox /
12
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
+ * ```
13
22
  */
14
23
  export declare function batteryStatus(debugName?: string): Signal<BatteryStatus | null>;
@@ -7,6 +7,15 @@ type FocusWithinTarget = ElementRef<Element> | Element | Signal<ElementRef<Eleme
7
7
  * Defaults `target` to the current `ElementRef` so it can be used inline in a
8
8
  * component's `class` field. SSR-safe — returns a constant `false` signal on
9
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
+ * ```
10
19
  */
11
20
  export declare function focusWithin(target?: FocusWithinTarget): Signal<boolean>;
12
21
  export {};
@@ -23,5 +23,13 @@ export type IdleSignal = Signal<boolean> & {
23
23
  * activity) resets the timer and flips the signal back to `false`.
24
24
  *
25
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
+ * ```
26
34
  */
27
35
  export declare function idle(opt?: IdleOptions): IdleSignal;
@@ -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;
@@ -10,5 +10,14 @@ export type ScreenOrientation = {
10
10
  *
11
11
  * SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
12
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
+ * ```
13
22
  */
14
23
  export declare function orientation(debugName?: string): Signal<ScreenOrientation>;
@@ -226,5 +226,26 @@ type SensorsOptions<TKey extends keyof SensorTypedOptions> = {
226
226
  type Sensors<TKey extends keyof SensorTypedOptions> = {
227
227
  [K in TKey]: SensorTypedOptions[K]['returnType'];
228
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
+ */
229
250
  export declare function sensors<const TType extends keyof SensorTypedOptions>(track: TType[], opt?: SensorsOptions<TType>): Sensors<TType>;
230
251
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/primitives",
3
- "version": "19.3.6",
3
+ "version": "19.3.7",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",