UNPKG

motion-on-native

Version:

Framer Motion-inspired animation library for React Native with Reanimated. Easy spring animations, gestures, and transitions for mobile apps.

515 lines (514 loc) 28.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NativeMotion = void 0; const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const react_native_reanimated_1 = __importStar(require("react-native-reanimated")); const DEFAULT_TRANSITION = { type: 'spring', damping: 15, stiffness: 100, duration: 300, repeat: 0, repeatType: 'loop', }; function createMotionComponent(Component) { return function MotionComponent(props) { const { initial = {}, animate = {}, exit = {}, transition = DEFAULT_TRANSITION, shouldExit = false, onExitComplete, whileHover, whileTap, whileFocus, layout, layoutId, style = {}, children } = props, rest = __rest(props, ["initial", "animate", "exit", "transition", "shouldExit", "onExitComplete", "whileHover", "whileTap", "whileFocus", "layout", "layoutId", "style", "children"]); const [isPresent, setIsPresent] = (0, react_1.useState)(true); const [hasAnimated, setHasAnimated] = (0, react_1.useState)(false); const isExitingRef = (0, react_1.useRef)(false); // Create shared values const opacity = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('opacity', initial)); const x = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('x', initial)); const y = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('y', initial)); const z = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('z', initial)); const translateX = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('translateX', initial)); const translateY = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('translateY', initial)); const scale = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('scale', initial)); const scaleX = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('scaleX', initial)); const scaleY = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('scaleY', initial)); const rotate = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('rotate', initial)); const rotateX = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('rotateX', initial)); const rotateY = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('rotateY', initial)); const rotateZ = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('rotateZ', initial)); const skewX = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('skewX', initial)); const skewY = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('skewY', initial)); // Layout properties const width = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('width', initial)); const height = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('height', initial)); const minWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('minWidth', initial)); const minHeight = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('minHeight', initial)); const maxWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('maxWidth', initial)); const maxHeight = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('maxHeight', initial)); // Spacing properties const margin = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('margin', initial)); const marginTop = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginTop', initial)); const marginBottom = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginBottom', initial)); const marginLeft = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginLeft', initial)); const marginRight = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginRight', initial)); const marginHorizontal = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginHorizontal', initial)); const marginVertical = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('marginVertical', initial)); const padding = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('padding', initial)); const paddingTop = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingTop', initial)); const paddingBottom = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingBottom', initial)); const paddingLeft = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingLeft', initial)); const paddingRight = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingRight', initial)); const paddingHorizontal = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingHorizontal', initial)); const paddingVertical = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('paddingVertical', initial)); // Border properties const borderRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderRadius', initial)); const borderTopLeftRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderTopLeftRadius', initial)); const borderTopRightRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderTopRightRadius', initial)); const borderBottomLeftRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderBottomLeftRadius', initial)); const borderBottomRightRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderBottomRightRadius', initial)); const borderWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderWidth', initial)); const borderTopWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderTopWidth', initial)); const borderBottomWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderBottomWidth', initial)); const borderLeftWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderLeftWidth', initial)); const borderRightWidth = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderRightWidth', initial)); const borderColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderColor', initial)); const borderTopColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderTopColor', initial)); const borderBottomColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderBottomColor', initial)); const borderLeftColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderLeftColor', initial)); const borderRightColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('borderRightColor', initial)); // Color properties const backgroundColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('backgroundColor', initial)); const color = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('color', initial)); // Position properties const top = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('top', initial)); const bottom = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('bottom', initial)); const left = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('left', initial)); const right = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('right', initial)); // Shadow properties const shadowColor = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('shadowColor', initial)); const shadowOpacity = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('shadowOpacity', initial)); const shadowRadius = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('shadowRadius', initial)); const elevation = (0, react_native_reanimated_1.useSharedValue)(getInitialValue('elevation', initial)); // Animation helper const animateToValues = (targetValues, transitionConfig = transition) => { Object.entries(targetValues).forEach(([key, value]) => { var _a, _b, _c, _d, _e, _f, _g, _h; if (value !== undefined) { const sharedValue = getSharedValue(key); if (sharedValue) { let config; if (transitionConfig.repeat && (transitionConfig.repeat > 0 || transitionConfig.repeat === 'infinity')) { // Use withRepeat for repeated animations const baseAnimation = transitionConfig.type === 'spring' ? (0, react_native_reanimated_1.withSpring)(value, { damping: (_a = transitionConfig.damping) !== null && _a !== void 0 ? _a : DEFAULT_TRANSITION.damping, stiffness: (_b = transitionConfig.stiffness) !== null && _b !== void 0 ? _b : DEFAULT_TRANSITION.stiffness, mass: (_c = transitionConfig.mass) !== null && _c !== void 0 ? _c : 1, }) : (0, react_native_reanimated_1.withTiming)(value, { duration: (_d = transitionConfig.duration) !== null && _d !== void 0 ? _d : DEFAULT_TRANSITION.duration, easing: react_native_reanimated_1.Easing.linear, }); const repeatCount = transitionConfig.repeat === 'infinity' ? -1 : transitionConfig.repeat; const reverse = transitionConfig.repeatType === 'reverse'; config = (0, react_native_reanimated_1.withRepeat)(baseAnimation, repeatCount, reverse); } else { // Single animation config = transitionConfig.type === 'spring' ? (0, react_native_reanimated_1.withSpring)(value, { damping: (_e = transitionConfig.damping) !== null && _e !== void 0 ? _e : DEFAULT_TRANSITION.damping, stiffness: (_f = transitionConfig.stiffness) !== null && _f !== void 0 ? _f : DEFAULT_TRANSITION.stiffness, mass: (_g = transitionConfig.mass) !== null && _g !== void 0 ? _g : 1, }) : (0, react_native_reanimated_1.withTiming)(value, { duration: (_h = transitionConfig.duration) !== null && _h !== void 0 ? _h : DEFAULT_TRANSITION.duration, }); } if (transitionConfig.delay) { setTimeout(() => { sharedValue.value = config; }, transitionConfig.delay); } else { sharedValue.value = config; } } } }); }; // Get shared value by key const getSharedValue = (key) => { switch (key) { // Transform properties case 'opacity': return opacity; case 'x': return x; case 'y': return y; case 'z': return z; case 'translateX': return translateX; case 'translateY': return translateY; case 'scale': return scale; case 'scaleX': return scaleX; case 'scaleY': return scaleY; case 'rotate': return rotate; case 'rotateX': return rotateX; case 'rotateY': return rotateY; case 'rotateZ': return rotateZ; case 'skewX': return skewX; case 'skewY': return skewY; // Layout properties case 'width': return width; case 'height': return height; case 'minWidth': return minWidth; case 'minHeight': return minHeight; case 'maxWidth': return maxWidth; case 'maxHeight': return maxHeight; // Spacing properties case 'margin': return margin; case 'marginTop': return marginTop; case 'marginBottom': return marginBottom; case 'marginLeft': return marginLeft; case 'marginRight': return marginRight; case 'marginHorizontal': return marginHorizontal; case 'marginVertical': return marginVertical; case 'padding': return padding; case 'paddingTop': return paddingTop; case 'paddingBottom': return paddingBottom; case 'paddingLeft': return paddingLeft; case 'paddingRight': return paddingRight; case 'paddingHorizontal': return paddingHorizontal; case 'paddingVertical': return paddingVertical; // Border properties case 'borderRadius': return borderRadius; case 'borderTopLeftRadius': return borderTopLeftRadius; case 'borderTopRightRadius': return borderTopRightRadius; case 'borderBottomLeftRadius': return borderBottomLeftRadius; case 'borderBottomRightRadius': return borderBottomRightRadius; case 'borderWidth': return borderWidth; case 'borderTopWidth': return borderTopWidth; case 'borderBottomWidth': return borderBottomWidth; case 'borderLeftWidth': return borderLeftWidth; case 'borderRightWidth': return borderRightWidth; case 'borderColor': return borderColor; case 'borderTopColor': return borderTopColor; case 'borderBottomColor': return borderBottomColor; case 'borderLeftColor': return borderLeftColor; case 'borderRightColor': return borderRightColor; // Color properties case 'backgroundColor': return backgroundColor; case 'color': return color; // Position properties case 'top': return top; case 'bottom': return bottom; case 'left': return left; case 'right': return right; // Shadow properties case 'shadowColor': return shadowColor; case 'shadowOpacity': return shadowOpacity; case 'shadowRadius': return shadowRadius; case 'elevation': return elevation; default: return null; } }; // Memoize animate prop to prevent unnecessary re-animations const animateString = JSON.stringify(animate); const memoizedAnimate = (0, react_1.useMemo)(() => animate, [animateString]); // Set initial values on mount (0, react_1.useEffect)(() => { if (initial !== false) { Object.entries(initial).forEach(([key, value]) => { const sharedValue = getSharedValue(key); if (sharedValue && value !== undefined) { sharedValue.value = value; } }); } }, []); // Mount animation: initial -> animate (0, react_1.useEffect)(() => { if (!hasAnimated && !isExitingRef.current) { // Animate to target values const timer = setTimeout(() => { if (!isExitingRef.current) { animateToValues(memoizedAnimate); setHasAnimated(true); } }, 16); return () => clearTimeout(timer); } return undefined; }, [memoizedAnimate]); // Handle animate prop changes (only animate if values actually changed) const prevAnimateRef = (0, react_1.useRef)(memoizedAnimate); (0, react_1.useEffect)(() => { if (hasAnimated && !isExitingRef.current) { // Only animate if animate prop actually changed const hasChanged = JSON.stringify(prevAnimateRef.current) !== JSON.stringify(memoizedAnimate); if (hasChanged) { animateToValues(memoizedAnimate); prevAnimateRef.current = memoizedAnimate; } } }, [memoizedAnimate]); // Handle shouldExit (0, react_1.useEffect)(() => { var _a; if (shouldExit && !isExitingRef.current && exit && Object.keys(exit).length > 0) { isExitingRef.current = true; animateToValues(exit, transition); const exitDuration = (_a = transition.duration) !== null && _a !== void 0 ? _a : 300; setTimeout(() => { setIsPresent(false); if (onExitComplete) { onExitComplete(); } }, exitDuration); } else if (!shouldExit && isExitingRef.current) { // Re-entering: reset everything isExitingRef.current = false; setIsPresent(true); setHasAnimated(false); // Reset to initial values and animate setTimeout(() => { if (initial !== false) { Object.entries(initial).forEach(([key, value]) => { const sharedValue = getSharedValue(key); if (sharedValue && value !== undefined) { sharedValue.value = value; } }); } // Animate to target after a frame setTimeout(() => { if (!isExitingRef.current) { animateToValues(memoizedAnimate); setHasAnimated(true); } }, 16); }, 0); } }, [shouldExit]); // Animated style const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => { const style = {}; const transform = []; // Transform properties style.opacity = opacity.value; if (x.value !== 0) transform.push({ translateX: x.value }); if (y.value !== 0) transform.push({ translateY: y.value }); if (z.value !== 0) transform.push({ translateZ: z.value }); if (translateX.value !== 0) transform.push({ translateX: translateX.value }); if (translateY.value !== 0) transform.push({ translateY: translateY.value }); if (scale.value !== 1) transform.push({ scale: scale.value }); if (scaleX.value !== 1) transform.push({ scaleX: scaleX.value }); if (scaleY.value !== 1) transform.push({ scaleY: scaleY.value }); if (rotate.value !== '0deg') transform.push({ rotate: rotate.value }); if (rotateX.value !== '0deg') transform.push({ rotateX: rotateX.value }); if (rotateY.value !== '0deg') transform.push({ rotateY: rotateY.value }); if (rotateZ.value !== '0deg') transform.push({ rotateZ: rotateZ.value }); if (skewX.value !== '0deg') transform.push({ skewX: skewX.value }); if (skewY.value !== '0deg') transform.push({ skewY: skewY.value }); if (transform.length > 0) style.transform = transform; // Layout properties if (width.value !== 0) style.width = width.value; if (height.value !== 0) style.height = height.value; if (minWidth.value !== 0) style.minWidth = minWidth.value; if (minHeight.value !== 0) style.minHeight = minHeight.value; if (maxWidth.value !== 0) style.maxWidth = maxWidth.value; if (maxHeight.value !== 0) style.maxHeight = maxHeight.value; // Spacing properties if (margin.value !== 0) style.margin = margin.value; if (marginTop.value !== 0) style.marginTop = marginTop.value; if (marginBottom.value !== 0) style.marginBottom = marginBottom.value; if (marginLeft.value !== 0) style.marginLeft = marginLeft.value; if (marginRight.value !== 0) style.marginRight = marginRight.value; if (marginHorizontal.value !== 0) style.marginHorizontal = marginHorizontal.value; if (marginVertical.value !== 0) style.marginVertical = marginVertical.value; if (padding.value !== 0) style.padding = padding.value; if (paddingTop.value !== 0) style.paddingTop = paddingTop.value; if (paddingBottom.value !== 0) style.paddingBottom = paddingBottom.value; if (paddingLeft.value !== 0) style.paddingLeft = paddingLeft.value; if (paddingRight.value !== 0) style.paddingRight = paddingRight.value; if (paddingHorizontal.value !== 0) style.paddingHorizontal = paddingHorizontal.value; if (paddingVertical.value !== 0) style.paddingVertical = paddingVertical.value; // Border properties if (borderRadius.value !== 0) style.borderRadius = borderRadius.value; if (borderTopLeftRadius.value !== 0) style.borderTopLeftRadius = borderTopLeftRadius.value; if (borderTopRightRadius.value !== 0) style.borderTopRightRadius = borderTopRightRadius.value; if (borderBottomLeftRadius.value !== 0) style.borderBottomLeftRadius = borderBottomLeftRadius.value; if (borderBottomRightRadius.value !== 0) style.borderBottomRightRadius = borderBottomRightRadius.value; if (borderWidth.value !== 0) style.borderWidth = borderWidth.value; if (borderTopWidth.value !== 0) style.borderTopWidth = borderTopWidth.value; if (borderBottomWidth.value !== 0) style.borderBottomWidth = borderBottomWidth.value; if (borderLeftWidth.value !== 0) style.borderLeftWidth = borderLeftWidth.value; if (borderRightWidth.value !== 0) style.borderRightWidth = borderRightWidth.value; if (borderColor.value !== 0) style.borderColor = borderColor.value; if (borderTopColor.value !== 0) style.borderTopColor = borderTopColor.value; if (borderBottomColor.value !== 0) style.borderBottomColor = borderBottomColor.value; if (borderLeftColor.value !== 0) style.borderLeftColor = borderLeftColor.value; if (borderRightColor.value !== 0) style.borderRightColor = borderRightColor.value; // Color properties if (backgroundColor.value !== 0) style.backgroundColor = backgroundColor.value; if (color.value !== 0) style.color = color.value; // Position properties if (top.value !== 0) style.top = top.value; if (bottom.value !== 0) style.bottom = bottom.value; if (left.value !== 0) style.left = left.value; if (right.value !== 0) style.right = right.value; // Shadow properties if (shadowColor.value !== 0) style.shadowColor = shadowColor.value; if (shadowOpacity.value !== 0) style.shadowOpacity = shadowOpacity.value; if (shadowRadius.value !== 0) style.shadowRadius = shadowRadius.value; if (elevation.value !== 0) style.elevation = elevation.value; return style; }); const AnimatedComponent = react_native_reanimated_1.default.createAnimatedComponent(Component); if (!isPresent) return null; return react_1.default.createElement(AnimatedComponent, Object.assign({ style: [style, animatedStyle] }, rest), children); }; } function getInitialValue(key, initial) { if (initial === false) { return getDefaultValue(key); } const value = initial[key]; return value !== undefined ? value : getDefaultValue(key); } function getDefaultValue(key) { switch (key) { case 'opacity': case 'scale': case 'scaleX': case 'scaleY': return 1; case 'x': case 'y': case 'z': case 'translateX': case 'translateY': return 0; case 'rotate': case 'rotateX': case 'rotateY': case 'rotateZ': case 'skewX': case 'skewY': return '0deg'; default: return 0; } } exports.NativeMotion = { View: createMotionComponent(react_native_1.View), Text: createMotionComponent(react_native_1.Text), Image: createMotionComponent(react_native_1.Image), ImageBackground: createMotionComponent(react_native_1.ImageBackground), TextInput: createMotionComponent(react_native_1.TextInput), TouchableOpacity: createMotionComponent(react_native_1.TouchableOpacity), ScrollView: createMotionComponent(react_native_1.ScrollView), FlatList: createMotionComponent(react_native_1.FlatList), SectionList: createMotionComponent(react_native_1.SectionList), Pressable: createMotionComponent(react_native_1.Pressable), };