@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.
Files changed (61) hide show
  1. package/README.md +44 -3
  2. package/dist/index.d.mts +259 -3
  3. package/dist/index.d.ts +259 -3
  4. package/dist/index.js +1866 -161
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1864 -165
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/motion/Image.d.mts +1 -1
  9. package/dist/motion/Image.d.ts +1 -1
  10. package/dist/motion/Image.js +1696 -146
  11. package/dist/motion/Image.js.map +1 -1
  12. package/dist/motion/Image.mjs +1698 -148
  13. package/dist/motion/Image.mjs.map +1 -1
  14. package/dist/motion/Pressable.d.mts +1 -1
  15. package/dist/motion/Pressable.d.ts +1 -1
  16. package/dist/motion/Pressable.js +1696 -146
  17. package/dist/motion/Pressable.js.map +1 -1
  18. package/dist/motion/Pressable.mjs +1698 -148
  19. package/dist/motion/Pressable.mjs.map +1 -1
  20. package/dist/motion/ScrollView.d.mts +1 -1
  21. package/dist/motion/ScrollView.d.ts +1 -1
  22. package/dist/motion/ScrollView.js +1696 -146
  23. package/dist/motion/ScrollView.js.map +1 -1
  24. package/dist/motion/ScrollView.mjs +1698 -148
  25. package/dist/motion/ScrollView.mjs.map +1 -1
  26. package/dist/motion/Text.d.mts +1 -1
  27. package/dist/motion/Text.d.ts +1 -1
  28. package/dist/motion/Text.js +1696 -146
  29. package/dist/motion/Text.js.map +1 -1
  30. package/dist/motion/Text.mjs +1698 -148
  31. package/dist/motion/Text.mjs.map +1 -1
  32. package/dist/motion/View.d.mts +1 -1
  33. package/dist/motion/View.d.ts +1 -1
  34. package/dist/motion/View.js +1696 -146
  35. package/dist/motion/View.js.map +1 -1
  36. package/dist/motion/View.mjs +1698 -148
  37. package/dist/motion/View.mjs.map +1 -1
  38. package/dist/{types-DeZZzE_e.d.mts → types-CjztO3RW.d.mts} +89 -20
  39. package/dist/{types-DeZZzE_e.d.ts → types-CjztO3RW.d.ts} +89 -20
  40. package/llms.txt +54 -6
  41. package/package.json +1 -1
  42. package/src/__type-tests__/animate.test-d.tsx +88 -0
  43. package/src/index.ts +16 -1
  44. package/src/layout/index.ts +1 -0
  45. package/src/layout/resolveLayout.ts +54 -0
  46. package/src/motion/createMotionComponent.tsx +292 -153
  47. package/src/motion/installCheck.ts +69 -0
  48. package/src/transitions/easing.ts +3 -1
  49. package/src/transitions/index.ts +3 -0
  50. package/src/transitions/keys.ts +32 -0
  51. package/src/transitions/resolve.ts +1 -24
  52. package/src/transitions/sig.ts +40 -0
  53. package/src/transitions/spring.ts +41 -0
  54. package/src/types.ts +96 -18
  55. package/src/values/index.ts +14 -0
  56. package/src/values/useAnimation.ts +69 -0
  57. package/src/values/useGesture.ts +144 -0
  58. package/src/values/useMotionValue.ts +33 -0
  59. package/src/values/useScroll.ts +72 -0
  60. package/src/values/useSpring.ts +93 -0
  61. package/src/values/useTransform.ts +132 -0
