@rootnative/inertia 0.0.6 → 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.
- package/CHANGELOG.md +51 -1
- package/README.md +7 -1
- package/dist/chunk-24NUL23E.js +8 -0
- package/dist/chunk-4XBX7KOD.js +8 -0
- package/dist/{chunk-OBRGJAST.js → chunk-6AN5X3YE.js} +3 -3
- package/dist/{chunk-5MGWHOSV.js → chunk-6NOCY764.js} +2 -2
- package/dist/{chunk-PFPO7DX2.mjs → chunk-6YIUETBF.mjs} +1 -1
- package/dist/{chunk-767UKZXG.js → chunk-E3ALFSH2.js} +76 -33
- package/dist/chunk-EN4PN7X3.mjs +22 -0
- package/dist/{chunk-CMHVF6F4.mjs → chunk-GSUC4HWP.mjs} +1 -1
- package/dist/chunk-IHVMNOS4.js +28 -0
- package/dist/{chunk-2ICQLWH2.mjs → chunk-IX6SEOSK.mjs} +53 -12
- package/dist/{chunk-C3EDC5ZW.mjs → chunk-JBK5ZLMH.mjs} +1 -1
- package/dist/{chunk-OW5XTGVN.mjs → chunk-L4JFECXU.mjs} +2 -2
- package/dist/{chunk-BAAQI37F.mjs → chunk-M4766VUV.mjs} +1 -1
- package/dist/{chunk-Z3HCJ43H.mjs → chunk-MFAFB4K7.mjs} +1 -1
- package/dist/{chunk-4NAMD62I.js → chunk-MUT6BTZS.js} +7 -7
- package/dist/{chunk-F7LJX56B.mjs → chunk-QNLSCOR5.mjs} +1 -1
- package/dist/{chunk-REYL77RE.mjs → chunk-W7NTRSPD.mjs} +17 -10
- package/dist/{chunk-PM6CVGXJ.js → chunk-WNVHPMBI.js} +16 -8
- package/dist/chunk-Z5USXWTE.js +8 -0
- package/dist/{chunk-X7B5WR5A.js → chunk-ZBHQPVWE.js} +2 -2
- package/dist/gestureLayer/index.d.mts +2 -2
- package/dist/gestureLayer/index.d.ts +2 -2
- package/dist/gestureLayer/index.js +10 -10
- package/dist/gestureLayer/index.mjs +3 -3
- package/dist/index.d.mts +89 -10
- package/dist/index.d.ts +89 -10
- package/dist/index.js +67 -49
- package/dist/index.mjs +21 -18
- package/dist/motion/FlatList.d.mts +53 -0
- package/dist/motion/FlatList.d.ts +53 -0
- package/dist/motion/FlatList.js +13 -0
- package/dist/motion/FlatList.mjs +4 -0
- package/dist/motion/Image.d.mts +1 -1
- package/dist/motion/Image.d.ts +1 -1
- package/dist/motion/Image.js +5 -5
- package/dist/motion/Image.mjs +4 -4
- package/dist/motion/Pressable.d.mts +1 -1
- package/dist/motion/Pressable.d.ts +1 -1
- package/dist/motion/Pressable.js +5 -5
- package/dist/motion/Pressable.mjs +4 -4
- package/dist/motion/ScrollView.d.mts +1 -1
- package/dist/motion/ScrollView.d.ts +1 -1
- package/dist/motion/ScrollView.js +5 -5
- package/dist/motion/ScrollView.mjs +4 -4
- package/dist/motion/Text.d.mts +1 -1
- package/dist/motion/Text.d.ts +1 -1
- package/dist/motion/Text.js +5 -5
- package/dist/motion/Text.mjs +4 -4
- package/dist/motion/View.d.mts +1 -1
- package/dist/motion/View.d.ts +1 -1
- package/dist/motion/View.js +5 -5
- package/dist/motion/View.mjs +4 -4
- package/dist/touch/index.d.mts +1 -1
- package/dist/touch/index.d.ts +1 -1
- package/dist/touch/index.js +3 -3
- package/dist/touch/index.mjs +1 -1
- package/dist/{types-BQgLJeQG.d.mts → types-DyJpG64F.d.mts} +1 -1
- package/dist/{types-BQgLJeQG.d.ts → types-DyJpG64F.d.ts} +1 -1
- package/dist/{useGesture-DfaTx-3t.d.mts → useGesture-CnZQnYHH.d.mts} +1 -1
- package/dist/{useGesture-B0_CzOUz.d.ts → useGesture-DbH46EBp.d.ts} +1 -1
- package/jest-setup.js +7 -1
- package/llms.txt +25 -4
- package/package.json +8 -1
- package/src/index.ts +5 -0
- package/src/motion/FlatList.tsx +77 -0
- package/src/motion/createMotionComponent.tsx +68 -10
- package/src/motion/index.ts +3 -0
- package/src/stagger/Stagger.tsx +75 -0
- package/src/stagger/StaggerContext.ts +25 -0
- package/src/stagger/index.ts +2 -0
- package/src/transitions/index.ts +6 -1
- package/src/transitions/resolve.ts +7 -1
- package/src/transitions/runtime.ts +22 -7
- package/src/values/useScroll.ts +9 -5
- package/dist/chunk-FWWVHLXB.js +0 -8
- package/dist/chunk-K5SI6VXS.js +0 -8
- package/dist/chunk-STARIT6W.js +0 -8
package/dist/motion/View.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { MotionView } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
1
|
+
export { MotionView } from '../chunk-GSUC4HWP.mjs';
|
|
2
|
+
import '../chunk-IX6SEOSK.mjs';
|
|
3
|
+
import '../chunk-MFAFB4K7.mjs';
|
|
4
|
+
import '../chunk-W7NTRSPD.mjs';
|
package/dist/touch/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PanResponderInstance } from 'react-native';
|
|
2
2
|
import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
|
|
3
|
-
import { T as TransitionConfig } from '../types-
|
|
3
|
+
import { T as TransitionConfig } from '../types-DyJpG64F.mjs';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/touch/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PanResponderInstance } from 'react-native';
|
|
2
2
|
import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
|
|
3
|
-
import { T as TransitionConfig } from '../types-
|
|
3
|
+
import { T as TransitionConfig } from '../types-DyJpG64F.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/touch/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
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 =
|
|
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 =
|
|
58
|
+
dragY.value = chunkWNVHPMBI_js.buildReleaseAnimation(
|
|
59
59
|
result.y,
|
|
60
60
|
toY
|
|
61
61
|
);
|
package/dist/touch/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildReleaseAnimation } from '../chunk-
|
|
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';
|
|
@@ -456,4 +456,4 @@ interface MotionComponent<C extends ComponentType<any>> {
|
|
|
456
456
|
displayName?: string;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
export type { AnimatableValue as A, BoxShadowInput as B, DecayTransition as D, EasingInput as E, GestureLayerTransitions as G, MotionComponent as M, NamedTransitions as N, PerPropertyTransition as P, RegisteredTransitions as R, SpringTransition as S, TransitionConfig as T,
|
|
459
|
+
export type { AnimatableValue as A, BoxShadowInput as B, DecayTransition as D, EasingInput as E, GestureLayerTransitions as G, MotionComponent as M, NamedTransitions as N, PerPropertyTransition as P, RegisteredTransitions as R, SpringTransition as S, TransitionConfig as T, VariantsMap as V, MotionProps as a, TransitionInput as b, TransitionName as c, VariantController as d, AnimateStyle as e, AnimationCallbackInfo as f, EasingFunction as g, EasingFunctionFactory as h, GestureSubStates as i, NoAnimationTransition as j, RepeatConfig as k, SequenceStep as l, TimingTransition as m, Transition as n };
|
|
@@ -456,4 +456,4 @@ interface MotionComponent<C extends ComponentType<any>> {
|
|
|
456
456
|
displayName?: string;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
export type { AnimatableValue as A, BoxShadowInput as B, DecayTransition as D, EasingInput as E, GestureLayerTransitions as G, MotionComponent as M, NamedTransitions as N, PerPropertyTransition as P, RegisteredTransitions as R, SpringTransition as S, TransitionConfig as T,
|
|
459
|
+
export type { AnimatableValue as A, BoxShadowInput as B, DecayTransition as D, EasingInput as E, GestureLayerTransitions as G, MotionComponent as M, NamedTransitions as N, PerPropertyTransition as P, RegisteredTransitions as R, SpringTransition as S, TransitionConfig as T, VariantsMap as V, MotionProps as a, TransitionInput as b, TransitionName as c, VariantController as d, AnimateStyle as e, AnimationCallbackInfo as f, EasingFunction as g, EasingFunctionFactory as h, GestureSubStates as i, NoAnimationTransition as j, RepeatConfig as k, SequenceStep as l, TimingTransition as m, Transition as n };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SharedValue } from 'react-native-reanimated';
|
|
2
|
-
import {
|
|
2
|
+
import { b as TransitionInput, G as GestureLayerTransitions } from './types-DyJpG64F.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Handler bag returned by `useGesture`. Spread on a `Pressable` to drive the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SharedValue } from 'react-native-reanimated';
|
|
2
|
-
import {
|
|
2
|
+
import { b as TransitionInput, G as GestureLayerTransitions } from './types-DyJpG64F.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Handler bag returned by `useGesture`. Spread on a `Pressable` to drive the
|
package/jest-setup.js
CHANGED
|
@@ -45,7 +45,7 @@ jest.mock('react-native/Libraries/Text/Text', () => {
|
|
|
45
45
|
// run; targets snap in one step.
|
|
46
46
|
jest.mock('react-native-reanimated', () => {
|
|
47
47
|
const React = require('react')
|
|
48
|
-
const { Image, ScrollView, Text, View } = require('react-native')
|
|
48
|
+
const { FlatList, Image, ScrollView, Text, View } = require('react-native')
|
|
49
49
|
|
|
50
50
|
const wrap = (Component, displayName) => {
|
|
51
51
|
const Wrapped = React.forwardRef((props, ref) =>
|
|
@@ -59,6 +59,10 @@ jest.mock('react-native-reanimated', () => {
|
|
|
59
59
|
const AnimatedText = wrap(Text, 'Animated.Text')
|
|
60
60
|
const AnimatedImage = wrap(Image, 'Animated.Image')
|
|
61
61
|
const AnimatedScrollView = wrap(ScrollView, 'Animated.ScrollView')
|
|
62
|
+
// Reanimated's real `Animated.FlatList` also defaults `scrollEventThrottle`
|
|
63
|
+
// to 1 and injects a `CellRendererComponent`; neither is observable in this
|
|
64
|
+
// mock, so the wrapper is a plain pass-through like the others.
|
|
65
|
+
const AnimatedFlatList = wrap(FlatList, 'Animated.FlatList')
|
|
62
66
|
|
|
63
67
|
return {
|
|
64
68
|
__esModule: true,
|
|
@@ -67,6 +71,7 @@ jest.mock('react-native-reanimated', () => {
|
|
|
67
71
|
Text: AnimatedText,
|
|
68
72
|
Image: AnimatedImage,
|
|
69
73
|
ScrollView: AnimatedScrollView,
|
|
74
|
+
FlatList: AnimatedFlatList,
|
|
70
75
|
createAnimatedComponent: (c) =>
|
|
71
76
|
wrap(c, `Animated(${c.displayName ?? c.name ?? 'Component'})`),
|
|
72
77
|
},
|
|
@@ -74,6 +79,7 @@ jest.mock('react-native-reanimated', () => {
|
|
|
74
79
|
Text: AnimatedText,
|
|
75
80
|
Image: AnimatedImage,
|
|
76
81
|
ScrollView: AnimatedScrollView,
|
|
82
|
+
FlatList: AnimatedFlatList,
|
|
77
83
|
useSharedValue: (initial) => {
|
|
78
84
|
const ref = React.useRef(null)
|
|
79
85
|
if (ref.current === null) ref.current = { value: initial }
|
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,
|
|
@@ -34,18 +50,23 @@ import {
|
|
|
34
50
|
import { useGestureLayer } from '@rootnative/inertia/gesture-layer'
|
|
35
51
|
// Render-layer Reanimated interop for custom animated components:
|
|
36
52
|
import { Animated, useAnimatedStyle } from '@rootnative/inertia/reanimated'
|
|
37
|
-
// or for tree-shaking the primitives:
|
|
53
|
+
// or for tree-shaking the primitives. NOTE: these subpaths export a
|
|
54
|
+
// differently NAMED symbol — `MotionScrollView`, not `Motion.ScrollView`.
|
|
55
|
+
// `Motion` itself is only exported from the package root above, and there
|
|
56
|
+
// is no '@rootnative/inertia/motion' subpath.
|
|
38
57
|
import { MotionView } from '@rootnative/inertia/view'
|
|
39
58
|
import { MotionText } from '@rootnative/inertia/text'
|
|
40
59
|
import { MotionImage } from '@rootnative/inertia/image'
|
|
41
60
|
import { MotionPressable } from '@rootnative/inertia/pressable'
|
|
42
61
|
import { MotionScrollView } from '@rootnative/inertia/scroll-view'
|
|
62
|
+
import { MotionFlatList } from '@rootnative/inertia/flat-list'
|
|
43
63
|
```
|
|
44
64
|
|
|
45
65
|
## Public API
|
|
46
66
|
|
|
47
|
-
- `Motion.View` / `Motion.Text` / `Motion.Image` / `Motion.Pressable` / `Motion.ScrollView` — animatable primitives. Per-primitive style inference (no shared `ViewStyle & TextStyle & ImageStyle` fallback).
|
|
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`.
|
|
48
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()`.
|
|
49
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)`.
|
|
50
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).
|
|
51
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.
|
|
@@ -60,7 +81,7 @@ import { MotionScrollView } from '@rootnative/inertia/scroll-view'
|
|
|
60
81
|
- `useColorTransition(progress, [from, to], options?)` — pure value-layer interpolator for a single color channel, driven by a `SharedValue<number>` (0→1). Returns an animated style fragment with one color key (default `backgroundColor`; configurable via `options.key` to `color` / `borderColor` / `tintColor` / `shadowColor` / per-side border colors). For raw `SharedValue<string>` output, use `useTransform(progress, [0, 1], [from, to])` instead.
|
|
61
82
|
- `useColorCascade(rest, layers, options?)` — pure value-layer interpolator compositing a **priority-ordered** stack of color layers over a base `rest` color; each `layers` entry is `{ progress: SharedValue<number>, color: string }`, later entries win as their progress rises (equivalent to the nested `focus(error(hover(rest)))` `interpolateColor` chain). `options.key` reuses `ColorStyleKey` (default `backgroundColor`). Returns a spreadable animated style fragment; memoized on a colors+key signature. Color-only by design — cascade a numeric key separately via `useInterpolatedStyle` + `useTransform` max. `useColorTransition` stays the single-layer fast path.
|
|
62
83
|
- `useInterpolatedStyle(progress, map, options?)` — pure value-layer interpolator mapping one `SharedValue<number>` onto **N** style props at once, returned as a spreadable animated style fragment (the multi-key, style-fragment counterpart to `useTransform`'s output-range form). Numeric keys (`opacity` / `height` / `fontSize` / …) route through `interpolate`; transform keys (`translateX` / `scale` / `rotate` / …) lift into a `transform` array in map key-order (`rotate*` emit `'<n>deg'`); color keys (`backgroundColor` / `borderColor` / …) route through `interpolateColor` (a multi-stop `useColorTransition`). Stop types are compile-checked. `options.inputRange` (default `[0,1]` / evenly-spaced) and `options.extrapolate` (default `'clamp'`) apply to every key. Memoized on an order-preserving signature. For function-valued or multi-source composition, drop to `useTransform`'s worklet form or a hand-rolled `useAnimatedStyle`.
|
|
63
|
-
- `useScroll()` — returns `{ scrollX, scrollY, onScroll }` for use with `Motion.ScrollView`. Scroll events fire on the UI thread.
|
|
84
|
+
- `useScroll()` — returns `{ scrollX, scrollY, onScroll }` for use with `Motion.ScrollView` or `Motion.FlatList`. Scroll events fire on the UI thread. Set `scrollEventThrottle={16}` on a `Motion.ScrollView`; `Motion.FlatList` defaults it to 1.
|
|
64
85
|
- `createMotionComponent<C>(C)` — wrap any component with the same Motion prop surface, inferring style from `C`.
|
|
65
86
|
- `buildReleaseAnimation(transition, toValue)` — worklet-safe single-step animation builder (spring / timing / decay / no-animation). For assigning Inertia-resolved animations to shared values from inside a gesture worklet. Used internally by `@rootnative/inertia-gestures`'s `useDrag({ onRelease })`.
|
|
66
87
|
- `resolveTransition(config, toValue, callback?)` — JS-thread resolver: bakes a `TransitionConfig` + target into a Reanimated animation (`withSpring` / `withTiming` / `withDecay`, plus `delay` / `repeat` wrapping) ready to assign to a shared value. The building block for custom animated components that accept Inertia's `transition` shape; this is what the gradient and SVG adapter packages use.
|
|
@@ -149,7 +170,7 @@ Auto-layout transitions ship via the `layout` prop (`true` / `TransitionConfig`)
|
|
|
149
170
|
|
|
150
171
|
- `@rootnative/inertia-gradients` — `MotionLinearGradient` over `expo-linear-gradient`. Animatable: `colors`, `start`, `end`, `locations`.
|
|
151
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).
|
|
152
|
-
- `@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.
|
|
153
174
|
|
|
154
175
|
## Docs
|
|
155
176
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootnative/inertia",
|
|
3
|
-
"version": "0.0.
|
|
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",
|
|
@@ -72,6 +72,13 @@
|
|
|
72
72
|
"import": "./dist/motion/ScrollView.mjs",
|
|
73
73
|
"require": "./dist/motion/ScrollView.js"
|
|
74
74
|
},
|
|
75
|
+
"./flat-list": {
|
|
76
|
+
"types": "./dist/motion/FlatList.d.ts",
|
|
77
|
+
"react-native": "./src/motion/FlatList.tsx",
|
|
78
|
+
"source": "./src/motion/FlatList.tsx",
|
|
79
|
+
"import": "./dist/motion/FlatList.mjs",
|
|
80
|
+
"require": "./dist/motion/FlatList.js"
|
|
81
|
+
},
|
|
75
82
|
"./testing": {
|
|
76
83
|
"types": "./dist/testing/index.d.ts",
|
|
77
84
|
"react-native": "./src/testing/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export {
|
|
|
16
16
|
MotionImage,
|
|
17
17
|
MotionPressable,
|
|
18
18
|
MotionScrollView,
|
|
19
|
+
MotionFlatList,
|
|
19
20
|
createMotionComponent,
|
|
20
21
|
} from './motion'
|
|
21
22
|
export {
|
|
@@ -32,12 +33,16 @@ export type {
|
|
|
32
33
|
} from './config'
|
|
33
34
|
export { Presence, usePresence } from './presence'
|
|
34
35
|
export type { PresenceContextValue } from './presence'
|
|
36
|
+
export { Stagger, useStaggerDelay } from './stagger'
|
|
37
|
+
export type { StaggerProps } from './stagger'
|
|
35
38
|
export {
|
|
39
|
+
applyDelay,
|
|
36
40
|
buildReleaseAnimation,
|
|
37
41
|
cubicBezier,
|
|
38
42
|
resolveTransition,
|
|
39
43
|
resolveAnimatableValue,
|
|
40
44
|
ensureWorkletEasing,
|
|
45
|
+
type AnimationCallback,
|
|
41
46
|
} from './transitions'
|
|
42
47
|
// Seed value for any color shared value a custom animated component drives
|
|
43
48
|
// through `resolveTransition`. Exported because the obvious choice —
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FlatList as RNFlatList, FlatListProps } from 'react-native'
|
|
2
|
+
import Animated from 'react-native-reanimated'
|
|
3
|
+
import type { MotionProps, VariantsMap } from '../types'
|
|
4
|
+
import { createMotionComponent } from './createMotionComponent'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Animatable, virtualized `FlatList`.
|
|
8
|
+
*
|
|
9
|
+
* Built on Reanimated's `Animated.FlatList` rather than RN's `FlatList`. That
|
|
10
|
+
* wrapper supplies two things this primitive would otherwise have to
|
|
11
|
+
* re-implement: the `CellRendererComponent` injection that powers per-row
|
|
12
|
+
* `itemLayoutAnimation`, and a `scrollEventThrottle` default of 1.
|
|
13
|
+
*
|
|
14
|
+
* A note on that throttle default, because the reasoning is easy to get wrong
|
|
15
|
+
* from Reanimated's own source comment: it says RN defaults FlatList's
|
|
16
|
+
* `scrollEventThrottle` to 50, which **is stale**. On RN 0.81
|
|
17
|
+
* (`@react-native/virtualized-lists`, `VirtualizedList.js`) the default is
|
|
18
|
+
* `props.scrollEventThrottle ?? 0.0001` — effectively every frame. So the
|
|
19
|
+
* patch is belt-and-braces on current RN rather than the load-bearing reason
|
|
20
|
+
* to use the wrapper. It is still worth inheriting: it costs nothing, and it
|
|
21
|
+
* pins the behaviour if RN's default changes again. **Do not restate the "50"
|
|
22
|
+
* figure as fact** — verify against the installed RN before relying on it.
|
|
23
|
+
*
|
|
24
|
+
* This is the primitive that lets one list both virtualize and animate. The
|
|
25
|
+
* `useScroll()` handler works because `Motion.*` components are Reanimated
|
|
26
|
+
* animated components — the same mechanism `Motion.ScrollView` relies on, and
|
|
27
|
+
* nothing about it was ever specific to `ScrollView`:
|
|
28
|
+
*
|
|
29
|
+
* ```tsx
|
|
30
|
+
* const { scrollY, onScroll } = useScroll()
|
|
31
|
+
*
|
|
32
|
+
* <Motion.FlatList
|
|
33
|
+
* data={items}
|
|
34
|
+
* renderItem={renderItem}
|
|
35
|
+
* onScroll={onScroll}
|
|
36
|
+
* initial={{ opacity: 0 }}
|
|
37
|
+
* animate={{ opacity: 1 }}
|
|
38
|
+
* />
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Two scoping notes:
|
|
42
|
+
*
|
|
43
|
+
* - `animate` / `initial` / `gesture` apply to the **scroll container**, not to
|
|
44
|
+
* rows. Animate rows by using a `Motion.*` primitive inside `renderItem`.
|
|
45
|
+
* - The `layout` prop animates the list frame. Per-row layout animation is
|
|
46
|
+
* Reanimated's `itemLayoutAnimation`, which is forwarded through untouched.
|
|
47
|
+
* Note that row-level layout animation can fight the list's own measurement
|
|
48
|
+
* passes — measure before adopting it.
|
|
49
|
+
*/
|
|
50
|
+
export const MotionFlatList = createMotionComponent(
|
|
51
|
+
Animated.FlatList as never,
|
|
52
|
+
// `createMotionComponent<C>` returns a non-generic `MotionComponent<C>`, so
|
|
53
|
+
// `data` / `renderItem` would collapse to `any` and lose `ItemT` inference.
|
|
54
|
+
// Restore it with the same call-signature cast Reanimated itself uses for
|
|
55
|
+
// this exact problem (see its `ReanimatedFlatList` export, and the
|
|
56
|
+
// `@ts-expect-error` above `AnimatedFlatList` explaining that
|
|
57
|
+
// `createAnimatedComponent` cannot create generic components).
|
|
58
|
+
//
|
|
59
|
+
// **Both** generics live on the one call signature, and that is deliberate:
|
|
60
|
+
// `ItemT` infers from `data` while `V` infers from `variants`, independently.
|
|
61
|
+
// Declaring only `ItemT` (the obvious first cut) silently drops the
|
|
62
|
+
// variant-key narrowing that `MotionComponent` provides on every other
|
|
63
|
+
// primitive — `animate="typo"` would stop being a compile error here and
|
|
64
|
+
// nowhere else. Both directions are pinned in
|
|
65
|
+
// `__type-tests__/flat-list.test-d.tsx`.
|
|
66
|
+
) as unknown as <
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
ItemT = any,
|
|
69
|
+
V extends VariantsMap<FlatListProps<ItemT>> = VariantsMap<
|
|
70
|
+
FlatListProps<ItemT>
|
|
71
|
+
>,
|
|
72
|
+
>(
|
|
73
|
+
props: FlatListProps<ItemT> &
|
|
74
|
+
MotionProps<FlatListProps<ItemT>, V> & {
|
|
75
|
+
ref?: React.Ref<RNFlatList<ItemT>>
|
|
76
|
+
},
|
|
77
|
+
) => React.ReactElement
|
|
@@ -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 =
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
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
|
|
|
@@ -2025,6 +2060,26 @@ function useGestureHandlers(
|
|
|
2025
2060
|
// forward to those when present so wrapping consumers stay consistent.
|
|
2026
2061
|
handlers.onPressIn = compose(rest.onPressIn, () => setPressed(true))
|
|
2027
2062
|
handlers.onPressOut = compose(rest.onPressOut, () => setPressed(false))
|
|
2063
|
+
// Web pointer events. `onTouchStart` reaches the DOM as a real
|
|
2064
|
+
// `touchstart` listener under react-native-web, which a mouse never
|
|
2065
|
+
// fires — so without these a plain `Motion.View` (anything that isn't a
|
|
2066
|
+
// Pressable, i.e. has no `onPressIn` path) was inert under a desktop
|
|
2067
|
+
// click while `Motion.Pressable` worked. Pointer events cover mouse, pen
|
|
2068
|
+
// and touch, so `pressed` means "any pointer" on every platform.
|
|
2069
|
+
//
|
|
2070
|
+
// A web touch fires `touchstart` *and* `pointerdown`; both set the same
|
|
2071
|
+
// boolean to the same value, so the overlap is idempotent, not a
|
|
2072
|
+
// double-toggle. React Native has no pointer props, so these are inert
|
|
2073
|
+
// on native and cost nothing there.
|
|
2074
|
+
handlers.onPointerDown = compose(rest.onPointerDown, () =>
|
|
2075
|
+
setPressed(true),
|
|
2076
|
+
)
|
|
2077
|
+
handlers.onPointerUp = compose(rest.onPointerUp, () => setPressed(false))
|
|
2078
|
+
// Fires when the browser takes over the gesture (scroll, drag start) —
|
|
2079
|
+
// without it the pressed layer would stick on after the pointer is gone.
|
|
2080
|
+
handlers.onPointerCancel = compose(rest.onPointerCancel, () =>
|
|
2081
|
+
setPressed(false),
|
|
2082
|
+
)
|
|
2028
2083
|
}
|
|
2029
2084
|
// Mount onFocus/onBlur if either focus sub-state is declared. The two flags
|
|
2030
2085
|
// are independent: `focused` always tracks focus; `focusVisible` only
|
|
@@ -2061,6 +2116,9 @@ function useGestureHandlers(
|
|
|
2061
2116
|
rest.onTouchCancel,
|
|
2062
2117
|
rest.onPressIn,
|
|
2063
2118
|
rest.onPressOut,
|
|
2119
|
+
rest.onPointerDown,
|
|
2120
|
+
rest.onPointerUp,
|
|
2121
|
+
rest.onPointerCancel,
|
|
2064
2122
|
rest.onFocus,
|
|
2065
2123
|
rest.onBlur,
|
|
2066
2124
|
rest.onMouseEnter,
|
package/src/motion/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MotionFlatList } from './FlatList'
|
|
1
2
|
import { MotionImage } from './Image'
|
|
2
3
|
import { MotionPressable } from './Pressable'
|
|
3
4
|
import { MotionScrollView } from './ScrollView'
|
|
@@ -11,6 +12,7 @@ export {
|
|
|
11
12
|
MotionImage,
|
|
12
13
|
MotionPressable,
|
|
13
14
|
MotionScrollView,
|
|
15
|
+
MotionFlatList,
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -23,4 +25,5 @@ export const Motion = {
|
|
|
23
25
|
Image: MotionImage,
|
|
24
26
|
Pressable: MotionPressable,
|
|
25
27
|
ScrollView: MotionScrollView,
|
|
28
|
+
FlatList: MotionFlatList,
|
|
26
29
|
} as const
|
|
@@ -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
|
+
}
|
package/src/transitions/index.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export {
|
|
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
|
-
|
|
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
|
}
|