@onlynative/inertia 0.0.1-alpha.2 → 0.0.1-alpha.4
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/README.md +44 -3
- package/dist/index.d.mts +259 -3
- package/dist/index.d.ts +259 -3
- package/dist/index.js +1866 -161
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1864 -165
- package/dist/index.mjs.map +1 -1
- package/dist/motion/Image.d.mts +1 -1
- package/dist/motion/Image.d.ts +1 -1
- package/dist/motion/Image.js +1696 -146
- package/dist/motion/Image.js.map +1 -1
- package/dist/motion/Image.mjs +1698 -148
- package/dist/motion/Image.mjs.map +1 -1
- package/dist/motion/Pressable.d.mts +1 -1
- package/dist/motion/Pressable.d.ts +1 -1
- package/dist/motion/Pressable.js +1696 -146
- package/dist/motion/Pressable.js.map +1 -1
- package/dist/motion/Pressable.mjs +1698 -148
- package/dist/motion/Pressable.mjs.map +1 -1
- package/dist/motion/ScrollView.d.mts +1 -1
- package/dist/motion/ScrollView.d.ts +1 -1
- package/dist/motion/ScrollView.js +1696 -146
- package/dist/motion/ScrollView.js.map +1 -1
- package/dist/motion/ScrollView.mjs +1698 -148
- package/dist/motion/ScrollView.mjs.map +1 -1
- package/dist/motion/Text.d.mts +1 -1
- package/dist/motion/Text.d.ts +1 -1
- package/dist/motion/Text.js +1696 -146
- package/dist/motion/Text.js.map +1 -1
- package/dist/motion/Text.mjs +1698 -148
- package/dist/motion/Text.mjs.map +1 -1
- package/dist/motion/View.d.mts +1 -1
- package/dist/motion/View.d.ts +1 -1
- package/dist/motion/View.js +1696 -146
- package/dist/motion/View.js.map +1 -1
- package/dist/motion/View.mjs +1698 -148
- package/dist/motion/View.mjs.map +1 -1
- package/dist/{types-DeZZzE_e.d.mts → types-CjztO3RW.d.mts} +89 -20
- package/dist/{types-DeZZzE_e.d.ts → types-CjztO3RW.d.ts} +89 -20
- package/llms.txt +54 -6
- package/package.json +1 -1
- package/src/__type-tests__/animate.test-d.tsx +88 -0
- package/src/index.ts +16 -1
- package/src/layout/index.ts +1 -0
- package/src/layout/resolveLayout.ts +54 -0
- package/src/motion/createMotionComponent.tsx +292 -153
- package/src/motion/installCheck.ts +69 -0
- package/src/transitions/easing.ts +3 -1
- package/src/transitions/index.ts +3 -0
- package/src/transitions/keys.ts +32 -0
- package/src/transitions/resolve.ts +1 -24
- package/src/transitions/sig.ts +40 -0
- package/src/transitions/spring.ts +41 -0
- package/src/types.ts +96 -18
- package/src/values/index.ts +14 -0
- package/src/values/useAnimation.ts +69 -0
- package/src/values/useGesture.ts +144 -0
- package/src/values/useMotionValue.ts +33 -0
- package/src/values/useScroll.ts +72 -0
- package/src/values/useSpring.ts +93 -0
- package/src/values/useTransform.ts +132 -0
|
@@ -62,17 +62,57 @@ type RepeatConfig = number | 'infinite' | {
|
|
|
62
62
|
type PerPropertyTransition<S> = {
|
|
63
63
|
[K in keyof S]?: TransitionConfig;
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Per-gesture-layer transition map. Each `gesture` sub-state animates a
|
|
67
|
+
* progress value 0↔1 with its own transition; the worklet composites the
|
|
68
|
+
* layers in priority order (`hovered → focused → focusVisible → pressed`).
|
|
69
|
+
*
|
|
70
|
+
* Keys live on the same `transition` object as `PerPropertyTransition` because
|
|
71
|
+
* the only other place they could go (nested inside `gesture` itself) would
|
|
72
|
+
* collide with the primitive's inferred style keys.
|
|
73
|
+
*/
|
|
74
|
+
interface GestureLayerTransitions {
|
|
75
|
+
pressed?: TransitionConfig;
|
|
76
|
+
focused?: TransitionConfig;
|
|
77
|
+
focusVisible?: TransitionConfig;
|
|
78
|
+
hovered?: TransitionConfig;
|
|
79
|
+
}
|
|
80
|
+
type Transition<S> = TransitionConfig | (PerPropertyTransition<S> & GestureLayerTransitions);
|
|
81
|
+
/**
|
|
82
|
+
* Transform shorthands that Inertia exposes on `animate` but that don't
|
|
83
|
+
* appear on RN's typed ViewStyle as top-level keys. RN keeps `scale`,
|
|
84
|
+
* `rotate`, `rotateX`, and `rotateY` inside the `transform` array; only
|
|
85
|
+
* `scaleX`/`scaleY` and `translateX`/`translateY` are surfaced as
|
|
86
|
+
* (deprecated) top-level shortcuts. Inertia's runtime treats these as
|
|
87
|
+
* transform-group keys (see `TRANSFORM_KEYS` in `createMotionComponent`),
|
|
88
|
+
* so they're documented as first-class animatables in `CLAUDE.md` and must
|
|
89
|
+
* be reachable from `animate` without dropping into the `transform: [...]`
|
|
90
|
+
* array form. Rotation values are degrees as numbers — the runtime appends
|
|
91
|
+
* `'deg'` before handing the transform to Reanimated.
|
|
92
|
+
*/
|
|
93
|
+
type AnimatableTransformExtras = {
|
|
94
|
+
scale?: AnimatableValue<number>;
|
|
95
|
+
rotate?: AnimatableValue<number>;
|
|
96
|
+
rotateX?: AnimatableValue<number>;
|
|
97
|
+
rotateY?: AnimatableValue<number>;
|
|
98
|
+
};
|
|
66
99
|
/**
|
|
67
100
|
* The animation state shape inferred from the underlying component's style
|
|
68
101
|
* prop. We narrow to the value side of `style` so consumers see ViewStyle on
|
|
69
102
|
* `Motion.View`, TextStyle on `Motion.Text`, etc. — no shared union.
|
|
103
|
+
*
|
|
104
|
+
* Some components (notably `Pressable`) type `style` as a union of
|
|
105
|
+
* `StyleProp<T>` and a callback `(state) => StyleProp<T>`. If we infer `S`
|
|
106
|
+
* directly from `StyleProp<infer S>`, the callback branch widens `S` to
|
|
107
|
+
* `unknown`, which collapses the animate map to `| {}` and silently
|
|
108
|
+
* accepts any key. Excluding functions first keeps inference tight.
|
|
70
109
|
*/
|
|
110
|
+
type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
|
|
71
111
|
type AnimateStyle<C> = C extends {
|
|
72
|
-
style?:
|
|
73
|
-
} ? {
|
|
112
|
+
style?: infer Raw;
|
|
113
|
+
} ? _StyleValue<Raw> extends StyleProp<infer S> ? {
|
|
74
114
|
[K in keyof S]?: AnimatableValue<S[K]>;
|
|
75
|
-
} : never;
|
|
115
|
+
} & AnimatableTransformExtras : never : never;
|
|
76
116
|
interface AnimationCallbackInfo<S> {
|
|
77
117
|
/**
|
|
78
118
|
* The animatable key that just settled — typically a `keyof S` (e.g.
|
|
@@ -109,18 +149,26 @@ type VariantsMap<C> = Record<string, AnimateStyle<C>>;
|
|
|
109
149
|
* - `hovered` — web-only. Typed for cross-platform call sites; the runtime is
|
|
110
150
|
* a no-op on native.
|
|
111
151
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* ring on keyboard focus only.
|
|
152
|
+
* Sub-states layer additively. Each declared sub-state owns an independent
|
|
153
|
+
* progress value (0↔1) that animates in/out with its own transition; the
|
|
154
|
+
* worklet composites layers in priority order (lowest-to-highest):
|
|
155
|
+
* `hovered → focused → focusVisible → pressed`. Per-property the chain is
|
|
117
156
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
157
|
+
* v = base
|
|
158
|
+
* v = lerp(v, hovered.value, progressHovered) // if declared
|
|
159
|
+
* v = lerp(v, focused.value, progressFocused) // if declared
|
|
160
|
+
* v = lerp(v, focusVisible.value, progressFocusVisible) // if declared
|
|
161
|
+
* v = lerp(v, pressed.value, progressPressed) // if declared
|
|
162
|
+
*
|
|
163
|
+
* (Color-valued keys use `interpolateColor` instead of `lerp`.) When a single
|
|
164
|
+
* sub-state is active, this collapses to "the highest-priority declared layer
|
|
165
|
+
* wins". When multiple are mid-transition (e.g. release-while-still-hovered)
|
|
166
|
+
* each layer fades independently — a press layer fading out at 50ms while a
|
|
167
|
+
* hover layer holds at full opacity matches MD3 state-layer semantics.
|
|
168
|
+
*
|
|
169
|
+
* Configure per-layer fade timing via `transition.<stateName>` on the parent
|
|
170
|
+
* primitive (see `GestureLayerTransitions`); without it, layers default to
|
|
171
|
+
* the parent transition or the library default spring.
|
|
124
172
|
*/
|
|
125
173
|
interface GestureSubStates<C> {
|
|
126
174
|
pressed?: AnimateStyle<C>;
|
|
@@ -173,10 +221,11 @@ interface MotionProps<C> {
|
|
|
173
221
|
*/
|
|
174
222
|
controller?: VariantController;
|
|
175
223
|
/**
|
|
176
|
-
* Gesture-driven sub-states (`pressed`, `focused`, `
|
|
177
|
-
* no handlers are mounted on the underlying
|
|
178
|
-
*
|
|
179
|
-
*
|
|
224
|
+
* Gesture-driven sub-states (`pressed`, `focused`, `focusVisible`,
|
|
225
|
+
* `hovered`). When omitted, no handlers are mounted on the underlying
|
|
226
|
+
* component. Each declared sub-state animates as an independent layer
|
|
227
|
+
* fading in/out over the base `animate` target — see `GestureSubStates`
|
|
228
|
+
* for the composition model and per-layer transition wiring.
|
|
180
229
|
*/
|
|
181
230
|
gesture?: GestureSubStates<C>;
|
|
182
231
|
/**
|
|
@@ -184,6 +233,26 @@ interface MotionProps<C> {
|
|
|
184
233
|
* precedence over the top-level transition.
|
|
185
234
|
*/
|
|
186
235
|
transition?: Transition<AnimateStyle<C>>;
|
|
236
|
+
/**
|
|
237
|
+
* Auto-layout animation. When the component's position or size changes
|
|
238
|
+
* because of a parent layout change (a flex sibling growing, a list
|
|
239
|
+
* reordering, a column toggling its width), interpolate between the old
|
|
240
|
+
* and new layout instead of snapping.
|
|
241
|
+
*
|
|
242
|
+
* - `true` — animate with the library's default spring.
|
|
243
|
+
* - `TransitionConfig` — spring (react-spring vocab) or timing config; the
|
|
244
|
+
* resolver bridges to Reanimated's `LinearTransition` builder.
|
|
245
|
+
* - omitted / `false` — no layout animation (default).
|
|
246
|
+
*
|
|
247
|
+
* Only `'spring'` / `'timing'` / `'no-animation'` map to layout transitions
|
|
248
|
+
* — decay is downgraded to spring (no clear target). Reduced motion gates
|
|
249
|
+
* the prop the same way it gates `animate`.
|
|
250
|
+
*
|
|
251
|
+
* `layoutId` for shared element transitions across screens is deferred:
|
|
252
|
+
* Reanimated 4 dropped the underlying `sharedTransitionTag` API and a
|
|
253
|
+
* Inertia-side measure-based registry is the in-flight design.
|
|
254
|
+
*/
|
|
255
|
+
layout?: boolean | TransitionConfig;
|
|
187
256
|
/**
|
|
188
257
|
* Fired once per logical animation completion. See `AnimationCallbackInfo`
|
|
189
258
|
* for the payload shape — transform parents fire once, not per axis.
|
|
@@ -199,4 +268,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
|
|
|
199
268
|
style?: React.ComponentProps<C>['style'];
|
|
200
269
|
}>;
|
|
201
270
|
|
|
202
|
-
export type { AnimatableValue as A, DecayTransition as D,
|
|
271
|
+
export type { AnimatableValue as A, DecayTransition as D, GestureLayerTransitions as G, MotionComponent as M, NoAnimationTransition as N, PerPropertyTransition as P, RepeatConfig as R, SpringTransition as S, TransitionConfig as T, VariantController as V, AnimateStyle as a, AnimationCallbackInfo as b, GestureSubStates as c, MotionProps as d, SequenceStep as e, TimingTransition as f, Transition as g, VariantsMap as h };
|
|
@@ -62,17 +62,57 @@ type RepeatConfig = number | 'infinite' | {
|
|
|
62
62
|
type PerPropertyTransition<S> = {
|
|
63
63
|
[K in keyof S]?: TransitionConfig;
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Per-gesture-layer transition map. Each `gesture` sub-state animates a
|
|
67
|
+
* progress value 0↔1 with its own transition; the worklet composites the
|
|
68
|
+
* layers in priority order (`hovered → focused → focusVisible → pressed`).
|
|
69
|
+
*
|
|
70
|
+
* Keys live on the same `transition` object as `PerPropertyTransition` because
|
|
71
|
+
* the only other place they could go (nested inside `gesture` itself) would
|
|
72
|
+
* collide with the primitive's inferred style keys.
|
|
73
|
+
*/
|
|
74
|
+
interface GestureLayerTransitions {
|
|
75
|
+
pressed?: TransitionConfig;
|
|
76
|
+
focused?: TransitionConfig;
|
|
77
|
+
focusVisible?: TransitionConfig;
|
|
78
|
+
hovered?: TransitionConfig;
|
|
79
|
+
}
|
|
80
|
+
type Transition<S> = TransitionConfig | (PerPropertyTransition<S> & GestureLayerTransitions);
|
|
81
|
+
/**
|
|
82
|
+
* Transform shorthands that Inertia exposes on `animate` but that don't
|
|
83
|
+
* appear on RN's typed ViewStyle as top-level keys. RN keeps `scale`,
|
|
84
|
+
* `rotate`, `rotateX`, and `rotateY` inside the `transform` array; only
|
|
85
|
+
* `scaleX`/`scaleY` and `translateX`/`translateY` are surfaced as
|
|
86
|
+
* (deprecated) top-level shortcuts. Inertia's runtime treats these as
|
|
87
|
+
* transform-group keys (see `TRANSFORM_KEYS` in `createMotionComponent`),
|
|
88
|
+
* so they're documented as first-class animatables in `CLAUDE.md` and must
|
|
89
|
+
* be reachable from `animate` without dropping into the `transform: [...]`
|
|
90
|
+
* array form. Rotation values are degrees as numbers — the runtime appends
|
|
91
|
+
* `'deg'` before handing the transform to Reanimated.
|
|
92
|
+
*/
|
|
93
|
+
type AnimatableTransformExtras = {
|
|
94
|
+
scale?: AnimatableValue<number>;
|
|
95
|
+
rotate?: AnimatableValue<number>;
|
|
96
|
+
rotateX?: AnimatableValue<number>;
|
|
97
|
+
rotateY?: AnimatableValue<number>;
|
|
98
|
+
};
|
|
66
99
|
/**
|
|
67
100
|
* The animation state shape inferred from the underlying component's style
|
|
68
101
|
* prop. We narrow to the value side of `style` so consumers see ViewStyle on
|
|
69
102
|
* `Motion.View`, TextStyle on `Motion.Text`, etc. — no shared union.
|
|
103
|
+
*
|
|
104
|
+
* Some components (notably `Pressable`) type `style` as a union of
|
|
105
|
+
* `StyleProp<T>` and a callback `(state) => StyleProp<T>`. If we infer `S`
|
|
106
|
+
* directly from `StyleProp<infer S>`, the callback branch widens `S` to
|
|
107
|
+
* `unknown`, which collapses the animate map to `| {}` and silently
|
|
108
|
+
* accepts any key. Excluding functions first keeps inference tight.
|
|
70
109
|
*/
|
|
110
|
+
type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
|
|
71
111
|
type AnimateStyle<C> = C extends {
|
|
72
|
-
style?:
|
|
73
|
-
} ? {
|
|
112
|
+
style?: infer Raw;
|
|
113
|
+
} ? _StyleValue<Raw> extends StyleProp<infer S> ? {
|
|
74
114
|
[K in keyof S]?: AnimatableValue<S[K]>;
|
|
75
|
-
} : never;
|
|
115
|
+
} & AnimatableTransformExtras : never : never;
|
|
76
116
|
interface AnimationCallbackInfo<S> {
|
|
77
117
|
/**
|
|
78
118
|
* The animatable key that just settled — typically a `keyof S` (e.g.
|
|
@@ -109,18 +149,26 @@ type VariantsMap<C> = Record<string, AnimateStyle<C>>;
|
|
|
109
149
|
* - `hovered` — web-only. Typed for cross-platform call sites; the runtime is
|
|
110
150
|
* a no-op on native.
|
|
111
151
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* ring on keyboard focus only.
|
|
152
|
+
* Sub-states layer additively. Each declared sub-state owns an independent
|
|
153
|
+
* progress value (0↔1) that animates in/out with its own transition; the
|
|
154
|
+
* worklet composites layers in priority order (lowest-to-highest):
|
|
155
|
+
* `hovered → focused → focusVisible → pressed`. Per-property the chain is
|
|
117
156
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
157
|
+
* v = base
|
|
158
|
+
* v = lerp(v, hovered.value, progressHovered) // if declared
|
|
159
|
+
* v = lerp(v, focused.value, progressFocused) // if declared
|
|
160
|
+
* v = lerp(v, focusVisible.value, progressFocusVisible) // if declared
|
|
161
|
+
* v = lerp(v, pressed.value, progressPressed) // if declared
|
|
162
|
+
*
|
|
163
|
+
* (Color-valued keys use `interpolateColor` instead of `lerp`.) When a single
|
|
164
|
+
* sub-state is active, this collapses to "the highest-priority declared layer
|
|
165
|
+
* wins". When multiple are mid-transition (e.g. release-while-still-hovered)
|
|
166
|
+
* each layer fades independently — a press layer fading out at 50ms while a
|
|
167
|
+
* hover layer holds at full opacity matches MD3 state-layer semantics.
|
|
168
|
+
*
|
|
169
|
+
* Configure per-layer fade timing via `transition.<stateName>` on the parent
|
|
170
|
+
* primitive (see `GestureLayerTransitions`); without it, layers default to
|
|
171
|
+
* the parent transition or the library default spring.
|
|
124
172
|
*/
|
|
125
173
|
interface GestureSubStates<C> {
|
|
126
174
|
pressed?: AnimateStyle<C>;
|
|
@@ -173,10 +221,11 @@ interface MotionProps<C> {
|
|
|
173
221
|
*/
|
|
174
222
|
controller?: VariantController;
|
|
175
223
|
/**
|
|
176
|
-
* Gesture-driven sub-states (`pressed`, `focused`, `
|
|
177
|
-
* no handlers are mounted on the underlying
|
|
178
|
-
*
|
|
179
|
-
*
|
|
224
|
+
* Gesture-driven sub-states (`pressed`, `focused`, `focusVisible`,
|
|
225
|
+
* `hovered`). When omitted, no handlers are mounted on the underlying
|
|
226
|
+
* component. Each declared sub-state animates as an independent layer
|
|
227
|
+
* fading in/out over the base `animate` target — see `GestureSubStates`
|
|
228
|
+
* for the composition model and per-layer transition wiring.
|
|
180
229
|
*/
|
|
181
230
|
gesture?: GestureSubStates<C>;
|
|
182
231
|
/**
|
|
@@ -184,6 +233,26 @@ interface MotionProps<C> {
|
|
|
184
233
|
* precedence over the top-level transition.
|
|
185
234
|
*/
|
|
186
235
|
transition?: Transition<AnimateStyle<C>>;
|
|
236
|
+
/**
|
|
237
|
+
* Auto-layout animation. When the component's position or size changes
|
|
238
|
+
* because of a parent layout change (a flex sibling growing, a list
|
|
239
|
+
* reordering, a column toggling its width), interpolate between the old
|
|
240
|
+
* and new layout instead of snapping.
|
|
241
|
+
*
|
|
242
|
+
* - `true` — animate with the library's default spring.
|
|
243
|
+
* - `TransitionConfig` — spring (react-spring vocab) or timing config; the
|
|
244
|
+
* resolver bridges to Reanimated's `LinearTransition` builder.
|
|
245
|
+
* - omitted / `false` — no layout animation (default).
|
|
246
|
+
*
|
|
247
|
+
* Only `'spring'` / `'timing'` / `'no-animation'` map to layout transitions
|
|
248
|
+
* — decay is downgraded to spring (no clear target). Reduced motion gates
|
|
249
|
+
* the prop the same way it gates `animate`.
|
|
250
|
+
*
|
|
251
|
+
* `layoutId` for shared element transitions across screens is deferred:
|
|
252
|
+
* Reanimated 4 dropped the underlying `sharedTransitionTag` API and a
|
|
253
|
+
* Inertia-side measure-based registry is the in-flight design.
|
|
254
|
+
*/
|
|
255
|
+
layout?: boolean | TransitionConfig;
|
|
187
256
|
/**
|
|
188
257
|
* Fired once per logical animation completion. See `AnimationCallbackInfo`
|
|
189
258
|
* for the payload shape — transform parents fire once, not per axis.
|
|
@@ -199,4 +268,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
|
|
|
199
268
|
style?: React.ComponentProps<C>['style'];
|
|
200
269
|
}>;
|
|
201
270
|
|
|
202
|
-
export type { AnimatableValue as A, DecayTransition as D,
|
|
271
|
+
export type { AnimatableValue as A, DecayTransition as D, GestureLayerTransitions as G, MotionComponent as M, NoAnimationTransition as N, PerPropertyTransition as P, RepeatConfig as R, SpringTransition as S, TransitionConfig as T, VariantController as V, AnimateStyle as a, AnimationCallbackInfo as b, GestureSubStates as c, MotionProps as d, SequenceStep as e, TimingTransition as f, Transition as g, VariantsMap as h };
|
package/llms.txt
CHANGED
|
@@ -13,7 +13,18 @@ Enable the Reanimated Babel plugin per its install guide.
|
|
|
13
13
|
## Imports
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
Motion,
|
|
18
|
+
Presence,
|
|
19
|
+
MotionConfig,
|
|
20
|
+
useGesture,
|
|
21
|
+
useVariants,
|
|
22
|
+
useMotionValue,
|
|
23
|
+
useAnimation,
|
|
24
|
+
useSpring,
|
|
25
|
+
useTransform,
|
|
26
|
+
useScroll,
|
|
27
|
+
} from '@onlynative/inertia'
|
|
17
28
|
// or for tree-shaking:
|
|
18
29
|
import { MotionView } from '@onlynative/inertia/view'
|
|
19
30
|
import { MotionText } from '@onlynative/inertia/text'
|
|
@@ -27,12 +38,20 @@ import { MotionScrollView } from '@onlynative/inertia/scroll-view'
|
|
|
27
38
|
- `Motion.View` / `Motion.Text` / `Motion.Image` / `Motion.Pressable` / `Motion.ScrollView` — animatable primitives. Per-primitive style inference (no shared `ViewStyle & TextStyle & ImageStyle` fallback).
|
|
28
39
|
- `<Presence>` — mount / unmount transitions; children need explicit `key`s. Exiting children get `pointerEvents: 'none'` automatically.
|
|
29
40
|
- `<MotionConfig reducedMotion="user" | "never" | "always">` — gates motion against the OS reduce-motion setting (default `"user"`).
|
|
41
|
+
- `useGesture(transition?)` — hook-form of the `gesture` prop. Returns 0↔1 progress shared values (`pressed`, `focused`, `focusVisible`, `hovered`) plus a `handlers` bag to spread on a `Pressable`. Use when one gesture needs to drive multiple animated siblings (focus rings, MD3 state-layer halos, multi-element compositions).
|
|
30
42
|
- `useVariants(variants, initial?)` — returns `{ current, transitionTo }` controller for the `controller` prop.
|
|
43
|
+
- `useMotionValue(initial)` — thin pass-through over `useSharedValue<T>`. Returns a `SharedValue<T>` directly so it interops with every Reanimated API without unwrapping.
|
|
44
|
+
- `useAnimation(target, transition?)` — drive a `SharedValue<number>` toward `target` with any `TransitionConfig` (spring / timing / decay / no-animation, plus `repeat`). The general-purpose value-layer hook for boolean-state progress, indeterminate loops, and anywhere the same value needs to feed multiple `useAnimatedStyle` blocks.
|
|
45
|
+
- `useSpring(target, config?)` — spring-only shorthand. Animates a `SharedValue<number>` toward `target` with react-spring vocab. `target` may be a plain number (effect-driven) or a `SharedValue<number>` (UI-thread reaction); the latter is the gesture-smoothing path.
|
|
46
|
+
- `useTransform(value, inputRange, outputRange, options?)` / `useTransform(transformer)` — interpolate a numeric shared value onto a number or color range, or derive any value from any number of shared values via a worklet. Non-worklet transformers are auto-wrapped.
|
|
47
|
+
- `useScroll()` — returns `{ scrollX, scrollY, onScroll }` for use with `Motion.ScrollView`. Scroll events fire on the UI thread.
|
|
31
48
|
- `createMotionComponent<C>(C)` — wrap any component with the same Motion prop surface, inferring style from `C`.
|
|
32
49
|
|
|
33
50
|
## Motion props
|
|
34
51
|
|
|
35
|
-
`initial` (mount-only, non-reactive after first render — pass `false` to skip), `animate`, `exit`, `variants`, `controller`, `gesture`, `transition`, `onAnimationEnd`.
|
|
52
|
+
`initial` (mount-only, non-reactive after first render — pass `false` to skip), `animate`, `exit`, `variants`, `controller`, `gesture`, `transition`, `layout`, `onAnimationEnd`.
|
|
53
|
+
|
|
54
|
+
`layout` accepts `true` (default spring) or a `TransitionConfig` (spring / timing) and animates position + size changes that come from outside the `animate` flow (siblings reordering, dimensions toggling). `'decay'` downgrades to spring; `'no-animation'` and reduced motion both skip the animation.
|
|
36
55
|
|
|
37
56
|
## Transitions
|
|
38
57
|
|
|
@@ -62,18 +81,47 @@ Spring uses react-spring vocabulary (`tension`, `friction`, `mass`). Reanimated'
|
|
|
62
81
|
## Gestures
|
|
63
82
|
|
|
64
83
|
```tsx
|
|
65
|
-
<Motion.View
|
|
84
|
+
<Motion.View
|
|
85
|
+
gesture={{
|
|
86
|
+
hovered: { backgroundColor: '#0001' },
|
|
87
|
+
focused: { backgroundColor: '#0002' },
|
|
88
|
+
pressed: { backgroundColor: '#0003' },
|
|
89
|
+
}}
|
|
90
|
+
transition={{ type: 'timing', duration: 120 }}
|
|
91
|
+
/>
|
|
66
92
|
```
|
|
67
93
|
|
|
68
|
-
Sub-
|
|
94
|
+
Sub-states layer **additively** in priority order: `hovered → focused → focusVisible → pressed`. Each declared sub-state owns an independent progress (0↔1) that fades in/out with its own transition; the worklet composites layers via `lerp` (numerics) / `interpolateColor` (colors). MD3 release-while-hovered behaves correctly — the press layer fades out without disturbing the hover layer.
|
|
95
|
+
|
|
96
|
+
Per-layer transitions via `transition.<stateName>`:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
transition={{
|
|
100
|
+
pressed: { type: 'timing', duration: 50 },
|
|
101
|
+
hovered: { type: 'timing', duration: 90 },
|
|
102
|
+
}}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`focusVisible` engages only on keyboard focus (W3C `:focus-visible`) so click-focus on web doesn't flash a ring; on native it tracks `focused`. `hovered` is a no-op on native.
|
|
106
|
+
|
|
107
|
+
## Caveats
|
|
108
|
+
|
|
109
|
+
- `Motion.Pressable` does **not** support function-form `style={({ pressed }) => ...}`. Reanimated's animated-component wrapper silently drops it. Drive press/focus/hover styling through `gesture` instead, or compute conditional styles once in render.
|
|
110
|
+
- `initial` is read once on mount and intentionally non-reactive. To reset after a state change, change the component `key`, remount via `<Presence>`, or drive the value through a controller. Pass `initial={false}` to skip the initial-mount animation.
|
|
69
111
|
|
|
70
112
|
## Animatable properties (alpha)
|
|
71
113
|
|
|
72
|
-
Numeric: `opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `width`, `height`, `borderRadius`.
|
|
114
|
+
Numeric: `opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `width`, `height`, `borderRadius`. Rotation values are degrees; the factory wraps them as `'${value}deg'`. `rotateX` / `rotateY` need a sibling `perspective` style entry to render in 3D.
|
|
73
115
|
|
|
74
116
|
Color: `backgroundColor`, `borderColor`, `color`, `tintColor` (Image only). Hex, `rgb()` / `rgba()`, `hsl()` / `hsla()`, and named colors all work; the target string is forwarded straight through `withSpring` / `withTiming` and Reanimated handles RGBA interpolation natively.
|
|
75
117
|
|
|
76
|
-
|
|
118
|
+
Auto-layout transitions ship via the `layout` prop (`true` / `TransitionConfig`) on every `Motion.*` primitive — see Layout. Shared element transitions (`layoutId`) are deferred: Reanimated 4 dropped the `sharedTransitionTag` API the previous design relied on.
|
|
119
|
+
|
|
120
|
+
## Optional adapter packages
|
|
121
|
+
|
|
122
|
+
- `@onlynative/inertia-gradients` — `MotionLinearGradient` over `expo-linear-gradient`. Animatable: `colors`, `start`, `end`, `locations`.
|
|
123
|
+
- `@onlynative/inertia-svg` — `MotionPath` over `react-native-svg`. Animatable: `d` (path morphing on structurally-compatible paths), `fill`, `stroke`, `strokeWidth`, opacities, `strokeDashoffset`. Source and target paths must share the same command sequence after implicit-repeat expansion; remount with `key` to switch shape.
|
|
124
|
+
- `@onlynative/inertia-gestures` — `useDrag`, `useSwipe`, `usePan` over `react-native-gesture-handler`.
|
|
77
125
|
|
|
78
126
|
## Docs
|
|
79
127
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-1 acceptance: per-primitive style inference must reject keys that
|
|
3
|
+
* don't exist on the underlying component's style prop, at compile time.
|
|
4
|
+
*
|
|
5
|
+
* These assertions run as part of `tsc --noEmit` (typecheck CI step). They
|
|
6
|
+
* are not Jest tests — Jest's Babel transform strips `@ts-expect-error`,
|
|
7
|
+
* so a runtime check can't enforce a compile-time gate. If any
|
|
8
|
+
* `@ts-expect-error` here becomes unused (i.e. the line below it stops
|
|
9
|
+
* being a type error), tsc fails with "Unused '@ts-expect-error'
|
|
10
|
+
* directive" and Phase-1 has regressed.
|
|
11
|
+
*
|
|
12
|
+
* The file is excluded from the tsup build via the explicit entry list in
|
|
13
|
+
* tsup.config.ts, and from Jest via the `__tests__`-only testMatch glob.
|
|
14
|
+
*
|
|
15
|
+
* We assert against `AnimateStyle<...>` directly (rather than mounting JSX)
|
|
16
|
+
* because Prettier reflows multi-line JSX, which would push the actual
|
|
17
|
+
* error onto a different line than the `@ts-expect-error` directive.
|
|
18
|
+
* Direct value-to-type assignment lines stay one line, regardless of
|
|
19
|
+
* Prettier print width.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ComponentProps } from 'react'
|
|
23
|
+
import type { Image, Pressable, ScrollView, Text, View } from 'react-native'
|
|
24
|
+
import type { AnimateStyle } from '../types'
|
|
25
|
+
|
|
26
|
+
type ViewAnimate = AnimateStyle<ComponentProps<typeof View>>
|
|
27
|
+
type TextAnimate = AnimateStyle<ComponentProps<typeof Text>>
|
|
28
|
+
type ImageAnimate = AnimateStyle<ComponentProps<typeof Image>>
|
|
29
|
+
type PressableAnimate = AnimateStyle<ComponentProps<typeof Pressable>>
|
|
30
|
+
type ScrollViewAnimate = AnimateStyle<ComponentProps<typeof ScrollView>>
|
|
31
|
+
|
|
32
|
+
// ─── Motion.View / ViewStyle ────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const _viewAccepts: ViewAnimate = { opacity: 1, translateX: 10, scale: 1.1 }
|
|
35
|
+
const _viewAcceptsRotate: ViewAnimate = { rotate: 45, rotateX: 30, rotateY: 60 }
|
|
36
|
+
// @ts-expect-error rotate is a numeric degree value; strings like '45deg' aren't accepted
|
|
37
|
+
const _viewRejectsRotateString: ViewAnimate = { rotate: '45deg' }
|
|
38
|
+
// @ts-expect-error tintColor is ImageStyle-only and must be rejected on View
|
|
39
|
+
const _viewRejectsTintColor: ViewAnimate = { tintColor: '#0a84ff' }
|
|
40
|
+
// @ts-expect-error fontSize is TextStyle-only and must be rejected on View
|
|
41
|
+
const _viewRejectsFontSize: ViewAnimate = { fontSize: 20 }
|
|
42
|
+
// @ts-expect-error completely unknown keys must be rejected
|
|
43
|
+
const _viewRejectsUnknown: ViewAnimate = { nonsenseKey: 1 }
|
|
44
|
+
|
|
45
|
+
// ─── Motion.Text / TextStyle ────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const _textAccepts: TextAnimate = { opacity: 1, color: '#000', fontSize: 16 }
|
|
48
|
+
// @ts-expect-error tintColor is ImageStyle-only and must be rejected on Text
|
|
49
|
+
const _textRejectsTintColor: TextAnimate = { tintColor: '#0a84ff' }
|
|
50
|
+
|
|
51
|
+
// ─── Motion.Image / ImageStyle ──────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const _imageAccepts: ImageAnimate = { tintColor: '#0a84ff', opacity: 0.5 }
|
|
54
|
+
// @ts-expect-error fontSize is TextStyle-only and must be rejected on Image
|
|
55
|
+
const _imageRejectsFontSize: ImageAnimate = { fontSize: 20 }
|
|
56
|
+
|
|
57
|
+
// ─── Motion.Pressable / Motion.ScrollView ───────────────────────────────────
|
|
58
|
+
//
|
|
59
|
+
// Both wrap View internally; their style surface is ViewStyle. Same rule as
|
|
60
|
+
// Motion.View — no tintColor. Pressable's `style` is a union with a
|
|
61
|
+
// `(state) => StyleProp<ViewStyle>` callback; the `_StyleValue` helper in
|
|
62
|
+
// `types.ts` strips the function variant so inference stays tight.
|
|
63
|
+
|
|
64
|
+
const _pressableAccepts: PressableAnimate = { opacity: 1, scale: 0.96 }
|
|
65
|
+
// @ts-expect-error tintColor is ImageStyle-only and must be rejected on Pressable
|
|
66
|
+
const _pressableRejectsTintColor: PressableAnimate = { tintColor: '#0a84ff' }
|
|
67
|
+
|
|
68
|
+
const _scrollViewAccepts: ScrollViewAnimate = { opacity: 1, translateY: 10 }
|
|
69
|
+
// @ts-expect-error tintColor is ImageStyle-only and must be rejected on ScrollView
|
|
70
|
+
const _scrollViewRejectsTintColor: ScrollViewAnimate = { tintColor: '#0a84ff' }
|
|
71
|
+
|
|
72
|
+
// Silence "declared but never read" — these exist purely as type assertions.
|
|
73
|
+
export type _PhaseOneTypeAssertions = [
|
|
74
|
+
typeof _viewAccepts,
|
|
75
|
+
typeof _viewAcceptsRotate,
|
|
76
|
+
typeof _viewRejectsRotateString,
|
|
77
|
+
typeof _viewRejectsTintColor,
|
|
78
|
+
typeof _viewRejectsFontSize,
|
|
79
|
+
typeof _viewRejectsUnknown,
|
|
80
|
+
typeof _textAccepts,
|
|
81
|
+
typeof _textRejectsTintColor,
|
|
82
|
+
typeof _imageAccepts,
|
|
83
|
+
typeof _imageRejectsFontSize,
|
|
84
|
+
typeof _pressableAccepts,
|
|
85
|
+
typeof _pressableRejectsTintColor,
|
|
86
|
+
typeof _scrollViewAccepts,
|
|
87
|
+
typeof _scrollViewRejectsTintColor,
|
|
88
|
+
]
|
package/src/index.ts
CHANGED
|
@@ -27,7 +27,22 @@ export {
|
|
|
27
27
|
resolveAnimatableValue,
|
|
28
28
|
ensureWorkletEasing,
|
|
29
29
|
} from './transitions'
|
|
30
|
-
export {
|
|
30
|
+
export {
|
|
31
|
+
useAnimation,
|
|
32
|
+
useGesture,
|
|
33
|
+
useMotionValue,
|
|
34
|
+
useScroll,
|
|
35
|
+
useSpring,
|
|
36
|
+
useTransform,
|
|
37
|
+
useVariants,
|
|
38
|
+
} from './values'
|
|
39
|
+
export type {
|
|
40
|
+
ExtrapolationMode,
|
|
41
|
+
UseGestureHandlers,
|
|
42
|
+
UseGestureResult,
|
|
43
|
+
UseScrollResult,
|
|
44
|
+
UseTransformOptions,
|
|
45
|
+
} from './values'
|
|
31
46
|
export type {
|
|
32
47
|
AnimatableValue,
|
|
33
48
|
AnimateStyle,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveLayoutTransition, type LayoutProp } from './resolveLayout'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { LinearTransition } from 'react-native-reanimated'
|
|
2
|
+
import { ensureWorkletEasing } from '../transitions/easing'
|
|
3
|
+
import { DEFAULT_SPRING, springToReanimated } from '../transitions/spring'
|
|
4
|
+
import { type TransitionConfig } from '../types'
|
|
5
|
+
|
|
6
|
+
export type LayoutProp = boolean | TransitionConfig | undefined
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the public `layout` prop into a Reanimated `LinearTransition` builder.
|
|
10
|
+
*
|
|
11
|
+
* - `undefined` / `false` / `{ type: 'no-animation' }` → `undefined` (no layout
|
|
12
|
+
* animation; layout changes snap).
|
|
13
|
+
* - `true` → default spring with the library's tuned tension / friction / mass.
|
|
14
|
+
* - `{ type: 'spring', ... }` → spring with react-spring vocabulary, bridged
|
|
15
|
+
* into `springify().damping().stiffness().mass()` via `springToReanimated`.
|
|
16
|
+
* - `{ type: 'timing', ... }` → `.duration().easing()`. User easing fns are
|
|
17
|
+
* auto-wrapped as worklets (Reanimated 3.9+ validates this).
|
|
18
|
+
* - `{ type: 'decay', ... }` → silently downgrades to spring; decay doesn't
|
|
19
|
+
* have a clear target for a layout transition.
|
|
20
|
+
*
|
|
21
|
+
* When `reducedMotion` is true the caller should pass `undefined` so the
|
|
22
|
+
* underlying component renders without a layout animation at all. We choose
|
|
23
|
+
* this over `LinearTransition.duration(0)` because Reanimated still runs the
|
|
24
|
+
* commit-tracking machinery in that case; the snap path is genuinely cheaper.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveLayoutTransition(
|
|
27
|
+
layout: LayoutProp,
|
|
28
|
+
): LinearTransition | undefined {
|
|
29
|
+
if (!layout) return undefined
|
|
30
|
+
|
|
31
|
+
const cfg: TransitionConfig = layout === true ? { type: 'spring' } : layout
|
|
32
|
+
|
|
33
|
+
if (cfg.type === 'no-animation') return undefined
|
|
34
|
+
|
|
35
|
+
if (cfg.type === 'timing') {
|
|
36
|
+
let builder = LinearTransition.duration(cfg.duration ?? 300)
|
|
37
|
+
const easing = ensureWorkletEasing(cfg.easing)
|
|
38
|
+
if (easing) builder = builder.easing(easing)
|
|
39
|
+
if (cfg.delay) builder = builder.delay(cfg.delay)
|
|
40
|
+
return builder
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spring = cfg.type === 'decay' ? ({ type: 'spring' } as const) : cfg
|
|
44
|
+
const { stiffness, damping, mass } = springToReanimated({
|
|
45
|
+
...DEFAULT_SPRING,
|
|
46
|
+
...spring,
|
|
47
|
+
})
|
|
48
|
+
let builder = LinearTransition.springify()
|
|
49
|
+
.stiffness(stiffness)
|
|
50
|
+
.damping(damping)
|
|
51
|
+
.mass(mass)
|
|
52
|
+
if ('delay' in spring && spring.delay) builder = builder.delay(spring.delay)
|
|
53
|
+
return builder
|
|
54
|
+
}
|