@tamagui/animations-motion 2.0.0-rc.11 → 2.0.0-rc.12

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-motion",
3
- "version": "2.0.0-rc.11",
3
+ "version": "2.0.0-rc.12",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "files": [
@@ -37,13 +37,13 @@
37
37
  "clean:build": "tamagui-build clean:build"
38
38
  },
39
39
  "dependencies": {
40
- "@tamagui/animation-helpers": "2.0.0-rc.11",
41
- "@tamagui/use-presence": "2.0.0-rc.11",
42
- "@tamagui/web": "2.0.0-rc.11",
40
+ "@tamagui/animation-helpers": "2.0.0-rc.12",
41
+ "@tamagui/use-presence": "2.0.0-rc.12",
42
+ "@tamagui/web": "2.0.0-rc.12",
43
43
  "motion": "^12.29.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@tamagui/build": "2.0.0-rc.11",
46
+ "@tamagui/build": "2.0.0-rc.12",
47
47
  "react": ">=19"
48
48
  },
49
49
  "peerDependencies": {
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/createAnimations.tsx"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import { getEffectiveAnimation, normalizeTransition } from '@tamagui/animation-helpers'\nimport { ResetPresence, usePresence } from '@tamagui/use-presence'\nimport {\n type AnimatedNumberStrategy,\n type AnimationDriver,\n fixStyles,\n getConfig,\n getSplitStyles,\n hooks,\n styleToCSS,\n Text,\n type TransitionProp,\n type UniversalAnimatedNumber,\n useComposedRefs,\n useIsomorphicLayoutEffect,\n useThemeWithState,\n View,\n} from '@tamagui/web'\nimport {\n type AnimationOptions,\n type AnimationPlaybackControlsWithThen,\n type MotionValue,\n useAnimate,\n useMotionValue,\n useMotionValueEvent,\n type ValueTransition,\n} from 'motion/react'\nimport React, {\n forwardRef,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\ntype MotionAnimatedNumber = MotionValue<number>\ntype AnimationConfig = ValueTransition\n\ntype MotionAnimatedNumberStyle = {\n getStyle: (cur: number) => Record<string, unknown>\n motionValue: MotionValue<number>\n}\n\n/**\n * Animation options with optional default and per-property configs.\n * This extends AnimationOptions to support the default key.\n */\ntype TransitionAnimationOptions = AnimationOptions & {\n default?: ValueTransition\n [propertyName: string]: ValueTransition | undefined\n}\n\nconst MotionValueStrategy = new WeakMap<MotionValue, AnimatedNumberStrategy>()\n\n// regex to detect non-position transform operations (scale, rotate, skew, matrix, perspective)\n// used to identify position-only transforms for the popper animation fix\nconst nonPositionTransformRe = /scale|rotate|skew|matrix|perspective/\n\ntype AnimationProps = {\n doAnimate?: Record<string, unknown>\n dontAnimate?: Record<string, unknown>\n animationOptions?: AnimationOptions\n}\n\n// track if we're still in the initial hydration phase\n// TODO didnt realize claude took the wrong one here - this should uust be isComponentHydrating ideally\n// but this is fine for beta motion driver rc.0 fix in rc.1\n\nexport function createAnimations<A extends Record<string, AnimationConfig>>(\n animations: A\n): AnimationDriver<A> {\n let isHydratingGlobal: boolean | undefined\n const hydratingComponents = new Set<Function>()\n\n return {\n // this is only used by Sheet basically for now to pass result of useAnimatedStyle to\n View: MotionView,\n Text: MotionText,\n isReactNative: false,\n supportsCSS: true,\n inputStyle: 'css',\n outputStyle: 'inline',\n needsWebStyles: true,\n avoidReRenders: true,\n animations,\n usePresence,\n ResetPresence,\n\n onMount() {\n isHydratingGlobal = false\n hydratingComponents.forEach((cb) => cb())\n },\n\n useAnimations: (animationProps) => {\n if (isHydratingGlobal === undefined && !getConfig().settings.disableSSR) {\n isHydratingGlobal = true\n }\n\n const { props, style, componentState, stateRef, useStyleEmitter, presence } =\n animationProps\n\n const animationKey = Array.isArray(props.transition)\n ? props.transition[0]\n : props.transition\n\n const isComponentHydrating = componentState.unmounted === true\n const isMounting = componentState.unmounted === 'should-enter'\n const isEntering = !!componentState.unmounted\n const isExiting = presence?.[0] === false\n const sendExitComplete = presence?.[1]\n\n // Track if we just finished entering (transition from entering to not entering)\n const wasEnteringRef = useRef(isEntering)\n const justFinishedEntering = wasEnteringRef.current && !isEntering\n useEffect(() => {\n wasEnteringRef.current = isEntering\n })\n\n // Determine animation state for enter/exit transitions\n // Use 'enter' if we're mounting OR if we just finished entering\n const animationState: 'enter' | 'exit' | 'default' = isExiting\n ? 'exit'\n : isMounting || justFinishedEntering\n ? 'enter'\n : 'default'\n\n // Disable animation during hydration AND during mounting (should-enter phase)\n // This prevents the \"flying across the page\" effect on initial render\n const disableAnimation = isComponentHydrating || isMounting || !animationKey\n\n const isFirstRender = useRef(true)\n const [scope, animate] = useAnimate()\n const lastDoAnimate = useRef<Record<string, unknown> | null>(null)\n const controls = useRef<AnimationPlaybackControlsWithThen | null>(null)\n const styleKey = JSON.stringify(style)\n\n // until fully stable allow debugging in prod to help debugging prod issues\n const shouldDebug =\n // process.env.NODE_ENV === 'development' &&\n props['debug'] && props['debug'] !== 'profile'\n\n const {\n dontAnimate = {},\n doAnimate,\n animationOptions,\n } = useMemo(() => {\n const motionAnimationState = getMotionAnimatedProps(\n props as any,\n style,\n disableAnimation,\n animationState\n )\n return motionAnimationState\n }, [isExiting, animationKey, styleKey, animationState, disableAnimation])\n\n const id = useId()\n const debugId = process.env.NODE_ENV === 'development' ? id : ''\n const lastAnimateAt = useRef(0)\n const disposed = useRef(false)\n const [firstRenderStyle] = useState(style)\n\n // avoid first render returning wrong styles - always render all, after that we can just mutate\n const lastDontAnimate = useRef<Record<string, unknown> | null>(firstRenderStyle)\n const [isHydrating, setIsHydrating] = useState(isHydratingGlobal)\n\n useLayoutEffect(() => {\n if (isHydratingGlobal) {\n hydratingComponents.add(() => {\n setIsHydrating(false)\n })\n }\n return () => {\n disposed.current = true\n }\n }, [])\n\n const flushAnimation = ({\n doAnimate = {},\n animationOptions = {},\n dontAnimate,\n }: AnimationProps) => {\n try {\n const node = stateRef.current.host\n\n // on first render, reset stale animation refs - they can persist if component\n // instance is reused (e.g. AnimatePresence keepChildrenMounted)\n if (isFirstRender.current) {\n lastDontAnimate.current = null\n lastDoAnimate.current = null\n }\n\n if (shouldDebug) {\n console.groupCollapsed(\n `[motion] ${debugId} 🌊 animate (${JSON.stringify(getDiff(lastDoAnimate.current, doAnimate), null, 2)})`\n )\n console.info({\n props,\n componentState,\n doAnimate,\n dontAnimate,\n animationOptions,\n animationProps,\n lastDoAnimate: { ...lastDoAnimate.current },\n lastDontAnimate: { ...lastDontAnimate.current },\n isExiting,\n style,\n node,\n })\n console.groupCollapsed(`trace >`)\n console.trace()\n console.groupEnd()\n console.groupEnd()\n }\n\n if (!(node instanceof HTMLElement)) {\n return\n }\n\n // handle case where dontAnimate changes\n // we just set it onto animate + set options to not actually animate\n const prevDont = lastDontAnimate.current\n if (dontAnimate) {\n if (prevDont) {\n // Pass doAnimate as preserve to prevent clearing styles that moved to doAnimate\n removeRemovedStyles(prevDont, dontAnimate, node, doAnimate)\n const changed = getDiff(prevDont, dontAnimate)\n if (changed) {\n Object.assign(node.style, changed as any)\n }\n } else {\n // First time - apply directly without diff check\n Object.assign(node.style, dontAnimate as any)\n }\n }\n\n if (doAnimate) {\n // bugfix: going from non-animated to animated in motion -\n // motion batches things so the above removal can happen a frame before causing flickering\n // we see this with tooltips, this is not an ideal solution though, ideally we can remove/update\n // in the same batch/frame as motion\n // Also sync motion's internal state for properties moving from dontAnimate to doAnimate\n if (prevDont) {\n const movedToAnimate: Record<string, unknown> = {}\n for (const key in prevDont) {\n if (key in doAnimate) {\n node.style[key] = prevDont[key]\n movedToAnimate[key] = prevDont[key]\n // Also update lastDoAnimate to include the previous value\n // This prevents animating from undefined to the current value\n // when a property transitions from dontAnimate to doAnimate\n if (lastDoAnimate.current) {\n lastDoAnimate.current[key] = prevDont[key]\n }\n }\n }\n // Sync motion's internal state for moved properties\n if (Object.keys(movedToAnimate).length > 0) {\n animate(scope.current, { ...movedToAnimate }, { duration: 0 })\n }\n }\n\n const lastAnimated = lastDoAnimate.current\n if (lastAnimated) {\n // Pass dontAnimate as third arg to prevent clearing styles that moved to dontAnimate\n removeRemovedStyles(lastAnimated, doAnimate, node, dontAnimate)\n }\n\n const diff = getDiff(lastDoAnimate.current, doAnimate)\n if (diff) {\n // FIX: Handle animation interruption for position-only animations\n // Only apply this fix when:\n // 1. There's a running animation\n // 2. The transform change is POSITION-ONLY (just translate, no scale/rotate/skew)\n // 3. animatePosition is being used (Popper/Tooltip pattern)\n // This fixes tooltip position jumping without breaking AnimatePresence scale/rotate animations\n // NOTE: We check for animatePosition to avoid this fix causing jitter\n // on components like the TAMAGUI logo dot indicator which also use translate-only transforms\n\n const isRunning =\n /**\n * TypeError: Cannot read properties of undefined (reading 'state')\n * at GroupAnimationWithThen.getAll (http://localhost:8081/node_modules/.vite/deps/@tamagui_config_v5-motion.js?v=d717d926:2374:30)\n * at get state (http://localhost:8081/node_modules/.vite/deps/@tamagui_config_v5-motion.js?v=d717d926:2403:17)\n * at flushAnimation (http://localhost:8081/node_modules/.vite/deps/@tamagui_config_v5-motion.js?v=d717d926:9686:49)\n **/\n // @ts-expect-error it is there, and for some crazy reason in ~/chat pretty often i get errors ^\n controls.current?.animations?.length === 0\n ? false\n : controls.current?.state === 'running'\n const targetTransform =\n typeof diff.transform === 'string' ? diff.transform : null\n\n // only apply position fix for translate-only transforms\n const isPositionOnlyTransform =\n targetTransform &&\n targetTransform.includes('translate') &&\n !nonPositionTransformRe.test(targetTransform)\n\n // Position fix for Popper/Tooltip elements with animatePosition.\n // Only apply when:\n // 1. Animation is actively running\n // 2. Transform is position-only (translate without scale/rotate/etc)\n // 3. Element has data-popper-animate-position attribute (set by Popper when\n // animatePosition is true)\n //\n // The issue: when a Popper animation is interrupted mid-flight, motion's\n // animate() may start from wrong position causing jumps to origin.\n //\n // Why check data-popper-animate-position: This attribute is ONLY set on Popper\n // elements that explicitly use animatePosition. Regular\n // components like the logo Circle don't have this attribute, so they won't\n // get this fix applied (which would cause jitter due to getComputedStyle\n // overhead on rapid updates).\n\n // check if this is a Popper element with animated position\n const isPopperElement = node.hasAttribute('data-popper-animate-position')\n\n // also apply fix for AnimatePresence children that just finished entering\n // this fixes roving tabs indicator jumping when rapidly switching\n const isEnteringPresenceChild = presence && justFinishedEntering\n\n if (\n isRunning &&\n controls.current &&\n isPositionOnlyTransform &&\n (isPopperElement || isEnteringPresenceChild)\n ) {\n const currentTransform = getComputedStyle(node).transform\n\n if (currentTransform && currentTransform !== 'none') {\n const matrixMatch = currentTransform.match(\n /matrix\\([^,]+,\\s*[^,]+,\\s*[^,]+,\\s*[^,]+,\\s*([^,]+),\\s*([^)]+)\\)/\n )\n\n if (matrixMatch) {\n // stop animation and preserve current position\n controls.current.stop()\n node.style.transform = currentTransform\n\n // animate from current matrix position to target\n const currentX = Number.parseFloat(matrixMatch[1])\n const currentY = Number.parseFloat(matrixMatch[2])\n const keyframeDiff = {\n ...diff,\n transform: [\n `translateX(${currentX}px) translateY(${currentY}px)`,\n targetTransform,\n ],\n }\n\n controls.current = animate(\n scope.current,\n keyframeDiff,\n animationOptions\n )\n lastAnimateAt.current = Date.now()\n lastDontAnimate.current = dontAnimate ? { ...dontAnimate } : {}\n lastDoAnimate.current = doAnimate ? { ...doAnimate } : {}\n return\n }\n }\n }\n\n // IMPORTANT: Spread to create mutable copy - style objects may be frozen\n // fix transparent colors to use rgba for motion.dev compatibility\n const fixedDiff = fixTransparentColors({ ...diff }, lastDoAnimate.current)\n\n controls.current = animate(scope.current, fixedDiff, animationOptions)\n lastAnimateAt.current = Date.now()\n }\n }\n\n // IMPORTANT: Spread to create mutable copies - objects may be frozen\n lastDontAnimate.current = dontAnimate ? { ...dontAnimate } : {}\n lastDoAnimate.current = doAnimate ? { ...doAnimate } : {}\n } finally {\n if (isExiting) {\n if (controls.current) {\n controls.current.finished.then(() => {\n sendExitComplete?.()\n })\n } else {\n sendExitComplete?.()\n }\n }\n }\n }\n\n useStyleEmitter?.((nextStyle) => {\n const animationProps = getMotionAnimatedProps(\n props as any,\n nextStyle,\n disableAnimation,\n animationState\n )\n\n flushAnimation(animationProps)\n })\n\n useIsomorphicLayoutEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n\n // during hydration, use full sync logic to prevent flash\n // doing this - will fix some of the enter (accordion) glitches but breaks animatepresence\n // isHydrating || (isMounting && !isEntering)\n if (isHydrating) {\n const node = stateRef.current.host\n\n if (node instanceof HTMLElement) {\n // IMPORTANT: On first render, we need to:\n // 1. Apply dontAnimate styles to the DOM (enterStyle values like scale(0))\n // 2. Tell motion about these styles so it knows the starting state\n // This ensures AnimatePresence enter animations work correctly.\n\n if (dontAnimate) {\n // Apply initial styles to DOM\n Object.assign(node.style, dontAnimate as any)\n\n // Tell motion about the initial state by animating instantly to dontAnimate\n // This syncs motion's internal state with what's actually on the DOM\n // IMPORTANT: Spread to create mutable copy - React/Tamagui style objects may be frozen\n animate(scope.current, { ...dontAnimate }, { duration: 0 })\n }\n\n // If there are styles to animate, set them up (but animation is disabled on first render)\n if (doAnimate && Object.keys(doAnimate).length > 0) {\n // IMPORTANT: Spread to create mutable copy - objects may be frozen\n lastDoAnimate.current = { ...doAnimate }\n animate(scope.current, { ...doAnimate }, { duration: 0 })\n } else {\n // doAnimate is empty, so track dontAnimate as the initial animated state\n // This way on next render, getDiff will detect the change\n // IMPORTANT: Spread to create mutable copy - objects may be frozen\n lastDoAnimate.current = dontAnimate ? { ...dontAnimate } : {}\n }\n }\n\n // IMPORTANT: Spread to create mutable copy - objects may be frozen\n lastDontAnimate.current = dontAnimate ? { ...dontAnimate } : {}\n lastAnimateAt.current = Date.now()\n return\n }\n\n // after hydration, use simpler logic\n lastDontAnimate.current = dontAnimate ? { ...dontAnimate } : {}\n lastDoAnimate.current = doAnimate ? { ...doAnimate } : {}\n return\n }\n\n // don't ever queue on a render\n flushAnimation({\n doAnimate,\n dontAnimate,\n animationOptions,\n })\n }, [styleKey, isExiting, disableAnimation])\n\n if (shouldDebug) {\n console.groupCollapsed(`[motion] 🌊 render`)\n console.info({\n style,\n doAnimate,\n dontAnimate,\n styleKey,\n scope,\n animationOptions,\n isExiting,\n isFirstRender: isFirstRender.current,\n animationProps,\n })\n console.groupEnd()\n }\n\n return {\n // we never change this, after first render on\n style: firstRenderStyle,\n ref: scope,\n render: 'div',\n }\n },\n\n useAnimatedNumber(initial): UniversalAnimatedNumber<MotionAnimatedNumber> {\n const motionValue = useMotionValue(initial)\n\n return React.useMemo(\n () => ({\n getInstance() {\n return motionValue\n },\n getValue() {\n return motionValue.get()\n },\n setValue(next, config = { type: 'spring' }, onFinish) {\n if (config.type === 'direct') {\n MotionValueStrategy.set(motionValue, {\n type: 'direct',\n })\n motionValue.set(next)\n onFinish?.()\n } else {\n MotionValueStrategy.set(motionValue, config)\n\n if (onFinish) {\n const unsubscribe = motionValue.on('change', (value) => {\n if (Math.abs(value - next) < 0.01) {\n unsubscribe()\n onFinish()\n }\n })\n }\n\n motionValue.set(next)\n // Motion doesn't have a direct onFinish callback, so we simulate it\n }\n },\n stop() {\n motionValue.stop()\n },\n }),\n [motionValue]\n )\n },\n\n useAnimatedNumberReaction({ value }, onValue) {\n const instance = value.getInstance() as MotionValue<number>\n useMotionValueEvent(instance, 'change', onValue)\n },\n\n useAnimatedNumberStyle(val, getStyleProp) {\n const motionValue = val.getInstance() as MotionValue<number>\n const getStyleRef = useRef<typeof getStyleProp>(getStyleProp)\n\n // we need to change useAnimatedNumberStyle to have dep args to be concurrent safe\n getStyleRef.current = getStyleProp\n\n // never changes\n return useMemo(() => {\n return {\n getStyle: (cur) => {\n return getStyleRef.current(cur)\n },\n motionValue,\n } satisfies MotionAnimatedNumberStyle\n }, [])\n },\n }\n\n function getMotionAnimatedProps(\n props: { transition: TransitionProp | null; animateOnly?: string[] },\n style: Record<string, unknown>,\n disable: boolean,\n animationState: 'enter' | 'exit' | 'default' = 'default'\n ): AnimationProps {\n if (disable) {\n return {\n dontAnimate: style,\n }\n }\n\n const animationOptions = getAnimationOptions(props.transition, animationState)\n\n let dontAnimate: Record<string, unknown> | undefined\n let doAnimate: Record<string, unknown> | undefined\n\n const animateOnly = props.animateOnly as string[] | undefined\n for (const key in style) {\n const value = style[key]\n if (disableAnimationProps.has(key) || (animateOnly && !animateOnly.includes(key))) {\n dontAnimate ||= {}\n dontAnimate[key] = value\n } else {\n doAnimate ||= {}\n doAnimate[key] = value\n }\n }\n\n return {\n dontAnimate,\n doAnimate,\n animationOptions,\n }\n }\n\n function getAnimationOptions(\n transitionProp: TransitionProp | null,\n animationState: 'enter' | 'exit' | 'default' = 'default'\n ): TransitionAnimationOptions {\n const normalized = normalizeTransition(transitionProp)\n\n // Get the effective animation key based on enter/exit/default state\n const effectiveKey = getEffectiveAnimation(normalized, animationState)\n\n // If no animation defined, return empty config\n if (!effectiveKey && Object.keys(normalized.properties).length === 0) {\n return {}\n }\n\n const defaultConfig = effectiveKey ? withInferredType(animations[effectiveKey]) : null\n\n // Framer Motion uses seconds, so convert from ms\n const delay =\n typeof normalized.delay === 'number' ? normalized.delay / 1000 : undefined\n\n // Convert global config overrides from ms to seconds where needed\n let globalConfigOverride: Record<string, unknown> | undefined\n if (normalized.config) {\n globalConfigOverride = { ...normalized.config }\n if (typeof normalized.config.duration === 'number') {\n globalConfigOverride.duration = normalized.config.duration / 1000\n }\n }\n\n // Build the animation options\n // Framer Motion's animate() expects default config at the TOP LEVEL, not nested under 'default'\n // Per-property configs can be nested under property names like { scale: { duration: 1 } }\n const result: TransitionAnimationOptions = {}\n\n // Apply base config from preset at top level (this is what animate() reads as default)\n if (defaultConfig) {\n Object.assign(result, defaultConfig)\n }\n\n // Apply global spring config overrides at top level\n if (globalConfigOverride) {\n Object.assign(result, globalConfigOverride)\n }\n\n // Apply delay at top level\n if (delay) {\n result.delay = delay\n }\n\n // Also set the 'default' key for backwards compatibility with per-property fallback logic\n if (defaultConfig || globalConfigOverride || delay) {\n result.default = {\n ...defaultConfig,\n ...globalConfigOverride,\n ...(delay ? { delay } : null),\n }\n }\n\n // Add property-specific animations\n for (const [propName, animationNameOrConfig] of Object.entries(\n normalized.properties\n )) {\n if (typeof animationNameOrConfig === 'string') {\n result[propName] = withInferredType(animations[animationNameOrConfig])\n } else if (animationNameOrConfig && typeof animationNameOrConfig === 'object') {\n const baseConfig = animationNameOrConfig.type\n ? withInferredType(animations[animationNameOrConfig.type])\n : defaultConfig\n\n // @ts-expect-error\n result[propName] = {\n ...baseConfig,\n ...animationNameOrConfig,\n }\n }\n }\n\n // we standardize to ms across drivers, motion expects s\n // apply to default and each per-property config\n convertMsToS(result.default)\n for (const key in result) {\n if (key !== 'default') convertMsToS(result[key])\n }\n\n return result\n }\n}\n\n// infer type from config shape if not explicitly set, always returns a copy\nfunction withInferredType(config: AnimationConfig): AnimationConfig {\n const isTimingBased =\n config.duration !== undefined &&\n config.damping === undefined &&\n config.stiffness === undefined &&\n config.mass === undefined\n return { type: isTimingBased ? 'tween' : 'spring', ...config }\n}\n\n// convert tween duration/delay from ms to s (motion expects seconds)\nfunction convertMsToS(config: ValueTransition | undefined) {\n if (!config || config.type !== 'tween') return\n if (typeof config.duration === 'number') config.duration = config.duration / 1000\n if (typeof config.delay === 'number') config.delay = config.delay / 1000\n}\n\nfunction removeRemovedStyles(\n prev: object,\n next: object,\n node: HTMLElement,\n dontClearIfIn?: object\n) {\n for (const key in prev) {\n if (!(key in next)) {\n // Don't clear if the style is now in dontAnimate (moved from animated to non-animated)\n if (dontClearIfIn && key in dontClearIfIn) {\n continue\n }\n node.style[key] = ''\n }\n }\n}\n\n// sort of temporary\nconst disableAnimationProps = new Set<string>([\n 'alignContent',\n 'alignItems',\n 'aspectRatio',\n 'backdropFilter',\n 'boxSizing',\n 'contain',\n 'containerType',\n 'display',\n 'flexBasis',\n 'flexDirection',\n 'flexGrow',\n 'flexShrink',\n 'fontFamily',\n 'justifyContent',\n 'marginBottom',\n 'marginLeft',\n 'marginRight',\n 'marginTop',\n 'maxHeight',\n 'maxWidth',\n 'minHeight',\n 'minWidth',\n 'overflow',\n 'overflowX',\n 'overflowY',\n 'pointerEvents',\n 'position',\n 'textWrap',\n 'transformOrigin',\n 'userSelect',\n 'WebkitBackdropFilter',\n 'zIndex',\n])\n\nconst MotionView = createMotionView('div')\nconst MotionText = createMotionView('span')\n\nfunction createMotionView(defaultTag: string) {\n // return forwardRef((props: any, ref) => {\n // console.info('rendering?', props)\n // const Element = motion[props.render || defaultTag]\n // return <Element ref={ref} {...props} />\n // })\n const isText = defaultTag === 'span'\n\n const Component = forwardRef((propsIn: any, ref) => {\n const { forwardedRef, animation, render = defaultTag, style, ...propsRest } = propsIn\n const [scope, animate] = useAnimate()\n const hostRef = useRef<HTMLElement>(null)\n const composedRefs = useComposedRefs(forwardedRef, ref, hostRef, scope)\n\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 const styles = Array.isArray(style) ? style : [style]\n\n // we can assume just one animatedStyle max for now\n const [animatedStyle, nonAnimatedStyles] = (() => {\n return [\n styles.find((x) => x.getStyle) as MotionAnimatedNumberStyle | undefined,\n styles.filter((x) => !x.getStyle),\n ] as const\n })()\n\n function getProps(props: any) {\n const out = getSplitStyles(\n props,\n isText ? Text.staticConfig : View.staticConfig,\n state?.theme,\n state?.name,\n {\n unmounted: false,\n },\n {\n isAnimated: false,\n noClass: true,\n // noMergeStyle: true,\n resolveValues: 'auto',\n }\n )\n\n if (!out) {\n return {}\n }\n\n // we can definitely get rid of this here\n if (out.viewProps.style) {\n fixStyles(out.viewProps.style)\n styleToCSS(out.viewProps.style)\n }\n\n return out.viewProps\n }\n\n const props = getProps({ ...propsRest, style: nonAnimatedStyles })\n const Element = render || 'div'\n const transformedProps = hooks.usePropsTransform?.(render, props, stateRef, false)\n\n useEffect(() => {\n if (!animatedStyle) return\n\n return animatedStyle.motionValue.on('change', (value) => {\n const nextStyle = animatedStyle.getStyle(value)\n const animationConfig = MotionValueStrategy.get(animatedStyle.motionValue)\n const node = hostRef.current\n\n const webStyle = getProps({ style: nextStyle }).style\n\n if (webStyle && node instanceof HTMLElement) {\n const motionAnimationConfig =\n animationConfig?.type === 'timing'\n ? {\n type: 'tween',\n duration: (animationConfig?.duration || 0) / 1000,\n }\n : animationConfig?.type === 'direct'\n ? { type: 'tween', duration: 0 }\n : {\n type: 'spring',\n ...(animationConfig as any),\n }\n\n animate(node, webStyle as any, motionAnimationConfig)\n }\n })\n }, [animatedStyle])\n\n return <Element {...transformedProps} ref={composedRefs} />\n })\n\n Component['acceptTagProp'] = true\n\n return Component\n}\n\nfunction getDiff<T extends Record<string, unknown>>(\n previous: T | null,\n next: T\n): Record<string, unknown> | null {\n if (!previous) {\n return next\n }\n\n let diff: Record<string, unknown> | null = null\n for (const key in next) {\n if (next[key] !== previous[key]) {\n diff ||= {}\n diff[key] = next[key]\n }\n }\n return diff\n}\n\n// motion.dev can't animate to \"transparent\" - convert it to rgba based on previous value\n// if previous was rgba, use same rgb with alpha 0, otherwise use rgba(0,0,0,0)\nfunction fixTransparentColors(\n diff: Record<string, unknown>,\n previous: Record<string, unknown> | null\n): Record<string, unknown> {\n let result = diff\n for (const key in diff) {\n if (diff[key] === 'transparent') {\n const prev = previous?.[key]\n let fixed = 'rgba(0, 0, 0, 0)'\n if (typeof prev === 'string') {\n // match rgba(r, g, b, a) or rgb(r, g, b)\n const rgbaMatch = prev.match(/^rgba?\\(([^,]+),\\s*([^,]+),\\s*([^,)]+)/)\n if (rgbaMatch) {\n fixed = `rgba(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]}, 0)`\n }\n }\n if (result === diff) {\n result = { ...diff }\n }\n result[key] = fixed\n }\n }\n return result\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/index.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "import './polyfill'\n\nexport * from './createAnimations'\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/index.native.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// native stub - animations-motion only works on web (uses framer-motion/motion library)\n// on native, use @tamagui/animations-react-native or @tamagui/animations-reanimated\n\nimport type { AnimationDriver } from '@tamagui/web'\n\nlet hasWarnedOnce = false\n\nexport function createAnimations<A extends Record<string, any>>(\n _animations: A\n): AnimationDriver<A> {\n if (process.env.NODE_ENV === 'development') {\n if (!hasWarnedOnce) {\n hasWarnedOnce = true\n console.warn(\n '[@tamagui/animations-motion] This animation driver only works on web. On native, use @tamagui/animations-react-native or @tamagui/animations-reanimated instead.'\n )\n }\n }\n\n // return a noop driver\n // @ts-expect-error its an error anyway\n return {\n isReactNative: false,\n animations: _animations,\n View: undefined as any,\n Text: undefined as any,\n }\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }
@@ -4,8 +4,8 @@
4
4
  "sources": [
5
5
  "src/polyfill.ts"
6
6
  ],
7
+ "version": 3,
7
8
  "sourcesContent": [
8
9
  "// for SSR\nif (typeof requestAnimationFrame === 'undefined') {\n globalThis['requestAnimationFrame'] = setTimeout\n}\n"
9
- ],
10
- "version": 3
10
+ ]
11
11
  }