@rootnative/inertia 0.0.9 → 0.0.11
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 +95 -1
- package/README.md +8 -8
- package/dist/{chunk-MFAFB4K7.mjs → chunk-2Q6JMDYZ.mjs} +8 -10
- package/dist/chunk-2UREDR4Z.js +8 -0
- package/dist/{chunk-ZBHQPVWE.js → chunk-3PB27UIE.js} +2 -2
- package/dist/{chunk-IX6SEOSK.mjs → chunk-5EYIZG72.mjs} +60 -45
- package/dist/{chunk-EN4PN7X3.mjs → chunk-7RMSLKQQ.mjs} +1 -1
- package/dist/{chunk-6YIUETBF.mjs → chunk-DT2KVAAO.mjs} +1 -1
- package/dist/{chunk-L4JFECXU.mjs → chunk-FKJLPYGY.mjs} +6 -3
- package/dist/{chunk-E3ALFSH2.js → chunk-HBURWGCZ.js} +86 -71
- package/dist/chunk-JAJHKJC5.js +8 -0
- package/dist/chunk-JBMTTICC.js +8 -0
- package/dist/{chunk-GSUC4HWP.mjs → chunk-JGQ6YRDN.mjs} +1 -1
- package/dist/chunk-KFHZMFAX.js +23 -0
- package/dist/{chunk-M4766VUV.mjs → chunk-LHW4VNWD.mjs} +1 -1
- package/dist/{chunk-MUT6BTZS.js → chunk-NTG6PSGZ.js} +10 -7
- package/dist/chunk-OH46TGEA.mjs +20 -0
- package/dist/{chunk-6AN5X3YE.js → chunk-QDMSFRQR.js} +10 -11
- package/dist/{chunk-W7NTRSPD.mjs → chunk-QIRKPDXA.mjs} +118 -68
- package/dist/{chunk-WNVHPMBI.js → chunk-QX5JHRZ2.js} +118 -66
- package/dist/{chunk-6NOCY764.js → chunk-R7YD6L3D.js} +2 -2
- package/dist/{chunk-JBK5ZLMH.mjs → chunk-RO4XFTQ6.mjs} +1 -1
- package/dist/{chunk-QNLSCOR5.mjs → chunk-UGYBFAVY.mjs} +1 -1
- package/dist/{chunk-IHVMNOS4.js → chunk-XLKI2MSD.js} +2 -2
- package/dist/gestureLayer/index.d.mts +3 -3
- package/dist/gestureLayer/index.d.ts +3 -3
- package/dist/gestureLayer/index.js +12 -18
- package/dist/gestureLayer/index.mjs +5 -11
- package/dist/index.d.mts +108 -49
- package/dist/index.d.ts +108 -49
- package/dist/index.js +114 -91
- package/dist/index.mjs +66 -59
- package/dist/motion/FlatList.d.mts +2 -1
- package/dist/motion/FlatList.d.ts +2 -1
- package/dist/motion/FlatList.js +5 -5
- package/dist/motion/FlatList.mjs +4 -4
- package/dist/motion/Image.d.mts +2 -1
- package/dist/motion/Image.d.ts +2 -1
- package/dist/motion/Image.js +5 -5
- package/dist/motion/Image.mjs +4 -4
- package/dist/motion/Pressable.d.mts +2 -1
- package/dist/motion/Pressable.d.ts +2 -1
- package/dist/motion/Pressable.js +5 -5
- package/dist/motion/Pressable.mjs +4 -4
- package/dist/motion/ScrollView.d.mts +2 -1
- package/dist/motion/ScrollView.d.ts +2 -1
- package/dist/motion/ScrollView.js +5 -5
- package/dist/motion/ScrollView.mjs +4 -4
- package/dist/motion/Text.d.mts +2 -1
- package/dist/motion/Text.d.ts +2 -1
- package/dist/motion/Text.js +5 -5
- package/dist/motion/Text.mjs +4 -4
- package/dist/motion/View.d.mts +2 -1
- package/dist/motion/View.d.ts +2 -1
- package/dist/motion/View.js +5 -5
- package/dist/motion/View.mjs +4 -4
- package/dist/touch/index.d.mts +5 -3
- package/dist/touch/index.d.ts +5 -3
- package/dist/touch/index.js +16 -31
- package/dist/touch/index.mjs +11 -29
- package/dist/{types-DyJpG64F.d.mts → types-C5Zbs9MC.d.mts} +22 -4
- package/dist/{types-DyJpG64F.d.ts → types-C5Zbs9MC.d.ts} +22 -4
- package/dist/{useGesture-DbH46EBp.d.ts → useGesture-Be0ZhCWj.d.ts} +1 -1
- package/dist/{useGesture-CnZQnYHH.d.mts → useGesture-Ve8EGjTr.d.mts} +1 -1
- package/dist/useTranslateStyle-BDrOD97O.d.mts +58 -0
- package/dist/useTranslateStyle-BDrOD97O.d.ts +58 -0
- package/jest-preset.js +31 -2
- package/llms.txt +2 -1
- package/package.json +15 -12
- package/src/config/namedTransitions.ts +7 -9
- package/src/gestureLayer/index.ts +3 -0
- package/src/gestureLayer/useGestureLayer.ts +6 -9
- package/src/gestures/focusVisibility.ts +19 -17
- package/src/gestures/index.ts +1 -1
- package/src/index.ts +12 -0
- package/src/internal/boxShadow.ts +24 -2
- package/src/layout/resolveLayout.ts +4 -1
- package/src/layout/useSharedLayout.ts +2 -1
- package/src/motion/createMotionComponent.tsx +92 -44
- package/src/presence/Presence.tsx +5 -7
- package/src/touch/applyBounds.ts +30 -0
- package/src/touch/index.ts +1 -0
- package/src/touch/useTouchDrag.ts +18 -46
- package/src/transitions/constants.ts +14 -0
- package/src/transitions/easing.ts +23 -11
- package/src/transitions/index.ts +5 -0
- package/src/transitions/keys.ts +0 -2
- package/src/transitions/resolve.ts +86 -11
- package/src/transitions/resolveSequence.ts +45 -3
- package/src/transitions/runtime.ts +4 -7
- package/src/transitions/spring.ts +7 -2
- package/src/types.ts +23 -3
- package/src/values/extrapolation.ts +21 -0
- package/src/values/index.ts +3 -0
- package/src/values/useColorCascade.ts +3 -3
- package/src/values/useColorTransition.ts +17 -2
- package/src/values/useGesture.ts +9 -2
- package/src/values/useInterpolatedStyle.ts +13 -13
- package/src/values/useShadow.ts +36 -9
- package/src/values/useSpring.ts +34 -25
- package/src/values/useTransform.ts +24 -20
- package/src/values/useTranslateStyle.ts +47 -0
- package/dist/chunk-24NUL23E.js +0 -8
- package/dist/chunk-4XBX7KOD.js +0 -8
- package/dist/chunk-Z5USXWTE.js +0 -8
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
withSpring,
|
|
7
7
|
withTiming,
|
|
8
8
|
} from 'react-native-reanimated'
|
|
9
|
+
import { DEFAULT_TIMING_DURATION } from './constants'
|
|
9
10
|
import { ensureWorkletEasing } from './easing'
|
|
10
11
|
import { springToReanimated } from './spring'
|
|
12
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
11
13
|
import {
|
|
12
14
|
type DecayTransition,
|
|
13
15
|
type RepeatConfig,
|
|
@@ -37,8 +39,6 @@ export type CallbackFactory = (
|
|
|
37
39
|
step: number | undefined,
|
|
38
40
|
) => AnimationCallback | undefined
|
|
39
41
|
|
|
40
|
-
const DEFAULT_TIMING_DURATION = 250
|
|
41
|
-
|
|
42
42
|
function buildSpring(
|
|
43
43
|
cfg: SpringTransition,
|
|
44
44
|
toValue: number | string,
|
|
@@ -94,26 +94,101 @@ function buildOne(
|
|
|
94
94
|
return buildSpring(cfg as SpringTransition, toValue, cb)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Normalised repeat: `count` is the total number of iterations
|
|
99
|
+
* (`Number.POSITIVE_INFINITY` for `'infinite'`), `alternate` is the resolved
|
|
100
|
+
* flag, `explicitAlternate` says whether the caller set `alternate` by hand.
|
|
101
|
+
*/
|
|
102
|
+
export interface NormalizedRepeat {
|
|
103
|
+
count: number
|
|
104
|
+
alternate: boolean
|
|
105
|
+
explicitAlternate: boolean
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reduce the three public `repeat` shapes to one record, or `undefined` when
|
|
110
|
+
* the animation runs once. A count below `1` is treated as "run once" and
|
|
111
|
+
* warns in dev: Reanimated's `withRepeat` reads `0` and negative counts as
|
|
112
|
+
* endless, so forwarding them would turn `repeat: 0` into an infinite loop.
|
|
113
|
+
*/
|
|
114
|
+
export function normalizeRepeat(
|
|
115
|
+
repeat: RepeatConfig | undefined,
|
|
116
|
+
): NormalizedRepeat | undefined {
|
|
117
|
+
if (repeat === undefined) return undefined
|
|
118
|
+
if (repeat === 'infinite') {
|
|
119
|
+
return {
|
|
120
|
+
count: Number.POSITIVE_INFINITY,
|
|
121
|
+
alternate: true,
|
|
122
|
+
explicitAlternate: false,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const rawCount = typeof repeat === 'number' ? repeat : repeat.count
|
|
126
|
+
const alternate = typeof repeat === 'number' ? true : repeat.alternate
|
|
127
|
+
if (rawCount === 'infinite') {
|
|
128
|
+
return {
|
|
129
|
+
count: Number.POSITIVE_INFINITY,
|
|
130
|
+
alternate: alternate ?? true,
|
|
131
|
+
explicitAlternate: alternate !== undefined,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!(rawCount >= 1)) {
|
|
135
|
+
warnOnce(
|
|
136
|
+
`repeat-count:${String(rawCount)}`,
|
|
137
|
+
`[inertia] repeat count ${String(rawCount)} is below 1 — the ` +
|
|
138
|
+
`animation runs once. Use \`repeat: 2\` or more to repeat, or ` +
|
|
139
|
+
`\`repeat: 'infinite'\`. (Reanimated reads a count of 0 as endless, ` +
|
|
140
|
+
`so it is not forwarded.)`,
|
|
141
|
+
)
|
|
142
|
+
return undefined
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
count: rawCount,
|
|
146
|
+
alternate: alternate ?? true,
|
|
147
|
+
explicitAlternate: alternate !== undefined,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Total number of iterations an animation built from `repeat` runs, including
|
|
153
|
+
* the first pass. `1` when there is no repeat or the count is below 1;
|
|
154
|
+
* `Number.POSITIVE_INFINITY` for `'infinite'`.
|
|
155
|
+
*/
|
|
156
|
+
export function repeatIterationsOf(repeat: RepeatConfig | undefined): number {
|
|
157
|
+
return normalizeRepeat(repeat)?.count ?? 1
|
|
158
|
+
}
|
|
159
|
+
|
|
97
160
|
/**
|
|
98
161
|
* Wrap an animation in `withRepeat` per the unified `repeat` shape:
|
|
99
162
|
* - `number` → finite count, alternating direction
|
|
100
163
|
* - `'infinite'` → endless, alternating direction
|
|
101
164
|
* - `{ count, alternate }`→ explicit; `alternate` defaults to `true`
|
|
165
|
+
*
|
|
166
|
+
* Pass `{ sequence: true }` when `animation` is a `withSequence` result.
|
|
167
|
+
* Reanimated's `reverse` flag only swaps the wrapped animation's `toValue`,
|
|
168
|
+
* which a sequence ignores — it restarts at step 0 on every pass — so the
|
|
169
|
+
* flag is not forwarded for sequences. An explicit `alternate: true` on a
|
|
170
|
+
* sequence warns in dev; write the reverse steps into the sequence instead.
|
|
102
171
|
*/
|
|
103
172
|
export function applyRepeat(
|
|
104
173
|
animation: unknown,
|
|
105
174
|
repeat: RepeatConfig | undefined,
|
|
175
|
+
options?: { sequence?: boolean },
|
|
106
176
|
) {
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
177
|
+
const r = normalizeRepeat(repeat)
|
|
178
|
+
if (r === undefined) return animation
|
|
179
|
+
const count = Number.isFinite(r.count) ? r.count : -1
|
|
180
|
+
if (options?.sequence) {
|
|
181
|
+
if (r.explicitAlternate && r.alternate) {
|
|
182
|
+
warnOnce(
|
|
183
|
+
'repeat-sequence-alternate',
|
|
184
|
+
'[inertia] repeat.alternate has no effect on a sequence — Reanimated ' +
|
|
185
|
+
'restarts a sequence at its first step on every pass. Append the ' +
|
|
186
|
+
'reverse steps to the sequence to alternate.',
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
return withRepeat(animation as never, count, false)
|
|
113
190
|
}
|
|
114
|
-
|
|
115
|
-
const alternate = repeat.alternate ?? true
|
|
116
|
-
return withRepeat(animation as never, count, alternate)
|
|
191
|
+
return withRepeat(animation as never, count, r.alternate)
|
|
117
192
|
}
|
|
118
193
|
|
|
119
194
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { withSequence } from 'react-native-reanimated'
|
|
1
|
+
import { withSequence, withTiming } from 'react-native-reanimated'
|
|
2
2
|
import {
|
|
3
3
|
applyRepeat,
|
|
4
4
|
repeatOf,
|
|
@@ -13,6 +13,18 @@ import {
|
|
|
13
13
|
type TransitionConfig,
|
|
14
14
|
} from '../types'
|
|
15
15
|
|
|
16
|
+
/** A zero-length timing, for a snapped step inside an animated sequence. */
|
|
17
|
+
const INSTANT = { duration: 0 } as const
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True when a resolved step is a bare target rather than a Reanimated
|
|
21
|
+
* animation object. `buildOne` returns the target itself for `no-animation`,
|
|
22
|
+
* which is what the reduced-motion gate collapses every step to.
|
|
23
|
+
*/
|
|
24
|
+
function isSnapped(step: unknown): step is number | string {
|
|
25
|
+
return typeof step === 'number' || typeof step === 'string'
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* True when the value is a `{ to, ...transitionOverride }` sequence step.
|
|
18
30
|
* Plain numbers and plain transition objects fail this check.
|
|
@@ -37,6 +49,12 @@ function isStepObject<V>(
|
|
|
37
49
|
* 3. array of either → `withSequence` of resolved steps, with the
|
|
38
50
|
* top-level `repeat` applied at the **sequence level** (not per step).
|
|
39
51
|
* Per-step `repeat` overrides remain step-local.
|
|
52
|
+
*
|
|
53
|
+
* `withSequence` writes a `finished` flag onto each argument it is handed, so
|
|
54
|
+
* a bare target throws `Cannot create property 'finished' on number` and takes
|
|
55
|
+
* the whole render down with it. Two paths produce bare targets, and both are
|
|
56
|
+
* handled below rather than at the call site: reduced motion (every step) and
|
|
57
|
+
* a per-step `{ type: 'no-animation' }` override (some steps).
|
|
40
58
|
*/
|
|
41
59
|
export function resolveAnimatableValue<V extends number | string>(
|
|
42
60
|
value: AnimatableValue<V>,
|
|
@@ -49,8 +67,32 @@ export function resolveAnimatableValue<V extends number | string>(
|
|
|
49
67
|
const animations = steps.map((step, i) =>
|
|
50
68
|
resolveStep(step, stepBase, factory?.('step', i)),
|
|
51
69
|
)
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
// The reduced-motion gate. `mergeTransition` hands a `no-animation` base
|
|
71
|
+
// back untouched for every step, so the whole sequence is a snap and there
|
|
72
|
+
// is nothing to sequence. Settle on the last step — where the sequence
|
|
73
|
+
// would have ended. `repeat` and `delay` are already dropped for
|
|
74
|
+
// `no-animation` (`repeatOf` and `delayOf` both return `undefined`), so
|
|
75
|
+
// nothing needs to wrap this.
|
|
76
|
+
//
|
|
77
|
+
// Read off the config rather than sniffing the resolved values: under the
|
|
78
|
+
// Jest mock `withSpring` and `withTiming` also return bare targets, so a
|
|
79
|
+
// shape test reports every sequence as snapped and the gate stops being
|
|
80
|
+
// observable in either direction.
|
|
81
|
+
if (stepBase?.type === 'no-animation' && animations.length > 0) {
|
|
82
|
+
return animations[animations.length - 1]
|
|
83
|
+
}
|
|
84
|
+
// A step that declares `{ type: 'no-animation' }` inside a sequence that
|
|
85
|
+
// otherwise animates resolves to a bare target of its own. The step still
|
|
86
|
+
// owns its slot in the order, so it becomes an instant timing rather than
|
|
87
|
+
// being dropped.
|
|
88
|
+
const seq = withSequence(
|
|
89
|
+
...(animations.map((a) =>
|
|
90
|
+
isSnapped(a) ? withTiming(a as number, INSTANT) : a,
|
|
91
|
+
) as never[]),
|
|
92
|
+
)
|
|
93
|
+
return applyRepeat(seq, base ? repeatOf(base) : undefined, {
|
|
94
|
+
sequence: true,
|
|
95
|
+
})
|
|
54
96
|
}
|
|
55
97
|
const step = value as SequenceStep<V>
|
|
56
98
|
const cb = factory?.('animation', undefined)
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
withSpring,
|
|
5
5
|
withTiming,
|
|
6
6
|
} from 'react-native-reanimated'
|
|
7
|
+
import { DEFAULT_TIMING_DURATION } from './constants'
|
|
8
|
+
import { unwrapEasingFactory } from './easing'
|
|
7
9
|
import { springToReanimated } from './spring'
|
|
8
10
|
import { type AnimationCallback } from './resolve'
|
|
9
11
|
import { type TransitionConfig } from '../types'
|
|
10
12
|
|
|
11
|
-
const DEFAULT_TIMING_DURATION = 250
|
|
12
|
-
|
|
13
13
|
/**
|
|
14
14
|
* Worklet-safe single-step animation builder. Mirrors a subset of
|
|
15
15
|
* `resolveTransition` for the UI-thread path where the transition config is
|
|
@@ -58,13 +58,10 @@ export function buildReleaseAnimation(
|
|
|
58
58
|
}
|
|
59
59
|
if (transition.type === 'timing') {
|
|
60
60
|
// Reanimated 4's `Easing.bezier(...)` returns an `EasingFunctionFactory`
|
|
61
|
-
// rather than the function itself. Unwrap
|
|
61
|
+
// rather than the function itself. Unwrap so consumers calling
|
|
62
62
|
// `buildReleaseAnimation` from a gesture worklet don't have to.
|
|
63
63
|
const e = transition.easing
|
|
64
|
-
const easingFn =
|
|
65
|
-
e && typeof e === 'object' && 'factory' in e
|
|
66
|
-
? e.factory()
|
|
67
|
-
: (e ?? Easing.inOut(Easing.ease))
|
|
64
|
+
const easingFn = e ? unwrapEasingFactory(e) : Easing.inOut(Easing.ease)
|
|
68
65
|
return withTiming(
|
|
69
66
|
toValue,
|
|
70
67
|
{
|
|
@@ -28,6 +28,13 @@ export const DEFAULT_SPRING: Required<
|
|
|
28
28
|
* names differ but the underlying physics constants are the same. We don't
|
|
29
29
|
* surface the raw names publicly because the react-spring vocabulary is
|
|
30
30
|
* what designers and prior-art consumers expect.
|
|
31
|
+
*
|
|
32
|
+
* Settle control is deliberately absent: Reanimated 4 settles springs on an
|
|
33
|
+
* internal `energyThreshold` and ignores Reanimated 3's `restSpeedThreshold`
|
|
34
|
+
* / `restDisplacementThreshold`. Inertia surfaced those two names through
|
|
35
|
+
* `0.0.9`, where they were silently inert — do not re-add them. If a settle
|
|
36
|
+
* knob is ever needed, expose Reanimated 4's `overshootClamping` or
|
|
37
|
+
* `energyThreshold`, which the runtime actually reads.
|
|
31
38
|
*/
|
|
32
39
|
export function springToReanimated(t: SpringTransition) {
|
|
33
40
|
'worklet'
|
|
@@ -36,7 +43,5 @@ export function springToReanimated(t: SpringTransition) {
|
|
|
36
43
|
damping: t.friction ?? DEFAULT_SPRING.friction,
|
|
37
44
|
mass: t.mass ?? DEFAULT_SPRING.mass,
|
|
38
45
|
velocity: t.velocity,
|
|
39
|
-
restSpeedThreshold: t.restSpeedThreshold,
|
|
40
|
-
restDisplacementThreshold: t.restDisplacementThreshold,
|
|
41
46
|
}
|
|
42
47
|
}
|
package/src/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Ref,
|
|
6
6
|
} from 'react'
|
|
7
7
|
import { type BoxShadowValue, type StyleProp } from 'react-native'
|
|
8
|
+
import { type AnimatedProps } from 'react-native-reanimated'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* A single animation step's destination, optionally overriding the transition
|
|
@@ -31,8 +32,6 @@ export interface SpringTransition {
|
|
|
31
32
|
friction?: number
|
|
32
33
|
mass?: number
|
|
33
34
|
velocity?: number
|
|
34
|
-
restSpeedThreshold?: number
|
|
35
|
-
restDisplacementThreshold?: number
|
|
36
35
|
delay?: number
|
|
37
36
|
repeat?: RepeatConfig
|
|
38
37
|
}
|
|
@@ -130,6 +129,15 @@ export type TransitionInput = TransitionConfig | TransitionName
|
|
|
130
129
|
|
|
131
130
|
/**
|
|
132
131
|
* Repeat config — one shape, not three flags. Default `alternate: true`.
|
|
132
|
+
*
|
|
133
|
+
* `count` is the total number of iterations, including the first pass, so
|
|
134
|
+
* `repeat: 2` plays the animation twice. A count below `1` runs the animation
|
|
135
|
+
* once and warns in dev.
|
|
136
|
+
*
|
|
137
|
+
* `alternate` reverses direction on every other pass for a single-value
|
|
138
|
+
* animation. It has no effect on a sequence (an array target): Reanimated
|
|
139
|
+
* restarts a sequence at its first step on every pass. Append the reverse
|
|
140
|
+
* steps to the sequence to alternate.
|
|
133
141
|
*/
|
|
134
142
|
export type RepeatConfig =
|
|
135
143
|
| number
|
|
@@ -548,6 +556,14 @@ export interface MotionProps<C, V extends VariantsMap<C> = VariantsMap<C>> {
|
|
|
548
556
|
* Props of a Motion primitive for a given underlying component `C` and a
|
|
549
557
|
* concrete variants map `V`: the component's own props (minus `style`, which
|
|
550
558
|
* we replace with an animated style) intersected with the Motion props.
|
|
559
|
+
*
|
|
560
|
+
* `style` is taken from `AnimatedProps<…>`, not from `C` directly, because a
|
|
561
|
+
* Motion primitive renders `Animated.createAnimatedComponent(C)` — so the
|
|
562
|
+
* styles it accepts are the ones that animated component accepts. Reanimated
|
|
563
|
+
* 4.5 brands what `useAnimatedStyle` returns, and `ComponentProps<C>['style']`
|
|
564
|
+
* rejects a branded value, which would have made the sanctioned escape hatch
|
|
565
|
+
* (Principle 2: own your `useAnimatedStyle` over Inertia-driven shared values,
|
|
566
|
+
* then pass it to a `Motion.*` primitive) require a cast at every call site.
|
|
551
567
|
*/
|
|
552
568
|
export type MotionComponentProps<
|
|
553
569
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -555,7 +571,11 @@ export type MotionComponentProps<
|
|
|
555
571
|
V extends VariantsMap<ComponentProps<C>> = VariantsMap<ComponentProps<C>>,
|
|
556
572
|
> = Omit<ComponentProps<C>, 'style'> &
|
|
557
573
|
MotionProps<ComponentProps<C>, V> & {
|
|
558
|
-
style?: ComponentProps<C
|
|
574
|
+
style?: AnimatedProps<ComponentProps<C>> extends {
|
|
575
|
+
style?: infer S
|
|
576
|
+
}
|
|
577
|
+
? S
|
|
578
|
+
: ComponentProps<C>['style']
|
|
559
579
|
ref?: Ref<unknown>
|
|
560
580
|
}
|
|
561
581
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Extrapolation } from 'react-native-reanimated'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extrapolation behavior at the edges of the input range. Mirrors
|
|
5
|
+
* Reanimated's enum so consumers don't need a separate import.
|
|
6
|
+
*
|
|
7
|
+
* - `'clamp'` (default) — output stays pinned at the first/last value
|
|
8
|
+
* outside the input range. Matches Framer Motion's default.
|
|
9
|
+
* - `'identity'` — return the input unchanged outside the range.
|
|
10
|
+
* - `'extend'` — continue the linear slope beyond the range.
|
|
11
|
+
*/
|
|
12
|
+
export type ExtrapolationMode = 'clamp' | 'identity' | 'extend'
|
|
13
|
+
|
|
14
|
+
/** Map the public mode name to Reanimated's `Extrapolation` enum. */
|
|
15
|
+
export function mapExtrapolation(
|
|
16
|
+
mode: ExtrapolationMode | undefined,
|
|
17
|
+
): Extrapolation {
|
|
18
|
+
if (mode === 'identity') return Extrapolation.IDENTITY
|
|
19
|
+
if (mode === 'extend') return Extrapolation.EXTEND
|
|
20
|
+
return Extrapolation.CLAMP
|
|
21
|
+
}
|
package/src/values/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
} from './useColorCascade'
|
|
9
9
|
export {
|
|
10
10
|
useColorTransition,
|
|
11
|
+
type ColorStyle,
|
|
11
12
|
type ColorStyleKey,
|
|
12
13
|
type UseColorTransitionOptions,
|
|
13
14
|
} from './useColorTransition'
|
|
@@ -36,6 +37,8 @@ export {
|
|
|
36
37
|
useShadow,
|
|
37
38
|
type BoxShadowLayer,
|
|
38
39
|
type ShadowConfig,
|
|
40
|
+
type ShadowStyle,
|
|
39
41
|
type UseShadowOptions,
|
|
40
42
|
} from './useShadow'
|
|
43
|
+
export { useTranslateStyle, type TranslateStyle } from './useTranslateStyle'
|
|
41
44
|
export { useVariants } from './useVariants'
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
useAnimatedStyle,
|
|
5
5
|
type SharedValue,
|
|
6
6
|
} from 'react-native-reanimated'
|
|
7
|
-
import type { ColorStyleKey } from './useColorTransition'
|
|
7
|
+
import type { ColorStyle, ColorStyleKey } from './useColorTransition'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* One layer in a color cascade: its own `progress` shared value (0→1) and the
|
|
@@ -70,7 +70,7 @@ export function useColorCascade(
|
|
|
70
70
|
rest: string,
|
|
71
71
|
layers: readonly ColorCascadeLayer[],
|
|
72
72
|
options?: UseColorCascadeOptions,
|
|
73
|
-
):
|
|
73
|
+
): ColorStyle {
|
|
74
74
|
const key = options?.key ?? 'backgroundColor'
|
|
75
75
|
|
|
76
76
|
// Resolve the layer chain into two flat arrays the worklet closes over — the
|
|
@@ -121,5 +121,5 @@ export function useColorCascade(
|
|
|
121
121
|
)
|
|
122
122
|
}
|
|
123
123
|
return { [key]: acc }
|
|
124
|
-
})
|
|
124
|
+
}) as unknown as ColorStyle
|
|
125
125
|
}
|
|
@@ -20,6 +20,21 @@ export type ColorStyleKey =
|
|
|
20
20
|
| 'tintColor'
|
|
21
21
|
| 'shadowColor'
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A one-key colour style fragment, as returned by {@link useColorTransition}
|
|
25
|
+
* and {@link useColorCascade}.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately **not** `ReturnType<typeof useAnimatedStyle>`. Reanimated 4.5
|
|
28
|
+
* brands that value (`AnimatedStyleHandle`), and a branded type is rejected
|
|
29
|
+
* inside a `StyleProp` array — so every call site would need a cast, which is
|
|
30
|
+
* the defect `InterpolatedStyle` was introduced to remove in `0.0.9`. The brand
|
|
31
|
+
* is compile-time only, so the runtime value is unchanged.
|
|
32
|
+
*
|
|
33
|
+
* Optional per key because which slot is filled is chosen at run time from
|
|
34
|
+
* `options.key`; exactly one is ever present.
|
|
35
|
+
*/
|
|
36
|
+
export type ColorStyle = { [K in ColorStyleKey]?: string }
|
|
37
|
+
|
|
23
38
|
export interface UseColorTransitionOptions {
|
|
24
39
|
/**
|
|
25
40
|
* Which style slot the interpolated color is emitted under. Defaults to
|
|
@@ -56,7 +71,7 @@ export function useColorTransition(
|
|
|
56
71
|
progress: SharedValue<number>,
|
|
57
72
|
range: readonly [string, string],
|
|
58
73
|
options?: UseColorTransitionOptions,
|
|
59
|
-
):
|
|
74
|
+
): ColorStyle {
|
|
60
75
|
// Resolve the slot key once on the JS thread so the worklet body
|
|
61
76
|
// consumes a single string literal — consistent with the JS-thread
|
|
62
77
|
// resolver principle that keeps `Object.keys`-style walks off the UI
|
|
@@ -68,5 +83,5 @@ export function useColorTransition(
|
|
|
68
83
|
return useAnimatedStyle(() => {
|
|
69
84
|
'worklet'
|
|
70
85
|
return { [key]: interpolateColor(progress.value, [0, 1], [from, to]) }
|
|
71
|
-
})
|
|
86
|
+
}) as unknown as ColorStyle
|
|
72
87
|
}
|
package/src/values/useGesture.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
2
2
|
import { useSharedValue, type SharedValue } from 'react-native-reanimated'
|
|
3
3
|
import {
|
|
4
4
|
resolveNamedTransitionProp,
|
|
5
5
|
useNamedTransitions,
|
|
6
6
|
useShouldReduceMotion,
|
|
7
7
|
} from '../config'
|
|
8
|
-
import { isFocusVisible } from '../gestures'
|
|
8
|
+
import { installFocusVisibility, isFocusVisible } from '../gestures'
|
|
9
9
|
import { isTopLevelTransition, resolveTransition } from '../transitions'
|
|
10
10
|
import {
|
|
11
11
|
type GestureLayerTransitions,
|
|
@@ -110,6 +110,13 @@ export function useGesture(
|
|
|
110
110
|
// the callbacks below only ever see concrete configs.
|
|
111
111
|
const resolved = resolveNamedTransitionProp(transition, useNamedTransitions())
|
|
112
112
|
|
|
113
|
+
// The web modality listeners behind `focusVisible` attach on mount — see
|
|
114
|
+
// `focusVisibility.ts` for why mount time is early enough and import time
|
|
115
|
+
// is not an option.
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
installFocusVisibility()
|
|
118
|
+
}, [])
|
|
119
|
+
|
|
113
120
|
const setLayer = useCallback(
|
|
114
121
|
(sv: SharedValue<number>, layer: LayerName, target: 0 | 1) => {
|
|
115
122
|
const cfg = shouldReduceMotion
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'
|
|
3
3
|
import {
|
|
4
|
-
Extrapolation,
|
|
5
4
|
interpolate,
|
|
6
5
|
interpolateColor,
|
|
7
6
|
useAnimatedStyle,
|
|
8
7
|
type SharedValue,
|
|
9
8
|
} from 'react-native-reanimated'
|
|
9
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
10
|
+
import { mapExtrapolation, type ExtrapolationMode } from './extrapolation'
|
|
10
11
|
import type { ColorStyleKey } from './useColorTransition'
|
|
11
|
-
import type { ExtrapolationMode } from './useTransform'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Numeric style keys `useInterpolatedStyle` can emit directly (not lifted into
|
|
@@ -158,12 +158,6 @@ function evenlySpaced(count: number): number[] {
|
|
|
158
158
|
return out
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
function mapExtrapolation(mode: ExtrapolationMode | undefined): Extrapolation {
|
|
162
|
-
if (mode === 'identity') return Extrapolation.IDENTITY
|
|
163
|
-
if (mode === 'extend') return Extrapolation.EXTEND
|
|
164
|
-
return Extrapolation.CLAMP
|
|
165
|
-
}
|
|
166
|
-
|
|
167
161
|
/**
|
|
168
162
|
* Order-preserving structural signature of the map + options. Unlike
|
|
169
163
|
* `stableSig` (which sorts keys), this walks `map` in insertion order because
|
|
@@ -202,8 +196,9 @@ function buildEntries(
|
|
|
202
196
|
? [0, 1]
|
|
203
197
|
: evenlySpaced(output.length)
|
|
204
198
|
|
|
205
|
-
if (
|
|
206
|
-
|
|
199
|
+
if (explicitInput && explicitInput.length !== output.length) {
|
|
200
|
+
warnOnce(
|
|
201
|
+
`interpolated-style-range:${String(key)}:${explicitInput.length}:${output.length}`,
|
|
207
202
|
`[inertia] useInterpolatedStyle: inputRange has ${explicitInput.length} stops but the "${String(
|
|
208
203
|
key,
|
|
209
204
|
)}" output has ${output.length}. They must match — interpolation results are undefined otherwise.`,
|
|
@@ -313,6 +308,13 @@ export function useInterpolatedStyle<K extends keyof InterpolatedStyleMap>(
|
|
|
313
308
|
// The worklet builds a `Record<string, unknown>` by design — the emitted keys
|
|
314
309
|
// are only known from `entries` at run time. `InterpolatedStyle<K>` is the
|
|
315
310
|
// static statement of that same shape, so the cast is where the two meet.
|
|
311
|
+
//
|
|
312
|
+
// Through `unknown` because Reanimated 4.5 brands what `useAnimatedStyle`
|
|
313
|
+
// returns (`AnimatedStyleHandle`), and a branded type no longer overlaps a
|
|
314
|
+
// plain style object. Erasing the brand is the point of this hook: the
|
|
315
|
+
// returned value has to drop into a `StyleProp<ViewStyle>` array without the
|
|
316
|
+
// consumer casting. The brand is compile-time only, so the runtime value is
|
|
317
|
+
// unchanged — it is still the animated style Reanimated produced.
|
|
316
318
|
return useAnimatedStyle(() => {
|
|
317
319
|
'worklet'
|
|
318
320
|
const out: Record<string, unknown> = {}
|
|
@@ -343,7 +345,5 @@ export function useInterpolatedStyle<K extends keyof InterpolatedStyleMap>(
|
|
|
343
345
|
}
|
|
344
346
|
if (transform.length > 0) out.transform = transform
|
|
345
347
|
return out
|
|
346
|
-
}) as InterpolatedStyle<K>
|
|
348
|
+
}) as unknown as InterpolatedStyle<K>
|
|
347
349
|
}
|
|
348
|
-
|
|
349
|
-
declare const __DEV__: boolean
|
package/src/values/useShadow.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
1
2
|
import {
|
|
2
3
|
interpolate,
|
|
3
4
|
interpolateColor,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
resolveBoxShadowInput,
|
|
10
11
|
type BoxShadowLayer,
|
|
11
12
|
} from '../internal/boxShadow'
|
|
13
|
+
import { stableSig } from '../transitions/sig'
|
|
12
14
|
|
|
13
15
|
export type { BoxShadowLayer }
|
|
14
16
|
|
|
@@ -41,6 +43,22 @@ export interface ShadowConfig {
|
|
|
41
43
|
boxShadow?: string | readonly BoxShadowLayer[]
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The style fragment {@link useShadow} returns: the native shadow keys plus the
|
|
48
|
+
* CSS `boxShadow`, each present only when the paired configs drive it.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately **not** `ReturnType<typeof useAnimatedStyle>` — see
|
|
51
|
+
* {@link ColorStyle} for why the brand is erased here.
|
|
52
|
+
*/
|
|
53
|
+
export type ShadowStyle = {
|
|
54
|
+
shadowColor?: string
|
|
55
|
+
shadowOffset?: { width: number; height: number }
|
|
56
|
+
shadowOpacity?: number
|
|
57
|
+
shadowRadius?: number
|
|
58
|
+
elevation?: number
|
|
59
|
+
boxShadow?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
export interface UseShadowOptions {
|
|
45
63
|
/** Shadow state at `progress === 0`. */
|
|
46
64
|
from: ShadowConfig
|
|
@@ -95,7 +113,7 @@ export function useShadow({
|
|
|
95
113
|
from,
|
|
96
114
|
to,
|
|
97
115
|
progress,
|
|
98
|
-
}: UseShadowOptions):
|
|
116
|
+
}: UseShadowOptions): ShadowStyle {
|
|
99
117
|
// Resolve presence + endpoints once on the JS thread so the worklet body
|
|
100
118
|
// consumes flat literals — consistent with the JS-thread resolver
|
|
101
119
|
// principle that keeps `Object.keys`-style walks off the UI thread.
|
|
@@ -113,13 +131,22 @@ export function useShadow({
|
|
|
113
131
|
// boxShadow layers: parse/pair once on the JS thread into flat records so
|
|
114
132
|
// the worklet only interpolates numbers/colors and concatenates — no
|
|
115
133
|
// frame-time parsing. `[]` when neither side provides the key.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
//
|
|
135
|
+
// Memoised on the inputs' structure: the pairs array is the one non-scalar
|
|
136
|
+
// the worklet captures, and `from` / `to` are usually inline literals, so
|
|
137
|
+
// without the memo a fresh array each render would rebuild the worklet.
|
|
138
|
+
const boxShadowSig = stableSig([from.boxShadow, to.boxShadow])
|
|
139
|
+
const boxShadowPairs = useMemo(
|
|
140
|
+
() =>
|
|
141
|
+
from.boxShadow !== undefined || to.boxShadow !== undefined
|
|
142
|
+
? pairBoxShadowLayers(
|
|
143
|
+
resolveBoxShadowInput(from.boxShadow),
|
|
144
|
+
resolveBoxShadowInput(to.boxShadow),
|
|
145
|
+
)
|
|
146
|
+
: [],
|
|
147
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
148
|
+
[boxShadowSig],
|
|
149
|
+
)
|
|
123
150
|
|
|
124
151
|
const opacityFrom = from.shadowOpacity ?? 0
|
|
125
152
|
const opacityTo = to.shadowOpacity ?? 0
|
|
@@ -187,5 +214,5 @@ export function useShadow({
|
|
|
187
214
|
out.boxShadow = css
|
|
188
215
|
}
|
|
189
216
|
return out
|
|
190
|
-
})
|
|
217
|
+
}) as unknown as ShadowStyle
|
|
191
218
|
}
|