@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/README.md +276 -924
- package/fesm2022/mmstack-primitives.mjs +630 -73
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +518 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isDevMode, inject, Injector, untracked, effect, DestroyRef, linkedSignal, computed, signal, isWritableSignal as isWritableSignal$1, isSignal,
|
|
2
|
+
import { isDevMode, inject, Injector, untracked, effect, DestroyRef, linkedSignal, computed, signal, isWritableSignal as isWritableSignal$1, isSignal, PLATFORM_ID, ElementRef, Injectable, runInInjectionContext } from '@angular/core';
|
|
3
3
|
import { isPlatformServer } from '@angular/common';
|
|
4
4
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
5
5
|
|
|
@@ -349,9 +349,9 @@ function debounce(source, opt) {
|
|
|
349
349
|
const { is } = Object;
|
|
350
350
|
function mutable(initial, opt) {
|
|
351
351
|
const baseEqual = opt?.equal ?? is;
|
|
352
|
-
let
|
|
352
|
+
let cnt = 0;
|
|
353
353
|
const equal = (a, b) => {
|
|
354
|
-
if (
|
|
354
|
+
if (cnt > 0)
|
|
355
355
|
return false;
|
|
356
356
|
return baseEqual(a, b);
|
|
357
357
|
};
|
|
@@ -361,9 +361,9 @@ function mutable(initial, opt) {
|
|
|
361
361
|
});
|
|
362
362
|
const internalUpdate = sig.update;
|
|
363
363
|
sig.mutate = (updater) => {
|
|
364
|
-
|
|
364
|
+
cnt++;
|
|
365
365
|
internalUpdate(updater);
|
|
366
|
-
|
|
366
|
+
cnt--;
|
|
367
367
|
};
|
|
368
368
|
sig.inline = (updater) => {
|
|
369
369
|
sig.mutate((prev) => {
|
|
@@ -430,10 +430,10 @@ function derived(source, optOrKey, opt) {
|
|
|
430
430
|
};
|
|
431
431
|
const rest = typeof optOrKey === 'object' ? { ...optOrKey, ...opt } : opt;
|
|
432
432
|
const baseEqual = rest?.equal ?? Object.is;
|
|
433
|
-
let
|
|
433
|
+
let cnt = 0;
|
|
434
434
|
const equal = isMutable(source)
|
|
435
435
|
? (a, b) => {
|
|
436
|
-
if (
|
|
436
|
+
if (cnt > 0)
|
|
437
437
|
return false;
|
|
438
438
|
return baseEqual(a, b);
|
|
439
439
|
}
|
|
@@ -442,9 +442,9 @@ function derived(source, optOrKey, opt) {
|
|
|
442
442
|
sig.from = from;
|
|
443
443
|
if (isMutable(source)) {
|
|
444
444
|
sig.mutate = (updater) => {
|
|
445
|
-
|
|
445
|
+
cnt++;
|
|
446
446
|
sig.update(updater);
|
|
447
|
-
|
|
447
|
+
cnt--;
|
|
448
448
|
};
|
|
449
449
|
sig.inline = (updater) => {
|
|
450
450
|
sig.mutate((prev) => {
|
|
@@ -549,9 +549,11 @@ function indexArray(source, map, opt = {}) {
|
|
|
549
549
|
});
|
|
550
550
|
if (isWritableSignal(data) && isMutable(data) && !opt.equal) {
|
|
551
551
|
opt.equal = (a, b) => {
|
|
552
|
-
if (a !== b)
|
|
553
|
-
return false;
|
|
554
|
-
|
|
552
|
+
if (typeof a !== typeof b)
|
|
553
|
+
return false;
|
|
554
|
+
if (typeof a === 'object' || typeof a === 'function')
|
|
555
|
+
return false;
|
|
556
|
+
return a === b;
|
|
555
557
|
};
|
|
556
558
|
}
|
|
557
559
|
return linkedSignal({
|
|
@@ -598,7 +600,30 @@ const mapArray = indexArray;
|
|
|
598
600
|
* @param mapFn The mapping function. Receives the item and its index as a Signal.
|
|
599
601
|
* @param options Optional configuration:
|
|
600
602
|
* - `onDestroy`: A callback invoked when a mapped item is removed from the array.
|
|
603
|
+
* - `key`: A custom key extractor for identity matching (e.g. `(item) => item.id`)
|
|
604
|
+
* when item references change but conceptual identity is preserved.
|
|
601
605
|
* @returns A `Signal<U[]>` containing the mapped array.
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```ts
|
|
609
|
+
* const users = signal([
|
|
610
|
+
* { id: 1, name: 'Alice' },
|
|
611
|
+
* { id: 2, name: 'Bob' },
|
|
612
|
+
* ]);
|
|
613
|
+
*
|
|
614
|
+
* const rows = keyArray(
|
|
615
|
+
* users,
|
|
616
|
+
* (user, index) => ({
|
|
617
|
+
* label: computed(() => `#${index()} ${user.name}`),
|
|
618
|
+
* id: user.id,
|
|
619
|
+
* }),
|
|
620
|
+
* { key: (u) => u.id },
|
|
621
|
+
* );
|
|
622
|
+
*
|
|
623
|
+
* // Reordering users() rebuilds index signals only — `rows` entries
|
|
624
|
+
* // are matched by id and reused, not re-created.
|
|
625
|
+
* users.set([users()[1], users()[0]]);
|
|
626
|
+
* ```
|
|
602
627
|
*/
|
|
603
628
|
function keyArray(source, mapFn, options = {}) {
|
|
604
629
|
const sourceSignal = isSignal(source) ? source : computed(source);
|
|
@@ -755,15 +780,69 @@ function mapObject(source, mapFn, options = {}) {
|
|
|
755
780
|
}).asReadonly();
|
|
756
781
|
}
|
|
757
782
|
|
|
758
|
-
/**
|
|
783
|
+
/**
|
|
784
|
+
* Synchronous projection of a signal value with optional `CreateSignalOptions`
|
|
785
|
+
* (custom `equal`, `debugName`, etc.). Equivalent to `map` plus the ability to
|
|
786
|
+
* pass signal options through to the underlying `computed()`.
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```ts
|
|
790
|
+
* const user = piped({ id: 1, name: 'Alice' });
|
|
791
|
+
* const name = user.pipe(select((u) => u.name));
|
|
792
|
+
* name(); // 'Alice'
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
759
795
|
const select = (projector, opt) => (src) => computed(() => projector(src()), opt);
|
|
760
|
-
/**
|
|
796
|
+
/**
|
|
797
|
+
* Combine the piped signal with another `Signal` using a projector. The result
|
|
798
|
+
* recomputes whenever either source changes.
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```ts
|
|
802
|
+
* const price = piped(10);
|
|
803
|
+
* const quantity = signal(3);
|
|
804
|
+
* const total = price.pipe(combineWith(quantity, (p, q) => p * q));
|
|
805
|
+
* total(); // 30
|
|
806
|
+
* ```
|
|
807
|
+
*/
|
|
761
808
|
const combineWith = (other, project, opt) => (src) => computed(() => project(src(), other()), opt);
|
|
762
|
-
/**
|
|
809
|
+
/**
|
|
810
|
+
* Suppress emissions while consecutive values are considered equal. The
|
|
811
|
+
* comparator defaults to `Object.is`; pass a custom one for structural or
|
|
812
|
+
* key-based equality (e.g. compare by `id` only).
|
|
813
|
+
*
|
|
814
|
+
* @example
|
|
815
|
+
* ```ts
|
|
816
|
+
* const user = piped({ id: 1, lastSeen: Date.now() });
|
|
817
|
+
* const byId = user.pipe(distinct((a, b) => a.id === b.id));
|
|
818
|
+
* // byId only re-emits when `id` changes, not on every `lastSeen` update
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
763
821
|
const distinct = (equal = Object.is) => (src) => computed(() => src(), { equal });
|
|
764
|
-
/**
|
|
822
|
+
/**
|
|
823
|
+
* Pure synchronous transform from input to output. Equivalent to a `computed()`
|
|
824
|
+
* that reads the source and returns `fn(value)`.
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* ```ts
|
|
828
|
+
* const count = piped(2);
|
|
829
|
+
* const doubled = count.pipe(map((n) => n * 2));
|
|
830
|
+
* doubled(); // 4
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
765
833
|
const map = (fn) => (src) => computed(() => fn(src()));
|
|
766
|
-
/**
|
|
834
|
+
/**
|
|
835
|
+
* Keep only values that pass the predicate. The result holds the last passing
|
|
836
|
+
* value across emissions; before any value passes, the result is `undefined` —
|
|
837
|
+
* see {@link filterWith} when you need a non-`undefined` seed.
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```ts
|
|
841
|
+
* const event = piped<MouseEvent | null>(null);
|
|
842
|
+
* const clicks = event.pipe(filter((e): e is MouseEvent => e?.type === 'click'));
|
|
843
|
+
* clicks(); // undefined until a click happens, then the last MouseEvent
|
|
844
|
+
* ```
|
|
845
|
+
*/
|
|
767
846
|
const filter = (predicate) => (src) => linkedSignal({
|
|
768
847
|
source: src,
|
|
769
848
|
computation: (next, prev) => {
|
|
@@ -772,11 +851,90 @@ const filter = (predicate) => (src) => linkedSignal({
|
|
|
772
851
|
return prev?.source;
|
|
773
852
|
},
|
|
774
853
|
});
|
|
775
|
-
/**
|
|
776
|
-
|
|
777
|
-
|
|
854
|
+
/**
|
|
855
|
+
* Run a side effect on every emission without altering the signal value. Wraps
|
|
856
|
+
* Angular's `effect()`, so it must run in an injection context or receive an
|
|
857
|
+
* explicit `injector`. Use for logging / analytics — not for setting other
|
|
858
|
+
* signals (that's what regular `effect()` is for).
|
|
859
|
+
*
|
|
860
|
+
* @example
|
|
861
|
+
* ```ts
|
|
862
|
+
* const count = piped(0);
|
|
863
|
+
* count.pipe(tap((n) => console.log('count:', n)));
|
|
864
|
+
* count.set(1); // logs 'count: 1'
|
|
865
|
+
* ```
|
|
866
|
+
*/
|
|
867
|
+
const tap = (fn, injector) => (src) => {
|
|
868
|
+
effect(() => fn(src()), {
|
|
869
|
+
injector,
|
|
870
|
+
});
|
|
778
871
|
return src;
|
|
779
872
|
};
|
|
873
|
+
/**
|
|
874
|
+
* Like {@link filter}, but emits `initial` until a value first passes the
|
|
875
|
+
* predicate. Eliminates the `T | undefined` return type at the cost of an
|
|
876
|
+
* explicit seed value.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```ts
|
|
880
|
+
* const event = piped<MouseEvent | null>(null);
|
|
881
|
+
* const lastClick = event.pipe(filterWith((e) => e?.type === 'click', null));
|
|
882
|
+
* lastClick(); // null until the first click, then the most recent click event
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
const filterWith = (predicate, initial) => (src) => linkedSignal({
|
|
886
|
+
source: src,
|
|
887
|
+
computation: (next, prev) => predicate(next) ? next : (prev?.value ?? initial),
|
|
888
|
+
});
|
|
889
|
+
/**
|
|
890
|
+
* Emit `initial` on the first read, then mirror the source on every subsequent
|
|
891
|
+
* read. Useful for giving a pipeline a sensible seed value before the source
|
|
892
|
+
* is ready (e.g. loading state).
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* ```ts
|
|
896
|
+
* const data = piped<User | null>(null);
|
|
897
|
+
* const view = data.pipe(startWith<User | null, 'loading'>('loading'));
|
|
898
|
+
* view(); // 'loading' on first read, then User | null afterward
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
const startWith = (initial) => (src) => linkedSignal({
|
|
902
|
+
source: src,
|
|
903
|
+
computation: (next, prev) => (prev === undefined ? initial : next),
|
|
904
|
+
});
|
|
905
|
+
/**
|
|
906
|
+
* Emit `[prev, curr]` tuples so consumers can react to transitions instead of
|
|
907
|
+
* raw values. On the first emission `prev` is `undefined`.
|
|
908
|
+
*
|
|
909
|
+
* @example
|
|
910
|
+
* ```ts
|
|
911
|
+
* const count = piped(0);
|
|
912
|
+
* const delta = count.pipe(pairwise(), map(([prev, curr]) => curr - (prev ?? 0)));
|
|
913
|
+
* count.set(5);
|
|
914
|
+
* delta(); // 5
|
|
915
|
+
* ```
|
|
916
|
+
*/
|
|
917
|
+
const pairwise = () => (src) => linkedSignal({
|
|
918
|
+
source: src,
|
|
919
|
+
computation: (next, prev) => [prev?.source, next],
|
|
920
|
+
});
|
|
921
|
+
/**
|
|
922
|
+
* Reduce-like accumulator that folds each emission into a running result.
|
|
923
|
+
* Behaves like `Array.prototype.reduce` but applied over time, with the
|
|
924
|
+
* accumulator persisted across emissions.
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* ```ts
|
|
928
|
+
* const delta = piped(0);
|
|
929
|
+
* const total = delta.pipe(scan((acc, n) => acc + n, 0));
|
|
930
|
+
* delta.set(5); // total() === 5
|
|
931
|
+
* delta.set(3); // total() === 8
|
|
932
|
+
* ```
|
|
933
|
+
*/
|
|
934
|
+
const scan = (reducer, seed) => (src) => linkedSignal({
|
|
935
|
+
source: src,
|
|
936
|
+
computation: (next, prev) => reducer(prev?.value ?? seed, next),
|
|
937
|
+
});
|
|
780
938
|
|
|
781
939
|
/**
|
|
782
940
|
* Decorate any `Signal<T>` with a chainable `.pipe(...)` method.
|
|
@@ -880,12 +1038,10 @@ function pooled({ create, reset, computation, ...opt }) {
|
|
|
880
1038
|
const next = other ?? untracked(() => create());
|
|
881
1039
|
if (current !== undefined) {
|
|
882
1040
|
if (currentFresh) {
|
|
883
|
-
// never-mutated buffer leaving the active slot; nothing to clean
|
|
884
1041
|
other = current;
|
|
885
1042
|
}
|
|
886
1043
|
else {
|
|
887
|
-
//
|
|
888
|
-
// (also threads the swap-return correctly into the pool's spare slot)
|
|
1044
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guarded by the `current !== undefined` check above
|
|
889
1045
|
other = untracked(() => reset(current)) ?? current;
|
|
890
1046
|
}
|
|
891
1047
|
}
|
|
@@ -932,6 +1088,96 @@ function pooledMap(optOrComputation, signalOpt) {
|
|
|
932
1088
|
return pooled(toPooledOptions(optOrComputation, createEmptyMap, resetClearable, signalOpt));
|
|
933
1089
|
}
|
|
934
1090
|
|
|
1091
|
+
const EVENTS = [
|
|
1092
|
+
'chargingchange',
|
|
1093
|
+
'levelchange',
|
|
1094
|
+
'chargingtimechange',
|
|
1095
|
+
'dischargingtimechange',
|
|
1096
|
+
];
|
|
1097
|
+
/**
|
|
1098
|
+
* Creates a read-only signal that tracks the system battery status using the
|
|
1099
|
+
* Battery Status API. Returns `null` until the underlying `getBattery()`
|
|
1100
|
+
* promise resolves, or permanently when the API is unsupported (Firefox /
|
|
1101
|
+
* Safari at the time of writing). SSR-safe.
|
|
1102
|
+
*
|
|
1103
|
+
* @example
|
|
1104
|
+
* ```ts
|
|
1105
|
+
* const battery = batteryStatus();
|
|
1106
|
+
* effect(() => {
|
|
1107
|
+
* const b = battery();
|
|
1108
|
+
* if (b) console.log(`${Math.round(b.level * 100)}% • charging: ${b.charging}`);
|
|
1109
|
+
* });
|
|
1110
|
+
* ```
|
|
1111
|
+
*/
|
|
1112
|
+
function batteryStatus(debugName = 'batteryStatus') {
|
|
1113
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
1114
|
+
typeof navigator === 'undefined' ||
|
|
1115
|
+
typeof navigator.getBattery !== 'function') {
|
|
1116
|
+
return computed(() => null, { debugName });
|
|
1117
|
+
}
|
|
1118
|
+
const state = signal(null, { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), debugName });
|
|
1119
|
+
const abortController = new AbortController();
|
|
1120
|
+
inject(DestroyRef).onDestroy(() => abortController.abort());
|
|
1121
|
+
navigator.getBattery().then((battery) => {
|
|
1122
|
+
if (abortController.signal.aborted)
|
|
1123
|
+
return;
|
|
1124
|
+
const read = () => ({
|
|
1125
|
+
level: battery.level,
|
|
1126
|
+
charging: battery.charging,
|
|
1127
|
+
chargingTime: battery.chargingTime,
|
|
1128
|
+
dischargingTime: battery.dischargingTime,
|
|
1129
|
+
});
|
|
1130
|
+
const onChange = () => state.set(read());
|
|
1131
|
+
state.set(read());
|
|
1132
|
+
for (const ev of EVENTS) {
|
|
1133
|
+
battery.addEventListener(ev, onChange, {
|
|
1134
|
+
signal: abortController.signal,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
return state.asReadonly();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Creates a read-only signal mirroring the system clipboard contents.
|
|
1143
|
+
*
|
|
1144
|
+
* The signal value starts empty and updates whenever a `copy` event fires on
|
|
1145
|
+
* the document (or {@link ClipboardSignal.copy} is invoked from this app).
|
|
1146
|
+
* SSR-safe — returns `''` and `isSupported: false` on the server.
|
|
1147
|
+
*
|
|
1148
|
+
* Note: read access requires the Clipboard API and an active permission grant
|
|
1149
|
+
* in browsers that gate it. Errors from `navigator.clipboard.readText` are
|
|
1150
|
+
* swallowed silently to keep the signal value stable.
|
|
1151
|
+
*/
|
|
1152
|
+
function clipboard(debugName = 'clipboard') {
|
|
1153
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
1154
|
+
typeof navigator === 'undefined' ||
|
|
1155
|
+
!navigator.clipboard) {
|
|
1156
|
+
const sig = computed(() => '', { debugName });
|
|
1157
|
+
sig.copy = () => Promise.resolve();
|
|
1158
|
+
sig.isSupported = computed(() => false, ...(ngDevMode ? [{ debugName: "isSupported" }] : /* istanbul ignore next */ []));
|
|
1159
|
+
return sig;
|
|
1160
|
+
}
|
|
1161
|
+
const state = signal('', { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), debugName });
|
|
1162
|
+
const refresh = () => {
|
|
1163
|
+
navigator.clipboard.readText().then((value) => state.set(value), () => {
|
|
1164
|
+
// permission denied / focus required — ignore
|
|
1165
|
+
});
|
|
1166
|
+
};
|
|
1167
|
+
const abortController = new AbortController();
|
|
1168
|
+
const onCopy = () => refresh();
|
|
1169
|
+
document.addEventListener('copy', onCopy, { signal: abortController.signal });
|
|
1170
|
+
document.addEventListener('cut', onCopy, { signal: abortController.signal });
|
|
1171
|
+
inject(DestroyRef).onDestroy(() => abortController.abort());
|
|
1172
|
+
const sig = state.asReadonly();
|
|
1173
|
+
sig.copy = async (value) => {
|
|
1174
|
+
await navigator.clipboard.writeText(value);
|
|
1175
|
+
state.set(value);
|
|
1176
|
+
};
|
|
1177
|
+
sig.isSupported = computed(() => true, ...(ngDevMode ? [{ debugName: "isSupported" }] : /* istanbul ignore next */ []));
|
|
1178
|
+
return sig;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
935
1181
|
function observerSupported$1() {
|
|
936
1182
|
return typeof ResizeObserver !== 'undefined';
|
|
937
1183
|
}
|
|
@@ -1132,6 +1378,184 @@ function elementVisibility(target = inject(ElementRef), opt) {
|
|
|
1132
1378
|
return base;
|
|
1133
1379
|
}
|
|
1134
1380
|
|
|
1381
|
+
function unwrap$1(target) {
|
|
1382
|
+
if (!target)
|
|
1383
|
+
return null;
|
|
1384
|
+
return target instanceof ElementRef ? target.nativeElement : target;
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Creates a read-only signal that tracks whether the focused element is the
|
|
1388
|
+
* target or a descendant of it. Mirrors the CSS `:focus-within` pseudo-class.
|
|
1389
|
+
*
|
|
1390
|
+
* Defaults `target` to the current `ElementRef` so it can be used inline in a
|
|
1391
|
+
* component's `class` field. SSR-safe — returns a constant `false` signal on
|
|
1392
|
+
* the server.
|
|
1393
|
+
*
|
|
1394
|
+
* @example
|
|
1395
|
+
* ```ts
|
|
1396
|
+
* @Component({ ... })
|
|
1397
|
+
* class MenuComponent {
|
|
1398
|
+
* // Defaults to the host element — flips true when focus is inside.
|
|
1399
|
+
* readonly hasFocus = focusWithin();
|
|
1400
|
+
* }
|
|
1401
|
+
* ```
|
|
1402
|
+
*/
|
|
1403
|
+
function focusWithin(target = inject(ElementRef)) {
|
|
1404
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
1405
|
+
return computed(() => false, { debugName: 'focusWithin' });
|
|
1406
|
+
}
|
|
1407
|
+
const state = signal(false, { debugName: 'focusWithin' });
|
|
1408
|
+
const attach = (el) => {
|
|
1409
|
+
state.set(el.contains(document.activeElement));
|
|
1410
|
+
const abortController = new AbortController();
|
|
1411
|
+
el.addEventListener('focusin', () => state.set(true), {
|
|
1412
|
+
signal: abortController.signal,
|
|
1413
|
+
});
|
|
1414
|
+
el.addEventListener('focusout', () => {
|
|
1415
|
+
// Defer so `document.activeElement` reflects the focus move.
|
|
1416
|
+
queueMicrotask(() => state.set(el.contains(document.activeElement)));
|
|
1417
|
+
}, { signal: abortController.signal });
|
|
1418
|
+
return () => abortController.abort();
|
|
1419
|
+
};
|
|
1420
|
+
if (isSignal(target)) {
|
|
1421
|
+
const targetSig = target;
|
|
1422
|
+
effect((cleanup) => {
|
|
1423
|
+
const el = unwrap$1(targetSig());
|
|
1424
|
+
if (!el) {
|
|
1425
|
+
state.set(false);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
cleanup(attach(el));
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
else {
|
|
1432
|
+
const el = unwrap$1(target);
|
|
1433
|
+
if (el) {
|
|
1434
|
+
const detach = attach(el);
|
|
1435
|
+
inject(DestroyRef).onDestroy(detach);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return state.asReadonly();
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Creates a read-only signal that exposes the current geolocation position.
|
|
1443
|
+
*
|
|
1444
|
+
* The returned signal carries `error` and `loading` sub-signals for permission
|
|
1445
|
+
* failures and the in-flight initial fetch respectively. SSR-safe — on the
|
|
1446
|
+
* server the position is `null`, loading is `false`, and no API calls are made.
|
|
1447
|
+
*
|
|
1448
|
+
* @example
|
|
1449
|
+
* ```ts
|
|
1450
|
+
* const where = geolocation({ watch: true, enableHighAccuracy: true });
|
|
1451
|
+
* effect(() => console.log(where()?.coords, where.error()));
|
|
1452
|
+
* ```
|
|
1453
|
+
*/
|
|
1454
|
+
function geolocation(opt) {
|
|
1455
|
+
if (isPlatformServer(inject(PLATFORM_ID)) || typeof navigator === 'undefined' || !navigator.geolocation) {
|
|
1456
|
+
const sig = computed(() => null, {
|
|
1457
|
+
debugName: opt?.debugName ?? 'geolocation',
|
|
1458
|
+
});
|
|
1459
|
+
sig.error = computed(() => null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
1460
|
+
sig.loading = computed(() => false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1461
|
+
return sig;
|
|
1462
|
+
}
|
|
1463
|
+
const position = signal(null, {
|
|
1464
|
+
debugName: opt?.debugName ?? 'geolocation',
|
|
1465
|
+
});
|
|
1466
|
+
const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
1467
|
+
const loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1468
|
+
const onSuccess = (p) => {
|
|
1469
|
+
position.set(p);
|
|
1470
|
+
error.set(null);
|
|
1471
|
+
loading.set(false);
|
|
1472
|
+
};
|
|
1473
|
+
const onError = (e) => {
|
|
1474
|
+
error.set(e);
|
|
1475
|
+
loading.set(false);
|
|
1476
|
+
};
|
|
1477
|
+
if (opt?.watch) {
|
|
1478
|
+
const watchId = navigator.geolocation.watchPosition(onSuccess, onError, opt);
|
|
1479
|
+
inject(DestroyRef).onDestroy(() => navigator.geolocation.clearWatch(watchId));
|
|
1480
|
+
}
|
|
1481
|
+
else {
|
|
1482
|
+
navigator.geolocation.getCurrentPosition(onSuccess, onError, opt);
|
|
1483
|
+
}
|
|
1484
|
+
const sig = position.asReadonly();
|
|
1485
|
+
sig.error = error.asReadonly();
|
|
1486
|
+
sig.loading = loading.asReadonly();
|
|
1487
|
+
return sig;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
const DEFAULT_EVENTS = [
|
|
1491
|
+
'mousemove',
|
|
1492
|
+
'keydown',
|
|
1493
|
+
'touchstart',
|
|
1494
|
+
'scroll',
|
|
1495
|
+
'visibilitychange',
|
|
1496
|
+
];
|
|
1497
|
+
const serverDate$1 = new Date();
|
|
1498
|
+
/**
|
|
1499
|
+
* Creates a read-only signal that flips to `true` after a window of user
|
|
1500
|
+
* inactivity. Any of the configured `events` (default: pointer/keyboard/scroll
|
|
1501
|
+
* activity) resets the timer and flips the signal back to `false`.
|
|
1502
|
+
*
|
|
1503
|
+
* SSR-safe — always `false` with a frozen `since` date on the server.
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* ```ts
|
|
1507
|
+
* const isAway = idle({ ms: 30_000 });
|
|
1508
|
+
* effect(() => {
|
|
1509
|
+
* if (isAway()) console.log('idle since', isAway.since());
|
|
1510
|
+
* });
|
|
1511
|
+
* ```
|
|
1512
|
+
*/
|
|
1513
|
+
function idle(opt) {
|
|
1514
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
1515
|
+
const sig = computed(() => false, {
|
|
1516
|
+
debugName: opt?.debugName ?? 'idle',
|
|
1517
|
+
});
|
|
1518
|
+
sig.since = computed(() => serverDate$1, ...(ngDevMode ? [{ debugName: "since" }] : /* istanbul ignore next */ []));
|
|
1519
|
+
return sig;
|
|
1520
|
+
}
|
|
1521
|
+
const ms = opt?.ms ?? 60_000;
|
|
1522
|
+
const events = opt?.events ?? DEFAULT_EVENTS;
|
|
1523
|
+
const state = signal(false, { debugName: opt?.debugName ?? 'idle' });
|
|
1524
|
+
const since = signal(new Date(), ...(ngDevMode ? [{ debugName: "since" }] : /* istanbul ignore next */ []));
|
|
1525
|
+
let timer;
|
|
1526
|
+
const goIdle = () => {
|
|
1527
|
+
if (state())
|
|
1528
|
+
return;
|
|
1529
|
+
state.set(true);
|
|
1530
|
+
since.set(new Date());
|
|
1531
|
+
};
|
|
1532
|
+
const reset = () => {
|
|
1533
|
+
if (timer)
|
|
1534
|
+
clearTimeout(timer);
|
|
1535
|
+
if (state()) {
|
|
1536
|
+
state.set(false);
|
|
1537
|
+
since.set(new Date());
|
|
1538
|
+
}
|
|
1539
|
+
timer = setTimeout(goIdle, ms);
|
|
1540
|
+
};
|
|
1541
|
+
const abortController = new AbortController();
|
|
1542
|
+
for (const ev of events) {
|
|
1543
|
+
window.addEventListener(ev, reset, {
|
|
1544
|
+
passive: true,
|
|
1545
|
+
signal: abortController.signal,
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
timer = setTimeout(goIdle, ms);
|
|
1549
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1550
|
+
if (timer)
|
|
1551
|
+
clearTimeout(timer);
|
|
1552
|
+
abortController.abort();
|
|
1553
|
+
});
|
|
1554
|
+
const sig = state.asReadonly();
|
|
1555
|
+
sig.since = since.asReadonly();
|
|
1556
|
+
return sig;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1135
1559
|
/**
|
|
1136
1560
|
* Creates a read-only signal that reactively tracks whether a CSS media query
|
|
1137
1561
|
* string currently matches.
|
|
@@ -1293,26 +1717,41 @@ function throttled(initial, opt) {
|
|
|
1293
1717
|
*/
|
|
1294
1718
|
function throttle(source, opt) {
|
|
1295
1719
|
const ms = opt?.ms ?? 0;
|
|
1720
|
+
const leading = opt?.leading ?? false;
|
|
1721
|
+
const trailing = opt?.trailing ?? true;
|
|
1296
1722
|
const trigger = signal(false, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
|
|
1723
|
+
const fire = () => trigger.update((c) => !c);
|
|
1297
1724
|
let timeout;
|
|
1725
|
+
let pendingTrailing = false;
|
|
1298
1726
|
try {
|
|
1299
1727
|
const destroyRef = opt?.destroyRef ?? inject(DestroyRef, { optional: true });
|
|
1300
1728
|
destroyRef?.onDestroy(() => {
|
|
1301
1729
|
if (timeout)
|
|
1302
1730
|
clearTimeout(timeout);
|
|
1303
1731
|
timeout = undefined;
|
|
1732
|
+
pendingTrailing = false;
|
|
1304
1733
|
});
|
|
1305
1734
|
}
|
|
1306
1735
|
catch {
|
|
1307
1736
|
// not in injection context & no destroyRef provided opting out of cleanup
|
|
1308
1737
|
}
|
|
1309
1738
|
const tick = () => {
|
|
1310
|
-
if (timeout)
|
|
1739
|
+
if (!timeout) {
|
|
1740
|
+
if (leading)
|
|
1741
|
+
fire();
|
|
1742
|
+
else
|
|
1743
|
+
pendingTrailing = trailing;
|
|
1744
|
+
timeout = setTimeout(() => {
|
|
1745
|
+
timeout = undefined;
|
|
1746
|
+
if (trailing && pendingTrailing) {
|
|
1747
|
+
pendingTrailing = false;
|
|
1748
|
+
fire();
|
|
1749
|
+
}
|
|
1750
|
+
}, ms);
|
|
1311
1751
|
return;
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
}, ms);
|
|
1752
|
+
}
|
|
1753
|
+
if (trailing)
|
|
1754
|
+
pendingTrailing = true;
|
|
1316
1755
|
};
|
|
1317
1756
|
const set = (value) => {
|
|
1318
1757
|
source.set(value);
|
|
@@ -1433,6 +1872,14 @@ const serverDate = new Date();
|
|
|
1433
1872
|
*
|
|
1434
1873
|
* @param debugName Optional debug name for the signal.
|
|
1435
1874
|
* @returns A `NetworkStatusSignal` instance.
|
|
1875
|
+
*
|
|
1876
|
+
* @example
|
|
1877
|
+
* ```ts
|
|
1878
|
+
* const online = networkStatus();
|
|
1879
|
+
* effect(() => {
|
|
1880
|
+
* if (!online()) console.log('offline since', online.since());
|
|
1881
|
+
* });
|
|
1882
|
+
* ```
|
|
1436
1883
|
*/
|
|
1437
1884
|
function networkStatus(debugName = 'networkStatus') {
|
|
1438
1885
|
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
@@ -1463,6 +1910,44 @@ function networkStatus(debugName = 'networkStatus') {
|
|
|
1463
1910
|
return sig;
|
|
1464
1911
|
}
|
|
1465
1912
|
|
|
1913
|
+
const SSR_FALLBACK = {
|
|
1914
|
+
angle: 0,
|
|
1915
|
+
type: 'portrait-primary',
|
|
1916
|
+
};
|
|
1917
|
+
/**
|
|
1918
|
+
* Creates a read-only signal that tracks `screen.orientation`.
|
|
1919
|
+
*
|
|
1920
|
+
* SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
|
|
1921
|
+
* and in environments without `screen.orientation` support.
|
|
1922
|
+
*
|
|
1923
|
+
* @example
|
|
1924
|
+
* ```ts
|
|
1925
|
+
* const screenOrientation = orientation();
|
|
1926
|
+
* effect(() => {
|
|
1927
|
+
* const { type, angle } = screenOrientation();
|
|
1928
|
+
* console.log(`${type} at ${angle}°`);
|
|
1929
|
+
* });
|
|
1930
|
+
* ```
|
|
1931
|
+
*/
|
|
1932
|
+
function orientation(debugName = 'orientation') {
|
|
1933
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
1934
|
+
typeof screen === 'undefined' ||
|
|
1935
|
+
!screen.orientation) {
|
|
1936
|
+
return computed(() => SSR_FALLBACK, { debugName });
|
|
1937
|
+
}
|
|
1938
|
+
const so = screen.orientation;
|
|
1939
|
+
const read = () => ({
|
|
1940
|
+
angle: so.angle,
|
|
1941
|
+
type: so.type,
|
|
1942
|
+
});
|
|
1943
|
+
const state = signal(read(), { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), debugName,
|
|
1944
|
+
equal: (a, b) => a.angle === b.angle && a.type === b.type });
|
|
1945
|
+
const onChange = () => state.set(read());
|
|
1946
|
+
so.addEventListener('change', onChange);
|
|
1947
|
+
inject(DestroyRef).onDestroy(() => so.removeEventListener('change', onChange));
|
|
1948
|
+
return state.asReadonly();
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1466
1951
|
/**
|
|
1467
1952
|
* Creates a read-only signal that tracks the page's visibility state.
|
|
1468
1953
|
*
|
|
@@ -1682,39 +2167,67 @@ function windowSize(opt) {
|
|
|
1682
2167
|
* @internal
|
|
1683
2168
|
*/
|
|
1684
2169
|
function sensor(type, options) {
|
|
2170
|
+
const opts = options;
|
|
1685
2171
|
switch (type) {
|
|
1686
2172
|
case 'mousePosition':
|
|
1687
|
-
return mousePosition(
|
|
2173
|
+
return mousePosition(opts);
|
|
1688
2174
|
case 'networkStatus':
|
|
1689
|
-
return networkStatus(
|
|
2175
|
+
return networkStatus(opts?.debugName);
|
|
1690
2176
|
case 'pageVisibility':
|
|
1691
|
-
return pageVisibility(
|
|
2177
|
+
return pageVisibility(opts?.debugName);
|
|
1692
2178
|
case 'darkMode':
|
|
1693
2179
|
case 'dark-mode':
|
|
1694
|
-
return prefersDarkMode(
|
|
2180
|
+
return prefersDarkMode(opts?.debugName);
|
|
1695
2181
|
case 'reducedMotion':
|
|
1696
2182
|
case 'reduced-motion':
|
|
1697
|
-
return prefersReducedMotion(
|
|
1698
|
-
case 'mediaQuery':
|
|
1699
|
-
|
|
1700
|
-
return mediaQuery(opt.query, opt.debugName);
|
|
1701
|
-
}
|
|
2183
|
+
return prefersReducedMotion(opts?.debugName);
|
|
2184
|
+
case 'mediaQuery':
|
|
2185
|
+
return mediaQuery(opts.query, opts.debugName);
|
|
1702
2186
|
case 'windowSize':
|
|
1703
|
-
return windowSize(
|
|
2187
|
+
return windowSize(opts);
|
|
1704
2188
|
case 'scrollPosition':
|
|
1705
|
-
return scrollPosition(
|
|
1706
|
-
case 'elementVisibility':
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
case '
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
2189
|
+
return scrollPosition(opts);
|
|
2190
|
+
case 'elementVisibility':
|
|
2191
|
+
return elementVisibility(opts?.target, opts);
|
|
2192
|
+
case 'elementSize':
|
|
2193
|
+
return elementSize(opts?.target, opts);
|
|
2194
|
+
case 'geolocation':
|
|
2195
|
+
return geolocation(opts);
|
|
2196
|
+
case 'clipboard':
|
|
2197
|
+
return clipboard(opts?.debugName);
|
|
2198
|
+
case 'orientation':
|
|
2199
|
+
return orientation(opts?.debugName);
|
|
2200
|
+
case 'batteryStatus':
|
|
2201
|
+
return batteryStatus(opts?.debugName);
|
|
2202
|
+
case 'idle':
|
|
2203
|
+
return idle(opts);
|
|
2204
|
+
case 'focusWithin':
|
|
2205
|
+
return focusWithin(opts?.target);
|
|
1714
2206
|
default:
|
|
1715
2207
|
throw new Error(`Unknown sensor type: ${type}`);
|
|
1716
2208
|
}
|
|
1717
2209
|
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Bulk sensor factory — creates several sensor signals at once and returns
|
|
2212
|
+
* them keyed by sensor type. Convenient when a single consumer needs to react
|
|
2213
|
+
* to multiple browser signals; for a single sensor prefer {@link sensor}
|
|
2214
|
+
* directly.
|
|
2215
|
+
*
|
|
2216
|
+
* @typeParam TType The union of sensor keys being requested.
|
|
2217
|
+
* @param track Array of sensor type keys to create.
|
|
2218
|
+
* @param opt Optional per-sensor options keyed by sensor type.
|
|
2219
|
+
* @returns A record `{ [key]: <SensorReturnType> }` for each requested key.
|
|
2220
|
+
*
|
|
2221
|
+
* @example
|
|
2222
|
+
* ```ts
|
|
2223
|
+
* const { windowSize, networkStatus } = sensors(
|
|
2224
|
+
* ['windowSize', 'networkStatus'],
|
|
2225
|
+
* { windowSize: { throttle: 200 } },
|
|
2226
|
+
* );
|
|
2227
|
+
*
|
|
2228
|
+
* effect(() => console.log(windowSize(), networkStatus()));
|
|
2229
|
+
* ```
|
|
2230
|
+
*/
|
|
1718
2231
|
function sensors(track, opt) {
|
|
1719
2232
|
return track.reduce((result, key) => {
|
|
1720
2233
|
result[key] = sensor(key, opt?.[key]);
|
|
@@ -1722,6 +2235,49 @@ function sensors(track, opt) {
|
|
|
1722
2235
|
}, {});
|
|
1723
2236
|
}
|
|
1724
2237
|
|
|
2238
|
+
function unwrap(t) {
|
|
2239
|
+
if (!t)
|
|
2240
|
+
return null;
|
|
2241
|
+
return t instanceof ElementRef ? t.nativeElement : t;
|
|
2242
|
+
}
|
|
2243
|
+
function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
2244
|
+
const project = typeof projectOrOpt === 'function' ? projectOrOpt : undefined;
|
|
2245
|
+
const opt = typeof projectOrOpt === 'function' ? maybeOpt : projectOrOpt;
|
|
2246
|
+
const injector = opt?.injector ?? inject(Injector);
|
|
2247
|
+
if (isPlatformServer(injector.get(PLATFORM_ID))) {
|
|
2248
|
+
return computed(() => initial, { debugName: opt?.debugName });
|
|
2249
|
+
}
|
|
2250
|
+
const state = signal(initial, {
|
|
2251
|
+
debugName: opt?.debugName,
|
|
2252
|
+
});
|
|
2253
|
+
const handler = (event) => {
|
|
2254
|
+
if (project)
|
|
2255
|
+
state.set(project(event));
|
|
2256
|
+
else
|
|
2257
|
+
state.set(event);
|
|
2258
|
+
};
|
|
2259
|
+
const { destroyRef: providedDestroyRef, ...listenerOpts } = opt ?? {};
|
|
2260
|
+
if (isSignal(target)) {
|
|
2261
|
+
const targetSig = target;
|
|
2262
|
+
effect((cleanup) => {
|
|
2263
|
+
const resolved = unwrap(targetSig());
|
|
2264
|
+
if (!resolved)
|
|
2265
|
+
return;
|
|
2266
|
+
resolved.addEventListener(eventName, handler, listenerOpts);
|
|
2267
|
+
cleanup(() => resolved.removeEventListener(eventName, handler, listenerOpts));
|
|
2268
|
+
}, { injector });
|
|
2269
|
+
}
|
|
2270
|
+
else {
|
|
2271
|
+
const resolved = unwrap(target);
|
|
2272
|
+
if (resolved) {
|
|
2273
|
+
resolved.addEventListener(eventName, handler, listenerOpts);
|
|
2274
|
+
const destroyRef = providedDestroyRef ?? injector.get(DestroyRef);
|
|
2275
|
+
destroyRef.onDestroy(() => resolved.removeEventListener(eventName, handler, listenerOpts));
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
return untracked(() => state.asReadonly());
|
|
2279
|
+
}
|
|
2280
|
+
|
|
1725
2281
|
const IS_STORE = Symbol('MMSTACK::IS_STORE');
|
|
1726
2282
|
const PROXY_CACHE = new WeakMap();
|
|
1727
2283
|
const SIGNAL_FN_PROP = new Set([
|
|
@@ -2212,14 +2768,11 @@ function generateDeterministicID() {
|
|
|
2212
2768
|
*
|
|
2213
2769
|
* @template T - The type of the WritableSignal
|
|
2214
2770
|
* @param sig - The WritableSignal to synchronize across tabs
|
|
2215
|
-
* @param opt -
|
|
2216
|
-
* @param opt.id - Explicit channel ID for synchronization.
|
|
2217
|
-
* a deterministic ID is generated based on the call site.
|
|
2218
|
-
* Use explicit IDs in production for reliability.
|
|
2771
|
+
* @param opt - configuration object
|
|
2772
|
+
* @param opt.id - Explicit channel ID for synchronization.
|
|
2219
2773
|
*
|
|
2220
2774
|
* @returns The same WritableSignal instance, now synchronized across tabs
|
|
2221
2775
|
*
|
|
2222
|
-
* @throws {Error} When deterministic ID generation fails and no explicit ID is provided
|
|
2223
2776
|
*
|
|
2224
2777
|
* @example
|
|
2225
2778
|
* ```typescript
|
|
@@ -2243,7 +2796,7 @@ function generateDeterministicID() {
|
|
|
2243
2796
|
function tabSync(sig, opt) {
|
|
2244
2797
|
if (isPlatformServer(inject(PLATFORM_ID)))
|
|
2245
2798
|
return sig;
|
|
2246
|
-
const id = opt?.id
|
|
2799
|
+
const id = typeof opt === 'string' ? opt : (opt?.id ?? generateDeterministicID());
|
|
2247
2800
|
const bus = inject(MessageBus);
|
|
2248
2801
|
const { unsub, post } = bus.subscribe(id, (next) => sig.set(next));
|
|
2249
2802
|
let first = false;
|
|
@@ -2375,8 +2928,13 @@ function getSignalEquality(sig) {
|
|
|
2375
2928
|
* console.log('Can undo:', name.canUndo()); // false
|
|
2376
2929
|
* ```
|
|
2377
2930
|
*/
|
|
2378
|
-
function withHistory(
|
|
2379
|
-
const equal = opt?.equal ??
|
|
2931
|
+
function withHistory(sourceOrValue, opt) {
|
|
2932
|
+
const equal = (opt?.equal ?? isSignal(sourceOrValue))
|
|
2933
|
+
? getSignalEquality(sourceOrValue)
|
|
2934
|
+
: Object.is;
|
|
2935
|
+
const source = isSignal(sourceOrValue)
|
|
2936
|
+
? sourceOrValue
|
|
2937
|
+
: signal(sourceOrValue);
|
|
2380
2938
|
const maxSize = opt?.maxSize ?? Infinity;
|
|
2381
2939
|
const history = mutable([], {
|
|
2382
2940
|
...opt,
|
|
@@ -2384,20 +2942,22 @@ function withHistory(source, opt) {
|
|
|
2384
2942
|
});
|
|
2385
2943
|
const redoArray = mutable([]);
|
|
2386
2944
|
const originalSet = source.set;
|
|
2945
|
+
const trim = (arr) => {
|
|
2946
|
+
if (arr.length < maxSize)
|
|
2947
|
+
return arr;
|
|
2948
|
+
if (opt?.cleanupStrategy === 'shift') {
|
|
2949
|
+
arr.shift();
|
|
2950
|
+
return arr;
|
|
2951
|
+
}
|
|
2952
|
+
return arr.slice(Math.floor(maxSize / 2));
|
|
2953
|
+
};
|
|
2387
2954
|
const set = (value) => {
|
|
2388
2955
|
const current = untracked(source);
|
|
2389
2956
|
if (equal(value, current))
|
|
2390
2957
|
return;
|
|
2391
2958
|
source.set(value);
|
|
2392
2959
|
history.mutate((c) => {
|
|
2393
|
-
|
|
2394
|
-
if (opt?.cleanupStrategy === 'shift') {
|
|
2395
|
-
c.shift();
|
|
2396
|
-
}
|
|
2397
|
-
else {
|
|
2398
|
-
c = c.slice(Math.floor(maxSize / 2));
|
|
2399
|
-
}
|
|
2400
|
-
}
|
|
2960
|
+
c = trim(c);
|
|
2401
2961
|
c.push(current);
|
|
2402
2962
|
return c;
|
|
2403
2963
|
});
|
|
@@ -2421,7 +2981,11 @@ function withHistory(source, opt) {
|
|
|
2421
2981
|
return;
|
|
2422
2982
|
originalSet.call(source, valueToRestore);
|
|
2423
2983
|
history.inline((h) => h.pop());
|
|
2424
|
-
redoArray.
|
|
2984
|
+
redoArray.mutate((r) => {
|
|
2985
|
+
r = trim(r);
|
|
2986
|
+
r.push(valueForRedo);
|
|
2987
|
+
return r;
|
|
2988
|
+
});
|
|
2425
2989
|
};
|
|
2426
2990
|
internal.redo = () => {
|
|
2427
2991
|
const redoStack = untracked(redoArray);
|
|
@@ -2434,14 +2998,7 @@ function withHistory(source, opt) {
|
|
|
2434
2998
|
originalSet.call(source, valueToRestore);
|
|
2435
2999
|
redoArray.inline((r) => r.pop());
|
|
2436
3000
|
history.mutate((h) => {
|
|
2437
|
-
|
|
2438
|
-
if (opt?.cleanupStrategy === 'shift') {
|
|
2439
|
-
h.shift();
|
|
2440
|
-
}
|
|
2441
|
-
else {
|
|
2442
|
-
h = h.slice(Math.floor(maxSize / 2));
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
3001
|
+
h = trim(h);
|
|
2445
3002
|
h.push(valueForUndo);
|
|
2446
3003
|
return h;
|
|
2447
3004
|
});
|
|
@@ -2460,5 +3017,5 @@ function withHistory(source, opt) {
|
|
|
2460
3017
|
* Generated bundle index. Do not edit.
|
|
2461
3018
|
*/
|
|
2462
3019
|
|
|
2463
|
-
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 };
|
|
3020
|
+
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 };
|
|
2464
3021
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|