@rootnative/inertia 0.0.8 → 0.0.10
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 +81 -1
- package/README.md +7 -7
- package/dist/applyBounds-BYGiHrYO.d.mts +18 -0
- package/dist/applyBounds-BYGiHrYO.d.ts +18 -0
- package/dist/{chunk-E3ALFSH2.js → chunk-2UGWAB6N.js} +78 -70
- package/dist/{chunk-ZBHQPVWE.js → chunk-3ZNHPT5Z.js} +2 -2
- package/dist/{chunk-IHVMNOS4.js → chunk-46766TBM.js} +2 -2
- package/dist/{chunk-MUT6BTZS.js → chunk-4EZ5DBL3.js} +10 -7
- package/dist/{chunk-MFAFB4K7.mjs → chunk-6FCATUMY.mjs} +8 -10
- package/dist/{chunk-6YIUETBF.mjs → chunk-7BH3CAO5.mjs} +1 -1
- package/dist/{chunk-JBK5ZLMH.mjs → chunk-CUHOCONB.mjs} +1 -1
- package/dist/{chunk-M4766VUV.mjs → chunk-DRA2K53O.mjs} +1 -1
- package/dist/{chunk-L4JFECXU.mjs → chunk-F4HMCXAX.mjs} +6 -3
- package/dist/{chunk-W7NTRSPD.mjs → chunk-HEAWMZEM.mjs} +105 -66
- package/dist/{chunk-IX6SEOSK.mjs → chunk-JENYDTXH.mjs} +52 -44
- package/dist/chunk-KFHZMFAX.js +23 -0
- package/dist/{chunk-6AN5X3YE.js → chunk-KXU6VH2Y.js} +10 -11
- package/dist/{chunk-WNVHPMBI.js → chunk-N33GSDNQ.js} +106 -65
- package/dist/chunk-OH46TGEA.mjs +20 -0
- package/dist/{chunk-QNLSCOR5.mjs → chunk-OLOSU2BD.mjs} +1 -1
- package/dist/chunk-PLKXCJIB.js +8 -0
- package/dist/{chunk-GSUC4HWP.mjs → chunk-QXKPIENP.mjs} +1 -1
- package/dist/chunk-UBHQJFUW.js +8 -0
- package/dist/{chunk-6NOCY764.js → chunk-VKXSL3ZL.js} +2 -2
- package/dist/chunk-XKNEQDAT.js +8 -0
- package/dist/{chunk-EN4PN7X3.mjs → chunk-Z23AMUSW.mjs} +1 -1
- 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 +105 -47
- package/dist/index.d.ts +105 -47
- package/dist/index.js +119 -92
- package/dist/index.mjs +71 -60
- package/dist/motion/FlatList.d.mts +1 -1
- package/dist/motion/FlatList.d.ts +1 -1
- package/dist/motion/FlatList.js +5 -5
- package/dist/motion/FlatList.mjs +4 -4
- 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 +2 -1
- package/dist/touch/index.d.ts +2 -1
- package/dist/touch/index.js +16 -31
- package/dist/touch/index.mjs +11 -29
- package/dist/{types-DyJpG64F.d.mts → types-08mG8fNk.d.mts} +10 -3
- package/dist/{types-DyJpG64F.d.ts → types-08mG8fNk.d.ts} +10 -3
- package/dist/{useGesture-DbH46EBp.d.ts → useGesture-D1lHeQQ6.d.ts} +1 -1
- package/dist/{useGesture-CnZQnYHH.d.mts → useGesture-DjszaHWz.d.mts} +1 -1
- package/llms.txt +1 -1
- package/package.json +4 -2
- 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 +10 -0
- 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 +15 -45
- 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 +3 -1
- package/src/transitions/runtime.ts +4 -7
- package/src/transitions/spring.ts +7 -2
- package/src/types.ts +9 -2
- package/src/values/extrapolation.ts +21 -0
- package/src/values/index.ts +2 -0
- package/src/values/useGesture.ts +9 -2
- package/src/values/useInterpolatedStyle.ts +42 -19
- package/src/values/useShadow.ts +18 -7
- package/src/values/useSpring.ts +34 -25
- package/src/values/useTransform.ts +24 -20
- package/src/values/useTranslateStyle.ts +31 -0
- package/dist/chunk-24NUL23E.js +0 -8
- package/dist/chunk-4XBX7KOD.js +0 -8
- package/dist/chunk-Z5USXWTE.js +0 -8
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
// dep); Reanimated's own re-export is deprecated.
|
|
3
3
|
import { isWorkletFunction } from 'react-native-worklets'
|
|
4
4
|
import { warnNonWorkletOnce } from '../internal/nonWorkletWarning'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type EasingFunction,
|
|
7
|
+
type EasingFunctionFactory,
|
|
8
|
+
type EasingInput,
|
|
9
|
+
} from '../types'
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Reanimated 3.9+ validates that easing functions used in nested-transition
|
|
@@ -33,7 +37,7 @@ export function ensureWorkletEasing(
|
|
|
33
37
|
// Reanimated 4 `EasingFunctionFactory` — unwrap via `.factory()` before
|
|
34
38
|
// checking worklet status, so the wrapped fn (not the factory wrapper)
|
|
35
39
|
// ends up in the transition config.
|
|
36
|
-
const fn =
|
|
40
|
+
const fn = unwrapEasingFactory(easing)
|
|
37
41
|
if (isWorkletFunction(fn)) return fn
|
|
38
42
|
warnNonWorkletOnce(
|
|
39
43
|
'timing-easing',
|
|
@@ -46,13 +50,21 @@ export function ensureWorkletEasing(
|
|
|
46
50
|
return wrapped
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Return the easing function behind an `EasingInput`: a Reanimated 4
|
|
55
|
+
* `EasingFunctionFactory` is unwrapped through `.factory()`, a plain function
|
|
56
|
+
* passes through. Worklet, so `buildReleaseAnimation` can call it from a
|
|
57
|
+
* gesture `onEnd` handler on the UI thread.
|
|
58
|
+
*/
|
|
59
|
+
export function unwrapEasingFactory(easing: EasingInput): EasingFunction {
|
|
60
|
+
'worklet'
|
|
61
|
+
if (
|
|
62
|
+
typeof easing === 'object' &&
|
|
63
|
+
easing !== null &&
|
|
64
|
+
'factory' in easing &&
|
|
65
|
+
typeof (easing as { factory: unknown }).factory === 'function'
|
|
66
|
+
) {
|
|
67
|
+
return (easing as EasingFunctionFactory).factory()
|
|
68
|
+
}
|
|
69
|
+
return easing as EasingFunction
|
|
58
70
|
}
|
package/src/transitions/index.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export {
|
|
2
2
|
applyDelay,
|
|
3
|
+
applyRepeat,
|
|
4
|
+
normalizeRepeat,
|
|
5
|
+
repeatIterationsOf,
|
|
3
6
|
resolveTransition,
|
|
4
7
|
type AnimationCallback,
|
|
5
8
|
type CallbackFactory,
|
|
9
|
+
type NormalizedRepeat,
|
|
6
10
|
} from './resolve'
|
|
11
|
+
export { DEFAULT_LAYOUT_DURATION, DEFAULT_TIMING_DURATION } from './constants'
|
|
7
12
|
export { resolveAnimatableValue } from './resolveSequence'
|
|
8
13
|
export { cubicBezier } from './cubicBezier'
|
|
9
14
|
export { ensureWorkletEasing } from './easing'
|
package/src/transitions/keys.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -50,7 +50,9 @@ export function resolveAnimatableValue<V extends number | string>(
|
|
|
50
50
|
resolveStep(step, stepBase, factory?.('step', i)),
|
|
51
51
|
)
|
|
52
52
|
const seq = withSequence(...(animations as never[]))
|
|
53
|
-
return applyRepeat(seq, base ? repeatOf(base) : undefined
|
|
53
|
+
return applyRepeat(seq, base ? repeatOf(base) : undefined, {
|
|
54
|
+
sequence: true,
|
|
55
|
+
})
|
|
54
56
|
}
|
|
55
57
|
const step = value as SequenceStep<V>
|
|
56
58
|
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
|
@@ -31,8 +31,6 @@ export interface SpringTransition {
|
|
|
31
31
|
friction?: number
|
|
32
32
|
mass?: number
|
|
33
33
|
velocity?: number
|
|
34
|
-
restSpeedThreshold?: number
|
|
35
|
-
restDisplacementThreshold?: number
|
|
36
34
|
delay?: number
|
|
37
35
|
repeat?: RepeatConfig
|
|
38
36
|
}
|
|
@@ -130,6 +128,15 @@ export type TransitionInput = TransitionConfig | TransitionName
|
|
|
130
128
|
|
|
131
129
|
/**
|
|
132
130
|
* Repeat config — one shape, not three flags. Default `alternate: true`.
|
|
131
|
+
*
|
|
132
|
+
* `count` is the total number of iterations, including the first pass, so
|
|
133
|
+
* `repeat: 2` plays the animation twice. A count below `1` runs the animation
|
|
134
|
+
* once and warns in dev.
|
|
135
|
+
*
|
|
136
|
+
* `alternate` reverses direction on every other pass for a single-value
|
|
137
|
+
* animation. It has no effect on a sequence (an array target): Reanimated
|
|
138
|
+
* restarts a sequence at its first step on every pass. Append the reverse
|
|
139
|
+
* steps to the sequence to alternate.
|
|
133
140
|
*/
|
|
134
141
|
export type RepeatConfig =
|
|
135
142
|
| number
|
|
@@ -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
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
} from './useGesture'
|
|
19
19
|
export {
|
|
20
20
|
useInterpolatedStyle,
|
|
21
|
+
type InterpolatedStyle,
|
|
21
22
|
type InterpolatedStyleMap,
|
|
22
23
|
type NumericStyleKey,
|
|
23
24
|
type TransformKey,
|
|
@@ -37,4 +38,5 @@ export {
|
|
|
37
38
|
type ShadowConfig,
|
|
38
39
|
type UseShadowOptions,
|
|
39
40
|
} from './useShadow'
|
|
41
|
+
export { useTranslateStyle } from './useTranslateStyle'
|
|
40
42
|
export { useVariants } from './useVariants'
|
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,13 +1,14 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
|
+
import type { ImageStyle, TextStyle, ViewStyle } from 'react-native'
|
|
2
3
|
import {
|
|
3
|
-
Extrapolation,
|
|
4
4
|
interpolate,
|
|
5
5
|
interpolateColor,
|
|
6
6
|
useAnimatedStyle,
|
|
7
7
|
type SharedValue,
|
|
8
8
|
} from 'react-native-reanimated'
|
|
9
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
10
|
+
import { mapExtrapolation, type ExtrapolationMode } from './extrapolation'
|
|
9
11
|
import type { ColorStyleKey } from './useColorTransition'
|
|
10
|
-
import type { ExtrapolationMode } from './useTransform'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Numeric style keys `useInterpolatedStyle` can emit directly (not lifted into
|
|
@@ -83,6 +84,29 @@ export type InterpolatedStyleMap = {
|
|
|
83
84
|
[K in ColorStyleKey]?: readonly string[]
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The style shape a map of `K` produces. Transform keys collapse into the
|
|
89
|
+
* single `transform` array the worklet emits; every other key survives under
|
|
90
|
+
* its own name, typed from the RN style family it belongs to.
|
|
91
|
+
*
|
|
92
|
+
* This is what lets the return narrow to `ViewStyle` at a call site whose map
|
|
93
|
+
* holds only view keys. Reanimated's own `useAnimatedStyle` resolves to
|
|
94
|
+
* `DefaultStyle` (`ViewStyle | ImageStyle | TextStyle`), and that union is
|
|
95
|
+
* rejected inside a `StyleProp<ViewStyle>` array — `TextStyle` fails on
|
|
96
|
+
* `cursor` — so a consumer had to cast.
|
|
97
|
+
*/
|
|
98
|
+
export type InterpolatedStyle<K extends keyof InterpolatedStyleMap> = {
|
|
99
|
+
[P in Exclude<K, TransformKey>]: P extends keyof TextStyle
|
|
100
|
+
? TextStyle[P]
|
|
101
|
+
: P extends keyof ImageStyle
|
|
102
|
+
? ImageStyle[P]
|
|
103
|
+
: P extends keyof ViewStyle
|
|
104
|
+
? ViewStyle[P]
|
|
105
|
+
: never
|
|
106
|
+
} & (Extract<K, TransformKey> extends never
|
|
107
|
+
? unknown
|
|
108
|
+
: { transform: NonNullable<ViewStyle['transform']> })
|
|
109
|
+
|
|
86
110
|
export interface UseInterpolatedStyleOptions {
|
|
87
111
|
/**
|
|
88
112
|
* Input range mapped onto every key's output range. Defaults to `[0, 1]`
|
|
@@ -134,12 +158,6 @@ function evenlySpaced(count: number): number[] {
|
|
|
134
158
|
return out
|
|
135
159
|
}
|
|
136
160
|
|
|
137
|
-
function mapExtrapolation(mode: ExtrapolationMode | undefined): Extrapolation {
|
|
138
|
-
if (mode === 'identity') return Extrapolation.IDENTITY
|
|
139
|
-
if (mode === 'extend') return Extrapolation.EXTEND
|
|
140
|
-
return Extrapolation.CLAMP
|
|
141
|
-
}
|
|
142
|
-
|
|
143
161
|
/**
|
|
144
162
|
* Order-preserving structural signature of the map + options. Unlike
|
|
145
163
|
* `stableSig` (which sorts keys), this walks `map` in insertion order because
|
|
@@ -178,8 +196,9 @@ function buildEntries(
|
|
|
178
196
|
? [0, 1]
|
|
179
197
|
: evenlySpaced(output.length)
|
|
180
198
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
199
|
+
if (explicitInput && explicitInput.length !== output.length) {
|
|
200
|
+
warnOnce(
|
|
201
|
+
`interpolated-style-range:${String(key)}:${explicitInput.length}:${output.length}`,
|
|
183
202
|
`[inertia] useInterpolatedStyle: inputRange has ${explicitInput.length} stops but the "${String(
|
|
184
203
|
key,
|
|
185
204
|
)}" output has ${output.length}. They must match — interpolation results are undefined otherwise.`,
|
|
@@ -262,17 +281,17 @@ function buildEntries(
|
|
|
262
281
|
* hook stays fully declarative and hashable so unchanged maps produce zero
|
|
263
282
|
* new UI-thread closures.
|
|
264
283
|
*/
|
|
265
|
-
export function useInterpolatedStyle(
|
|
284
|
+
export function useInterpolatedStyle<K extends keyof InterpolatedStyleMap>(
|
|
266
285
|
progress: SharedValue<number>,
|
|
267
|
-
map: InterpolatedStyleMap,
|
|
286
|
+
map: Pick<InterpolatedStyleMap, K>,
|
|
268
287
|
options?: UseInterpolatedStyleOptions,
|
|
269
|
-
):
|
|
288
|
+
): InterpolatedStyle<K> {
|
|
270
289
|
const extrapolate = mapExtrapolation(options?.extrapolate)
|
|
271
290
|
|
|
272
291
|
// Order-preserving signature: the map's key order is load-bearing (transform
|
|
273
292
|
// lifting emits axes in author order), so `stableSig` (which sorts keys) is
|
|
274
293
|
// wrong here — sign the ordered key/output pairs plus the options directly.
|
|
275
|
-
const sig = buildSignature(map, options)
|
|
294
|
+
const sig = buildSignature(map as InterpolatedStyleMap, options)
|
|
276
295
|
|
|
277
296
|
// Resolve every key's plan once on the JS thread so the worklet body only
|
|
278
297
|
// consumes flat arrays — consistent with the JS-thread resolver principle
|
|
@@ -280,9 +299,15 @@ export function useInterpolatedStyle(
|
|
|
280
299
|
// principle 8). Memoized on `sig` so a fresh-but-equal map literal each
|
|
281
300
|
// render yields the same `entries` reference — Reanimated then sees an
|
|
282
301
|
// unchanged closure dependency and does not rebuild the UI-thread worklet.
|
|
283
|
-
|
|
284
|
-
|
|
302
|
+
const entries = useMemo<Entry[]>(
|
|
303
|
+
() => buildEntries(map as InterpolatedStyleMap, options),
|
|
304
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
305
|
+
[sig],
|
|
306
|
+
)
|
|
285
307
|
|
|
308
|
+
// The worklet builds a `Record<string, unknown>` by design — the emitted keys
|
|
309
|
+
// are only known from `entries` at run time. `InterpolatedStyle<K>` is the
|
|
310
|
+
// static statement of that same shape, so the cast is where the two meet.
|
|
286
311
|
return useAnimatedStyle(() => {
|
|
287
312
|
'worklet'
|
|
288
313
|
const out: Record<string, unknown> = {}
|
|
@@ -313,7 +338,5 @@ export function useInterpolatedStyle(
|
|
|
313
338
|
}
|
|
314
339
|
if (transform.length > 0) out.transform = transform
|
|
315
340
|
return out
|
|
316
|
-
})
|
|
341
|
+
}) as InterpolatedStyle<K>
|
|
317
342
|
}
|
|
318
|
-
|
|
319
|
-
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
|
|
|
@@ -113,13 +115,22 @@ export function useShadow({
|
|
|
113
115
|
// boxShadow layers: parse/pair once on the JS thread into flat records so
|
|
114
116
|
// the worklet only interpolates numbers/colors and concatenates — no
|
|
115
117
|
// frame-time parsing. `[]` when neither side provides the key.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
//
|
|
119
|
+
// Memoised on the inputs' structure: the pairs array is the one non-scalar
|
|
120
|
+
// the worklet captures, and `from` / `to` are usually inline literals, so
|
|
121
|
+
// without the memo a fresh array each render would rebuild the worklet.
|
|
122
|
+
const boxShadowSig = stableSig([from.boxShadow, to.boxShadow])
|
|
123
|
+
const boxShadowPairs = useMemo(
|
|
124
|
+
() =>
|
|
125
|
+
from.boxShadow !== undefined || to.boxShadow !== undefined
|
|
126
|
+
? pairBoxShadowLayers(
|
|
127
|
+
resolveBoxShadowInput(from.boxShadow),
|
|
128
|
+
resolveBoxShadowInput(to.boxShadow),
|
|
129
|
+
)
|
|
130
|
+
: [],
|
|
131
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
|
+
[boxShadowSig],
|
|
133
|
+
)
|
|
123
134
|
|
|
124
135
|
const opacityFrom = from.shadowOpacity ?? 0
|
|
125
136
|
const opacityTo = to.shadowOpacity ?? 0
|
package/src/values/useSpring.ts
CHANGED
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
withSpring,
|
|
7
7
|
type SharedValue,
|
|
8
8
|
} from 'react-native-reanimated'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
lookupNamedTransition,
|
|
11
|
+
useNamedTransitions,
|
|
12
|
+
useShouldReduceMotion,
|
|
13
|
+
} from '../config'
|
|
14
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
10
15
|
import { springToReanimated } from '../transitions/spring'
|
|
11
16
|
import {
|
|
12
17
|
type NamedTransitions,
|
|
@@ -14,8 +19,6 @@ import {
|
|
|
14
19
|
type TransitionName,
|
|
15
20
|
} from '../types'
|
|
16
21
|
|
|
17
|
-
declare const __DEV__: boolean
|
|
18
|
-
|
|
19
22
|
/**
|
|
20
23
|
* Animate a shared value toward `target` with spring physics, using the
|
|
21
24
|
* library's react-spring vocabulary (`tension` / `friction` / `mass`).
|
|
@@ -35,12 +38,16 @@ declare const __DEV__: boolean
|
|
|
35
38
|
* must resolve to a spring config — a name registered as timing / decay /
|
|
36
39
|
* no-animation warns in dev and falls back to the default spring (reach for
|
|
37
40
|
* `useAnimation` when the named transition's type should be honored).
|
|
41
|
+
*
|
|
42
|
+
* Reduced motion (via `<MotionConfig reducedMotion>`) is honoured: the output
|
|
43
|
+
* is assigned the target directly, with no spring, on both paths.
|
|
38
44
|
*/
|
|
39
45
|
export function useSpring(
|
|
40
46
|
target: number | SharedValue<number>,
|
|
41
47
|
config?: SpringTransition | TransitionName,
|
|
42
48
|
): SharedValue<number> {
|
|
43
49
|
const spring = resolveSpringInput(config, useNamedTransitions())
|
|
50
|
+
const shouldReduceMotion = useShouldReduceMotion()
|
|
44
51
|
// Reanimated config is rebuilt only when the public config object changes
|
|
45
52
|
// shape. The worklet path reads this from JS-thread closure capture, which
|
|
46
53
|
// is fine: it's the resolved config that's invariant across UI-thread
|
|
@@ -52,14 +59,7 @@ export function useSpring(
|
|
|
52
59
|
// depending on `config` itself would rebuild the Reanimated config on
|
|
53
60
|
// every render and defeat the memo.
|
|
54
61
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
-
[
|
|
56
|
-
spring?.tension,
|
|
57
|
-
spring?.friction,
|
|
58
|
-
spring?.mass,
|
|
59
|
-
spring?.velocity,
|
|
60
|
-
spring?.restSpeedThreshold,
|
|
61
|
-
spring?.restDisplacementThreshold,
|
|
62
|
-
],
|
|
62
|
+
[spring?.tension, spring?.friction, spring?.mass, spring?.velocity],
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
const isSharedTarget = isSharedValue(target)
|
|
@@ -72,28 +72,38 @@ export function useSpring(
|
|
|
72
72
|
// a stale closure.
|
|
73
73
|
useEffect(() => {
|
|
74
74
|
if (isSharedTarget) return
|
|
75
|
-
output.value =
|
|
75
|
+
output.value = shouldReduceMotion
|
|
76
|
+
? (target as number)
|
|
77
|
+
: withSpring(target as number, reanimConfig)
|
|
76
78
|
// `output` is identity-stable per hook instance (Reanimated guarantee).
|
|
77
79
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
-
}, [isSharedTarget, target, reanimConfig])
|
|
80
|
+
}, [isSharedTarget, target, reanimConfig, shouldReduceMotion])
|
|
79
81
|
|
|
80
82
|
// SharedValue path. `useAnimatedReaction` runs the prepare worklet whenever
|
|
81
83
|
// its returned value changes; we read `.value` off the target SV and pipe
|
|
82
84
|
// it through `withSpring` on the UI thread. When the target is a plain
|
|
83
85
|
// number we never declare a source so the reaction is inert (returns
|
|
84
86
|
// `null`, never fires `react`).
|
|
87
|
+
//
|
|
88
|
+
// `source` is in the dependency list on purpose. An explicit list replaces
|
|
89
|
+
// Reanimated's closure-derived one, so a caller that swaps which shared
|
|
90
|
+
// value drives the spring would otherwise keep the reaction wired to the
|
|
91
|
+
// old one — the same stale-closure class `useColorCascade` fixed in 0.0.4.
|
|
92
|
+
// A plain-number target maps to `null`, so a changing number does not
|
|
93
|
+
// re-register the inert reaction.
|
|
94
|
+
const source = isSharedTarget ? target : null
|
|
85
95
|
useAnimatedReaction(
|
|
86
96
|
() => {
|
|
87
97
|
'worklet'
|
|
88
|
-
if (
|
|
89
|
-
return
|
|
98
|
+
if (source === null) return null
|
|
99
|
+
return source.value
|
|
90
100
|
},
|
|
91
101
|
(next, prev) => {
|
|
92
102
|
'worklet'
|
|
93
103
|
if (next === null || next === prev) return
|
|
94
|
-
output.value = withSpring(next, reanimConfig)
|
|
104
|
+
output.value = shouldReduceMotion ? next : withSpring(next, reanimConfig)
|
|
95
105
|
},
|
|
96
|
-
[
|
|
106
|
+
[source, reanimConfig, shouldReduceMotion],
|
|
97
107
|
)
|
|
98
108
|
|
|
99
109
|
// Stop the in-flight spring when the owning component unmounts so its
|
|
@@ -122,14 +132,13 @@ function resolveSpringInput(
|
|
|
122
132
|
if (typeof config !== 'string') return config
|
|
123
133
|
const cfg = lookupNamedTransition(config, registry)
|
|
124
134
|
if (cfg.type === undefined || cfg.type === 'spring') return cfg
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
135
|
+
warnOnce(
|
|
136
|
+
`spring-only:${config}`,
|
|
137
|
+
`[inertia] Named transition "${config}" resolves to type ` +
|
|
138
|
+
`'${cfg.type}', but useSpring / useBooleanSpring are spring-only — ` +
|
|
139
|
+
`falling back to the default spring. Use useAnimation to honor ` +
|
|
140
|
+
`non-spring named transitions.`,
|
|
141
|
+
)
|
|
133
142
|
return undefined
|
|
134
143
|
}
|
|
135
144
|
|