@onlynative/inertia 0.0.1-alpha.4 → 0.0.1-alpha.6

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 (48) hide show
  1. package/dist/index.d.mts +30 -4
  2. package/dist/index.d.ts +30 -4
  3. package/dist/index.js +123 -24
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +123 -25
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/motion/Image.d.mts +1 -1
  8. package/dist/motion/Image.d.ts +1 -1
  9. package/dist/motion/Image.js +100 -24
  10. package/dist/motion/Image.js.map +1 -1
  11. package/dist/motion/Image.mjs +100 -24
  12. package/dist/motion/Image.mjs.map +1 -1
  13. package/dist/motion/Pressable.d.mts +1 -1
  14. package/dist/motion/Pressable.d.ts +1 -1
  15. package/dist/motion/Pressable.js +100 -24
  16. package/dist/motion/Pressable.js.map +1 -1
  17. package/dist/motion/Pressable.mjs +100 -24
  18. package/dist/motion/Pressable.mjs.map +1 -1
  19. package/dist/motion/ScrollView.d.mts +1 -1
  20. package/dist/motion/ScrollView.d.ts +1 -1
  21. package/dist/motion/ScrollView.js +100 -24
  22. package/dist/motion/ScrollView.js.map +1 -1
  23. package/dist/motion/ScrollView.mjs +100 -24
  24. package/dist/motion/ScrollView.mjs.map +1 -1
  25. package/dist/motion/Text.d.mts +1 -1
  26. package/dist/motion/Text.d.ts +1 -1
  27. package/dist/motion/Text.js +100 -24
  28. package/dist/motion/Text.js.map +1 -1
  29. package/dist/motion/Text.mjs +100 -24
  30. package/dist/motion/Text.mjs.map +1 -1
  31. package/dist/motion/View.d.mts +1 -1
  32. package/dist/motion/View.d.ts +1 -1
  33. package/dist/motion/View.js +100 -24
  34. package/dist/motion/View.js.map +1 -1
  35. package/dist/motion/View.mjs +100 -24
  36. package/dist/motion/View.mjs.map +1 -1
  37. package/dist/{types-CjztO3RW.d.mts → types-NmNeJjo1.d.mts} +23 -2
  38. package/dist/{types-CjztO3RW.d.ts → types-NmNeJjo1.d.ts} +23 -2
  39. package/jest-preset.js +33 -0
  40. package/jest-setup.js +209 -0
  41. package/package.json +5 -1
  42. package/src/index.ts +1 -0
  43. package/src/motion/createMotionComponent.tsx +172 -26
  44. package/src/transitions/easing.ts +25 -3
  45. package/src/transitions/index.ts +1 -0
  46. package/src/transitions/runtime.ts +63 -0
  47. package/src/transitions/spring.ts +1 -0
  48. package/src/types.ts +23 -1
@@ -29,10 +29,23 @@ interface SpringTransition {
29
29
  delay?: number;
30
30
  repeat?: RepeatConfig;
31
31
  }
32
+ /**
33
+ * Easing input accepted by `TimingTransition`. Either a plain easing function
34
+ * `(t: number) => number` (the pre-Reanimated-4 shape and the shape consumers
35
+ * author by hand) or an `EasingFunctionFactory` (the Reanimated 4 shape
36
+ * returned by `Easing.bezier(...)` and similar builders). The resolver
37
+ * unwraps the factory automatically — consumers don't have to call
38
+ * `.factory()` themselves.
39
+ */
40
+ type EasingFunction = (t: number) => number;
41
+ interface EasingFunctionFactory {
42
+ factory: () => EasingFunction;
43
+ }
44
+ type EasingInput = EasingFunction | EasingFunctionFactory;
32
45
  interface TimingTransition {
33
46
  type: 'timing';
34
47
  duration?: number;
35
- easing?: (t: number) => number;
48
+ easing?: EasingInput;
36
49
  delay?: number;
37
50
  repeat?: RepeatConfig;
38
51
  }
@@ -169,6 +182,14 @@ type VariantsMap<C> = Record<string, AnimateStyle<C>>;
169
182
  * Configure per-layer fade timing via `transition.<stateName>` on the parent
