@rootnative/inertia 0.0.0-alpha.0

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 (113) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +131 -0
  4. package/dist/gestureLayer/index.d.mts +119 -0
  5. package/dist/gestureLayer/index.d.ts +119 -0
  6. package/dist/gestureLayer/index.js +346 -0
  7. package/dist/gestureLayer/index.js.map +1 -0
  8. package/dist/gestureLayer/index.mjs +344 -0
  9. package/dist/gestureLayer/index.mjs.map +1 -0
  10. package/dist/index.d.mts +507 -0
  11. package/dist/index.d.ts +507 -0
  12. package/dist/index.js +1546 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +1515 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/motion/Image.d.mts +12 -0
  17. package/dist/motion/Image.d.ts +12 -0
  18. package/dist/motion/Image.js +1138 -0
  19. package/dist/motion/Image.js.map +1 -0
  20. package/dist/motion/Image.mjs +1132 -0
  21. package/dist/motion/Image.mjs.map +1 -0
  22. package/dist/motion/Pressable.d.mts +15 -0
  23. package/dist/motion/Pressable.d.ts +15 -0
  24. package/dist/motion/Pressable.js +1138 -0
  25. package/dist/motion/Pressable.js.map +1 -0
  26. package/dist/motion/Pressable.mjs +1132 -0
  27. package/dist/motion/Pressable.mjs.map +1 -0
  28. package/dist/motion/ScrollView.d.mts +12 -0
  29. package/dist/motion/ScrollView.d.ts +12 -0
  30. package/dist/motion/ScrollView.js +1138 -0
  31. package/dist/motion/ScrollView.js.map +1 -0
  32. package/dist/motion/ScrollView.mjs +1132 -0
  33. package/dist/motion/ScrollView.mjs.map +1 -0
  34. package/dist/motion/Text.d.mts +11 -0
  35. package/dist/motion/Text.d.ts +11 -0
  36. package/dist/motion/Text.js +1138 -0
  37. package/dist/motion/Text.js.map +1 -0
  38. package/dist/motion/Text.mjs +1132 -0
  39. package/dist/motion/Text.mjs.map +1 -0
  40. package/dist/motion/View.d.mts +11 -0
  41. package/dist/motion/View.d.ts +11 -0
  42. package/dist/motion/View.js +1138 -0
  43. package/dist/motion/View.js.map +1 -0
  44. package/dist/motion/View.mjs +1132 -0
  45. package/dist/motion/View.mjs.map +1 -0
  46. package/dist/testing/index.d.mts +57 -0
  47. package/dist/testing/index.d.ts +57 -0
  48. package/dist/testing/index.js +19 -0
  49. package/dist/testing/index.js.map +1 -0
  50. package/dist/testing/index.mjs +16 -0
  51. package/dist/testing/index.mjs.map +1 -0
  52. package/dist/touch/index.d.mts +146 -0
  53. package/dist/touch/index.d.ts +146 -0
  54. package/dist/touch/index.js +166 -0
  55. package/dist/touch/index.js.map +1 -0
  56. package/dist/touch/index.mjs +164 -0
  57. package/dist/touch/index.mjs.map +1 -0
  58. package/dist/types-cU43dEmH.d.mts +339 -0
  59. package/dist/types-cU43dEmH.d.ts +339 -0
  60. package/dist/useGesture-BnY65PlQ.d.ts +84 -0
  61. package/dist/useGesture-DxtXdz-K.d.mts +84 -0
  62. package/jest-preset.js +33 -0
  63. package/jest-setup.js +213 -0
  64. package/llms.txt +142 -0
  65. package/package.json +151 -0
  66. package/src/__type-tests__/animate.test-d.tsx +88 -0
  67. package/src/__type-tests__/variants.test-d.tsx +67 -0
  68. package/src/config/MotionConfig.tsx +30 -0
  69. package/src/config/MotionConfigContext.ts +53 -0
  70. package/src/config/index.ts +9 -0
  71. package/src/gestureLayer/index.ts +21 -0
  72. package/src/gestureLayer/useGestureLayer.ts +285 -0
  73. package/src/gestures/focusVisibility.ts +61 -0
  74. package/src/gestures/index.ts +1 -0
  75. package/src/index.ts +72 -0
  76. package/src/layout/index.ts +16 -0
  77. package/src/layout/resolveLayout.ts +54 -0
  78. package/src/layout/sharedRegistry.ts +111 -0
  79. package/src/layout/useSharedLayout.ts +289 -0
  80. package/src/motion/Image.tsx +9 -0
  81. package/src/motion/Pressable.tsx +12 -0
  82. package/src/motion/ScrollView.tsx +9 -0
  83. package/src/motion/Text.tsx +8 -0
  84. package/src/motion/View.tsx +8 -0
  85. package/src/motion/createMotionComponent.tsx +1306 -0
  86. package/src/motion/index.ts +26 -0
  87. package/src/motion/installCheck.ts +65 -0
  88. package/src/presence/Presence.tsx +165 -0
  89. package/src/presence/PresenceContext.ts +28 -0
  90. package/src/presence/index.ts +6 -0
  91. package/src/testing/index.ts +78 -0
  92. package/src/touch/index.ts +18 -0
  93. package/src/touch/useTouchDrag.ts +289 -0
  94. package/src/transitions/easing.ts +53 -0
  95. package/src/transitions/index.ts +7 -0
  96. package/src/transitions/keys.ts +32 -0
  97. package/src/transitions/resolve.ts +169 -0
  98. package/src/transitions/resolveSequence.ts +87 -0
  99. package/src/transitions/runtime.ts +63 -0
  100. package/src/transitions/sig.ts +40 -0
  101. package/src/transitions/spring.ts +42 -0
  102. package/src/types.ts +383 -0
  103. package/src/values/index.ts +26 -0
  104. package/src/values/useAnimation.ts +69 -0
  105. package/src/values/useBooleanSpring.ts +33 -0
  106. package/src/values/useColorTransition.ts +72 -0
  107. package/src/values/useGesture.ts +144 -0
  108. package/src/values/useMotionValue.ts +33 -0
  109. package/src/values/useScroll.ts +72 -0
  110. package/src/values/useShadow.ts +116 -0
  111. package/src/values/useSpring.ts +98 -0
  112. package/src/values/useTransform.ts +132 -0
  113. package/src/values/useVariants.ts +60 -0
