UNPKG

react-native-reanimated

Version:

More powerful alternative to Animated library for React Native.

175 lines (174 loc) • 7.45 kB
import { defineAnimation } from './util'; import { withTiming } from './timing'; import { ColorProperties } from '../UpdateProps'; import { processColor } from '../Colors'; // resolves path to value for nested objects // if path cannot be resolved returns undefined export function resolvePath(obj, path) { 'worklet'; const keys = Array.isArray(path) ? path : [path]; return keys.reduce((acc, current) => { if (Array.isArray(acc) && typeof current === 'number') { return acc[current]; } else if (typeof acc === 'object' && current in acc) { return acc[current]; } return undefined; }, obj); } export function setPath(obj, path, value) { 'worklet'; const keys = Array.isArray(path) ? path : [path]; let currObj = obj; for (let i = 0; i < keys.length - 1; i++) { // creates entry if there isn't one currObj = currObj; if (!(keys[i] in currObj)) { // if next key is a number create an array if (typeof keys[i + 1] === 'number') { currObj[keys[i]] = []; } else { currObj[keys[i]] = {}; } } currObj = currObj[keys[i]]; } currObj[keys[keys.length - 1]] = value; } export function withStyleAnimation(styleAnimations) { 'worklet'; return defineAnimation({}, () => { 'worklet'; const onFrame = (animation, now) => { let stillGoing = false; const entriesToCheck = [ { value: animation.styleAnimations, path: [] }, ]; while (entriesToCheck.length > 0) { const currentEntry = entriesToCheck.pop(); if (Array.isArray(currentEntry.value)) { for (let index = 0; index < currentEntry.value.length; index++) { entriesToCheck.push({ value: currentEntry.value[index], path: currentEntry.path.concat(index), }); } } else if (typeof currentEntry.value === 'object' && currentEntry.value.onFrame === undefined) { // nested object for (const key of Object.keys(currentEntry.value)) { entriesToCheck.push({ value: currentEntry.value[key], path: currentEntry.path.concat(key), }); } } else { const currentStyleAnimation = currentEntry.value; if (currentStyleAnimation.finished) { continue; } const finished = currentStyleAnimation.onFrame(currentStyleAnimation, now); if (finished) { currentStyleAnimation.finished = true; if (currentStyleAnimation.callback) { currentStyleAnimation.callback(true); } } else { stillGoing = true; } if (ColorProperties.includes(currentEntry.path[0])) { currentStyleAnimation.current = processColor(currentStyleAnimation.current); } setPath(animation.current, currentEntry.path, currentStyleAnimation.current); } } return !stillGoing; }; const onStart = (animation, value, now, previousAnimation) => { const entriesToCheck = [{ value: styleAnimations, path: [] }]; while (entriesToCheck.length > 0) { const currentEntry = entriesToCheck.pop(); if (Array.isArray(currentEntry.value)) { for (let index = 0; index < currentEntry.value.length; index++) { entriesToCheck.push({ value: currentEntry.value[index], path: currentEntry.path.concat(index), }); } } else if (typeof currentEntry.value === 'object' && currentEntry.value.onStart === undefined) { for (const key of Object.keys(currentEntry.value)) { entriesToCheck.push({ value: currentEntry.value[key], path: currentEntry.path.concat(key), }); } } else { const prevAnimation = resolvePath(previousAnimation === null || previousAnimation === void 0 ? void 0 : previousAnimation.styleAnimations, currentEntry.path); let prevVal = resolvePath(value, currentEntry.path); if (prevAnimation && !prevVal) { prevVal = prevAnimation.current; } if (prevVal === undefined) { console.warn(`Initial values for animation are missing for property ${currentEntry.path.join('.')}`); } setPath(animation.current, currentEntry.path, prevVal); let currentAnimation; if (typeof currentEntry.value !== 'object' || !currentEntry.value.onStart) { currentAnimation = withTiming(currentEntry.value, { duration: 0 }); setPath(animation.styleAnimations, currentEntry.path, currentAnimation); } else { currentAnimation = currentEntry.value; } currentAnimation.onStart(currentAnimation, prevVal, now, prevAnimation); } } }; const callback = (finished) => { if (!finished) { const animationsToCheck = [ styleAnimations, ]; while (animationsToCheck.length > 0) { const currentAnimation = animationsToCheck.pop(); if (Array.isArray(currentAnimation)) { for (const element of currentAnimation) { animationsToCheck.push(element); } } else if (typeof currentAnimation === 'object' && currentAnimation.onStart === undefined) { for (const value of Object.values(currentAnimation)) { animationsToCheck.push(value); } } else { const currentStyleAnimation = currentAnimation; if (!currentStyleAnimation.finished && currentStyleAnimation.callback) { currentStyleAnimation.callback(false); } } } } }; return { isHigherOrder: true, onFrame, onStart, current: {}, styleAnimations, callback, }; }); }