import { ReadonlySignal, useComputed, useSignal, useSignalEffect, } from "@preact-signals/unified-signals"; import { AnimatableValue, DerivedValue, SharedValue, runOnJS, useAnimatedReaction, useSharedValue, withSpring, withTiming, } from "react-native-reanimated"; /** * * @example * ```tsx * import { useSharedValue } from "react-native-reanimated"; * import { useSignalOfSharedValue } from "@preact-signals/utils/integrations/reanimated"; * * function ExampleComponent() { * const sharedValue = useSharedValue(0); * const signal = useSignalOfSharedValue(sharedValue); * * useSignalEffect(() => { * // running some code on JS thread * }) * * * // ... rest of the component * } * ``` */ export const useSignalOfSharedValue = (shared: Readonly>) => { const sig = useSignal(shared.value); const updateSignal = (newValue: T) => { sig.value = newValue; }; useAnimatedReaction( () => shared.value, (newValue) => runOnJS(updateSignal)(newValue), [shared] ); return sig as ReadonlySignal; }; export const useComputedOfSharedValue = ( shared: Readonly>, compute: (v: T) => TResult ) => { const sig = useSignalOfSharedValue(shared); return useComputed(() => compute(sig.value)); }; /** * @description creates a shared value from a signal. * * @example * ```tsx * function ExampleComponent() { * const signal = useSignal(0); * const sharedValue = useSharedValueOfSignal(signal); * * // Now you can use the shared value in your component * // ... * * return ({ * opacity: sharedValue.value, * }))} />; * } * ``` */ export const useSharedValueOfSignal = ( _sig: ReadonlySignal ): Readonly> => { const shared = useSharedValue(_sig.peek()); useSignalEffect(() => { shared.value = _sig.value; }); return shared; }; const defaultSharedValueSetter = ( shared: SharedValue, newValue: T ): unknown => (shared.value = newValue); export type SignalInteropSetter = ( target: TTarget, source: TSource ) => unknown; export type SharedValueSetter = SignalInteropSetter, T>; export const useSharedValueOfAccessor = ( accessor: () => T, setter: SharedValueSetter = defaultSharedValueSetter ): DerivedValue => { // memoizing accessor value to not to break animations when accessor deps are changed but value is not const accessorSignal = useComputed(accessor); const shared = useSharedValue(accessorSignal.peek()); useSignalEffect(() => { setter(shared, accessorSignal.value); }); return shared; }; /** * @description applies withSpring or withTiming to shared value of accessor * @example * ```tsx * const maxLength = 10 * function ExampleComponent() { * const input = useSignal('') * const progress = useAnimatedSharedValueOfAccessor( * () => input.value.length / maxLength, * { * type: 'spring', * params: { * damping: 10, * } * }) * * return ( * * input.value = v} /> * ({ * alignSelf: 'stretch', * height: 10, * transform: [{ scaleX: progress.value }], * }))} /> * * ); * } * ``` * */ export const useAnimatedSharedValueOfAccessor = ( accessor: () => T, animateOptions: | { type: "timing"; params?: Parameters[1]; } | { type: "spring"; params?: Parameters[1] } ) => useSharedValueOfAccessor(accessor, (shared, newValue) => { if (animateOptions?.type === "timing") { shared.value = withTiming(newValue, animateOptions.params); return; } if (animateOptions?.type === "spring") { shared.value = withSpring(newValue, animateOptions.params); return; } throw new Error("unknown animateOptions type"); }); export const useSpringSharedValueOfAccessor = ( accessor: () => T, params?: Parameters[1] ) => useAnimatedSharedValueOfAccessor(accessor, { type: "spring", params, }); export const useTimingSharedValueOfAccessor = ( accessor: () => T, params?: Parameters[1] ) => useAnimatedSharedValueOfAccessor(accessor, { type: "timing", params, });