@react-native-ama/animations 1.2.0 → 2.0.0-beta.0

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.
Files changed (30) hide show
  1. package/dist/hooks/useAnimation.js +4 -4
  2. package/dist/hooks/useAnimationDuration.js +2 -2
  3. package/dist/hooks/useReanimatedAnimationBuilder.js +4 -4
  4. package/dist/hooks/useReanimatedTiming.d.ts +1 -1
  5. package/dist/hooks/useReanimatedTiming.js +2 -2
  6. package/dist/internals/animationConstants.d.ts +3 -0
  7. package/dist/internals/animationConstants.js +32 -0
  8. package/dist/internals/animationStates.d.ts +2 -0
  9. package/dist/internals/animationStates.js +43 -0
  10. package/dist/internals/useAMAContextSafe.d.ts +3 -0
  11. package/dist/internals/useAMAContextSafe.js +17 -0
  12. package/dist/utils/isMotionAnimation.d.ts +1 -1
  13. package/dist/utils/isMotionAnimation.js +2 -2
  14. package/package.json +79 -19
  15. package/src/components/AnimatedContainer.test.tsx +6 -12
  16. package/src/components/AnimatedContainer.tsx +2 -2
  17. package/src/hooks/useAnimation.test.ts +0 -1
  18. package/src/hooks/useAnimation.ts +3 -3
  19. package/src/hooks/useAnimationDuration.test.ts +0 -1
  20. package/src/hooks/useAnimationDuration.ts +2 -3
  21. package/src/hooks/useReanimatedAnimationBuilder.test.ts +1 -2
  22. package/src/hooks/useReanimatedAnimationBuilder.ts +3 -3
  23. package/src/hooks/useReanimatedTiming.test.ts +3 -4
  24. package/src/hooks/useReanimatedTiming.ts +3 -4
  25. package/src/internals/animationConstants.ts +33 -0
  26. package/src/internals/animationStates.test.ts +71 -0
  27. package/src/internals/animationStates.ts +55 -0
  28. package/src/internals/useAMAContextSafe.test.ts +44 -0
  29. package/src/internals/useAMAContextSafe.ts +13 -0
  30. package/src/utils/isMotionAnimation.ts +1 -1
