@rootnative/inertia 0.0.3 → 0.0.4
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 +41 -5
- package/README.md +5 -3
- package/dist/{chunk-SGUHE5CX.mjs → chunk-4ASOELLA.mjs} +1 -1
- package/dist/{chunk-CSODMRJ7.mjs → chunk-55W7BNO2.mjs} +475 -83
- package/dist/chunk-5SRD2WCO.js +8 -0
- package/dist/{chunk-PTRF47DA.js → chunk-7A6WTXNK.js} +10 -3
- package/dist/{chunk-3XTVY34H.js → chunk-CENTULW6.js} +2 -2
- package/dist/{chunk-4PEHWDAZ.js → chunk-D5KAHWMC.js} +7 -7
- package/dist/{chunk-O22NXXCZ.mjs → chunk-J5Q46OPW.mjs} +1 -1
- package/dist/{chunk-RGNX6UZN.mjs → chunk-K3CLP74S.mjs} +2 -2
- package/dist/{chunk-BP3Y2SHQ.js → chunk-MYE5NMXW.js} +495 -101
- package/dist/{chunk-OQV66TBQ.mjs → chunk-NAOXQJ7U.mjs} +1 -1
- package/dist/{chunk-R63GIUNU.mjs → chunk-QKC3APP4.mjs} +1 -1
- package/dist/{chunk-46P57VMY.mjs → chunk-RF7J5FRM.mjs} +1 -1
- package/dist/{chunk-CY7Y64C3.mjs → chunk-TU2XFPAK.mjs} +10 -4
- package/dist/chunk-TVFNJ25Z.js +8 -0
- package/dist/chunk-VLDIFL3F.js +8 -0
- package/dist/{chunk-7AOERN53.js → chunk-WVRCKOPO.js} +3 -3
- package/dist/{chunk-W5MC3P4N.js → chunk-XMEVPEMH.js} +2 -2
- package/dist/{chunk-BQQTHG2V.mjs → chunk-YKGY22SB.mjs} +1 -1
- package/dist/gestureLayer/index.d.mts +2 -2
- package/dist/gestureLayer/index.d.ts +2 -2
- package/dist/gestureLayer/index.js +10 -10
- package/dist/gestureLayer/index.mjs +3 -3
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +48 -169
- package/dist/index.mjs +17 -137
- package/dist/motion/Image.d.mts +1 -1
- package/dist/motion/Image.d.ts +1 -1
- package/dist/motion/Image.js +5 -5
- package/dist/motion/Image.mjs +4 -4
- package/dist/motion/Pressable.d.mts +1 -1
- package/dist/motion/Pressable.d.ts +1 -1
- package/dist/motion/Pressable.js +5 -5
- package/dist/motion/Pressable.mjs +4 -4
- package/dist/motion/ScrollView.d.mts +1 -1
- package/dist/motion/ScrollView.d.ts +1 -1
- package/dist/motion/ScrollView.js +5 -5
- package/dist/motion/ScrollView.mjs +4 -4
- package/dist/motion/Text.d.mts +1 -1
- package/dist/motion/Text.d.ts +1 -1
- package/dist/motion/Text.js +5 -5
- package/dist/motion/Text.mjs +4 -4
- package/dist/motion/View.d.mts +1 -1
- package/dist/motion/View.d.ts +1 -1
- package/dist/motion/View.js +5 -5
- package/dist/motion/View.mjs +4 -4
- package/dist/touch/index.d.mts +1 -1
- package/dist/touch/index.d.ts +1 -1
- package/dist/touch/index.js +3 -3
- package/dist/touch/index.mjs +1 -1
- package/dist/{types-G1dBU2IQ.d.mts → types-37pN82yh.d.mts} +49 -14
- package/dist/{types-G1dBU2IQ.d.ts → types-37pN82yh.d.ts} +49 -14
- package/dist/{useGesture-D1e4ixJo.d.ts → useGesture-Cl4uzZIr.d.ts} +1 -1
- package/dist/{useGesture-RFY6kTL3.d.mts → useGesture-_IKkS8Dv.d.mts} +1 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/internal/boxShadow.ts +177 -6
- package/src/internal/nonWorkletWarning.ts +4 -6
- package/src/internal/warnOnce.ts +28 -0
- package/src/layout/index.ts +10 -0
- package/src/layout/measureWindow.ts +135 -0
- package/src/layout/sharedRegistry.ts +107 -18
- package/src/layout/useSharedLayout.ts +256 -91
- package/src/motion/createMotionComponent.tsx +378 -39
- package/src/motion/installCheck.ts +1 -1
- package/src/transitions/index.ts +1 -1
- package/src/types.ts +52 -12
- package/src/values/useColorTransition.ts +1 -1
- package/dist/chunk-FNVFV4EY.js +0 -8
- package/dist/chunk-FWQOXA43.js +0 -8
- package/dist/chunk-KBP4LR75.js +0 -8
|
@@ -22,9 +22,17 @@ import {
|
|
|
22
22
|
useShouldReduceMotion,
|
|
23
23
|
} from '../config'
|
|
24
24
|
import { isFocusVisible } from '../gestures'
|
|
25
|
+
import {
|
|
26
|
+
normalizeBoxShadow,
|
|
27
|
+
prepareBoxShadowAnimation,
|
|
28
|
+
type AnimatedBoxShadowLayer,
|
|
29
|
+
type SplitBoxShadow,
|
|
30
|
+
} from '../internal/boxShadow'
|
|
31
|
+
import { warnOnce } from '../internal/warnOnce'
|
|
25
32
|
import {
|
|
26
33
|
resolveLayoutTransition,
|
|
27
34
|
type LayoutProp,
|
|
35
|
+
type SharedStyleSnapshot,
|
|
28
36
|
useSharedLayout,
|
|
29
37
|
} from '../layout'
|
|
30
38
|
import { usePresence } from '../presence'
|
|
@@ -33,6 +41,7 @@ import {
|
|
|
33
41
|
resolveAnimatableValue,
|
|
34
42
|
resolveTransition,
|
|
35
43
|
stableSig,
|
|
44
|
+
type CallbackFactory,
|
|
36
45
|
} from '../transitions'
|
|
37
46
|
import { ensureReanimatedInstalled } from './installCheck'
|
|
38
47
|
import {
|
|
@@ -42,6 +51,7 @@ import {
|
|
|
42
51
|
type GestureLayerTransitions,
|
|
43
52
|
type GestureSubStates,
|
|
44
53
|
type MotionComponent,
|
|
54
|
+
type BoxShadowInput,
|
|
45
55
|
type MotionProps,
|
|
46
56
|
type PerPropertyTransition,
|
|
47
57
|
type Transition,
|
|
@@ -108,6 +118,42 @@ const COLOR_KEYS = [
|
|
|
108
118
|
// these keys directly.
|
|
109
119
|
const SHADOW_OFFSET_KEYS = ['shadowOffsetWidth', 'shadowOffsetHeight'] as const
|
|
110
120
|
|
|
121
|
+
// Keys whose shared value holds a structure rather than a scalar. `boxShadow`
|
|
122
|
+
// is the only one: its slot carries an array of layer objects, which
|
|
123
|
+
// Reanimated's animation drivers recurse into, animating each leaf number and
|
|
124
|
+
// color independently (`arrayOnStart` → `objectOnStart` in Reanimated's
|
|
125
|
+
// `animation/util.ts`).
|
|
126
|
+
//
|
|
127
|
+
// Passing the CSS string through instead would NOT work — a box-shadow string
|
|
128
|
+
// isn't a color, so it lands in Reanimated's prefix-number-suffix branch,
|
|
129
|
+
// which is built for values like '100%' and would pull a single number out of
|
|
130
|
+
// a four-value shadow. All string parsing therefore happens on the JS thread,
|
|
131
|
+
// once per change, in `internal/boxShadow.ts`.
|
|
132
|
+
const STRUCTURED_KEYS = ['boxShadow'] as const
|
|
133
|
+
|
|
134
|
+
// Keys a shared-element transition (`layoutId`) carries from the source
|
|
135
|
+
// element to the target, crossfading them alongside the rect FLIP. Everything
|
|
136
|
+
// here is either a scalar or a color, so the worklet's existing lerp /
|
|
137
|
+
// `interpolateColor` branches handle it with no new machinery.
|
|
138
|
+
//
|
|
139
|
+
// Transform keys are deliberately absent: they are the FLIP's job, and
|
|
140
|
+
// carrying them too would apply the same displacement twice.
|
|
141
|
+
//
|
|
142
|
+
// So is `shadowColor`, despite being a color key. Carrying one of the four
|
|
143
|
+
// native shadow props crossfades a shadow's *color* over geometry that snapped
|
|
144
|
+
// — a worse result than letting the whole shadow snap together. (It would also
|
|
145
|
+
// pull `shadowColor` into the active set on any `layoutId` element whose style
|
|
146
|
+
// declares it, tripping the `boxShadow`-with-native-`shadow*` warning for a
|
|
147
|
+
// consumer who never asked for either.)
|
|
148
|
+
const SHARED_STYLE_KEYS = [
|
|
149
|
+
'opacity',
|
|
150
|
+
'borderRadius',
|
|
151
|
+
'backgroundColor',
|
|
152
|
+
'borderColor',
|
|
153
|
+
'color',
|
|
154
|
+
'tintColor',
|
|
155
|
+
] as const
|
|
156
|
+
|
|
111
157
|
/**
|
|
112
158
|
* Per-effect transform-group coordinator. Counts how many transform-axis
|
|
113
159
|
* terminal callbacks are still pending; when the last one fires, the
|
|
@@ -121,6 +167,7 @@ const ALL_KEYS = [
|
|
|
121
167
|
...NUMERIC_TOP_LEVEL_KEYS,
|
|
122
168
|
...COLOR_KEYS,
|
|
123
169
|
...SHADOW_OFFSET_KEYS,
|
|
170
|
+
...STRUCTURED_KEYS,
|
|
124
171
|
] as const
|
|
125
172
|
type AnimatableKey = (typeof ALL_KEYS)[number]
|
|
126
173
|
type TransformKey = (typeof TRANSFORM_KEYS)[number]
|
|
@@ -129,6 +176,20 @@ type ShadowOffsetKey = (typeof SHADOW_OFFSET_KEYS)[number]
|
|
|
129
176
|
const TRANSFORM_KEY_SET = new Set<AnimatableKey>(TRANSFORM_KEYS)
|
|
130
177
|
const COLOR_KEY_SET = new Set<AnimatableKey>(COLOR_KEYS)
|
|
131
178
|
const SHADOW_OFFSET_KEY_SET = new Set<AnimatableKey>(SHADOW_OFFSET_KEYS)
|
|
179
|
+
const STRUCTURED_KEY_SET = new Set<AnimatableKey>(STRUCTURED_KEYS)
|
|
180
|
+
const SHARED_STYLE_KEY_SET = new Set<AnimatableKey>(SHARED_STYLE_KEYS)
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* What a per-key shared value can hold. Scalars for every key except
|
|
184
|
+
* `boxShadow`, whose slot carries the layer array (see `STRUCTURED_KEYS`).
|
|
185
|
+
*/
|
|
186
|
+
type AnimatableSlotValue = number | string | readonly AnimatedBoxShadowLayer[]
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resting `boxShadow` — no shadow at all. Frozen and hoisted so every
|
|
190
|
+
* primitive shares one reference and an untouched slot never allocates.
|
|
191
|
+
*/
|
|
192
|
+
const NO_BOX_SHADOW: readonly AnimatedBoxShadowLayer[] = Object.freeze([])
|
|
132
193
|
|
|
133
194
|
const GESTURE_LAYER_NAMES = [
|
|
134
195
|
'hovered',
|
|
@@ -144,7 +205,7 @@ const GESTURE_LAYER_NAME_SET = new Set<string>(GESTURE_LAYER_NAMES)
|
|
|
144
205
|
// Reanimated's style merging treats it as a no-op when present.
|
|
145
206
|
const EXITING_POINTER_EVENTS_STYLE = { pointerEvents: 'none' } as const
|
|
146
207
|
|
|
147
|
-
const DEFAULT_RESTING: Record<AnimatableKey,
|
|
208
|
+
const DEFAULT_RESTING: Record<AnimatableKey, AnimatableSlotValue> = {
|
|
148
209
|
translateX: 0,
|
|
149
210
|
translateY: 0,
|
|
150
211
|
scale: 1,
|
|
@@ -171,6 +232,7 @@ const DEFAULT_RESTING: Record<AnimatableKey, number | string> = {
|
|
|
171
232
|
shadowColor: 'transparent',
|
|
172
233
|
shadowOffsetWidth: 0,
|
|
173
234
|
shadowOffsetHeight: 0,
|
|
235
|
+
boxShadow: NO_BOX_SHADOW,
|
|
174
236
|
}
|
|
175
237
|
|
|
176
238
|
// Both lookups run after the factory has resolved registered transition
|
|
@@ -398,6 +460,24 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
398
460
|
// identity only changes on the renders that actually add a key, so the
|
|
399
461
|
// `useAnimatedStyle` worklet (which reads `.current` each frame) sees the
|
|
400
462
|
// expansion without churning frame-to-frame.
|
|
463
|
+
const hasLayoutId = layoutId !== undefined
|
|
464
|
+
|
|
465
|
+
// The static `style`, flattened at most once per render and only when
|
|
466
|
+
// something asks for it. Two readers: the shared-element key scan just
|
|
467
|
+
// below, and the resting-value pass further down. A plain `animate`-only
|
|
468
|
+
// instance triggers neither and never pays for the flatten.
|
|
469
|
+
let flatStyle: Record<string, unknown> | undefined
|
|
470
|
+
let flatStyleRead = false
|
|
471
|
+
const getFlatStyle = () => {
|
|
472
|
+
if (!flatStyleRead) {
|
|
473
|
+
flatStyleRead = true
|
|
474
|
+
flatStyle = StyleSheet.flatten(style as never) as
|
|
475
|
+
| Record<string, unknown>
|
|
476
|
+
| undefined
|
|
477
|
+
}
|
|
478
|
+
return flatStyle
|
|
479
|
+
}
|
|
480
|
+
|
|
401
481
|
const touched = new Set<AnimatableKey>()
|
|
402
482
|
collectTouchedKeys(touched, animateRecord)
|
|
403
483
|
if (initialRecord) collectTouchedKeys(touched, initialRecord)
|
|
@@ -420,9 +500,31 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
420
500
|
}
|
|
421
501
|
if (exitRecord) collectTouchedKeys(touched, exitRecord)
|
|
422
502
|
|
|
503
|
+
// Shared-element style carry: a `layoutId` element crossfades the carried
|
|
504
|
+
// keys from its counterpart, so those keys have to be in the active set
|
|
505
|
+
// for the worklet to emit them at all.
|
|
506
|
+
//
|
|
507
|
+
// Only keys this element already has a value for join — from a record
|
|
508
|
+
// above, or from the static style. Never invented: activating a key with
|
|
509
|
+
// no source value rests it at `DEFAULT_RESTING`, and for `color` on a
|
|
510
|
+
// `Motion.Text` that inherits its colour from a parent, that default is
|
|
511
|
+
// `'transparent'` — invisible text on an element the consumer only asked
|
|
512
|
+
// to move. Same failure mode as the `0.0.3` P0, reached from the other
|
|
513
|
+
// side.
|
|
514
|
+
if (hasLayoutId) {
|
|
515
|
+
const flat = getFlatStyle()
|
|
516
|
+
if (flat) {
|
|
517
|
+
for (const key of SHARED_STYLE_KEYS) {
|
|
518
|
+
if (touched.has(key)) continue
|
|
519
|
+
if (styleValueFor(flat, key) !== undefined) touched.add(key)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
423
524
|
const activeKeysRef = useRef<readonly AnimatableKey[] | null>(null)
|
|
424
525
|
const hasTransformRef = useRef<boolean>(false)
|
|
425
526
|
const hasShadowOffsetRef = useRef<boolean>(false)
|
|
527
|
+
const hasBoxShadowRef = useRef<boolean>(false)
|
|
426
528
|
// Expand the active set only when this render touched a key we haven't
|
|
427
529
|
// recorded yet. When nothing new appears we keep the existing array
|
|
428
530
|
// identity so the worklet's captured ref doesn't see a fresh value.
|
|
@@ -446,6 +548,27 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
446
548
|
hasShadowOffsetRef.current = activeKeysRef.current.some((k) =>
|
|
447
549
|
SHADOW_OFFSET_KEY_SET.has(k),
|
|
448
550
|
)
|
|
551
|
+
hasBoxShadowRef.current = activeKeysRef.current.includes('boxShadow')
|
|
552
|
+
// Animating `boxShadow` alongside the native `shadow*` keys puts two
|
|
553
|
+
// shadow systems on one element; whichever the underlying view resolves
|
|
554
|
+
// last wins, and the result reads as a bug rather than a choice.
|
|
555
|
+
// Checked here rather than per render — the active key set is what
|
|
556
|
+
// decides it, and this block only runs when that set grows.
|
|
557
|
+
if (
|
|
558
|
+
__DEV__ &&
|
|
559
|
+
hasBoxShadowRef.current &&
|
|
560
|
+
activeKeysRef.current.some(
|
|
561
|
+
(k) => k !== 'boxShadow' && k.startsWith('shadow'),
|
|
562
|
+
)
|
|
563
|
+
) {
|
|
564
|
+
warnOnce(
|
|
565
|
+
'boxShadow-with-native-shadow',
|
|
566
|
+
'[inertia] `boxShadow` is animated alongside the native `shadow*` ' +
|
|
567
|
+
'keys on the same element, which applies two shadow systems at ' +
|
|
568
|
+
'once. Pick one: `boxShadow` for the cross-platform CSS form, or ' +
|
|
569
|
+
'the native `shadow*` keys.',
|
|
570
|
+
)
|
|
571
|
+
}
|
|
449
572
|
}
|
|
450
573
|
|
|
451
574
|
// Which keys a record actually drives *right now*. Everything else in
|
|
@@ -473,21 +596,52 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
473
596
|
// a style it has no use for.
|
|
474
597
|
const activeKeys = activeKeysRef.current!
|
|
475
598
|
const everDriven = everDrivenRef.current
|
|
476
|
-
const styleResting: Partial<Record<AnimatableKey,
|
|
599
|
+
const styleResting: Partial<Record<AnimatableKey, AnimatableSlotValue>> =
|
|
600
|
+
{}
|
|
601
|
+
// `boxShadow`'s resting value is two halves (animated layers + static
|
|
602
|
+
// inset flags), so it can't ride the scalar map above.
|
|
603
|
+
let styleRestingShadow: SplitBoxShadow | null = null
|
|
477
604
|
if (activeKeys.some((k) => !everDriven.has(k))) {
|
|
478
|
-
const flat =
|
|
479
|
-
| Record<string, unknown>
|
|
480
|
-
| undefined
|
|
605
|
+
const flat = getFlatStyle()
|
|
481
606
|
if (flat) {
|
|
482
607
|
for (const key of activeKeys) {
|
|
483
608
|
if (everDriven.has(key)) continue
|
|
609
|
+
if (key === 'boxShadow') {
|
|
610
|
+
const raw = flat.boxShadow as BoxShadowInput | undefined
|
|
611
|
+
if (raw !== undefined) {
|
|
612
|
+
styleRestingShadow = normalizeBoxShadow(raw)
|
|
613
|
+
styleResting.boxShadow = styleRestingShadow.layers
|
|
614
|
+
}
|
|
615
|
+
continue
|
|
616
|
+
}
|
|
484
617
|
const v = styleValueFor(flat, key)
|
|
485
618
|
if (v !== undefined) styleResting[key] = v
|
|
486
619
|
}
|
|
487
620
|
}
|
|
488
621
|
}
|
|
489
622
|
|
|
623
|
+
// Seed the `boxShadow` slot once, at mount, on the same precedence chain
|
|
624
|
+
// every other key uses (`initial` → `animate` → static style → default).
|
|
625
|
+
// Held in a ref rather than recomputed per render because resolving it
|
|
626
|
+
// may parse a CSS string, and the value is only ever read on the mount
|
|
627
|
+
// pass.
|
|
628
|
+
const boxShadowSeedRef = useRef<SplitBoxShadow | null>(null)
|
|
629
|
+
if (boxShadowSeedRef.current === null) {
|
|
630
|
+
const source =
|
|
631
|
+
initial === false
|
|
632
|
+
? animateRecord.boxShadow
|
|
633
|
+
: (initialRecord?.boxShadow ?? animateRecord.boxShadow)
|
|
634
|
+
boxShadowSeedRef.current =
|
|
635
|
+
source !== undefined
|
|
636
|
+
? normalizeBoxShadow(source)
|
|
637
|
+
: (styleRestingShadow ?? {
|
|
638
|
+
layers: [...NO_BOX_SHADOW],
|
|
639
|
+
insets: null,
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
|
|
490
643
|
const sharedValues = useAnimatableSharedValues((key) => {
|
|
644
|
+
if (key === 'boxShadow') return boxShadowSeedRef.current!.layers
|
|
491
645
|
// Shadow offset synthetics seed from the corresponding axis on the
|
|
492
646
|
// `shadowOffset: { width, height }` source — the consumer doesn't write
|
|
493
647
|
// `shadowOffsetWidth` / `shadowOffsetHeight` directly. Fall back to the
|
|
@@ -532,12 +686,25 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
532
686
|
// swap, a conditional colour) would stay invisible for any key some
|
|
533
687
|
// gesture / exit / variant branch happens to mention. Direct assignment,
|
|
534
688
|
// not an animation: this is a static value, not a target.
|
|
689
|
+
// Static `inset` flags for the layers currently in the `boxShadow` slot,
|
|
690
|
+
// parallel to `sharedValues.boxShadow`. Kept out of the animated payload
|
|
691
|
+
// because Reanimated would drive the boolean down its numeric path and
|
|
692
|
+
// hand native a number mid-flight; see `AnimatedBoxShadowLayer`. `null`
|
|
693
|
+
// whenever no layer is inset, which lets the worklet emit the animated
|
|
694
|
+
// array with no per-frame reassembly at all.
|
|
695
|
+
const boxShadowInsets = useSharedValue<boolean[] | null>(
|
|
696
|
+
boxShadowSeedRef.current.insets,
|
|
697
|
+
)
|
|
698
|
+
|
|
535
699
|
const styleRestingSig = stableSig(styleResting)
|
|
536
700
|
useEffect(() => {
|
|
537
701
|
for (const key of activeKeysRef.current!) {
|
|
538
702
|
if (everDrivenRef.current.has(key)) continue
|
|
539
703
|
sharedValues[key].value = (styleResting[key] ??
|
|
540
704
|
DEFAULT_RESTING[key]) as never
|
|
705
|
+
if (key === 'boxShadow') {
|
|
706
|
+
boxShadowInsets.value = styleRestingShadow?.insets ?? null
|
|
707
|
+
}
|
|
541
708
|
}
|
|
542
709
|
// `styleResting` is rebuilt each render; its signature is the real dep.
|
|
543
710
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -666,6 +833,33 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
666
833
|
transformPending > 0 ? { remaining: transformPending } : undefined
|
|
667
834
|
|
|
668
835
|
for (const key of ALL_KEYS) {
|
|
836
|
+
// Structured keys don't go through `resolveAnimatableValue` — their
|
|
837
|
+
// target is already an array, which that resolver would read as a
|
|
838
|
+
// keyframe sequence.
|
|
839
|
+
if (key === 'boxShadow') {
|
|
840
|
+
const shadowTarget = baseRecord.boxShadow
|
|
841
|
+
if (shadowTarget === undefined) continue
|
|
842
|
+
const cfg = configFor('boxShadow')
|
|
843
|
+
if (isExiting && reachesTerminalPhase(cfg)) pending++
|
|
844
|
+
driveBoxShadow(
|
|
845
|
+
sharedValues.boxShadow,
|
|
846
|
+
boxShadowInsets,
|
|
847
|
+
shadowTarget,
|
|
848
|
+
cfg,
|
|
849
|
+
makeKeyCallbackFactory(
|
|
850
|
+
'boxShadow',
|
|
851
|
+
sharedValues.boxShadow,
|
|
852
|
+
// The declared target, not the padded one — the padding is an
|
|
853
|
+
// implementation detail of getting Reanimated to interpolate.
|
|
854
|
+
shadowTarget,
|
|
855
|
+
onAnimationEndRef,
|
|
856
|
+
{ stepCount: 1, totalIterations: totalIterationsOf(cfg) },
|
|
857
|
+
isExiting ? onSettle : undefined,
|
|
858
|
+
undefined,
|
|
859
|
+
),
|
|
860
|
+
)
|
|
861
|
+
continue
|
|
862
|
+
}
|
|
669
863
|
// Shadow offset synthetics read their target from the nested
|
|
670
864
|
// `shadowOffset: { width, height }` source on `baseRecord` — the
|
|
671
865
|
// animate / exit record never has `shadowOffsetWidth` etc. on it
|
|
@@ -770,20 +964,44 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
770
964
|
// transform array so they compose with the user's animate transforms —
|
|
771
965
|
// multiple `translateX` entries sum, multiple `scaleX` entries multiply,
|
|
772
966
|
// which is exactly the FLIP semantic.
|
|
967
|
+
//
|
|
968
|
+
// The style half rides the same wiring: `readStyles` lets a later mount
|
|
969
|
+
// (or our own unmount) snapshot the carried keys straight off the shared
|
|
970
|
+
// values, and the worklet below crossfades a consumed snapshot out over
|
|
971
|
+
// whatever this element resolves to. Reached through a ref inside the
|
|
972
|
+
// hook, so building it inline here costs nothing downstream.
|
|
973
|
+
const readSharedStyles = () => {
|
|
974
|
+
if (!hasLayoutId) return undefined
|
|
975
|
+
const out: SharedStyleSnapshot = {}
|
|
976
|
+
let any = false
|
|
977
|
+
for (const key of activeKeysRef.current!) {
|
|
978
|
+
if (!SHARED_STYLE_KEY_SET.has(key)) continue
|
|
979
|
+
const v = sharedValues[key].value
|
|
980
|
+
// Structured slots can't be carried, and a key nothing ever gave a
|
|
981
|
+
// value to has nothing to hand over.
|
|
982
|
+
if (typeof v !== 'number' && typeof v !== 'string') continue
|
|
983
|
+
out[key] = v
|
|
984
|
+
any = true
|
|
985
|
+
}
|
|
986
|
+
return any ? out : undefined
|
|
987
|
+
}
|
|
988
|
+
|
|
773
989
|
const sharedLayout = useSharedLayout({
|
|
774
990
|
layoutId,
|
|
775
991
|
userRef: ref,
|
|
776
992
|
transition: isTopLevelTransition(transition) ? transition : undefined,
|
|
777
993
|
shouldReduceMotion,
|
|
778
994
|
userOnLayout,
|
|
995
|
+
readStyles: readSharedStyles,
|
|
779
996
|
})
|
|
780
997
|
const flip = sharedLayout.flip
|
|
781
|
-
const
|
|
998
|
+
const carry = sharedLayout.carry
|
|
782
999
|
|
|
783
1000
|
const animatedStyle = useAnimatedStyle(() => {
|
|
784
1001
|
const activeKeys = activeKeysRef.current!
|
|
785
1002
|
const hasTransform = hasTransformRef.current
|
|
786
1003
|
const hasShadowOffset = hasShadowOffsetRef.current
|
|
1004
|
+
const hasBoxShadow = hasBoxShadowRef.current
|
|
787
1005
|
const out: Record<string, unknown> = {}
|
|
788
1006
|
const transform: Array<Record<string, unknown>> = []
|
|
789
1007
|
// shadow-offset reassembly buffers. The two synthetic axis SVs feed in
|
|
@@ -799,6 +1017,12 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
799
1017
|
const pfv = focusVisibleProgress.value
|
|
800
1018
|
const pp = pressedProgress.value
|
|
801
1019
|
|
|
1020
|
+
// Shared-element style carry. Both reads are cheap and the pair rests
|
|
1021
|
+
// at (null, 0), so a primitive that isn't mid-transition — which is
|
|
1022
|
+
// every primitive, almost all of the time — pays one comparison.
|
|
1023
|
+
const carried = carry.snapshot.value
|
|
1024
|
+
const pc = carry.progress.value
|
|
1025
|
+
|
|
802
1026
|
const layers = gestureSV.value
|
|
803
1027
|
// Locals are suffixed `Layer` so they don't shadow the outer `pressed` /
|
|
804
1028
|
// `focused` / `focusVisible` / `hovered` JS-state booleans — Reanimated's
|
|
@@ -810,6 +1034,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
810
1034
|
const pressedLayer = layers ? layers.pressed : null
|
|
811
1035
|
|
|
812
1036
|
for (const key of activeKeys) {
|
|
1037
|
+
// `boxShadow` is emitted after the loop: its value is a layer array,
|
|
1038
|
+
// which none of the scalar compositing below can act on. Gesture
|
|
1039
|
+
// sub-states are rejected for this key upstream, so there is no
|
|
1040
|
+
// layer contribution to skip past either.
|
|
1041
|
+
if (key === 'boxShadow') continue
|
|
813
1042
|
let v = sharedValues[key].value
|
|
814
1043
|
const isColor = COLOR_KEY_SET.has(key)
|
|
815
1044
|
|
|
@@ -848,6 +1077,23 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
848
1077
|
: (v as number) + ((t as number) - (v as number)) * pp
|
|
849
1078
|
}
|
|
850
1079
|
|
|
1080
|
+
// Shared-element carry sits above every gesture layer: the element
|
|
1081
|
+
// is arriving from somewhere else, and where it came from outranks
|
|
1082
|
+
// how it is being touched right now. `pc` runs 1 → 0, so the first
|
|
1083
|
+
// frame shows the source's value and the last shows this element's
|
|
1084
|
+
// own — including whatever the layers underneath contributed.
|
|
1085
|
+
//
|
|
1086
|
+
// Transform keys never appear in a snapshot (the FLIP owns them), so
|
|
1087
|
+
// this can't double-apply displacement.
|
|
1088
|
+
if (carried !== null && pc > 0) {
|
|
1089
|
+
const s = carried[key]
|
|
1090
|
+
if (s !== undefined) {
|
|
1091
|
+
v = isColor
|
|
1092
|
+
? interpolateColor(pc, [0, 1], [v as string, s as string])
|
|
1093
|
+
: (v as number) + ((s as number) - (v as number)) * pc
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
851
1097
|
if (TRANSFORM_KEY_SET.has(key)) {
|
|
852
1098
|
transform.push(
|
|
853
1099
|
ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v },
|
|
@@ -876,12 +1122,28 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
876
1122
|
if (hasShadowOffset) {
|
|
877
1123
|
out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH }
|
|
878
1124
|
}
|
|
1125
|
+
if (hasBoxShadow) {
|
|
1126
|
+
const layers = sharedValues.boxShadow
|
|
1127
|
+
.value as readonly AnimatedBoxShadowLayer[]
|
|
1128
|
+
const insets = boxShadowInsets.value
|
|
1129
|
+
// Fast path — no inset layer anywhere, so the animated array is
|
|
1130
|
+
// already exactly what RN wants and crosses over untouched.
|
|
1131
|
+
if (insets === null) {
|
|
1132
|
+
out.boxShadow = layers
|
|
1133
|
+
} else {
|
|
1134
|
+
const withInset = []
|
|
1135
|
+
for (let i = 0; i < layers.length; i++) {
|
|
1136
|
+
withInset.push({ ...layers[i], inset: insets[i] })
|
|
1137
|
+
}
|
|
1138
|
+
out.boxShadow = withInset
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
879
1141
|
return out
|
|
880
1142
|
})
|
|
881
1143
|
|
|
882
1144
|
// Exiting children are tap-deaf: the next press should fall through to
|
|
883
1145
|
// whatever is underneath, not re-trigger a soon-to-unmount node. This is
|
|
884
|
-
// the moti #297 fix and a
|
|
1146
|
+
// the moti #297 fix and a `0.0.1` acceptance criterion. RN 0.71+ deprecates
|
|
885
1147
|
// `pointerEvents` as a prop in favor of the style key, so we merge it
|
|
886
1148
|
// alongside the animated style instead of spreading as a prop.
|
|
887
1149
|
const mergedStyle = useMemo(
|
|
@@ -969,7 +1231,7 @@ function hasMotionProps(props: Record<string, unknown>): boolean {
|
|
|
969
1231
|
return false
|
|
970
1232
|
}
|
|
971
1233
|
|
|
972
|
-
type SharedValueMap = Record<AnimatableKey, SharedValue<
|
|
1234
|
+
type SharedValueMap = Record<AnimatableKey, SharedValue<AnimatableSlotValue>>
|
|
973
1235
|
|
|
974
1236
|
/**
|
|
975
1237
|
* Allocate one shared value per animatable key in `ALL_KEYS` and return a
|
|
@@ -990,36 +1252,39 @@ type SharedValueMap = Record<AnimatableKey, SharedValue<number | string>>
|
|
|
990
1252
|
* `withSpring` / `withTiming` call.
|
|
991
1253
|
*/
|
|
992
1254
|
function useAnimatableSharedValues(
|
|
993
|
-
init: (key: AnimatableKey) =>
|
|
1255
|
+
init: (key: AnimatableKey) => AnimatableSlotValue,
|
|
994
1256
|
): SharedValueMap {
|
|
995
|
-
const translateX = useSharedValue<
|
|
996
|
-
const translateY = useSharedValue<
|
|
997
|
-
const scale = useSharedValue<
|
|
998
|
-
const scaleX = useSharedValue<
|
|
999
|
-
const scaleY = useSharedValue<
|
|
1000
|
-
const rotate = useSharedValue<
|
|
1001
|
-
const rotateX = useSharedValue<
|
|
1002
|
-
const rotateY = useSharedValue<
|
|
1003
|
-
const opacity = useSharedValue<
|
|
1004
|
-
const width = useSharedValue<
|
|
1005
|
-
const height = useSharedValue<
|
|
1006
|
-
const borderRadius = useSharedValue<
|
|
1007
|
-
const shadowOpacity = useSharedValue<
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1257
|
+
const translateX = useSharedValue<AnimatableSlotValue>(init('translateX'))
|
|
1258
|
+
const translateY = useSharedValue<AnimatableSlotValue>(init('translateY'))
|
|
1259
|
+
const scale = useSharedValue<AnimatableSlotValue>(init('scale'))
|
|
1260
|
+
const scaleX = useSharedValue<AnimatableSlotValue>(init('scaleX'))
|
|
1261
|
+
const scaleY = useSharedValue<AnimatableSlotValue>(init('scaleY'))
|
|
1262
|
+
const rotate = useSharedValue<AnimatableSlotValue>(init('rotate'))
|
|
1263
|
+
const rotateX = useSharedValue<AnimatableSlotValue>(init('rotateX'))
|
|
1264
|
+
const rotateY = useSharedValue<AnimatableSlotValue>(init('rotateY'))
|
|
1265
|
+
const opacity = useSharedValue<AnimatableSlotValue>(init('opacity'))
|
|
1266
|
+
const width = useSharedValue<AnimatableSlotValue>(init('width'))
|
|
1267
|
+
const height = useSharedValue<AnimatableSlotValue>(init('height'))
|
|
1268
|
+
const borderRadius = useSharedValue<AnimatableSlotValue>(init('borderRadius'))
|
|
1269
|
+
const shadowOpacity = useSharedValue<AnimatableSlotValue>(
|
|
1270
|
+
init('shadowOpacity'),
|
|
1271
|
+
)
|
|
1272
|
+
const shadowRadius = useSharedValue<AnimatableSlotValue>(init('shadowRadius'))
|
|
1273
|
+
const elevation = useSharedValue<AnimatableSlotValue>(init('elevation'))
|
|
1274
|
+
const backgroundColor = useSharedValue<AnimatableSlotValue>(
|
|
1011
1275
|
init('backgroundColor'),
|
|
1012
1276
|
)
|
|
1013
|
-
const borderColor = useSharedValue<
|
|
1014
|
-
const color = useSharedValue<
|
|
1015
|
-
const tintColor = useSharedValue<
|
|
1016
|
-
const shadowColor = useSharedValue<
|
|
1017
|
-
const shadowOffsetWidth = useSharedValue<
|
|
1277
|
+
const borderColor = useSharedValue<AnimatableSlotValue>(init('borderColor'))
|
|
1278
|
+
const color = useSharedValue<AnimatableSlotValue>(init('color'))
|
|
1279
|
+
const tintColor = useSharedValue<AnimatableSlotValue>(init('tintColor'))
|
|
1280
|
+
const shadowColor = useSharedValue<AnimatableSlotValue>(init('shadowColor'))
|
|
1281
|
+
const shadowOffsetWidth = useSharedValue<AnimatableSlotValue>(
|
|
1018
1282
|
init('shadowOffsetWidth'),
|
|
1019
1283
|
)
|
|
1020
|
-
const shadowOffsetHeight = useSharedValue<
|
|
1284
|
+
const shadowOffsetHeight = useSharedValue<AnimatableSlotValue>(
|
|
1021
1285
|
init('shadowOffsetHeight'),
|
|
1022
1286
|
)
|
|
1287
|
+
const boxShadow = useSharedValue<AnimatableSlotValue>(init('boxShadow'))
|
|
1023
1288
|
|
|
1024
1289
|
const ref = useRef<SharedValueMap | null>(null)
|
|
1025
1290
|
if (ref.current === null) {
|
|
@@ -1046,6 +1311,7 @@ function useAnimatableSharedValues(
|
|
|
1046
1311
|
shadowColor,
|
|
1047
1312
|
shadowOffsetWidth,
|
|
1048
1313
|
shadowOffsetHeight,
|
|
1314
|
+
boxShadow,
|
|
1049
1315
|
}
|
|
1050
1316
|
}
|
|
1051
1317
|
|
|
@@ -1084,8 +1350,11 @@ function useAnimatableSharedValues(
|
|
|
1084
1350
|
*/
|
|
1085
1351
|
function makeKeyCallbackFactory(
|
|
1086
1352
|
key: string,
|
|
1087
|
-
sharedValue: SharedValue<
|
|
1088
|
-
|
|
1353
|
+
sharedValue: SharedValue<AnimatableSlotValue>,
|
|
1354
|
+
// Widened past the scalar surface for `boxShadow`, whose declared target is
|
|
1355
|
+
// a CSS string or a layer array. Reaches the consumer as
|
|
1356
|
+
// `AnimationCallbackInfo.target`, which is `unknown`.
|
|
1357
|
+
target: number | string | BoxShadowInput | undefined,
|
|
1089
1358
|
onAnimationEndRef: {
|
|
1090
1359
|
current: ((info: AnimationCallbackInfo<unknown>) => void) | undefined
|
|
1091
1360
|
},
|
|
@@ -1105,7 +1374,9 @@ function makeKeyCallbackFactory(
|
|
|
1105
1374
|
rawPhase: 'step' | 'animation',
|
|
1106
1375
|
step: number | undefined,
|
|
1107
1376
|
finished: boolean,
|
|
1108
|
-
value
|
|
1377
|
+
// Widened for `boxShadow`, whose settled value is a layer array. Surfaces
|
|
1378
|
+
// on `AnimationCallbackInfo.value`, which is `unknown` either way.
|
|
1379
|
+
value: AnimatableSlotValue | undefined,
|
|
1109
1380
|
) => {
|
|
1110
1381
|
const isLastIteration = state.iteration >= meta.totalIterations - 1
|
|
1111
1382
|
let phase: 'step' | 'sequence' | 'repeat' | 'animation'
|
|
@@ -1191,18 +1462,25 @@ function makeKeyCallbackFactory(
|
|
|
1191
1462
|
* nested object decomposes into the `shadowOffsetWidth` / `shadowOffsetHeight`
|
|
1192
1463
|
* synthetic axes downstream; consumers don't see the synthetics.
|
|
1193
1464
|
*
|
|
1194
|
-
*
|
|
1195
|
-
* (no sequences, no `{ to }` step objects, no array keyframes).
|
|
1196
|
-
* forms on the nested axes can land
|
|
1465
|
+
* Contract since `0.0.1`: `shadowOffset` accepts a single `{ width, height }`
|
|
1466
|
+
* literal (no sequences, no `{ to }` step objects, no array keyframes).
|
|
1467
|
+
* Sequence forms on the nested axes can land later if real consumers ask.
|
|
1197
1468
|
*/
|
|
1469
|
+
type ScalarKey = Exclude<AnimatableKey, (typeof STRUCTURED_KEYS)[number]>
|
|
1470
|
+
|
|
1198
1471
|
type InternalAnimateRecord = Partial<
|
|
1199
|
-
Record<
|
|
1472
|
+
Record<ScalarKey, AnimatableValue<number | string>>
|
|
1200
1473
|
> & {
|
|
1201
1474
|
shadowOffset?: { width?: number; height?: number }
|
|
1475
|
+
// Structured keys depart from `AnimatableValue` — see `STRUCTURED_KEYS`.
|
|
1476
|
+
// Kept out of the mapped half above so the two don't intersect into an
|
|
1477
|
+
// uninhabitable `AnimatableValue<number | string> & BoxShadowInput`.
|
|
1478
|
+
boxShadow?: BoxShadowInput
|
|
1202
1479
|
}
|
|
1203
1480
|
|
|
1204
|
-
type InternalInitialRecord = Partial<Record<
|
|
1481
|
+
type InternalInitialRecord = Partial<Record<ScalarKey, number | string>> & {
|
|
1205
1482
|
shadowOffset?: { width?: number; height?: number }
|
|
1483
|
+
boxShadow?: BoxShadowInput
|
|
1206
1484
|
}
|
|
1207
1485
|
|
|
1208
1486
|
/**
|
|
@@ -1225,6 +1503,46 @@ function shadowOffsetAxisValue(
|
|
|
1225
1503
|
return source?.[axis]
|
|
1226
1504
|
}
|
|
1227
1505
|
|
|
1506
|
+
/**
|
|
1507
|
+
* Drive the `boxShadow` slot toward a new target.
|
|
1508
|
+
*
|
|
1509
|
+
* Two things make this different from the scalar path:
|
|
1510
|
+
*
|
|
1511
|
+
* 1. **Both endpoints are padded to a common layer count first.** Reanimated's
|
|
1512
|
+
* array driver iterates the *current* value's indices and pairs each with
|
|
1513
|
+
* `toValue[i]`, so a target with a different layer count either strands
|
|
1514
|
+
* leaves at `toValue: undefined` or never animates the extras. When the
|
|
1515
|
+
* padding changes the slot's shape, we snap it to the padded base before
|
|
1516
|
+
* starting the animation — otherwise the animation would begin from a
|
|
1517
|
+
* differently-shaped value and the pairing would be lost again.
|
|
1518
|
+
* 2. **`inset` travels separately**, in its own slot, because Reanimated
|
|
1519
|
+
* would drive the boolean down the numeric path.
|
|
1520
|
+
*/
|
|
1521
|
+
function driveBoxShadow(
|
|
1522
|
+
slot: SharedValue<AnimatableSlotValue>,
|
|
1523
|
+
insetSlot: SharedValue<boolean[] | null>,
|
|
1524
|
+
target: BoxShadowInput,
|
|
1525
|
+
cfg: TransitionConfig | undefined,
|
|
1526
|
+
factory: CallbackFactory | undefined,
|
|
1527
|
+
): void {
|
|
1528
|
+
const currentLayers = Array.isArray(slot.value)
|
|
1529
|
+
? (slot.value as readonly AnimatedBoxShadowLayer[])
|
|
1530
|
+
: NO_BOX_SHADOW
|
|
1531
|
+
const { from, to, insets } = prepareBoxShadowAnimation(
|
|
1532
|
+
{ layers: [...currentLayers], insets: insetSlot.value },
|
|
1533
|
+
target,
|
|
1534
|
+
)
|
|
1535
|
+
insetSlot.value = insets
|
|
1536
|
+
if (from.length !== currentLayers.length) slot.value = from
|
|
1537
|
+
// `resolveTransition` is typed for the scalar surface it was written for;
|
|
1538
|
+
// Reanimated itself accepts the structured target and recurses into it.
|
|
1539
|
+
slot.value = resolveTransition(
|
|
1540
|
+
cfg,
|
|
1541
|
+
to as unknown as number,
|
|
1542
|
+
factory?.('animation', undefined),
|
|
1543
|
+
) as AnimatableSlotValue
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1228
1546
|
/**
|
|
1229
1547
|
* Read an animatable key's resting value out of a **flattened style object**.
|
|
1230
1548
|
*
|
|
@@ -1460,6 +1778,27 @@ function resolveGestureLayers(
|
|
|
1460
1778
|
if (!subState) continue
|
|
1461
1779
|
const resolved: Record<string, number | string> = {}
|
|
1462
1780
|
for (const key of ALL_KEYS) {
|
|
1781
|
+
// Structured keys have no place in the layer cascade: compositing them
|
|
1782
|
+
// would mean per-layer, per-field interpolation on the UI thread for
|
|
1783
|
+
// every primitive, whether or not it animates a shadow. Rejected for
|
|
1784
|
+
// now rather than half-supported — a silently-ignored `boxShadow` in a
|
|
1785
|
+
// `gesture` sub-state is exactly the class of bug this library exists
|
|
1786
|
+
// to avoid, so say so.
|
|
1787
|
+
if (STRUCTURED_KEY_SET.has(key)) {
|
|
1788
|
+
if (
|
|
1789
|
+
__DEV__ &&
|
|
1790
|
+
(subState as Record<string, unknown>)[key] !== undefined
|
|
1791
|
+
) {
|
|
1792
|
+
warnOnce(
|
|
1793
|
+
`gesture-structured-${layer}-${key}`,
|
|
1794
|
+
`[inertia] \`${key}\` is not supported inside \`gesture.${layer}\` ` +
|
|
1795
|
+
'and will be ignored. Drive it from `animate` (optionally via a ' +
|
|
1796
|
+
'variant keyed off the same state), or interpolate it yourself ' +
|
|
1797
|
+
'with `useShadow`.',
|
|
1798
|
+
)
|
|
1799
|
+
}
|
|
1800
|
+
continue
|
|
1801
|
+
}
|
|
1463
1802
|
// Shadow offset synthetics decompose from the nested `shadowOffset:
|
|
1464
1803
|
// { width, height }` source on the sub-state, the same as on `animate`.
|
|
1465
1804
|
if (SHADOW_OFFSET_KEY_SET.has(key)) {
|
|
@@ -41,7 +41,7 @@ export function ensureReanimatedInstalled(): void {
|
|
|
41
41
|
const major = parseInt(version.split('.')[0] ?? '0', 10)
|
|
42
42
|
if (major < 4) {
|
|
43
43
|
console.error(
|
|
44
|
-
`[inertia] react-native-reanimated
|
|
44
|
+
`[inertia] react-native-reanimated ${version} is installed, but @rootnative/inertia requires 4.0.0 or later. ` +
|
|
45
45
|
`Upgrade with \`pnpm add react-native-reanimated@^4\` (or your package manager's equivalent).`,
|
|
46
46
|
)
|
|
47
47
|
return
|
package/src/transitions/index.ts
CHANGED