Skip to content

Commit 5e28c1d

Browse files
docs: add transformOrigin to README and create AGENTS.md
Document the transformOrigin prop in the README guide and API reference sections. Add AGENTS.md with project architecture, development workflow, and key gotchas for AI agents. Symlink CLAUDE.md to AGENTS.md.
1 parent aad0b8e commit 5e28c1d

3 files changed

Lines changed: 139 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,34 @@ Animations are interruptible by default. If you change `animate` values while an
181181
/>
182182
```
183183

184+
### Transform Origin
185+
186+
By default, scale and rotation animate from the view's center. Use `transformOrigin` to change the pivot point with 0–1 fractions.
187+
188+
```tsx
189+
// Rotate from top-left corner
190+
<EaseView
191+
animate={{ rotate: isOpen ? 45 : 0 }}
192+
transformOrigin={{ x: 0, y: 0 }}
193+
transition={{ type: 'spring', damping: 12, stiffness: 200, mass: 1 }}
194+
style={styles.card}
195+
/>
196+
197+
// Scale from bottom-right
198+
<EaseView
199+
animate={{ scale: active ? 1.2 : 1 }}
200+
transformOrigin={{ x: 1, y: 1 }}
201+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
202+
style={styles.card}
203+
/>
204+
```
205+
206+
| Value | Position |
207+
|---|---|
208+
| `{ x: 0, y: 0 }` | Top-left |
209+
| `{ x: 0.5, y: 0.5 }` | Center (default) |
210+
| `{ x: 1, y: 1 }` | Bottom-right |
211+
184212
### Style Handling
185213

186214
Use `animate` for animated properties and `style` for everything else. If you accidentally put `opacity` or `transform` in `style`, they will be ignored and you'll get a dev warning.
@@ -210,7 +238,8 @@ A `View` that animates property changes using native platform APIs.
210238
| `animate` | `AnimateProps` | Target values for animated properties |
211239
| `initialAnimate` | `AnimateProps` | Starting values for enter animations (animates to `animate` on mount) |
212240
| `transition` | `Transition` | Animation configuration (timing or spring) |
213-
| `onTransitionEnd` | `(event) => void` | Called when an animation finishes with `{ finished: boolean }` |
241+
| `onTransitionEnd` | `(event) => void` | Called when all animations complete with `{ finished: boolean }` |
242+
| `transformOrigin` | `{ x?: number; y?: number }` | Pivot point for scale/rotation as 0–1 fractions. Default: `{ x: 0.5, y: 0.5 }` (center) |
214243
| `useHardwareLayer` | `boolean` | Android only — rasterize to GPU texture during animations. See [Hardware Layers](#hardware-layers-android). Default: `false` |
215244
| `style` | `ViewStyle` | Non-animated styles (layout, colors, borders, etc.) |
216245
| `children` | `ReactNode` | Child elements |

0 commit comments

Comments
 (0)