@mmstack/primitives 21.0.19 → 21.0.21

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/README.md CHANGED
@@ -29,6 +29,7 @@ This library provides the following primitives:
29
29
  - `toWritable` - Converts a read-only signal to writable using custom write logic.
30
30
  - `derived` - Creates a signal with two-way binding to a source signal.
31
31
  - `chunked` - Creates a signal that time-slices an array into chunked values & emits thats array based on the provided options.
32
+ - `pooled` / `pooledArray` / `pooledMap` / `pooledSet` - Double-buffered object pools for `computed` signals; recycle the output container to remove allocation pressure in high-frequency recomputation.
32
33
  - `tabSync` - Low level primitive to "share" the value of a WritableSignal accross tabs via the BroadcastChannel api.
33
34
  - `sensor` - A facade function to create various reactive sensor signals (e.g., mouse position, network status, page visibility, dark mode preference)." (This was the suggestion from before; it just reads a little smoother and more accurately reflects what the facade creates directly).
34
35
  - `mediaQuery` - A generic primitive that tracks a CSS media query (forms the basis for `prefersDarkMode` and `prefersReducedMotion`).
@@ -59,6 +60,7 @@ import { FormsModule } from '@angular/forms';
59
60
  })
60
61
  export class SearchComponent {
61
62
  searchTerm = debounced('', { ms: 300 }); // Debounce for 300ms
63
+ example2 = debounce(signal(''), { ms: 300 }); // pattern for adding debounce to an existing signal
62
64
 
63
65
  constructor() {
64
66
  effect(() => {
@@ -93,7 +95,6 @@ import { JsonPipe } from '@angular/common';
93
95
 
94
96
  @Component({
95
97
  selector: 'app-throttle-demo',
96
- standalone: true,
97
98
  imports: [JsonPipe],
98
99
  template: `
99
100
  <div (mousemove)="onMouseMove($event)" style="width: 300px; height: 200px; border: 1px solid black; padding: 10px; user-select: none;">Move mouse here to see updates...</div>
@@ -195,7 +196,6 @@ import { stored } from '@mmstack/primitives';
195
196
 
196
197
  @Component({
197
198
  selector: 'app-theme-selector',
198
- standalone: true,
199
199
  // imports: [FormsModule], // Import if using ngModel
200
200
  template: `
201
201
  Theme:
@@ -273,7 +273,6 @@ import { JsonPipe } from '@angular/common';
273
273
 
274
274
  @Component({
275
275
  selector: 'app-store-demo',
276
- standalone: true,
277
276
  imports: [FormsModule, JsonPipe],
278
277
  template: `
279
278
  <h3>User Profile</h3>
@@ -657,6 +656,75 @@ export class HeavyListComponent {
657
656
  }
658
657
  ```
659
658
 
659
+ ### pooled / pooledArray / pooledMap / pooledSet
660
+
661
+ A double-buffered object pool for `computed` signal outputs. After a brief warm-up the pool reaches steady state with **zero allocations per recomputation** — two buffers are swapped on every read, with `reset` invoked before each `computation`. Each read returns a different identity from the previous read, so default `Object.is` equality still flags changes correctly. Most users will reach for the preset helpers (`pooledArray`, `pooledMap`, `pooledSet`); drop down to `pooled` only when you need a custom buffer type.
662
+
663
+ > **Retention contract:** the value returned from a pooled signal is only valid until the next read of that signal. The container is reused on the second-next read and will be `reset` first, mutating any reference you still hold. Do not store the result in component state, async closures, or anywhere outside the same reactive tick. Treat it as scratch output consumed synchronously.
664
+
665
+ Use these when a computed is recomputed at high frequency and produces a large allocation (filter/map outputs over big arrays, lookup indices, RAF-driven computeds). For typical UI computeds over small data, just use `computed` — the docs cost and footgun aren't worth saving an allocation that doesn't show up in a profile.
666
+
667
+ ```typescript
668
+ import { Component, signal } from '@angular/core';
669
+ import { pooledArray, pooledMap, pooledSet } from '@mmstack/primitives';
670
+
671
+ @Component({
672
+ selector: 'app-pooled-demo',
673
+ template: `<p>Active: {{ activeIds().length }} / {{ items().length }}</p>`,
674
+ })
675
+ export class PooledDemoComponent {
676
+ readonly items = signal(Array.from({ length: 10_000 }, (_, i) => ({ id: i, active: i % 2 === 0 })));
677
+
678
+ // Recycles a single number[] across recomputations.
679
+ readonly activeIds = pooledArray<number[]>((buf) => {
680
+ for (const item of this.items()) {
681
+ if (item.active) buf.push(item.id);
682
+ }
683
+ return buf;
684
+ });
685
+
686
+ // Recycles a Map for fast id → item lookups.
687
+ readonly byId = pooledMap<Map<number, { id: number; active: boolean }>>((buf) => {
688
+ for (const item of this.items()) buf.set(item.id, item);
689
+ return buf;
690
+ });
691
+
692
+ // Recycles a Set of distinct values.
693
+ readonly distinctFlags = pooledSet<Set<boolean>>((buf) => {
694
+ for (const item of this.items()) buf.add(item.active);
695
+ return buf;
696
+ });
697
+ }
698
+ ```
699
+
700
+ Need a custom buffer type (typed array, your own struct)? Use `pooled` directly:
701
+
702
+ ```typescript
703
+ import { signal } from '@angular/core';
704
+ import { pooled } from '@mmstack/primitives';
705
+
706
+ const source = signal<{ active: boolean }[]>([]);
707
+
708
+ // Pre-allocate both slots at construction (eager) — useful when create() is expensive.
709
+ const counters = pooled<{ total: number; active: number }>({
710
+ create: () => ({ total: 0, active: 0 }),
711
+ reset: (c) => {
712
+ c.total = 0;
713
+ c.active = 0;
714
+ },
715
+ computation: (c) => {
716
+ for (const item of source()) {
717
+ c.total++;
718
+ if (item.active) c.active++;
719
+ }
720
+ return c;
721
+ },
722
+ eager: true,
723
+ });
724
+ ```
725
+
726
+ Complementary to `linkedSignal` (which carries previous *state* forward, not the *container*) and `chunked` (which time-slices large outputs across frames).
727
+
660
728
  ### tabSync
661
729
 
662
730
  A low-level primitive that synchronizes a WritableSignal across multiple browser tabs or windows of the same application using the BroadcastChannel API. Used by the cache in @mmstack/resource & the stored signal.
@@ -680,7 +748,7 @@ import { tabSync } from '@mmstack/primitives';
680
748
  template: `
681
749
  <p>Open this page in two tabs!</p>
682
750
 
683
- <button (click)="counter.update(n => n + 1)">Count: {{ counter() }}</button>
751
+ <button (click)="counter.update((n) => n + 1)">Count: {{ counter() }}</button>
684
752
 
685
753
  <select [ngModel]="theme()" (ngModelChange)="theme.set($event)">
686
754
  <option value="light">Light</option>
@@ -713,7 +781,6 @@ import { Component, signal, effect } from '@angular/core';
713
781
 
714
782
  @Component({
715
783
  selector: 'app-history-demo',
716
- standalone: true,
717
784
  imports: [FormsModule, JsonPipe],
718
785
  template: `
719
786
  <h4>Simple Text Editor</h4>
@@ -780,7 +847,6 @@ import { JsonPipe } from '@angular/common';
780
847
 
781
848
  @Component({
782
849
  selector: 'app-mouse-tracker',
783
- standalone: true,
784
850
  imports: [JsonPipe],
785
851
  template: `
786
852
  <div (mousemove)="onMouseMove($event)" style="width: 300px; height: 200px; border: 1px solid black; padding: 10px; user-select: none;">Move mouse here...</div>
@@ -818,7 +884,6 @@ import { DatePipe } from '@angular/common';
818
884
 
819
885
  @Component({
820
886
  selector: 'app-network-info',
821
- standalone: true,
822
887
  imports: [DatePipe],
823
888
  template: `
824
889
  @if (netStatus()) {
@@ -849,7 +914,6 @@ import { sensor } from '@mmstack/primitives'; // Or import { pageVisibility }
849
914
 
850
915
  @Component({
851
916
  selector: 'app-visibility-logger',
852
- standalone: true,
853
917
  template: `<p>Page is currently: {{ visibility() }}</p>`,
854
918
  })
855
919
  export class VisibilityLoggerComponent {
@@ -876,7 +940,6 @@ import { sensor } from '@mmstack/primitives'; // Or import { windowSize }
876
940
 
877
941
  @Component({
878
942
  selector: 'app-responsive-display',
879
- standalone: true,
880
943
  template: `
881
944
  <p>Current Window Size: {{ winSize().width }}px x {{ winSize().height }}px</p>
882
945
  <p>Unthrottled: W: {{ winSize.unthrottled().width }} H: {{ winSize.unthrottled().height }}</p>
@@ -910,7 +973,6 @@ import { JsonPipe } from '@angular/common';
910
973
 
911
974
  @Component({
912
975
  selector: 'app-scroll-indicator',
913
- standalone: true,
914
976
  imports: [JsonPipe],
915
977
  template: `
916
978
  <div style="height: 100px; border-bottom: 2px solid red; position: fixed; top: 0; left: 0; width: 100%; background: white; z-index: 10;">
@@ -944,7 +1006,6 @@ import { mediaQuery, prefersDarkMode, prefersReducedMotion } from '@mmstack/prim
944
1006
 
945
1007
  @Component({
946
1008
  selector: 'app-layout-checker',
947
- standalone: true,
948
1009
  template: `
949
1010
  @if (isLargeScreen()) {
950
1011
  <p>Using large screen layout.</p>
@@ -1006,7 +1067,6 @@ import { elementVisibility } from '@mmstack/primitives';
1006
1067
 
1007
1068
  @Component({
1008
1069
  selector: 'app-lazy-load-item',
1009
- standalone: true,
1010
1070
  template: `
1011
1071
  <div #itemToObserve style="height: 300px; margin-top: 100vh; border: 2px solid green;">
1012
1072
  @if (intersectionEntry.visible()) {
@@ -138,6 +138,9 @@ function nestedEffect(effectFn, options) {
138
138
  manualCleanup: options?.manualCleanup ?? !!parent,
139
139
  });
140
140
  });
141
+ let unregisterCleanup;
142
+ if (!parent && !options?.manualCleanup)
143
+ unregisterCleanup = injector.get(DestroyRef).onDestroy(() => ref.destroy());
141
144
  const ref = {
142
145
  destroy: () => {
143
146
  if (isDestroyed)
@@ -145,10 +148,10 @@ function nestedEffect(effectFn, options) {
145
148
  isDestroyed = true;
146
149
  parent?.children.delete(ref);
147
150
  srcRef.destroy();
151
+ unregisterCleanup?.();
148
152
  },
149
153
  };
150
154
  parent?.children.add(ref);
151
- injector.get(DestroyRef).onDestroy(() => ref.destroy());
152
155
  return ref;
153
156
  }
154
157
 
@@ -194,7 +197,7 @@ function chunked(source, options) {
194
197
  return () => clearTimeout(num);
195
198
  };
196
199
  }
197
- const internal = linkedSignal({ ...(ngDevMode ? { debugName: "internal" } : {}), source,
200
+ const internal = linkedSignal({ ...(ngDevMode ? { debugName: "internal" } : /* istanbul ignore next */ {}), source,
198
201
  computation: (items) => items.slice(0, chunkSize),
199
202
  equal });
200
203
  nestedEffect((cleanup) => {
@@ -308,7 +311,7 @@ function debounced(initial, opt) {
308
311
  */
309
312
  function debounce(source, opt) {
310
313
  const ms = opt?.ms ?? 0;
311
- const trigger = signal(false, ...(ngDevMode ? [{ debugName: "trigger" }] : []));
314
+ const trigger = signal(false, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
312
315
  let timeout;
313
316
  try {
314
317
  const destroyRef = opt?.destroyRef ?? inject(DestroyRef, { optional: true });
@@ -537,7 +540,7 @@ function createItemSignal(source, index, setter, opt) {
537
540
  }
538
541
  function indexArray(source, map, opt = {}) {
539
542
  const data = isSignal(source) ? source : computed(source);
540
- const len = computed(() => data().length, ...(ngDevMode ? [{ debugName: "len" }] : []));
543
+ const len = computed(() => data().length, ...(ngDevMode ? [{ debugName: "len" }] : /* istanbul ignore next */ []));
541
544
  const setter = createSetter(data);
542
545
  const writableData = isWritableSignal(data)
543
546
  ? data
@@ -640,7 +643,7 @@ function keyArray(source, mapFn, options = {}) {
640
643
  for (j = 0; j < newLen; j++) {
641
644
  item = newItems[j];
642
645
  items[j] = item;
643
- const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : []));
646
+ const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
644
647
  newIndexes[j] = indexSignal;
645
648
  newMapped[j] = mapFn(item, indexSignal);
646
649
  }
@@ -663,14 +666,14 @@ function keyArray(source, mapFn, options = {}) {
663
666
  for (j = newEnd; j >= start; j--) {
664
667
  item = newItems[j];
665
668
  key = getKey(item);
666
- i = newIndices.get(key);
669
+ i = newIndices.get(key) ?? -1;
667
670
  newIndicesNext[j] = i === undefined ? -1 : i;
668
671
  newIndices.set(key, j);
669
672
  }
670
673
  for (i = start; i <= end; i++) {
671
674
  item = items[i];
672
675
  key = getKey(item);
673
- j = newIndices.get(key);
676
+ j = newIndices.get(key) ?? -1;
674
677
  if (j !== undefined && j !== -1) {
675
678
  temp[j] = mapped[i];
676
679
  tempIndexes[j] = indexes[i];
@@ -690,7 +693,7 @@ function keyArray(source, mapFn, options = {}) {
690
693
  newIndexes[j].set(j);
691
694
  }
692
695
  else {
693
- const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : []));
696
+ const indexSignal = signal(j, ...(ngDevMode ? [{ debugName: "indexSignal" }] : /* istanbul ignore next */ []));
694
697
  newIndexes[j] = indexSignal;
695
698
  newMapped[j] = mapFn(newItems[j], indexSignal);
696
699
  }
@@ -837,6 +840,102 @@ function piped(initial, opt) {
837
840
  return pipeable(signal(initial, opt));
838
841
  }
839
842
 
843
+ /**
844
+ * A `Signal<U>` backed by a two-slot object pool: `create` is called at most
845
+ * twice over the pool's lifetime, and the two `T` instances are swapped on
846
+ * every recomputation with `reset` invoked on the dirty one before
847
+ * `computation` writes into it. Consecutive reads return different identities,
848
+ * so the default `Object.is` equality still flags changes.
849
+ *
850
+ * **Retention contract:** the returned value is only valid until the next
851
+ * recomputation of this signal. The container is recycled and `reset`,
852
+ * mutating any reference you still hold — do not store the result, pass it to
853
+ * async code, or hand it to consumers that outlive the current reactive tick.
854
+ *
855
+ * For collection buffers prefer the presets: {@link pooledArray},
856
+ * {@link pooledMap}, {@link pooledSet}.
857
+ *
858
+ * @see [Angular `linkedSignal`](https://angular.dev/api/core/linkedSignal) — carries previous *state* forward; complementary, not a substitute.
859
+ *
860
+ * @example
861
+ * ```ts
862
+ * const source = signal<{ active: boolean }[]>([]);
863
+ *
864
+ * const counters = pooled<{ total: number; active: number }>({
865
+ * create: () => ({ total: 0, active: 0 }),
866
+ * reset: (c) => { c.total = 0; c.active = 0; },
867
+ * computation: (c) => {
868
+ * for (const item of source()) { c.total++; if (item.active) c.active++; }
869
+ * return c;
870
+ * },
871
+ * });
872
+ * ```
873
+ */
874
+ function pooled({ create, reset, computation, ...opt }) {
875
+ let other = opt.eager ? create() : undefined;
876
+ let current = opt.eager ? create() : undefined;
877
+ let otherFresh = opt.eager;
878
+ let currentFresh = opt.eager;
879
+ return computed(() => {
880
+ let next;
881
+ let nextFresh;
882
+ if (other !== undefined) {
883
+ next = other;
884
+ nextFresh = !!otherFresh;
885
+ }
886
+ else {
887
+ next = untracked(() => create());
888
+ nextFresh = true;
889
+ }
890
+ if (current !== undefined) {
891
+ other = current;
892
+ otherFresh = currentFresh;
893
+ }
894
+ current = next;
895
+ // the buffer is about to be mutated by `computation`, so it's no longer fresh
896
+ currentFresh = false;
897
+ const clean = nextFresh ? next : (untracked(() => reset(next)) ?? next);
898
+ return computation(clean);
899
+ }, opt);
900
+ }
901
+
902
+ function toPooledOptions(optOrComputation, create, reset, signalOpt) {
903
+ const opt = typeof optOrComputation === 'object' ? optOrComputation : signalOpt;
904
+ const computation = typeof optOrComputation === 'function'
905
+ ? optOrComputation
906
+ : optOrComputation.computation;
907
+ return {
908
+ create,
909
+ reset,
910
+ computation,
911
+ ...opt,
912
+ };
913
+ }
914
+ function createEmptyArray() {
915
+ return [];
916
+ }
917
+ function resetArray(arr) {
918
+ arr.length = 0;
919
+ }
920
+ function pooledArray(optOrComputation, signalOpt) {
921
+ return pooled(toPooledOptions(optOrComputation, createEmptyArray, resetArray, signalOpt));
922
+ }
923
+ function createEmptySet() {
924
+ return new Set();
925
+ }
926
+ function resetClearable(clearable) {
927
+ clearable.clear();
928
+ }
929
+ function pooledSet(optOrComputation, signalOpt) {
930
+ return pooled(toPooledOptions(optOrComputation, createEmptySet, resetClearable, signalOpt));
931
+ }
932
+ function createEmptyMap() {
933
+ return new Map();
934
+ }
935
+ function pooledMap(optOrComputation, signalOpt) {
936
+ return pooled(toPooledOptions(optOrComputation, createEmptyMap, resetClearable, signalOpt));
937
+ }
938
+
840
939
  function observerSupported$1() {
841
940
  return typeof ResizeObserver !== 'undefined';
842
941
  }
@@ -996,7 +1095,7 @@ function elementVisibility(target = inject(ElementRef), opt) {
996
1095
  const base = computed(() => undefined, {
997
1096
  debugName: opt?.debugName,
998
1097
  });
999
- base.visible = computed(() => false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
1098
+ base.visible = computed(() => false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
1000
1099
  return base;
1001
1100
  }
1002
1101
  const state = signal(undefined, {
@@ -1033,7 +1132,7 @@ function elementVisibility(target = inject(ElementRef), opt) {
1033
1132
  if (!s)
1034
1133
  return false;
1035
1134
  return s.isIntersecting;
1036
- }, ...(ngDevMode ? [{ debugName: "visible" }] : []));
1135
+ }, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
1037
1136
  return base;
1038
1137
  }
1039
1138
 
@@ -1198,7 +1297,7 @@ function throttled(initial, opt) {
1198
1297
  */
1199
1298
  function throttle(source, opt) {
1200
1299
  const ms = opt?.ms ?? 0;
1201
- const trigger = signal(false, ...(ngDevMode ? [{ debugName: "trigger" }] : []));
1300
+ const trigger = signal(false, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
1202
1301
  let timeout;
1203
1302
  try {
1204
1303
  const destroyRef = opt?.destroyRef ?? inject(DestroyRef, { optional: true });
@@ -1344,11 +1443,11 @@ function networkStatus(debugName = 'networkStatus') {
1344
1443
  const sig = computed(() => true, {
1345
1444
  debugName,
1346
1445
  });
1347
- sig.since = computed(() => serverDate, ...(ngDevMode ? [{ debugName: "since" }] : []));
1446
+ sig.since = computed(() => serverDate, ...(ngDevMode ? [{ debugName: "since" }] : /* istanbul ignore next */ []));
1348
1447
  return sig;
1349
1448
  }
1350
- const state = signal(navigator.onLine, { ...(ngDevMode ? { debugName: "state" } : {}), debugName });
1351
- const since = signal(new Date(), ...(ngDevMode ? [{ debugName: "since" }] : []));
1449
+ const state = signal(navigator.onLine, { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), debugName });
1450
+ const since = signal(new Date(), ...(ngDevMode ? [{ debugName: "since" }] : /* istanbul ignore next */ []));
1352
1451
  const goOnline = () => {
1353
1452
  state.set(true);
1354
1453
  since.set(new Date());
@@ -1408,7 +1507,7 @@ function pageVisibility(debugName = 'pageVisibility') {
1408
1507
  if (isPlatformServer(inject(PLATFORM_ID))) {
1409
1508
  return computed(() => 'visible', { debugName });
1410
1509
  }
1411
- const visibility = signal(document.visibilityState, { ...(ngDevMode ? { debugName: "visibility" } : {}), debugName });
1510
+ const visibility = signal(document.visibilityState, { ...(ngDevMode ? { debugName: "visibility" } : /* istanbul ignore next */ {}), debugName });
1412
1511
  const onVisibilityChange = () => visibility.set(document.visibilityState);
1413
1512
  document.addEventListener('visibilitychange', onVisibilityChange);
1414
1513
  inject(DestroyRef).onDestroy(() => document.removeEventListener('visibilitychange', onVisibilityChange));
@@ -1471,13 +1570,16 @@ function scrollPosition(opt) {
1471
1570
  base.unthrottled = base;
1472
1571
  return base;
1473
1572
  }
1474
- const { target = window, throttle = 100, debugName = 'scrollPosition', } = opt || {};
1573
+ const { target = globalThis.window, throttle = 100, debugName = 'scrollPosition', } = opt || {};
1475
1574
  let element;
1476
1575
  let getScrollPosition;
1477
- if (target instanceof Window) {
1576
+ if (target === globalThis.window || target.window === target) {
1478
1577
  element = target;
1479
1578
  getScrollPosition = () => {
1480
- return { x: target.scrollX, y: target.scrollY };
1579
+ return {
1580
+ x: target.scrollX ?? target.pageXOffset ?? 0,
1581
+ y: target.scrollY ?? target.pageYOffset ?? 0,
1582
+ };
1481
1583
  };
1482
1584
  }
1483
1585
  else if (target instanceof ElementRef) {
@@ -1669,7 +1771,7 @@ function toArrayStore(source, injector) {
1669
1771
  if (!Array.isArray(v))
1670
1772
  return 0;
1671
1773
  return v.length;
1672
- }, ...(ngDevMode ? [{ debugName: "lengthSignal" }] : []));
1774
+ }, ...(ngDevMode ? [{ debugName: "lengthSignal" }] : /* istanbul ignore next */ []));
1673
1775
  return new Proxy(source, {
1674
1776
  has(_, prop) {
1675
1777
  if (prop === 'length')
@@ -1934,7 +2036,6 @@ const noopStore = {
1934
2036
  *
1935
2037
  * @Component({
1936
2038
  * selector: 'app-settings',
1937
- * standalone: true,
1938
2039
  * template: `
1939
2040
  * Theme:
1940
2041
  * <select [ngModel]="theme()" (ngModelChange)="theme.set($event)">
@@ -1992,7 +2093,7 @@ function stored(fallback, { key, store: providedStore, serialize = JSON.stringif
1992
2093
  equal,
1993
2094
  };
1994
2095
  const initialKey = untracked(keySig);
1995
- const internal = signal(getValue(initialKey), { ...(ngDevMode ? { debugName: "internal" } : {}), ...opt,
2096
+ const internal = signal(getValue(initialKey), { ...(ngDevMode ? { debugName: "internal" } : /* istanbul ignore next */ {}), ...opt,
1996
2097
  equal: (a, b) => {
1997
2098
  if (a === null && b === null)
1998
2099
  return true;
@@ -2077,10 +2178,10 @@ class MessageBus {
2077
2178
  this.channel.removeEventListener('message', listener);
2078
2179
  this.listeners.delete(id);
2079
2180
  }
2080
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MessageBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2081
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MessageBus, providedIn: 'root' });
2181
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: MessageBus, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2182
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: MessageBus, providedIn: 'root' });
2082
2183
  }
2083
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MessageBus, decorators: [{
2184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: MessageBus, decorators: [{
2084
2185
  type: Injectable,
2085
2186
  args: [{
2086
2187
  providedIn: 'root',
@@ -2157,7 +2258,7 @@ function tabSync(sig, opt) {
2157
2258
  return;
2158
2259
  }
2159
2260
  post(val);
2160
- }, ...(ngDevMode ? [{ debugName: "effectRef" }] : []));
2261
+ }, ...(ngDevMode ? [{ debugName: "effectRef" }] : /* istanbul ignore next */ []));
2161
2262
  inject(DestroyRef).onDestroy(() => {
2162
2263
  effectRef.destroy();
2163
2264
  unsub();
@@ -2353,9 +2454,9 @@ function withHistory(source, opt) {
2353
2454
  history.set([]);
2354
2455
  redoArray.set([]);
2355
2456
  };
2356
- internal.canUndo = computed(() => history().length > 0, ...(ngDevMode ? [{ debugName: "canUndo" }] : []));
2357
- internal.canRedo = computed(() => redoArray().length > 0, ...(ngDevMode ? [{ debugName: "canRedo" }] : []));
2358
- internal.canClear = computed(() => internal.canUndo() || internal.canRedo(), ...(ngDevMode ? [{ debugName: "canClear" }] : []));
2457
+ internal.canUndo = computed(() => history().length > 0, ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
2458
+ internal.canRedo = computed(() => redoArray().length > 0, ...(ngDevMode ? [{ debugName: "canRedo" }] : /* istanbul ignore next */ []));
2459
+ internal.canClear = computed(() => internal.canUndo() || internal.canRedo(), ...(ngDevMode ? [{ debugName: "canClear" }] : /* istanbul ignore next */ []));
2359
2460
  return internal;
2360
2461
  }
2361
2462
 
@@ -2363,5 +2464,5 @@ function withHistory(source, opt) {
2363
2464
  * Generated bundle index. Do not edit.
2364
2465
  */
2365
2466
 
2366
- 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, prefersDarkMode, prefersReducedMotion, scrollPosition, select, sensor, sensors, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
2467
+ 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 };
2367
2468
  //# sourceMappingURL=mmstack-primitives.mjs.map