@mmstack/primitives 20.5.6 → 20.5.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.
@@ -604,7 +604,30 @@ const mapArray = indexArray;
604
604
  * @param mapFn The mapping function. Receives the item and its index as a Signal.
605
605
  * @param options Optional configuration:
606
606
  * - `onDestroy`: A callback invoked when a mapped item is removed from the array.
607
+ * - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
608
+ * when item references change but conceptual identity is preserved.
607
609
  * @returns A `Signal<U[]>` containing the mapped array.
610
+ *
611
+ * @example
612
+ * ```ts
613
+ * const users = signal([
614
+ * { id: 1, name: 'Alice' },
615
+ * { id: 2, name: 'Bob' },
616
+ * ]);
617
+ *
618
+ * const rows = keyArray(
619
+ * users,
620
+ * (user, index) => ({
621
+ * label: computed(() => `#${index()} ${user.name}`),
622
+ * id: user.id,
623
+ * }),
624
+ * { key: (u) => u.id },
625
+ * );
626
+ *
627
+ * // Reordering users() rebuilds index signals only — `rows` entries
628
+ * // are matched by id and reused, not re-created.
629
+ * users.set([users()[1], users()[0]]);
630
+ * ```
608
631
  */
609
632
  function keyArray(source, mapFn, options = {}) {
610
633
  const sourceSignal = isSignal(source) ? source : computed(source);
@@ -761,15 +784,69 @@ function mapObject(source, mapFn, options = {}) {
761
784
  }).asReadonly();
762
785
  }
763
786
 
764
- /** Project with optional equality. Pure & sync. */
787
+ /**
788
+ * Synchronous projection of a signal value with optional `CreateSignalOptions`
789
+ * (custom `equal`, `debugName`, etc.). Equivalent to `map` plus the ability to
790
+ * pass signal options through to the underlying `computed()`.
791
+ *
792
+ * @example
793
+ * ```ts
794
+ * const user = piped({ id: 1, name: 'Alice' });
795
+ * const name = user.pipe(select((u) => u.name));
796
+ * name(); // 'Alice'
797
+ * ```
798
+ */
765
799
  const select = (projector, opt) => (src) => computed(() => projector(src()), opt);
766
- /** Combine with another signal using a projector. */
800
+ /**
801
+ * Combine the piped signal with another `Signal` using a projector. The result
802
+ * recomputes whenever either source changes.
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * const price = piped(10);
807
+ * const quantity = signal(3);
808
+ * const total = price.pipe(combineWith(quantity, (p, q) => p * q));
809
+ * total(); // 30
810
+ * ```
811
+ */
767
812
  const combineWith = (other, project, opt) => (src) => computed(() => project(src(), other()), opt);
768
- /** Only re-emit when equal(prev, next) is false. */
813
+ /**
814
+ * Suppress emissions while consecutive values are considered equal. The
815
+ * comparator defaults to `Object.is`; pass a custom one for structural or
816
+ * key-based equality (e.g. compare by `id` only).
817
+ *
818
+ * @example
819
+ * ```ts
820
+ * const user = piped({ id: 1, lastSeen: Date.now() });
821
+ * const byId = user.pipe(distinct((a, b) => a.id === b.id));
822
+ * // byId only re-emits when `id` changes, not on every `lastSeen` update
823
+ * ```
824
+ */
769
825
  const distinct = (equal = Object.is) => (src) => computed(() => src(), { equal });
770
- /** map to new value */
826
+ /**
827
+ * Pure synchronous transform from input to output. Equivalent to a `computed()`
828
+ * that reads the source and returns `fn(value)`.
829
+ *
830
+ * @example
831
+ * ```ts
832
+ * const count = piped(2);
833
+ * const doubled = count.pipe(map((n) => n * 2));
834
+ * doubled(); // 4
835
+ * ```
836
+ */
771
837
  const map = (fn) => (src) => computed(() => fn(src()));
