UNPKG

@ddtmm/angular-signal-generators

Version:

Specialized Angular signals to help with frequently encountered situations.

1 lines 202 kB
{"version":3,"file":"ddtmm-angular-signal-generators.mjs","sources":["../../../projects/signal-generators/src/lib/signals/animated/interpolation.ts","../../../projects/signal-generators/src/lib/internal/reactive-source-utilities.ts","../../../projects/signal-generators/src/lib/internal/utilities.ts","../../../projects/signal-generators/src/lib/internal/signal-coercion.ts","../../../projects/signal-generators/src/lib/signals/animated/animation-utilities.ts","../../../projects/signal-generators/src/lib/signals/animated/animated-signal-base.ts","../../../projects/signal-generators/src/lib/signals/animated/spring-signal.ts","../../../projects/signal-generators/src/lib/signals/animated/tween-signal.ts","../../../projects/signal-generators/src/lib/signals/async-signal.ts","../../../projects/signal-generators/src/lib/internal/timer-internal.ts","../../../projects/signal-generators/src/lib/value-source.ts","../../../projects/signal-generators/src/lib/signals/debounce-signal.ts","../../../projects/signal-generators/src/lib/signals/dom-observers/dom-observer-base.ts","../../../projects/signal-generators/src/lib/signals/dom-observers/intersection-signal.ts","../../../projects/signal-generators/src/lib/signals/dom-observers/mutation-signal.ts","../../../projects/signal-generators/src/lib/signals/dom-observers/resize-signal.ts","../../../projects/signal-generators/src/lib/signals/event-signal.ts","../../../projects/signal-generators/src/lib/signals/filter-signal.ts","../../../projects/signal-generators/src/lib/signals/lift-signal.ts","../../../projects/signal-generators/src/lib/signals/map-signal.ts","../../../projects/signal-generators/src/lib/signals/media-query-signal.ts","../../../projects/signal-generators/src/lib/signals/nest-signal.ts","../../../projects/signal-generators/src/lib/signals/reduce-signal.ts","../../../projects/signal-generators/src/lib/support/cursors/array-like-cursor.ts","../../../projects/signal-generators/src/lib/support/cursors/iterable-cursor.ts","../../../projects/signal-generators/src/lib/signals/sequence-signal.ts","../../../projects/signal-generators/src/lib/internal/map-based-storage.ts","../../../projects/signal-generators/src/lib/internal/web-object-store.ts","../../../projects/signal-generators/src/lib/signals/storage-signal.ts","../../../projects/signal-generators/src/lib/signals/timer-signal.ts","../../../projects/signal-generators/src/lib/utilities/gated-effect.ts","../../../projects/signal-generators/src/lib/utilities/signal-to-iterator.ts","../../../projects/signal-generators/src/lib/utilities/inspect.ts","../../../projects/signal-generators/src/public_api.ts","../../../projects/signal-generators/src/ddtmm-angular-signal-generators.ts"],"sourcesContent":["/** Returns a value at a time *roughly* between 0 and 1. */\r\nexport type InterpolateStepFn<T> = (progress: number) => T;\r\n/** Returns a function that will return an interpolated value at an point in time. */\r\nexport type InterpolateFactoryFn<T> = (a: T, b: T) => InterpolateStepFn<T>;\r\n\r\n/** Either a number, array of numbers or a record of numbers. */\r\nexport type NumericValues = number | number[] | Record<string | number | symbol, number>;\r\n\r\n// export function createInterpolator(currentValue: number): InterpolateFactoryFn<number>;\r\n// export function createInterpolator(currentValue: number[]): InterpolateFactoryFn<number[]>;\r\n// export function createInterpolator(currentValue: Record<string | number | symbol, number>): InterpolateFactoryFn<Record<string | number | symbol, number>>;\r\n/** Creates an interpolator using an initial {@link NumericValues} to determine the type of function to use when interpolating future values.. */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function createInterpolator<T extends NumericValues>(currentValue: T): InterpolateFactoryFn<any> {\r\n \r\n if (typeof currentValue === 'number') {\r\n return (a: number, b: number) => (progress: number) => interpolateNumber(a, b, progress);\r\n } else if (Array.isArray(currentValue)) {\r\n return (a: number[], b: number[]) => (progress: number) =>\r\n b.map((val, index) => interpolateNumber(a[index] ?? val, val, progress));\r\n }\r\n return (a: Record<string | number | symbol, number>, b: Record<string | number | symbol, number>) => (progress: number) =>\r\n Object.entries(b).reduce((acc, cur) => {\r\n acc[cur[0]] = interpolateNumber(a[cur[0]] ?? cur[1], cur[1], progress);\r\n return acc;\r\n }, {} as typeof b);\r\n\r\n function interpolateNumber(a: number, b: number, progress: number): number {\r\n return a * (1 - progress) + b * progress;\r\n }\r\n}\r\n","import { isSignal } from '@angular/core';\r\nimport { ReactiveSource, ToSignalInput } from '../reactive-source';\r\n\r\nexport function isReactive<T>(obj: ReactiveSource<T> | T): obj is ReactiveSource<T>\r\nexport function isReactive(obj: unknown): obj is ReactiveSource<unknown>\r\n/**\r\n * Tests if an object is valid for coerceSignal and meets the criteria for being a {@link ReactiveSource}.\r\n *\r\n * @param obj Any type of a value can be checked.\r\n */\r\nexport function isReactive(obj: unknown): obj is ReactiveSource<unknown> {\r\n return (obj != null) && (isSignal(obj) || isReactiveSourceFunction(obj) || isToSignalInput(obj));\r\n}\r\n\r\nexport function isReactiveSourceFunction<T>(obj: ReactiveSource<T>): obj is () => T\r\nexport function isReactiveSourceFunction(obj: unknown): obj is () => unknown\r\n/** Is true if obj is a function, it has no arguments, and it is not a signal. */\r\nexport function isReactiveSourceFunction(obj: unknown): obj is () => unknown {\r\n return typeof obj === 'function' && obj.length === 0 && !isSignal(obj);\r\n}\r\n\r\nexport function isToSignalInput<T>(obj: ReactiveSource<T>): obj is ToSignalInput<T>\r\nexport function isToSignalInput(obj: unknown): obj is ToSignalInput<unknown>\r\n/**\r\n * Determines if an object is a parameter for toSignal by looking for subscribe property.\r\n * It does this by seeing if there is a subscribe function.\r\n * If toSignal's implementation changes, then this needs to be reviewed.\r\n *\r\n * @param obj Any type of a value can be checked.\r\n */\r\nexport function isToSignalInput(obj: unknown): obj is ToSignalInput<unknown> {\r\n return typeof (obj as Partial<ToSignalInput<unknown>>)?.subscribe === 'function';\r\n}\r\n","import { DestroyRef, Injector, Signal, ValueEqualityFn, assertInInjectionContext, inject, isDevMode } from '@angular/core';\r\nimport { ReactiveNode, SIGNAL, SignalNode } from '@angular/core/primitives/signals';\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\ntype AnyFunctionType = (...args: any[]) => void;\r\n\r\n/**\r\n * This is inspired by `signalAsReadonlyFn` from https://github.com/angular/angular/blob/main/packages/core/src/render3/reactivity/signal.ts#L90\r\n * It does not cache the readonlyFn, just creates a new one each time.\r\n */\r\nexport function asReadonlyFnFactory<T>($src: Signal<T>): () => Signal<T> {\r\n const $readonly = (() => $src()) as Signal<T>;\r\n $readonly[SIGNAL] = $src[SIGNAL];\r\n return () => $readonly;\r\n}\r\n\r\n/** Gets the DestroyRef either using the passed injector or inject function. */\r\nexport function getDestroyRef(fnType: AnyFunctionType, injector?: Injector | null | undefined): DestroyRef {\r\n if (injector) {\r\n return injector.get(DestroyRef);\r\n }\r\n\r\n assertInInjectionContext(fnType);\r\n return inject(DestroyRef);\r\n}\r\n\r\n/** Gets the injector, throwing if the function is in injection context. */\r\nexport function getInjector(fnType: AnyFunctionType): Injector {\r\n assertInInjectionContext(fnType);\r\n return inject(Injector);\r\n}\r\n\r\n/**\r\n * A type safe way for determining a key is in an object.\r\n * This is still needed because when \"in\" is used the propertyType can be inferred as undefined.\r\n */\r\nexport function hasKey<T extends object>(obj: T | null | undefined, key: keyof T): obj is T & { [K in typeof key]-?: T[K] } {\r\n return obj != null && (key in obj);\r\n}\r\n\r\n// an interesting idea - the type passed is just an object's methods\r\n// export function isMethodKey<T extends { [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never }>(obj: T, key: unknown): key is keyof T {\r\n// return (typeof obj[key as keyof T] === 'function');\r\n// }\r\n\r\n/** Detects if a key is a key of an object's method */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport function isMethodKey<T extends object>(obj: T | null | undefined, key: unknown): key is keyof { [K in keyof T as T[K] extends (...args: any[]) => any ? K : never] : K } & keyof T {\r\n return obj != null && (typeof obj[key as keyof T] === 'function');\r\n}\r\n\r\n/** Sets the debugName on a {@link ReactiveNode} from debugName property if it is set and if {@link isDevMode} returns true. */\r\nexport function setDebugNameOnNode(node: ReactiveNode, debugName: string | undefined): void {\r\n if (isDevMode() && debugName != null) {\r\n node.debugName = debugName;\r\n }\r\n}\r\n/** Sets the equal on a {@link SignalNode} if equalFn is defined. */\r\nexport function setEqualOnNode<T>(node: SignalNode<T>, equalFn: ValueEqualityFn<T> | undefined): void {\r\n if (equalFn != null) {\r\n node.equal = equalFn;\r\n }\r\n}\r\n","import { Injector, computed, isSignal } from '@angular/core';\r\nimport { toSignal } from '@angular/core/rxjs-interop';\r\nimport { ReactiveSource, ReactiveSignal } from '../reactive-source';\r\nimport { hasKey } from './utilities';\r\nimport { isToSignalInput } from './reactive-source-utilities';\r\n\r\nexport interface CoerceSignalOptions<T> {\r\n /**\r\n * This is only used if {@link https://angular.dev/api/core/rxjs-interop/toSignal | toSignal} is needed to convert to a signal.\r\n * If not passed it as assumed the source is sync.\r\n */\r\n initialValue?: T;\r\n /** This is only used if {@link https://angular.dev/api/core/rxjs-interop/toSignal | toSignal} is needed to convert to a signal. */\r\n injector?: Injector;\r\n}\r\n\r\n/**\r\n * Converts a source to a signal.\r\n * * If it is already a signal then it is returned.\r\n * * If it matches the input type to toSignal function, then it is converted to a signal.\r\n * If initial value is passed then it doesn't have to be an async observable.\r\n * * If it is just a function then it is converted to a signal with computed.\r\n *\r\n * @example\r\n * ```ts\r\n * const signalInput = signal(1);\r\n * const functionInput = () => signalInput() * 2;\r\n * const immediateInput = timer(0, 1000);\r\n * const delayedInput = timer(1000, 1000);\r\n *\r\n * const coercedSignal = coerceSignal(signalInput);\r\n * const coercedFunction = coerceSignal(functionInput);\r\n * const coercedImmediate = coerceSignal(immediateInput);\r\n * const coercedDelayed = coerceSignal(delayedInput, { initialValue: 0 });\r\n *\r\n * effect(() => {\r\n * console.log(coercedSignal, coercedFunction, coercedImmediate, coercedDelayed);\r\n * });\r\n * ```\r\n */\r\nexport function coerceSignal<T, S extends ReactiveSource<T>>(source: S, options?: CoerceSignalOptions<T>): ReactiveSignal<S> {\r\n if (isSignal(source)) {\r\n return source as ReactiveSignal<S>;\r\n }\r\n else if (isToSignalInput(source)) {\r\n // if there is no initialValue then assume the observable has an initial value and set requireSync as true.\r\n // Options are explicitly passed to avoid unintended values from effecting output.\r\n return (hasKey(options, 'initialValue'))\r\n ? toSignal(source, { injector: options.injector, initialValue: options.initialValue }) as ReactiveSignal<S>\r\n : toSignal(source, { injector: options?.injector, requireSync: true }) as ReactiveSignal<S>;\r\n }\r\n return computed(source) as ReactiveSignal<S> ;\r\n}\r\n\r\n\r\n","/** A function used for updating animations. Meant for requestAnimationFrame or some substitution. */\r\nexport type AnimationFrameFn = (callback: (timeStamp: number) => void) => unknown;\r\n\r\n/** Gets a function for requesting animation frames. Either requestAnimationFrame or a setTimeout approximating 30 fps. */\r\nexport function getRequestAnimationFrame(): AnimationFrameFn {\r\n return (!globalThis.requestAnimationFrame)\r\n ? ((callback: (timeStamp: number) => void) => setTimeout(() => callback(Date.now()), 33))\r\n : globalThis.requestAnimationFrame;\r\n}\r\n\r\n","import { CreateSignalOptions, Injector, Signal, WritableSignal, effect, signal, untracked } from '@angular/core';\r\nimport { SIGNAL, SignalGetter, createSignal, signalSetFn } from '@angular/core/primitives/signals';\r\nimport { isReactive } from '../../internal/reactive-source-utilities';\r\nimport { coerceSignal } from '../../internal/signal-coercion';\r\nimport { ReactiveSource } from '../../reactive-source';\r\nimport { ValueSource } from '../../value-source';\r\nimport { AnimationFrameFn, getRequestAnimationFrame } from './animation-utilities';\r\nimport { InterpolateFactoryFn, NumericValues, createInterpolator } from './interpolation';\r\n\r\n/** Request animation frame function */\r\nconst requestAnimationFrame = getRequestAnimationFrame();\r\n\r\n/**\r\n * Options that can be used to overwrite default options when calling {@link AnimatedSignal.setOptions},\r\n * {@link WritableAnimatedSignal.set}, or {@link WritableAnimatedSignal.update}.\r\n */\r\nexport interface AnimationOptions {\r\n /** A delay before starting. */\r\n delay: number;\r\n}\r\n\r\nexport type AnimationState<TState> = TState & {\r\n /** If the animation is done. */\r\n isDone: boolean;\r\n /** How far has the animation progressed as a percent, though it can fall outside 0 and 1. */\r\n progress: number;\r\n timeCurrent: number;\r\n timeElapsed: number;\r\n timeDelta: number;\r\n timeStart: number;\r\n}\r\n\r\nexport type AnimationOptionsWithInterpolator<TVal, TOpt extends AnimationOptions> = TOpt & {\r\n interpolator: InterpolateFactoryFn<TVal>;\r\n}\r\n\r\n/** Options for an {@link AnimatedSignal}. */\r\nexport type AnimatedSignalOptions<TVal, TOpt extends AnimationOptions> = Partial<TOpt> &\r\n Pick<CreateSignalOptions<TVal>, 'debugName'> & {\r\n /** This is only used if a signal is created from an observable. */\r\n injector?: Injector;\r\n /** The interpolator is required unless numeric values are used. */\r\n interpolator: InterpolateFactoryFn<TVal>;\r\n /** A function to get the next animation frame. Defaults to window.requestAnimationFrame in browser context. */\r\n raf?: AnimationFrameFn;\r\n };\r\n\r\n/** Same as regular {@link AnimatedSignalOptions}, but interpolator is not required. */\r\nexport type AnimatedNumericSignalOptions<TVal extends NumericValues, TOpt extends AnimationOptions> = Omit<\r\n AnimatedSignalOptions<TVal, TOpt>,\r\n 'interpolator'\r\n> &\r\n Partial<Pick<AnimatedSignalOptions<TVal, TOpt>, 'interpolator'>>;\r\n\r\nexport interface AnimatedSignal<TVal, TOpt extends AnimationOptions> extends Signal<TVal> {\r\n /** Sets the default animation parameters for the signal. */\r\n setOptions(options: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;\r\n}\r\nexport interface WritableAnimatedSignal<TVal, TOpt extends AnimationOptions> extends AnimatedSignal<TVal, TOpt> {\r\n /** Sets the value of signal with optional animation options during the next transition. */\r\n set(value: TVal, oneTimeOptions?: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;\r\n /** Update the value of the signal based on its current value, with optional animation options used during the next transition. */\r\n update(updateFn: (value: TVal) => TVal, oneTimeOptions?: Partial<AnimationOptionsWithInterpolator<TVal, TOpt>>): void;\r\n /** Returns a readonly version of this signal */\r\n asReadonly(): Signal<TVal>;\r\n}\r\n\r\n/**\r\n * A function executed with each step.\r\n * @param state An object that maintains any variables that need be maintained throughout the animation.\r\n * @param options Options for the animation.\r\n * @typeParam TState An object that maintains any variables that need be maintained throughout the animation.\r\n */\r\nexport type AnimationStepFn<TState extends object, TOpt extends AnimationOptions> = (\r\n state: AnimationState<TState>,\r\n options: TOpt\r\n) => void;\r\n\r\n// export function animatedSignalFactory<V extends ValueSource<number>, O extends AnimationOptions, TState extends object>(\r\n// source: V, \r\n// options: Partial<AnimatedNumericSignalOptions<number, O>> | undefined,\r\n// defaultAnimationOptions: Required<O>,\r\n// initialState: TState,\r\n// stepFn: AnimationStepFn<TState, O>\r\n// ): V extends ReactiveSource<number> ? AnimatedSignal<number, O> : WritableAnimatedSignal<number, O>\r\n// export function animatedSignalFactory<T extends NumericValues, V extends ValueSource<T>, O extends AnimationOptions, TState extends object>(\r\n// source: V, \r\n// options: Partial<AnimatedNumericSignalOptions<T, O>> | undefined,\r\n// defaultAnimationOptions: Required<O>,\r\n// initialState: TState,\r\n// stepFn: AnimationStepFn<TState, O>\r\n// ): V extends ReactiveSource<T> ? AnimatedSignal<T, O> : WritableAnimatedSignal<T, O>\r\n// export function animatedSignalFactory<T, V extends ValueSource<T>, O extends AnimationOptions, TState extends object>(\r\n// source: V, \r\n// options: Partial<AnimatedSignalOptions<T, O>> | undefined,\r\n// defaultAnimationOptions: Required<O>,\r\n// initialState: TState,\r\n// stepFn: AnimationStepFn<TState, O>\r\n// ): V extends ReactiveSource<T> ? AnimatedSignal<T, O> : WritableAnimatedSignal<T, O>\r\nexport function animatedSignalFactory<T, V extends ValueSource<T>, O extends AnimationOptions, TState extends object>(\r\n source: V,\r\n options: Partial<AnimatedSignalOptions<T, O>> | undefined,\r\n defaultAnimationOptions: O,\r\n initialState: TState,\r\n stepFn: AnimationStepFn<TState, O>\r\n): V extends ReactiveSource<T> ? AnimatedSignal<T, O> : WritableAnimatedSignal<T, O> {\r\n const [\r\n /** The output signal that will be returned and have methods added it it if writable. */\r\n $output,\r\n /** A function that will get the current value of the source. It could be a signal */\r\n signalValueFn\r\n ] = isReactive<T>(source) ? createFromReactiveSource(source, options) : createFromValue(source as T, options);\r\n\r\n /** THe SignalNode of the output signal. Used when the output is set during value changes. */\r\n const outputNode = $output[SIGNAL];\r\n // can't just use a spread since an option can be undefined.\r\n const defaults = {\r\n ...defaultAnimationOptions,\r\n interpolator: options?.interpolator || (createInterpolator(outputNode.value as NumericValues) as InterpolateFactoryFn<T>)\r\n };\r\n if (options) {\r\n overwriteProperties(defaults, options as Partial<O>);\r\n }\r\n\r\n let delayTimeoutId: ReturnType<typeof setTimeout> | undefined = undefined;\r\n let instanceId = 0;\r\n let state: AnimationState<TState>;\r\n effect((onCleanup) => {\r\n const priorValue = untracked($output);\r\n const [nextValue, overrideOptions] = signalValueFn();\r\n if (nextValue === priorValue) {\r\n // since an array is being passed from signalValueFn, it could be the same value was sent.\r\n return;\r\n }\r\n const animationOptions = overrideOptions ? { ...overwriteProperties({ ...defaults }, overrideOptions) } : defaults;\r\n const interpolate = (overrideOptions?.interpolator || defaults.interpolator)(priorValue, nextValue);\r\n const thisInstanceId = ++instanceId;\r\n\r\n // in case a previous animation was delayed then clear it before it starts.\r\n clearTimeout(delayTimeoutId);\r\n if (animationOptions.delay) {\r\n delayTimeoutId = setTimeout(start, animationOptions.delay);\r\n } else {\r\n start();\r\n }\r\n\r\n function start(): void {\r\n const timeCurrent = Date.now(); // performance.now can't be tested [SAD EMOJI] (switch to Jest?)\r\n state = {\r\n ...initialState,\r\n ...state,\r\n isDone: false,\r\n progress: 0,\r\n timeCurrent,\r\n timeElapsed: 0,\r\n timeDelta: 0,\r\n timeStart: timeCurrent,\r\n };\r\n\r\n stepFn(state, animationOptions); // run initial step function in case animation isn't necessary.\r\n if (state.isDone) {\r\n // don't bother with the animation since its done already.\r\n signalSetFn(outputNode, nextValue);\r\n return;\r\n }\r\n signalSetFn(outputNode, interpolate(state.progress));\r\n requestAnimationFrame(step);\r\n }\r\n \r\n function step(): void {\r\n \r\n if (thisInstanceId !== instanceId) {\r\n return; // another effect has occurred.\r\n }\r\n // I'm not sure if this is necessary. It might have been something I copied from an animation example.\r\n // if (previousTime === time) {\r\n // requestAnimationFrame(step); // no time elapsed.\r\n // return;\r\n // }\r\n const timeCurrent = Date.now(); // performance.now can't be tested [SAD EMOJI] (switch to Jest?)\r\n state.timeDelta = timeCurrent - state.timeCurrent;\r\n state.timeCurrent = timeCurrent;\r\n state.timeElapsed = timeCurrent - state.timeStart;\r\n stepFn(state, animationOptions);\r\n signalSetFn(outputNode, interpolate(state.progress));\r\n if (!state.isDone) {\r\n requestAnimationFrame(step);\r\n }\r\n }\r\n // force stop by incrementing instanceId on destroy.\r\n onCleanup(() => instanceId++);\r\n }, options);\r\n\r\n return $output;\r\n\r\n /** Coerces a source signal from signal input and creates the output signal.. */\r\n function createFromReactiveSource(\r\n reactiveSource: ReactiveSource<T>,\r\n signalOptions: Partial<AnimatedSignalOptions<T, O>> | undefined\r\n ): [\r\n output: SignalGetter<T> & WritableAnimatedSignal<T, O>,\r\n valueFn: () => Readonly<[value: T]>\r\n ] {\r\n const $source = coerceSignal(reactiveSource, signalOptions);\r\n const $output = signal(untracked($source), signalOptions) as SignalGetter<T> & WritableSignal<T> & AnimatedSignal<T, O>;\r\n $output.setOptions = (options) => overwriteProperties(defaults, options);\r\n const signalValueFn = () => [$source()] as const;\r\n return [$output, signalValueFn];\r\n }\r\n\r\n /** Creates a writable source signal and output signal from the initial value. */\r\n function createFromValue(\r\n sourceValue: T,\r\n signalOptions: Partial<AnimatedSignalOptions<T, O>> | undefined\r\n ): [\r\n output: SignalGetter<T> & WritableAnimatedSignal<T, O>,\r\n valueFn: () => Readonly<[value: T, oneTimeOptions: Partial<AnimationOptionsWithInterpolator<T, O>> | undefined]>\r\n ] {\r\n const $output = signal(sourceValue, signalOptions) as SignalGetter<T> & WritableSignal<T> & AnimatedSignal<T, O>;\r\n const [get, set, update] = createSignal<\r\n Readonly<[value: T, options: Partial<AnimationOptionsWithInterpolator<T, O>> | undefined]>\r\n >([sourceValue as T, undefined]);\r\n\r\n $output.set = (x, options?: Partial<AnimationOptionsWithInterpolator<T, O>>) => set([x, options] as const);\r\n $output.setOptions = (options) => overwriteProperties(defaults, options);\r\n $output.update = (updateFn: (value: T) => T, options?: Partial<O & { interpolator: InterpolateFactoryFn<T> }>) =>\r\n update(([value]) => [updateFn(value), options] as const);\r\n return [$output, get];\r\n }\r\n}\r\n/**\r\n * Sets values on {@link target} if they are defined in both {@link target} and {@link values}.\r\n * Different then spread operator as it will ignore undefined values.\r\n * @returns The value of {@link target}.\r\n */\r\nfunction overwriteProperties<T extends object>(target: T, values: Partial<T>): T {\r\n Object.entries(values).forEach(([key, value]) => {\r\n if (key in target && value !== undefined) {\r\n target[key as keyof T] = value as T[keyof T];\r\n }\r\n });\r\n return target;\r\n}\r\n","import { ReactiveSource } from '../../reactive-source';\r\nimport { ValueSource } from '../../value-source';\r\nimport { AnimatedSignal, AnimatedSignalOptions, AnimationOptions, WritableAnimatedSignal, animatedSignalFactory } from './animated-signal-base';\r\nimport { NumericValues } from './interpolation';\r\n\r\n/**\r\n * Options that can be used to overwrite default options when calling {@link SpringSignal.setOptions},\r\n * {@link WritableSpringSignal.set}, or {@link WritableSpringSignal.update}.\r\n */\r\nexport interface SpringOptions extends AnimationOptions {\r\n /** If true, will stay with the bounds of the starting and ending value during transitions. */\r\n clamp: boolean;\r\n /** The degree to suppress spring oscillation. The higher the value, the less movement.*/\r\n damping: number;\r\n /** How close the velocity must be to 0 and progress the final value before the animation is considered done. */\r\n precision: number;\r\n /** \r\n * Effects how quickly the animation changes direction. \r\n * The higher the value, the sooner the spring will reach the end and bounce back.\r\n */\r\n stiffness: number;\r\n}\r\n\r\n\r\n/** Options for {@link springSignal}. */\r\nexport type SpringSignalOptions<T> = AnimatedSignalOptions<T, SpringOptions>;\r\n\r\n/** Same as regular {@link SpringSignalOptions}, but interpolator is not required. */\r\nexport type SpringNumericSignalOptions<T extends NumericValues> = Omit<SpringSignalOptions<T>, 'interpolator'> & Partial<Pick<SpringSignalOptions<T>, 'interpolator'>>;\r\n\r\n/** A signal with a function to set animation parameters returned from {@link springSignal}. */\r\nexport type SpringSignal<T> = AnimatedSignal<T, SpringOptions>;\r\n\r\n/** Returned from returned from {@link springSignal}. It's like a writable a signal, but with extended options when setting and updating. */\r\nexport type WritableSpringSignal<T> = WritableAnimatedSignal<T, SpringOptions>;\r\n\r\nconst DEFAULT_OPTIONS: SpringOptions = {\r\n clamp: false,\r\n damping: 3,\r\n delay: 0,\r\n precision: 0.01,\r\n stiffness: 100\r\n};\r\n\r\n// for some reason extends NumericValues acted weird for number types.\r\nexport function springSignal<V extends ValueSource<number>>(source: V, options?: SpringNumericSignalOptions<number>):\r\n V extends ReactiveSource<number> ? SpringSignal<number> : WritableSpringSignal<number>\r\nexport function springSignal<T extends NumericValues>(source: ValueSource<T>, options?: SpringNumericSignalOptions<T>):\r\n typeof source extends ReactiveSource<T> ? SpringSignal<T> : WritableSpringSignal<T>\r\nexport function springSignal<T>(source: ValueSource<T>, options: SpringSignalOptions<T>):\r\n typeof source extends ReactiveSource<T> ? SpringSignal<T> : WritableSpringSignal<T>\r\n/**\r\n * Creates a signal whose value morphs from the old value to the new over a specified duration.\r\n * @param source Either a value, signal, observable, or function that can be used in a computed function.\r\n * @param options Options for the signal. If a number, number[] or Record<string | number symbol, number>\r\n * is passed then this is not required. Otherwise an interpolator is required to translate the change of the value.\r\n * @example\r\n * ```ts\r\n * const $animatedValue = springSignal(1, { damping: 3, stiffness: 100 });\r\n * function demo(): void {\r\n * $animatedValue.set(5); \r\n * }\r\n * ```\r\n */\r\nexport function springSignal<T, V extends ValueSource<T>>(source: V, options?: Partial<SpringSignalOptions<T>>):\r\n V extends ReactiveSource<T> ? SpringSignal<T> : WritableSpringSignal<T>\r\n{\r\n return animatedSignalFactory(source, options, DEFAULT_OPTIONS, { velocity: 1 }, (state, options) => {\r\n \r\n const dt = state.timeDelta / 1000;\r\n const force = -options.stiffness * (state.progress - 1) ;\r\n const damping = options.damping * state.velocity;\r\n const acceleration = force - damping;\r\n state.velocity += acceleration * dt; \r\n state.progress += state.velocity * dt;\r\n\r\n if (Math.abs(1 - state.progress) < options.precision && Math.abs(state.velocity / dt) <= options.precision / dt) {\r\n state.isDone = true;\r\n state.progress = 1;\r\n return;\r\n }\r\n if (options.clamp && state.progress > 1) {\r\n state.progress = 2 - state.progress;\r\n state.velocity = -state.velocity + damping * dt;\r\n }\r\n });\r\n}\r\n\r\n","import { ReactiveSource } from '../../reactive-source';\r\nimport { ValueSource } from '../../value-source';\r\nimport { AnimatedSignal, AnimatedSignalOptions, AnimationOptions, WritableAnimatedSignal, animatedSignalFactory } from './animated-signal-base';\r\nimport { NumericValues } from './interpolation';\r\n\r\n\r\n\r\n/** A function that alters progress between 0 and 1. */\r\nexport type EasingFn = ((progress: number) => number);\r\n\r\n/**\r\n * Options that can be used to overwrite default options when calling {@link TweenSignal.setOptions},\r\n * {@link WritableTweenSignal.set}, or {@link WritableTweenSignal.update}.\r\n */\r\nexport interface TweenOptions extends AnimationOptions {\r\n /** How long the animation should last. */\r\n duration: number;\r\n /** An easing function that distorts progress. */\r\n easing: EasingFn;\r\n}\r\n\r\n/** Options for {@link tweenSignal}. */\r\nexport type TweenSignalOptions<T> = AnimatedSignalOptions<T, TweenOptions>;\r\n\r\n/** Same as regular {@link SpringSignalOptions}, but interpolator is not required. */\r\nexport type TweenNumericSignalOptions<T extends NumericValues> = Omit<TweenSignalOptions<T>, 'interpolator'> & Partial<Pick<TweenSignalOptions<T>, 'interpolator'>>;\r\n\r\n/** A signal with a function to set animation parameters returned from {@link tweenSignal}. */\r\nexport type TweenSignal<T> = AnimatedSignal<T, TweenOptions>;\r\n\r\n/** Returned from returned from {@link tweenSignal}. It's like a writable a signal, but with extended options when setting and updating. */\r\nexport type WritableTweenSignal<T> = WritableAnimatedSignal<T, TweenOptions>;\r\n\r\nconst DEFAULT_OPTIONS: TweenOptions = {\r\n delay: 0,\r\n duration: 400,\r\n easing: (x: number) => x\r\n};\r\n// for some reason extends TweenNumericValues acted weird for number types.\r\nexport function tweenSignal<V extends ValueSource<number>>(source: V, options?: TweenNumericSignalOptions<number>):\r\n V extends ReactiveSource<number> ? TweenSignal<number> : WritableTweenSignal<number>\r\nexport function tweenSignal<T extends NumericValues>(source: ValueSource<T>, options?: TweenNumericSignalOptions<T>):\r\n typeof source extends ReactiveSource<T> ? TweenSignal<T> : WritableTweenSignal<T>\r\nexport function tweenSignal<T>(source: ValueSource<T>, options: TweenSignalOptions<T>):\r\n typeof source extends ReactiveSource<T> ? TweenSignal<T> : WritableTweenSignal<T>\r\n/**\r\n * Creates a signal whose value morphs from the old value to the new over a specified duration.\r\n * @param source Either a value, signal, observable, or function that can be used in a computed function.\r\n * @param options Options for the signal. If a number, number[] or Record<string | number symbol, number>\r\n * is passed then this is not required. Otherwise an interpolator is required to translate the change of the value.\r\n * @example\r\n * ```ts\r\n * const fastLinearChange = tweenSignal(1);\r\n * const slowEaseInChange = tweenSignal(1, { duration: 5000, easing: (x) => return x ** 2; });\r\n * function demo(): void {\r\n * fastLinearChange.set(5); // in 400ms will display something like 1, 1.453, 2.134, 3.521, 4.123, 5.\r\n * slowEaseInChange.set(5, { duration: 10000 }); // in 10000ms will display something like 1, 1.21, 1.4301...\r\n * }\r\n * ```\r\n */\r\nexport function tweenSignal<T, V extends ValueSource<T>>(source: V, options?: Partial<TweenSignalOptions<T>>):\r\n V extends ReactiveSource<T> ? TweenSignal<T> : WritableTweenSignal<T>\r\n{\r\n return animatedSignalFactory(\r\n source, \r\n options, \r\n DEFAULT_OPTIONS, \r\n { }, \r\n (state, options) => {\r\n const timeProgress = options.duration > 0 ? Math.min(1, state.timeElapsed / options.duration) : 1;\r\n state.isDone = timeProgress === 1;\r\n state.progress = options.easing(timeProgress);\r\n });\r\n}\r\n\r\n\r\n","import { CreateSignalOptions, Injector, Signal, ValueEqualityFn, computed, effect, isSignal, signal, untracked } from '@angular/core';\r\nimport { createSignal } from '@angular/core/primitives/signals';\r\nimport { isReactiveSourceFunction } from '../internal/reactive-source-utilities';\r\nimport { coerceSignal } from '../internal/signal-coercion';\r\nimport { TransformedSignal } from '../internal/transformed-signal';\r\nimport { asReadonlyFnFactory, getDestroyRef } from '../internal/utilities';\r\nimport { ToSignalInput } from '../reactive-source';\r\n\r\n/** Either something with a .subscribe() function or a promise. */\r\nexport type AsyncSource<T> = ToSignalInput<T> | Promise<T>;\r\n\r\n/** Options for {@link asyncSignal}. */\r\nexport interface AsyncSignalOptions<T> extends Pick<CreateSignalOptions<T>, 'debugName'> {\r\n /** The default value before the first emission. */\r\n defaultValue?: T;\r\n /** Equal functions run on values emitted from async sources. */\r\n equal?: ValueEqualityFn<T>;\r\n /** This is only used if a signal is created from an observable. */\r\n injector?: Injector;\r\n /**\r\n * If true, then the passed value will eagerly be read and it will throw an error if it hasn't been set.\r\n * You shouldn't use defaultValue in this case.\r\n */\r\n requireSync?: boolean;\r\n}\r\n\r\n/** An signal that returns values from an async source that can change. */\r\nexport type AsyncSignal<T> = TransformedSignal<AsyncSource<T>, T>;\r\n\r\nconst VOID_FN = () => { /* do nothing */ };\r\n\r\nenum AsyncSignalStatus {\r\n Error,\r\n Ok,\r\n NotSet\r\n}\r\n\r\ninterface AsyncSignalState<T> {\r\n err?: unknown;\r\n status: AsyncSignalStatus;\r\n value: T;\r\n}\r\n\r\nexport function asyncSignal<T>(\r\n valueSource: AsyncSource<T>,\r\n options: AsyncSignalOptions<T> & ({ defaultValue: T; requireSync?: false } | { defaultValue?: undefined; requireSync: true })\r\n): AsyncSignal<T>;\r\nexport function asyncSignal<T>(\r\n valueSource: AsyncSource<T>,\r\n options?: AsyncSignalOptions<T | undefined> & { defaultValue?: undefined; requireSync?: false }\r\n): AsyncSignal<T | undefined>;\r\nexport function asyncSignal<T>(\r\n valueSource: Signal<AsyncSource<T>> | (() => AsyncSource<T>),\r\n options: AsyncSignalOptions<T> & ({ defaultValue: T; requireSync?: false } | { defaultValue?: undefined; requireSync: true })\r\n): Signal<T>;\r\nexport function asyncSignal<T>(\r\n valueSource: Signal<AsyncSource<T>> | (() => AsyncSource<T>),\r\n options?: AsyncSignalOptions<T | undefined> & { defaultValue?: undefined; requireSync?: false }\r\n): Signal<T | undefined>;\r\n/**\r\n * Takes an async source (Promise, Observable) or signal/function that returns an async source\r\n * and returns that source's values as part of a signal. Kind of like an rxjs flattening operator.\r\n * When the async source changes, the old source is immediately released and the new source is listened.\r\n * @param valueSource A Promise or Subscribable to create writable signal,\r\n * otherwise a signal or function that returns a Promise or Subscribable.\r\n * @param options The options for the async signal\r\n * @returns a signal that returns values from the async source..\r\n * @example\r\n * ```ts\r\n * $id = signal(0);\r\n * // call getCustomer every time $id changes.\r\n * $customer = asyncSignal(() => this.$id() !== 0 ? this.getCustomer(this.$id()) : undefined);\r\n *\r\n * constructor() {\r\n * // writable overload can switch versions.\r\n * const artificialWritableExampleSource1 = new BehaviorSubject(1);\r\n * const $writable = asyncSignal(artificialWritableExampleSource1);\r\n * const artificialWritableExampleSource2 = new BehaviorSubject(2);\r\n * $writable.set(artificialWritableExampleSource2);\r\n * }\r\n * ```\r\n */\r\nexport function asyncSignal<T>(\r\n valueSource: AsyncSource<T> | Signal<AsyncSource<T>> | (() => AsyncSource<T>),\r\n options: AsyncSignalOptions<T | undefined> = {}\r\n): Signal<T | undefined> {\r\n return isSignal(valueSource)\r\n ? createFromSignal(valueSource, options)\r\n : isReactiveSourceFunction(valueSource)\r\n ? createFromReactiveSourceFunction(valueSource, options)\r\n : createFromValue(valueSource, options);\r\n}\r\n\r\n/** Called if this is a function that's NOT a signal to create a readonly AsyncSignal. */\r\nfunction createFromReactiveSourceFunction<T>(\r\n reactiveFn: () => AsyncSource<T>,\r\n options: AsyncSignalOptions<T | undefined>\r\n): Signal<T | undefined> {\r\n const $input = coerceSignal(reactiveFn, { initialValue: undefined as AsyncSource<T> | undefined, injector: options.injector });\r\n return createFromSignal($input, options);\r\n}\r\n\r\n/** Creates the writable version of an async signal from an initial async source. */\r\nfunction createFromValue<T>(\r\n initialSource: AsyncSource<T>,\r\n options: AsyncSignalOptions<T | undefined>\r\n): AsyncSignal<T | undefined> {\r\n const [get, set, update] = createSignal(initialSource);\r\n const $output = createFromSignal(get, options) as AsyncSignal<T | undefined>;\r\n $output.asReadonly = asReadonlyFnFactory($output);\r\n $output.set = set;\r\n $output.update = update;\r\n return $output;\r\n}\r\n\r\n/** Creates a readonly version of an async signal from another signal returning an async source. */\r\nfunction createFromSignal<T>(\r\n $input: Signal<AsyncSource<T>>,\r\n options: AsyncSignalOptions<T | undefined>\r\n): Signal<T | undefined> {\r\n /**\r\n * The current source of asynchronous values.\r\n * Initially this is undefined because we're trying to defer reading the source until the effect first runs.\r\n * If requireSync is true then it will get the value immediately\r\n */\r\n let currentSource: AsyncSource<T> | undefined;\r\n /** An \"unsubscribe\" function. */\r\n let currentListenerCleanupFn: () => void;\r\n\r\n // if requireSync is true, the set the state as NotSet, otherwise it's Ok. Being NotSet and read will throw an error.\r\n const $state = signal<AsyncSignalState<T | undefined>>(\r\n options.requireSync\r\n ? { status: AsyncSignalStatus.NotSet, value: options.defaultValue }\r\n : { status: AsyncSignalStatus.Ok, value: options.defaultValue }\r\n );\r\n\r\n // if requireSync is true then immediately start listening.\r\n if (options?.requireSync) {\r\n currentSource = untracked($input);\r\n currentListenerCleanupFn = updateListener(currentSource);\r\n } else {\r\n currentListenerCleanupFn = () => { /* do nothing */ };\r\n }\r\n effect(\r\n () => {\r\n // Initially this used to only run inside a conditional branch if the state was OK.\r\n // The problem with that was if another source had an error, the error would bubble up, since we're not catching it.\r\n const nextSource = $input();\r\n if (nextSource === currentSource) {\r\n // don't start listening to an already listened to source.\r\n // This is only necessary in case requireSync was true and this is the first effect was run.\r\n return;\r\n }\r\n // manually cleanup old listener.\r\n currentListenerCleanupFn();\r\n // store the currentSource so it can be used in next invocation of effect.\r\n currentSource = nextSource;\r\n // Call the updateListener process and set currentListenerCleanupFn from result.\r\n // (This is untracked because a signal may be used inside the source and cause additional invocations.)\r\n currentListenerCleanupFn = untracked(() => updateListener(nextSource));\r\n },\r\n { injector: options.injector }\r\n );\r\n\r\n // Call cleanup on the last listener. The effect cleanup can't be used because of the risk of initially repeating subscriptions.\r\n getDestroyRef(asyncSignal, options.injector).onDestroy(() => currentListenerCleanupFn());\r\n\r\n return computed<T | undefined>(\r\n () => {\r\n const { err, status, value } = $state();\r\n switch (status) {\r\n case AsyncSignalStatus.Error:\r\n throw new Error('Error in Async Source', { cause: err });\r\n case AsyncSignalStatus.NotSet:\r\n throw new Error('requireSync is true, but no value was returned from asynchronous source.');\r\n case AsyncSignalStatus.Ok:\r\n return value;\r\n }\r\n },\r\n options // pass along the debugName and equal options.\r\n );\r\n\r\n /** Starts listening to the new async value, and returns the cleanup function. */\r\n function updateListener(asyncSource: AsyncSource<T>): () => void {\r\n // This was removed because it was confusing that a value could be a subscribable and a subscribable could be converted to a signal.\r\n // if (asyncSource === undefined) {\r\n // return VOID_FN; // don't listen to anything and return a dummy unsubscribe function.\r\n // }\r\n if ('subscribe' in asyncSource) {\r\n const unsubscribe = asyncSource.subscribe({ error: setError, next: setValue });\r\n return () => unsubscribe.unsubscribe();\r\n }\r\n asyncSource.then(setValue, setError).catch(setError);\r\n return VOID_FN; // there is no way to cleanup a promise that I know of.\r\n\r\n /** Sets the state of errored if an error hadn't already occurred. */\r\n function setError(err: unknown): void {\r\n $state.update((cur) =>\r\n cur.status === AsyncSignalStatus.Error ? cur : { err, status: AsyncSignalStatus.Error, value: cur.value }\r\n );\r\n }\r\n /** Updates value if the status isn't error. */\r\n function setValue(value: T): void {\r\n $state.update((cur) => (cur.status === AsyncSignalStatus.Error ? cur : { status: AsyncSignalStatus.Ok, value }));\r\n }\r\n }\r\n}\r\n","/** The status of the timer. */\r\nexport enum TimerStatus { Destroyed, Paused, Running, Stopped }\r\n\r\n/** Options for the timer */\r\nexport interface TimerInternalOptions {\r\n /** Callback to when status changes */\r\n onStatusChange?: (status: TimerStatus) => void;\r\n /** Callback to call when timer ticks. */\r\n onTick?: (tickCount: number) => void;\r\n /** If true, will immediately start time. */\r\n runAtStart?: boolean;\r\n}\r\n\r\n/** Unique properties for the mode the timer is in. */\r\ninterface TimerInternalRunner {\r\n onTickComplete: () => void;\r\n dueTime: number;\r\n}\r\n\r\n/** A general timer. */\r\nexport class TimerInternal {\r\n /** Gets or set intervalTime if this was started with an interval. Will throw if not initially passed an interval. */\r\n get intervalTime() {\r\n TimerInternal.assertHasIntervalRunner(this);\r\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\r\n return this.intervalRunner!.dueTime;\r\n }\r\n set intervalTime(value: number) {\r\n TimerInternal.assertHasIntervalRunner(this);\r\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\r\n this.updateRunnerDueTime(value, this.intervalRunner!);\r\n }\r\n\r\n /** Gets the number of ticks since start. */\r\n get ticks(): number { return this.tickCount; }\r\n\r\n /** Gets or sets the timeoutTime. */\r\n get timeoutTime() { return this.timeoutRunner.dueTime; }\r\n set timeoutTime(value: number) { this.updateRunnerDueTime(value, this.timeoutRunner); }\r\n\r\n /** Readonly status of the timer. */\r\n get timerStatus() { return this.status; }\r\n\r\n /** The runner that is used in intervalMode */\r\n private readonly intervalRunner?: TimerInternalRunner;\r\n /** The time the last tick completed. Doesn't have to be the actual last time. */\r\n private lastCompleteTime = Date.now();\r\n /** Called when the status changes. */\r\n private readonly onStatusChangeCallback: (status: TimerStatus) => void;\r\n /** Called when the timer completes. */\r\n private readonly onTickCallback: (tickCount: number) => void;\r\n /** When paused, stores what time was remaining. */\r\n private remainingTimeAtPause = 0;\r\n /** The currently active runner. Initially timeout, and then switches to interval. */\r\n private runner: TimerInternalRunner;\r\n /** The current status of timer. Do not modify directly! All changes should go through setStatus. */\r\n private status = TimerStatus.Stopped;\r\n /** The runner that is used in timeout mode. */\r\n private readonly timeoutRunner: TimerInternalRunner;\r\n /** The count of ticks */\r\n private tickCount = 0;\r\n /** The id of the last timeout. */\r\n private timeoutId?: ReturnType<typeof setTimeout>;\r\n\r\n /**\r\n * passing only timeoutTime will have this behave like a timeout.\r\n * passing intervalTime will have this first execute timeoutTime then intervalTime.\r\n */\r\n constructor(\r\n timeoutTime: number,\r\n intervalTime?: number,\r\n options?: TimerInternalOptions) {\r\n\r\n this.runner = this.timeoutRunner = {\r\n dueTime: timeoutTime,\r\n onTickComplete: this.onTimeoutTickComplete.bind(this)\r\n };\r\n\r\n if (intervalTime !== undefined) {\r\n this.intervalRunner = {\r\n dueTime: intervalTime,\r\n onTickComplete: this.tickStart.bind(this)// loop\r\n };\r\n }\r\n\r\n this.onTickCallback = options?.onTick ?? (() => undefined);\r\n this.onStatusChangeCallback = options?.onStatusChange ?? (() => undefined);\r\n\r\n if (options?.runAtStart) {\r\n this.setStatus(TimerStatus.Running);\r\n this.tickStart();\r\n }\r\n }\r\n\r\n /** Clears the current tick and prevents any future processing. */\r\n destroy(): void {\r\n clearTimeout(this.timeoutId);\r\n this.setStatus(TimerStatus.Destroyed);\r\n }\r\n\r\n /** Pauses the timer. */\r\n pause(): void {\r\n if (this.status === TimerStatus.Running) {\r\n this.remainingTimeAtPause = this.getRemainingTime();\r\n clearTimeout(this.timeoutId);\r\n this.setStatus(TimerStatus.Paused);\r\n }\r\n }\r\n\r\n /** Resumes the timer if it was paused, otherwise does nothing. */\r\n resume(): void {\r\n if (this.status === TimerStatus.Paused) {\r\n this.setStatus(TimerStatus.Running);\r\n // if duration is adjusted by a signal then this is a problem.\r\n this.lastCompleteTime = Date.now() - (this.runner.dueTime - this.remainingTimeAtPause);\r\n this.tickStart();\r\n }\r\n }\r\n\r\n /** Start or restarts the timer as long as it isn't destroyed. */\r\n start(): void {\r\n if (this.status !== TimerStatus.Destroyed) {\r\n this.setStatus(TimerStatus.Running);\r\n this.tickCount = 0;\r\n this.runner = this.timeoutRunner;\r\n this.lastCompleteTime = Date.now();\r\n this.tickStart();\r\n }\r\n }\r\n\r\n /** Throws if intervalRunner isn't defined. */\r\n private static assertHasIntervalRunner(ti: TimerInternal): boolean {\r\n if (!ti.intervalRunner) {\r\n throw new Error('This timer was not configured for intervals');\r\n }\r\n return true;\r\n }\r\n\r\n /** Determines the remaining time. */\r\n private getRemainingTime(): number {\r\n return this.runner.dueTime - (Date.now() - this.lastCompleteTime);\r\n }\r\n\r\n /** Switch to intervalRunner or set as stopped. */\r\n private onTimeoutTickComplete(): void {\r\n if (this.intervalRunner) {\r\n this.runner = this.intervalRunner;\r\n this.tickStart(); // begin intervalLoop\r\n }\r\n else {\r\n this.setStatus(TimerStatus.Stopped);\r\n }\r\n }\r\n\r\n private setStatus(status: TimerStatus): void {\r\n if (this.status !== status) {\r\n this.status = status;\r\n this.onStatusChangeCallback(status);\r\n }\r\n }\r\n\r\n /**\r\n * Handles when a tick is complete.\r\n * If for some reason there is remaining time, it will restart the tick.\r\n * Otherwise it will increase the internalCount, execute the callback, update the completed,\r\n * and call the current runner's onTickComplete method so that it handles the next step.\r\n */\r\n private tickComplete(): void {\r\n const remainingTime = this.getRemainingTime();\r\n if (remainingTime > 0) { // this could occur if the end time changed.\r\n this.tickStart();\r\n }\r\n else {\r\n ++this.tickCount;\r\n this.onTickCallback(this.tickCount);\r\n this.lastCompleteTime = Date.now() + this.getRemainingTime();\r\n this.runner.onTickComplete();\r\n }\r\n }\r\n\r\n /** Attempts to starts the tick timeout. */\r\n private tickStart(): void {\r\n clearTimeout(this.timeoutId);\r\n if (this.status === TimerStatus.Running) {\r\n this.timeoutId = setTimeout(this.tickComplete.bind(this), this.getRemainingTime());\r\n }\r\n }\r\n\r\n /** Sets the dueTime on the runner and if necessary starts the timer. */\r\n private updateRunnerDueTime(dueTime: number, targetRunner: TimerInternalRunner): void {\r\n const oldTime = targetRunner.dueTime;\r\n targetRunner.dueTime = dueTime;\r\n if (targetRunner === this.runner && this.status === TimerStatus.Running && oldTime > dueTime) {\r\n this.tickStart();\r\n }\r\n }\r\n}\r\n","import { Injector, Signal, effect, isSignal } from '@angular/core';\r\nimport { coerceSignal } from './internal/signal-coercion';\r\nimport { isReactive } from './internal/reactive-source-utilities';\r\nimport { ReactiveSource, ReactiveValue } from './reactive-source';\r\n\r\n/**\r\n * A constant value or a {@link ReactiveSource}.\r\n * @typeParam T The type of the value or the type emitted by the {@link ReactiveSource}.\r\n */\r\nexport type ValueSource<T> = T | Reacti