@ddtmm/angular-signal-generators
Version:
Specialized Angular signals to help with frequently encountered situations.
878 lines (848 loc) • 47.5 kB
TypeScript
import { Signal, CreateSignalOptions, Injector, ValueEqualityFn, WritableSignal, ElementRef, CreateEffectOptions, EffectCleanupRegisterFn, EffectRef } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
/** A function used for updating animations. Meant for requestAnimationFrame or some substitution. */
type AnimationFrameFn = (callback: (timeStamp: number) => void) => unknown;
/**
* Could be a function used in a {@link https://angular.dev/api/core/computed | computed },
* a type compatible with {@link https://angular.dev/api/core/rxjs-interop/toSignal | toSignal},
* or just a {@link https://angular.dev/api/core/signal | signal}.
*/
type ReactiveSource<T> = (() => T) | ToSignalInput<T> | Signal<T>;
/** Extracts the emitted value of a {@link ReactiveSource} */
type ReactiveValue<S extends ReactiveSource<unknown>> = S extends ReactiveSource<infer R> ? R : never;
/** Extracts what the signal would be from a {@link ReactiveSource} */
type ReactiveSignal<S extends ReactiveSource<unknown>> = S extends Signal<unknown> ? S : S extends ReactiveSource<infer R> ? Signal<R> : never;
/** A type that can be converted to a signal with {@link https://angular.dev/api/core/rxjs-interop/toSignal | toSignal}. */
type ToSignalInput<T> = Parameters<typeof toSignal<T>>[0];
/**
* A constant value or a {@link ReactiveSource}.
* @typeParam T The type of the value or the type emitted by the {@link ReactiveSource}.
*/
type ValueSource<T> = T | ReactiveSource<T>;
/** Extracts the value of the {@link ValueSource}. */
type ValueSourceValue<V extends ValueSource<unknown>> = V extends ReactiveSource<unknown> ? ReactiveValue<V> : V;
/** Returns a value at a time *roughly* between 0 and 1. */
type InterpolateStepFn<T> = (progress: number) => T;
/** Returns a function that will return an interpolated value at an point in time. */
type InterpolateFactoryFn<T> = (a: T, b: T) => InterpolateStepFn<T>;
/** Either a number, array of numbers or a record of numbers. */
type NumericValues = number | number[] | Record<string | number | symbol, number>;
/** Creates an interpolator using an initial {@link NumericValues} to determine the type of function to use when interpolating future values.. */
declare function createInterpolator<T extends NumericValues>(currentValue: T): InterpolateFactoryFn<any>;
/**
* Options that can be used to overwrite default options when calling {@link AnimatedSignal.setOptions},
* {@link WritableAnimatedSignal.set}, or {@link WritableAnimatedSignal.update}.
*/
interface AnimationOptions {
/** A delay before starting. */
delay: number;
}
type AnimationOptionsWithInterpolator<TVal, TOpt extends AnimationOptions> = TOpt & {
interpolator: InterpolateFactoryFn<TVal>;
};
/** Options for an {@link AnimatedSignal}. */
type AnimatedSignalOptions<TVal, TOpt extends AnimationOptions> = Partial<TOpt> & Pick<CreateSignalOptions<TVal>, 'debugName'> & {
/** This is only used if a signal is created from an observable. */
injector?: Injector;
/** The interpolator is required unless numeric values are used. */
interpolator: InterpolateFactoryFn<TVal>;
/** A function to get the next animation frame. Defaults to window.requestAnimationFrame in browser context. */
raf?: AnimationFrameFn;
};
interface AnimatedSignal<TVal, TOpt extends AnimationOptions> extends Signal<TVal> {
/** Sets the default animation parameters for the signal. */
setOptions(options: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;
}
interface WritableAnimatedSignal<TVal, TOpt extends AnimationOptions> extends AnimatedSignal<TVal, TOpt> {
/** Sets the value of signal with optional animation options during the next transition. */
set(value: TVal, oneTimeOptions?: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;
/** Update the value of the signal based on its current value, with optional animation options used during the next transition. */
update(updateFn: (value: TVal) => TVal, oneTimeOptions?: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;
/** Returns a readonly version of this signal */
asReadonly(): Signal<TVal>;
}
/**
* Options that can be used to overwrite default options when calling {@link SpringSignal.setOptions},
* {@link WritableSpringSignal.set}, or {@link WritableSpringSignal.update}.
*/
interface SpringOptions extends AnimationOptions {
/** If true, will stay with the bounds of the starting and ending value during transitions. */
clamp: boolean;
/** The degree to suppress spring oscillation. The higher the value, the less movement.*/
damping: number;
/** How close the velocity must be to 0 and progress the final value before the animation is considered done. */
precision: number;
/**
* Effects how quickly the animation changes direction.
* The higher the value, the sooner the spring will reach the end and bounce back.
*/
stiffness: number;
}
/** Options for {@link springSignal}. */
type SpringSignalOptions<T> = AnimatedSignalOptions<T, SpringOptions>;
/** Same as regular {@link SpringSignalOptions}, but interpolator is not required. */
type SpringNumericSignalOptions<T extends NumericValues> = Omit<SpringSignalOptions<T>, 'interpolator'> & Partial<Pick<SpringSignalOptions<T>, 'interpolator'>>;
/** A signal with a function to set animation parameters returned from {@link springSignal}. */
type SpringSignal<T> = AnimatedSignal<T, SpringOptions>;
/** Returned from returned from {@link springSignal}. It's like a writable a signal, but with extended options when setting and updating. */
type WritableSpringSignal<T> = WritableAnimatedSignal<T, SpringOptions>;
declare function springSignal<V extends ValueSource<number>>(source: V, options?: SpringNumericSignalOptions<number>): V extends ReactiveSource<number> ? SpringSignal<number> : WritableSpringSignal<number>;
declare function springSignal<T extends NumericValues>(source: ValueSource<T>, options?: SpringNumericSignalOptions<T>): typeof source extends ReactiveSource<T> ? SpringSignal<T> : WritableSpringSignal<T>;
declare function springSignal<T>(source: ValueSource<T>, options: SpringSignalOptions<T>): typeof source extends ReactiveSource<T> ? SpringSignal<T> : WritableSpringSignal<T>;
/** A function that alters progress between 0 and 1. */
type EasingFn = ((progress: number) => number);
/**
* Options that can be used to overwrite default options when calling {@link TweenSignal.setOptions},
* {@link WritableTweenSignal.set}, or {@link WritableTweenSignal.update}.
*/
interface TweenOptions extends AnimationOptions {
/** How long the animation should last. */
duration: number;
/** An easing function that distorts progress. */
easing: EasingFn;
}
/** Options for {@link tweenSignal}. */
type TweenSignalOptions<T> = AnimatedSignalOptions<T, TweenOptions>;
/** Same as regular {@link SpringSignalOptions}, but interpolator is not required. */
type TweenNumericSignalOptions<T extends NumericValues> = Omit<TweenSignalOptions<T>, 'interpolator'> & Partial<Pick<TweenSignalOptions<T>, 'interpolator'>>;
/** A signal with a function to set animation parameters returned from {@link tweenSignal}. */
type TweenSignal<T> = AnimatedSignal<T, TweenOptions>;
/** Returned from returned from {@link tweenSignal}. It's like a writable a signal, but with extended options when setting and updating. */
type WritableTweenSignal<T> = WritableAnimatedSignal<T, TweenOptions>;
declare function tweenSignal<V extends ValueSource<number>>(source: V, options?: TweenNumericSignalOptions<number>): V extends ReactiveSource<number> ? TweenSignal<number> : WritableTweenSignal<number>;
declare function tweenSignal<T extends NumericValues>(source: ValueSource<T>, options?: TweenNumericSignalOptions<T>): typeof source extends ReactiveSource<T> ? TweenSignal<T> : WritableTweenSignal<T>;
declare function tweenSignal<T>(source: ValueSource<T>, options: TweenSignalOptions<T>): typeof source extends ReactiveSource<T> ? TweenSignal<T> : WritableTweenSignal<T>;
/**
* This is like a writable signal, but the inputs and outputs are different types.
* @typeParam TIn The input type used in set and update.
* @typeParam TOut The output type used in asReadonly or when this is called as a signal.
*/
interface TransformedSignal<TIn, TOut> extends Signal<TOut> {
/** Returns the output signal as a readonly. */
asReadonly(): Signal<TOut>;
/** Sets the input value of the signal. */
set(value: TIn): void;
/** Updates the input value of the signal. */
update(updateFn: (value: TIn) => TIn): void;
}
/** Either something with a .subscribe() function or a promise. */
type AsyncSource<T> = ToSignalInput<T> | Promise<T>;
/** Options for {@link asyncSignal}. */
interface AsyncSignalOptions<T> extends Pick<CreateSignalOptions<T>, 'debugName'> {
/** The default value before the first emission. */
defaultValue?: T;
/** Equal functions run on values emitted from async sources. */
equal?: ValueEqualityFn<T>;
/** This is only used if a signal is created from an observable. */
injector?: Injector;
/**
* If true, then the passed value will eagerly be read and it will throw an error if it hasn't been set.
* You shouldn't use defaultValue in this case.
*/
requireSync?: boolean;
}
/** An signal that returns values from an async source that can change. */
type AsyncSignal<T> = TransformedSignal<AsyncSource<T>, T>;
declare function asyncSignal<T>(valueSource: AsyncSource<T>, options: AsyncSignalOptions<T> & ({
defaultValue: T;
requireSync?: false;
} | {
defaultValue?: undefined;
requireSync: true;
})): AsyncSignal<T>;
declare function asyncSignal<T>(valueSource: AsyncSource<T>, options?: AsyncSignalOptions<T | undefined> & {
defaultValue?: undefined;
requireSync?: false;
}): AsyncSignal<T | undefined>;
declare function asyncSignal<T>(valueSource: Signal<AsyncSource<T>> | (() => AsyncSource<T>), options: AsyncSignalOptions<T> & ({
defaultValue: T;
requireSync?: false;
} | {
defaultValue?: undefined;
requireSync: true;
})): Signal<T>;
declare function asyncSignal<T>(valueSource: Signal<AsyncSource<T>> | (() => AsyncSource<T>), options?: AsyncSignalOptions<T | undefined> & {
defaultValue?: undefined;
requireSync?: false;
}): Signal<T | undefined>;
interface DebounceSignalOptions {
/** pass injector if this is not created in Injection Context */
injector?: Injector;
}
/**
* A writable signal whose value changes are debounced by a configured time period.
* @privateRemarks
* This could have been just a WriteableSignal, but I'm concerned about any confusion created by the fact
* that the SIGNAL symbol comes from a different signal than what the write functions are writing to.
*/
type DebouncedSignal<T> = Signal<T> & Pick<WritableSignal<T>, 'set' | 'update' | 'asReadonly'>;
/**
* Creates a signal which emits the debounced changes from another signal.
* See the other overload if you want to create a writable signal that is debounced.
* @param source The signal like object whose values are debounced.
* @param debounceTime The time from last change before the value is emitted. Can be signal like.
* @param options Options for the signal.
* @example
* ```ts
* const original = signal('unchanged');
* const debounced = debounceSignal(original, 500);
*
* original.set('changed');
* console.log(original(), debounced()) // changed, unchanged
*
* setTimeout(() => console.log(original(), debounced()), 500) // changed, changed.
* ```
*/
declare function debounceSignal<T>(source: ReactiveSource<T>, debounceTime: ValueSource<number>, options?: DebounceSignalOptions & Pick<CreateSignalOptions<T>, 'debugName'>): Signal<T>;
/**
* Creates a signal whose changes are debounced after a period of time from when the signal was updated.
* @param initialValue The initial value like a regular signal.
* @param debounceTime The time from last change before the value is emitted. Can be signal like.
* @param options Options for the signal.
* * @example
* ```ts
* const debounced = debounceSignal('unchanged', 500);
*
* debounced.set('changed');
* console.log(debounced()) // unchanged
*
* setTimeout(() => console.log(debounced()), 500) // changed
* ```
*/
declare function debounceSignal<T>(initialValue: T, debounceTime: ValueSource<number>, options?: DebounceSignalOptions & CreateSignalOptions<T>): DebouncedSignal<T>;
/**
* Optional injector reference if created outside injector context and IntersectionObserver options.
* If no IntersectionObserver options are passed then only all attributes are observed.
*/
interface IntersectionObserverOptions extends Omit<IntersectionObserverInit, 'root'>, Pick<CreateSignalOptions<unknown>, 'debugName'> {
/** This signal must either be created in an injection context or passed an injector. */
injector?: Injector;
/** The root element where intersections will be observed. */
root?: Document | ElementRef | Element | null | undefined;
}
type IntersectionSignalValue = ElementRef | Element | null | undefined;
/** A signal that watches when elements are resized. */
type IntersectionSignal = Signal<IntersectionObserverEntry[]> & {
/** Returns the output signal as a readonly. */
asReadonly(): Signal<IntersectionObserverEntry[]>;
/** Sets the new Element to watch. */
set(value: IntersectionSignalValue): void;
/** Updates the new Node or Element to watch from the existing value. */
update(updateFn: (value: IntersectionSignalValue) => IntersectionSignalValue): void;
};
declare function intersectionSignal(source: IntersectionSignalValue, options?: IntersectionObserverOptions): IntersectionSignal;
declare function intersectionSignal(source: ReactiveSource<IntersectionSignalValue>, options?: IntersectionObserverOptions): Signal<IntersectionObserverEntry[]>;
/**
* Optional injector reference if created outside injector context and MutationObserver options.
* If no MutationObserver options are passed then only all attributes are observed.
*/
interface MutationSignalOptions extends MutationObserverInit, Pick<CreateSignalOptions<unknown>, 'debugName'> {
/** This signal must either be created in an injection context or passed an injector. */
injector?: Injector;
}
type MutationSignalValue = ElementRef | Node | null | undefined;
/** A signal that watches changes to nodes or elements. */
type MutationSignal = Signal<MutationRecord[]> & {
/** Returns the output signal as a readonly. */
asReadonly(): Signal<MutationRecord[]>;
/** Sets the new Node or ElementRef to watch. Can optionally update options. */
set(value: MutationSignalValue, options?: MutationObserverInit): void;
/** Updates the new Node or ElementRef to watch from the existing value. */
update(updateFn: (value: MutationSignalValue) => MutationSignalValue): void;
};
declare function mutationSignal(source: MutationSignalValue, options?: MutationSignalOptions): MutationSignal;
declare function mutationSignal(source: ReactiveSource<MutationSignalValue>, options?: MutationSignalOptions): Signal<MutationRecord[]>;
/**
* Optional injector reference if created outside injector context and MutationObserver options.
* If no MutationObserver options are passed then only all attributes are observed.
*/
interface ResizeSignalOptions extends ResizeObserverOptions, Pick<CreateSignalOptions<unknown>, 'debugName'> {
/** This signal must either be created in an injection context or passed an injector. */
injector?: Injector;
}
type ResizeSignalValue = ElementRef | Element | null | undefined;
/** A signal that watches when elements are resized. */
type ResizeSignal = Signal<ResizeObserverEntry[]> & {
/** Returns the output signal as a readonly. */
asReadonly(): Signal<ResizeObserverEntry[]>;
/** Sets the new ElementRef to watch and optionally can set options too. */
set(value: ResizeSignalValue, options?: ResizeObserverOptions): void;
/** Updates the new Node or ElementRef to watch from the existing value. */
update(updateFn: (value: ResizeSignalValue) => ResizeSignalValue): void;
};
declare function resizeSignal(source: ResizeSignalValue, options?: ResizeSignalOptions): ResizeSignal;
declare function resizeSignal(source: ReactiveSource<ResizeSignalValue>, options?: ResizeSignalOptions): Signal<ResizeObserverEntry[]>;
/** Options for {@link eventSignal}. */
interface EventSignalOptions<T> extends Pick<CreateSignalOptions<T>, 'debugName'> {
/** An equal function put on the selector result. */
equal?: ValueEqualityFn<T | undefined>;
/** The initial value until the first emission. */
initialValue?: unknown;
/** Needed if not created in injection context. */
injector?: Injector;
}
/** Anything Renderer2 can listen to plus ElementRef. */
type EventSignalTarget = 'window' | 'document' | 'body' | ElementRef | any;
/**
* Creates a signal that listens to a DOM object event and maps the event to a value, initially returning undefined if default is not set.
* Requires being in an injection context.
* @example
* ```ts
* const $viewElem = viewChild('div');
* const $bodyEvent = eventSignal('body', 'click', (event) => event.clientX);
* const $divEvent = eventSignal($viewElem, 'click', (event) => event.clientX);
* effect(() => console.log(`body: ${$bodyEvent()}`)); // initially undefined.
* effect(() => console.log(`div: ${$divEvent()}`)); // initially undefined.
* ```
* @param T The type of the return value from the signal which comes from the selector or initialValue if provided.
* @param source The source the contains the target to listen to. Can be window, document, body, or an element ref, or anything else Renderer2 can listen to.
* @param eventName The name of the event to listen to.
* @param selector The selector to run when the event is emitted that will be output by the signal.
* @param options Options used when creating the signal.
*/
declare function eventSignal<T>(source: ValueSource<EventSignalTarget>, eventName: string, selector: (event: any) => T, options?: EventSignalOptions<T> & {
initialValue?: undefined;
}): Signal<T | undefined>;
/**
* Creates a signal that listens to a DOM object event and maps the event to a value.
* Requires being in an injection context.
* @example
* ```ts
* const $viewElem = viewChild('div');
* const $bodyEvent = eventSignal('body', 'click', (event) => event.clientX, { initialValue: 0 });
* const $divEvent = eventSignal($viewElem, 'click', (event) => event.clientX, { initialValue: 0 });
* effect(() => console.log(`body: ${$bodyEvent()}`)); // initially 0.
* effect(() => console.log(`div: ${$divEvent()}`)); // initially 0.
* ```
* @param T The type of the return value from the signal which comes from the selector or initialValue.
* @param source The source the contains the target to listen to. Can be window, document, body, or an element ref, or anything else Renderer2 can listen to.
* @param eventName The name of the event to listen to.
* @param selector The selector to run when the event is emitted that will be output by the signal.
* @param options Options used when creating the signal.
*/
declare function eventSignal<T>(source: ValueSource<EventSignalTarget>, eventName: string, selector: (event: any) => T, options: EventSignalOptions<T> & {
initialValue: T;
}): Signal<T>;
/**
* Creates a signal that listens to a DOM object event and returns that event.
* Requires being in an injection context.
* @example
* ```ts
* const $viewElem = viewChild('div');
* const $bodyEvent = eventSignal('body', 'click');
* const $divEvent = eventSignal($viewElem, 'click');
* // The following emit HTML events:
* effect(() => console.log(`body: ${$bodyEvent()}`));
* effect(() => console.log(`div: ${$divEvent()}`));
* ```
* @param source The source the contains the target to listen to. Can be window, document, body, or an element ref, or anything else Renderer2 can listen to.
* @param eventName The name of the event to listen to.
* @param options Options used when creating the signal.
*/
declare function eventSignal(source: ValueSource<EventSignalTarget>, eventName: string, options?: EventSignalOptions<any> & {
initialValue?: any;
}): Signal<any>;
/** The signal returned from {@link filterSignal}. */
interface FilterSignal<T, O = T> extends Signal<O> {
/** Returns the output signal as a readonly. */
asReadonly(): Signal<O>;
/** Sets the new value IF it is compatible with the filter function. */
set(value: T): void;
/** Updates the signal's value IF it is compatible with the filter function. */
update(updateFn: (value: O) => T): void;
}
declare function filterSignal<T, O extends T>(initialValue: O, filterFn: (x: T) => x is O, options?: CreateSignalOptions<O>): FilterSignal<T, O>;
declare function filterSignal<O>(initialValue: O, filterFn: (x: O) => boolean, options?: CreateSignalOptions<O>): FilterSignal<O>;
/** Options for {@link liftSignal}. */
interface LiftSignalOptions<T> {
/**
* Because signals only place nice with mutable objects, all mutations work by first cloning.
* There is a default clone function present, but if there are problems with it, you can provide your own.
*/
cloneFn?: (source: T) => T;
/**
* A debug name for the signal. Used in Angular DevTools to identify the signal.
* Only used if a {@link https://angular.dev/api/core/WritableSignal WritableSignal} is NOT passed as the first argument.
*/
debugName?: string;
/**
* Custom equality function.
* Only used if a value and not a {@link https://angular.dev/api/core/WritableSignal WritableSignal} is passed as the first argument.
*/
equal?: ValueEqualityFn<T>;
}
type MethodKey<T> = keyof {
[K in keyof T as T[K] extends (...args: any[]) => unknown ? K : never]: K;
} & keyof T;
type UpdaterKey<T> = keyof {
[K in keyof T as T[K] extends (...args: any[]) => T ? K : never]: K;
} & keyof T;
type MethodParameters<T, K extends MethodKey<T> | UpdaterKey<T>> = T[K] extends (...args: infer P) => unknown ? P : never;
type BoundMethods<T, K extends readonly (MethodKey<T> | UpdaterKey<T>)[]> = {
[Key in K[number]]: (...args: MethodParameters<T, Key>) => void;
};
/**
* Lifts methods from the signal's value to the signal itself.
* @example
* ```ts
* const awesomeArray = liftSignal([1, 2, 3, 4], ['filter'], ['push', 'pop']);
* awesomeArray.push(5);
* console.log(awesomeArray()); //[1, 2, 3, 4, 5];
* awesomeArray.pop();
* console.log(awesomeArray()); //[1, 2, 3, 4];
* awesomeArray.filter(x => x % 2 === 0);
* console.log(awesomeArray()); //[2, 4];
* ```
* @param valueSource Either a value or a Writable signal.
* @param updaters A tuple that contains the names that will return a new value.
* @param mutators A tuple that contains the names that will modify the signal's value directly.
* To guarantee this will return a new value, structuredClone or object.assign is used to create a brand new object,
* so use with caution.
* @typeParam T the type of the signal's value as well as the type where the functions are lifted from.
* @typeParam U A tuple that contains the names of methods appropriate for updating.
* @typeParam M A tuple that contains the names of methods appropriate for mutating.
*/
declare function liftSignal<T extends NonNullable<unknown>, const U extends readonly UpdaterKey<T>[] | null | undefined, const M extends readonly MethodKey<T>[] | null | undefined = null>(valueSource: Exclude<T, Signal<unknown>> | WritableSignal<T>, updaters: U, mutators?: M, options?: LiftSignalOptions<T>): WritableSignal<T> & BoundMethods<T, NonNullable<M>> & BoundMethods<T, NonNullable<U>>;
/** Options for {@link mapSignal}. */
interface MapSignalOptions<R> extends Pick<CreateSignalOptions<R>, 'debugName'> {
/** An equal function put on the selector result. */
equal?: ValueEqualityFn<R>;
/** This is only used if toSignal is needed to convert to a signal OR to get destroyedRef. */
injector?: Injector;
}
/** A signal that is updated with TIn, but emits TOut due to a selector specified at creation. */
interface MapSignal<TIn, TOut> extends TransformedSignal<TIn, TOut> {
/**
* Contains the values that are input to the signal.
* Calling set or update on this will have the same behavior as calling the main set or update methods
* and is exposed to make it easier for binding.
*/
input: WritableSignal<TIn>;
}
/** Used for when one or more signals passed as parameters */
type FromReactiveTupleType<T = unknown> = readonly ReactiveSource<T>[];
/** Extracts the values from a tuple of signal and converts them into a tuple of their own. */
type FromReactiveValues<T extends FromReactiveTupleType> = {
[I in keyof T]: ReactiveValue<T[I]>;
};
/** Creates a selector function type that uses the tuple of values as parameters */
type FromReactiveSelector<T extends FromReactiveTupleType, R> = (...x: FromReactiveValues<T>) => R;
/** The function parameters if signals are being passed without options. */
type FromReactiveParameters<T extends FromReactiveTupleType, R> = readonly [...inputs: T, selector: FromReactiveSelector<T, R>];
/** The function parameters if signals are being passed with options. Using an optional tuple member produced weird results. */
type FromReactiveParametersWithOptions<T extends FromReactiveTupleType, R> = readonly [
...inputs: T,
selector: FromReactiveSelector<T, R>,
options: MapSignalOptions<R>
];
/**
* Creates a signal from one or more signals that are mapped using the selector function.
* This is slightly different from compute as all values will be recalculated even if logic in the selector only uses some.
* @typeParam T The type of the signal tuple portion of params.
* @typeParam R The type of the value output by the selector function
* @param params One or more signals, then the selector.
* @returns A readonly signal emitting the selector value
* @example
* ```ts
* const num1 = signal(0);
* const num2 = signal(1);
* const num3 = signal(2);
* const mapped = mapSignal(num1, num2, num3, (a, b, c) => a + b + c);
* console.log(mapped()); // 3
* num1.set(5);
* console.log(mapped()); // 8
* ```
*/
declare function mapSignal<const T extends FromReactiveTupleType, R>(...params: FromReactiveParameters<T, R> | FromReactiveParametersWithOptions<T, R>): Signal<R>;
/**
* Creates a signal whose input value is immediately mapped to a different value based on a selector.
* The selector can contain signals and will react to changes in those signals.
* @typeParam T The type of the initial value
* @typeParam R The type of the value output by the selector function
* @param initialValue The initial value that will be run
* @param selector A selector that is run after the value of the signal is changed.
* @param options Can see equality function or if the selector or injector since this uses a computed function.
* @returns A writable signal whose output value is mapped with the selector.
* @example
* ```ts
* const addOne = mapSignal(1, x => x + 1);
* console.log(addOne()); // 2
*
* const addOnePlusOne = mapSignal(1, x => x + addOne());
* console.log(addOnePlusOne()); // 3
* ```
*/
declare function mapSignal<T, R>(initialValue: T, selector: (x: T) => R, options?: MapSignalOptions<R>): MapSignal<T, R>;
/**
* A signal that can be destroyed, releasing underlying resources and preventing future emissions.
* If created in an injector context, then it should be destroyed automatically, and calling {@link destroy}
* is only if for cases when it is desirable to stop future emissions early.
* For signals created outside an injector context, this is the only way to clean up resources such as event listeners.
*/
interface DestroyableSignal<T> extends Signal<T> {
/**
* This signal relies on resources that must be cleaned up.
* If it was created in an injector context then this doesn't have to be called, but is safe to call early.
* if it was created outside of an injector context then this should be called when it is no longer needed.
*/
destroy(): void;
}
/** The latest state of a MediaQueryList. */
type MediaQueryState = Pick<MediaQueryList, 'matches' | 'media'>;
/** Options for {@link mediaQuerySignal} when generating values from another reactive source. */
interface MediaQuerySignalOptions extends Pick<CreateSignalOptions<unknown>, 'debugName'> {
/** When passed another reactive source then this needs to be passed if outside injector context since this signal relies on effect. */
injector?: Injector;
}
/** Options for {@link mediaQuerySignal} when using writeable overload. */
interface MediaQueryWriteableSignalOptions extends Pick<CreateSignalOptions<unknown>, 'debugName'> {
/** This is only necessary if called outside injector context and {@link manualDestroy} is not true. */
injector?: Injector;
/**
* Whether this will be destroyed manually instead of when the injection context is destroyed.
* If true, this signal does not have to be created in an injection context,
* and {@link MediaQuerySignal.destroy} should be called when or if it is time to release resources.
*/
manualDestroy?: boolean;
}
/** A writeable signal created from {@link MediaQuerySignal}. */
interface MediaQuerySignal extends TransformedSignal<string, MediaQueryState>, DestroyableSignal<MediaQueryState> {
}
declare function mediaQuerySignal(querySource: ReactiveSource<string>, options?: MediaQuerySignalOptions): DestroyableSignal<MediaQueryState>;
declare function mediaQuerySignal(querySource: string, options?: MediaQueryWriteableSignalOptions): MediaQuerySignal;
/** Options for {@link nestSignal}. */
interface NestSignalOptions<T> extends CreateSignalOptions<NestSignalValue<T>> {
/** If true, errors will be caught, and the value will be set to undefined. */
ignoreErrors?: boolean;
/** Needed if not created in injection context and an Subscribable is passed as source. */
injector?: Injector;
}
/** The value returned by {@link nestSignal} that recursively replaces signals with their value types. */
type NestSignalValue<T> = T extends Signal<infer R> ? NestSignalValue<R> : T extends [] ? NestSignalValue<T[number]>[] : T extends Set<infer R> ? Iterable<NestSignalValue<R>> : T extends Map<infer K, infer V> ? Iterable<NestSignalValue<K>, NestSignalValue<V>> : T extends Date ? T : T extends object ? {
[K in keyof T]: NestSignalValue<T[K]>;
} : T;
declare function nestSignal<T>(source: ReactiveSource<T>, options?: NestSignalOptions<T>): Signal<NestSignalValue<T>>;
declare function nestSignal<T>(initialValue: T, options?: NestSignalOptions<T>): TransformedSignal<T, NestSignalValue<T>>;
interface ReduceSignal<T, U> extends Signal<T> {
asReadonly(): Signal<T>;
set(value: U): void;
update(updateFn: (prior: T) => U): void;
}
declare function reduceSignal<T>(initialValue: T, callbackFn: (prior: T, current: T) => T, options?: CreateSignalOptions<T>): ReduceSignal<T, T>;
declare function reduceSignal<T, U>(initialValue: T, callbackFn: (prior: T, current: U) => T, options?: CreateSignalOptions<T>): ReduceSignal<T, U>;
/** A result from a {@link Cursor}. */
type CursorResult<T> = {
/** If false there was no value, possibly because it went past a boundary or has no elements. */
hasValue: false;
/** The value will be undefined if hasValue is false. */
value?: unknown;
} | {
/** If true, then the value property should be set. */
hasValue: true;
/** The current value after last move. */
value: T;
};
/**
* Like an iterator, but with a reset method.
* @typeParam TVal The type of the value being iterated.
*/
interface Cursor<TVal> {
/**
* Performs a forward search from current position.
* If nothing is found, and autoReset is true, then it will resume search from the beginning.
* Will set position past end if nothing is found.
*
* @param value The value to search for.
* @returns The current cursor result after search is conducted.
*/
moveTo(value: TVal): CursorResult<TVal>;
/**
* Should move to the next element if relativeChange isn't passed,
* otherwise it should move the amount of relativeChange.
*
* @param relativeChange The amount to move. If no value is passed then it will move forward 1.
* @returns The current cursor state after movement.
*/
next(relativeChange?: number): CursorResult<TVal>;
/** Should reset to right before the beginning. */
reset(): void;
}
declare const ERR_BOUNDS_EXCEEDED = "Sequence cursor has exceeded bounds.";
declare const ERR_ELEMENT_NOT_PRESENT = "Element was not present in sequence.";
declare const ERR_NO_ELEMENTS = "Sequence contains no elements.";
declare const ERR_INVALID_SOURCE = "Invalid source type passed to sequence signal.";
/** Types that can be used in conjunction with {@link sequenceSignal}. */
type SequenceSource<T> = ArrayLike<T> | Cursor<T> | Iterable<T>;
interface SequenceSignalOptions extends Pick<CreateSignalOptions<unknown>, 'debugName'> {
/**
* If true, then the sequence will not loop and restart needs to be called.
* This is used when creating a cursor internally. If a cursor is provided as a source, then this will be ignored.
*/
disableAutoReset?: boolean;
/** injector should only be necessary if passing in an observable outside injector context. */
injector?: Injector;
}
interface SequenceSignal<T> extends Signal<T> {
/**
* Updates the signal to the next item in sequence.
* If at end and the option {@link SequenceSignalOptions.disableAutoReset} was passed as true, then it will move to the start.
*/
next(relativeChange?: number): void;
/** Updates the signal to the first item in sequence. */
reset(): void;
/** Moves the sequence to the next element that matches the passed value, ignoring the current element. Will throw if not found. */
set(value: T): void;
/** Moves the sequence to the next element that matches the passed value, ignoring the current element. Will throw if not found. */
update(updateFn: (value: T) => T): void;
}
/**
* This creates a signal that will cycle through a source of values, returning the the start after each call of next.
* @param sequence The source of sequence values.
* Could be an ArrayLike object or a "Cursor" which is like an iterator, but with a reset method.
* @param options Options for creating the sequence signal.
* @returns The sequence signal.
* @example
* ```ts
* const boolSeq = sequenceSignal([true, false]);
* console.log(boolSeq()) // true
* boolSeq.next();
* console.log(boolSeq()) // false
* boolSeq.next();
* console.log(boolSeq()) // true
*
* const lettersSeq = sequenceSignal('ABCDEFG');
* console.log(letterSeq()) // A;
* boolSeq.next(2);
* console.log(letterSeq()) // C;
*
* // pass a "Cursor" to generate a sequence.
* sequenceSignal((() => {
* let values = [1, 2];
* return {
* next: (relativeChange: number) => {
* for (let i = 0; i < relativeChange; i++) {
* values = [values[1], values[0] + values[1]];
* }
* for (let i = relativeChange; i < 0; i++) {
* values = [Math.max(1, values[1] - values[0]), Math.max(values[0], 2)];
* }
* return { hasValue: true, value: values[0] };
* },
* reset: () => values = [1, 2]
* };
* })());
* console.log(fibSeq()); // 1;
* fibSeq.next();
* console.log(fibSeq()); // 2;
* ```
*/
declare function sequenceSignal<T>(sequence: ValueSource<SequenceSource<T>>, options?: SequenceSignalOptions): SequenceSignal<T>;
/** A simple provider of persistent storage for {@link storageSignal}. */
interface StorageSignalStore<T> {
get(key: string): T | undefined;
set(key: string, value: T): void;
}
/**
* Creates a signal that will sync changes to some sort of storage.
* The next time a signal with the same key is read, an alternative value to the initial value will be used.
* It's probably better to use {@link sessionStorageSignal} or {@link localStorageSignal} instead of this.
* @param initialValue The initialValue for the signal if it isn't in storage.
* @param key The key to use for storage. This should be unique to avoid conflicts when deserializing values.
* @param storageProvider The provider of storage.
* @param options Standard create signal options.
* @typeParam T The type of the value that should be stored and deserialized.
* @returns A writable signal
* @example
* ```ts
* const storageProvider = Map<string, number>();
* const signal1 = storageSignal(1, 'someKey', storageProvider);
* signal1.set(100);
* const signal2 = storageSignal(1, 'someKey', storageProvider);
* console.log(signal1(), signal2()); // [LOG]: 100, 100
* ```
*/
declare function storageSignal<T>(initialValue: T, key: string, storageProvider: StorageSignalStore<T>, options?: CreateSignalOptions<T>): WritableSignal<T>;
/**
* Options for {@link localStorageSignal} and {@link sessionStorageSignal}.
* @typeParam T The type of the value that should be stored and deserialized.
*/
interface WebStorageOptions<T> extends CreateSignalOptions<T> {
/** An optional function to use when serializing a value with JSON.parse. */
replacer?: (key: string, value: unknown) => unknown;
/** An optional function to use when deserializing a value with JSON.parse. */
reviver?: (key: string, value: unknown) => unknown;
}
/**
* Generates a signal using localStorage as the store. A shared Map is used if session storage is not supported.
*
* @param initialValue the initial value for the signal
* @param key the key to use in localStorage
* @param options optional options to configure the signal and underlying storage.
* @typeParam T The type of the value that should be stored and deserialized.
* @returns the writable signal generated from storageSignal.
* @example
* ```ts
* const signal1 = localStorageSignal(1, 'someKey');
* console.log(signal1()); // This MIGHT not be 1 depending on what was stored in localStorage for "someKey".
* signal1.set(100);
* console.log(signal1()); // 100 ("someKey" is now 100 in localStorage)
* ```
* @see {@link storageSignal}
*/
declare function localStorageSignal<T>(initialValue: T, key: string, options?: WebStorageOptions<T>): WritableSignal<T>;
/**
* Generates a signal using sessionStorage as the store. A shared Map is used if session storage is not supported.
*
* @param initialValue the initial value for the signal
* @param key the key to use in sessionStorage
* @param options optional options to configure the signal and underlying storage.
* @typeParam T The type of the value that should be stored and deserialized.
* @returns the writable signal generated from storageSignal.
* @example
* ```ts
* const signal1 = sessionStorageSignal(1, 'someKey');
* console.log(signal1()); // This MIGHT not be 1 depending on what was stored in sessionStorage for "someKey".
* signal1.set(100);
* console.log(signal1()); // 100 ("someKey" is now 100 in sessionStorage)
* ```
* @see {@link storageSignal}
*/
declare function sessionStorageSignal<T>(initialValue: T, key: string, options?: WebStorageOptions<T>): WritableSignal<T>;
/** The state of the timer. */
type TimerSignalStatus = 'running' | 'paused' | 'stopped' | 'destroyed';
/** Options for {@link timerSignal}. */
interface TimerSignalOptions<T = number> extends Pick<CreateSignalOptions<T>, 'debugName'> {
/** pass injector if this is not created in Injection Context */
injector?: Injector;
/**
* If true, the timer isn't running at start.
* When running in a non-browser environment, the signal always begins in a stopped state by default.
*/
stopped?: boolean;
/**
* A selector function that receives the tick count and returns the value to emit from the signal.
* If not provided, the tick count (number) is emitted.
*/
selector?: (tickCount: number) => T;
}
/** A readonly signal with methods to affect execution created from {@link timerSignal}. */
interface TimerSignal<T = number> extends Signal<T> {
/** Pauses the timer. */
pause(): void;
/** Restarts the timer if it is an interval, or incomplete "one-time" timer. */
restart(): void;
/** Resumes the timer if paused using the remaining time when paused. */
resume(): void;
/** The status of the timer as a signal. */
state: Signal<TimerSignalStatus>;
}
declare function timerSignal<T>(timerTime: ValueSource<number>, intervalTime: ValueSource<number> | null | undefined, options: TimerSignalOptions<T> & {
selector: (tickCount: number) => T;
}): TimerSignal<T>;
declare function timerSignal(timerTime: ValueSource<number>, intervalTime?: ValueSource<number> | null, options?: TimerSignalOptions): TimerSignal<number>;
/** Options that control the behavior of {@link gatedEffect}. */
interface GatedEffectOptions extends CreateEffectOptions {
/** Prevents execution of the effect if this returns false. */
filter?: () => boolean;
/**
* A condition that will prevent the effect from running until it is true.
* Once it is true once, it will not be checked again.
*/
start?: () => boolean;
/**
* The number of times the effect should run.
* Only increments if the effect function is run, so occasions where {@link filter} or {@link start} return false aren't counted.
* If {@link until} is also set, then whichever comes first will terminate the effect.
*/
times?: number;
/**
* A condition that will terminate the effect.
* If {@link times} is also set, then whichever comes first will terminate the effect.
*/
until?: () => boolean;
}
/**
* An effect that will start, stop, or conditionally run based on conditions set in the {@link GatedEffectOptions}.
* This is most effective for stopping an effect as it will properly clean up after itself and not track signals unnecessarily.
*
* @param effectFn The function to be executed whenever all gate conditions are met.
* @param options Standard effect options, plus gate conditions filter (condition that prevents running),
* start (start running effect), times (number of times to execute), and until (stop running effect)
* @example
* ```ts
* const $userInfo = signal<UserInfo | undefined>(undefined);
* gatedEffect(() => doSomethingWithRequiredUserInfo($userInfo()!), { start: () => $userInfo() !== undefined, times: 1 });
* ```
*/
declare function gatedEffect(effectFn: (onCleanup: EffectCleanupRegisterFn) => void, options?: GatedEffectOptions): EffectRef;
/** Options for {@link signalToIterator}. */
interface SignalToIteratorOptions {
/** pass injector if this is not created in Injection Context */
injector?: Injector;
}
/**
* Creates an async iterator for a signal.
* @param source The signal to create an async iterator for.
* @param options Options for the signal.
* @example
* ```ts
* // assumes this is within a component
* readonly source = signal('initial');
* constructor() {
* for await (const item of signalToIterator(this.source)) {
* console.log(item); // 'initial', 'next';
* }
* this.source.set('next');
* }
* ```
*/
declare function signalToIterator<T>(source: Signal<T>, options?: SignalToIteratorOptions): Required<AsyncIterableIterator<T>>;
/**
* Options that control the behavior of {@link inspect}.
* Options specific to inspect can be set globally from {@link INSPECT_DEFAULTS}.
*/
interface InspectOptions<T> extends NestSignalOptions<T> {
/** Needed if not created in an injection context. */
injector?: Injector;
/** Overrides the default reporter.*/
reporter?: (value: NestSignalValue<T>) => void;
/** By default inspect will only work in dev mode. Set to true to run in prod mode. */
runInProdMode?: boolean;
/**
* If true, then the first output is not reported.
* This is dependent on one when the first effect actually executes.
*/
skipInitial?: boolean;
}
/**
* Given that {@link inspect} should have no side-effects and does nothing in production mode,
* this is provided as a simple way to set the default behavior without having to get into dependency injection.
* You can modify this from wherever you want.
*/
declare const INSPECT_DEFAULTS: {
/** This is the default reporter for inspect. */
reporter: (value: unknown) => void;
/** If true, this will run even if in prod mode is enabled. */
runInProdMode: boolean;
/** If true, the initial value will not be reported. */
skipInitial: boolean;
};
/**
* Reports the latest state of a subject by resolving all the values deeply contained within by using {@link nestSignal}.
* By default the output is just a console.log, but this can be changed with the {@link InspectOptions<T>.reporter} option.
* Subject can be anything, but to be effective it should be a signal, an object that contains signals, or an array of signals.
*
* @param subject This can be anything, but ideally it is some object that contains signals somewhere.
* @param options Options that control the behavior of inspect. Globally, options can be changed from {@link INSPECT_DEFAULTS}.
* @example
* ```ts
* const $a = signal(1);
* const $b = signal(2);
* const $c = signal({ a: $a, b: $b });
* inspect([$c]); // log: [{ a: 1, b: 2 }];
* $b.set(3) // log: [{ a: 1, b: 3 }];
* ```
*/
declare function inspect<T>(subject: T, options?: InspectOptions<T>): void;
export { ERR_BOUNDS_EXCEEDED, ERR_ELEMENT_NOT_PRESENT, ERR_INVALID_SOURCE, ERR_NO_ELEMENTS, INSPECT_DEFAULTS, asyncSignal, createInterpolator, debounceSignal, eventSignal, filterSignal, gatedEffect, inspect, intersectionSignal, liftSignal, localStorageSignal, mapSignal, mediaQuerySignal, mutationSignal, nestSignal, reduceSignal, resizeSignal, sequenceSignal, sessionStorageSignal, signalToIterator, springSignal, storageSignal, timerSignal, tweenSignal };
export type { AnimatedSignal, AnimatedSignalOptions, AnimationFrameFn, AnimationOptions, AsyncSignal, AsyncSignalOptions, AsyncSource, BoundMethods, Cursor, CursorResult, DebounceSignalOptions, DebouncedSignal, DestroyableSignal, EasingFn, EventSignalOptions, EventSignalTarget, FilterSignal, FromReactiveSelector, FromReactiveTupleType, FromReactiveValues, GatedEffectOptions, InspectOptions, InterpolateFactoryFn, InterpolateStepFn, IntersectionObserverOptions, IntersectionSignal, IntersectionSignalValue, LiftSignalOptions, MapSignal, MapSignalOptions, MediaQuerySignal, MediaQuerySignalOptions, MediaQueryState, MediaQueryWriteableSignalOptions, MethodKey, MethodParameters, MutationSignal, MutationSignalOptions, MutationSignalValue, NestSignalOptions, NestSignalValue, NumericValues, ReactiveSignal, ReactiveSource, ReactiveValue, ReduceSignal, ResizeSignal, ResizeSignalOptions, ResizeSignalValue, SequenceSignal, SequenceSignalOptions, SequenceSource, SignalToIteratorOptions, SpringNumericSignalOptions, SpringOptions, SpringSignal, SpringSignalOptions, StorageSignalStore, TimerSignal, TimerSignalOptions, TimerSignalStatus, ToSignalInput, TransformedSignal, TweenNumericSignalOptions, TweenOptions, TweenSignal, TweenSignalOptions, UpdaterKey, ValueSource, ValueSourceValue, WebStorageOptions, WritableAnimatedSignal, WritableSpringSignal, WritableTweenSignal };