170
183
  * primitive (see `GestureLayerTransitions`); without it, layers default to
171
184
  * the parent transition or the library default spring.
185
+ *
186
+ * **Priority cascade is the only composition mode on this prop.** Non-priority
187
+ * blends — clamped-max (`Math.max(hover*α, focus*β, press*γ)`, as used by MD3
188
+ * state-layer haloes), additive accumulation, or any per-key custom blend —
189
+ * are not expressible declaratively. Drop to `useGesture()` for those: it
190
+ * returns the four progress shared values and a handler bag, and you write
191
+ * a `useAnimatedStyle` block with whatever composition you need. The hook's
192
+ * JSDoc shows the clamped-max halo pattern in full.
172
193
  */
173
194
  interface GestureSubStates<C> {
174
195
  pressed?: AnimateStyle<C>;
@@ -268,4 +289,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
268
289
  style?: React.ComponentProps<C>['style'];
269
290
  }>;
270
291
 
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 };
292
+ export type { AnimatableValue as A, DecayTransition as D, EasingInput as E, 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 };
@@ -29,10 +29,23 @@ interface SpringTransition {
29
29
  delay?: number;
30
30
  repeat?: RepeatConfig;
31
31
  }
32
+ /**
33
+ * Easing input accepted by `TimingTransition`. Either a plain easing function
34
+ * `(t: number) => number` (the pre-Reanimated-4 shape and the shape consumers
35
+ * author by hand) or an `EasingFunctionFactory` (the Reanimated 4 shape
36
+ * returned by `Easing.bezier(...)` and similar builders). The resolver
37
+ * unwraps the factory automatically — consumers don't have to call
38
+ * `.factory()` themselves.
39
+ */
40
+ type EasingFunction = (t: number) => number;
41
+ interface EasingFunctionFactory {
42
+ factory: () => EasingFunction;
43
+ }
44
+ type EasingInput = EasingFunction | EasingFunctionFactory;
32
45
  interface TimingTransition {
33
46
  type: 'timing';
34
47
  duration?: number;
35
- easing?: (t: number) => number;
48
+ easing?: EasingInput;
36
49
  delay?: number;
37
50
  repeat?: RepeatConfig;
38
51
  }
@@ -169,6 +182,14 @@ type VariantsMap<C> = Record<string, AnimateStyle<C>>;
169
182
  * Configure per-layer fade timing via `transition.<stateName>` on the parent
170
183
  * primitive (see `GestureLayerTransitions`); without it, layers default to
171
184
  * the parent transition or the library default spring.
185
+ *
186
+ * **Priority cascade is the only composition mode on this prop.** Non-priority
187
+ * blends — clamped-max (`Math.max(hover*α, focus*β, press*γ)`, as used by MD3
188
+ * state-layer haloes), additive accumulation, or any per-key custom blend —
189
+ * are not expressible declaratively. Drop to `useGesture()` for those: it
190
+ * returns the four progress shared values and a handler bag, and you write
191
+ * a `useAnimatedStyle` block with whatever composition you need. The hook's
192
+ * JSDoc shows the clamped-max halo pattern in full.
172
193
  */
173
194
  interface GestureSubStates<C> {
174
195
  pressed?: AnimateStyle<C>;
@@ -268,4 +289,4 @@ type MotionComponent<C extends ComponentType<any>> = ComponentType<Omit<React.Co
268
289
  style?: React.ComponentProps<C>['style'];
269
290
  }>;
