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