react-native-reanimated
Version: 
More powerful alternative to Animated library for React Native.
135 lines (120 loc) • 3.84 kB
text/typescript
;
import type {
  AnimatableValue,
  Animation,
  AnimationObject,
  ReduceMotion,
  Timestamp,
} from '../commonTypes';
import { logger } from '../logger';
import type { ClampAnimation } from './commonTypes';
import {
  defineAnimation,
  getReduceMotionForAnimation,
  recognizePrefixSuffix,
} from './util';
type withClampType = <T extends number | string>(
  config: {
    min?: T;
    max?: T;
  },
  clampedAnimation: T
) => T;
export const withClamp = function <T extends number | string>(
  config: { min?: T; max?: T; reduceMotion?: ReduceMotion },
  _animationToClamp: AnimationObject<T> | (() => AnimationObject<T>)
): Animation<ClampAnimation> {
  'worklet';
  return defineAnimation<ClampAnimation, AnimationObject<T>>(
    _animationToClamp,
    (): ClampAnimation => {
      'worklet';
      const animationToClamp =
        typeof _animationToClamp === 'function'
          ? _animationToClamp()
          : _animationToClamp;
      const strippedMin =
        config.min === undefined
          ? undefined
          : recognizePrefixSuffix(config.min).strippedValue;
      const strippedMax =
        config.max === undefined
          ? undefined
          : recognizePrefixSuffix(config.max).strippedValue;
      function clampOnFrame(
        animation: ClampAnimation,
        now: Timestamp
      ): boolean {
        const finished = animationToClamp.onFrame(animationToClamp, now);
        if (animationToClamp.current === undefined) {
          logger.warn(
            "Error inside 'withClamp' animation, the inner animation has invalid current value"
          );
          return true;
        } else {
          const { prefix, strippedValue, suffix } = recognizePrefixSuffix(
            animationToClamp.current
          );
          let newValue;
          if (strippedMax !== undefined && strippedMax < strippedValue) {
            newValue = strippedMax;
          } else if (strippedMin !== undefined && strippedMin > strippedValue) {
            newValue = strippedMin;
          } else {
            newValue = strippedValue;
          }
          animation.current =
            typeof animationToClamp.current === 'number'
              ? newValue
              : `${prefix === undefined ? '' : prefix}${newValue}${
                  suffix === undefined ? '' : suffix
                }`;
        }
        return finished;
      }
      function onStart(
        animation: Animation<any>,
        value: AnimatableValue,
        now: Timestamp,
        previousAnimation: Animation<any> | null
      ): void {
        animation.current = value;
        animation.previousAnimation = animationToClamp;
        const animationBeforeClamped = previousAnimation?.previousAnimation;
        if (
          config.max !== undefined &&
          config.min !== undefined &&
          config.max < config.min
        ) {
          logger.warn(
            'Wrong config was provided to withClamp. Min value is bigger than max'
          );
        }
        animationToClamp.onStart(
          animationToClamp,
          /**
           * Provide the current value of the previous animation of the clamped
           * animation so we can animate from the original "un-truncated" value
           */
          animationBeforeClamped?.current || value,
          now,
          animationBeforeClamped
        );
      }
      const callback = (finished?: boolean): void => {
        if (animationToClamp.callback) {
          animationToClamp.callback(finished);
        }
      };
      return {
        isHigherOrder: true,
        onFrame: clampOnFrame,
        onStart,
        current: animationToClamp.current!,
        callback,
        previousAnimation: null,
        reduceMotion: getReduceMotionForAnimation(config.reduceMotion),
      };
    }
  );
} as withClampType;