@mmstack/primitives 19.3.4 → 19.3.6
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 +440 -80
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/lib/pipeable/operators.d.ts +13 -2
- package/lib/sensors/battery-status.d.ts +14 -0
- package/lib/sensors/clipboard.d.ts +22 -0
- package/lib/sensors/focus-within.d.ts +12 -0
- package/lib/sensors/geolocation.d.ts +29 -0
- package/lib/sensors/idle.d.ts +27 -0
- package/lib/sensors/index.d.ts +7 -0
- package/lib/sensors/orientation.d.ts +14 -0
- package/lib/sensors/sensor.d.ts +68 -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({
|
|
@@ -775,10 +777,35 @@ const filter = (predicate) => (src) => linkedSignal({
|
|
|
775
777
|
},
|
|
776
778
|
});
|
|
777
779
|
/** tap into the value */
|
|
778
|
-
const tap = (fn) => (src) => {
|
|
779
|
-
effect(() => fn(src())
|
|
780
|
+
const tap = (fn, injector) => (src) => {
|
|
781
|
+
effect(() => fn(src()), {
|
|
782
|
+
injector,
|
|
783
|
+
});
|
|
780
784
|
return src;
|
|
781
785
|
};
|
|
786
|
+
/**
|
|
787
|
+
* Like {@link filter}, but emits `initial` until a value passes the predicate
|
|
788
|
+
* for the first time. Avoids the `T | undefined` return type.
|
|
789
|
+
*/
|
|
790
|
+
const filterWith = (predicate, initial) => (src) => linkedSignal({
|
|
791
|
+
source: src,
|
|
792
|
+
computation: (next, prev) => predicate(next) ? next : (prev?.value ?? initial),
|
|
793
|
+
});
|
|
794
|
+
/** Emits `initial` first, then mirrors source. */
|
|
795
|
+
const startWith = (initial) => (src) => linkedSignal({
|
|
796
|
+
source: src,
|
|
797
|
+
computation: (next, prev) => (prev === undefined ? initial : next),
|
|
798
|
+
});
|
|
799
|
+
/** Emits `[prev, curr]` pairs. The first emission has prev = undefined. */
|
|
800
|
+
const pairwise = () => (src) => linkedSignal({
|
|
801
|
+
source: src,
|
|
802
|
+
computation: (next, prev) => [prev?.source, next],
|
|
803
|
+
});
|
|
804
|
+
/** Reduce-like accumulator across emissions. */
|
|
805
|
+
const scan = (reducer, seed) => (src) => linkedSignal({
|
|
806
|
+
source: src,
|
|
807
|
+
computation: (next, prev) => reducer(prev?.value ?? seed, next),
|
|
808
|
+
});
|
|
782
809
|
|
|
783
810
|
/**
|
|
784
811
|
* Decorate any `Signal<T>` with a chainable `.pipe(...)` method.
|
|
@@ -876,28 +903,22 @@ function piped(initial, opt) {
|
|
|
876
903
|
function pooled({ create, reset, computation, ...opt }) {
|
|
877
904
|
let other = opt.eager ? create() : undefined;
|
|
878
905
|
let current = opt.eager ? create() : undefined;
|
|
879
|
-
|
|
906
|
+
// only relevant for eager mode: the pre-allocated `current` is fresh until its first demote
|
|
880
907
|
let currentFresh = opt.eager;
|
|
881
908
|
return computed(() => {
|
|
882
|
-
|
|
883
|
-
let nextFresh;
|
|
884
|
-
if (other !== undefined) {
|
|
885
|
-
next = other;
|
|
886
|
-
nextFresh = !!otherFresh;
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
next = untracked(() => create());
|
|
890
|
-
nextFresh = true;
|
|
891
|
-
}
|
|
909
|
+
const next = other ?? untracked(() => create());
|
|
892
910
|
if (current !== undefined) {
|
|
893
|
-
|
|
894
|
-
|
|
911
|
+
if (currentFresh) {
|
|
912
|
+
other = current;
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guarded by the `current !== undefined` check above
|
|
916
|
+
other = untracked(() => reset(current)) ?? current;
|
|
917
|
+
}
|
|
895
918
|
}
|
|
896
919
|
current = next;
|
|
897
|
-
// the buffer is about to be mutated by `computation`, so it's no longer fresh
|
|
898
920
|
currentFresh = false;
|
|
899
|
-
|
|
900
|
-
return computation(clean);
|
|
921
|
+
return computation(next);
|
|
901
922
|
}, opt);
|
|
902
923
|
}
|
|
903
924
|
|
|
@@ -938,6 +959,87 @@ function pooledMap(optOrComputation, signalOpt) {
|
|
|
938
959
|
return pooled(toPooledOptions(optOrComputation, createEmptyMap, resetClearable, signalOpt));
|
|
939
960
|
}
|
|
940
961
|
|
|
962
|
+
const EVENTS = [
|
|
963
|
+
'chargingchange',
|
|
964
|
+
'levelchange',
|
|
965
|
+
'chargingtimechange',
|
|
966
|
+
'dischargingtimechange',
|
|
967
|
+
];
|
|
968
|
+
/**
|
|
969
|
+
* Creates a read-only signal that tracks the system battery status using the
|
|
970
|
+
* Battery Status API. Returns `null` until the underlying `getBattery()`
|
|
971
|
+
* promise resolves, or permanently when the API is unsupported (Firefox /
|
|
972
|
+
* Safari at the time of writing). SSR-safe.
|
|
973
|
+
*/
|
|
974
|
+
function batteryStatus(debugName = 'batteryStatus') {
|
|
975
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
976
|
+
typeof navigator === 'undefined' ||
|
|
977
|
+
typeof navigator.getBattery !== 'function') {
|
|
978
|
+
return computed(() => null, { debugName });
|
|
979
|
+
}
|
|
980
|
+
const state = signal(null, { debugName });
|
|
981
|
+
const abortController = new AbortController();
|
|
982
|
+
inject(DestroyRef).onDestroy(() => abortController.abort());
|
|
983
|
+
navigator.getBattery().then((battery) => {
|
|
984
|
+
if (abortController.signal.aborted)
|
|
985
|
+
return;
|
|
986
|
+
const read = () => ({
|
|
987
|
+
level: battery.level,
|
|
988
|
+
charging: battery.charging,
|
|
989
|
+
chargingTime: battery.chargingTime,
|
|
990
|
+
dischargingTime: battery.dischargingTime,
|
|
991
|
+
});
|
|
992
|
+
const onChange = () => state.set(read());
|
|
993
|
+
state.set(read());
|
|
994
|
+
for (const ev of EVENTS) {
|
|
995
|
+
battery.addEventListener(ev, onChange, {
|
|
996
|
+
signal: abortController.signal,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
return state.asReadonly();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Creates a read-only signal mirroring the system clipboard contents.
|
|
1005
|
+
*
|
|
1006
|
+
* The signal value starts empty and updates whenever a `copy` event fires on
|
|
1007
|
+
* the document (or {@link ClipboardSignal.copy} is invoked from this app).
|
|
1008
|
+
* SSR-safe — returns `''` and `isSupported: false` on the server.
|
|
1009
|
+
*
|
|
1010
|
+
* Note: read access requires the Clipboard API and an active permission grant
|
|
1011
|
+
* in browsers that gate it. Errors from `navigator.clipboard.readText` are
|
|
1012
|
+
* swallowed silently to keep the signal value stable.
|
|
1013
|
+
*/
|
|
1014
|
+
function clipboard(debugName = 'clipboard') {
|
|
1015
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
1016
|
+
typeof navigator === 'undefined' ||
|
|
1017
|
+
!navigator.clipboard) {
|
|
1018
|
+
const sig = computed(() => '', { debugName });
|
|
1019
|
+
sig.copy = () => Promise.resolve();
|
|
1020
|
+
sig.isSupported = computed(() => false);
|
|
1021
|
+
return sig;
|
|
1022
|
+
}
|
|
1023
|
+
const state = signal('', { debugName });
|
|
1024
|
+
const refresh = () => {
|
|
1025
|
+
navigator.clipboard.readText().then((value) => state.set(value), () => {
|
|
1026
|
+
// permission denied / focus required — ignore
|
|
1027
|
+
});
|
|
1028
|
+
};
|
|
1029
|
+
const abortController = new AbortController();
|
|
1030
|
+
const onCopy = () => refresh();
|
|
1031
|
+
document.addEventListener('copy', onCopy, { signal: abortController.signal });
|
|
1032
|
+
document.addEventListener('cut', onCopy, { signal: abortController.signal });
|
|
1033
|
+
inject(DestroyRef).onDestroy(() => abortController.abort());
|
|
1034
|
+
const sig = state.asReadonly();
|
|
1035
|
+
sig.copy = async (value) => {
|
|
1036
|
+
await navigator.clipboard.writeText(value);
|
|
1037
|
+
state.set(value);
|
|
1038
|
+
};
|
|
1039
|
+
sig.isSupported = computed(() => true);
|
|
1040
|
+
return sig;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
941
1043
|
function observerSupported$1() {
|
|
942
1044
|
return typeof ResizeObserver !== 'undefined';
|
|
943
1045
|
}
|
|
@@ -1138,6 +1240,167 @@ function elementVisibility(target = inject(ElementRef), opt) {
|
|
|
1138
1240
|
return base;
|
|
1139
1241
|
}
|
|
1140
1242
|
|
|
1243
|
+
function unwrap$1(target) {
|
|
1244
|
+
if (!target)
|
|
1245
|
+
return null;
|
|
1246
|
+
return target instanceof ElementRef ? target.nativeElement : target;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Creates a read-only signal that tracks whether the focused element is the
|
|
1250
|
+
* target or a descendant of it. Mirrors the CSS `:focus-within` pseudo-class.
|
|
1251
|
+
*
|
|
1252
|
+
* Defaults `target` to the current `ElementRef` so it can be used inline in a
|
|
1253
|
+
* component's `class` field. SSR-safe — returns a constant `false` signal on
|
|
1254
|
+
* the server.
|
|
1255
|
+
*/
|
|
1256
|
+
function focusWithin(target = inject(ElementRef)) {
|
|
1257
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
1258
|
+
return computed(() => false, { debugName: 'focusWithin' });
|
|
1259
|
+
}
|
|
1260
|
+
const state = signal(false, { debugName: 'focusWithin' });
|
|
1261
|
+
const attach = (el) => {
|
|
1262
|
+
state.set(el.contains(document.activeElement));
|
|
1263
|
+
const abortController = new AbortController();
|
|
1264
|
+
el.addEventListener('focusin', () => state.set(true), {
|
|
1265
|
+
signal: abortController.signal,
|
|
1266
|
+
});
|
|
1267
|
+
el.addEventListener('focusout', () => {
|
|
1268
|
+
// Defer so `document.activeElement` reflects the focus move.
|
|
1269
|
+
queueMicrotask(() => state.set(el.contains(document.activeElement)));
|
|
1270
|
+
}, { signal: abortController.signal });
|
|
1271
|
+
return () => abortController.abort();
|
|
1272
|
+
};
|
|
1273
|
+
if (isSignal(target)) {
|
|
1274
|
+
const targetSig = target;
|
|
1275
|
+
effect((cleanup) => {
|
|
1276
|
+
const el = unwrap$1(targetSig());
|
|
1277
|
+
if (!el) {
|
|
1278
|
+
state.set(false);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
cleanup(attach(el));
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
const el = unwrap$1(target);
|
|
1286
|
+
if (el) {
|
|
1287
|
+
const detach = attach(el);
|
|
1288
|
+
inject(DestroyRef).onDestroy(detach);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return state.asReadonly();
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Creates a read-only signal that exposes the current geolocation position.
|
|
1296
|
+
*
|
|
1297
|
+
* The returned signal carries `error` and `loading` sub-signals for permission
|
|
1298
|
+
* failures and the in-flight initial fetch respectively. SSR-safe — on the
|
|
1299
|
+
* server the position is `null`, loading is `false`, and no API calls are made.
|
|
1300
|
+
*
|
|
1301
|
+
* @example
|
|
1302
|
+
* ```ts
|
|
1303
|
+
* const where = geolocation({ watch: true, enableHighAccuracy: true });
|
|
1304
|
+
* effect(() => console.log(where()?.coords, where.error()));
|
|
1305
|
+
* ```
|
|
1306
|
+
*/
|
|
1307
|
+
function geolocation(opt) {
|
|
1308
|
+
if (isPlatformServer(inject(PLATFORM_ID)) || typeof navigator === 'undefined' || !navigator.geolocation) {
|
|
1309
|
+
const sig = computed(() => null, {
|
|
1310
|
+
debugName: opt?.debugName ?? 'geolocation',
|
|
1311
|
+
});
|
|
1312
|
+
sig.error = computed(() => null);
|
|
1313
|
+
sig.loading = computed(() => false);
|
|
1314
|
+
return sig;
|
|
1315
|
+
}
|
|
1316
|
+
const position = signal(null, {
|
|
1317
|
+
debugName: opt?.debugName ?? 'geolocation',
|
|
1318
|
+
});
|
|
1319
|
+
const error = signal(null);
|
|
1320
|
+
const loading = signal(true);
|
|
1321
|
+
const onSuccess = (p) => {
|
|
1322
|
+
position.set(p);
|
|
1323
|
+
error.set(null);
|
|
1324
|
+
loading.set(false);
|
|
1325
|
+
};
|
|
1326
|
+
const onError = (e) => {
|
|
1327
|
+
error.set(e);
|
|
1328
|
+
loading.set(false);
|
|
1329
|
+
};
|
|
1330
|
+
if (opt?.watch) {
|
|
1331
|
+
const watchId = navigator.geolocation.watchPosition(onSuccess, onError, opt);
|
|
1332
|
+
inject(DestroyRef).onDestroy(() => navigator.geolocation.clearWatch(watchId));
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
navigator.geolocation.getCurrentPosition(onSuccess, onError, opt);
|
|
1336
|
+
}
|
|
1337
|
+
const sig = position.asReadonly();
|
|
1338
|
+
sig.error = error.asReadonly();
|
|
1339
|
+
sig.loading = loading.asReadonly();
|
|
1340
|
+
return sig;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const DEFAULT_EVENTS = [
|
|
1344
|
+
'mousemove',
|
|
1345
|
+
'keydown',
|
|
1346
|
+
'touchstart',
|
|
1347
|
+
'scroll',
|
|
1348
|
+
'visibilitychange',
|
|
1349
|
+
];
|
|
1350
|
+
const serverDate$1 = new Date();
|
|
1351
|
+
/**
|
|
1352
|
+
* Creates a read-only signal that flips to `true` after a window of user
|
|
1353
|
+
* inactivity. Any of the configured `events` (default: pointer/keyboard/scroll
|
|
1354
|
+
* activity) resets the timer and flips the signal back to `false`.
|
|
1355
|
+
*
|
|
1356
|
+
* SSR-safe — always `false` with a frozen `since` date on the server.
|
|
1357
|
+
*/
|
|
1358
|
+
function idle(opt) {
|
|
1359
|
+
if (isPlatformServer(inject(PLATFORM_ID))) {
|
|
1360
|
+
const sig = computed(() => false, {
|
|
1361
|
+
debugName: opt?.debugName ?? 'idle',
|
|
1362
|
+
});
|
|
1363
|
+
sig.since = computed(() => serverDate$1);
|
|
1364
|
+
return sig;
|
|
1365
|
+
}
|
|
1366
|
+
const ms = opt?.ms ?? 60_000;
|
|
1367
|
+
const events = opt?.events ?? DEFAULT_EVENTS;
|
|
1368
|
+
const state = signal(false, { debugName: opt?.debugName ?? 'idle' });
|
|
1369
|
+
const since = signal(new Date());
|
|
1370
|
+
let timer;
|
|
1371
|
+
const goIdle = () => {
|
|
1372
|
+
if (state())
|
|
1373
|
+
return;
|
|
1374
|
+
state.set(true);
|
|
1375
|
+
since.set(new Date());
|
|
1376
|
+
};
|
|
1377
|
+
const reset = () => {
|
|
1378
|
+
if (timer)
|
|
1379
|
+
clearTimeout(timer);
|
|
1380
|
+
if (state()) {
|
|
1381
|
+
state.set(false);
|
|
1382
|
+
since.set(new Date());
|
|
1383
|
+
}
|
|
1384
|
+
timer = setTimeout(goIdle, ms);
|
|
1385
|
+
};
|
|
1386
|
+
const abortController = new AbortController();
|
|
1387
|
+
for (const ev of events) {
|
|
1388
|
+
window.addEventListener(ev, reset, {
|
|
1389
|
+
passive: true,
|
|
1390
|
+
signal: abortController.signal,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
timer = setTimeout(goIdle, ms);
|
|
1394
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1395
|
+
if (timer)
|
|
1396
|
+
clearTimeout(timer);
|
|
1397
|
+
abortController.abort();
|
|
1398
|
+
});
|
|
1399
|
+
const sig = state.asReadonly();
|
|
1400
|
+
sig.since = since.asReadonly();
|
|
1401
|
+
return sig;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1141
1404
|
/**
|
|
1142
1405
|
* Creates a read-only signal that reactively tracks whether a CSS media query
|
|
1143
1406
|
* string currently matches.
|
|
@@ -1299,26 +1562,41 @@ function throttled(initial, opt) {
|
|
|
1299
1562
|
*/
|
|
1300
1563
|
function throttle(source, opt) {
|
|
1301
1564
|
const ms = opt?.ms ?? 0;
|
|
1565
|
+
const leading = opt?.leading ?? false;
|
|
1566
|
+
const trailing = opt?.trailing ?? true;
|
|
1302
1567
|
const trigger = signal(false);
|
|
1568
|
+
const fire = () => trigger.update((c) => !c);
|
|
1303
1569
|
let timeout;
|
|
1570
|
+
let pendingTrailing = false;
|
|
1304
1571
|
try {
|
|
1305
1572
|
const destroyRef = opt?.destroyRef ?? inject(DestroyRef, { optional: true });
|
|
1306
1573
|
destroyRef?.onDestroy(() => {
|
|
1307
1574
|
if (timeout)
|
|
1308
1575
|
clearTimeout(timeout);
|
|
1309
1576
|
timeout = undefined;
|
|
1577
|
+
pendingTrailing = false;
|
|
1310
1578
|
});
|
|
1311
1579
|
}
|
|
1312
1580
|
catch {
|
|
1313
1581
|
// not in injection context & no destroyRef provided opting out of cleanup
|
|
1314
1582
|
}
|
|
1315
1583
|
const tick = () => {
|
|
1316
|
-
if (timeout)
|
|
1584
|
+
if (!timeout) {
|
|
1585
|
+
if (leading)
|
|
1586
|
+
fire();
|
|
1587
|
+
else
|
|
1588
|
+
pendingTrailing = trailing;
|
|
1589
|
+
timeout = setTimeout(() => {
|
|
1590
|
+
timeout = undefined;
|
|
1591
|
+
if (trailing && pendingTrailing) {
|
|
1592
|
+
pendingTrailing = false;
|
|
1593
|
+
fire();
|
|
1594
|
+
}
|
|
1595
|
+
}, ms);
|
|
1317
1596
|
return;
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
}, ms);
|
|
1597
|
+
}
|
|
1598
|
+
if (trailing)
|
|
1599
|
+
pendingTrailing = true;
|
|
1322
1600
|
};
|
|
1323
1601
|
const set = (value) => {
|
|
1324
1602
|
source.set(value);
|
|
@@ -1471,6 +1749,37 @@ function networkStatus(debugName = 'networkStatus') {
|
|
|
1471
1749
|
return sig;
|
|
1472
1750
|
}
|
|
1473
1751
|
|
|
1752
|
+
const SSR_FALLBACK = {
|
|
1753
|
+
angle: 0,
|
|
1754
|
+
type: 'portrait-primary',
|
|
1755
|
+
};
|
|
1756
|
+
/**
|
|
1757
|
+
* Creates a read-only signal that tracks `screen.orientation`.
|
|
1758
|
+
*
|
|
1759
|
+
* SSR-safe — returns a constant `portrait-primary / 0°` signal on the server
|
|
1760
|
+
* and in environments without `screen.orientation` support.
|
|
1761
|
+
*/
|
|
1762
|
+
function orientation(debugName = 'orientation') {
|
|
1763
|
+
if (isPlatformServer(inject(PLATFORM_ID)) ||
|
|
1764
|
+
typeof screen === 'undefined' ||
|
|
1765
|
+
!screen.orientation) {
|
|
1766
|
+
return computed(() => SSR_FALLBACK, { debugName });
|
|
1767
|
+
}
|
|
1768
|
+
const so = screen.orientation;
|
|
1769
|
+
const read = () => ({
|
|
1770
|
+
angle: so.angle,
|
|
1771
|
+
type: so.type,
|
|
1772
|
+
});
|
|
1773
|
+
const state = signal(read(), {
|
|
1774
|
+
debugName,
|
|
1775
|
+
equal: (a, b) => a.angle === b.angle && a.type === b.type,
|
|
1776
|
+
});
|
|
1777
|
+
const onChange = () => state.set(read());
|
|
1778
|
+
so.addEventListener('change', onChange);
|
|
1779
|
+
inject(DestroyRef).onDestroy(() => so.removeEventListener('change', onChange));
|
|
1780
|
+
return state.asReadonly();
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1474
1783
|
/**
|
|
1475
1784
|
* Creates a read-only signal that tracks the page's visibility state.
|
|
1476
1785
|
*
|
|
@@ -1687,35 +1996,42 @@ function windowSize(opt) {
|
|
|
1687
1996
|
* @internal
|
|
1688
1997
|
*/
|
|
1689
1998
|
function sensor(type, options) {
|
|
1999
|
+
const opts = options;
|
|
1690
2000
|
switch (type) {
|
|
1691
2001
|
case 'mousePosition':
|
|
1692
|
-
return mousePosition(
|
|
2002
|
+
return mousePosition(opts);
|
|
1693
2003
|
case 'networkStatus':
|
|
1694
|
-
return networkStatus(
|
|
2004
|
+
return networkStatus(opts?.debugName);
|
|
1695
2005
|
case 'pageVisibility':
|
|
1696
|
-
return pageVisibility(
|
|
2006
|
+
return pageVisibility(opts?.debugName);
|
|
1697
2007
|
case 'darkMode':
|
|
1698
2008
|
case 'dark-mode':
|
|
1699
|
-
return prefersDarkMode(
|
|
2009
|
+
return prefersDarkMode(opts?.debugName);
|
|
1700
2010
|
case 'reducedMotion':
|
|
1701
2011
|
case 'reduced-motion':
|
|
1702
|
-
return prefersReducedMotion(
|
|
1703
|
-
case 'mediaQuery':
|
|
1704
|
-
|
|
1705
|
-
return mediaQuery(opt.query, opt.debugName);
|
|
1706
|
-
}
|
|
2012
|
+
return prefersReducedMotion(opts?.debugName);
|
|
2013
|
+
case 'mediaQuery':
|
|
2014
|
+
return mediaQuery(opts.query, opts.debugName);
|
|
1707
2015
|
case 'windowSize':
|
|
1708
|
-
return windowSize(
|
|
2016
|
+
return windowSize(opts);
|
|
1709
2017
|
case 'scrollPosition':
|
|
1710
|
-
return scrollPosition(
|
|
1711
|
-
case 'elementVisibility':
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
case '
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2018
|
+
return scrollPosition(opts);
|
|
2019
|
+
case 'elementVisibility':
|
|
2020
|
+
return elementVisibility(opts?.target, opts);
|
|
2021
|
+
case 'elementSize':
|
|
2022
|
+
return elementSize(opts?.target, opts);
|
|
2023
|
+
case 'geolocation':
|
|
2024
|
+
return geolocation(opts);
|
|
2025
|
+
case 'clipboard':
|
|
2026
|
+
return clipboard(opts?.debugName);
|
|
2027
|
+
case 'orientation':
|
|
2028
|
+
return orientation(opts?.debugName);
|
|
2029
|
+
case 'batteryStatus':
|
|
2030
|
+
return batteryStatus(opts?.debugName);
|
|
2031
|
+
case 'idle':
|
|
2032
|
+
return idle(opts);
|
|
2033
|
+
case 'focusWithin':
|
|
2034
|
+
return focusWithin(opts?.target);
|
|
1719
2035
|
default:
|
|
1720
2036
|
throw new Error(`Unknown sensor type: ${type}`);
|
|
1721
2037
|
}
|
|
@@ -1727,6 +2043,49 @@ function sensors(track, opt) {
|
|
|
1727
2043
|
}, {});
|
|
1728
2044
|
}
|
|
1729
2045
|
|
|
2046
|
+
function unwrap(t) {
|
|
2047
|
+
if (!t)
|
|
2048
|
+
return null;
|
|
2049
|
+
return t instanceof ElementRef ? t.nativeElement : t;
|
|
2050
|
+
}
|
|
2051
|
+
function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
|
|
2052
|
+
const project = typeof projectOrOpt === 'function' ? projectOrOpt : undefined;
|
|
2053
|
+
const opt = typeof projectOrOpt === 'function' ? maybeOpt : projectOrOpt;
|
|
2054
|
+
const injector = opt?.injector ?? inject(Injector);
|
|
2055
|
+
if (isPlatformServer(injector.get(PLATFORM_ID))) {
|
|
2056
|
+
return computed(() => initial, { debugName: opt?.debugName });
|
|
2057
|
+
}
|
|
2058
|
+
const state = signal(initial, {
|
|
2059
|
+
debugName: opt?.debugName,
|
|
2060
|
+
});
|
|
2061
|
+
const handler = (event) => {
|
|
2062
|
+
if (project)
|
|
2063
|
+
state.set(project(event));
|
|
2064
|
+
else
|
|
2065
|
+
state.set(event);
|
|
2066
|
+
};
|
|
2067
|
+
const { destroyRef: providedDestroyRef, ...listenerOpts } = opt ?? {};
|
|
2068
|
+
if (isSignal(target)) {
|
|
2069
|
+
const targetSig = target;
|
|
2070
|
+
effect((cleanup) => {
|
|
2071
|
+
const resolved = unwrap(targetSig());
|
|
2072
|
+
if (!resolved)
|
|
2073
|
+
return;
|
|
2074
|
+
resolved.addEventListener(eventName, handler, listenerOpts);
|
|
2075
|
+
cleanup(() => resolved.removeEventListener(eventName, handler, listenerOpts));
|
|
2076
|
+
}, { injector });
|
|
2077
|
+
}
|
|
2078
|
+
else {
|
|
2079
|
+
const resolved = unwrap(target);
|
|
2080
|
+
if (resolved) {
|
|
2081
|
+
resolved.addEventListener(eventName, handler, listenerOpts);
|
|
2082
|
+
const destroyRef = providedDestroyRef ?? injector.get(DestroyRef);
|
|
2083
|
+
destroyRef.onDestroy(() => resolved.removeEventListener(eventName, handler, listenerOpts));
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
return untracked(() => state.asReadonly());
|
|
2087
|
+
}
|
|
2088
|
+
|
|
1730
2089
|
const IS_STORE = Symbol('MMSTACK::IS_STORE');
|
|
1731
2090
|
const PROXY_CACHE = new WeakMap();
|
|
1732
2091
|
const SIGNAL_FN_PROP = new Set([
|
|
@@ -2219,14 +2578,11 @@ function generateDeterministicID() {
|
|
|
2219
2578
|
*
|
|
2220
2579
|
* @template T - The type of the WritableSignal
|
|
2221
2580
|
* @param sig - The WritableSignal to synchronize across tabs
|
|
2222
|
-
* @param opt -
|
|
2223
|
-
* @param opt.id - Explicit channel ID for synchronization.
|
|
2224
|
-
* a deterministic ID is generated based on the call site.
|
|
2225
|
-
* Use explicit IDs in production for reliability.
|
|
2581
|
+
* @param opt - configuration object
|
|
2582
|
+
* @param opt.id - Explicit channel ID for synchronization.
|
|
2226
2583
|
*
|
|
2227
2584
|
* @returns The same WritableSignal instance, now synchronized across tabs
|
|
2228
2585
|
*
|
|
2229
|
-
* @throws {Error} When deterministic ID generation fails and no explicit ID is provided
|
|
2230
2586
|
*
|
|
2231
2587
|
* @example
|
|
2232
2588
|
* ```typescript
|
|
@@ -2250,7 +2606,7 @@ function generateDeterministicID() {
|
|
|
2250
2606
|
function tabSync(sig, opt) {
|
|
2251
2607
|
if (isPlatformServer(inject(PLATFORM_ID)))
|
|
2252
2608
|
return sig;
|
|
2253
|
-
const id = opt?.id
|
|
2609
|
+
const id = typeof opt === 'string' ? opt : (opt?.id ?? generateDeterministicID());
|
|
2254
2610
|
const bus = inject(MessageBus);
|
|
2255
2611
|
const { unsub, post } = bus.subscribe(id, (next) => sig.set(next));
|
|
2256
2612
|
let first = false;
|
|
@@ -2382,8 +2738,13 @@ function getSignalEquality(sig) {
|
|
|
2382
2738
|
* console.log('Can undo:', name.canUndo()); // false
|
|
2383
2739
|
* ```
|
|
2384
2740
|
*/
|
|
2385
|
-
function withHistory(
|
|
2386
|
-
const equal = opt?.equal ??
|
|
2741
|
+
function withHistory(sourceOrValue, opt) {
|
|
2742
|
+
const equal = (opt?.equal ?? isSignal(sourceOrValue))
|
|
2743
|
+
? getSignalEquality(sourceOrValue)
|
|
2744
|
+
: Object.is;
|
|
2745
|
+
const source = isSignal(sourceOrValue)
|
|
2746
|
+
? sourceOrValue
|
|
2747
|
+
: signal(sourceOrValue);
|
|
2387
2748
|
const maxSize = opt?.maxSize ?? Infinity;
|
|
2388
2749
|
const history = mutable([], {
|
|
2389
2750
|
...opt,
|
|
@@ -2391,20 +2752,22 @@ function withHistory(source, opt) {
|
|
|
2391
2752
|
});
|
|
2392
2753
|
const redoArray = mutable([]);
|
|
2393
2754
|
const originalSet = source.set;
|
|
2755
|
+
const trim = (arr) => {
|
|
2756
|
+
if (arr.length < maxSize)
|
|
2757
|
+
return arr;
|
|
2758
|
+
if (opt?.cleanupStrategy === 'shift') {
|
|
2759
|
+
arr.shift();
|
|
2760
|
+
return arr;
|
|
2761
|
+
}
|
|
2762
|
+
return arr.slice(Math.floor(maxSize / 2));
|
|
2763
|
+
};
|
|
2394
2764
|
const set = (value) => {
|
|
2395
2765
|
const current = untracked(source);
|
|
2396
2766
|
if (equal(value, current))
|
|
2397
2767
|
return;
|
|
2398
2768
|
source.set(value);
|
|
2399
2769
|
history.mutate((c) => {
|
|
2400
|
-
|
|
2401
|
-
if (opt?.cleanupStrategy === 'shift') {
|
|
2402
|
-
c.shift();
|
|
2403
|
-
}
|
|
2404
|
-
else {
|
|
2405
|
-
c = c.slice(Math.floor(maxSize / 2));
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2770
|
+
c = trim(c);
|
|
2408
2771
|
c.push(current);
|
|
2409
2772
|
return c;
|
|
2410
2773
|
});
|
|
@@ -2428,7 +2791,11 @@ function withHistory(source, opt) {
|
|
|
2428
2791
|
return;
|
|
2429
2792
|
originalSet.call(source, valueToRestore);
|
|
2430
2793
|
history.inline((h) => h.pop());
|
|
2431
|
-
redoArray.
|
|
2794
|
+
redoArray.mutate((r) => {
|
|
2795
|
+
r = trim(r);
|
|
2796
|
+
r.push(valueForRedo);
|
|
2797
|
+
return r;
|
|
2798
|
+
});
|
|
2432
2799
|
};
|
|
2433
2800
|
internal.redo = () => {
|
|
2434
2801
|
const redoStack = untracked(redoArray);
|
|
@@ -2441,14 +2808,7 @@ function withHistory(source, opt) {
|
|
|
2441
2808
|
originalSet.call(source, valueToRestore);
|
|
2442
2809
|
redoArray.inline((r) => r.pop());
|
|
2443
2810
|
history.mutate((h) => {
|
|
2444
|
-
|
|
2445
|
-
if (opt?.cleanupStrategy === 'shift') {
|
|
2446
|
-
h.shift();
|
|
2447
|
-
}
|
|
2448
|
-
else {
|
|
2449
|
-
h = h.slice(Math.floor(maxSize / 2));
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2811
|
+
h = trim(h);
|
|
2452
2812
|
h.push(valueForUndo);
|
|
2453
2813
|
return h;
|
|
2454
2814
|
});
|
|
@@ -2467,5 +2827,5 @@ function withHistory(source, opt) {
|
|
|
2467
2827
|
* Generated bundle index. Do not edit.
|
|
2468
2828
|
*/
|
|
2469
2829
|
|
|
2470
|
-
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 };
|
|
2830
|
+
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 };
|
|
2471
2831
|
//# sourceMappingURL=mmstack-primitives.mjs.map
|