@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
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
useNamedTransitions,
|
|
22
22
|
useShouldReduceMotion,
|
|
23
23
|
} from '../config'
|
|
24
|
-
import { isFocusVisible } from '../gestures'
|
|
24
|
+
import { installFocusVisibility, isFocusVisible } from '../gestures'
|
|
25
25
|
import {
|
|
26
26
|
layersToPayload,
|
|
27
27
|
normalizeBoxShadow,
|
|
@@ -47,6 +47,7 @@ import { useStaggerDelay } from '../stagger'
|
|
|
47
47
|
import {
|
|
48
48
|
applyDelay,
|
|
49
49
|
isTopLevelTransition,
|
|
50
|
+
repeatIterationsOf,
|
|
50
51
|
resolveAnimatableValue,
|
|
51
52
|
resolveTransition,
|
|
52
53
|
stableSig,
|
|
@@ -271,6 +272,18 @@ const SHARED_STYLE_KEY_SET = new Set<AnimatableKey>(SHARED_STYLE_KEYS)
|
|
|
271
272
|
*/
|
|
272
273
|
type AnimatableSlotValue = number | string | BoxShadowPayload
|
|
273
274
|
|
|
275
|
+
/**
|
|
276
|
+
* The keys one instance animates plus the three flags the worklet branches
|
|
277
|
+
* on. Grown monotonically on the JS thread and mirrored to the UI thread
|
|
278
|
+
* through a shared value — see `activeSetSV` in the factory.
|
|
279
|
+
*/
|
|
280
|
+
type ActiveKeySet = {
|
|
281
|
+
keys: readonly AnimatableKey[]
|
|
282
|
+
hasTransform: boolean
|
|
283
|
+
hasShadowOffset: boolean
|
|
284
|
+
hasBoxShadow: boolean
|
|
285
|
+
}
|
|
286
|
+
|
|
274
287
|
/**
|
|
275
288
|
* Resting `boxShadow` — no shadow at all. Frozen and hoisted so every
|
|
276
289
|
* primitive shares one reference and an untouched slot never allocates.
|
|
@@ -603,10 +616,10 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
603
616
|
// render silently dropped the new key (its SV updated, but the
|
|
604
617
|
// worklet — which iterates this set — never read it).
|
|
605
618
|
//
|
|
606
|
-
// Growing-only keeps the worklet stable: the
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
//
|
|
619
|
+
// Growing-only keeps the worklet stable: the set object only changes on
|
|
620
|
+
// the renders that actually add a key. The worklet reads it through a
|
|
621
|
+
// shared value (`activeSetSV`, below), so an expansion crosses to the UI
|
|
622
|
+
// thread once per growth and nothing churns frame-to-frame.
|
|
610
623
|
const hasLayoutId = layoutId !== undefined
|
|
611
624
|
|
|
612
625
|
// The static `style`, flattened at most once per render and only when
|
|
@@ -668,14 +681,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
668
681
|
}
|
|
669
682
|
}
|
|
670
683
|
|
|
671
|
-
const
|
|
672
|
-
const hasTransformRef = useRef<boolean>(false)
|
|
673
|
-
const hasShadowOffsetRef = useRef<boolean>(false)
|
|
674
|
-
const hasBoxShadowRef = useRef<boolean>(false)
|
|
684
|
+
const activeSetRef = useRef<ActiveKeySet | null>(null)
|
|
675
685
|
// Expand the active set only when this render touched a key we haven't
|
|
676
|
-
// recorded yet. When nothing new appears we keep the existing
|
|
677
|
-
// identity so the
|
|
678
|
-
const prevActive =
|
|
686
|
+
// recorded yet. When nothing new appears we keep the existing object
|
|
687
|
+
// identity so the JS-side readers below see a stable value.
|
|
688
|
+
const prevActive = activeSetRef.current?.keys ?? null
|
|
679
689
|
let grew = prevActive === null
|
|
680
690
|
if (!grew && prevActive) {
|
|
681
691
|
for (const k of touched) {
|
|
@@ -688,14 +698,13 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
688
698
|
if (grew) {
|
|
689
699
|
const merged = new Set<AnimatableKey>(prevActive ?? [])
|
|
690
700
|
for (const k of touched) merged.add(k)
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
hasBoxShadowRef.current = activeKeysRef.current.includes('boxShadow')
|
|
701
|
+
const keys = ALL_KEYS.filter((k) => merged.has(k))
|
|
702
|
+
activeSetRef.current = {
|
|
703
|
+
keys,
|
|
704
|
+
hasTransform: keys.some((k) => TRANSFORM_KEY_SET.has(k)),
|
|
705
|
+
hasShadowOffset: keys.some((k) => SHADOW_OFFSET_KEY_SET.has(k)),
|
|
706
|
+
hasBoxShadow: keys.includes('boxShadow'),
|
|
707
|
+
}
|
|
699
708
|
// Animating `boxShadow` alongside the native `shadow*` keys puts two
|
|
700
709
|
// shadow systems on one element; whichever the underlying view resolves
|
|
701
710
|
// last wins, and the result reads as a bug rather than a choice.
|
|
@@ -703,10 +712,8 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
703
712
|
// decides it, and this block only runs when that set grows.
|
|
704
713
|
if (
|
|
705
714
|
__DEV__ &&
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
(k) => k !== 'boxShadow' && k.startsWith('shadow'),
|
|
709
|
-
)
|
|
715
|
+
activeSetRef.current.hasBoxShadow &&
|
|
716
|
+
keys.some((k) => k !== 'boxShadow' && k.startsWith('shadow'))
|
|
710
717
|
) {
|
|
711
718
|
warnOnce(
|
|
712
719
|
'boxShadow-with-native-shadow',
|
|
@@ -717,6 +724,25 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
717
724
|
)
|
|
718
725
|
}
|
|
719
726
|
}
|
|
727
|
+
const activeKeys = activeSetRef.current!.keys
|
|
728
|
+
|
|
729
|
+
// The worklet's copy of the active set. It must be a shared value, not
|
|
730
|
+
// the ref above: react-native-worklets clones a captured plain object
|
|
731
|
+
// once, when the worklet is first serialised, and in dev it freezes the
|
|
732
|
+
// original so later writes to `.current` are dropped with a warning. A
|
|
733
|
+
// ref read inside the worklet therefore only ever sees the mount-time
|
|
734
|
+
// set, and a key added after mount drives its shared value but never
|
|
735
|
+
// reaches the screen (Worklet rule 2). The Jest mock runs the worklet on
|
|
736
|
+
// the JS thread, so only a device shows the difference. The write lives
|
|
737
|
+
// in an effect because Reanimated's strict mode warns on a shared-value
|
|
738
|
+
// write during render; it is declared before the value-driving effects
|
|
739
|
+
// below, so the grown set reaches the UI thread in the same commit as
|
|
740
|
+
// the first animation of the new key.
|
|
741
|
+
const activeSetSV = useSharedValue<ActiveKeySet>(activeSetRef.current!)
|
|
742
|
+
const activeSet = activeSetRef.current!
|
|
743
|
+
useEffect(() => {
|
|
744
|
+
if (activeSetSV.value !== activeSet) activeSetSV.value = activeSet
|
|
745
|
+
}, [activeSetSV, activeSet])
|
|
720
746
|
|
|
721
747
|
// Which keys a record actually drives *right now*. Everything else in
|
|
722
748
|
// the active set is there only because a gesture sub-state, an `exit`
|
|
@@ -741,7 +767,6 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
741
767
|
// Resting values pulled off the static `style`, for active keys nothing
|
|
742
768
|
// has driven. Gated so the common `animate`-only instance never flattens
|
|
743
769
|
// a style it has no use for.
|
|
744
|
-
const activeKeys = activeKeysRef.current!
|
|
745
770
|
const everDriven = everDrivenRef.current
|
|
746
771
|
const styleResting: Partial<Record<AnimatableKey, AnimatableSlotValue>> =
|
|
747
772
|
{}
|
|
@@ -857,7 +882,7 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
857
882
|
|
|
858
883
|
const styleRestingSig = stableSig(styleResting)
|
|
859
884
|
useEffect(() => {
|
|
860
|
-
for (const key of
|
|
885
|
+
for (const key of activeKeys) {
|
|
861
886
|
if (everDrivenRef.current.has(key)) continue
|
|
862
887
|
sharedValues[key].value = (styleResting[key] ??
|
|
863
888
|
DEFAULT_RESTING[key]) as never
|
|
@@ -926,6 +951,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
926
951
|
(isExiting ? '|exit' : '') +
|
|
927
952
|
(shouldReduceMotion ? '|rm' : '')
|
|
928
953
|
const transitionSig = stableSig(transition)
|
|
954
|
+
// The callback factory skips callback installation when there is no
|
|
955
|
+
// `onAnimationEnd` and nothing to settle, so a handler that appears
|
|
956
|
+
// after mount needs the effect to run again. Keyed on presence, not
|
|
957
|
+
// identity: a changing handler is read through `onAnimationEndRef`.
|
|
958
|
+
const hasAnimationEnd = onAnimationEnd !== undefined
|
|
929
959
|
|
|
930
960
|
// Stable ref to the live `safeToRemove` so the effect's settle-counter
|
|
931
961
|
// closure can reach the latest <Presence> binding without retriggering.
|
|
@@ -943,6 +973,10 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
943
973
|
return
|
|
944
974
|
}
|
|
945
975
|
|
|
976
|
+
// `done` doubles as the run token. When this effect re-runs, the
|
|
977
|
+
// cleanup below sets it, so the callbacks of the superseded animations
|
|
978
|
+
// — which Reanimated fires with `finished: false` as they are replaced
|
|
979
|
+
// — cannot drain this run's counter and release <Presence> early.
|
|
946
980
|
let pending = 0
|
|
947
981
|
let done = false
|
|
948
982
|
const onSettle = () => {
|
|
@@ -1095,8 +1129,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
1095
1129
|
if (isExiting && pending === 0) {
|
|
1096
1130
|
safeToRemoveRef.current?.()
|
|
1097
1131
|
}
|
|
1132
|
+
return () => {
|
|
1133
|
+
done = true
|
|
1134
|
+
}
|
|
1098
1135
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1099
|
-
}, [baseSig, transitionSig])
|
|
1136
|
+
}, [baseSig, transitionSig, hasAnimationEnd])
|
|
1100
1137
|
|
|
1101
1138
|
// Per-layer progress: when a sub-state activation flips, animate its
|
|
1102
1139
|
// progress SV 0↔1 with the layer's own transition (or the parent
|
|
@@ -1162,7 +1199,7 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
1162
1199
|
if (!hasLayoutId) return undefined
|
|
1163
1200
|
const out: SharedStyleSnapshot = {}
|
|
1164
1201
|
let any = false
|
|
1165
|
-
for (const key of
|
|
1202
|
+
for (const key of activeKeys) {
|
|
1166
1203
|
if (!SHARED_STYLE_KEY_SET.has(key)) continue
|
|
1167
1204
|
const v = sharedValues[key].value
|
|
1168
1205
|
// Structured slots can't be carried, and a key nothing ever gave a
|
|
@@ -1186,10 +1223,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
1186
1223
|
const carry = sharedLayout.carry
|
|
1187
1224
|
|
|
1188
1225
|
const animatedStyle = useAnimatedStyle(() => {
|
|
1189
|
-
const
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1226
|
+
const activeSet = activeSetSV.value
|
|
1227
|
+
const activeKeys = activeSet.keys
|
|
1228
|
+
const hasTransform = activeSet.hasTransform
|
|
1229
|
+
const hasShadowOffset = activeSet.hasShadowOffset
|
|
1230
|
+
const hasBoxShadow = activeSet.hasBoxShadow
|
|
1193
1231
|
const out: Record<string, unknown> = {}
|
|
1194
1232
|
const transform: Array<Record<string, unknown>> = []
|
|
1195
1233
|
// shadow-offset reassembly buffers. The two synthetic axis SVs feed in
|
|
@@ -1448,7 +1486,7 @@ type SharedValueMap = Record<AnimatableKey, SharedValue<AnimatableSlotValue>>
|
|
|
1448
1486
|
* The ESLint exhaustive-deps rule can't see through the loop, hence the
|
|
1449
1487
|
* disable — the invariant it would check for us is asserted above instead.
|
|
1450
1488
|
*
|
|
1451
|
-
* Unused shared values are cheap; the worklet skips them via `
|
|
1489
|
+
* Unused shared values are cheap; the worklet skips them via `activeSetSV`.
|
|
1452
1490
|
* Color keys are seeded with the initial color string so Reanimated's value
|
|
1453
1491
|
* setter recognizes the slot as a color from the first `withSpring` /
|
|
1454
1492
|
* `withTiming` call.
|
|
@@ -1805,12 +1843,9 @@ function stepCountOf(v: AnimatableValue<number | string> | undefined): number {
|
|
|
1805
1843
|
*/
|
|
1806
1844
|
function totalIterationsOf(cfg: TransitionConfig | undefined): number {
|
|
1807
1845
|
if (!cfg || cfg.type === 'no-animation' || cfg.type === 'decay') return 1
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
if (typeof r === 'number') return r
|
|
1812
|
-
if (r.count === 'infinite') return Number.POSITIVE_INFINITY
|
|
1813
|
-
return r.count
|
|
1846
|
+
// Shares the resolver's count rule, so a `repeat: 0` that the resolver
|
|
1847
|
+
// treats as "run once" is also counted as one iteration here.
|
|
1848
|
+
return repeatIterationsOf(cfg.repeat)
|
|
1814
1849
|
}
|
|
1815
1850
|
|
|
1816
1851
|
/**
|
|
@@ -1880,16 +1915,23 @@ function resolveAnimateInput(
|
|
|
1880
1915
|
variants: VariantsMap<unknown> | undefined,
|
|
1881
1916
|
controllerKey: string | undefined,
|
|
1882
1917
|
): AnimateStyle<unknown> | undefined {
|
|
1918
|
+
if (controllerKey !== undefined && animate !== undefined) {
|
|
1919
|
+
warnOnce(
|
|
1920
|
+
'controller-and-animate',
|
|
1921
|
+
'[inertia] Both `controller` and `animate` are set on the same ' +
|
|
1922
|
+
'primitive. The controller drives the animation and `animate` is ' +
|
|
1923
|
+
'ignored — remove one of the two.',
|
|
1924
|
+
)
|
|
1925
|
+
}
|
|
1883
1926
|
if (controllerKey !== undefined && variants && controllerKey in variants) {
|
|
1884
1927
|
return variants[controllerKey]
|
|
1885
1928
|
}
|
|
1886
1929
|
if (typeof animate === 'string') {
|
|
1887
1930
|
if (variants && animate in variants) return variants[animate]
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1931
|
+
warnOnce(
|
|
1932
|
+
`missing-variant:${animate}`,
|
|
1933
|
+
`[inertia] animate="${animate}" but no matching variant. Did you forget to pass \`variants\`?`,
|
|
1934
|
+
)
|
|
1893
1935
|
return undefined
|
|
1894
1936
|
}
|
|
1895
1937
|
return animate as AnimateStyle<unknown> | undefined
|
|
@@ -2047,6 +2089,12 @@ function useGestureHandlers(
|
|
|
2047
2089
|
const hasFocused = gesture?.focused ? 1 : 0
|
|
2048
2090
|
const hasFocusVisible = gesture?.focusVisible ? 1 : 0
|
|
2049
2091
|
const hasHovered = gesture?.hovered ? 1 : 0
|
|
2092
|
+
// The web modality listeners attach on the first mount that tracks
|
|
2093
|
+
// `focusVisible` — see `focusVisibility.ts` for why mount time is early
|
|
2094
|
+
// enough and why import time is not an option.
|
|
2095
|
+
useEffect(() => {
|
|
2096
|
+
if (hasFocusVisible) installFocusVisibility()
|
|
2097
|
+
}, [hasFocusVisible])
|
|
2050
2098
|
return useMemo(() => {
|
|
2051
2099
|
if (!gesture) return {}
|
|
2052
2100
|
const handlers: GestureHandlers = {}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
} from 'react'
|
|
12
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
12
13
|
import { PresenceContext, type PresenceContextValue } from './PresenceContext'
|
|
13
14
|
|
|
14
15
|
interface RenderEntry {
|
|
@@ -38,11 +39,10 @@ export function Presence({ children }: { children: ReactNode }) {
|
|
|
38
39
|
Children.forEach(children, (child) => {
|
|
39
40
|
if (!isValidElement(child)) return
|
|
40
41
|
if (child.key === null) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
42
|
+
warnOnce(
|
|
43
|
+
'presence-keyless-child',
|
|
44
|
+
'[inertia] <Presence> children must have a `key`. Skipping a keyless child.',
|
|
45
|
+
)
|
|
46
46
|
return
|
|
47
47
|
}
|
|
48
48
|
out.push(child)
|
|
@@ -224,5 +224,3 @@ function PresenceItem({
|
|
|
224
224
|
</PresenceContext.Provider>
|
|
225
225
|
)
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
declare const __DEV__: boolean
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clamp `value` to `[min, max]`. When `elastic > 0` the overshoot past a
|
|
3
|
+
* bound is scaled by `elastic` instead of hard-clamped, giving a rubber-band
|
|
4
|
+
* feel. `min` / `max` may be `undefined` to leave that side unbounded.
|
|
5
|
+
*
|
|
6
|
+
* Worklet, so it runs both inside a gesture-handler pan handler on the UI
|
|
7
|
+
* thread (`useDrag`) and from a JS-thread `PanResponder` callback
|
|
8
|
+
* (`useTouchDrag`).
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* applyBounds(120, -100, 100, 0) // 100
|
|
12
|
+
* applyBounds(120, -100, 100, 0.5) // 110
|
|
13
|
+
* applyBounds(50, undefined, 100, 0) // 50
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function applyBounds(
|
|
17
|
+
value: number,
|
|
18
|
+
min: number | undefined,
|
|
19
|
+
max: number | undefined,
|
|
20
|
+
elastic: number,
|
|
21
|
+
): number {
|
|
22
|
+
'worklet'
|
|
23
|
+
if (min !== undefined && value < min) {
|
|
24
|
+
return elastic > 0 ? min + (value - min) * elastic : min
|
|
25
|
+
}
|
|
26
|
+
if (max !== undefined && value > max) {
|
|
27
|
+
return elastic > 0 ? max + (value - max) * elastic : max
|
|
28
|
+
}
|
|
29
|
+
return value
|
|
30
|
+
}
|
package/src/touch/index.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
1
|
+
import { useMemo, useRef } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
PanResponder,
|
|
4
4
|
type PanResponderGestureState,
|
|
5
5
|
type PanResponderInstance,
|
|
6
6
|
} from 'react-native'
|
|
7
|
-
import {
|
|
8
|
-
useAnimatedStyle,
|
|
9
|
-
useSharedValue,
|
|
10
|
-
type SharedValue,
|
|
11
|
-
} from 'react-native-reanimated'
|
|
7
|
+
import { useSharedValue, type SharedValue } from 'react-native-reanimated'
|
|
12
8
|
import { buildReleaseAnimation } from '../transitions'
|
|
9
|
+
import {
|
|
10
|
+
useTranslateStyle,
|
|
11
|
+
type TranslateStyle,
|
|
12
|
+
} from '../values/useTranslateStyle'
|
|
13
|
+
import { applyBounds } from './applyBounds'
|
|
13
14
|
import type { TransitionConfig } from '../types'
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -23,7 +24,7 @@ export interface UseTouchDragResult {
|
|
|
23
24
|
/** Spread onto a `View` / `Pressable` to install the pan responder. */
|
|
24
25
|
panHandlers: PanResponderInstance['panHandlers']
|
|
25
26
|
/** Stable animated `transform` style. */
|
|
26
|
-
animatedStyle:
|
|
27
|
+
animatedStyle: TranslateStyle
|
|
27
28
|
/** Live x translation, persistent across gestures. */
|
|
28
29
|
dragX: SharedValue<number>
|
|
29
30
|
/** Live y translation, persistent across gestures. */
|
|
@@ -166,23 +167,17 @@ export function useTouchDrag(
|
|
|
166
167
|
const top = constraints?.top
|
|
167
168
|
const bottom = constraints?.bottom
|
|
168
169
|
const elasticCoef = elastic
|
|
169
|
-
|
|
170
|
+
|
|
171
|
+
// Callbacks are read through a ref at call time, so an inline arrow in
|
|
172
|
+
// `options` does not rebuild the PanResponder every render. PanResponder
|
|
173
|
+
// callbacks run on the JS thread, so a ref read is safe here.
|
|
174
|
+
const callbacksRef = useRef(options)
|
|
175
|
+
callbacksRef.current = options
|
|
170
176
|
|
|
171
177
|
const responder = useMemo(
|
|
172
178
|
() => buildResponder(),
|
|
173
179
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
174
|
-
[
|
|
175
|
-
lockX,
|
|
176
|
-
lockY,
|
|
177
|
-
left,
|
|
178
|
-
right,
|
|
179
|
-
top,
|
|
180
|
-
bottom,
|
|
181
|
-
elasticCoef,
|
|
182
|
-
onDragStart,
|
|
183
|
-
onDragEnd,
|
|
184
|
-
onRelease,
|
|
185
|
-
],
|
|
180
|
+
[lockX, lockY, left, right, top, bottom, elasticCoef],
|
|
186
181
|
)
|
|
187
182
|
|
|
188
183
|
// Hoisted out of the inline `useMemo` factory to keep the dep list readable
|
|
@@ -196,6 +191,7 @@ export function useTouchDrag(
|
|
|
196
191
|
// `@rootnative/inertia-gestures` API (px/sec from gesture-handler).
|
|
197
192
|
const vx = g.vx * 1000
|
|
198
193
|
const vy = g.vy * 1000
|
|
194
|
+
const { onRelease, onDragEnd } = callbacksRef.current
|
|
199
195
|
if (onRelease) {
|
|
200
196
|
const result = onRelease({ x, y, velocity: { x: vx, y: vy } })
|
|
201
197
|
if (result) {
|
|
@@ -228,7 +224,7 @@ export function useTouchDrag(
|
|
|
228
224
|
startX.value = dragX.value
|
|
229
225
|
startY.value = dragY.value
|
|
230
226
|
isDragging.value = true
|
|
231
|
-
|
|
227
|
+
callbacksRef.current.onDragStart?.()
|
|
232
228
|
},
|
|
233
229
|
onPanResponderMove: (_e, g) => {
|
|
234
230
|
if (lockX) {
|
|
@@ -253,9 +249,7 @@ export function useTouchDrag(
|
|
|
253
249
|
})
|
|
254
250
|
}
|
|
255
251
|
|
|
256
|
-
const animatedStyle =
|
|
257
|
-
transform: [{ translateX: dragX.value }, { translateY: dragY.value }],
|
|
258
|
-
}))
|
|
252
|
+
const animatedStyle = useTranslateStyle(dragX, dragY)
|
|
259
253
|
|
|
260
254
|
return {
|
|
261
255
|
panHandlers: responder.panHandlers,
|
|
@@ -265,25 +259,3 @@ export function useTouchDrag(
|
|
|
265
259
|
isDragging,
|
|
266
260
|
}
|
|
267
261
|
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Clamp `value` to `[min, max]`. When `elastic > 0` the overshoot past a
|
|
271
|
-
* bound is scaled by `elastic`, giving a rubber-band feel. `min` / `max`
|
|
272
|
-
* may be `undefined` to leave that side unbounded.
|
|
273
|
-
*
|
|
274
|
-
* JS-thread (PanResponder callbacks are JS, not worklets).
|
|
275
|
-
*/
|
|
276
|
-
function applyBounds(
|
|
277
|
-
value: number,
|
|
278
|
-
min: number | undefined,
|
|
279
|
-
max: number | undefined,
|
|
280
|
-
elastic: number,
|
|
281
|
-
): number {
|
|
282
|
-
if (min !== undefined && value < min) {
|
|
283
|
-
return elastic > 0 ? min + (value - min) * elastic : min
|
|
284
|
-
}
|
|
285
|
-
if (max !== undefined && value > max) {
|
|
286
|
-
return elastic > 0 ? max + (value - max) * elastic : max
|
|
287
|
-
}
|
|
288
|
-
return value
|
|
289
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `duration` for a `timing` transition that does not set one, in ms.
|
|
3
|
+
* Shared by the JS-thread resolver (`resolve.ts`) and the UI-thread builder
|
|
4
|
+
* (`runtime.ts`) so the two paths cannot drift apart.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_TIMING_DURATION = 250
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default `duration` for a `timing` layout transition (`layout` prop and
|
|
10
|
+
* `layoutId` shared-element legs), in ms. Layout moves cover more distance
|
|
11
|
+
* than a property tween, so the default is longer than
|
|
12
|
+
* `DEFAULT_TIMING_DURATION`.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_LAYOUT_DURATION = 300
|
|
@@ -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'
|