@rootnative/inertia 0.0.7 → 0.0.8

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 (55) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +1 -1
  3. package/dist/chunk-24NUL23E.js +8 -0
  4. package/dist/chunk-4XBX7KOD.js +8 -0
  5. package/dist/{chunk-OBRGJAST.js → chunk-6AN5X3YE.js} +3 -3
  6. package/dist/{chunk-WGCF3WQG.js → chunk-6NOCY764.js} +2 -2
  7. package/dist/{chunk-KBFN4TRO.mjs → chunk-6YIUETBF.mjs} +1 -1
  8. package/dist/{chunk-6V7MOBXO.js → chunk-E3ALFSH2.js} +64 -33
  9. package/dist/{chunk-ZOBOKLAS.mjs → chunk-EN4PN7X3.mjs} +1 -1
  10. package/dist/{chunk-RUIOM2YZ.mjs → chunk-GSUC4HWP.mjs} +1 -1
  11. package/dist/{chunk-QJYFTTM2.js → chunk-IHVMNOS4.js} +2 -2
  12. package/dist/{chunk-HPJMIBBK.mjs → chunk-IX6SEOSK.mjs} +41 -12
  13. package/dist/{chunk-52QSN2VQ.mjs → chunk-JBK5ZLMH.mjs} +1 -1
  14. package/dist/{chunk-OW5XTGVN.mjs → chunk-L4JFECXU.mjs} +2 -2
  15. package/dist/{chunk-VSK2E35J.mjs → chunk-M4766VUV.mjs} +1 -1
  16. package/dist/{chunk-Z3HCJ43H.mjs → chunk-MFAFB4K7.mjs} +1 -1
  17. package/dist/{chunk-4NAMD62I.js → chunk-MUT6BTZS.js} +7 -7
  18. package/dist/{chunk-WMLS4TMX.mjs → chunk-QNLSCOR5.mjs} +1 -1
  19. package/dist/{chunk-REYL77RE.mjs → chunk-W7NTRSPD.mjs} +17 -10
  20. package/dist/{chunk-PM6CVGXJ.js → chunk-WNVHPMBI.js} +16 -8
  21. package/dist/chunk-Z5USXWTE.js +8 -0
  22. package/dist/{chunk-QYR66E2G.js → chunk-ZBHQPVWE.js} +2 -2
  23. package/dist/gestureLayer/index.js +10 -10
  24. package/dist/gestureLayer/index.mjs +3 -3
  25. package/dist/index.d.mts +73 -2
  26. package/dist/index.d.ts +73 -2
  27. package/dist/index.js +64 -52
  28. package/dist/index.mjs +19 -19
  29. package/dist/motion/FlatList.js +5 -5
  30. package/dist/motion/FlatList.mjs +4 -4
  31. package/dist/motion/Image.js +5 -5
  32. package/dist/motion/Image.mjs +4 -4
  33. package/dist/motion/Pressable.js +5 -5
  34. package/dist/motion/Pressable.mjs +4 -4
  35. package/dist/motion/ScrollView.js +5 -5
  36. package/dist/motion/ScrollView.mjs +4 -4
  37. package/dist/motion/Text.js +5 -5
  38. package/dist/motion/Text.mjs +4 -4
  39. package/dist/motion/View.js +5 -5
  40. package/dist/motion/View.mjs +4 -4
  41. package/dist/touch/index.js +3 -3
  42. package/dist/touch/index.mjs +1 -1
  43. package/llms.txt +18 -1
  44. package/package.json +1 -1
  45. package/src/index.ts +4 -0
  46. package/src/motion/createMotionComponent.tsx +45 -10
  47. package/src/stagger/Stagger.tsx +75 -0
  48. package/src/stagger/StaggerContext.ts +25 -0
  49. package/src/stagger/index.ts +2 -0
  50. package/src/transitions/index.ts +6 -1
  51. package/src/transitions/resolve.ts +7 -1
  52. package/src/transitions/runtime.ts +22 -7
  53. package/dist/chunk-NPJ46457.js +0 -8
  54. package/dist/chunk-YUTS6JRU.js +0 -8
  55. package/dist/chunk-ZSOCRHU3.js +0 -8
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkYUTS6JRU_js = require('../chunk-YUTS6JRU.js');
4
- require('../chunk-6V7MOBXO.js');
5
- require('../chunk-OBRGJAST.js');
6
- require('../chunk-PM6CVGXJ.js');
3
+ var chunkZ5USXWTE_js = require('../chunk-Z5USXWTE.js');
4
+ require('../chunk-E3ALFSH2.js');
5
+ require('../chunk-6AN5X3YE.js');
6
+ require('../chunk-WNVHPMBI.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionImage", {
11
11
  enumerable: true,
12
- get: function () { return chunkYUTS6JRU_js.MotionImage; }
12
+ get: function () { return chunkZ5USXWTE_js.MotionImage; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionImage } from '../chunk-KBFN4TRO.mjs';
2
- import '../chunk-HPJMIBBK.mjs';
3
- import '../chunk-Z3HCJ43H.mjs';
4
- import '../chunk-REYL77RE.mjs';
1
+ export { MotionImage } from '../chunk-6YIUETBF.mjs';
2
+ import '../chunk-IX6SEOSK.mjs';
3
+ import '../chunk-MFAFB4K7.mjs';
4
+ import '../chunk-W7NTRSPD.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkWGCF3WQG_js = require('../chunk-WGCF3WQG.js');
4
- require('../chunk-6V7MOBXO.js');
5
- require('../chunk-OBRGJAST.js');
6
- require('../chunk-PM6CVGXJ.js');
3
+ var chunk6NOCY764_js = require('../chunk-6NOCY764.js');
4
+ require('../chunk-E3ALFSH2.js');
5
+ require('../chunk-6AN5X3YE.js');
6
+ require('../chunk-WNVHPMBI.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionPressable", {
11
11
  enumerable: true,
12
- get: function () { return chunkWGCF3WQG_js.MotionPressable; }
12
+ get: function () { return chunk6NOCY764_js.MotionPressable; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionPressable } from '../chunk-52QSN2VQ.mjs';
2
- import '../chunk-HPJMIBBK.mjs';
3
- import '../chunk-Z3HCJ43H.mjs';
4
- import '../chunk-REYL77RE.mjs';
1
+ export { MotionPressable } from '../chunk-JBK5ZLMH.mjs';
2
+ import '../chunk-IX6SEOSK.mjs';
3
+ import '../chunk-MFAFB4K7.mjs';
4
+ import '../chunk-W7NTRSPD.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkQYR66E2G_js = require('../chunk-QYR66E2G.js');
4
- require('../chunk-6V7MOBXO.js');
5
- require('../chunk-OBRGJAST.js');
6
- require('../chunk-PM6CVGXJ.js');
3
+ var chunkZBHQPVWE_js = require('../chunk-ZBHQPVWE.js');
4
+ require('../chunk-E3ALFSH2.js');
5
+ require('../chunk-6AN5X3YE.js');
6
+ require('../chunk-WNVHPMBI.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionScrollView", {
11
11
  enumerable: true,
12
- get: function () { return chunkQYR66E2G_js.MotionScrollView; }
12
+ get: function () { return chunkZBHQPVWE_js.MotionScrollView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionScrollView } from '../chunk-VSK2E35J.mjs';
2
- import '../chunk-HPJMIBBK.mjs';
3
- import '../chunk-Z3HCJ43H.mjs';
4
- import '../chunk-REYL77RE.mjs';
1
+ export { MotionScrollView } from '../chunk-M4766VUV.mjs';
2
+ import '../chunk-IX6SEOSK.mjs';
3
+ import '../chunk-MFAFB4K7.mjs';
4
+ import '../chunk-W7NTRSPD.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkZSOCRHU3_js = require('../chunk-ZSOCRHU3.js');
4
- require('../chunk-6V7MOBXO.js');
5
- require('../chunk-OBRGJAST.js');
6
- require('../chunk-PM6CVGXJ.js');
3
+ var chunk4XBX7KOD_js = require('../chunk-4XBX7KOD.js');
4
+ require('../chunk-E3ALFSH2.js');
5
+ require('../chunk-6AN5X3YE.js');
6
+ require('../chunk-WNVHPMBI.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionText", {
11
11
  enumerable: true,
12
- get: function () { return chunkZSOCRHU3_js.MotionText; }
12
+ get: function () { return chunk4XBX7KOD_js.MotionText; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionText } from '../chunk-WMLS4TMX.mjs';
2
- import '../chunk-HPJMIBBK.mjs';
3
- import '../chunk-Z3HCJ43H.mjs';
4
- import '../chunk-REYL77RE.mjs';
1
+ export { MotionText } from '../chunk-QNLSCOR5.mjs';
2
+ import '../chunk-IX6SEOSK.mjs';
3
+ import '../chunk-MFAFB4K7.mjs';
4
+ import '../chunk-W7NTRSPD.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkNPJ46457_js = require('../chunk-NPJ46457.js');
4
- require('../chunk-6V7MOBXO.js');
5
- require('../chunk-OBRGJAST.js');
6
- require('../chunk-PM6CVGXJ.js');
3
+ var chunk24NUL23E_js = require('../chunk-24NUL23E.js');
4
+ require('../chunk-E3ALFSH2.js');
5
+ require('../chunk-6AN5X3YE.js');
6
+ require('../chunk-WNVHPMBI.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionView", {
11
11
  enumerable: true,
12
- get: function () { return chunkNPJ46457_js.MotionView; }
12
+ get: function () { return chunk24NUL23E_js.MotionView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionView } from '../chunk-RUIOM2YZ.mjs';
2
- import '../chunk-HPJMIBBK.mjs';
3
- import '../chunk-Z3HCJ43H.mjs';
4
- import '../chunk-REYL77RE.mjs';
1
+ export { MotionView } from '../chunk-GSUC4HWP.mjs';
2
+ import '../chunk-IX6SEOSK.mjs';
3
+ import '../chunk-MFAFB4K7.mjs';
4
+ import '../chunk-W7NTRSPD.mjs';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkPM6CVGXJ_js = require('../chunk-PM6CVGXJ.js');
3
+ var chunkWNVHPMBI_js = require('../chunk-WNVHPMBI.js');
4
4
  var react = require('react');
5
5
  var reactNative = require('react-native');
6
6
  var reactNativeReanimated = require('react-native-reanimated');
@@ -48,14 +48,14 @@ function useTouchDrag(options = {}) {
48
48
  if (result) {
49
49
  if (result.x && lockX) {
50
50
  const toX = "to" in result.x ? result.x.to : x;
51
- dragX.value = chunkPM6CVGXJ_js.buildReleaseAnimation(
51
+ dragX.value = chunkWNVHPMBI_js.buildReleaseAnimation(
52
52
  result.x,
53
53
  toX
54
54
  );
55
55
  }
56
56
  if (result.y && lockY) {
57
57
  const toY = "to" in result.y ? result.y.to : y;
58
- dragY.value = chunkPM6CVGXJ_js.buildReleaseAnimation(
58
+ dragY.value = chunkWNVHPMBI_js.buildReleaseAnimation(
59
59
  result.y,
60
60
  toY
61
61
  );
@@ -1,4 +1,4 @@
1
- import { buildReleaseAnimation } from '../chunk-REYL77RE.mjs';
1
+ import { buildReleaseAnimation } from '../chunk-W7NTRSPD.mjs';
2
2
  import { useMemo } from 'react';
3
3
  import { PanResponder } from 'react-native';
4
4
  import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
package/llms.txt CHANGED
@@ -10,12 +10,28 @@ pnpm add @rootnative/inertia react-native-reanimated
10
10
 
11
11
  Enable the Reanimated Babel plugin per its install guide.
12
12
 
13
+ ## Which package has what
14
+
15
+ The core package does not contain the gesture hooks, the gradient primitive, or the SVG primitives. Each is a separate install. `useSwipe` and `useDrag` are **not** in core — a swipe deck needs `@rootnative/inertia-gestures`.
16
+
17
+ | You want | Package | Extra peer dependency |
18
+ | --- | --- | --- |
19
+ | `Motion.*`, `Presence`, `Stagger`, `MotionConfig`, every value-layer hook | `@rootnative/inertia` | — (core, always required) |
20
+ | `useDrag`, `useSwipe`, `usePan` | `@rootnative/inertia-gestures` | `react-native-gesture-handler` |
21
+ | Animatable linear gradients | `@rootnative/inertia-gradients` | `expo-linear-gradient` |
22
+ | Animatable SVG paths and shapes | `@rootnative/inertia-svg` | `react-native-svg` |
23
+
24
+ `useTouchDrag` is the exception: a drag hook in **core**, under the `@rootnative/inertia/touch` subpath, because PanResponder is built into React Native. Use it for drag without `react-native-gesture-handler`, or when the drag needs keyboard accessibility.
25
+
26
+ All four packages ship in **lockstep** — same version, released together. Install the same version of every `@rootnative/inertia*` package you use. Two things that mislead: the `@rootnative/core` and `@rootnative/components` packages of RootNative UI are a different library on a different version line, and `npm view` shows an `alpha` dist-tag on these packages that is **older** than `latest` (a prerelease leftover — follow `latest`).
27
+
13
28
  ## Imports
14
29
 
15
30
  ```ts
16
31
  import {
17
32
  Motion,
18
33
  Presence,
34
+ Stagger,
19
35
  MotionConfig,
20
36
  useGesture,
21
37
  useVariants,
@@ -50,6 +66,7 @@ import { MotionFlatList } from '@rootnative/inertia/flat-list'
50
66
 
51
67
  - `Motion.View` / `Motion.Text` / `Motion.Image` / `Motion.Pressable` / `Motion.ScrollView` / `Motion.FlatList` — animatable primitives. Per-primitive style inference (no shared `ViewStyle & TextStyle & ImageStyle` fallback). `Motion.FlatList` is the **virtualized** animated scroller: it takes `useScroll`'s `onScroll` handler and keeps `data` / `renderItem` item-type inference, so a long list can virtualize and animate at once. Animation props apply to the scroll container; animate rows with a `Motion.*` inside `renderItem`.
52
68
  - `<Presence>` — mount / unmount transitions; children need explicit `key`s. Exiting children get `pointerEvents: 'none'` automatically.
69
+ - `<Stagger interval delay? from? enabled?>` — assigns each child a delay of `delay + position * interval` (ms), applied to the declarative animations (`initial` → `animate`, later `animate` changes) of every `Motion.*` primitive in that child's subtree. `from='last'` reverses the cascade; `enabled={false}` zeroes every delay (the "cascade in, snap out" switch). Composes with the child's own `transition.delay`; never delays `gesture` feedback, `<Presence>` exits, or reduced-motion snaps. Renders no host view. Custom animated components read the slot's delay with `useStaggerDelay()`.
53
70
  - `<MotionConfig reducedMotion="user" | "never" | "always" transitions={{ name: TransitionConfig }}>` — gates motion against the OS reduce-motion setting (default `"user"`) and registers named transitions for the subtree. A registered name is accepted anywhere a `TransitionConfig` is: the `transition` prop (top-level and per-property/per-layer), the `layout` prop, and the value-layer hooks (`transition="selection"`, `useBooleanSpring(checked, 'selection')`). Nested providers merge (child overrides per name); unknown names warn in dev and fall back to the default spring. Names are consumer vocabulary — no presets ship with the library. Optional compile-time narrowing via `declare module '@rootnative/inertia' { interface RegisteredTransitions { ... } }`. Custom components join the registry with `useNamedTransitions()` + `resolveNamedTransition(input, registry)`.
54
71
  - `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).
55
72
  - `useGestureLayer(states, options?)` — opt-in helper at `@rootnative/inertia/gesture-layer` for the **strongest-active-layer-wins** composition (MD3 state-layer haloes, iOS-translucent overlays). Supply per-state target maps (`rest` / `hovered` / `focused` / `focusVisible` / `pressed` / `disabled`); the hook owns the worklet. Numeric keys compose via clamped-max with `rest` as the floor; color keys compose via priority cascade with `interpolateColor`; `disabled` (JS-side flag) overrides every gesture layer. Returns `{ style, handlers, states }` — `states` is the per-state 0↔1 progress shared values (`hovered` / `focused` / `focusVisible` / `pressed` / `disabled`) behind the composed style, for deriving extra styles (e.g. `useShadow({ progress: states.hovered })`) from the same gesture wiring. Reach for plain `useGesture` for additive blends or per-key custom rules.
@@ -153,7 +170,7 @@ Auto-layout transitions ship via the `layout` prop (`true` / `TransitionConfig`)
153
170
 
154
171
  - `@rootnative/inertia-gradients` — `MotionLinearGradient` over `expo-linear-gradient`. Animatable: `colors`, `start`, `end`, `locations`.
155
172
  - `@rootnative/inertia-svg` — animatable SVG primitives over `react-native-svg`, all under the `MotionSvg` namespace (`MotionSvg.Path` / `.Circle` / `.Rect` / `.Line`) or as direct imports. `MotionPath` animates `d` (path morphing on structurally-compatible paths — same command sequence after implicit-repeat expansion; remount with `key` to switch shape), `fill`, `stroke`, `strokeWidth`, opacities, `strokeDashoffset`. `MotionCircle` / `MotionRect` / `MotionLine` animate their geometry props, paints, and `strokeDasharray` (element-wise; array length locked at mount). `createMotionSvgComponent(Component, { animatableProps, colorProps, arrayProps })` — the factory behind the prebuilt shapes — wraps any other `react-native-svg` element with the same `initial` / `animate` / `transition` surface; `transition` accepts named transitions from the nearest `<MotionConfig transitions>` (top-level and per-property).
156
- - `@rootnative/inertia-gestures` — `useDrag`, `useSwipe`, `usePan` over `react-native-gesture-handler`. `useDrag` accepts an `onRelease` worklet that returns per-axis Inertia release transitions (snap-to-tick spring, decay with bounds, etc.) — the release velocity stays on the UI thread, no JS round-trip.
173
+ - `@rootnative/inertia-gestures` — `useDrag`, `useSwipe`, `usePan` over `react-native-gesture-handler`. `useDrag` accepts an `onRelease` worklet that returns per-axis Inertia release transitions (snap-to-tick spring, decay with bounds, etc.) — the release velocity stays on the UI thread, no JS round-trip. `useSwipe`'s snap-back is configurable (`releaseTransition`, inline or a named transition, release velocity passed in), and a committed swipe can exit the screen instead of snapping back: `onCommit` (UI-thread worklet, returns per-axis exit transitions) + `onSwipeEnd` (JS thread, fires when the release animation settles) — the card-deck shape.
157
174
 
158
175
  ## Docs
159
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/inertia",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Declarative animation primitives for React Native, built on react-native-reanimated.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
package/src/index.ts CHANGED
@@ -33,12 +33,16 @@ export type {
33
33
  } from './config'
34
34
  export { Presence, usePresence } from './presence'
35
35
  export type { PresenceContextValue } from './presence'
36
+ export { Stagger, useStaggerDelay } from './stagger'
37
+ export type { StaggerProps } from './stagger'
36
38
  export {
39
+ applyDelay,
37
40
  buildReleaseAnimation,
38
41
  cubicBezier,
39
42
  resolveTransition,
40
43
  resolveAnimatableValue,
41
44
  ensureWorkletEasing,
45
+ type AnimationCallback,
42
46
  } from './transitions'
43
47
  // Seed value for any color shared value a custom animated component drives
44
48
  // through `resolveTransition`. Exported because the obvious choice —
@@ -43,7 +43,9 @@ import {
43
43
  useSharedLayout,
44
44
  } from '../layout'
45
45
  import { usePresence } from '../presence'
46
+ import { useStaggerDelay } from '../stagger'
46
47
  import {
48
+ applyDelay,
47
49
  isTopLevelTransition,
48
50
  resolveAnimatableValue,
49
51
  resolveTransition,
@@ -547,6 +549,13 @@ export function createMotionComponent<C extends ComponentType<any>>(
547
549
  // at app start — a runtime toggle takes effect on the next launch.
548
550
  const shouldReduceMotion = useShouldReduceMotion()
549
551
 
552
+ // Per-child delay assigned by the nearest <Stagger> ancestor; 0 outside
553
+ // one. Applied in the value-driving effect, not here: the effect's
554
+ // closure reads the value current at the render that starts an
555
+ // animation, and a delay change alone must not re-trigger anything —
556
+ // so it is deliberately absent from the effect's deps.
557
+ const staggerDelay = useStaggerDelay()
558
+
550
559
  // Pin the latest `onAnimationEnd` in a ref so the worklet callback always
551
560
  // dispatches against the current closure without re-resolving the
552
561
  // animation graph. Worklets can read refs via `runOnJS`.
@@ -962,6 +971,22 @@ export function createMotionComponent<C extends ComponentType<any>>(
962
971
  transition,
963
972
  )
964
973
 
974
+ // <Stagger> delay for this run, wrapped around each key's fully
975
+ // resolved animation via `applyDelay` — once per animation, never
976
+ // merged into the base config (a sequence applies a base `delay` per
977
+ // step, which would stretch the whole sequence by N × delay). Composes
978
+ // with a consumer's own `transition.delay`, which `resolveTransition`
979
+ // has already applied inside.
980
+ //
981
+ // Deliberately 0 while exiting (an exit delayed by list position holds
982
+ // <Presence>'s unmount hostage to the cascade) and under reduced
983
+ // motion (a snap must not be deferred — same contract as `delayOf`
984
+ // returning `undefined` for `no-animation`).
985
+ const runStagger =
986
+ shouldReduceMotion || isExiting || staggerDelay <= 0
987
+ ? 0
988
+ : staggerDelay
989
+
965
990
  // Count transform axes participating in this effect run so the factory
966
991
  // can coalesce their terminal callbacks into a single transform-group
967
992
  // event. `undefined` when no transform axis is animating, which lets
@@ -996,6 +1021,7 @@ export function createMotionComponent<C extends ComponentType<any>>(
996
1021
  boxShadowInsets,
997
1022
  shadowTarget,
998
1023
  cfg,
1024
+ runStagger,
999
1025
  makeKeyCallbackFactory(
1000
1026
  'boxShadow',
1001
1027
  sharedValues.boxShadow,
@@ -1051,12 +1077,15 @@ export function createMotionComponent<C extends ComponentType<any>>(
1051
1077
  // `'no-animation'` (and so under reduced motion) it is assigned
1052
1078
  // straight into the slot, and the next animation would start from a
1053
1079
  // value that can't be parsed.
1054
- sharedValues[key].value = resolveAnimatableValue(
1055
- COLOR_KEY_SET.has(key)
1056
- ? normalizeAnimatableColorTarget(target)
1057
- : target,
1058
- cfg,
1059
- factory,
1080
+ sharedValues[key].value = applyDelay(
1081
+ resolveAnimatableValue(
1082
+ COLOR_KEY_SET.has(key)
1083
+ ? normalizeAnimatableColorTarget(target)
1084
+ : target,
1085
+ cfg,
1086
+ factory,
1087
+ ),
1088
+ runStagger,
1060
1089
  ) as never
1061
1090
  }
1062
1091
 
@@ -1655,6 +1684,7 @@ function driveBoxShadow(
1655
1684
  insetSlot: SharedValue<boolean[] | null>,
1656
1685
  target: BoxShadowInput,
1657
1686
  cfg: TransitionConfig | undefined,
1687
+ staggerDelay: number,
1658
1688
  factory: CallbackFactory | undefined,
1659
1689
  ): void {
1660
1690
  const currentLayers = payloadToLayers(slot.value as BoxShadowPayload)
@@ -1666,10 +1696,15 @@ function driveBoxShadow(
1666
1696
  if (from.length !== currentLayers.length) slot.value = layersToPayload(from)
1667
1697
  // `resolveTransition` is typed for the scalar surface it was written for;
1668
1698
  // Reanimated itself accepts the structured target and recurses into it.
1669
- slot.value = resolveTransition(
1670
- cfg,
1671
- layersToPayload(to) as unknown as number,
1672
- factory?.('animation', undefined),
1699
+ // The stagger wrap sits outside, same as the scalar path — this key can't
1700
+ // be a sequence, but keeping the delay out of `cfg` keeps one rule.
1701
+ slot.value = applyDelay(
1702
+ resolveTransition(
1703
+ cfg,
1704
+ layersToPayload(to) as unknown as number,
1705
+ factory?.('animation', undefined),
1706
+ ),
1707
+ staggerDelay,
1673
1708
  ) as AnimatableSlotValue
1674
1709
  }
1675
1710
 
@@ -0,0 +1,75 @@
1
+ import { Children, isValidElement, type ReactNode } from 'react'
2
+ import { StaggerContext } from './StaggerContext'
3
+
4
+ export interface StaggerProps {
5
+ children?: ReactNode
6
+ /**
7
+ * Milliseconds between consecutive children. Child `i` (in render order)
8
+ * receives a delay of `delay + i * interval`.
9
+ */
10
+ interval: number
11
+ /**
12
+ * Base delay in milliseconds applied to every child before the interval,
13
+ * so the whole cascade can start late without pushing the spacing into
14
+ * each child. Defaults to `0`.
15
+ */
16
+ delay?: number
17
+ /**
18
+ * Which end of the child list starts the cascade. `'first'` (default)
19
+ * staggers top-down in render order; `'last'` reverses it, so the final
20
+ * child animates first.
21
+ */
22
+ from?: 'first' | 'last'
23
+ /**
24
+ * Turn the stagger on or off in one place. When `false`, every child gets
25
+ * a delay of `0` — the single-switch escape hatch for "cascade in, but
26
+ * not back out": pass `enabled={revealed}` and the hide direction snaps
27
+ * together. Defaults to `true`.
28
+ */
29
+ enabled?: boolean
30
+ }
31
+
32
+ /**
33
+ * Assign each child a stagger delay from its position, so a list entrance
34
+ * cascades without every child computing `index * ms` itself.
35
+ *
36
+ * The parent owns the timing: reordering, filtering, or reversing the list
37
+ * re-derives every delay from the new render order, and `enabled` turns the
38
+ * whole cascade off in one place. Each child slot gets its own provider, so
39
+ * a `Motion.*` primitive anywhere inside child `i`'s subtree inherits child
40
+ * `i`'s delay.
41
+ *
42
+ * The delay applies to the declarative animations of the `Motion.*`
43
+ * primitives underneath — the mount animation (`initial` → `animate`) and
44
+ * any later `animate` change. It deliberately does not delay `gesture`
45
+ * feedback, `<Presence>` exits, or reduced-motion snaps.
46
+ *
47
+ * `<Stagger>` renders no host view — only per-child context providers.
48
+ */
49
+ export function Stagger({
50
+ children,
51
+ interval,
52
+ delay = 0,
53
+ from = 'first',
54
+ enabled = true,
55
+ }: StaggerProps) {
56
+ // `Children.toArray` drops `null` / `undefined` / booleans, so conditional
57
+ // children don't leave holes in the cascade, and it assigns stable
58
+ // element keys that the providers below reuse.
59
+ const items = Children.toArray(children)
60
+ const count = items.length
61
+ return (
62
+ <>
63
+ {items.map((child, index) => {
64
+ const position = from === 'last' ? count - 1 - index : index
65
+ const childDelay = enabled ? delay + position * interval : 0
66
+ const key = isValidElement(child) ? (child.key ?? index) : index
67
+ return (
68
+ <StaggerContext.Provider key={key} value={childDelay}>
69
+ {child}
70
+ </StaggerContext.Provider>
71
+ )
72
+ })}
73
+ </>
74
+ )
75
+ }
@@ -0,0 +1,25 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ /**
4
+ * Per-child stagger delay in milliseconds, provided by `<Stagger>`. `0`
5
+ * outside a `<Stagger>` (or under a disabled one), which every consumer
6
+ * treats as "no stagger".
7
+ *
8
+ * Internal wiring — consumers use `<Stagger>`; custom animated components
9
+ * read the value through `useStaggerDelay()`.
10
+ */
11
+ export const StaggerContext = createContext(0)
12
+
13
+ /**
14
+ * The stagger delay (in milliseconds) assigned to this element's child slot
15
+ * by the nearest `<Stagger>` ancestor. `0` when there is none, when the
16
+ * stagger is disabled, or for the first child of a zero-`delay` stagger.
17
+ *
18
+ * The `Motion.*` primitives consume this automatically. Custom animated
19
+ * components built on `resolveTransition` / `resolveAnimatableValue` call
20
+ * this hook and add the returned value to their animation delay so they
21
+ * participate in a `<Stagger>` the same way the built-in primitives do.
22
+ */
23
+ export function useStaggerDelay(): number {
24
+ return useContext(StaggerContext)
25
+ }
@@ -0,0 +1,2 @@
1
+ export { Stagger, type StaggerProps } from './Stagger'
2
+ export { useStaggerDelay } from './StaggerContext'
@@ -1,4 +1,9 @@
1
- export { resolveTransition, type CallbackFactory } from './resolve'
1
+ export {
2
+ applyDelay,
3
+ resolveTransition,
4
+ type AnimationCallback,
5
+ type CallbackFactory,
6
+ } from './resolve'
2
7
  export { resolveAnimatableValue } from './resolveSequence'
3
8
  export { cubicBezier } from './cubicBezier'
4
9
  export { ensureWorkletEasing } from './easing'
@@ -116,7 +116,13 @@ export function applyRepeat(
116
116
  return withRepeat(animation as never, count, alternate)
117
117
  }
118
118
 
119
- function applyDelay(animation: unknown, delay: number | undefined) {
119
+ /**
120
+ * Wrap an animation in `withDelay`. A missing / zero / negative delay is a
121
+ * pass-through. Exported for the factory's stagger wrap, which must delay a
122
+ * fully-resolved animation exactly once — merging the delay into the base
123
+ * config instead would re-apply it per sequence step.
124
+ */
125
+ export function applyDelay(animation: unknown, delay: number | undefined) {
120
126
  if (!delay || delay <= 0) return animation
121
127
  return withDelay(delay, animation as never)
122
128
  }
@@ -5,6 +5,7 @@ import {
5
5
  withTiming,
6
6
  } from 'react-native-reanimated'
7
7
  import { springToReanimated } from './spring'
8
+ import { type AnimationCallback } from './resolve'
8
9
  import { type TransitionConfig } from '../types'
9
10
 
10
11
  const DEFAULT_TIMING_DURATION = 250
@@ -26,13 +27,23 @@ const DEFAULT_TIMING_DURATION = 250
26
27
  *
27
28
  * For decay transitions, `toValue` is ignored — decay decelerates from the
28
29
  * SV's current position via its own physics. Pass `0` if you don't have one.
30
+ *
31
+ * `callback`, when provided, fires once when the animation settles — the same
32
+ * `(finished) => void` shape Reanimated's `with*` factories accept. It runs on
33
+ * the UI thread, so bridge to JS with `runOnJS(...)` inside it. For
34
+ * `no-animation` the callback fires synchronously with `finished: true`, since
35
+ * a direct assignment has no settle point of its own.
29
36
  */
30
37
  export function buildReleaseAnimation(
31
38
  transition: TransitionConfig,
32
39
  toValue: number,
40
+ callback?: AnimationCallback,
33
41
  ): unknown {
34
42
  'worklet'
35
- if (transition.type === 'no-animation') return toValue
43
+ if (transition.type === 'no-animation') {
44
+ if (callback) callback(true, toValue)
45
+ return toValue
46
+ }
36
47
  if (transition.type === 'decay') {
37
48
  const cfg: {
38
49
  velocity: number
@@ -43,7 +54,7 @@ export function buildReleaseAnimation(
43
54
  cfg.deceleration = transition.deceleration
44
55
  }
45
56
  if (transition.clamp !== undefined) cfg.clamp = transition.clamp
46
- return withDecay(cfg)
57
+ return withDecay(cfg, callback as never)
47
58
  }
48
59
  if (transition.type === 'timing') {
49
60
  // Reanimated 4's `Easing.bezier(...)` returns an `EasingFunctionFactory`
@@ -54,10 +65,14 @@ export function buildReleaseAnimation(
54
65
  e && typeof e === 'object' && 'factory' in e
55
66
  ? e.factory()
56
67
  : (e ?? Easing.inOut(Easing.ease))
57
- return withTiming(toValue, {
58
- duration: transition.duration ?? DEFAULT_TIMING_DURATION,
59
- easing: easingFn,
60
- })
68
+ return withTiming(
69
+ toValue,
70
+ {
71
+ duration: transition.duration ?? DEFAULT_TIMING_DURATION,
72
+ easing: easingFn,
73
+ },
74
+ callback as never,
75
+ )
61
76
  }
62
- return withSpring(toValue, springToReanimated(transition))
77
+ return withSpring(toValue, springToReanimated(transition), callback as never)
63
78
  }
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6V7MOBXO_js = require('./chunk-6V7MOBXO.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionView = chunk6V7MOBXO_js.createMotionComponent(reactNative.View);
7
-
8
- exports.MotionView = MotionView;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6V7MOBXO_js = require('./chunk-6V7MOBXO.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionImage = chunk6V7MOBXO_js.createMotionComponent(reactNative.Image);
7
-
8
- exports.MotionImage = MotionImage;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6V7MOBXO_js = require('./chunk-6V7MOBXO.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionText = chunk6V7MOBXO_js.createMotionComponent(reactNative.Text);
7
-
8
- exports.MotionText = MotionText;