@onlynative/inertia 0.0.1-alpha.3 → 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 +19 -5
- package/dist/index.d.mts +259 -3
- package/dist/index.d.ts +259 -3
- package/dist/index.js +1711 -118
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1709 -122
- 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 +1502 -64
- package/dist/motion/Image.js.map +1 -1
- package/dist/motion/Image.mjs +1504 -66
- 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 +1502 -64
- package/dist/motion/Pressable.js.map +1 -1
- package/dist/motion/Pressable.mjs +1504 -66
- 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 +1502 -64
- package/dist/motion/ScrollView.js.map +1 -1
- package/dist/motion/ScrollView.mjs +1504 -66
- 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 +1502 -64
- package/dist/motion/Text.js.map +1 -1
- package/dist/motion/Text.mjs +1504 -66
- 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 +1502 -64
- package/dist/motion/View.js.map +1 -1
- package/dist/motion/View.mjs +1504 -66
- package/dist/motion/View.mjs.map +1 -1
- package/dist/{types-DAhX3fC2.d.mts → types-CjztO3RW.d.mts} +49 -4
- package/dist/{types-DAhX3fC2.d.ts → types-CjztO3RW.d.ts} +49 -4
- package/llms.txt +29 -4
- 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 +38 -60
- 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 +52 -2
- 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
|
@@ -78,16 +78,41 @@ interface GestureLayerTransitions {
|
|
|
78
78
|
hovered?: TransitionConfig;
|
|
79
79
|
}
|
|
80
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
|
+
};
|
|
81
99
|
/**
|
|
82
100
|
* The animation state shape inferred from the underlying component's style
|
|
83
101
|
* prop. We narrow to the value side of `style` so consumers see ViewStyle on
|
|
84
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.
|
|
85
109
|
*/
|
|
110
|
+
type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
|
|
86
111
|
type AnimateStyle<C> = C extends {
|
|
87
|
-
style?:
|
|
88
|
-
} ? {
|
|
112
|
+
style?: infer Raw;
|
|
113
|
+
} ? _StyleValue<Raw> extends StyleProp<infer S> ? {
|
|
89
114
|
[K in keyof S]?: AnimatableValue<S[K]>;
|
|
90
|
-
} : never;
|
|
115
|
+
} & AnimatableTransformExtras : never : never;
|
|
91
116
|
interface AnimationCallbackInfo<S> {
|
|
92
117
|
/**
|
|
93
118
|
* The animatable key that just settled — typically a `keyof S` (e.g.
|
|
@@ -208,6 +233,26 @@ interface MotionProps<C> {
|
|
|
208
233
|
* precedence over the top-level transition.
|
|
209
234
|
*/
|
|
210
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;
|
|
211
256
|
/**
|
|
212
257
|
* Fired once per logical animation completion. See `AnimationCallbackInfo`
|
|
213
258
|
* for the payload shape — transform parents fire once, not per axis.
|
|
@@ -223,4 +268,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
|
|
|
223
268
|
style?: React.ComponentProps<C>['style'];
|
|
224
269
|
}>;
|
|
225
270
|
|
|
226
|
-
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 };
|
|
@@ -78,16 +78,41 @@ interface GestureLayerTransitions {
|
|
|
78
78
|
hovered?: TransitionConfig;
|
|
79
79
|
}
|
|
80
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
|
+
};
|
|
81
99
|
/**
|
|
82
100
|
* The animation state shape inferred from the underlying component's style
|
|
83
101
|
* prop. We narrow to the value side of `style` so consumers see ViewStyle on
|
|
84
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.
|
|
85
109
|
*/
|
|
110
|
+
type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
|
|
86
111
|
type AnimateStyle<C> = C extends {
|
|
87
|
-
style?:
|
|
88
|
-
} ? {
|
|
112
|
+
style?: infer Raw;
|
|
113
|
+
} ? _StyleValue<Raw> extends StyleProp<infer S> ? {
|
|
89
114
|
[K in keyof S]?: AnimatableValue<S[K]>;
|
|
90
|
-
} : never;
|
|
115
|
+
} & AnimatableTransformExtras : never : never;
|
|
91
116
|
interface AnimationCallbackInfo<S> {
|
|
92
117
|
/**
|
|
93
118
|
* The animatable key that just settled — typically a `keyof S` (e.g.
|
|
@@ -208,6 +233,26 @@ interface MotionProps<C> {
|
|
|
208
233
|
* precedence over the top-level transition.
|
|
209
234
|
*/
|
|
210
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;
|
|
211
256
|
/**
|
|
212
257
|
* Fired once per logical animation completion. See `AnimationCallbackInfo`
|
|
213
258
|
* for the payload shape — transform parents fire once, not per axis.
|
|
@@ -223,4 +268,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
|
|
|
223
268
|
style?: React.ComponentProps<C>['style'];
|
|
224
269
|
}>;
|
|
225
270
|
|
|
226
|
-
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
|
|
|
@@ -92,11 +111,17 @@ transition={{
|
|
|
92
111
|
|
|
93
112
|
## Animatable properties (alpha)
|
|
94
113
|
|
|
95
|
-
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.
|
|
96
115
|
|
|
97
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.
|
|
98
117
|
|
|
99
|
-
|
|
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`.
|
|
100
125
|
|
|
101
126
|
## Docs
|
|
102
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
|
+
}
|
|
@@ -15,8 +15,14 @@ import Animated, {
|
|
|
15
15
|
} from 'react-native-reanimated'
|
|
16
16
|
import { useShouldReduceMotion } from '../config'
|
|
17
17
|
import { isFocusVisible } from '../gestures'
|
|
18
|
+
import { resolveLayoutTransition, type LayoutProp } from '../layout'
|
|
18
19
|
import { usePresence } from '../presence'
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
isTopLevelTransition,
|
|
22
|
+
resolveAnimatableValue,
|
|
23
|
+
resolveTransition,
|
|
24
|
+
stableSig,
|
|
25
|
+
} from '../transitions'
|
|
20
26
|
import { ensureReanimatedInstalled } from './installCheck'
|
|
21
27
|
import {
|
|
22
28
|
type AnimatableValue,
|
|
@@ -45,8 +51,17 @@ const TRANSFORM_KEYS = [
|
|
|
45
51
|
'scaleX',
|
|
46
52
|
'scaleY',
|
|
47
53
|
'rotate',
|
|
54
|
+
'rotateX',
|
|
55
|
+
'rotateY',
|
|
48
56
|
] as const
|
|
49
57
|
|
|
58
|
+
// Rotation keys land in the transform array as `{ rotate: '45deg' }` / `{
|
|
59
|
+
// rotateX: '45deg' }` etc. — Reanimated needs the unit-suffixed string form.
|
|
60
|
+
// We hold the underlying shared value as a plain number (degrees) and wrap
|
|
61
|
+
// in the worklet so the resolver pipeline stays uniform with the other
|
|
62
|
+
// numeric transform keys.
|
|
63
|
+
const ROTATION_KEYS = new Set<string>(['rotate', 'rotateX', 'rotateY'])
|
|
64
|
+
|
|
50
65
|
const NUMERIC_TOP_LEVEL_KEYS = [
|
|
51
66
|
'opacity',
|
|
52
67
|
'width',
|
|
@@ -110,6 +125,8 @@ const DEFAULT_RESTING: Record<AnimatableKey, number | string> = {
|
|
|
110
125
|
scaleX: 1,
|
|
111
126
|
scaleY: 1,
|
|
112
127
|
rotate: 0,
|
|
128
|
+
rotateX: 0,
|
|
129
|
+
rotateY: 0,
|
|
113
130
|
opacity: 1,
|
|
114
131
|
width: 0,
|
|
115
132
|
height: 0,
|
|
@@ -124,29 +141,6 @@ const DEFAULT_RESTING: Record<AnimatableKey, number | string> = {
|
|
|
124
141
|
tintColor: 'transparent',
|
|
125
142
|
}
|
|
126
143
|
|
|
127
|
-
const TRANSITION_KEYS = new Set([
|
|
128
|
-
'type',
|
|
129
|
-
'tension',
|
|
130
|
-
'friction',
|
|
131
|
-
'mass',
|
|
132
|
-
'velocity',
|
|
133
|
-
'restSpeedThreshold',
|
|
134
|
-
'restDisplacementThreshold',
|
|
135
|
-
'duration',
|
|
136
|
-
'easing',
|
|
137
|
-
'delay',
|
|
138
|
-
'repeat',
|
|
139
|
-
'deceleration',
|
|
140
|
-
'clamp',
|
|
141
|
-
])
|
|
142
|
-
|
|
143
|
-
function isTopLevelTransition(t: unknown): t is TransitionConfig {
|
|
144
|
-
if (t === null || typeof t !== 'object') return false
|
|
145
|
-
const keys = Object.keys(t as object)
|
|
146
|
-
if (keys.length === 0) return false
|
|
147
|
-
return keys.every((k) => TRANSITION_KEYS.has(k))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
144
|
function transitionFor<S>(
|
|
151
145
|
prop: keyof S,
|
|
152
146
|
transition: Transition<S> | undefined,
|
|
@@ -207,10 +201,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
207
201
|
variants,
|
|
208
202
|
controller,
|
|
209
203
|
gesture,
|
|
204
|
+
layout,
|
|
210
205
|
onAnimationEnd,
|
|
211
206
|
style,
|
|
212
207
|
...rest
|
|
213
|
-
} = props as Props & { style?: unknown }
|
|
208
|
+
} = props as Props & { style?: unknown; layout?: LayoutProp }
|
|
214
209
|
|
|
215
210
|
// <Presence> contract: when an ancestor flips `isPresent` to false the
|
|
216
211
|
// child stays rendered until `safeToRemove` is called, giving the exit
|
|
@@ -553,7 +548,7 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
553
548
|
|
|
554
549
|
if (TRANSFORM_KEY_SET.has(key)) {
|
|
555
550
|
transform.push(
|
|
556
|
-
key
|
|
551
|
+
ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v },
|
|
557
552
|
)
|
|
558
553
|
} else {
|
|
559
554
|
out[key] = v
|
|
@@ -585,11 +580,24 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
585
580
|
setHovered,
|
|
586
581
|
)
|
|
587
582
|
|
|
583
|
+
// Resolve the `layout` prop into a Reanimated `LinearTransition` builder.
|
|
584
|
+
// Memoized on the value's stable signature so a fresh `layout={true}` or
|
|
585
|
+
// `layout={{ ... }}` literal each render doesn't rebuild the builder. When
|
|
586
|
+
// reduced motion is active we pass `undefined` — see `resolveLayout` for
|
|
587
|
+
// why we don't pass a duration-0 builder instead.
|
|
588
|
+
const layoutSig = stableSig(layout)
|
|
589
|
+
const layoutTransition = useMemo(
|
|
590
|
+
() => (shouldReduceMotion ? undefined : resolveLayoutTransition(layout)),
|
|
591
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
592
|
+
[layoutSig, shouldReduceMotion],
|
|
593
|
+
)
|
|
594
|
+
|
|
588
595
|
return (
|
|
589
596
|
<AnimatedComponent
|
|
590
597
|
ref={ref as never}
|
|
591
598
|
{...(rest as object)}
|
|
592
599
|
{...gestureHandlers}
|
|
600
|
+
layout={layoutTransition}
|
|
593
601
|
style={mergedStyle}
|
|
594
602
|
/>
|
|
595
603
|
)
|
|
@@ -629,6 +637,8 @@ function useAnimatableSharedValues(
|
|
|
629
637
|
const scaleX = useSharedValue<number | string>(init('scaleX'))
|
|
630
638
|
const scaleY = useSharedValue<number | string>(init('scaleY'))
|
|
631
639
|
const rotate = useSharedValue<number | string>(init('rotate'))
|
|
640
|
+
const rotateX = useSharedValue<number | string>(init('rotateX'))
|
|
641
|
+
const rotateY = useSharedValue<number | string>(init('rotateY'))
|
|
632
642
|
const opacity = useSharedValue<number | string>(init('opacity'))
|
|
633
643
|
const width = useSharedValue<number | string>(init('width'))
|
|
634
644
|
const height = useSharedValue<number | string>(init('height'))
|
|
@@ -649,6 +659,8 @@ function useAnimatableSharedValues(
|
|
|
649
659
|
scaleX,
|
|
650
660
|
scaleY,
|
|
651
661
|
rotate,
|
|
662
|
+
rotateX,
|
|
663
|
+
rotateY,
|
|
652
664
|
opacity,
|
|
653
665
|
width,
|
|
654
666
|
height,
|
|
@@ -895,40 +907,6 @@ function restValue(
|
|
|
895
907
|
return undefined
|
|
896
908
|
}
|
|
897
909
|
|
|
898
|
-
function stableSig(value: unknown): string {
|
|
899
|
-
if (value === undefined) return ''
|
|
900
|
-
try {
|
|
901
|
-
return stableStringify(value)
|
|
902
|
-
} catch {
|
|
903
|
-
return String(value)
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* JSON.stringify with keys sorted at every level — gives a stable signature
|
|
909
|
-
* regardless of property declaration order. Functions serialize as `null` so a
|
|
910
|
-
* change in easing-fn reference is invisible here; that's fine for v0.1
|
|
911
|
-
* (easing swaps are rare and the worklet wrapper handles correctness).
|
|
912
|
-
*/
|
|
913
|
-
function stableStringify(v: unknown): string {
|
|
914
|
-
if (v === null || typeof v !== 'object') {
|
|
915
|
-
if (typeof v === 'function' || v === undefined) return 'null'
|
|
916
|
-
return JSON.stringify(v)
|
|
917
|
-
}
|
|
918
|
-
if (Array.isArray(v)) {
|
|
919
|
-
return '[' + v.map(stableStringify).join(',') + ']'
|
|
920
|
-
}
|
|
921
|
-
const obj = v as Record<string, unknown>
|
|
922
|
-
const keys = Object.keys(obj).sort()
|
|
923
|
-
return (
|
|
924
|
-
'{' +
|
|
925
|
-
keys
|
|
926
|
-
.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k]))
|
|
927
|
-
.join(',') +
|
|
928
|
-
'}'
|
|
929
|
-
)
|
|
930
|
-
}
|
|
931
|
-
|
|
932
910
|
/**
|
|
933
911
|
* Per-layer resolved targets: each declared gesture sub-state collapses to a
|
|
934
912
|
* map of primitive endpoints (numbers or color strings), already passed
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// `isWorkletFunction` lives in `react-native-worklets` (the Reanimated 4 peer
|
|
2
|
+
// dep); Reanimated's own re-export is deprecated.
|
|
3
|
+
import { isWorkletFunction } from 'react-native-worklets'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Reanimated 3.9+ validates that easing functions used in nested-transition
|
package/src/transitions/index.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { resolveTransition, resolveAnimatableValue } from './resolve'
|
|
2
2
|
export { ensureWorkletEasing } from './easing'
|
|
3
|
+
export { isTopLevelTransition, TRANSITION_CONFIG_KEYS } from './keys'
|
|
4
|
+
export { stableSig } from './sig'
|
|
5
|
+
export { DEFAULT_SPRING, springToReanimated } from './spring'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type TransitionConfig } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Field names that may appear on a `TransitionConfig` (spring / timing /
|
|
5
|
+
* decay / no-animation). Used as a structural discriminator: if every key on
|
|
6
|
+
* an object is in this set, the object is treated as a top-level transition;
|
|
7
|
+
* otherwise it's a per-property / per-layer transition map.
|
|
8
|
+
*
|
|
9
|
+
* Adding a new field to `TransitionConfig` requires adding the name here.
|
|
10
|
+
*/
|
|
11
|
+
export const TRANSITION_CONFIG_KEYS = new Set([
|
|
12
|
+
'type',
|
|
13
|
+
'tension',
|
|
14
|
+
'friction',
|
|
15
|
+
'mass',
|
|
16
|
+
'velocity',
|
|
17
|
+
'restSpeedThreshold',
|
|
18
|
+
'restDisplacementThreshold',
|
|
19
|
+
'duration',
|
|
20
|
+
'easing',
|
|
21
|
+
'delay',
|
|
22
|
+
'repeat',
|
|
23
|
+
'deceleration',
|
|
24
|
+
'clamp',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
export function isTopLevelTransition(t: unknown): t is TransitionConfig {
|
|
28
|
+
if (t === null || typeof t !== 'object') return false
|
|
29
|
+
const keys = Object.keys(t as object)
|
|
30
|
+
if (keys.length === 0) return false
|
|
31
|
+
return keys.every((k) => TRANSITION_CONFIG_KEYS.has(k))
|
|
32
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
withTiming,
|
|
9
9
|
} from 'react-native-reanimated'
|
|
10
10
|
import { ensureWorkletEasing } from './easing'
|
|
11
|
+
import { springToReanimated } from './spring'
|
|
11
12
|
import {
|
|
12
13
|
type AnimatableValue,
|
|
13
14
|
type DecayTransition,
|
|
@@ -39,32 +40,8 @@ export type CallbackFactory = (
|
|
|
39
40
|
step: number | undefined,
|
|
40
41
|
) => AnimationCallback | undefined
|
|
41
42
|
|
|
42
|
-
/**
|
|
43
|
-
* Default spring physics, expressed in react-spring vocabulary. Conversion
|
|
44
|
-
* to Reanimated's raw `stiffness` / `damping` lives below; raw config never
|
|
45
|
-
* leaks past this module.
|
|
46
|
-
*/
|
|
47
|
-
const DEFAULT_SPRING: Required<
|
|
48
|
-
Pick<SpringTransition, 'tension' | 'friction' | 'mass'>
|
|
49
|
-
> = {
|
|
50
|
-
tension: 170,
|
|
51
|
-
friction: 26,
|
|
52
|
-
mass: 1,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
43
|
const DEFAULT_TIMING_DURATION = 250
|
|
56
44
|
|
|
57
|
-
function springToReanimated(t: SpringTransition) {
|
|
58
|
-
return {
|
|
59
|
-
stiffness: t.tension ?? DEFAULT_SPRING.tension,
|
|
60
|
-
damping: t.friction ?? DEFAULT_SPRING.friction,
|
|
61
|
-
mass: t.mass ?? DEFAULT_SPRING.mass,
|
|
62
|
-
velocity: t.velocity,
|
|
63
|
-
restSpeedThreshold: t.restSpeedThreshold,
|
|
64
|
-
restDisplacementThreshold: t.restDisplacementThreshold,
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
45
|
function buildSpring(
|
|
69
46
|
cfg: SpringTransition,
|
|
70
47
|
toValue: number | string,
|