|
| 1 | +# Agent Instructions for react-native-ease |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +`react-native-ease` is a React Native library that provides declarative, native-powered animations via a single `EaseView` component. It uses Core Animation (iOS) and ObjectAnimator/SpringAnimation (Android) — no JS animation loop, no worklets, no C++ runtime. |
| 6 | + |
| 7 | +**Fabric (new architecture) only.** Does not support the old architecture. |
| 8 | + |
| 9 | +## Project Structure |
| 10 | + |
| 11 | +``` |
| 12 | +src/ # TypeScript source (library) |
| 13 | + EaseView.tsx # React component — flattens props to native |
| 14 | + EaseViewNativeComponent.ts # Codegen spec — defines native props/events |
| 15 | + types.ts # Public TypeScript types |
| 16 | + index.tsx # Public exports |
| 17 | + __tests__/ # Jest tests |
| 18 | +
|
| 19 | +ios/ |
| 20 | + EaseView.h # Native view header (ObjC++) |
| 21 | + EaseView.mm # Native view implementation (Core Animation) |
| 22 | +
|
| 23 | +android/src/main/java/com/ease/ |
| 24 | + EaseView.kt # Native view (ObjectAnimator/SpringAnimation) |
| 25 | + EaseViewManager.kt # ViewManager with @ReactProp setters |
| 26 | + EasePackage.kt # Package registration |
| 27 | +
|
| 28 | +example/ # Demo app (separate workspace) |
| 29 | + src/App.tsx # Main demo screen with animation examples |
| 30 | + src/ComparisonScreen.tsx # Comparison with Reanimated |
| 31 | + src/components/ # Shared demo components (Section, Button, TabBar) |
| 32 | +``` |
| 33 | + |
| 34 | +## Architecture |
| 35 | + |
| 36 | +The JS component (`EaseView.tsx`) takes structured props (`animate`, `transition`, etc.) and flattens them into individual native props defined in the codegen spec (`EaseViewNativeComponent.ts`). When those flat props change on the native side, the native view diffs previous vs new values and creates platform-native animations. |
| 37 | + |
| 38 | +**Prop flattening example:** |
| 39 | +``` |
| 40 | +animate={{ opacity: 0.5, scale: 1.2 }} → animateOpacity=0.5, animateScale=1.2 |
| 41 | +transition={{ type: 'spring', damping: 10 }} → transitionType="spring", transitionDamping=10 |
| 42 | +``` |
| 43 | + |
| 44 | +**Key design pattern:** All animation logic lives on the native side. The JS layer is purely a prop resolver — no animation state, no timers, no refs. |
| 45 | + |
| 46 | +## Adding a New Animatable Property |
| 47 | + |
| 48 | +1. Add to `AnimateProps` in `src/types.ts` |
| 49 | +2. Add `animate<Prop>` and `initialAnimate<Prop>` codegen props in `src/EaseViewNativeComponent.ts` |
| 50 | +3. Add default to `IDENTITY` in `src/EaseView.tsx` and pass the flat props to `NativeEaseView` |
| 51 | +4. **iOS:** Handle the new property in `updateProps:` — diff old/new, read presentation value, create animation |
| 52 | +5. **Android:** Add `pending<Prop>` field, `@ReactProp` setter in `EaseViewManager.kt`, and handle in `applyAnimateValues()` |
| 53 | +6. Add tests and update README |
| 54 | + |
| 55 | +## Development Commands |
| 56 | + |
| 57 | +```sh |
| 58 | +yarn # Install dependencies |
| 59 | +yarn test # Jest tests (JS layer) |
| 60 | +yarn lint # ESLint + TypeScript check |
| 61 | +yarn format:check # Prettier + clang-format check |
| 62 | +yarn format:write # Auto-fix formatting |
| 63 | +yarn prepare # Build JS module (bob build) |
| 64 | +yarn example start # Start Metro for example app |
| 65 | +yarn example ios # Build and run example on iOS |
| 66 | +yarn example android # Build and run example on Android |
| 67 | +``` |
| 68 | + |
| 69 | +## Pre-commit Checklist |
| 70 | + |
| 71 | +Before committing, always run: |
| 72 | + |
| 73 | +```sh |
| 74 | +yarn format:check && yarn lint && yarn test |
| 75 | +``` |
| 76 | + |
| 77 | +Lefthook pre-commit hooks enforce ESLint and TypeScript checks. Fix all failures before committing. |
| 78 | + |
| 79 | +**Formatting tools:** |
| 80 | +- **Prettier** for TypeScript/JavaScript files |
| 81 | +- **clang-format** for Objective-C++, Kotlin, and C++ files |
| 82 | + |
| 83 | +## Codegen |
| 84 | + |
| 85 | +React Native Codegen generates the native component interface from `src/EaseViewNativeComponent.ts`. The generated spec name is `EaseViewSpec`. |
| 86 | + |
| 87 | +After changing the codegen spec, you need to rebuild the example app for the native side to pick up changes. On iOS, `pod install` in `example/ios/` may also be needed. |
| 88 | + |
| 89 | +## Testing |
| 90 | + |
| 91 | +- Unit tests are in `src/__tests__/` and test JS prop resolution and native prop passing |
| 92 | +- Uses `@testing-library/react-native` with a mocked native component |
| 93 | +- **No native integration tests** — test native behavior by running the example app on a device/simulator |
| 94 | +- Example app has demos for all features; use it to verify changes visually |
| 95 | + |
| 96 | +## Commit Style |
| 97 | + |
| 98 | +Use conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, etc. |
| 99 | + |
| 100 | +## Key Gotchas |
| 101 | + |
| 102 | +- **`style` must not contain `opacity` or `transform`** — these are controlled by the `animate` prop. The component warns in dev mode if you do this. |
| 103 | +- **translateX/translateY are in DIPs (density-independent pixels) on the JS side.** Android `EaseViewManager` converts to pixels via `PixelUtil.toPixelFromDIP()`. iOS codegen handles this automatically. |
| 104 | +- **iOS uses `CALayer` key-path animations** (`transform.scale.x`, `transform.translation.x`, etc.), not the `transform` property directly. This means `anchorPoint` controls the pivot for scale/rotation. |
| 105 | +- **iOS `anchorPoint` changes shift visual position.** The `updateLayoutMetrics:oldLayoutMetrics:` override compensates for this — don't change it without understanding the position math. |
| 106 | +- **Spring damping ratio on Android is derived** from `damping`, `stiffness`, and `mass` using: `dampingRatio = damping / (2 * sqrt(stiffness * mass))`. iOS passes these values directly to `CASpringAnimation`. |
| 107 | +- **Animation batching:** Both platforms track animation batches with a generation ID. When new animations start, any pending old-batch callbacks are fired as interrupted (`finished: false`). |
| 108 | +- **Loop only works with timing animations**, not springs. Loop requires `initialAnimate` to define the start value. |
0 commit comments