772
- /** filter values, keeping the last value if it was ever available, if first value is filtered will return undefined */
838
+ /**
839
+ * Keep only values that pass the predicate. The result holds the last passing
840
+ * value across emissions; before any value passes, the result is `undefined` —
841
+ * see {@link filterWith} when you need a non-`undefined` seed.
842
+ *
843
+ * @example
844
+ * ```ts
845
+ * const event = piped<MouseEvent | null>(null);
846
+ * const clicks = event.pipe(filter((e): e is MouseEvent => e?.type === 'click'));
847
+ * clicks(); // undefined until a click happens, then the last MouseEvent
848
+ * ```
849
+ */
773
850
  const filter = (predicate) => (src) => linkedSignal({
774
851
  source: src,
775
852
  computation: (next, prev) => {
@@ -778,7 +855,19 @@ const filter = (predicate) => (src) => linkedSignal({
778
855
  return prev?.source;
779
856
  },
780
857
  });
781
- /** tap into the value */
858
+ /**
859
+ * Run a side effect on every emission without altering the signal value. Wraps
860
+ * Angular's `effect()`, so it must run in an injection context or receive an
861
+ * explicit `injector`. Use for logging / analytics — not for setting other
862
+ * signals (that's what regular `effect()` is for).
863
+ *
864
+ * @example
865
+ * ```ts
866
+ * const count = piped(0);
867
+ * count.pipe(tap((n) => console.log('count:', n)));
868
+ * count.set(1); // logs 'count: 1'
869
+ * ```
870
+ */
782
871
  const tap = (fn, injector) => (src) => {
783
872
  effect(() => fn(src()), {
784
873
  injector,
@@ -786,24 +875,66 @@ const tap = (fn, injector) => (src) => {
786
875
  return src;
787
876
  };
788
877
  /**
789
- * Like {@link filter}, but emits `initial` until a value passes the predicate
790
- * for the first time. Avoids the `T | undefined` return type.
878
+ * Like {@link filter}, but emits `initial` until a value first passes the
879
+ * predicate. Eliminates the `T | undefined` return type at the cost of an
880
+ * explicit seed value.
881
+ *
882
+ * @example
883
+ * ```ts
884
+ * const event = piped<MouseEvent | null>(null);
885
+ * const lastClick = event.pipe(filterWith((e) => e?.type === 'click', null));
886
+ * lastClick(); // null until the first click, then the most recent click event
887
+ * ```
791
888
  */
792
889
  const filterWith = (predicate, initial) => (src) => linkedSignal({
793
890
  source: src,
794
891
  computation: (next, prev) => predicate(next) ? next : (prev?.value ?? initial),
795
892
  });
796
- /** Emits `initial` first, then mirrors source. */
893
+ /**
894
+ * Emit `initial` on the first read, then mirror the source on every subsequent
895
+ * read. Useful for giving a pipeline a sensible seed value before the source
896
+ * is ready (e.g. loading state).
897
+ *
898
+ * @example
899
+ * ```ts
900
+ * const data = piped<User | null>(null);
901
+ * const view = data.pipe(startWith<User | null, 'loading'>('loading'));
902
+ * view(); // 'loading' on first read, then User | null afterward
903
+ * ```
904
+ */
797
905
  const startWith = (initial) => (src) => linkedSignal({
798
906
  source: src,
799
907
  computation: (next, prev) => (prev === undefined ? initial : next),
800
908
  });
801
- /** Emits `[prev, curr]` pairs. The first emission has prev = undefined. */
909
+ /**
910
+ * Emit `[prev, curr]` tuples so consumers can react to transitions instead of
911
+ * raw values. On the first emission `prev` is `undefined`.
912
+ *
913
+ * @example
914
+ * ```ts
915
+ * const count = piped(0);
916
+ * const delta = count.pipe(pairwise(), map(([prev, curr]) => curr - (prev ?? 0)));
917
+ * count.set(5);
918
+ * delta(); // 5
919
+ * ```
920
+ */
802
921
  const pairwise = () => (src) => linkedSignal({
803
922
  source: src,
804
923
  computation: (next, prev) => [prev?.source, next],
805
924
  });
806
- /** Reduce-like accumulator across emissions. */
925
+ /**
926
+ * Reduce-like accumulator that folds each emission into a running result.
927
+ * Behaves like `Array.prototype.reduce` but applied over time, with the
928
+ * accumulator persisted across emissions.
929
+ *
930
+ * @example
931
+ * ```ts
932
+ * const delta = piped(0);
933
+ * const total = delta.pipe(scan((acc, n) => acc + n, 0));
934
+ * delta.set(5); // total() === 5
935
+ * delta.set(3); // total() === 8
936
+ * ```
937
+ */
807
938
  const scan = (reducer, seed) => (src) => linkedSignal({
808
939
  source: src,
809
940
  computation: (next, prev) => reducer(prev?.value ?? seed, next),
@@ -972,6 +1103,15 @@ const EVENTS = [
972
1103
  * Battery Status API. Returns `null` until the underlying `getBattery()`
973
1104
  * promise resolves, or permanently when the API is unsupported (Firefox /
974
1105
  * Safari at the time of writing). SSR-safe.
1106
+ *
1107
+ * @example
1108
+ * ```ts
1109
+ * const battery = batteryStatus();
1110
+ * effect(() => {
1111
+ * const b = battery();
1112
+ * if (b) console.log(`${Math.round(b.level * 100)}% • charging: ${b.charging}`);
1113
+ * });
1114
+ * ```
975
1115
  */
976
1116
  function batteryStatus(debugName = 'batteryStatus') {
977
1117
  if (isPlatformServer(inject(PLATFORM_ID)) ||
@@ -1254,6 +1394,15 @@ function unwrap$1(target) {
1254
1394
  * Defaults `target` to the current `ElementRef` so it can be used inline in a
1255
1395
  * component's `class` field. SSR-safe — returns a constant `false` signal on
1256
1396
  * the server.
1397
+ *
1398
+ * @example
1399
+ * ```ts
1400
+ * @Component({ ... })
1401
+ * class MenuComponent {
1402
+ * // Defaults to the host element — flips true when focus is inside.
1403
+ * readonly hasFocus = focusWithin();
1404
+ * }
1405
+ * ```
1257
1406
  */
1258
1407
  function focusWithin(target = inject(ElementRef)) {
1259
1408
  if (isPlatformServer(inject(PLATFORM_ID))) {
@@ -1356,6 +1505,14 @@ const serverDate$1 = new Date();
1356
1505
  * activity) resets the timer and flips the signal back to `false`.
1357
1506
  *
1358
1507
  * SSR-safe — always `false` with a frozen `since` date on the server.
1508
+ *
1509
+ * @example
1510
+ * ```ts
1511
+ * const isAway = idle({ ms: 30_000 });
1512
+ * effect(() => {
1513
+ * if (isAway()) console.log('idle since', isAway.since());
1514
+ * });
1515
+ * ```
1359
1516
  */
1360
1517
  function idle(opt) {
1361
1518
  if (isPlatformServer(inject(PLATFORM_ID))) {
@@ -1719,6 +1876,14 @@ const serverDate = new Date();
1719
1876
  *
1720
1877
  * @param debugName Optional debug name for the signal.
1721
1878
  * @returns A `NetworkStatusSignal` instance.
1879
+ *
1880
+ * @example
1881
+ * ```ts
1882
+ * const online = networkStatus();
1883
+ * effect(() => {
1884
+ * if (!online()) console.log('offline since', online.since());
1885
+ * });
1886
+ * ```
1722
1887
  */
1723
1888
  function networkStatus(debugName = 'networkStatus') {
1724
1889
  if (isPlatformServer(inject(PLATFORM_ID))) {
@@ -1760,6 +1925,15 @@ const SSR_FALLBACK = {
1760
1925
  *
1761
1926
  * SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
1762
1927
  * and in environments without `screen.orientation` support.
1928
+ *
1929
+ * @example
1930
+ * ```ts
1931
+ * const screenOrientation = orientation();
1932
+ * effect(() => {
1933
+ * const { type, angle } = screenOrientation();
1934
+ * console.log(`${type} at ${angle}°`);
1935
+ * });
1936
+ * ```
1763
1937
  */
1764
1938
  function orientation(debugName = 'orientation') {
1765
1939
  if (isPlatformServer(inject(PLATFORM_ID)) ||
@@ -2039,6 +2213,27 @@ function sensor(type, options) {
2039
2213
  throw new Error(`Unknown sensor type: ${type}`);
2040
2214
  }
2041
2215
  }
2216
+ /**
2217
+ * Bulk sensor factory — creates several sensor signals at once and returns
2218
+ * them keyed by sensor type. Convenient when a single consumer needs to react
2219
+ * to multiple browser signals; for a single sensor prefer {@link sensor}
2220
+ * directly.
2221
+ *
2222
+ * @typeParam TType The union of sensor keys being requested.
2223
+ * @param track Array of sensor type keys to create.
2224
+ * @param opt Optional per-sensor options keyed by sensor type.
2225
+ * @returns A record `{ [key]: <SensorReturnType> }` for each requested key.
2226
+ *
2227
+ * @example
2228
+ * ```ts
2229
+ * const { windowSize, networkStatus } = sensors(
2230
+ * ['windowSize', 'networkStatus'],
2231
+ * { windowSize: { throttle: 200 } },
2232
+ * );
2233
+ *
2234
+ * effect(() => console.log(windowSize(), networkStatus()));
2235
+ * ```
2236
+ */
2042
2237
  function sensors(track, opt) {
2043
2238
  return track.reduce((result, key) => {
2044
2239
  result[key] = sensor(key, opt?.[key]);