@tamagui/animations-moti 2.0.0-rc.35 → 2.0.0-rc.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/animations-moti",
3
- "version": "2.0.0-rc.35",
3
+ "version": "2.0.0-rc.36",
4
4
  "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
5
5
  "license": "MIT",
6
6
  "source": "src/index.ts",
@@ -39,11 +39,11 @@
39
39
  "clean:build": "tamagui-build clean:build"
40
40
  },
41
41
  "dependencies": {
42
- "@tamagui/core": "2.0.0-rc.35",
43
- "@tamagui/use-presence": "2.0.0-rc.35"
42
+ "@tamagui/core": "2.0.0-rc.36",
43
+ "@tamagui/use-presence": "2.0.0-rc.36"
44
44
  },
45
45
  "devDependencies": {
46
- "@tamagui/build": "2.0.0-rc.35",
46
+ "@tamagui/build": "2.0.0-rc.36",
47
47
  "react": ">=19",
48
48
  "react-native": "0.83.2",
49
49
  "react-native-reanimated": "~4.2.2"
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAGA,cAQO,uBAEA;AAUP,cAAc,wBAAwB;AAwHtC,OAAO,iBAAS,iBAAiB,UAAU,eAAe,mBACxD,YAAY,IACX,gBAAgB",
2
+ "mappings": "AAGA,cAQO,uBAEA;AAUP,cAAc,wBAAwB;AAyHtC,OAAO,iBAAS,iBAAiB,UAAU,eAAe,mBACxD,YAAY,IACX,gBAAgB",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/createAnimations.tsx"
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "// @ts-nocheck - deprecated package, moti dependency intentionally not included\nimport { PresenceContext, ResetPresence, usePresence } from '@tamagui/use-presence'\n// we need core for hooks.usePropsTransform\nimport {\n getSplitStyles,\n hooks,\n isWeb,\n Text,\n useComposedRefs,\n useThemeWithState,\n View,\n type AnimationDriver,\n type UniversalAnimatedNumber,\n} from '@tamagui/core'\n\n// Helper to resolve dynamic theme values like {dynamic: {dark: \"value\", light: undefined}}\nconst resolveDynamicValue = (value: any, isDark: boolean): any => {\n if (value && typeof value === 'object' && 'dynamic' in value) {\n const dynamicValue = isDark ? value.dynamic.dark : value.dynamic.light\n return dynamicValue\n }\n return value\n}\nimport type { TransitionConfig } from 'moti'\nimport { useMotify } from 'moti/author'\nimport type { CSSProperties } from 'react'\nimport React, { forwardRef, useMemo, useRef } from 'react'\nimport type { TextStyle } from 'react-native'\nimport type { SharedValue } from 'react-native-reanimated'\nimport Animated_, {\n cancelAnimation,\n runOnJS,\n useAnimatedReaction,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated'\n\n// fix for building with type module\n// see https://github.com/evanw/esbuild/issues/2480#issuecomment-1833104754\nconst safeESModule = <T,>(a: T | { default: T }): T => {\n const b = a as any\n const out = b.__esModule || b[Symbol.toStringTag] === 'Module' ? b.default : b\n // add metro support\n return out || a\n}\n\nconst Animated = safeESModule(Animated_)\n\ntype ReanimatedAnimatedNumber = SharedValue<number>\n\n// this is our own custom reanimated animated component so we can allow data- attributes, className etc\n// this should ultimately be merged with react-native-web-lite\n\nfunction createTamaguiAnimatedComponent(defaultTag = 'div') {\n const isText = defaultTag === 'span'\n\n const Component = Animated.createAnimatedComponent(\n forwardRef((propsIn: any, ref) => {\n const { forwardedRef, animation, render = defaultTag, ...propsRest } = propsIn\n const hostRef = useRef(null)\n const composedRefs = useComposedRefs(forwardedRef, ref, hostRef)\n const stateRef = useRef<any>(null)\n if (!stateRef.current) {\n stateRef.current = {\n get host() {\n return hostRef.current\n },\n }\n }\n\n const [_, state] = useThemeWithState({})\n\n // get styles but only inline style\n const result = getSplitStyles(\n propsRest,\n isText ? Text.staticConfig : View.staticConfig,\n state?.theme,\n state?.name,\n {\n unmounted: false,\n } as any,\n {\n isAnimated: false,\n noClass: true,\n }\n )\n\n const props = result?.viewProps || {}\n const Element = render\n const transformedProps = hooks.usePropsTransform?.(render, props, stateRef, false)\n\n return <Element {...transformedProps} ref={composedRefs} />\n })\n )\n Component['acceptRenderProp'] = true\n return Component\n}\n\nconst AnimatedView = createTamaguiAnimatedComponent('div')\nconst AnimatedText = createTamaguiAnimatedComponent('span')\n\n// const AnimatedView = styled(View, {\n// disableClassName: true,\n// })\n\n// const AnimatedText = styled(Text, {\n// disableClassName: true,\n// })\n\nconst onlyAnimateKeys: { [key in keyof TextStyle | keyof CSSProperties]?: boolean } = {\n transform: true,\n opacity: true,\n height: true,\n width: true,\n backgroundColor: true,\n borderColor: true,\n borderLeftColor: true,\n borderRightColor: true,\n borderTopColor: true,\n borderBottomColor: true,\n borderRadius: true,\n borderTopLeftRadius: true,\n borderTopRightRadius: true,\n borderBottomLeftRadius: true,\n borderBottomRightRadius: true,\n borderLeftWidth: true,\n borderRightWidth: true,\n borderTopWidth: true,\n borderBottomWidth: true,\n color: true,\n left: true,\n right: true,\n top: true,\n bottom: true,\n fontSize: true,\n fontWeight: true,\n lineHeight: true,\n letterSpacing: true,\n}\n\nexport function createAnimations<A extends Record<string, TransitionConfig>>(\n animations: A\n): AnimationDriver<A> {\n return {\n needsCustomComponent: true,\n View: isWeb ? AnimatedView : Animated.View,\n Text: isWeb ? AnimatedText : Animated.Text,\n // View: Animated.View,\n // Text: Animated.Text,\n isReactNative: true,\n inputStyle: 'value',\n outputStyle: 'inline',\n animations,\n usePresence,\n ResetPresence,\n\n useAnimatedNumber(initial): UniversalAnimatedNumber<ReanimatedAnimatedNumber> {\n const sharedValue = useSharedValue(initial)\n\n return React.useMemo(\n () => ({\n getInstance() {\n 'worklet'\n return sharedValue\n },\n getValue() {\n 'worklet'\n return sharedValue.value\n },\n setValue(next, config = { type: 'spring' }, onFinish) {\n 'worklet'\n if (config.type === 'direct') {\n sharedValue.value = next\n onFinish?.()\n } else if (config.type === 'spring') {\n sharedValue.value = withSpring(\n next,\n config,\n onFinish\n ? () => {\n 'worklet'\n runOnJS(onFinish)()\n }\n : undefined\n )\n } else {\n sharedValue.value = withTiming(\n next,\n config,\n onFinish\n ? () => {\n 'worklet'\n runOnJS(onFinish)()\n }\n : undefined\n )\n }\n },\n stop() {\n 'worklet'\n cancelAnimation(sharedValue)\n },\n }),\n [sharedValue]\n )\n },\n\n useAnimatedNumberReaction({ value }, onValue) {\n const instance = value.getInstance()\n return useAnimatedReaction(\n () => {\n return instance.value\n },\n (next, prev) => {\n if (prev !== next) {\n // @nate what is the point of this hook? is this necessary?\n // without runOnJS, onValue would need to be a worklet\n runOnJS(onValue)(next)\n }\n },\n // dependency array is very important here\n [onValue, instance]\n )\n },\n\n /**\n * `getStyle` must be a worklet\n */\n useAnimatedNumberStyle(val, getStyle) {\n const instance = val.getInstance()\n\n // this seems wrong but it works\n const derivedValue = useDerivedValue(() => {\n return instance.value\n // dependency array is very important here\n }, [instance, getStyle])\n\n return useAnimatedStyle(() => {\n return getStyle(derivedValue.value)\n // dependency array is very important here\n }, [val, getStyle, derivedValue, instance])\n },\n\n useAnimations: (animationProps) => {\n const { props, presence, style, componentState } = animationProps\n const animationKey = Array.isArray(props.transition)\n ? props.transition[0]\n : props.transition\n\n const isHydrating = componentState.unmounted === true\n const disableAnimation = isHydrating || !animationKey\n const presenceContext = React.useContext(PresenceContext)\n const [, themeState] = useThemeWithState({})\n // Check scheme first, then fall back to checking theme name for 'dark'\n const isDark = themeState?.scheme === 'dark' || themeState?.name?.startsWith('dark')\n\n // this memo is very important for performance, there's a big cost to\n // updating these values every render\n const { dontAnimate, motiProps } = useMemo(() => {\n let animate = {}\n let dontAnimate = {}\n\n if (disableAnimation) {\n // Resolve dynamic objects based on current theme\n for (const key in style) {\n const rawValue = style[key]\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n dontAnimate[key] = value\n }\n } else {\n const animateOnly = props.animateOnly as string[]\n for (const key in style) {\n const rawValue = style[key]\n // Resolve dynamic theme values (like $theme-dark)\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n if (\n !onlyAnimateKeys[key] ||\n value === 'auto' ||\n (typeof value === 'string' && value.startsWith('calc')) ||\n (animateOnly && !animateOnly.includes(key))\n ) {\n dontAnimate[key] = value\n } else {\n animate[key] = value\n }\n }\n }\n\n // if we don't do this moti seems to flicker a frame before applying animation\n if (componentState.unmounted === 'should-enter') {\n // Resolve dynamic objects based on current theme\n for (const key in style) {\n const rawValue = style[key]\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n dontAnimate[key] = value\n }\n }\n\n const styles = animate\n const isExiting = Boolean(presence?.[1])\n const usePresenceValue = (presence || undefined) as any\n\n type UseMotiProps = Parameters<typeof useMotify>[0]\n\n // TODO moti is giving us type troubles, but this should work\n let transition = isHydrating\n ? { type: 'transition', duration: 0 }\n : (animations[animationKey as keyof typeof animations] as any)\n\n let hasClonedTransition = false\n\n if (Array.isArray(props.transition)) {\n const config = props.transition[1]\n if (config && typeof config === 'object') {\n for (const key in config) {\n const val = config[key]\n\n // performance - this seems to have (strangely) huge performance effect in uniswap\n // so instead of cloning up front, we clone only when we absolutely have to\n if (!hasClonedTransition) {\n transition = Object.assign({}, transition)\n hasClonedTransition = true\n }\n\n // referencing a pre-defined config\n if (typeof val === 'string') {\n transition[key] = animations[val]\n } else {\n transition[key] = val\n }\n }\n }\n }\n\n return {\n dontAnimate,\n motiProps: {\n animate: isExiting || componentState.unmounted === true ? {} : styles,\n transition: componentState.unmounted ? { duration: 0 } : transition,\n usePresenceValue,\n presenceContext,\n exit: isExiting ? styles : undefined,\n } satisfies UseMotiProps,\n }\n }, [\n presenceContext,\n presence,\n animationKey,\n componentState.unmounted,\n JSON.stringify(style),\n presenceContext,\n isDark,\n ])\n\n const moti = useMotify(motiProps)\n\n if (\n process.env.NODE_ENV === 'development' &&\n props['debug'] &&\n props['debug'] !== 'profile'\n ) {\n console.info(`useMotify(`, JSON.stringify(motiProps, null, 2) + ')', {\n 'componentState.unmounted': componentState.unmounted,\n animationProps,\n motiProps,\n moti,\n style: [dontAnimate, moti.style],\n })\n }\n\n return {\n style: [dontAnimate, moti.style],\n }\n },\n }\n}\n"
9
+ "// @ts-nocheck - deprecated package, moti dependency intentionally not included\nimport { PresenceContext, ResetPresence, usePresence } from '@tamagui/use-presence'\n// we need core for hooks.usePropsTransform\nimport {\n getSplitStyles,\n hooks,\n isWeb,\n Text,\n useComposedRefs,\n useThemeWithState,\n View,\n type AnimationDriver,\n type UniversalAnimatedNumber,\n} from '@tamagui/core'\n\n// Helper to resolve dynamic theme values like {dynamic: {dark: \"value\", light: undefined}}\nconst resolveDynamicValue = (value: any, isDark: boolean): any => {\n if (value && typeof value === 'object' && 'dynamic' in value) {\n const dynamicValue = isDark ? value.dynamic.dark : value.dynamic.light\n return dynamicValue\n }\n return value\n}\nimport type { TransitionConfig } from 'moti'\nimport { useMotify } from 'moti/author'\nimport type { CSSProperties } from 'react'\nimport React, { forwardRef, useMemo, useRef } from 'react'\nimport type { TextStyle } from 'react-native'\nimport type { SharedValue } from 'react-native-reanimated'\nimport Animated_, {\n cancelAnimation,\n runOnJS,\n runOnUI,\n useAnimatedReaction,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated'\n\n// fix for building with type module\n// see https://github.com/evanw/esbuild/issues/2480#issuecomment-1833104754\nconst safeESModule = <T,>(a: T | { default: T }): T => {\n const b = a as any\n const out = b.__esModule || b[Symbol.toStringTag] === 'Module' ? b.default : b\n // add metro support\n return out || a\n}\n\nconst Animated = safeESModule(Animated_)\n\ntype ReanimatedAnimatedNumber = SharedValue<number>\n\n// this is our own custom reanimated animated component so we can allow data- attributes, className etc\n// this should ultimately be merged with react-native-web-lite\n\nfunction createTamaguiAnimatedComponent(defaultTag = 'div') {\n const isText = defaultTag === 'span'\n\n const Component = Animated.createAnimatedComponent(\n forwardRef((propsIn: any, ref) => {\n const { forwardedRef, animation, render = defaultTag, ...propsRest } = propsIn\n const hostRef = useRef(null)\n const composedRefs = useComposedRefs(forwardedRef, ref, hostRef)\n const stateRef = useRef<any>(null)\n if (!stateRef.current) {\n stateRef.current = {\n get host() {\n return hostRef.current\n },\n }\n }\n\n const [_, state] = useThemeWithState({})\n\n // get styles but only inline style\n const result = getSplitStyles(\n propsRest,\n isText ? Text.staticConfig : View.staticConfig,\n state?.theme,\n state?.name,\n {\n unmounted: false,\n } as any,\n {\n isAnimated: false,\n noClass: true,\n }\n )\n\n const props = result?.viewProps || {}\n const Element = render\n const transformedProps = hooks.usePropsTransform?.(render, props, stateRef, false)\n\n return <Element {...transformedProps} ref={composedRefs} />\n })\n )\n Component['acceptRenderProp'] = true\n return Component\n}\n\nconst AnimatedView = createTamaguiAnimatedComponent('div')\nconst AnimatedText = createTamaguiAnimatedComponent('span')\n\n// const AnimatedView = styled(View, {\n// disableClassName: true,\n// })\n\n// const AnimatedText = styled(Text, {\n// disableClassName: true,\n// })\n\nconst onlyAnimateKeys: { [key in keyof TextStyle | keyof CSSProperties]?: boolean } = {\n transform: true,\n opacity: true,\n height: true,\n width: true,\n backgroundColor: true,\n borderColor: true,\n borderLeftColor: true,\n borderRightColor: true,\n borderTopColor: true,\n borderBottomColor: true,\n borderRadius: true,\n borderTopLeftRadius: true,\n borderTopRightRadius: true,\n borderBottomLeftRadius: true,\n borderBottomRightRadius: true,\n borderLeftWidth: true,\n borderRightWidth: true,\n borderTopWidth: true,\n borderBottomWidth: true,\n color: true,\n left: true,\n right: true,\n top: true,\n bottom: true,\n fontSize: true,\n fontWeight: true,\n lineHeight: true,\n letterSpacing: true,\n}\n\nexport function createAnimations<A extends Record<string, TransitionConfig>>(\n animations: A\n): AnimationDriver<A> {\n return {\n needsCustomComponent: true,\n View: isWeb ? AnimatedView : Animated.View,\n Text: isWeb ? AnimatedText : Animated.Text,\n // View: Animated.View,\n // Text: Animated.Text,\n isReactNative: true,\n inputStyle: 'value',\n outputStyle: 'inline',\n animations,\n usePresence,\n ResetPresence,\n\n useAnimatedNumber(initial): UniversalAnimatedNumber<ReanimatedAnimatedNumber> {\n const sharedValue = useSharedValue(initial)\n\n return React.useMemo(\n () => ({\n getInstance() {\n 'worklet'\n return sharedValue\n },\n getValue() {\n 'worklet'\n return sharedValue.value\n },\n setValue(next, config = { type: 'spring' }, onFinish) {\n if (config.type === 'direct') {\n sharedValue.value = next\n onFinish?.()\n } else {\n const cb = onFinish\n ? () => {\n 'worklet'\n runOnJS(onFinish)()\n }\n : undefined\n\n if (isWeb) {\n sharedValue.value =\n config.type === 'spring'\n ? withSpring(next, config, cb)\n : withTiming(next, config, cb)\n } else {\n runOnUI(() => {\n 'worklet'\n sharedValue.value =\n config.type === 'spring'\n ? withSpring(next, config, cb)\n : withTiming(next, config, cb)\n })()\n }\n }\n },\n stop() {\n cancelAnimation(sharedValue)\n },\n }),\n [sharedValue]\n )\n },\n\n useAnimatedNumberReaction({ value }, onValue) {\n const instance = value.getInstance()\n return useAnimatedReaction(\n () => {\n return instance.value\n },\n (next, prev) => {\n if (prev !== next) {\n // @nate what is the point of this hook? is this necessary?\n // without runOnJS, onValue would need to be a worklet\n runOnJS(onValue)(next)\n }\n },\n // dependency array is very important here\n [onValue, instance]\n )\n },\n\n /**\n * `getStyle` must be a worklet\n */\n useAnimatedNumberStyle(val, getStyle) {\n const instance = val.getInstance()\n\n if (isWeb) {\n // web: must pass explicit deps (no babel plugin)\n return useAnimatedStyle(\n () => {\n 'worklet'\n return getStyle(instance.value)\n },\n [instance, getStyle]\n )\n }\n\n // native: use useAnimatedReaction to watch instance changes and\n // write computed style to a dedicated SharedValue. useAnimatedStyle\n // then reads only that SharedValue — pure closure, no non-SV items\n // to confuse startMapper's dependency tracking.\n const styleVal = useSharedValue(getStyle(instance.value))\n\n useAnimatedReaction(\n () => {\n 'worklet'\n return instance.value\n },\n (current, previous) => {\n 'worklet'\n if (current !== previous) {\n styleVal.value = getStyle(current)\n }\n }\n )\n\n return useAnimatedStyle(() => {\n 'worklet'\n return styleVal.value\n })\n },\n\n useAnimations: (animationProps) => {\n const { props, presence, style, componentState } = animationProps\n const animationKey = Array.isArray(props.transition)\n ? props.transition[0]\n : props.transition\n\n const isHydrating = componentState.unmounted === true\n const disableAnimation = isHydrating || !animationKey\n const presenceContext = React.useContext(PresenceContext)\n const [, themeState] = useThemeWithState({})\n // Check scheme first, then fall back to checking theme name for 'dark'\n const isDark = themeState?.scheme === 'dark' || themeState?.name?.startsWith('dark')\n\n // this memo is very important for performance, there's a big cost to\n // updating these values every render\n const { dontAnimate, motiProps } = useMemo(() => {\n let animate = {}\n let dontAnimate = {}\n\n if (disableAnimation) {\n // Resolve dynamic objects based on current theme\n for (const key in style) {\n const rawValue = style[key]\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n dontAnimate[key] = value\n }\n } else {\n const animateOnly = props.animateOnly as string[]\n for (const key in style) {\n const rawValue = style[key]\n // Resolve dynamic theme values (like $theme-dark)\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n if (\n !onlyAnimateKeys[key] ||\n value === 'auto' ||\n (typeof value === 'string' && value.startsWith('calc')) ||\n (animateOnly && !animateOnly.includes(key))\n ) {\n dontAnimate[key] = value\n } else {\n animate[key] = value\n }\n }\n }\n\n // if we don't do this moti seems to flicker a frame before applying animation\n if (componentState.unmounted === 'should-enter') {\n // Resolve dynamic objects based on current theme\n for (const key in style) {\n const rawValue = style[key]\n const value = resolveDynamicValue(rawValue, isDark)\n if (value === undefined) continue\n dontAnimate[key] = value\n }\n }\n\n const styles = animate\n const isExiting = Boolean(presence?.[1])\n const usePresenceValue = (presence || undefined) as any\n\n type UseMotiProps = Parameters<typeof useMotify>[0]\n\n // TODO moti is giving us type troubles, but this should work\n let transition = isHydrating\n ? { type: 'transition', duration: 0 }\n : (animations[animationKey as keyof typeof animations] as any)\n\n let hasClonedTransition = false\n\n if (Array.isArray(props.transition)) {\n const config = props.transition[1]\n if (config && typeof config === 'object') {\n for (const key in config) {\n const val = config[key]\n\n // performance - this seems to have (strangely) huge performance effect in uniswap\n // so instead of cloning up front, we clone only when we absolutely have to\n if (!hasClonedTransition) {\n transition = Object.assign({}, transition)\n hasClonedTransition = true\n }\n\n // referencing a pre-defined config\n if (typeof val === 'string') {\n transition[key] = animations[val]\n } else {\n transition[key] = val\n }\n }\n }\n }\n\n return {\n dontAnimate,\n motiProps: {\n animate: isExiting || componentState.unmounted === true ? {} : styles,\n transition: componentState.unmounted ? { duration: 0 } : transition,\n usePresenceValue,\n presenceContext,\n exit: isExiting ? styles : undefined,\n } satisfies UseMotiProps,\n }\n }, [\n presenceContext,\n presence,\n animationKey,\n componentState.unmounted,\n JSON.stringify(style),\n presenceContext,\n isDark,\n ])\n\n const moti = useMotify(motiProps)\n\n if (\n process.env.NODE_ENV === 'development' &&\n props['debug'] &&\n props['debug'] !== 'profile'\n ) {\n console.info(`useMotify(`, JSON.stringify(motiProps, null, 2) + ')', {\n 'componentState.unmounted': componentState.unmounted,\n animationProps,\n motiProps,\n moti,\n style: [dontAnimate, moti.style],\n })\n }\n\n return {\n style: [dontAnimate, moti.style],\n }\n },\n }\n}\n"
10
10
  ]
11
11
  }