import { useRef } from 'react'; import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'; import { useSharedValue } from 'react-native-reanimated'; import type { DynamicStyleProp, ExcludeFunctionKeys, UseDynamicAnimationState } from './types'; const fallback = () => ({}); /** * Creates a freeform animation state controller — an alternative to * {@link useAnimationState} that doesn't constrain you to pre-defined variant * keys. * * Unlike `useAnimationState` (which switches between named variants), this hook * lets you call `animateTo(nextStyle)` with any style object at any time. It is * the right choice when the target styles are computed dynamically rather than * chosen from a fixed set. * * ## Comparison with useAnimationState * * | Feature | `useAnimationState` | `useDynamicAnimation` | * |---|---|---| * | Style source | Pre-defined variant record | Any style object at call time | * | State shape | Named keys (`from`, `to`, …) | Arbitrary `DynamicStyleProp` | * | Best for | Known states (tabs, toggles, steps) | Computed styles (drag, scroll, gesture) | * * ```ts * const dynamic = useDynamicAnimation(() => ({ opacity: 0, translateX: -100 })) * * // Drive a MotiView * * * // Animate to any style * dynamic.animateTo({ opacity: 1, translateX: 0 }) * ``` * * @param initialState - A factory returning the initial style (called once). * Defaults to an empty object. * @returns A stable controller with `current` (the active style), `__state` * (a Reanimated shared value to pass to a Moti component's `state` prop), * and `animateTo(nextStyle | fn)`. */ export default function useDynamicAnimation< _Animate = ViewStyle | TextStyle | ImageStyle, Animate = ExcludeFunctionKeys<_Animate>, >(initialState: () => DynamicStyleProp = fallback) { // biome-ignore lint/plugin: lazy ref-init idiom — the ref is populated on first render below, so its initial value must be typed as the eventual shape const initializer = useRef<{ value: DynamicStyleProp }>(null as unknown as { value: DynamicStyleProp }); if (initializer.current === null) initializer.current = { value: initialState() }; const __state = useSharedValue(initializer.current.value); // biome-ignore lint/plugin: lazy ref-init idiom — the ref is populated on first render below, so its initial value must be typed as the eventual shape const controller = useRef>(null as unknown as UseDynamicAnimationState); if (controller.current === null) { // biome-ignore lint/plugin: the generic Animate can't be unified with the default DynamicStyleProp shape of the object literal at this call-site controller.current = { __state, get current() { return __state.value; }, animateTo(nextStateOrFunction) { 'worklet'; const nextStyle = typeof nextStateOrFunction === 'function' ? // biome-ignore lint/suspicious/noExplicitAny: worklet function cast — shared value type isn't narrowable without any // biome-ignore lint/plugin: worklet function cast — shared value type isn't narrowable without a cast (nextStateOrFunction as (s: any) => any)(__state.value) : nextStateOrFunction; // biome-ignore lint/suspicious/noExplicitAny: shared value assignment — generic Animate can't be expressed as the SharedValue's inferred type // biome-ignore lint/plugin: shared value assignment — generic Animate can't be expressed as the SharedValue's inferred type __state.value = nextStyle as any; }, } as UseDynamicAnimationState; } return controller.current; }