@@ -0,0 +1,72 @@
1
+ import {
2
+ useAnimatedScrollHandler,
3
+ useSharedValue,
4
+ type SharedValue,
5
+ } from 'react-native-reanimated'
6
+ import { type NativeScrollEvent, type NativeSyntheticEvent } from 'react-native'
7
+
8
+ export interface UseScrollResult {
9
+ /** Horizontal scroll offset in points. */
10
+ scrollX: SharedValue<number>
11
+ /** Vertical scroll offset in points. */
12
+ scrollY: SharedValue<number>
13
+ /**
14
+ * Handler to pass to a `Motion.ScrollView`'s `onScroll` prop (or any other
15
+ * Reanimated `Animated.ScrollView`). The handler is opaque to JS — it runs
16
+ * as a worklet — but the type narrows to the same shape RN's native
17
+ * `onScroll` prop expects so it composes cleanly.
18
+ */
19
+ onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
20
+ }
21
+
22
+ /**
23
+ * Track the scroll offset of a `Motion.ScrollView` as shared values.
24
+ *
25
+ * ```tsx
26
+ * const { scrollY, onScroll } = useScroll()
27
+ * const headerOpacity = useTransform(scrollY, [0, 100], [1, 0])
28
+ *
29
+ * return (
30
+ * <>
31
+ * <Motion.View animate={{ opacity: headerOpacity }} />
32
+ * <Motion.ScrollView onScroll={onScroll} scrollEventThrottle={16}>
33
+ * …
34
+ * </Motion.ScrollView>
35
+ * </>
36
+ * )
37
+ * ```
38
+ *
39
+ * Scroll events fire on the UI thread, so `scrollX` / `scrollY` are safe to
40
+ * read from any worklet (`useAnimatedStyle`, `useDerivedValue`,
41
+ * `useTransform`) without a JS-thread bounce.
42
+ *
43
+ * Remember to set `scrollEventThrottle={16}` on the `ScrollView` for 60Hz
44
+ * updates — RN's default is to dispatch on every event, which on iOS still
45
+ * means one per frame, but Android benefits from the explicit cap.
46
+ */
47
+ export function useScroll(): UseScrollResult {
48
+ const scrollX = useSharedValue(0)
49
+ const scrollY = useSharedValue(0)
50
+
51
+ const handler = useAnimatedScrollHandler({
52
+ onScroll: (event) => {
53
+ 'worklet'
54
+ scrollX.value = event.contentOffset.x
55
+ scrollY.value = event.contentOffset.y
56
+ },
57
+ })
58
+
59
+ // `useAnimatedScrollHandler` returns an opaque worklet bag whose JS-side
60
+ // type is `(event) => void` but actually carries native event-handler
61
+ // wiring. Cast through `unknown` because the public RN `onScroll` type
62
+ // wants a `NativeSyntheticEvent`-taking function and Reanimated's handler
63
+ // is structurally compatible — the cast is to satisfy the consumer's
64
+ // prop type at the call site.
65
+ return {
66
+ scrollX,
67
+ scrollY,
68
+ onScroll: handler as unknown as (
69
+ event: NativeSyntheticEvent<NativeScrollEvent>,
70
+ ) => void,
71
+ }
72
+ }
@@ -0,0 +1,93 @@
1
+ import { useEffect, useMemo } from 'react'
2
+ import {
3
+ useAnimatedReaction,
4
+ useSharedValue,
5
+ withSpring,
6
+ type SharedValue,
7
+ } from 'react-native-reanimated'
8
+ import { springToReanimated } from '../transitions/spring'
9
+ import { type SpringTransition } from '../types'
10
+
11
+ /**
12
+ * Animate a shared value toward `target` with spring physics, using the
13
+ * library's react-spring vocabulary (`tension` / `friction` / `mass`).
14
+ *
15
+ * `target` may be a plain number or a `SharedValue<number>`. The plain-number
16
+ * path drives the spring from a JS `useEffect`, so the animation re-runs on
17
+ * every render where `target` changes. The shared-value path drives the
18
+ * spring from a Reanimated reaction on the UI thread, so values produced by
19
+ * gestures, scroll handlers, or other worklets flow through without bouncing
20
+ * back to JS.
21
+ *
22
+ * Both call sites end up at the same `withSpring` invocation; the split is
23
+ * just about which thread observes the source change.
24
+ */
25
+ export function useSpring(
26
+ target: number | SharedValue<number>,
27
+ config?: SpringTransition,
28
+ ): SharedValue<number> {
29
+ // Reanimated config is rebuilt only when the public config object changes
30
+ // shape. The worklet path reads this from JS-thread closure capture, which
31
+ // is fine: it's the resolved config that's invariant across UI-thread
32
+ // ticks, not a JS-thread reference that would go stale.
33
+ const reanimConfig = useMemo(
34
+ () => springToReanimated(config ?? {}),
35
+ [
36
+ config?.tension,
37
+ config?.friction,
38
+ config?.mass,
39
+ config?.velocity,
40
+ config?.restSpeedThreshold,
41
+ config?.restDisplacementThreshold,
42
+ ],
43
+ )
44
+
45
+ const isSharedTarget = isSharedValue(target)
46
+ const initial = isSharedTarget ? target.value : (target as number)
47
+ const output = useSharedValue<number>(initial)
48
+
49
+ // Plain-number path. The reaction below is a no-op when `target` is a
50
+ // number, so this effect carries the change. Reading `target` directly in
51
+ // the dep array means React drives the schedule; we don't have to babysit
52
+ // a stale closure.
53
+ useEffect(() => {
54
+ if (isSharedTarget) return
55
+ output.value = withSpring(target as number, reanimConfig)
56
+ // `output` is identity-stable per hook instance (Reanimated guarantee).
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [isSharedTarget, target, reanimConfig])
59
+
60
+ // SharedValue path. `useAnimatedReaction` runs the prepare worklet whenever
61
+ // its returned value changes; we read `.value` off the target SV and pipe
62
+ // it through `withSpring` on the UI thread. When the target is a plain
63
+ // number we never declare a source so the reaction is inert (returns
64
+ // `null`, never fires `react`).
65
+ useAnimatedReaction(
66
+ () => {
67
+ 'worklet'
68
+ if (!isSharedTarget) return null
69
+ return (target as SharedValue<number>).value
70
+ },
71
+ (next, prev) => {
72
+ 'worklet'
73
+ if (next === null || next === prev) return
74
+ output.value = withSpring(next, reanimConfig)
75
+ },
76
+ [isSharedTarget, reanimConfig],
77
+ )
78
+
79
+ return output
80
+ }
81
+
82
+ function isSharedValue(v: unknown): v is SharedValue<number> {
83
+ // SharedValues are plain objects with a single `value` accessor — there is
84
+ // no public constructor or instanceof check. Reading `'value' in v` on any
85
+ // POJO would also pass, but the hook's call site already narrows the type;
86
+ // this guard exists to dispatch between the two implementation paths, not
87
+ // to validate untrusted input.
88
+ return (
89
+ typeof v === 'object' &&
90
+ v !== null &&
91
+ 'value' in (v as Record<string, unknown>)
92
+ )
93
+ }
@@ -0,0 +1,132 @@
1
+ import {
2
+ Extrapolation,
3
+ interpolate,
4
+ interpolateColor,
5
+ useDerivedValue,
6
+ type SharedValue,
7
+ } from 'react-native-reanimated'
8
+ // `isWorkletFunction` was re-exported from `react-native-reanimated` historically
9
+ // but is deprecated there in favor of importing from `react-native-worklets`.
10
+ // `react-native-worklets` is a required peer of Reanimated 4, so the direct
11
+ // import is always available wherever Inertia is.
12
+ import { isWorkletFunction } from 'react-native-worklets'
13
+
14
+ /**
15
+ * Extrapolation behavior at the edges of the input range. Mirrors
16
+ * Reanimated's enum so consumers don't need a separate import.
17
+ *
18
+ * - `'clamp'` (default) — output stays pinned at the first/last value
19
+ * outside the input range. Matches Framer Motion's default.
20
+ * - `'identity'` — return the input unchanged outside the range.
21
+ * - `'extend'` — continue the linear slope beyond the range.
22
+ */
23
+ export type ExtrapolationMode = 'clamp' | 'identity' | 'extend'
24
+
25
+ export interface UseTransformOptions {
26
+ extrapolateLeft?: ExtrapolationMode
27
+ extrapolateRight?: ExtrapolationMode
28
+ }
29
+
30
+ /**
31
+ * Derive a value from one or more shared values via a transformer worklet.
32
+ *
33
+ * ```ts
34
+ * const x = useMotionValue(0)
35
+ * const y = useMotionValue(0)
36
+ * const distance = useTransform(() => Math.sqrt(x.value ** 2 + y.value ** 2))
37
+ * ```
38
+ *
39
+ * The transformer must be a worklet (or a plain function we auto-wrap —
40
+ * see the easing wrapper for the rationale). It runs on the UI thread on
41
+ * every frame where any read shared value changes.
42
+ */
43
+ export function useTransform<T>(transformer: () => T): SharedValue<T>
44
+
45
+ /**
46
+ * Interpolate a numeric shared value onto a range of numbers or colors.
47
+ *
48
+ * ```ts
49
+ * const scroll = useMotionValue(0)
50
+ * const headerOpacity = useTransform(scroll, [0, 100], [1, 0])
51
+ * const headerColor = useTransform(scroll, [0, 100], ['#fff', '#000'])
52
+ * ```
53
+ *
54
+ * When `outputRange` is numeric, this maps to Reanimated's `interpolate`;
55
+ * when it's a tuple of color strings, it maps to `interpolateColor`. The
56
+ * input range must be monotonically increasing.
57
+ */
58
+ export function useTransform(
59
+ value: SharedValue<number>,
60
+ inputRange: readonly number[],
61
+ outputRange: readonly number[],
62
+ options?: UseTransformOptions,
63
+ ): SharedValue<number>
64
+ export function useTransform(
65
+ value: SharedValue<number>,
66
+ inputRange: readonly number[],
67
+ outputRange: readonly string[],
68
+ options?: UseTransformOptions,
69
+ ): SharedValue<string>
70
+
71
+ export function useTransform<T>(
72
+ arg1: (() => T) | SharedValue<number>,
73
+ inputRange?: readonly number[],
74
+ outputRange?: readonly number[] | readonly string[],
75
+ options?: UseTransformOptions,
76
+ ): SharedValue<T> | SharedValue<number> | SharedValue<string> {
77
+ // Build the producer worklet on the JS thread, then call
78
+ // `useDerivedValue` exactly once. Keeping the hook call unconditional
79
+ // satisfies rules-of-hooks; per-call branching (transformer vs
80
+ // interpolation) is decided once at JS time, never at frame time.
81
+ let producer: () => unknown
82
+ if (typeof arg1 === 'function') {
83
+ // Transformer overload. The public surface accepts a plain function;
84
+ // Reanimated 3.9+ requires worklets in nested-derivation contexts, so
85
+ // we auto-wrap at JS time the same way `ensureWorkletEasing` does.
86
+ const userFn = arg1 as () => T
87
+ producer = isWorkletFunction(userFn)
88
+ ? userFn
89
+ : () => {
90
+ 'worklet'
91
+ return userFn()
92
+ }
93
+ } else {
94
+ // Interpolation overload. We pre-resolve everything JS-side so the
95
+ // worklet body only consumes flat values.
96
+ const source = arg1
97
+ const input = inputRange as readonly number[]
98
+ const output = outputRange as readonly (number | string)[]
99
+ const isColor = output.length > 0 && typeof output[0] === 'string'
100
+ const extrapolateLeft = mapExtrapolation(options?.extrapolateLeft)
101
+ const extrapolateRight = mapExtrapolation(options?.extrapolateRight)
102
+ producer = isColor
103
+ ? () => {
104
+ 'worklet'
105
+ return interpolateColor(
106
+ source.value,
107
+ input as number[],
108
+ output as string[],
109
+ )
110
+ }
111
+ : () => {
112
+ 'worklet'
113
+ return interpolate(
114
+ source.value,
115
+ input as number[],
116
+ output as number[],
117
+ { extrapolateLeft, extrapolateRight },
118
+ )
119
+ }
120
+ }
121
+
122
+ return useDerivedValue(producer as () => never) as unknown as
123
+ | SharedValue<T>
124
+ | SharedValue<number>
125
+ | SharedValue<string>
126
+ }
127
+
128
+ function mapExtrapolation(mode: ExtrapolationMode | undefined): Extrapolation {
129
+ if (mode === 'identity') return Extrapolation.IDENTITY
130
+ if (mode === 'extend') return Extrapolation.EXTEND
131
+ return Extrapolation.CLAMP
132
+ }