package/src/types.ts ADDED
@@ -0,0 +1,383 @@
1
+ import {
2
+ type ComponentProps,
3
+ type ComponentType,
4
+ type ReactElement,
5
+ type Ref,
6
+ } from 'react'
7
+ import { type StyleProp } from 'react-native'
8
+
9
+ /**
10
+ * A single animation step's destination, optionally overriding the transition
11
+ * for that step.
12
+ */
13
+ export type SequenceStep<V> = V | ({ to: V; delay?: number } & TransitionConfig)
14
+
15
+ /**
16
+ * A target value for an animatable property: a single value, a sequence of
17
+ * steps (keyframes), or a single step object.
18
+ */
19
+ export type AnimatableValue<V> =
20
+ | V
21
+ | SequenceStep<V>
22
+ | ReadonlyArray<SequenceStep<V>>
23
+
24
+ /**
25
+ * Spring transition — public surface uses react-spring vocabulary
26
+ * (`tension` / `friction` / `mass`), not Reanimated's raw stiffness/damping.
27
+ */
28
+ export interface SpringTransition {
29
+ type?: 'spring'
30
+ tension?: number
31
+ friction?: number
32
+ mass?: number
33
+ velocity?: number
34
+ restSpeedThreshold?: number
35
+ restDisplacementThreshold?: number
36
+ delay?: number
37
+ repeat?: RepeatConfig
38
+ }
39
+
40
+ /**
41
+ * Easing input accepted by `TimingTransition`. Either a plain easing function
42
+ * `(t: number) => number` (the pre-Reanimated-4 shape and the shape consumers
43
+ * author by hand) or an `EasingFunctionFactory` (the Reanimated 4 shape
44
+ * returned by `Easing.bezier(...)` and similar builders). The resolver
45
+ * unwraps the factory automatically — consumers don't have to call
46
+ * `.factory()` themselves.
47
+ */
48
+ export type EasingFunction = (t: number) => number
49
+ export interface EasingFunctionFactory {
50
+ factory: () => EasingFunction
51
+ }
52
+ export type EasingInput = EasingFunction | EasingFunctionFactory
53
+
54
+ export interface TimingTransition {
55
+ type: 'timing'
56
+ duration?: number
57
+ easing?: EasingInput
58
+ delay?: number
59
+ repeat?: RepeatConfig
60
+ }
61
+
62
+ export interface DecayTransition {
63
+ type: 'decay'
64
+ velocity?: number
65
+ deceleration?: number
66
+ clamp?: [number, number]
67
+ delay?: number
68
+ }
69
+
70
+ export interface NoAnimationTransition {
71
+ type: 'no-animation'
72
+ }
73
+
74
+ export type TransitionConfig =
75
+ | SpringTransition
76
+ | TimingTransition
77
+ | DecayTransition
78
+ | NoAnimationTransition
79
+
80
+ /**
81
+ * Repeat config — one shape, not three flags. Default `alternate: true`.
82
+ */
83
+ export type RepeatConfig =
84
+ | number
85
+ | 'infinite'
86
+ | { count: number | 'infinite'; alternate?: boolean }
87
+
88
+ /**
89
+ * Per-property transition map. Keys must match keys present on `animate`.
90
+ * Top-level entries on `transition` apply to all properties unless overridden
91
+ * here.
92
+ */
93
+ export type PerPropertyTransition<S> = {
94
+ [K in keyof S]?: TransitionConfig
95
+ }
96
+
97
+ /**
98
+ * Per-gesture-layer transition map. Each `gesture` sub-state animates a
99
+ * progress value 0↔1 with its own transition; the worklet composites the
100
+ * layers in priority order (`hovered → focused → focusVisible → pressed`).
101
+ *
102
+ * Keys live on the same `transition` object as `PerPropertyTransition` because
103
+ * the only other place they could go (nested inside `gesture` itself) would
104
+ * collide with the primitive's inferred style keys.
105
+ */
106
+ export interface GestureLayerTransitions {
107
+ pressed?: TransitionConfig
108
+ focused?: TransitionConfig
109
+ focusVisible?: TransitionConfig
110
+ hovered?: TransitionConfig
111
+ }
112
+
113
+ export type Transition<S> =
114
+ | TransitionConfig
115
+ | (PerPropertyTransition<S> & GestureLayerTransitions)
116
+
117
+ /**
118
+ * Transform shorthands that Inertia exposes on `animate` but that don't
119
+ * appear on RN's typed ViewStyle as top-level keys. RN keeps `scale`,
120
+ * `rotate`, `rotateX`, and `rotateY` inside the `transform` array; only
121
+ * `scaleX`/`scaleY` and `translateX`/`translateY` are surfaced as
122
+ * (deprecated) top-level shortcuts. Inertia's runtime treats these as
123
+ * transform-group keys (see `TRANSFORM_KEYS` in `createMotionComponent`),
124
+ * so they're documented as first-class animatables in `CLAUDE.md` and must
125
+ * be reachable from `animate` without dropping into the `transform: [...]`
126
+ * array form. Rotation values are degrees as numbers — the runtime appends
127
+ * `'deg'` before handing the transform to Reanimated.
128
+ */
129
+ type AnimatableTransformExtras = {
130
+ scale?: AnimatableValue<number>
131
+ rotate?: AnimatableValue<number>
132
+ rotateX?: AnimatableValue<number>
133
+ rotateY?: AnimatableValue<number>
134
+ }
135
+
136
+ /**
137
+ * The animation state shape inferred from the underlying component's style
138
+ * prop. We narrow to the value side of `style` so consumers see ViewStyle on
139
+ * `Motion.View`, TextStyle on `Motion.Text`, etc. — no shared union.
140
+ *
141
+ * Some components (notably `Pressable`) type `style` as a union of
142
+ * `StyleProp<T>` and a callback `(state) => StyleProp<T>`. If we infer `S`
143
+ * directly from `StyleProp<infer S>`, the callback branch widens `S` to
144
+ * `unknown`, which collapses the animate map to `| {}` and silently
145
+ * accepts any key. Excluding functions first keeps inference tight.
146
+ */
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ type _StyleValue<T> = Exclude<T, (...args: any[]) => any>
149
+
150
+ export type AnimateStyle<C> = C extends { style?: infer Raw }
151
+ ? _StyleValue<Raw> extends StyleProp<infer S>
152
+ ? { [K in keyof S]?: AnimatableValue<S[K]> } & AnimatableTransformExtras
153
+ : never
154
+ : never
155
+
156
+ export interface AnimationCallbackInfo<S> {
157
+ /**
158
+ * The animatable key that just settled — typically a `keyof S` (e.g.
159
+ * `'opacity'`, `'translateX'`). The sentinel `'transform'` is emitted in
160
+ * lieu of any specific transform axis (`translateX`/`Y`, `scale`/`X`/`Y`,
161
+ * `rotate`) when the terminal `'animation'` phase fires for a transform
162
+ * group, so a multi-axis translate produces one callback rather than two.
163
+ */
164
+ key: keyof S | 'transform'
165
+ finished: boolean
166
+ value: unknown
167
+ target: unknown
168
+ phase: 'step' | 'sequence' | 'repeat' | 'animation'
169
+ step: number | undefined
170
+ iteration: number
171
+ }
172
+
173
+ /**
174
+ * A variants map: string state names → animate target objects.
175
+ */
176
+ export type VariantsMap<C> = Record<string, AnimateStyle<C>>
177
+
178
+ /**
179
+ * Gesture sub-states accepted by the `gesture` prop on every Motion primitive.
180
+ *
181
+ * - `pressed` — active while the user is touching the component (touch start
182
+ * to touch end / cancel).
183
+ * - `focused` — active while a focusable component owns focus, regardless of
184
+ * how focus arrived (mouse, touch, or keyboard). No-op for non-focusable
185
+ * underlying components.
186
+ * - `focusVisible` — active only when focus arrived from the keyboard
187
+ * (W3C `:focus-visible` semantics). Use this for focus rings to avoid
188
+ * flashing them on click-focus on web. On native — where focus always
189
+ * arrives via D-pad, screen reader, or hardware keyboard — this behaves
190
+ * identically to `focused`.
191
+ * - `hovered` — web-only. Typed for cross-platform call sites; the runtime is
192
+ * a no-op on native.
193
+ *
194
+ * Sub-states layer additively. Each declared sub-state owns an independent
195
+ * progress value (0↔1) that animates in/out with its own transition; the
196
+ * worklet composites layers in priority order (lowest-to-highest):
197
+ * `hovered → focused → focusVisible → pressed`. Per-property the chain is
198
+ *
199
+ * v = base
200
+ * v = lerp(v, hovered.value, progressHovered) // if declared
201
+ * v = lerp(v, focused.value, progressFocused) // if declared
202
+ * v = lerp(v, focusVisible.value, progressFocusVisible) // if declared
203
+ * v = lerp(v, pressed.value, progressPressed) // if declared
204
+ *
205
+ * (Color-valued keys use `interpolateColor` instead of `lerp`.) When a single
206
+ * sub-state is active, this collapses to "the highest-priority declared layer
207
+ * wins". When multiple are mid-transition (e.g. release-while-still-hovered)
208
+ * each layer fades independently — a press layer fading out at 50ms while a
209
+ * hover layer holds at full opacity matches MD3 state-layer semantics.
210
+ *
211
+ * Configure per-layer fade timing via `transition.<stateName>` on the parent
212
+ * primitive (see `GestureLayerTransitions`); without it, layers default to
213
+ * the parent transition or the library default spring.
214
+ *
215
+ * **Priority cascade is the only composition mode on this prop.** Non-priority
216
+ * blends — clamped-max (`Math.max(hover*α, focus*β, press*γ)`, as used by MD3
217
+ * state-layer haloes), additive accumulation, or any per-key custom blend —
218
+ * are not expressible declaratively. Drop to `useGesture()` for those: it
219
+ * returns the four progress shared values and a handler bag, and you write
220
+ * a `useAnimatedStyle` block with whatever composition you need. The hook's
221
+ * JSDoc shows the clamped-max halo pattern in full.
222
+ */
223
+ export interface GestureSubStates<C> {
224
+ pressed?: AnimateStyle<C>
225
+ focused?: AnimateStyle<C>
226
+ focusVisible?: AnimateStyle<C>
227
+ hovered?: AnimateStyle<C>
228
+ }
229
+
230
+ /**
231
+ * Controller returned by `useVariants`. The `current` shared state is read
232
+ * via `controller` prop on a Motion primitive; `transitionTo` drives the
233
+ * controller from JS code (event handlers, async chains, etc.).
234
+ */
235
+ export interface VariantController<K extends string = string> {
236
+ current: K
237
+ // Method-shorthand on purpose: TS treats parameters as bivariant so a
238
+ // `VariantController<'open' | 'closed'>` is assignable to the wider
239
+ // `VariantController<string>` that the `controller` prop is typed against.
240
+ transitionTo(next: K): void
241
+ /** @internal — subscription used by Motion primitives to re-render. */
242
+ subscribe(listener: (next: K) => void): () => void
243
+ }
244
+
245
+ /**
246
+ * Props injected onto every Motion primitive.
247
+ *
248
+ * The second type parameter `V` is the concrete `variants` map. It is inferred
249
+ * from the `variants` prop at each JSX use (see `MotionComponent`), which is
250
+ * what lets `animate` narrow to the variant key union and reject typos. When
251
+ * no `variants` prop is passed, `V` falls back to `VariantsMap<C>` — whose key
252
+ * type is the open `string`, so `animate` still accepts any string and nothing
253
+ * regresses for the variant-less case.
254
+ */
255
+ export interface MotionProps<C, V extends VariantsMap<C> = VariantsMap<C>> {
256
+ /**
257
+ * Initial values applied on mount. Read once on mount and intentionally
258
+ * non-reactive — to reset after a state change, change the component `key`,
259
+ * remount via `<Presence>`, or drive the value through a controller.
260
+ *
261
+ * Pass `false` to skip the initial-mount animation entirely.
262
+ */
263
+ initial?: AnimateStyle<C> | false
264
+ /**
265
+ * The animation target. A style object, a variant key (when `variants` is
266
+ * supplied), or an array of sequence steps. When `variants` is set, the
267
+ * string form is narrowed to the map's keys, so a key typo is a compile
268
+ * error and the keys autocomplete — no `as const` required.
269
+ */
270
+ animate?: AnimateStyle<C> | (keyof V & string)
271
+ /**
272
+ * Values applied while the component exits via `<Presence>`.
273
+ */
274
+ exit?: AnimateStyle<C>
275
+ /**
276
+ * Named animation states. With `variants` set, `animate` accepts a key from
277
+ * this map.
278
+ */
279
+ variants?: V
280
+ /**
281
+ * Imperative controller from `useVariants(...)`. When supplied, `animate`
282
+ * is read from `controller.current` and re-applied whenever the controller
283
+ * transitions. `animate` and `controller` should not both be set.
284
+ */
285
+ controller?: VariantController<keyof V & string>
286
+ /**
287
+ * Gesture-driven sub-states (`pressed`, `focused`, `focusVisible`,
288
+ * `hovered`). When omitted, no handlers are mounted on the underlying
289
+ * component. Each declared sub-state animates as an independent layer
290
+ * fading in/out over the base `animate` target — see `GestureSubStates`
291
+ * for the composition model and per-layer transition wiring.
292
+ */
293
+ gesture?: GestureSubStates<C>
294
+ /**
295
+ * Per-property or top-level transition config. Per-property entries take
296
+ * precedence over the top-level transition.
297
+ */
298
+ transition?: Transition<AnimateStyle<C>>
299
+ /**
300
+ * Auto-layout animation. When the component's position or size changes
301
+ * because of a parent layout change (a flex sibling growing, a list
302
+ * reordering, a column toggling its width), interpolate between the old
303
+ * and new layout instead of snapping.
304
+ *
305
+ * - `true` — animate with the library's default spring.
306
+ * - `TransitionConfig` — spring (react-spring vocab) or timing config; the
307
+ * resolver bridges to Reanimated's `LinearTransition` builder.
308
+ * - omitted / `false` — no layout animation (default).
309
+ *
310
+ * Only `'spring'` / `'timing'` / `'no-animation'` map to layout transitions
311
+ * — decay is downgraded to spring (no clear target). Reduced motion gates
312
+ * the prop the same way it gates `animate`.
313
+ *
314
+ * `layoutId` (below) is a related but distinct mechanism for shared
315
+ * element transitions across screens — `layout` animates this element's
316
+ * own layout changes, `layoutId` animates from a different element's
317
+ * last measured rect to this element's current rect.
318
+ */
319
+ layout?: boolean | TransitionConfig
320
+ /**
321
+ * Shared-element transition id. When a Motion primitive with `layoutId`
322
+ * unmounts, its last on-screen rect is recorded under that id; the next
323
+ * mount of any Motion primitive with the same id animates from the
324
+ * recorded rect to its natural position via a FLIP transform stack.
325
+ *
326
+ * Reanimated 4 removed the `sharedTransitionTag` API — `layoutId` is the
327
+ * Inertia-side measure-based replacement. Rects are recorded in
328
+ * parent-relative coordinates (from `onLayout`), which composes when the
329
+ * source and target screens share an outer content container (the common
330
+ * stack-navigator case); nested-parent layouts need the v2
331
+ * window-coordinate path.
332
+ *
333
+ * The same `transition` prop drives the FLIP animation (spring by
334
+ * default; `'timing'` honored; `'decay'` downgrades to spring; reduced
335
+ * motion skips the transition). Out of scope for the first iteration:
336
+ * style-prop interpolation (border radius, colors, etc.) — only the
337
+ * rect-to-rect transform is animated. Two simultaneously-mounted
338
+ * primitives sharing the same `layoutId` are undefined behavior; pick a
339
+ * primitive per id at a time.
340
+ */
341
+ layoutId?: string
342
+ /**
343
+ * Fired once per logical animation completion. See `AnimationCallbackInfo`
344
+ * for the payload shape — transform parents fire once, not per axis.
345
+ */
346
+ onAnimationEnd?: (info: AnimationCallbackInfo<AnimateStyle<C>>) => void
347
+ }
348
+
349
+ /**
350
+ * Props of a Motion primitive for a given underlying component `C` and a
351
+ * concrete variants map `V`: the component's own props (minus `style`, which
352
+ * we replace with an animated style) intersected with the Motion props.
353
+ */
354
+ export type MotionComponentProps<
355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
356
+ C extends ComponentType<any>,
357
+ V extends VariantsMap<ComponentProps<C>> = VariantsMap<ComponentProps<C>>,
358
+ > = Omit<ComponentProps<C>, 'style'> &
359
+ MotionProps<ComponentProps<C>, V> & {
360
+ style?: ComponentProps<C>['style']
361
+ ref?: Ref<unknown>
362
+ }
363
+
364
+ /**
365
+ * The component type produced by `createMotionComponent`.
366
+ *
367
+ * It is a **generic call signature**, not a plain `ComponentType`: the variant
368
+ * map `V` is inferred from the `variants` prop at each JSX use. That inference
369
+ * is what narrows `animate`'s string form to the variant keys, so
370
+ * `<Motion.View variants={{ open, closed }} animate="opne" />` is a compile
371
+ * error and `open` / `closed` autocomplete. With no `variants` prop, `V` falls
372
+ * back to the open `VariantsMap`, so `animate` still accepts any string and the
373
+ * variant-less call site is unchanged.
374
+ */
375
+ export interface MotionComponent<
376
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
377
+ C extends ComponentType<any>,
378
+ > {
379
+ <V extends VariantsMap<ComponentProps<C>> = VariantsMap<ComponentProps<C>>>(
380
+ props: MotionComponentProps<C, V>,
381
+ ): ReactElement | null
382
+ displayName?: string
383
+ }
@@ -0,0 +1,26 @@
1
+ export { useAnimation } from './useAnimation'
2
+ export { useBooleanSpring } from './useBooleanSpring'
3
+ export {
4
+ useColorTransition,
5
+ type ColorStyleKey,
6
+ type UseColorTransitionOptions,
7
+ } from './useColorTransition'
8
+ export {
9
+ useGesture,
10
+ type UseGestureHandlers,
11
+ type UseGestureResult,
12
+ } from './useGesture'
13
+ export { useMotionValue } from './useMotionValue'
14
+ export { useSpring } from './useSpring'
15
+ export {
16
+ useTransform,
17
+ type ExtrapolationMode,
18
+ type UseTransformOptions,
19
+ } from './useTransform'
20
+ export { useScroll, type UseScrollResult } from './useScroll'
21
+ export {
22
+ useShadow,
23
+ type ShadowConfig,
24
+ type UseShadowOptions,
25
+ } from './useShadow'
26
+ export { useVariants } from './useVariants'
@@ -0,0 +1,69 @@
1
+ import { useEffect } from 'react'
2
+ import { useSharedValue, type SharedValue } from 'react-native-reanimated'
3
+ import { useShouldReduceMotion } from '../config'
4
+ import { resolveTransition, stableSig } from '../transitions'
5
+ import { type TransitionConfig } from '../types'
6
+
7
+ /**
8
+ * Drive a `SharedValue<number>` toward `target` with **any** transition shape
9
+ * — spring, timing, decay, or no-animation. The general-purpose value-layer
10
+ * hook: reach for it when you need raw `useSharedValue + useEffect + withX`
11
+ * outside the declarative `animate` flow.
12
+ *
13
+ * Re-runs whenever `target` changes shape (`target` is in the dep array) or
14
+ * the transition signature changes (kept stable via JSON-style hashing).
15
+ * Reduced motion (via `<MotionConfig reducedMotion>`) collapses the
16
+ * transition to `no-animation` so the value snaps instead of interpolating.
17
+ *
18
+ * **Spring shorthand.** Prefer [`useSpring`](./useSpring) when you only want
19
+ * spring physics — it accepts the same `tension`/`friction`/`mass` config and
20
+ * also supports a `SharedValue<number>` as the target (UI-thread reactive
21
+ * source). `useAnimation` is JS-thread-driven only.
22
+ *
23
+ * **Loops.** Repeat is part of `TransitionConfig` and flows through
24
+ * untouched — `useAnimation(1, { type: 'timing', duration: 1800, repeat: {
25
+ * count: 'infinite', alternate: false } })` produces an indeterminate-style
26
+ * progress driver.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * // Toggle progress (Switch / Checkbox / Radio).
31
+ * const progress = useAnimation(isChecked ? 1 : 0, {
32
+ * type: 'spring',
33
+ * tension: 380,
34
+ * friction: 33,
35
+ * })
36
+ *
37
+ * // Float a TextField label when the value becomes non-empty.
38
+ * const floated = useAnimation(hasValue ? 1 : 0, {
39
+ * type: 'timing',
40
+ * duration: 150,
41
+ * })
42
+ *
43
+ * // Indeterminate progress slider (loops forever, snaps back).
44
+ * const slide = useAnimation(1, {
45
+ * type: 'timing',
46
+ * duration: 1800,
47
+ * repeat: { count: 'infinite', alternate: false },
48
+ * })
49
+ * ```
50
+ */
51
+ export function useAnimation(
52
+ target: number,
53
+ transition?: TransitionConfig,
54
+ ): SharedValue<number> {
55
+ const output = useSharedValue<number>(target)
56
+ const shouldReduceMotion = useShouldReduceMotion()
57
+ const cfgSig = stableSig(transition)
58
+
59
+ useEffect(() => {
60
+ const cfg = shouldReduceMotion
61
+ ? ({ type: 'no-animation' } as const)
62
+ : (transition ?? ({ type: 'spring' } as const))
63
+ output.value = resolveTransition(cfg, target) as never
64
+ // `output` is identity-stable per hook instance.
65
+ // eslint-disable-next-line react-hooks/exhaustive-deps
66
+ }, [target, cfgSig, shouldReduceMotion])
67
+
68
+ return output
69
+ }
@@ -0,0 +1,33 @@
1
+ import { type SharedValue } from 'react-native-reanimated'
2
+ import { useSpring } from './useSpring'
3
+ import { type SpringTransition } from '../types'
4
+
5
+ /**
6
+ * Toggle a 0↔1 progress value with a spring whenever `active` flips.
7
+ *
8
+ * This is the recurring shape behind checkbox checks, accordion expansions,
9
+ * drawer open/closed states, focus rings, and every other binary UI flip
10
+ * that wants spring physics rather than a hard cut. The returned shared
11
+ * value sits at `0` when `active` is `false` and animates toward `1` when
12
+ * `active` flips to `true` (and back again on the reverse flip). Feed it to
13
+ * a `useTransform`, `useShadow`, or a hand-rolled `useAnimatedStyle` to
14
+ * drive whatever the boolean controls visually.
15
+ *
16
+ * ```tsx
17
+ * const progress = useBooleanSpring(isChecked)
18
+ * const indicatorStyle = useAnimatedStyle(() => ({
19
+ * opacity: progress.value,
20
+ * transform: [{ scale: progress.value }],
21
+ * }))
22
+ * ```
23
+ *
24
+ * The spring config follows the same react-spring vocabulary as the rest of
25
+ * the library (`tension` / `friction` / `mass`); omit it to take the
26
+ * library's defaults.
27
+ */
28
+ export function useBooleanSpring(
29
+ active: boolean,
30
+ springConfig?: SpringTransition,
31
+ ): SharedValue<number> {
32
+ return useSpring(active ? 1 : 0, springConfig)
33
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ interpolateColor,
3
+ useAnimatedStyle,
4
+ type SharedValue,
5
+ } from 'react-native-reanimated'
6
+
7
+ /**
8
+ * Color style keys understood by React Native that this hook can target.
9
+ * Sticks to the keys that exist on the v0.1 animatable surface so the
10
+ * returned style fragment is always a legal RN style.
11
+ */
12
+ export type ColorStyleKey =
13
+ | 'backgroundColor'
14
+ | 'color'
15
+ | 'borderColor'
16
+ | 'borderTopColor'
17
+ | 'borderRightColor'
18
+ | 'borderBottomColor'
19
+ | 'borderLeftColor'
20
+ | 'tintColor'
21
+ | 'shadowColor'
22
+
23
+ export interface UseColorTransitionOptions {
24
+ /**
25
+ * Which style slot the interpolated color is emitted under. Defaults to
26
+ * `backgroundColor` — the dominant case for state-layer haloes, card
27
+ * fills, and chip surfaces. Override for ring colors (`borderColor`),
28
+ * text colors (`color`), image tints (`tintColor`), etc.
29
+ */
30
+ key?: ColorStyleKey
31
+ }
32
+
33
+ /**
34
+ * Interpolate a single color channel between `from` and `to` as `progress`
35
+ * moves 0→1, returning an animated style fragment that can be spread onto
36
+ * any Reanimated-aware view.
37
+ *
38
+ * ```tsx
39
+ * const progress = useBooleanSpring(isPressed)
40
+ * const fillStyle = useColorTransition(progress, [colors.surface, colors.pressed])
41
+ * const ringStyle = useColorTransition(progress, [colors.outline, colors.primary], {
42
+ * key: 'borderColor',
43
+ * })
44
+ *
45
+ * return <Motion.View style={[styles.chip, fillStyle, ringStyle]} />
46
+ * ```
47
+ *
48
+ * This is a pure interpolator: it does not animate on its own. Drive
49
+ * `progress` upstream with a `useSpring`, `useBooleanSpring`, gesture
50
+ * progress, or scroll-derived `useTransform`. Values outside `[0, 1]`
51
+ * clamp. For a raw `SharedValue<string>` (e.g. to feed a gradient or
52
+ * compose into a hand-rolled `useAnimatedStyle`), use `useTransform`
53
+ * directly with a color output range.
54
+ */
55
+ export function useColorTransition(
56
+ progress: SharedValue<number>,
57
+ range: readonly [string, string],
58
+ options?: UseColorTransitionOptions,
59
+ ): ReturnType<typeof useAnimatedStyle> {
60
+ // Resolve the slot key once on the JS thread so the worklet body
61
+ // consumes a single string literal — consistent with the JS-thread
62
+ // resolver principle that keeps `Object.keys`-style walks off the UI
63
+ // thread (see CLAUDE.md design principle 8).
64
+ const key = options?.key ?? 'backgroundColor'
65
+ const from = range[0]
66
+ const to = range[1]
67
+
68
+ return useAnimatedStyle(() => {
69
+ 'worklet'
70
+ return { [key]: interpolateColor(progress.value, [0, 1], [from, to]) }
71
+ })
72
+ }