UNPKG

create-expo-cljs-app

Version:

Create a react native application with Expo and Shadow-CLJS!

282 lines (256 loc) 8.54 kB
import { Animation, AnimationObject, HigherOrderAnimation, NextAnimation, AnimatableValue, Timestamp, } from './commonTypes'; /* global _WORKLET */ import { ParsedColorArray, convertToHSVA, isColor, toRGBA } from '../Colors'; import { AnimatedStyle, SharedValue } from '../commonTypes'; import { DelayAnimation } from './delay'; import NativeReanimatedModule from '../NativeReanimated'; import { RepeatAnimation } from './repeat'; import { SequenceAnimation } from './sequence'; import { StyleLayoutAnimation } from './styleAnimation'; let IN_STYLE_UPDATER = false; export type UserUpdater = () => AnimatedStyle; export function initialUpdaterRun<T>(updater: () => T): T { IN_STYLE_UPDATER = true; const result = updater(); IN_STYLE_UPDATER = false; return result; } interface RecognizedPrefixSuffix { prefix?: string; suffix?: string; strippedValue: number; } function recognizePrefixSuffix(value: string | number): RecognizedPrefixSuffix { 'worklet'; if (typeof value === 'string') { const match = value.match( /([A-Za-z]*)(-?\d*\.?\d*)([eE][-+]?[0-9]+)?([A-Za-z%]*)/ ); if (!match) { throw Error( "Couldn't parse animation value. Check if there isn't any typo." ); } const prefix = match[1]; const suffix = match[4]; // number with scientific notation const number = match[2] + (match[3] ?? ''); return { prefix, suffix, strippedValue: parseFloat(number) }; } else { return { strippedValue: value }; } } function decorateAnimation<T extends AnimationObject | StyleLayoutAnimation>( animation: T ): void { 'worklet'; if ((animation as HigherOrderAnimation).isHigherOrder) { return; } const baseOnStart = (animation as Animation<AnimationObject>).onStart; const baseOnFrame = (animation as Animation<AnimationObject>).onFrame; const animationCopy = Object.assign({}, animation); delete animationCopy.callback; const prefNumberSuffOnStart = ( animation: Animation<AnimationObject>, value: string | number, timestamp: number, previousAnimation: Animation<AnimationObject> ) => { // recognize prefix, suffix, and updates stripped value on animation start const { prefix, suffix, strippedValue } = recognizePrefixSuffix(value); animation.__prefix = prefix; animation.__suffix = suffix; animation.strippedCurrent = strippedValue; const { strippedValue: strippedToValue } = recognizePrefixSuffix( animation.toValue as string | number ); animation.current = strippedValue; animation.startValue = strippedValue; animation.toValue = strippedToValue; if (previousAnimation && previousAnimation !== animation) { previousAnimation.current = previousAnimation.strippedCurrent; } baseOnStart(animation, strippedValue, timestamp, previousAnimation); animation.current = (animation.__prefix ?? '') + animation.current + (animation.__suffix ?? ''); if (previousAnimation && previousAnimation !== animation) { previousAnimation.current = (previousAnimation.__prefix ?? '') + previousAnimation.current + (previousAnimation.__suffix ?? ''); } }; const prefNumberSuffOnFrame = ( animation: Animation<AnimationObject>, timestamp: number ) => { animation.current = animation.strippedCurrent; const res = baseOnFrame(animation, timestamp); animation.strippedCurrent = animation.current; animation.current = (animation.__prefix ?? '') + animation.current + (animation.__suffix ?? ''); return res; }; const tab = ['H', 'S', 'V', 'A']; const colorOnStart = ( animation: Animation<AnimationObject>, value: string | number, timestamp: Timestamp, previousAnimation: Animation<AnimationObject> ): void => { let HSVAValue: ParsedColorArray; let HSVACurrent: ParsedColorArray; let HSVAToValue: ParsedColorArray; const res: Array<number> = []; if (isColor(value)) { HSVACurrent = convertToHSVA(animation.current); HSVAValue = convertToHSVA(value); if (animation.toValue) { HSVAToValue = convertToHSVA(animation.toValue); } } tab.forEach((i, index) => { animation[i] = Object.assign({}, animationCopy); animation[i].current = HSVACurrent[index]; animation[i].toValue = HSVAToValue ? HSVAToValue[index] : undefined; animation[i].onStart( animation[i], HSVAValue[index], timestamp, previousAnimation ? previousAnimation[i] : undefined ); res.push(animation[i].current); }); animation.current = toRGBA(res as ParsedColorArray); }; const colorOnFrame = ( animation: Animation<AnimationObject>, timestamp: Timestamp ): boolean => { const HSVACurrent = convertToHSVA(animation.current); const res: Array<number> = []; let finished = true; tab.forEach((i, index) => { animation[i].current = HSVACurrent[index]; // @ts-ignore: disable-next-line finished &= animation[i].onFrame(animation[i], timestamp); res.push(animation[i].current); }); animation.current = toRGBA(res as ParsedColorArray); return finished; }; const arrayOnStart = ( animation: Animation<AnimationObject>, value: Array<number>, timestamp: Timestamp, previousAnimation: Animation<AnimationObject> ): void => { value.forEach((v, i) => { animation[i] = Object.assign({}, animationCopy); animation[i].current = v; animation[i].toValue = (animation.toValue as Array<number>)[i]; animation[i].onStart( animation[i], v, timestamp, previousAnimation ? previousAnimation[i] : undefined ); }); animation.current = value; }; const arrayOnFrame = ( animation: Animation<AnimationObject>, timestamp: Timestamp ): boolean => { let finished = true; (animation.current as Array<number>).forEach((v, i) => { // @ts-ignore: disable-next-line finished &= animation[i].onFrame(animation[i], timestamp); (animation.current as Array<number>)[i] = animation[i].current; }); return finished; }; animation.onStart = ( animation: Animation<AnimationObject>, value: number, timestamp: Timestamp, previousAnimation: Animation<AnimationObject> ) => { if (isColor(value)) { colorOnStart(animation, value, timestamp, previousAnimation); animation.onFrame = colorOnFrame; return; } else if (Array.isArray(value)) { arrayOnStart(animation, value, timestamp, previousAnimation); animation.onFrame = arrayOnFrame; return; } else if (typeof value === 'string') { prefNumberSuffOnStart(animation, value, timestamp, previousAnimation); animation.onFrame = prefNumberSuffOnFrame; return; } baseOnStart(animation, value, timestamp, previousAnimation); }; } type AnimationToDecoration< T extends AnimationObject | StyleLayoutAnimation > = T extends StyleLayoutAnimation ? Record<string, unknown> : T extends DelayAnimation ? NextAnimation<DelayAnimation> : T extends RepeatAnimation ? NextAnimation<RepeatAnimation> : T extends SequenceAnimation ? NextAnimation<SequenceAnimation> : AnimatableValue | T; export function defineAnimation< T extends AnimationObject | StyleLayoutAnimation >(starting: AnimationToDecoration<T>, factory: () => T): T { 'worklet'; if (IN_STYLE_UPDATER) { return starting as T; } const create = () => { 'worklet'; const animation = factory(); decorateAnimation<T>(animation); return animation; }; if (_WORKLET || !NativeReanimatedModule.native) { return create(); } // @ts-ignore: eslint-disable-line return create; } export function cancelAnimation<T>(sharedValue: SharedValue<T>): void { 'worklet'; // setting the current value cancels the animation if one is currently running sharedValue.value = sharedValue.value; // eslint-disable-line no-self-assign } // TODO it should work only if there was no animation before. export function withStartValue( startValue: AnimatableValue, animation: NextAnimation<AnimationObject> ): Animation<AnimationObject> { 'worklet'; return defineAnimation(startValue, () => { 'worklet'; if (!_WORKLET && typeof animation === 'function') { animation = animation(); } (animation as Animation<AnimationObject>).current = startValue; return animation as Animation<AnimationObject>; }); }