@mmstack/primitives 19.4.0 → 19.4.2

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.
Files changed (37) hide show
  1. package/README.md +8 -5
  2. package/fesm2022/mmstack-primitives.mjs +526 -242
  3. package/fesm2022/mmstack-primitives.mjs.map +1 -1
  4. package/index.d.ts +2 -2
  5. package/lib/chunked.d.ts +2 -2
  6. package/lib/concurrent/pausable.d.ts +3 -2
  7. package/lib/concurrent/start-transition.d.ts +5 -0
  8. package/lib/concurrent/transaction.d.ts +5 -0
  9. package/lib/effect/index.d.ts +1 -0
  10. package/lib/get-signal-equality.d.ts +1 -1
  11. package/lib/mutable.d.ts +1 -1
  12. package/lib/pipeable/{pipeble.d.ts → pipeable.d.ts} +1 -1
  13. package/lib/pipeable/public_api.d.ts +1 -1
  14. package/lib/pipeable/types.d.ts +1 -1
  15. package/lib/sensors/battery-status.d.ts +2 -1
  16. package/lib/sensors/clipboard.d.ts +2 -1
  17. package/lib/sensors/element-size.d.ts +2 -4
  18. package/lib/sensors/element-visibility.d.ts +2 -4
  19. package/lib/sensors/focus-within.d.ts +2 -1
  20. package/lib/sensors/geolocation.d.ts +2 -3
  21. package/lib/sensors/idle.d.ts +6 -4
  22. package/lib/sensors/index.d.ts +1 -0
  23. package/lib/sensors/media-query.d.ts +4 -3
  24. package/lib/sensors/mouse-position.d.ts +6 -7
  25. package/lib/sensors/network-status.d.ts +8 -3
  26. package/lib/sensors/orientation.d.ts +9 -2
  27. package/lib/sensors/page-visibility.d.ts +4 -2
  28. package/lib/sensors/scroll-position.d.ts +10 -18
  29. package/lib/sensors/sensor-options.d.ts +25 -0
  30. package/lib/sensors/sensor.d.ts +15 -30
  31. package/lib/sensors/window-size.d.ts +3 -6
  32. package/lib/store/store.d.ts +4 -4
  33. package/lib/stored.d.ts +4 -3
  34. package/lib/{tabSync.d.ts → tab-sync.d.ts} +9 -4
  35. package/lib/throttled.d.ts +2 -0
  36. package/lib/to-writable.d.ts +4 -0
  37. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export * from './lib/chunked';
2
2
  export * from './lib/concurrent';
3
3
  export * from './lib/debounced';
4
4
  export * from './lib/derived';
5
- export { nestedEffect } from './lib/effect';
5
+ export { nestedEffect, type Frame } from './lib/effect';
6
6
  export * from './lib/keep-previous';
7
7
  export * from './lib/mappers';
8
8
  export * from './lib/mutable';
@@ -11,7 +11,7 @@ export * from './lib/pooled';
11
11
  export * from './lib/sensors';
12
12
  export * from './lib/store/public_api';
13
13
  export * from './lib/stored';
14
- export { tabSync } from './lib/tabSync';
14
+ export { tabSync } from './lib/tab-sync';
15
15
  export * from './lib/throttled';
16
16
  export * from './lib/to-writable';
17
17
  export * from './lib/until';