270
291
 
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 };
292
+ export type { AnimatableValue as A, DecayTransition as D, EasingInput as E, 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/jest-preset.js ADDED
@@ -0,0 +1,33 @@
1
+ // Jest preset for projects consuming `@onlynative/inertia` and its sibling
2
+ // adapter packages (`@onlynative/inertia-gestures`, `-gradients`, `-svg`).
3
+ //
4
+ // Layered on top of `react-native`'s own preset. Adds:
5
+ // - the `react-native-worklets` + Reanimated mock surface Inertia exercises
6
+ // (worklet stubs, animation primitives, color/layout utilities)
7
+ // - `transformIgnorePatterns` widened so Jest transforms the published
8
+ // ESM/CJS bundles of `@onlynative/inertia*` and `react-native-worklets`
9
+ // (their `dist/` files are ESM-only and won't run through the default
10
+ // `react-native` transformIgnorePatterns)
11
+ //
12
+ // Usage:
13
+ //
14
+ // // jest.config.js
15
+ // module.exports = {
16
+ // preset: require.resolve('@onlynative/inertia/jest-preset'),
17
+ // }
18
+ //
19
+ // If you need to allowlist additional packages for transformation, extend
20
+ // `transformIgnorePatterns` in your own config — Jest merges over the preset.
21
+
22
+ const rnPreset = require('react-native/jest-preset')
23
+
24
+ module.exports = {
25
+ ...rnPreset,
26
+ setupFiles: [
27
+ ...(rnPreset.setupFiles ?? []),
28
+ require.resolve('./jest-setup.js'),
29
+ ],
30
+ transformIgnorePatterns: [
31
+ 'node_modules/(?!(react-native|@react-native|@react-native-community|@onlynative/inertia|@onlynative/inertia-gestures|@onlynative/inertia-gradients|@onlynative/inertia-svg|react-native-worklets)/)',
32
+ ],
33
+ }
package/jest-setup.js ADDED
@@ -0,0 +1,209 @@
1
+ // Inertia's Jest setup. Loaded automatically by `@onlynative/inertia/jest-preset`,
2
+ // or addable to a hand-rolled config via `setupFiles`.
3
+ //
4
+ // The mocks here cover everything the core, gestures, gradients, and svg
5
+ // packages exercise. Re-create this list locally only if you can't use the
6
+ // preset (e.g. you have a custom transform pipeline that conflicts).
7
+
8
+ // `react-native-worklets` is a required peer of Reanimated 4 and ships its
9
+ // own native module. The source files import `isWorkletFunction` from it
10
+ // directly (Reanimated's re-export is deprecated); under Jest we stub the
11
+ // surface so the source guard works the same way the Reanimated mock does.
12
+ jest.mock('react-native-worklets', () => ({
13
+ __esModule: true,
14
+ isWorkletFunction: () => false,
15
+ }))
16
+
17
+ // Override RN's Text mock — the default mockComponent crashes on arrow function
18
+ // components exported by RN 0.81's Flow `component` syntax.
19
+ jest.mock('react-native/Libraries/Text/Text', () => {
20
+ const React = require('react')
21
+ const Text = React.forwardRef(({ children, ...props }, ref) =>
22
+ React.createElement('RCTText', { ...props, ref }, children),
23
+ )
24
+ Text.displayName = 'Text'
25
+ return { __esModule: true, default: Text }
26
+ })
27
+
28
+ // Reanimated mock — STATIC RENDER, with stable `useSharedValue` semantics.
29
+ //
30
+ // `useAnimatedStyle` invokes the worklet exactly once per render and returns
31
+ // a plain object. It does not subscribe to shared-value mutations — to
32
+ // observe a post-mount target value you must trigger a re-render (the
33
+ // `renderWithMotion` / `flushMotion` helpers in `@onlynative/inertia/testing`
34
+ // do this for you).
35
+ //
36
+ // `useSharedValue` is backed by `useRef` so the same `{ value }` object
37
+ // persists across renders. After a `useEffect` assigns the target, the next
38
+ // render reads it back.
39
+ //
40
+ // What this means for tests:
41
+ // ✅ assert at-rest structure / role / accessibility / static styles
42
+ // ✅ assert post-effect target styles by re-rendering (use
43
+ // `renderWithMotion` from the testing subpath)
44
+ // ❌ frame-level intermediate states are not observable — physics doesn't
45
+ // run; targets snap in one step.
46
+ jest.mock('react-native-reanimated', () => {
47
+ const React = require('react')
48
+ const { Image, ScrollView, Text, View } = require('react-native')
49
+
50
+ const wrap = (Component, displayName) => {
51
+ const Wrapped = React.forwardRef((props, ref) =>
52
+ React.createElement(Component, { ...props, ref }),
53
+ )
54
+ Wrapped.displayName = displayName
55
+ return Wrapped
56
+ }
57
+
58
+ const AnimatedView = wrap(View, 'Animated.View')
59
+ const AnimatedText = wrap(Text, 'Animated.Text')
60
+ const AnimatedImage = wrap(Image, 'Animated.Image')
61
+ const AnimatedScrollView = wrap(ScrollView, 'Animated.ScrollView')
62
+
63
+ return {
64
+ __esModule: true,
65
+ default: {
66
+ View: AnimatedView,
67
+ Text: AnimatedText,
68
+ Image: AnimatedImage,
69
+ ScrollView: AnimatedScrollView,
70
+ createAnimatedComponent: (c) =>
71
+ wrap(c, `Animated(${c.displayName ?? c.name ?? 'Component'})`),
72
+ },
73
+ View: AnimatedView,
74
+ Text: AnimatedText,
75
+ Image: AnimatedImage,
76
+ ScrollView: AnimatedScrollView,
77
+ useSharedValue: (initial) => {
78
+ const ref = React.useRef(null)
79
+ if (ref.current === null) ref.current = { value: initial }
80
+ return ref.current
81
+ },
82
+ useDerivedValue: (fn) => ({ value: fn() }),
83
+ useAnimatedStyle: (fn) => fn(),
84
+ useAnimatedProps: (fn) => fn(),
85
+ useAnimatedReaction: (prepare, react) => {
86
+ // Best-effort sync invocation: run prepare() once, hand the result to
87
+ // react() so tests can observe the side effect (e.g. `useSpring`
88
+ // forwarding a target into its output shared value). Physics still
89
+ // doesn't run — `withSpring` is the identity in this mock.
90
+ const value = typeof prepare === 'function' ? prepare() : undefined
91
+ if (typeof react === 'function') react(value, undefined)
92
+ },
93
+ useAnimatedScrollHandler: (handlers) => {
94
+ // The real handler is an opaque worklet bag; in tests we return a plain
95
+ // function that invokes the appropriate user handler synchronously so
96
+ // assertions on scroll-driven shared values work without a native event
97
+ // loop. Shape: `useAnimatedScrollHandler({ onScroll })` or
98
+ // `useAnimatedScrollHandler(onScroll)`.
99
+ const onScroll =
100
+ typeof handlers === 'function' ? handlers : handlers?.onScroll
101
+ return (event) => {
102
+ if (typeof onScroll === 'function')
103
+ onScroll(event?.nativeEvent ?? event)
104
+ }
105
+ },
106
+ useReducedMotion: () => false,
107
+ isWorkletFunction: () => false,
108
+ cancelAnimation: () => {},
109
+ runOnJS: (fn) => fn,
110
+ runOnUI: (fn) => fn,
111
+ withSpring: (v) => v,
112
+ withTiming: (v) => v,
113
+ withDecay: (v) => v,
114
+ withDelay: (_d, v) => v,
115
+ withRepeat: (v) => v,
116
+ withSequence: (...args) => args[args.length - 1],
117
+ Easing: {
118
+ // Reanimated 4's `Easing.bezier(...)` returns an `EasingFunctionFactory`
119
+ // (`{ factory: () => EasingFunction }`), not a bare function. We surface
120
+ // both shapes: the returned value is callable (backward-compat with
121
+ // pre-4 call sites that did `Easing.bezier(...)(t)`) AND has `.factory()`
122
+ // so the canonical Reanimated 4 unwrap `Easing.bezier(...).factory()`
123
+ // works under Jest without consumer overrides.
124
+ bezier: () => {
125
+ const fn = () => 0
126
+ fn.factory = () => fn
127
+ return fn
128
+ },
129
+ ease: () => 0,
130
+ linear: (t) => t,
131
+ quad: () => 0,
132
+ cubic: () => 0,
133
+ sin: () => 0,
134
+ circle: () => 0,
135
+ exp: () => 0,
136
+ poly: () => () => 0,
137
+ back: () => () => 0,
138
+ bounce: () => 0,
139
+ elastic: () => () => 0,
140
+ in: (fn) => fn,
141
+ out: (fn) => fn,
142
+ inOut: (fn) => fn,
143
+ step0: () => 0,
144
+ step1: () => 0,
145
+ },
146
+ interpolate: (value, _input, output) =>
147
+ value >= 1 ? output[output.length - 1] : output[0],
148
+ interpolateColor: (value, _input, output) =>
149
+ value >= 1 ? output[output.length - 1] : output[0],
150
+ Extrapolation: { CLAMP: 'clamp', IDENTITY: 'identity', EXTEND: 'extend' },
151
+ // Layout-animation builder stub. The real builder is chainable and records
152
+ // spring / timing config; tests assert against the recorded fields. Each
153
+ // chain call returns a fresh instance with the field set so the resolver's
154
+ // immutable-chain idiom (rebinding `builder = builder.x(...)`) works.
155
+ LinearTransition: (() => {
156
+ class LinearTransitionStub {
157
+ constructor() {
158
+ this.__kind = 'LinearTransition'
159
+ }
160
+ _clone(patch) {
161
+ const next = new LinearTransitionStub()
162
+ Object.assign(next, this, patch)
163
+ return next
164
+ }
165
+ springify(duration) {
166
+ return this._clone({ __mode: 'spring', __duration: duration })
167
+ }
168
+ damping(v) {
169
+ return this._clone({ damping: v })
170
+ }
171
+ stiffness(v) {
172
+ return this._clone({ stiffness: v })
173
+ }
174
+ mass(v) {
175
+ return this._clone({ mass: v })
176
+ }
177
+ dampingRatio(v) {
178
+ return this._clone({ dampingRatio: v })
179
+ }
180
+ duration(v) {
181
+ return this._clone({ __mode: 'timing', __duration: v })
182
+ }
183
+ easing(fn) {
184
+ return this._clone({ easing: fn })
185
+ }
186
+ delay(v) {
187
+ return this._clone({ delay: v })
188
+ }
189
+ reduceMotion(v) {
190
+ return this._clone({ reduceMotion: v })
191
+ }
192
+ }
193
+ const proxy = new Proxy(LinearTransitionStub, {
194
+ get(target, prop) {
195
+ if (prop in target) return target[prop]
196
+ // Forward static-style invocations to a fresh instance so
197
+ // `LinearTransition.springify()` works as well as
198
+ // `new LinearTransition().springify()`.
199
+ return (...args) => {
200
+ const instance = new LinearTransitionStub()
201
+ return instance[prop](...args)
202
+ }
203
+ },
204
+ })
205
+ return proxy
206
+ })(),
207
+ ReduceMotion: { System: 'system', Always: 'always', Never: 'never' },
208
+ }
209
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlynative/inertia",
3
- "version": "0.0.1-alpha.4",
3
+ "version": "0.0.1-alpha.6",
4
4
  "description": "Declarative animation primitives for React Native, built on react-native-reanimated.",
5
5
  "license": "MIT",
6
6
  "author": "OnlyNative",
@@ -79,11 +79,15 @@
79
79
  "import": "./dist/testing/index.mjs",
80
80
  "require": "./dist/testing/index.js"
81
81
  },
82
+ "./jest-preset": "./jest-preset.js",
83
+ "./jest-setup": "./jest-setup.js",
82
84
  "./package.json": "./package.json"
83
85
  },
84
86
  "files": [
85
87
  "dist",
86
88
  "src",
89
+ "jest-preset.js",
90
+ "jest-setup.js",
87
91
  "llms.txt",
88
92
  "README.md",
89
93
  "LICENSE",
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export type { MotionConfigValue, ReducedMotion } from './config'
23
23
  export { Presence, usePresence } from './presence'
24
24
  export type { PresenceContextValue } from './presence'
25
25
  export {
26
+ buildReleaseAnimation,
26
27
  resolveTransition,
27
28
  resolveAnimatableValue,
28
29
  ensureWorkletEasing,