@@ -35,12 +35,12 @@ var __rest = (this && this.__rest) || function (s, e) {
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.useAnimation = void 0;
38
- const core_1 = require("@react-native-ama/core");
39
- const internal_1 = require("@react-native-ama/internal");
38
+ const useAMAContextSafe_1 = require("../internals/useAMAContextSafe");
39
+ const animationStates_1 = require("../internals/animationStates");
40
40
  const React = __importStar(require("react"));
41
41
  const react_native_1 = require("react-native");
42
42
  const useAnimation = ({ duration, useNativeDriver, from, to, skipIfReduceMotionEnabled = false, }) => {
43
- const { isReduceMotionEnabled } = (0, core_1.useAMAContext)();
43
+ const { isReduceMotionEnabled } = (0, useAMAContextSafe_1.useAMAContextSafe)();
44
44
  const progress = React.useRef(new react_native_1.Animated.Value(0)).current;
45
45
  const reduceMotionProgress = React.useRef(new react_native_1.Animated.Value(0)).current;
46
46
  const hasOnlyMotionAnimation = React.useRef(false);
@@ -62,7 +62,7 @@ const useAnimation = ({ duration, useNativeDriver, from, to, skipIfReduceMotionE
62
62
  useNativeDriver,
63
63
  });
64
64
  };
65
- const _a = (0, internal_1.interpolateAnimationStates)(from, to, isReduceMotionEnabled, progress, reduceMotionProgress), { __hasOnlyMotionAnimation } = _a, style = __rest(_a, ["__hasOnlyMotionAnimation"]);
65
+ const _a = (0, animationStates_1.interpolateAnimationStates)(from, to, isReduceMotionEnabled, progress, reduceMotionProgress), { __hasOnlyMotionAnimation } = _a, style = __rest(_a, ["__hasOnlyMotionAnimation"]);
66
66
  hasOnlyMotionAnimation.current = __hasOnlyMotionAnimation;
67
67
  return {
68
68
  animatedStyle: style,
@@ -4,11 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useAnimationDuration = void 0;
7
- const core_1 = require("@react-native-ama/core");
7
+ const useAMAContextSafe_1 = require("../internals/useAMAContextSafe");
8
8
  const react_1 = __importDefault(require("react"));
9
9
  const isMotionAnimation_1 = require("../utils/isMotionAnimation");
10
10
  const useAnimationDuration = () => {
11
- const { isReduceMotionEnabled } = (0, core_1.useAMAContext)();
11
+ const { isReduceMotionEnabled } = (0, useAMAContextSafe_1.useAMAContextSafe)();
12
12
  const getAnimationDuration = react_1.default.useCallback((propertyToAnimate, duration) => {
13
13
  return isReduceMotionEnabled && (0, isMotionAnimation_1.isMotionAnimation)(propertyToAnimate)
14
14
  ? 0
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useReanimatedAnimationBuilder = void 0;
4
- const core_1 = require("@react-native-ama/core");
5
- const internal_1 = require("@react-native-ama/internal");
4
+ const useAMAContextSafe_1 = require("../internals/useAMAContextSafe");
5
+ const animationConstants_1 = require("../internals/animationConstants");
6
6
  const react_native_reanimated_1 = require("react-native-reanimated");
7
7
  const useReanimatedAnimationBuilder = ({ from, to, exitFrom, exitTo, duration, }) => {
8
- const { isReduceMotionEnabled } = (0, core_1.useAMAContext)();
8
+ const { isReduceMotionEnabled } = (0, useAMAContextSafe_1.useAMAContextSafe)();
9
9
  const animationBuilder = (initial, final) => {
10
10
  return (values) => {
11
11
  'worklet';
@@ -18,7 +18,7 @@ const useReanimatedAnimationBuilder = ({ from, to, exitFrom, exitTo, duration, }
18
18
  // @ts-ignore
19
19
  typeof toValue === 'string' ? values[toValue] || 0 : toValue;
20
20
  const realDuration = isReduceMotionEnabled &&
21
- internal_1.MOTION_ANIMATIONS.includes(key)
21
+ animationConstants_1.MOTION_ANIMATIONS.includes(key)
22
22
  ? 0
23
23
  : duration;
24
24
  const newValue = Array.isArray(value)
@@ -1,4 +1,4 @@
1
- import type { MotionAnimationKey } from '@react-native-ama/internal';
1
+ import type { MotionAnimationKey } from '../internals/animationConstants';
2
2
  import { AnimationCallback, WithSpringConfig, WithTimingConfig } from 'react-native-reanimated';
3
3
  export declare const useReanimatedTiming: () => {
4
4
  withTiming: (propertyKey: MotionAnimationKey, toValue: number, config?: WithTimingConfig, callback?: AnimationCallback) => number;
@@ -4,12 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useReanimatedTiming = void 0;
7
- const core_1 = require("@react-native-ama/core");
7
+ const useAMAContextSafe_1 = require("../internals/useAMAContextSafe");
8
8
  const react_1 = __importDefault(require("react"));
9
9
  const react_native_reanimated_1 = require("react-native-reanimated");
10
10
  const isMotionAnimation_1 = require("../utils/isMotionAnimation");
11
11
  const useReanimatedTiming = () => {
12
- const { isReduceMotionEnabled } = (0, core_1.useAMAContext)();
12
+ const { isReduceMotionEnabled } = (0, useAMAContextSafe_1.useAMAContextSafe)();
13
13
  const withTiming = react_1.default.useCallback((propertyKey, toValue, config = {}, callback) => {
14
14
  if (isReduceMotionEnabled && (0, isMotionAnimation_1.isMotionAnimation)(propertyKey)) {
15
15
  config.duration = 0;
@@ -0,0 +1,3 @@
1
+ import type { ViewStyle } from 'react-native';
2
+ export type MotionAnimationKey = Partial<keyof ViewStyle> | 'scale';
3
+ export declare const MOTION_ANIMATIONS: MotionAnimationKey[];
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MOTION_ANIMATIONS = void 0;
4
+ exports.MOTION_ANIMATIONS = [
5
+ 'left',
6
+ 'bottom',
7
+ 'top',
8
+ 'right',
9
+ 'transform',
10
+ 'translateX',
11
+ 'translateY',
12
+ 'scaleX',
13
+ 'scaleY',
14
+ 'scale',
15
+ 'width',
16
+ 'height',
17
+ 'padding',
18
+ 'paddingBottom',
19
+ 'paddingLeft',
20
+ 'paddingRight',
21
+ 'paddingTop',
22
+ 'paddingVertical',
23
+ 'paddingHorizontal',
24
+ 'margin',
25
+ 'marginBottom',
26
+ 'marginLeft',
27
+ 'marginRight',
28
+ 'marginTop',
29
+ 'marginHorizontal',
30
+ 'marginVertical',
31
+ 'rotation',
32
+ ];
@@ -0,0 +1,2 @@
1
+ import type { Animated } from 'react-native';
2
+ export declare const interpolateAnimationStates: (from: Record<string, any>, to: Record<string, any>, isReduceMotionEnabled: boolean, progressValue: Animated.Value, reduceMotionProgressValue: Animated.Value) => Record<string, any>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.interpolateAnimationStates = void 0;
15
+ const animationConstants_1 = require("./animationConstants");
16
+ const interpolateAnimationStates = (from, to, isReduceMotionEnabled, progressValue, reduceMotionProgressValue) => {
17
+ return Object.keys(from).reduce((outputAnimation, key) => {
18
+ const isMotionAnimation = animationConstants_1.MOTION_ANIMATIONS.includes(key);
19
+ const progressKey = isReduceMotionEnabled && isMotionAnimation
20
+ ? reduceMotionProgressValue
21
+ : progressValue;
22
+ if (Array.isArray(from[key])) {
23
+ outputAnimation[key] = from[key].map((animationObject, index) => {
24
+ const _a = (0, exports.interpolateAnimationStates)(animationObject, to[key][index], isReduceMotionEnabled, progressValue, reduceMotionProgressValue), { __hasOnlyMotionAnimation } = _a, style = __rest(_a, ["__hasOnlyMotionAnimation"]);
25
+ outputAnimation.__hasOnlyMotionAnimation =
26
+ outputAnimation.__hasOnlyMotionAnimation &&
27
+ __hasOnlyMotionAnimation;
28
+ return style;
29
+ });
30
+ }
31
+ else {
32
+ outputAnimation[key] = progressKey.interpolate({
33
+ inputRange: [0, 1],
34
+ // @ts-ignore
35
+ outputRange: [from[key], to[key]],
36
+ });
37
+ outputAnimation.__hasOnlyMotionAnimation =
38
+ outputAnimation.__hasOnlyMotionAnimation && isMotionAnimation;
39
+ }
40
+ return outputAnimation;
41
+ }, { __hasOnlyMotionAnimation: true });
42
+ };
43
+ exports.interpolateAnimationStates = interpolateAnimationStates;
@@ -0,0 +1,3 @@
1
+ export declare const useAMAContextSafe: () => {
2
+ isReduceMotionEnabled: boolean;
3
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAMAContextSafe = void 0;
4
+ const AMACore = require('@react-native-ama/core');
5
+ const SAFE_DEFAULTS = {
6
+ isReduceMotionEnabled: false,
7
+ };
8
+ const useAMAContextSafe = () => {
9
+ var _a, _b;
10
+ try {
11
+ return (_b = (_a = AMACore === null || AMACore === void 0 ? void 0 : AMACore.useAMAContext) === null || _a === void 0 ? void 0 : _a.call(AMACore)) !== null && _b !== void 0 ? _b : SAFE_DEFAULTS;
12
+ }
13
+ catch (_c) {
14
+ return SAFE_DEFAULTS;
15
+ }
16
+ };
17
+ exports.useAMAContextSafe = useAMAContextSafe;
@@ -1,2 +1,2 @@
1
- import { type MotionAnimationKey } from '@react-native-ama/internal';
1
+ import { type MotionAnimationKey } from '../internals/animationConstants';
2
2
  export declare const isMotionAnimation: (key: MotionAnimationKey) => boolean;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isMotionAnimation = void 0;
4
- const internal_1 = require("@react-native-ama/internal");
4
+ const animationConstants_1 = require("../internals/animationConstants");
5
5
  const isMotionAnimation = (key) => {
6
- return internal_1.MOTION_ANIMATIONS.includes(key);
6
+ return animationConstants_1.MOTION_ANIMATIONS.includes(key);
7
7
  };
8
8
  exports.isMotionAnimation = isMotionAnimation;
package/package.json CHANGED
@@ -1,33 +1,53 @@
1
1
  {
2
2
  "name": "@react-native-ama/animations",
3
- "version": "1.2.0",
4
- "react-native": "src/index",
5
- "types": "dist/index.d.ts",
6
- "main": "dist/index.js",
3
+ "version": "2.0.0-beta.0",
4
+ "sideEffects": false,
7
5
  "exports": {
8
- ".": [
9
- {
10
- "imports": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- },
13
- "./dist/index.js"
14
- ]
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "react-native": "./src/index.ts",
9
+ "default": "./dist/index.js"
10
+ },
11
+ "./AnimatedContainer": {
12
+ "types": "./dist/components/AnimatedContainer.d.ts",
13
+ "default": "./dist/components/AnimatedContainer.js"
14
+ },
15
+ "./useAnimation": {
16
+ "types": "./dist/hooks/useAnimation.d.ts",
17
+ "default": "./dist/hooks/useAnimation.js"
18
+ },
19
+ "./useAnimationDuration": {
20
+ "types": "./dist/hooks/useAnimationDuration.d.ts",
21
+ "default": "./dist/hooks/useAnimationDuration.js"
22
+ },
23
+ "./useReanimatedAnimationBuilder": {
24
+ "types": "./dist/hooks/useReanimatedAnimationBuilder.d.ts",
25
+ "default": "./dist/hooks/useReanimatedAnimationBuilder.js"
26
+ },
27
+ "./useReanimatedTiming": {
28
+ "types": "./dist/hooks/useReanimatedTiming.d.ts",
29
+ "default": "./dist/hooks/useReanimatedTiming.js"
30
+ }
15
31
  },
16
32
  "files": [
17
33
  "src",
18
34
  "dist"
19
35
  ],
20
36
  "scripts": {
21
- "build": "rm -rf dist && tsc -p ./tsconfig.build.json",
22
- "typecheck": "tsc --noEmit"
23
- },
24
- "dependencies": {
25
- "@react-native-ama/core": "~1.2.0",
26
- "@react-native-ama/internal": "~1.2.0"
37
+ "build": "rm -rf dist && ../../node_modules/.bin/tsc -p ./tsconfig.build.json",
38
+ "typecheck": "../../node_modules/.bin/tsc --noEmit",
39
+ "test": "jest"
27
40
  },
28
41
  "peerDependencies": {
42
+ "@react-native-ama/core": "~2.0.0-beta.0",
29
43
  "react": "*",
30
- "react-native": "*"
44
+ "react-native": "*",
45
+ "react-native-reanimated": ">=2.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@react-native-ama/core": {
49
+ "optional": true
50
+ }
31
51
  },
32
52
  "keywords": [
33
53
  "react-native",
@@ -47,5 +67,45 @@
47
67
  "provenance": true
48
68
  },
49
69
  "author": "",
50
- "license": "MIT"
70
+ "license": "MIT",
71
+ "jest": {
72
+ "preset": "react-native",
73
+ "setupFilesAfterEnv": [
74
+ "<rootDir>/jest.setup.js"
75
+ ],
76
+ "modulePathIgnorePatterns": [
77
+ "<rootDir>/node_modules",
78
+ "<rootDir>/dist/"
79
+ ],
80
+ "collectCoverageFrom": [
81
+ "src/**/*.{ts,tsx}",
82
+ "!src/index.ts"
83
+ ],
84
+ "coverageThreshold": {
85
+ "global": {
86
+ "statements": 100,
87
+ "branches": 95,
88
+ "functions": 100,
89
+ "lines": 100
90
+ }
91
+ },
92
+ "transformIgnorePatterns": [
93
+ "node_modules/(?!(react-native-reanimated|expo|@expo)/)"
94
+ ],
95
+ "transform": {
96
+ "\\.[jt]sx?$": "babel-jest"
97
+ },
98
+ "moduleDirectories": [
99
+ "node_modules",
100
+ "../../playground/node_modules"
101
+ ],
102
+ "moduleNameMapper": {
103
+ "@react-native/js-polyfills/error-guard": "<rootDir>/../../jest-mocks/error-guard.js",
104
+ "^(\\.{1,2}/)*ama\\.config\\.json$": "<rootDir>/../../jest-mocks/ama-config.js",
105
+ "^(\\.{1,2}/)*ReactNativeAmaModule$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js",
106
+ "^(\\.{1,2}/)*ReactNativeAmaView(\\.web)?$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js",
107
+ "^@react-native-ama/core$": "<rootDir>/../../jest-mocks/ama-core.js"
108
+ },
109
+ "verbose": true
110
+ }
51
111
  }
@@ -1,7 +1,6 @@
1
1
  import { AutofocusContainer } from '@react-native-ama/core';
2
2
  import { render } from '@testing-library/react-native';
3
3
  import * as React from 'react';
4
-
5
4
  import * as UseReanimatedAnimationBuilder from '../hooks/useReanimatedAnimationBuilder';
6
5
  import { AnimatedContainer } from './AnimatedContainer';
7
6
 
@@ -27,14 +26,16 @@ describe('AnimatedContainer', () => {
27
26
  expect(useReanimatedAnimationBuilder).toHaveBeenCalledWith({
28
27
  from,
29
28
  to,
29
+ exitFrom: undefined,
30
+ exitTo: undefined,
30
31
  duration: 300,
31
- exit: undefined,
32
32
  });
33
33
 
34
34
  expect(getByTestId('test-id').props).toEqual({
35
35
  children: undefined,
36
36
  entering: 'entering',
37
37
  exiting: 'exiting',
38
+ ref: null,
38
39
  style: undefined,
39
40
  testID: 'test-id',
40
41
  });
@@ -52,14 +53,16 @@ describe('AnimatedContainer', () => {
52
53
  expect(useReanimatedAnimationBuilder).toHaveBeenCalledWith({
53
54
  from,
54
55
  to,
56
+ exitFrom: undefined,
57
+ exitTo: undefined,
55
58
  duration: 600,
56
- exit: undefined,
57
59
  });
58
60
 
59
61
  expect(getByTestId('test-id').props).toEqual({
60
62
  children: undefined,
61
63
  entering: 'entering',
62
64
  exiting: 'exiting',
65
+ ref: null,
63
66
  style: undefined,
64
67
  testID: 'test-id',
65
68
  });
@@ -81,15 +84,6 @@ describe('AnimatedContainer', () => {
81
84
  });
82
85
 
83
86
  jest.mock('../hooks/useReanimatedAnimationBuilder');
84
- jest.mock('../hooks/useFocus', () => {
85
- return {
86
- useFocus: () => {
87
- return {
88
- setFocus: jest.fn(),
89
- };
90
- },
91
- };
92
- });
93
87
 
94
88
  jest.mock('react-native-reanimated', () => {
95
89
  const { View } = require('react-native');
@@ -3,7 +3,6 @@ import * as React from 'react';
3
3
  import type { PropsWithChildren } from 'react';
4
4
  import type { ViewProps } from 'react-native';
5
5
  import Animated, { AnimateProps } from 'react-native-reanimated';
6
-
7
6
  import {
8
7
  AnimatedEntryViewStyle,
9
8
  AnimatedExitViewStyle,
@@ -55,7 +54,8 @@ export const AnimatedContainer = React.forwardRef<
55
54
  entering={entering}
56
55
  exiting={exiting}
57
56
  ref={forwardRef}
58
- {...rest}>
57
+ {...rest}
58
+ >
59
59
  {children}
60
60
  </Animated.View>
61
61
  </Wrapper>
@@ -1,7 +1,6 @@
1
1
  import * as AMAProvider from '@react-native-ama/core';
2
2
  import { renderHook } from '@testing-library/react-native';
3
3
  import { Animated } from 'react-native';
4
-
5
4
  import { useAnimation } from './useAnimation';
6
5
 
7
6
  beforeEach(() => {
@@ -1,5 +1,5 @@
1
- import { useAMAContext } from '@react-native-ama/core';
2
- import { interpolateAnimationStates } from '@react-native-ama/internal';
1
+ import { useAMAContextSafe } from '../internals/useAMAContextSafe';
2
+ import { interpolateAnimationStates } from '../internals/animationStates';
3
3
  import * as React from 'react';
4
4
  import type { ViewStyle } from 'react-native';
5
5
  import { Animated } from 'react-native';
@@ -19,7 +19,7 @@ export const useAnimation = ({
19
19
  to,
20
20
  skipIfReduceMotionEnabled = false,
21
21
  }: UseAnimation) => {
22
- const { isReduceMotionEnabled } = useAMAContext();
22
+ const { isReduceMotionEnabled } = useAMAContextSafe();
23
23
  const progress = React.useRef(new Animated.Value(0)).current;
24
24
  const reduceMotionProgress = React.useRef(new Animated.Value(0)).current;
25
25
  const hasOnlyMotionAnimation = React.useRef(false);
@@ -1,6 +1,5 @@
1
1
  import * as AMAProvider from '@react-native-ama/core';
2
2
  import { renderHook } from '@testing-library/react-native';
3
-
4
3
  import { useAnimationDuration } from './useAnimationDuration';
5
4
 
6
5
  describe('useAnimationDuration', () => {
@@ -1,11 +1,10 @@
1
- import { useAMAContext } from '@react-native-ama/core';
1
+ import { useAMAContextSafe } from '../internals/useAMAContextSafe';
2
2
  import React from 'react';
3
3
  import type { ViewStyle } from 'react-native';
4
-
5
4
  import { isMotionAnimation } from '../utils/isMotionAnimation';
6
5
 
7
6
  export const useAnimationDuration = () => {
8
- const { isReduceMotionEnabled } = useAMAContext();
7
+ const { isReduceMotionEnabled } = useAMAContextSafe();
9
8
 
10
9
  const getAnimationDuration = React.useCallback(
11
10
  (propertyToAnimate: keyof ViewStyle, duration: number) => {
@@ -1,13 +1,12 @@
1
1
  import * as AMAProvider from '@react-native-ama/core';
2
2
  import { renderHook } from '@testing-library/react-native';
3
-
4
3
  import { useReanimatedAnimationBuilder } from './useReanimatedAnimationBuilder';
5
4
 
6
5
  beforeEach(() => {
7
6
  jest.clearAllMocks();
8
7
  });
9
8
 
10
- let withTiming: jest.Mock;
9
+ var withTiming: jest.Mock;
11
10
 
12
11
  describe('useReanimatedAnimationBuilder', () => {
13
12
  describe('entering animation', () => {
@@ -1,5 +1,5 @@
1
- import { useAMAContext } from '@react-native-ama/core';
2
- import { MOTION_ANIMATIONS } from '@react-native-ama/internal';
1
+ import { useAMAContextSafe } from '../internals/useAMAContextSafe';
2
+ import { MOTION_ANIMATIONS } from '../internals/animationConstants';
3
3
  import type { ViewProps, ViewStyle } from 'react-native';
4
4
  import {
5
5
  EntryAnimationsValues,
@@ -26,7 +26,7 @@ export const useReanimatedAnimationBuilder = ({
26
26
  exitTo,
27
27
  duration,
28
28
  }: UseReanimatedAnimationBuilder) => {
29
- const { isReduceMotionEnabled } = useAMAContext();
29
+ const { isReduceMotionEnabled } = useAMAContextSafe();
30
30
 
31
31
  const animationBuilder = <InitialValues, FinalValues>(
32
32
  initial: InitialValues,
@@ -1,7 +1,6 @@
1
1
  import * as AMAProvider from '@react-native-ama/core';
2
- import { MOTION_ANIMATIONS } from '@react-native-ama/internal';
2
+ import { MOTION_ANIMATIONS } from '../internals/animationConstants';
3
3
  import { renderHook } from '@testing-library/react-native';
4
-
5
4
  import { useReanimatedTiming } from './useReanimatedTiming';
6
5
 
7
6
  beforeEach(() => {
@@ -79,8 +78,8 @@ const amaContextValues = {
79
78
  isScreenReaderEnabled: false,
80
79
  };
81
80
 
82
- let withSpring: jest.Mock;
83
- let withTiming: jest.Mock;
81
+ var withSpring: jest.Mock;
82
+ var withTiming: jest.Mock;
84
83
 
85
84
  function mockReanimated() {
86
85
  withSpring = jest.fn();
@@ -1,5 +1,5 @@
1
- import { useAMAContext } from '@react-native-ama/core';
2
- import type { MotionAnimationKey } from '@react-native-ama/internal';
1
+ import type { MotionAnimationKey } from '../internals/animationConstants';
2
+ import { useAMAContextSafe } from '../internals/useAMAContextSafe';
3
3
  import React from 'react';
4
4
  import {
5
5
  AnimationCallback,
@@ -8,11 +8,10 @@ import {
8
8
  WithSpringConfig,
9
9
  WithTimingConfig,
10
10
  } from 'react-native-reanimated';
11
-
12
11
  import { isMotionAnimation } from '../utils/isMotionAnimation';
13
12
 
14
13
  export const useReanimatedTiming = () => {
15
- const { isReduceMotionEnabled } = useAMAContext();
14
+ const { isReduceMotionEnabled } = useAMAContextSafe();
16
15
 
17
16
  const withTiming = React.useCallback(
18
17
  (
@@ -0,0 +1,33 @@
1
+ import type { ViewStyle } from 'react-native';
2
+
3
+ export type MotionAnimationKey = Partial<keyof ViewStyle> | 'scale';
4
+
5
+ export const MOTION_ANIMATIONS: MotionAnimationKey[] = [
6
+ 'left',
7
+ 'bottom',
8
+ 'top',
9
+ 'right',
10
+ 'transform',
11
+ 'translateX',
12
+ 'translateY',
13
+ 'scaleX',
14
+ 'scaleY',
15
+ 'scale',
16
+ 'width',
17
+ 'height',
18
+ 'padding',
19
+ 'paddingBottom',
20
+ 'paddingLeft',
21
+ 'paddingRight',
22
+ 'paddingTop',
23
+ 'paddingVertical',
24
+ 'paddingHorizontal',
25
+ 'margin',
26
+ 'marginBottom',
27
+ 'marginLeft',
28
+ 'marginRight',
29
+ 'marginTop',
30
+ 'marginHorizontal',
31
+ 'marginVertical',
32
+ 'rotation',
33
+ ];
@@ -0,0 +1,71 @@
1
+ import { Animated } from 'react-native';
2
+ import { interpolateAnimationStates } from './animationStates';
3
+
4
+ const makeValue = () => {
5
+ const value = new Animated.Value(0);
6
+ // react-native mock exposes .interpolate; ensure it returns something
7
+ return value;
8
+ };
9
+
10
+ describe('interpolateAnimationStates', () => {
11
+ it('interpolates scalar properties using the progress value', () => {
12
+ const progress = makeValue();
13
+ const reduceMotionProgress = makeValue();
14
+ const result = interpolateAnimationStates(
15
+ { opacity: 0 },
16
+ { opacity: 1 },
17
+ false,
18
+ progress,
19
+ reduceMotionProgress,
20
+ );
21
+
22
+ expect(result).toHaveProperty('opacity');
23
+ expect(result.__hasOnlyMotionAnimation).toBe(false);
24
+ });
25
+
26
+ it('uses reduceMotionProgressValue for motion properties when reduce motion is enabled', () => {
27
+ const progress = makeValue();
28
+ const reduceMotionProgress = makeValue();
29
+ const result = interpolateAnimationStates(
30
+ { translateX: 0 },
31
+ { translateX: 100 },
32
+ true,
33
+ progress,
34
+ reduceMotionProgress,
35
+ );
36
+
37
+ expect(result).toHaveProperty('translateX');
38
+ expect(result.__hasOnlyMotionAnimation).toBe(true);
39
+ });
40
+
41
+ it('handles array values (transform arrays) recursively', () => {
42
+ const progress = makeValue();
43
+ const reduceMotionProgress = makeValue();
44
+ const result = interpolateAnimationStates(
45
+ { transform: [{ translateX: 0 }, { scale: 1 }] },
46
+ { transform: [{ translateX: 100 }, { scale: 2 }] },
47
+ false,
48
+ progress,
49
+ reduceMotionProgress,
50
+ );
51
+
52
+ expect(Array.isArray(result.transform)).toBe(true);
53
+ expect(result.transform).toHaveLength(2);
54
+ expect(result.transform[0]).toHaveProperty('translateX');
55
+ expect(result.transform[1]).toHaveProperty('scale');
56
+ });
57
+
58
+ it('sets __hasOnlyMotionAnimation false when mixing motion and non-motion props', () => {
59
+ const progress = makeValue();
60
+ const reduceMotionProgress = makeValue();
61
+ const result = interpolateAnimationStates(
62
+ { opacity: 0, translateX: 0 },
63
+ { opacity: 1, translateX: 100 },
64
+ false,
65
+ progress,
66
+ reduceMotionProgress,
67
+ );
68
+
69
+ expect(result.__hasOnlyMotionAnimation).toBe(false);
70
+ });
71
+ });
@@ -0,0 +1,55 @@
1
+ import type { Animated, ViewStyle } from 'react-native';
2
+ import { MOTION_ANIMATIONS } from './animationConstants';
3
+
4
+ export const interpolateAnimationStates = (
5
+ from: Record<string, any>,
6
+ to: Record<string, any>,
7
+ isReduceMotionEnabled: boolean,
8
+ progressValue: Animated.Value,
9
+ reduceMotionProgressValue: Animated.Value,
10
+ ): Record<string, any> => {
11
+ return Object.keys(from).reduce(
12
+ (outputAnimation, key) => {
13
+ const isMotionAnimation = MOTION_ANIMATIONS.includes(
14
+ key as keyof ViewStyle,
15
+ );
16
+ const progressKey =
17
+ isReduceMotionEnabled && isMotionAnimation
18
+ ? reduceMotionProgressValue
19
+ : progressValue;
20
+
21
+ if (Array.isArray(from[key])) {
22
+ outputAnimation[key] = from[key].map(
23
+ (animationObject: Record<string, any>, index: number) => {
24
+ const { __hasOnlyMotionAnimation, ...style } =
25
+ interpolateAnimationStates(
26
+ animationObject,
27
+ to[key][index],
28
+ isReduceMotionEnabled,
29
+ progressValue,
30
+ reduceMotionProgressValue,
31
+ );
32
+
33
+ outputAnimation.__hasOnlyMotionAnimation =
34
+ outputAnimation.__hasOnlyMotionAnimation &&
35
+ __hasOnlyMotionAnimation;
36
+
37
+ return style;
38
+ },
39
+ );
40
+ } else {
41
+ outputAnimation[key] = progressKey.interpolate({
42
+ inputRange: [0, 1],
43
+ // @ts-ignore
44
+ outputRange: [from[key], to[key]],
45
+ });
46
+
47
+ outputAnimation.__hasOnlyMotionAnimation =
48
+ outputAnimation.__hasOnlyMotionAnimation && isMotionAnimation;
49
+ }
50
+
51
+ return outputAnimation;
52
+ },
53
+ { __hasOnlyMotionAnimation: true } as Record<string, any>,
54
+ );
55
+ };
@@ -0,0 +1,44 @@
1
+ import { renderHook } from '@testing-library/react-native';
2
+
3
+ beforeEach(() => {
4
+ jest.clearAllMocks();
5
+ jest.resetModules();
6
+ });
7
+
8
+ describe('useAMAContextSafe', () => {
9
+ it('returns isReduceMotionEnabled from core when core is present', () => {
10
+ jest.doMock('@react-native-ama/core', () => ({
11
+ useAMAContext: () => ({ isReduceMotionEnabled: true }),
12
+ }));
13
+
14
+ const { useAMAContextSafe: safe } =
15
+ require('./useAMAContextSafe') as typeof import('./useAMAContextSafe');
16
+ const { result } = renderHook(() => safe());
17
+
18
+ expect(result.current.isReduceMotionEnabled).toBe(true);
19
+ });
20
+
21
+ it('returns isReduceMotionEnabled: false when core is absent', () => {
22
+ jest.doMock('@react-native-ama/core', () => null);
23
+
24
+ const { useAMAContextSafe: safe } =
25
+ require('./useAMAContextSafe') as typeof import('./useAMAContextSafe');
26
+ const { result } = renderHook(() => safe());
27
+
28
+ expect(result.current.isReduceMotionEnabled).toBe(false);
29
+ });
30
+
31
+ it('returns isReduceMotionEnabled: false when useAMAContext throws', () => {
32
+ jest.doMock('@react-native-ama/core', () => ({
33
+ useAMAContext: () => {
34
+ throw new Error('Please wrap your app with <AMAProvider />');
35
+ },
36
+ }));
37
+
38
+ const { useAMAContextSafe: safe } =
39
+ require('./useAMAContextSafe') as typeof import('./useAMAContextSafe');
40
+ const { result } = renderHook(() => safe());
41
+
42
+ expect(result.current.isReduceMotionEnabled).toBe(false);
43
+ });
44
+ });
@@ -0,0 +1,13 @@
1
+ const AMACore = require('@react-native-ama/core');
2
+
3
+ const SAFE_DEFAULTS = {
4
+ isReduceMotionEnabled: false,
5
+ };
6
+
7
+ export const useAMAContextSafe = (): { isReduceMotionEnabled: boolean } => {
8
+ try {
9
+ return AMACore?.useAMAContext?.() ?? SAFE_DEFAULTS;
10
+ } catch {
11
+ return SAFE_DEFAULTS;
12
+ }
13
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MOTION_ANIMATIONS,
3
3
  type MotionAnimationKey,
4
- } from '@react-native-ama/internal';
4
+ } from '../internals/animationConstants';
5
5
 
6
6
  export const isMotionAnimation = (key: MotionAnimationKey) => {
7
7
  return MOTION_ANIMATIONS.includes(key);