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