package/lib/chunked.d.ts CHANGED
@@ -22,7 +22,7 @@ export type CreateChunkedOptions<T> = {
22
22
  /**
23
23
  * Creates a new `Signal` that processes an array of items in time-sliced chunks. This is useful for handling large lists without blocking the main thread.
24
24
  *
25
- * The returned signal will initially contain the first `chunkSize` items from the source array. It will then schedule updates to include additional chunks of items based on the specified `duration`.
25
+ * The returned signal will initially contain the first `chunkSize` items from the source array. It will then schedule updates to include additional chunks of items based on the specified `delay`.
26
26
  *
27
27
  * @template T The type of items in the array.
28
28
  * @param source A `Signal` or a function that returns an array of items to be processed in chunks.
@@ -31,6 +31,6 @@ export type CreateChunkedOptions<T> = {
31
31
  *
32
32
  * @example
33
33
  * const largeList = signal(Array.from({ length: 1000 }, (_, i) => i));
34
- * const chunkedList = chunked(largeList, { chunkSize: 100, duration: 100 });
34
+ * const chunkedList = chunked(largeList, { chunkSize: 100, delay: 100 });
35
35
  */
36
36
  export declare function chunked<T>(source: Signal<T[]> | (() => T[]), options?: CreateChunkedOptions<T>): Signal<T[]>;
@@ -46,8 +46,9 @@ export declare function pausableEffect(effectFn: (registerCleanup: EffectCleanup
46
46
  /**
47
47
  * Like `signal`, but pausable. While paused, READS hold the last value; writes still land on the
48
48
  * underlying signal and surface on resume. Built on the `keepPrevious`/`hold` shape — a
49
- * `linkedSignal` gated on the pause predicate, with `set`/`update`/`asReadonly` forwarded to the
50
- * source signal. With no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false`
49
+ * `linkedSignal` gated on the pause predicate, with `set`/`update` forwarded to the source signal.
50
+ * `asReadonly()` returns the held (gated) view, so both views of the signal agree while paused.
51
+ * With no `pause` option it defaults to the ambient `PAUSED_CONTEXT`; `pause: false`
51
52
  * makes it a plain `signal` — no `linkedSignal` is created.
52
53
  *
53
54
  * NOTE: while paused, `set(x)` followed by a read returns the *held* (pre-pause) value, not `x` — the
@@ -16,5 +16,10 @@ export type TransitionRef = {
16
16
  *
17
17
  * Must be called in an injection context. This is the *async* generalization (Tier 2): it adds
18
18
  * no rendering cost and needs no fork — holding direct/sync readers is a separate, deferred tier.
19
+ *
20
+ * Caveat: work must go in flight by the first post-write render to be awaited. A loader that
21
+ * starts later (a debounced request signal, a chained/deferred resource) is not attributable to
22
+ * this transition — the no-async fallback will have already resolved `done`. Trigger such work
23
+ * eagerly inside `fn`, or coordinate it separately.
19
24
  */
20
25
  export declare function injectStartTransition(): (fn: () => void) => TransitionRef;
@@ -32,5 +32,10 @@ export type TransactionRef = {
32
32
  * The writes land on LIVE state immediately (so derived variables and connector requests see the
33
33
  * new values and refetch); only the *display* is held, via `scope.hold`. Must run in an injection
34
34
  * context.
35
+ *
36
+ * Caveat: work must go in flight by the first post-write render to be part of the transaction. A
37
+ * loader that starts later (a debounced request signal, a chained/deferred resource) is not
38
+ * attributable to it — the no-async fallback will have already committed and released the hold,
39
+ * after which `abort()` is a no-op. Trigger such work eagerly inside `fn`.
35
40
  */
36
41
  export declare function injectStartTransaction(): (fn: () => void) => TransactionRef;
@@ -1 +1,2 @@
1
+ export type { Frame } from './frame-stack';
1
2
  export * from './nested-effect';
@@ -1,5 +1,5 @@
1
1
  import { type Signal, type ValueEqualityFn } from '@angular/core';
2
2
  /**
3
- * @interal
3
+ * @internal
4
4
  */
5
5
  export declare function getSignalEquality<T>(sig: Signal<T>): ValueEqualityFn<T>;
package/lib/mutable.d.ts CHANGED
@@ -92,4 +92,4 @@ export declare function mutable<T>(initial: T, opt?: CreateSignalOptions<T>): Mu
92
92
  * myMutableSignal.mutate(x => x + 1); // This is safe.
93
93
  * }
94
94
  */
95
- export declare function isMutable<T = any>(value: WritableSignal<T>): value is MutableSignal<T>;
95
+ export declare function isMutable<T = unknown>(value: WritableSignal<T>): value is MutableSignal<T>;
@@ -10,7 +10,7 @@ import type { PipeableSignal, SignalValue } from './types';
10
10
  */
11
11
  export declare function pipeable<TSig extends Signal<any>>(signal: TSig): PipeableSignal<SignalValue<TSig>, TSig>;
12
12
  /**
13
- * Create a new **writable** signal and return it as a `PipableSignal`.
13
+ * Create a new **writable** signal and return it as a `PipeableSignal`.
14
14
  *
15
15
  * The returned value is a `WritableSignal<T>` with `.set`, `.update`, `.asReadonly`
16
16
  * still available (via intersection type), plus a chainable `.pipe(...)`.
@@ -1,3 +1,3 @@
1
1
  export * from './operators';
2
- export * from './pipeble';
2
+ export * from './pipeable';
3
3
  export { type PipeableSignal } from './types';
@@ -39,7 +39,7 @@ type SignalPipe<In> = {
39
39
  * @see {@link SignalPipe}
40
40
  * @example
41
41
  * ```ts
42
- * import { piped } from '@ngrx/signals';
42
+ * import { piped } from '@mmstack/primitives';
43
43
  *
44
44
  * const count = piped(1);
45
45
  *
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  export type BatteryStatus = {
3
4
  readonly level: number;
4
5
  readonly charging: boolean;
@@ -20,4 +21,4 @@ export type BatteryStatus = {
20
21
  * });
21
22
  * ```
22
23
  */
23
- export declare function batteryStatus(debugName?: string): Signal<BatteryStatus | null>;
24
+ export declare function batteryStatus(opt?: string | SensorRunOptions): Signal<BatteryStatus | null>;
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  export type ClipboardSignal = Signal<string> & {
3
4
  /**
4
5
  * Writes `value` to the system clipboard. Resolves once the write completes;
@@ -19,4 +20,4 @@ export type ClipboardSignal = Signal<string> & {
19
20
  * in browsers that gate it. Errors from `navigator.clipboard.readText` are
20
21
  * swallowed silently to keep the signal value stable.
21
22
  */
22
- export declare function clipboard(debugName?: string): ClipboardSignal;
23
+ export declare function clipboard(opt?: string | SensorRunOptions): ClipboardSignal;
@@ -1,4 +1,5 @@
1
1
  import { ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * Represents the size of an element.
4
5
  */
@@ -9,10 +10,7 @@ export interface ElementSize {
9
10
  /**
10
11
  * Options for configuring the `elementSize` sensor.
11
12
  */
12
- export type ElementSizeOptions = ResizeObserverOptions & {
13
- /** Optional debug name for the internal signal. */
14
- debugName?: string;
15
- };
13
+ export type ElementSizeOptions = ResizeObserverOptions & SensorRunOptions;
16
14
  export type ElementSizeSignal = Signal<ElementSize | undefined>;
17
15
  /**
18
16
  * Creates a read-only signal that tracks the size of a target DOM element.
@@ -1,12 +1,10 @@
1
1
  import { ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * Options for configuring the `elementVisibility` sensor, extending
4
5
  * standard `IntersectionObserverInit` options.
5
6
  */
6
- export type ElementVisibilityOptions = IntersectionObserverInit & {
7
- /** Optional debug name for the internal signal. */
8
- debugName?: string;
9
- };
7
+ export type ElementVisibilityOptions = IntersectionObserverInit & SensorRunOptions;
10
8
  export type ElementVisibilitySignal = Signal<IntersectionObserverEntry | undefined> & {
11
9
  readonly visible: Signal<boolean>;
12
10
  };
@@ -1,4 +1,5 @@
1
1
  import { ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  type FocusWithinTarget = ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
3
4
  /**
4
5
  * Creates a read-only signal that tracks whether the focused element is the
@@ -17,5 +18,5 @@ type FocusWithinTarget = ElementRef<Element> | Element | Signal<ElementRef<Eleme
17
18
  * }
18
19
  * ```
19
20
  */
20
- export declare function focusWithin(target?: FocusWithinTarget): Signal<boolean>;
21
+ export declare function focusWithin(target?: FocusWithinTarget, opt?: SensorRunOptions): Signal<boolean>;
21
22
  export {};
@@ -1,13 +1,12 @@
1
1
  import { type Signal } from '@angular/core';
2
- export type GeolocationOptions = PositionOptions & {
2
+ import { type SensorRunOptions } from './sensor-options';
3
+ export type GeolocationOptions = PositionOptions & SensorRunOptions & {
3
4
  /**
4
5
  * If `true`, uses `navigator.geolocation.watchPosition` and updates the
5
6
  * signal continuously. Otherwise a single `getCurrentPosition` call is made.
6
7
  * @default false
7
8
  */
8
9
  watch?: boolean;
9
- /** Optional debug name for the produced signal. */
10
- debugName?: string;
11
10
  };
12
11
  export type GeolocationSignal = Signal<GeolocationPosition | null> & {
13
12
  readonly error: Signal<GeolocationPositionError | null>;
@@ -1,5 +1,6 @@
1
1
  import { type Signal } from '@angular/core';
2
- export type IdleOptions = {
2
+ import { type SensorRunOptions } from './sensor-options';
3
+ export type IdleOptions = SensorRunOptions & {
3
4
  /**
4
5
  * Milliseconds of user inactivity before the signal flips to `true`.
5
6
  * @default 60_000
@@ -10,11 +11,12 @@ export type IdleOptions = {
10
11
  * @default ['mousemove','keydown','touchstart','scroll','visibilitychange']
11
12
  */
12
13
  events?: string[];
13
- /** Optional debug name for the produced signal. */
14
- debugName?: string;
15
14
  };
16
15
  export type IdleSignal = Signal<boolean> & {
17
- /** Timestamp of the last idle/active transition. */
16
+ /**
17
+ * Timestamp of the last idle/active transition. Before any transition has occurred it
18
+ * holds the sensor's creation time, not an actual transition.
19
+ */
18
20
  readonly since: Signal<Date>;
19
21
  };
20
22
  /**
@@ -1,5 +1,6 @@
1
1
  export * from './battery-status';
2
2
  export * from './clipboard';
3
+ export { type SensorRunOptions } from './sensor-options';
3
4
  export * from './element-size';
4
5
  export * from './element-visibility';
5
6
  export * from './focus-within';
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * Creates a read-only signal that reactively tracks whether a CSS media query
4
5
  * string currently matches.
@@ -43,7 +44,7 @@ import { type Signal } from '@angular/core';
43
44
  * }
44
45
  * ```
45
46
  */
46
- export declare function mediaQuery(query: string, debugName?: string): Signal<boolean>;
47
+ export declare function mediaQuery(query: string, opt?: string | SensorRunOptions): Signal<boolean>;
47
48
  /**
48
49
  * Creates a read-only signal that tracks the user's OS/browser preference
49
50
  * for a dark color scheme using the `(prefers-color-scheme: dark)` media query.
@@ -65,7 +66,7 @@ export declare function mediaQuery(query: string, debugName?: string): Signal<bo
65
66
  * });
66
67
  * ```
67
68
  */
68
- export declare function prefersDarkMode(debugName?: string): Signal<boolean>;
69
+ export declare function prefersDarkMode(opt?: string | SensorRunOptions): Signal<boolean>;
69
70
  /**
70
71
  * Creates a read-only signal that tracks the user's OS/browser preference
71
72
  * for reduced motion using the `(prefers-reduced-motion: reduce)` media query.
@@ -91,4 +92,4 @@ export declare function prefersDarkMode(debugName?: string): Signal<boolean>;
91
92
  * });
92
93
  * ```
93
94
  */
94
- export declare function prefersReducedMotion(debugName?: string): Signal<boolean>;
95
+ export declare function prefersReducedMotion(opt?: string | SensorRunOptions): Signal<boolean>;
@@ -1,4 +1,5 @@
1
1
  import { ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  type MousePosition = {
3
4
  x: number;
4
5
  y: number;
@@ -6,13 +7,15 @@ type MousePosition = {
6
7
  /**
7
8
  * Options for configuring the `mousePosition` sensor.
8
9
  */
9
- export type MousePositionOptions = {
10
+ export type MousePositionOptions = SensorRunOptions & {
10
11
  /**
11
12
  * The target element to listen for mouse movements on.
12
- * Can be `window`, `document`, an `HTMLElement`, or an `ElementRef<HTMLElement>`.
13
+ * Can be `window`, `document`, an `HTMLElement`, an `ElementRef<HTMLElement>`, or a
14
+ * `Signal` resolving to one (e.g. a `viewChild` result) — listeners re-attach when the
15
+ * signal's element changes, and nothing is tracked while it is `null`/`undefined`.
13
16
  * @default window
14
17
  */
15
- target?: Window | Document | HTMLElement | ElementRef<HTMLElement>;
18
+ target?: Window | Document | HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | null | undefined>;
16
19
  /**
17
20
  * Defines the coordinate system for the reported position.
18
21
  * - `'client'`: Coordinates relative to the viewport (`clientX`, `clientY`).
@@ -26,10 +29,6 @@ export type MousePositionOptions = {
26
29
  * @default false
27
30
  */
28
31
  touch?: boolean;
29
- /**
30
- * Optional debug name for the internal signal.
31
- */
32
- debugName?: string;
33
32
  /**
34
33
  * Optional delay in milliseconds to throttle the updates.
35
34
  * @default 100
@@ -1,10 +1,14 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * A specialized Signal that tracks network status.
4
5
  * It's a boolean signal with an attached `since` signal.
5
6
  */
6
7
  export type NetworkStatusSignal = Signal<boolean> & {
7
- /** A signal tracking the timestamp of the last status change. */
8
+ /**
9
+ * A signal tracking the timestamp of the last status change. Before any change has
10
+ * occurred it holds the sensor's creation time, not an actual transition.
11
+ */
8
12
  readonly since: Signal<Date>;
9
13
  };
10
14
  /**
@@ -14,7 +18,8 @@ export type NetworkStatusSignal = Signal<boolean> & {
14
18
  * An additional `since` signal is attached, tracking when the status last changed.
15
19
  * It's SSR-safe and automatically cleans up its event listeners.
16
20
  *
17
- * @param debugName Optional debug name for the signal.
21
+ * @param opt Optional debug name for the signal, or a {@link SensorRunOptions} object
22
+ * (with an optional `injector` for creation outside an injection context).
18
23
  * @returns A `NetworkStatusSignal` instance.
19
24
  *
20
25
  * @example
@@ -25,4 +30,4 @@ export type NetworkStatusSignal = Signal<boolean> & {
25
30
  * });
26
31
  * ```
27
32
  */
28
- export declare function networkStatus(debugName?: string): NetworkStatusSignal;
33
+ export declare function networkStatus(opt?: string | SensorRunOptions): NetworkStatusSignal;
@@ -1,10 +1,17 @@
1
1
  import { type Signal } from '@angular/core';
2
- export type ScreenOrientation = {
2
+ import { type SensorRunOptions } from './sensor-options';
3
+ export type ScreenOrientationState = {
3
4
  /** Angle in degrees relative to the natural orientation. */
4
5
  readonly angle: number;
5
6
  /** One of the four `OrientationType` strings. */
6
7
  readonly type: OrientationType;
7
8
  };
9
+ /**
10
+ * @deprecated Use {@link ScreenOrientationState} instead — this name shadows the DOM's global
11
+ * `ScreenOrientation` interface in any module that imports it, silently changing the meaning of
12
+ * `screen.orientation`-related typings there.
13
+ */
14
+ export type ScreenOrientation = ScreenOrientationState;
8
15
  /**
9
16
  * Creates a read-only signal that tracks `screen.orientation`.
10
17
  *
@@ -20,4 +27,4 @@ export type ScreenOrientation = {
20
27
  * });
21
28
  * ```
22
29
  */
23
- export declare function orientation(debugName?: string): Signal<ScreenOrientation>;
30
+ export declare function orientation(opt?: string | SensorRunOptions): Signal<ScreenOrientationState>;
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * Creates a read-only signal that tracks the page's visibility state.
4
5
  *
@@ -7,7 +8,8 @@ import { type Signal } from '@angular/core';
7
8
  * The primitive is SSR-safe and automatically cleans up its event listeners
8
9
  * when the creating context is destroyed.
9
10
  *
10
- * @param debugName Optional debug name for the signal.
11
+ * @param opt Optional debug name for the signal, or a {@link SensorRunOptions} object
12
+ * (with an optional `injector` for creation outside an injection context).
11
13
  * @returns A read-only `Signal<DocumentVisibilityState>`. On the server,
12
14
  * it returns a static signal with a value of `'visible'`.
13
15
  *
@@ -35,4 +37,4 @@ import { type Signal } from '@angular/core';
35
37
  * }
36
38
  * ```
37
39
  */
38
- export declare function pageVisibility(debugName?: string): Signal<DocumentVisibilityState>;
40
+ export declare function pageVisibility(opt?: string | SensorRunOptions): Signal<DocumentVisibilityState>;
@@ -1,5 +1,6 @@
1
1
  import { ElementRef, // Used for SSR fallback
2
2
  type Signal } from '@angular/core';
3
+ import { type SensorRunOptions } from './sensor-options';
3
4
  /**
4
5
  * Represents the scroll position.
5
6
  */
@@ -12,21 +13,21 @@ export type ScrollPosition = {
12
13
  /**
13
14
  * Options for configuring the `scrollPosition` sensor.
14
15
  */
15
- export type ScrollPositionOptions = {
16
+ export type ScrollPositionOptions = SensorRunOptions & {
16
17
  /**
17
18
  * The target to listen for scroll events on.
18
- * Can be `window` (for page scroll) or an `HTMLElement`/`ElementRef<HTMLElement>`.
19
+ * Can be `window` (for page scroll), an `HTMLElement`/`ElementRef<HTMLElement>`, or a
20
+ * `Signal` resolving to one (e.g. a `viewChild` result) — listeners re-attach when the
21
+ * signal's element changes, and nothing is tracked while it is `null`/`undefined`.
19
22
  * @default window
20
23
  */
21
- target?: Window | HTMLElement | ElementRef<HTMLElement>;
24
+ target?: Window | HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | null | undefined>;
22
25
  /**
23
26
  * Optional delay in milliseconds to throttle the updates.
24
27
  * Scroll events can fire very rapidly.
25
28
  * @default 100 // A common default for scroll throttling
26
29
  */
27
30
  throttle?: number;
28
- /** Optional debug name for the internal signal. */
29
- debugName?: string;
30
31
  };
31
32
  /**
32
33
  * A specialized Signal that tracks scroll position.
@@ -57,26 +58,17 @@ export type ScrollPositionSignal = Signal<ScrollPosition> & {
57
58
  * selector: 'app-scroll-tracker',
58
59
  * template: `
59
60
  * <p>Window Scroll: X: {{ windowScroll().x }}, Y: {{ windowScroll().y }}</p>
60
- * <div #scrollableDiv style="height: 200px; width: 200px; overflow: auto; border: 1px solid black;">
61
- * <div style="height: 400px; width: 400px;">Scroll me!</div>
62
- * </div>
63
- * @if (divScroll()) {
64
- * <p>Div Scroll: X: {{ divScroll().x }}, Y: {{ divScroll().y }}</p>
65
- * }
61
+ * <p>Host Scroll: X: {{ hostScroll().x }}, Y: {{ hostScroll().y }}</p>
66
62
  * `
67
63
  * })
68
64
  * export class ScrollTrackerComponent {
69
65
  * readonly windowScroll = scrollPosition(); // Defaults to window
66
+ * // Signal targets (e.g. viewChild) attach once the element exists:
70
67
  * readonly scrollableDiv = viewChild<ElementRef<HTMLDivElement>>('scrollableDiv');
71
- * readonly divScroll = scrollPosition({ target: this.scrollableDiv() }); // Example with element target
68
+ * readonly divScroll = scrollPosition({ target: this.scrollableDiv });
72
69
  *
73
70
  * constructor() {
74
- * effect(() => {
75
- * console.log('Window scrolled to:', this.windowScroll());
76
- * if (this.divScroll()) {
77
- * console.log('Div scrolled to:', this.divScroll());
78
- * }
79
- * });
71
+ * effect(() => console.log('Window scrolled to:', this.windowScroll()));
80
72
  * }
81
73
  * }
82
74
  * ```
@@ -0,0 +1,25 @@
1
+ import { type Injector } from '@angular/core';
2
+ /**
3
+ * Options shared by every sensor: an optional `debugName` for the produced signal(s) and an
4
+ * optional `Injector` that lifts the injection-context requirement.
5
+ */
6
+ export type SensorRunOptions = {
7
+ /** Optional debug name for the produced signal(s). */
8
+ debugName?: string;
9
+ /**
10
+ * Injector used to resolve the sensor's dependencies (`PLATFORM_ID`, `DestroyRef`, default
11
+ * `ElementRef` targets, ...). Provide it when creating the sensor outside an injection
12
+ * context — e.g. in `ngOnInit`, an event handler, or an effect body. When omitted, the
13
+ * sensor must be created in an injection context (a constructor / field initializer).
14
+ */
15
+ injector?: Injector;
16
+ };
17
+ /**
18
+ * @internal Run a sensor factory inside `injector` when provided, else in the ambient
19
+ * injection context. Keeps every sensor's escape hatch identical and in one place.
20
+ */
21
+ export declare function runInSensorContext<T>(injector: Injector | undefined, fn: () => T): T;
22
+ /**
23
+ * @internal Normalize the legacy positional `debugName: string` form into {@link SensorRunOptions}.
24
+ */
25
+ export declare function coerceSensorOptions(opt?: string | SensorRunOptions): SensorRunOptions;
@@ -1,4 +1,5 @@
1
1
  import { type ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  import { type BatteryStatus } from './battery-status';
3
4
  import { type ClipboardSignal } from './clipboard';
4
5
  import { type ElementSizeOptions as BaseElementSizeOptions, type ElementSizeSignal } from './element-size';
@@ -7,7 +8,7 @@ import { type GeolocationOptions, type GeolocationSignal } from './geolocation';
7
8
  import { type IdleOptions, type IdleSignal } from './idle';
8
9
  import { type MousePositionOptions, type MousePositionSignal } from './mouse-position';
9
10
  import { type NetworkStatusSignal } from './network-status';
10
- import { type ScreenOrientation } from './orientation';
11
+ import { type ScreenOrientationState } from './orientation';
11
12
  import { type ScrollPositionOptions, type ScrollPositionSignal } from './scroll-position';
12
13
  import { type WindowSizeOptions, type WindowSizeSignal } from './window-size';
13
14
  type SensorTypedOptions = {
@@ -28,27 +29,19 @@ type SensorTypedOptions = {
28
29
  returnType: MousePositionSignal;
29
30
  };
30
31
  networkStatus: {
31
- opt: {
32
- debugName?: string;
33
- };
32
+ opt: SensorRunOptions;
34
33
  returnType: NetworkStatusSignal;
35
34
  };
36
35
  pageVisibility: {
37
- opt: {
38
- debugName?: string;
39
- };
36
+ opt: SensorRunOptions;
40
37
  returnType: Signal<DocumentVisibilityState>;
41
38
  };
42
39
  darkMode: {
43
- opt: {
44
- debugName?: string;
45
- };
40
+ opt: SensorRunOptions;
46
41
  returnType: Signal<boolean>;
47
42
  };
48
43
  reducedMotion: {
49
- opt: {
50
- debugName?: string;
51
- };
44
+ opt: SensorRunOptions;
52
45
  returnType: Signal<boolean>;
53
46
  };
54
47
  scrollPosition: {
@@ -60,9 +53,8 @@ type SensorTypedOptions = {
60
53
  returnType: WindowSizeSignal;
61
54
  };
62
55
  mediaQuery: {
63
- opt: {
56
+ opt: SensorRunOptions & {
64
57
  query: string;
65
- debugName?: string;
66
58
  };
67
59
  returnType: Signal<boolean>;
68
60
  };
@@ -71,21 +63,15 @@ type SensorTypedOptions = {
71
63
  returnType: GeolocationSignal;
72
64
  };
73
65
  clipboard: {
74
- opt: {
75
- debugName?: string;
76
- };
66
+ opt: SensorRunOptions;
77
67
  returnType: ClipboardSignal;
78
68
  };
79
69
  orientation: {
80
- opt: {
81
- debugName?: string;
82
- };
83
- returnType: Signal<ScreenOrientation>;
70
+ opt: SensorRunOptions;
71
+ returnType: Signal<ScreenOrientationState>;
84
72
  };
85
73
  batteryStatus: {
86
- opt: {
87
- debugName?: string;
88
- };
74
+ opt: SensorRunOptions;
89
75
  returnType: Signal<BatteryStatus | null>;
90
76
  };
91
77
  idle: {
@@ -93,8 +79,7 @@ type SensorTypedOptions = {
93
79
  returnType: IdleSignal;
94
80
  };
95
81
  focusWithin: {
96
- opt: {
97
- debugName?: string;
82
+ opt: SensorRunOptions & {
98
83
  target?: ElementRef<Element> | Element | Signal<ElementRef<Element> | Element | null>;
99
84
  };
100
85
  returnType: Signal<boolean>;
@@ -166,12 +151,12 @@ export declare function sensor(type: 'reducedMotion' | 'reduced-motion', options
166
151
  /**
167
152
  * Creates a sensor signal that tracks the provided media query.
168
153
  * @param type Must be `'mediaQuery'`.
169
- * @param options Optional configuration for the media query sensor, including `query` and `debugName`.
154
+ * @param options Required configuration for the media query sensor `query` is mandatory, `debugName` optional.
170
155
  * @returns A `Signal<boolean>` which is `true` if the media query currently matches.
171
156
  * @see {mediaQuery} for detailed documentation and examples.
172
157
  * @example const isDesktop = sensor('mediaQuery', { query: '(min-width: 1024px)' });
173
158
  */
174
- export declare function sensor(type: 'mediaQuery', options?: SensorTypedOptions['mediaQuery']['opt']): Signal<boolean>;
159
+ export declare function sensor(type: 'mediaQuery', options: SensorTypedOptions['mediaQuery']['opt']): Signal<boolean>;
175
160
  /**
176
161
  * Creates a sensor signal that tracks the browser window's inner dimensions (width and height).
177
162
  * @param type Must be `'windowSize'`.
@@ -204,7 +189,7 @@ export declare function sensor(type: 'clipboard', options?: SensorTypedOptions['
204
189
  * Creates a sensor signal tracking the screen orientation.
205
190
  * @see {orientation}
206
191
  */
207
- export declare function sensor(type: 'orientation', options?: SensorTypedOptions['orientation']['opt']): Signal<ScreenOrientation>;
192
+ export declare function sensor(type: 'orientation', options?: SensorTypedOptions['orientation']['opt']): Signal<ScreenOrientationState>;
208
193
  /**
209
194
  * Creates a sensor signal tracking the system battery status.
210
195
  * @see {batteryStatus}
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
2
3
  /**
3
4
  * Represents the dimensions of the window.
4
5
  */
@@ -9,13 +10,9 @@ export type WindowSize = {
9
10
  readonly height: number;
10
11
  };
11
12
  /**
12
- * Options for configuring the `mousePosition` sensor.
13
+ * Options for configuring the `windowSize` sensor.
13
14
  */
14
- export type WindowSizeOptions = {
15
- /**
16
- * Optional debug name for the internal signal.
17
- */
18
- debugName?: string;
15
+ export type WindowSizeOptions = SensorRunOptions & {
19
16
  /**
20
17
  * Optional delay in milliseconds to throttle the updates.
21
18
  * @default 100