@rootnative/inertia 0.0.3 → 0.0.5
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 +92 -5
- package/README.md +9 -3
- package/dist/{chunk-OQV66TBQ.mjs → chunk-27MKBTSG.mjs} +1 -1
- package/dist/{chunk-3XTVY34H.js → chunk-27U6766G.js} +2 -2
- package/dist/{chunk-4PEHWDAZ.js → chunk-4NAMD62I.js} +7 -7
- package/dist/chunk-67GHRCF6.js +8 -0
- package/dist/{chunk-SGUHE5CX.mjs → chunk-COEJVWRZ.mjs} +1 -1
- package/dist/chunk-GPOMFIIU.js +8 -0
- package/dist/{chunk-BQQTHG2V.mjs → chunk-GTJ6VAH5.mjs} +1 -1
- package/dist/chunk-K63LXGKS.js +8 -0
- package/dist/{chunk-46P57VMY.mjs → chunk-OAUWLPQH.mjs} +1 -1
- package/dist/{chunk-7AOERN53.js → chunk-OBRGJAST.js} +3 -3
- package/dist/{chunk-RGNX6UZN.mjs → chunk-OW5XTGVN.mjs} +2 -2
- package/dist/{chunk-PTRF47DA.js → chunk-PM6CVGXJ.js} +11 -3
- package/dist/{chunk-W5MC3P4N.js → chunk-QQVDZKSD.js} +2 -2
- package/dist/{chunk-CY7Y64C3.mjs → chunk-REYL77RE.mjs} +11 -4
- package/dist/{chunk-CSODMRJ7.mjs → chunk-RIVVBABB.mjs} +597 -134
- package/dist/{chunk-BP3Y2SHQ.js → chunk-VQ5D35UA.js} +617 -152
- package/dist/{chunk-O22NXXCZ.mjs → chunk-XSK5MNUH.mjs} +1 -1
- package/dist/{chunk-R63GIUNU.mjs → chunk-Z3HCJ43H.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-BQgLJeQG.d.mts} +85 -14
- package/dist/{types-G1dBU2IQ.d.ts → types-BQgLJeQG.d.ts} +85 -14
- package/dist/{useGesture-D1e4ixJo.d.ts → useGesture-B0_CzOUz.d.ts} +1 -1
- package/dist/{useGesture-RFY6kTL3.d.mts → useGesture-DfaTx-3t.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 +497 -74
- package/src/motion/installCheck.ts +1 -1
- package/src/transitions/index.ts +1 -1
- package/src/transitions/resolveSequence.ts +12 -0
- package/src/types.ts +155 -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,
|
|
@@ -81,6 +91,78 @@ const NUMERIC_TOP_LEVEL_KEYS = [
|
|
|
81
91
|
'shadowOpacity',
|
|
82
92
|
'shadowRadius',
|
|
83
93
|
'elevation',
|
|
94
|
+
// ─── Added in 0.0.5 ──────────────────────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// Every key below rides the generic numeric path: one shared value, resolved
|
|
97
|
+
// on the JS thread, emitted by the worklet through the same `out[key] = v`
|
|
98
|
+
// branch as `opacity`. None needs a new interpolation mode, so the cost is
|
|
99
|
+
// the shared value plus a `DEFAULT_RESTING` entry.
|
|
100
|
+
//
|
|
101
|
+
// The load-bearing constraint is the resting default, not the plumbing. A
|
|
102
|
+
// key declared only in `gesture` / `exit` / a non-active variant rests at
|
|
103
|
+
// `DEFAULT_RESTING` unless the static `style` names it, and the animated
|
|
104
|
+
// style merges *after* `style` — so a non-identity default silently stomps
|
|
105
|
+
// the layout. That is the `0.0.3` P0 class, and it is why the sizing and
|
|
106
|
+
// spacing keys below default to `0` only in the sense RN itself does (an
|
|
107
|
+
// unset `padding` *is* 0); `minWidth`/`maxWidth`/`aspectRatio` are
|
|
108
|
+
// deliberately excluded — their "unset" is not a number, so no default can
|
|
109
|
+
// stand in for it without changing layout on activation.
|
|
110
|
+
//
|
|
111
|
+
// Per-corner radii. Animating one corner is common (a card expanding into a
|
|
112
|
+
// sheet keeps its top corners and squares off the bottom); `borderRadius`
|
|
113
|
+
// alone can't express it.
|
|
114
|
+
'borderTopLeftRadius',
|
|
115
|
+
'borderTopRightRadius',
|
|
116
|
+
'borderBottomLeftRadius',
|
|
117
|
+
'borderBottomRightRadius',
|
|
118
|
+
// Border widths. `borderWidth` is the common case; the per-edge keys matter
|
|
119
|
+
// for underline-style focus affordances (`borderBottomWidth` on a field).
|
|
120
|
+
'borderWidth',
|
|
121
|
+
'borderTopWidth',
|
|
122
|
+
'borderRightWidth',
|
|
123
|
+
'borderBottomWidth',
|
|
124
|
+
'borderLeftWidth',
|
|
125
|
+
// Absolute-position insets. Bread-and-butter for drawers, tooltips, and
|
|
126
|
+
// anything pinned to an edge that slides in.
|
|
127
|
+
'top',
|
|
128
|
+
'right',
|
|
129
|
+
'bottom',
|
|
130
|
+
'left',
|
|
131
|
+
// Spacing. Padding animates the box from the inside (a button growing its
|
|
132
|
+
// hit area on press); margin shifts it within its parent's flow.
|
|
133
|
+
'padding',
|
|
134
|
+
'paddingTop',
|
|
135
|
+
'paddingRight',
|
|
136
|
+
'paddingBottom',
|
|
137
|
+
'paddingLeft',
|
|
138
|
+
'paddingHorizontal',
|
|
139
|
+
'paddingVertical',
|
|
140
|
+
'margin',
|
|
141
|
+
'marginTop',
|
|
142
|
+
'marginRight',
|
|
143
|
+
'marginBottom',
|
|
144
|
+
'marginLeft',
|
|
145
|
+
'marginHorizontal',
|
|
146
|
+
'marginVertical',
|
|
147
|
+
// Flex sizing. `flex` / `flexGrow` interpolate the split between siblings —
|
|
148
|
+
// the idiomatic RN way to animate a pane resize without measuring.
|
|
149
|
+
'flex',
|
|
150
|
+
'flexGrow',
|
|
151
|
+
'flexShrink',
|
|
152
|
+
// Text metrics. `TextStyle`-only at the type level (`AnimateStyle<C>` gates
|
|
153
|
+
// them per primitive), but the runtime table is shared across primitives —
|
|
154
|
+
// an unused slot is one ref, same as `tintColor` has always been.
|
|
155
|
+
'fontSize',
|
|
156
|
+
'letterSpacing',
|
|
157
|
+
'lineHeight',
|
|
158
|
+
// Stacking. Integer-valued in practice, and interpolating it mid-flight is
|
|
159
|
+
// rarely what you want, but a `no-animation` transition on `zIndex` is the
|
|
160
|
+
// clean way to reorder at a step boundary in a sequence.
|
|
161
|
+
'zIndex',
|
|
162
|
+
// Gap. Row/column gaps animate a list's density without touching children.
|
|
163
|
+
'gap',
|
|
164
|
+
'rowGap',
|
|
165
|
+
'columnGap',
|
|
84
166
|
] as const
|
|
85
167
|
|
|
86
168
|
// Color-valued keys. Reanimated's value setter detects color strings and
|
|
@@ -108,6 +190,42 @@ const COLOR_KEYS = [
|
|
|
108
190
|
// these keys directly.
|
|
109
191
|
const SHADOW_OFFSET_KEYS = ['shadowOffsetWidth', 'shadowOffsetHeight'] as const
|
|
110
192
|
|
|
193
|
+
// Keys whose shared value holds a structure rather than a scalar. `boxShadow`
|
|
194
|
+
// is the only one: its slot carries an array of layer objects, which
|
|
195
|
+
// Reanimated's animation drivers recurse into, animating each leaf number and
|
|
196
|
+
// color independently (`arrayOnStart` → `objectOnStart` in Reanimated's
|
|
197
|
+
// `animation/util.ts`).
|
|
198
|
+
//
|
|
199
|
+
// Passing the CSS string through instead would NOT work — a box-shadow string
|
|
200
|
+
// isn't a color, so it lands in Reanimated's prefix-number-suffix branch,
|
|
201
|
+
// which is built for values like '100%' and would pull a single number out of
|
|
202
|
+
// a four-value shadow. All string parsing therefore happens on the JS thread,
|
|
203
|
+
// once per change, in `internal/boxShadow.ts`.
|
|
204
|
+
const STRUCTURED_KEYS = ['boxShadow'] as const
|
|
205
|
+
|
|
206
|
+
// Keys a shared-element transition (`layoutId`) carries from the source
|
|
207
|
+
// element to the target, crossfading them alongside the rect FLIP. Everything
|
|
208
|
+
// here is either a scalar or a color, so the worklet's existing lerp /
|
|
209
|
+
// `interpolateColor` branches handle it with no new machinery.
|
|
210
|
+
//
|
|
211
|
+
// Transform keys are deliberately absent: they are the FLIP's job, and
|
|
212
|
+
// carrying them too would apply the same displacement twice.
|
|
213
|
+
//
|
|
214
|
+
// So is `shadowColor`, despite being a color key. Carrying one of the four
|
|
215
|
+
// native shadow props crossfades a shadow's *color* over geometry that snapped
|
|
216
|
+
// — a worse result than letting the whole shadow snap together. (It would also
|
|
217
|
+
// pull `shadowColor` into the active set on any `layoutId` element whose style
|
|
218
|
+
// declares it, tripping the `boxShadow`-with-native-`shadow*` warning for a
|
|
219
|
+
// consumer who never asked for either.)
|
|
220
|
+
const SHARED_STYLE_KEYS = [
|
|
221
|
+
'opacity',
|
|
222
|
+
'borderRadius',
|
|
223
|
+
'backgroundColor',
|
|
224
|
+
'borderColor',
|
|
225
|
+
'color',
|
|
226
|
+
'tintColor',
|
|
227
|
+
] as const
|
|
228
|
+
|
|
111
229
|
/**
|
|
112
230
|
* Per-effect transform-group coordinator. Counts how many transform-axis
|
|
113
231
|
* terminal callbacks are still pending; when the last one fires, the
|
|
@@ -121,6 +239,7 @@ const ALL_KEYS = [
|
|
|
121
239
|
...NUMERIC_TOP_LEVEL_KEYS,
|
|
122
240
|
...COLOR_KEYS,
|
|
123
241
|
...SHADOW_OFFSET_KEYS,
|
|
242
|
+
...STRUCTURED_KEYS,
|
|
124
243
|
] as const
|
|
125
244
|
type AnimatableKey = (typeof ALL_KEYS)[number]
|
|
126
245
|
type TransformKey = (typeof TRANSFORM_KEYS)[number]
|
|
@@ -129,6 +248,20 @@ type ShadowOffsetKey = (typeof SHADOW_OFFSET_KEYS)[number]
|
|
|
129
248
|
const TRANSFORM_KEY_SET = new Set<AnimatableKey>(TRANSFORM_KEYS)
|
|
130
249
|
const COLOR_KEY_SET = new Set<AnimatableKey>(COLOR_KEYS)
|
|
131
250
|
const SHADOW_OFFSET_KEY_SET = new Set<AnimatableKey>(SHADOW_OFFSET_KEYS)
|
|
251
|
+
const STRUCTURED_KEY_SET = new Set<AnimatableKey>(STRUCTURED_KEYS)
|
|
252
|
+
const SHARED_STYLE_KEY_SET = new Set<AnimatableKey>(SHARED_STYLE_KEYS)
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* What a per-key shared value can hold. Scalars for every key except
|
|
256
|
+
* `boxShadow`, whose slot carries the layer array (see `STRUCTURED_KEYS`).
|
|
257
|
+
*/
|
|
258
|
+
type AnimatableSlotValue = number | string | readonly AnimatedBoxShadowLayer[]
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Resting `boxShadow` — no shadow at all. Frozen and hoisted so every
|
|
262
|
+
* primitive shares one reference and an untouched slot never allocates.
|
|
263
|
+
*/
|
|
264
|
+
const NO_BOX_SHADOW: readonly AnimatedBoxShadowLayer[] = Object.freeze([])
|
|
132
265
|
|
|
133
266
|
const GESTURE_LAYER_NAMES = [
|
|
134
267
|
'hovered',
|
|
@@ -144,7 +277,7 @@ const GESTURE_LAYER_NAME_SET = new Set<string>(GESTURE_LAYER_NAMES)
|
|
|
144
277
|
// Reanimated's style merging treats it as a no-op when present.
|
|
145
278
|
const EXITING_POINTER_EVENTS_STYLE = { pointerEvents: 'none' } as const
|
|
146
279
|
|
|
147
|
-
const DEFAULT_RESTING: Record<AnimatableKey,
|
|
280
|
+
const DEFAULT_RESTING: Record<AnimatableKey, AnimatableSlotValue> = {
|
|
148
281
|
translateX: 0,
|
|
149
282
|
translateY: 0,
|
|
150
283
|
scale: 1,
|
|
@@ -160,6 +293,54 @@ const DEFAULT_RESTING: Record<AnimatableKey, number | string> = {
|
|
|
160
293
|
shadowOpacity: 0,
|
|
161
294
|
shadowRadius: 0,
|
|
162
295
|
elevation: 0,
|
|
296
|
+
// 0.0.5 keys. Every one of these is 0 because that is RN's own unset value
|
|
297
|
+
// for it — an element with no `paddingTop` has 0 padding, no `borderWidth`
|
|
298
|
+
// has none, no `top` is unpinned at 0 within its positioning context. So a
|
|
299
|
+
// key that reaches its resting default lands where the element already was.
|
|
300
|
+
//
|
|
301
|
+
// The exception worth naming: `flex` / `flexGrow` / `flexShrink` do NOT have
|
|
302
|
+
// 0 as a universal identity (`flexShrink` defaults to 1 on the web, and RN's
|
|
303
|
+
// own default differs by axis). They are still 0 here, because the style-scan
|
|
304
|
+
// in the resting pass reads the real value off the static `style` whenever it
|
|
305
|
+
// is declared — and a consumer animating flex without declaring it is asking
|
|
306
|
+
// for the from-0 growth, which is the readable interpretation.
|
|
307
|
+
borderTopLeftRadius: 0,
|
|
308
|
+
borderTopRightRadius: 0,
|
|
309
|
+
borderBottomLeftRadius: 0,
|
|
310
|
+
borderBottomRightRadius: 0,
|
|
311
|
+
borderWidth: 0,
|
|
312
|
+
borderTopWidth: 0,
|
|
313
|
+
borderRightWidth: 0,
|
|
314
|
+
borderBottomWidth: 0,
|
|
315
|
+
borderLeftWidth: 0,
|
|
316
|
+
top: 0,
|
|
317
|
+
right: 0,
|
|
318
|
+
bottom: 0,
|
|
319
|
+
left: 0,
|
|
320
|
+
padding: 0,
|
|
321
|
+
paddingTop: 0,
|
|
322
|
+
paddingRight: 0,
|
|
323
|
+
paddingBottom: 0,
|
|
324
|
+
paddingLeft: 0,
|
|
325
|
+
paddingHorizontal: 0,
|
|
326
|
+
paddingVertical: 0,
|
|
327
|
+
margin: 0,
|
|
328
|
+
marginTop: 0,
|
|
329
|
+
marginRight: 0,
|
|
330
|
+
marginBottom: 0,
|
|
331
|
+
marginLeft: 0,
|
|
332
|
+
marginHorizontal: 0,
|
|
333
|
+
marginVertical: 0,
|
|
334
|
+
flex: 0,
|
|
335
|
+
flexGrow: 0,
|
|
336
|
+
flexShrink: 0,
|
|
337
|
+
fontSize: 0,
|
|
338
|
+
letterSpacing: 0,
|
|
339
|
+
lineHeight: 0,
|
|
340
|
+
zIndex: 0,
|
|
341
|
+
gap: 0,
|
|
342
|
+
rowGap: 0,
|
|
343
|
+
columnGap: 0,
|
|
163
344
|
// 'transparent' is the only safe universal default for colors: it works as
|
|
164
345
|
// an initial seed for any color animation (no jarring opaque flash on mount
|
|
165
346
|
// when `initial` is omitted) and rgba(0,0,0,0) interpolates cleanly into
|
|
@@ -171,6 +352,7 @@ const DEFAULT_RESTING: Record<AnimatableKey, number | string> = {
|
|
|
171
352
|
shadowColor: 'transparent',
|
|
172
353
|
shadowOffsetWidth: 0,
|
|
173
354
|
shadowOffsetHeight: 0,
|
|
355
|
+
boxShadow: NO_BOX_SHADOW,
|
|
174
356
|
}
|
|
175
357
|
|
|
176
358
|
// Both lookups run after the factory has resolved registered transition
|
|
@@ -398,6 +580,24 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
398
580
|
// identity only changes on the renders that actually add a key, so the
|
|
399
581
|
// `useAnimatedStyle` worklet (which reads `.current` each frame) sees the
|
|
400
582
|
// expansion without churning frame-to-frame.
|
|
583
|
+
const hasLayoutId = layoutId !== undefined
|
|
584
|
+
|
|
585
|
+
// The static `style`, flattened at most once per render and only when
|
|
586
|
+
// something asks for it. Two readers: the shared-element key scan just
|
|
587
|
+
// below, and the resting-value pass further down. A plain `animate`-only
|
|
588
|
+
// instance triggers neither and never pays for the flatten.
|
|
589
|
+
let flatStyle: Record<string, unknown> | undefined
|
|
590
|
+
let flatStyleRead = false
|
|
591
|
+
const getFlatStyle = () => {
|
|
592
|
+
if (!flatStyleRead) {
|
|
593
|
+
flatStyleRead = true
|
|
594
|
+
flatStyle = StyleSheet.flatten(style as never) as
|
|
595
|
+
| Record<string, unknown>
|
|
596
|
+
| undefined
|
|
597
|
+
}
|
|
598
|
+
return flatStyle
|
|
599
|
+
}
|
|
600
|
+
|
|
401
601
|
const touched = new Set<AnimatableKey>()
|
|
402
602
|
collectTouchedKeys(touched, animateRecord)
|
|
403
603
|
if (initialRecord) collectTouchedKeys(touched, initialRecord)
|
|
@@ -420,9 +620,31 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
420
620
|
}
|
|
421
621
|
if (exitRecord) collectTouchedKeys(touched, exitRecord)
|
|
422
622
|
|
|
623
|
+
// Shared-element style carry: a `layoutId` element crossfades the carried
|
|
624
|
+
// keys from its counterpart, so those keys have to be in the active set
|
|
625
|
+
// for the worklet to emit them at all.
|
|
626
|
+
//
|
|
627
|
+
// Only keys this element already has a value for join — from a record
|
|
628
|
+
// above, or from the static style. Never invented: activating a key with
|
|
629
|
+
// no source value rests it at `DEFAULT_RESTING`, and for `color` on a
|
|
630
|
+
// `Motion.Text` that inherits its colour from a parent, that default is
|
|
631
|
+
// `'transparent'` — invisible text on an element the consumer only asked
|
|
632
|
+
// to move. Same failure mode as the `0.0.3` P0, reached from the other
|
|
633
|
+
// side.
|
|
634
|
+
if (hasLayoutId) {
|
|
635
|
+
const flat = getFlatStyle()
|
|
636
|
+
if (flat) {
|
|
637
|
+
for (const key of SHARED_STYLE_KEYS) {
|
|
638
|
+
if (touched.has(key)) continue
|
|
639
|
+
if (styleValueFor(flat, key) !== undefined) touched.add(key)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
423
644
|
const activeKeysRef = useRef<readonly AnimatableKey[] | null>(null)
|
|
424
645
|
const hasTransformRef = useRef<boolean>(false)
|
|
425
646
|
const hasShadowOffsetRef = useRef<boolean>(false)
|
|
647
|
+
const hasBoxShadowRef = useRef<boolean>(false)
|
|
426
648
|
// Expand the active set only when this render touched a key we haven't
|
|
427
649
|
// recorded yet. When nothing new appears we keep the existing array
|
|
428
650
|
// identity so the worklet's captured ref doesn't see a fresh value.
|
|
@@ -446,6 +668,27 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
446
668
|
hasShadowOffsetRef.current = activeKeysRef.current.some((k) =>
|
|
447
669
|
SHADOW_OFFSET_KEY_SET.has(k),
|
|
448
670
|
)
|
|
671
|
+
hasBoxShadowRef.current = activeKeysRef.current.includes('boxShadow')
|
|
672
|
+
// Animating `boxShadow` alongside the native `shadow*` keys puts two
|
|
673
|
+
// shadow systems on one element; whichever the underlying view resolves
|
|
674
|
+
// last wins, and the result reads as a bug rather than a choice.
|
|
675
|
+
// Checked here rather than per render — the active key set is what
|
|
676
|
+
// decides it, and this block only runs when that set grows.
|
|
677
|
+
if (
|
|
678
|
+
__DEV__ &&
|
|
679
|
+
hasBoxShadowRef.current &&
|
|
680
|
+
activeKeysRef.current.some(
|
|
681
|
+
(k) => k !== 'boxShadow' && k.startsWith('shadow'),
|
|
682
|
+
)
|
|
683
|
+
) {
|
|
684
|
+
warnOnce(
|
|
685
|
+
'boxShadow-with-native-shadow',
|
|
686
|
+
'[inertia] `boxShadow` is animated alongside the native `shadow*` ' +
|
|
687
|
+
'keys on the same element, which applies two shadow systems at ' +
|
|
688
|
+
'once. Pick one: `boxShadow` for the cross-platform CSS form, or ' +
|
|
689
|
+
'the native `shadow*` keys.',
|
|
690
|
+
)
|
|
691
|
+
}
|
|
449
692
|
}
|
|
450
693
|
|
|
451
694
|
// Which keys a record actually drives *right now*. Everything else in
|
|
@@ -473,21 +716,52 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
473
716
|
// a style it has no use for.
|
|
474
717
|
const activeKeys = activeKeysRef.current!
|
|
475
718
|
const everDriven = everDrivenRef.current
|
|
476
|
-
const styleResting: Partial<Record<AnimatableKey,
|
|
719
|
+
const styleResting: Partial<Record<AnimatableKey, AnimatableSlotValue>> =
|
|
720
|
+
{}
|
|
721
|
+
// `boxShadow`'s resting value is two halves (animated layers + static
|
|
722
|
+
// inset flags), so it can't ride the scalar map above.
|
|
723
|
+
let styleRestingShadow: SplitBoxShadow | null = null
|
|
477
724
|
if (activeKeys.some((k) => !everDriven.has(k))) {
|
|
478
|
-
const flat =
|
|
479
|
-
| Record<string, unknown>
|
|
480
|
-
| undefined
|
|
725
|
+
const flat = getFlatStyle()
|
|
481
726
|
if (flat) {
|
|
482
727
|
for (const key of activeKeys) {
|
|
483
728
|
if (everDriven.has(key)) continue
|
|
729
|
+
if (key === 'boxShadow') {
|
|
730
|
+
const raw = flat.boxShadow as BoxShadowInput | undefined
|
|
731
|
+
if (raw !== undefined) {
|
|
732
|
+
styleRestingShadow = normalizeBoxShadow(raw)
|
|
733
|
+
styleResting.boxShadow = styleRestingShadow.layers
|
|
734
|
+
}
|
|
735
|
+
continue
|
|
736
|
+
}
|
|
484
737
|
const v = styleValueFor(flat, key)
|
|
485
738
|
if (v !== undefined) styleResting[key] = v
|
|
486
739
|
}
|
|
487
740
|
}
|
|
488
741
|
}
|
|
489
742
|
|
|
743
|
+
// Seed the `boxShadow` slot once, at mount, on the same precedence chain
|
|
744
|
+
// every other key uses (`initial` → `animate` → static style → default).
|
|
745
|
+
// Held in a ref rather than recomputed per render because resolving it
|
|
746
|
+
// may parse a CSS string, and the value is only ever read on the mount
|
|
747
|
+
// pass.
|
|
748
|
+
const boxShadowSeedRef = useRef<SplitBoxShadow | null>(null)
|
|
749
|
+
if (boxShadowSeedRef.current === null) {
|
|
750
|
+
const source =
|
|
751
|
+
initial === false
|
|
752
|
+
? animateRecord.boxShadow
|
|
753
|
+
: (initialRecord?.boxShadow ?? animateRecord.boxShadow)
|
|
754
|
+
boxShadowSeedRef.current =
|
|
755
|
+
source !== undefined
|
|
756
|
+
? normalizeBoxShadow(source)
|
|
757
|
+
: (styleRestingShadow ?? {
|
|
758
|
+
layers: [...NO_BOX_SHADOW],
|
|
759
|
+
insets: null,
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
|
|
490
763
|
const sharedValues = useAnimatableSharedValues((key) => {
|
|
764
|
+
if (key === 'boxShadow') return boxShadowSeedRef.current!.layers
|
|
491
765
|
// Shadow offset synthetics seed from the corresponding axis on the
|
|
492
766
|
// `shadowOffset: { width, height }` source — the consumer doesn't write
|
|
493
767
|
// `shadowOffsetWidth` / `shadowOffsetHeight` directly. Fall back to the
|
|
@@ -532,12 +806,25 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
532
806
|
// swap, a conditional colour) would stay invisible for any key some
|
|
533
807
|
// gesture / exit / variant branch happens to mention. Direct assignment,
|
|
534
808
|
// not an animation: this is a static value, not a target.
|
|
809
|
+
// Static `inset` flags for the layers currently in the `boxShadow` slot,
|
|
810
|
+
// parallel to `sharedValues.boxShadow`. Kept out of the animated payload
|
|
811
|
+
// because Reanimated would drive the boolean down its numeric path and
|
|
812
|
+
// hand native a number mid-flight; see `AnimatedBoxShadowLayer`. `null`
|
|
813
|
+
// whenever no layer is inset, which lets the worklet emit the animated
|
|
814
|
+
// array with no per-frame reassembly at all.
|
|
815
|
+
const boxShadowInsets = useSharedValue<boolean[] | null>(
|
|
816
|
+
boxShadowSeedRef.current.insets,
|
|
817
|
+
)
|
|
818
|
+
|
|
535
819
|
const styleRestingSig = stableSig(styleResting)
|
|
536
820
|
useEffect(() => {
|
|
537
821
|
for (const key of activeKeysRef.current!) {
|
|
538
822
|
if (everDrivenRef.current.has(key)) continue
|
|
539
823
|
sharedValues[key].value = (styleResting[key] ??
|
|
540
824
|
DEFAULT_RESTING[key]) as never
|
|
825
|
+
if (key === 'boxShadow') {
|
|
826
|
+
boxShadowInsets.value = styleRestingShadow?.insets ?? null
|
|
827
|
+
}
|
|
541
828
|
}
|
|
542
829
|
// `styleResting` is rebuilt each render; its signature is the real dep.
|
|
543
830
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -666,6 +953,33 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
666
953
|
transformPending > 0 ? { remaining: transformPending } : undefined
|
|
667
954
|
|
|
668
955
|
for (const key of ALL_KEYS) {
|
|
956
|
+
// Structured keys don't go through `resolveAnimatableValue` — their
|
|
957
|
+
// target is already an array, which that resolver would read as a
|
|
958
|
+
// keyframe sequence.
|
|
959
|
+
if (key === 'boxShadow') {
|
|
960
|
+
const shadowTarget = baseRecord.boxShadow
|
|
961
|
+
if (shadowTarget === undefined) continue
|
|
962
|
+
const cfg = configFor('boxShadow')
|
|
963
|
+
if (isExiting && reachesTerminalPhase(cfg)) pending++
|
|
964
|
+
driveBoxShadow(
|
|
965
|
+
sharedValues.boxShadow,
|
|
966
|
+
boxShadowInsets,
|
|
967
|
+
shadowTarget,
|
|
968
|
+
cfg,
|
|
969
|
+
makeKeyCallbackFactory(
|
|
970
|
+
'boxShadow',
|
|
971
|
+
sharedValues.boxShadow,
|
|
972
|
+
// The declared target, not the padded one — the padding is an
|
|
973
|
+
// implementation detail of getting Reanimated to interpolate.
|
|
974
|
+
shadowTarget,
|
|
975
|
+
onAnimationEndRef,
|
|
976
|
+
{ stepCount: 1, totalIterations: totalIterationsOf(cfg) },
|
|
977
|
+
isExiting ? onSettle : undefined,
|
|
978
|
+
undefined,
|
|
979
|
+
),
|
|
980
|
+
)
|
|
981
|
+
continue
|
|
982
|
+
}
|
|
669
983
|
// Shadow offset synthetics read their target from the nested
|
|
670
984
|
// `shadowOffset: { width, height }` source on `baseRecord` — the
|
|
671
985
|
// animate / exit record never has `shadowOffsetWidth` etc. on it
|
|
@@ -770,20 +1084,44 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
770
1084
|
// transform array so they compose with the user's animate transforms —
|
|
771
1085
|
// multiple `translateX` entries sum, multiple `scaleX` entries multiply,
|
|
772
1086
|
// which is exactly the FLIP semantic.
|
|
1087
|
+
//
|
|
1088
|
+
// The style half rides the same wiring: `readStyles` lets a later mount
|
|
1089
|
+
// (or our own unmount) snapshot the carried keys straight off the shared
|
|
1090
|
+
// values, and the worklet below crossfades a consumed snapshot out over
|
|
1091
|
+
// whatever this element resolves to. Reached through a ref inside the
|
|
1092
|
+
// hook, so building it inline here costs nothing downstream.
|
|
1093
|
+
const readSharedStyles = () => {
|
|
1094
|
+
if (!hasLayoutId) return undefined
|
|
1095
|
+
const out: SharedStyleSnapshot = {}
|
|
1096
|
+
let any = false
|
|
1097
|
+
for (const key of activeKeysRef.current!) {
|
|
1098
|
+
if (!SHARED_STYLE_KEY_SET.has(key)) continue
|
|
1099
|
+
const v = sharedValues[key].value
|
|
1100
|
+
// Structured slots can't be carried, and a key nothing ever gave a
|
|
1101
|
+
// value to has nothing to hand over.
|
|
1102
|
+
if (typeof v !== 'number' && typeof v !== 'string') continue
|
|
1103
|
+
out[key] = v
|
|
1104
|
+
any = true
|
|
1105
|
+
}
|
|
1106
|
+
return any ? out : undefined
|
|
1107
|
+
}
|
|
1108
|
+
|
|
773
1109
|
const sharedLayout = useSharedLayout({
|
|
774
1110
|
layoutId,
|
|
775
1111
|
userRef: ref,
|
|
776
1112
|
transition: isTopLevelTransition(transition) ? transition : undefined,
|
|
777
1113
|
shouldReduceMotion,
|
|
778
1114
|
userOnLayout,
|
|
1115
|
+
readStyles: readSharedStyles,
|
|
779
1116
|
})
|
|
780
1117
|
const flip = sharedLayout.flip
|
|
781
|
-
const
|
|
1118
|
+
const carry = sharedLayout.carry
|
|
782
1119
|
|
|
783
1120
|
const animatedStyle = useAnimatedStyle(() => {
|
|
784
1121
|
const activeKeys = activeKeysRef.current!
|
|
785
1122
|
const hasTransform = hasTransformRef.current
|
|
786
1123
|
const hasShadowOffset = hasShadowOffsetRef.current
|
|
1124
|
+
const hasBoxShadow = hasBoxShadowRef.current
|
|
787
1125
|
const out: Record<string, unknown> = {}
|
|
788
1126
|
const transform: Array<Record<string, unknown>> = []
|
|
789
1127
|
// shadow-offset reassembly buffers. The two synthetic axis SVs feed in
|
|
@@ -799,6 +1137,12 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
799
1137
|
const pfv = focusVisibleProgress.value
|
|
800
1138
|
const pp = pressedProgress.value
|
|
801
1139
|
|
|
1140
|
+
// Shared-element style carry. Both reads are cheap and the pair rests
|
|
1141
|
+
// at (null, 0), so a primitive that isn't mid-transition — which is
|
|
1142
|
+
// every primitive, almost all of the time — pays one comparison.
|
|
1143
|
+
const carried = carry.snapshot.value
|
|
1144
|
+
const pc = carry.progress.value
|
|
1145
|
+
|
|
802
1146
|
const layers = gestureSV.value
|
|
803
1147
|
// Locals are suffixed `Layer` so they don't shadow the outer `pressed` /
|
|
804
1148
|
// `focused` / `focusVisible` / `hovered` JS-state booleans — Reanimated's
|
|
@@ -810,6 +1154,11 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
810
1154
|
const pressedLayer = layers ? layers.pressed : null
|
|
811
1155
|
|
|
812
1156
|
for (const key of activeKeys) {
|
|
1157
|
+
// `boxShadow` is emitted after the loop: its value is a layer array,
|
|
1158
|
+
// which none of the scalar compositing below can act on. Gesture
|
|
1159
|
+
// sub-states are rejected for this key upstream, so there is no
|
|
1160
|
+
// layer contribution to skip past either.
|
|
1161
|
+
if (key === 'boxShadow') continue
|
|
813
1162
|
let v = sharedValues[key].value
|
|
814
1163
|
const isColor = COLOR_KEY_SET.has(key)
|
|
815
1164
|
|
|
@@ -848,6 +1197,23 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
848
1197
|
: (v as number) + ((t as number) - (v as number)) * pp
|
|
849
1198
|
}
|
|
850
1199
|
|
|
1200
|
+
// Shared-element carry sits above every gesture layer: the element
|
|
1201
|
+
// is arriving from somewhere else, and where it came from outranks
|
|
1202
|
+
// how it is being touched right now. `pc` runs 1 → 0, so the first
|
|
1203
|
+
// frame shows the source's value and the last shows this element's
|
|
1204
|
+
// own — including whatever the layers underneath contributed.
|
|
1205
|
+
//
|
|
1206
|
+
// Transform keys never appear in a snapshot (the FLIP owns them), so
|
|
1207
|
+
// this can't double-apply displacement.
|
|
1208
|
+
if (carried !== null && pc > 0) {
|
|
1209
|
+
const s = carried[key]
|
|
1210
|
+
if (s !== undefined) {
|
|
1211
|
+
v = isColor
|
|
1212
|
+
? interpolateColor(pc, [0, 1], [v as string, s as string])
|
|
1213
|
+
: (v as number) + ((s as number) - (v as number)) * pc
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
851
1217
|
if (TRANSFORM_KEY_SET.has(key)) {
|
|
852
1218
|
transform.push(
|
|
853
1219
|
ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v },
|
|
@@ -876,12 +1242,28 @@ export function createMotionComponent<C extends ComponentType<any>>(
|
|
|
876
1242
|
if (hasShadowOffset) {
|
|
877
1243
|
out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH }
|
|
878
1244
|
}
|
|
1245
|
+
if (hasBoxShadow) {
|
|
1246
|
+
const layers = sharedValues.boxShadow
|
|
1247
|
+
.value as readonly AnimatedBoxShadowLayer[]
|
|
1248
|
+
const insets = boxShadowInsets.value
|
|
1249
|
+
// Fast path — no inset layer anywhere, so the animated array is
|
|
1250
|
+
// already exactly what RN wants and crosses over untouched.
|
|
1251
|
+
if (insets === null) {
|
|
1252
|
+
out.boxShadow = layers
|
|
1253
|
+
} else {
|
|
1254
|
+
const withInset = []
|
|
1255
|
+
for (let i = 0; i < layers.length; i++) {
|
|
1256
|
+
withInset.push({ ...layers[i], inset: insets[i] })
|
|
1257
|
+
}
|
|
1258
|
+
out.boxShadow = withInset
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
879
1261
|
return out
|
|
880
1262
|
})
|
|
881
1263
|
|
|
882
1264
|
// Exiting children are tap-deaf: the next press should fall through to
|
|
883
1265
|
// whatever is underneath, not re-trigger a soon-to-unmount node. This is
|
|
884
|
-
// the moti #297 fix and a
|
|
1266
|
+
// the moti #297 fix and a `0.0.1` acceptance criterion. RN 0.71+ deprecates
|
|
885
1267
|
// `pointerEvents` as a prop in favor of the style key, so we merge it
|
|
886
1268
|
// alongside the animated style instead of spreading as a prop.
|
|
887
1269
|
const mergedStyle = useMemo(
|
|
@@ -969,7 +1351,7 @@ function hasMotionProps(props: Record<string, unknown>): boolean {
|
|
|
969
1351
|
return false
|
|
970
1352
|
}
|
|
971
1353
|
|
|
972
|
-
type SharedValueMap = Record<AnimatableKey, SharedValue<
|
|
1354
|
+
type SharedValueMap = Record<AnimatableKey, SharedValue<AnimatableSlotValue>>
|
|
973
1355
|
|
|
974
1356
|
/**
|
|
975
1357
|
* Allocate one shared value per animatable key in `ALL_KEYS` and return a
|
|
@@ -983,70 +1365,38 @@ type SharedValueMap = Record<AnimatableKey, SharedValue<number | string>>
|
|
|
983
1365
|
* are stable across renders (Reanimated's `useSharedValue` is a `useRef`
|
|
984
1366
|
* under the hood), so snapshotting the wrapping object once is safe.
|
|
985
1367
|
*
|
|
986
|
-
* Hooks are called
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
1368
|
+
* Hooks are called by iterating `ALL_KEYS`, which is a module-level `as const`
|
|
1369
|
+
* array — its length and order are fixed at module evaluation, so the hook
|
|
1370
|
+
* sequence is identical on every render of every instance. That satisfies
|
|
1371
|
+
* rules-of-hooks exactly as a hand-written list does, and it is what keeps this
|
|
1372
|
+
* function from growing a line per key: the 0.0.5 additions took the table from
|
|
1373
|
+
* 23 keys to 63, and 63 near-identical `useSharedValue` lines is both a bigger
|
|
1374
|
+
* bundle and a standing invitation to add a key in one list but not the other
|
|
1375
|
+
* (the failure mode is a `sharedValues[key]` of `undefined` inside the worklet,
|
|
1376
|
+
* which is exactly how the first draft of that change broke).
|
|
1377
|
+
*
|
|
1378
|
+
* The ESLint exhaustive-deps rule can't see through the loop, hence the
|
|
1379
|
+
* disable — the invariant it would check for us is asserted above instead.
|
|
1380
|
+
*
|
|
1381
|
+
* Unused shared values are cheap; the worklet skips them via `activeKeysRef`.
|
|
1382
|
+
* Color keys are seeded with the initial color string so Reanimated's value
|
|
1383
|
+
* setter recognizes the slot as a color from the first `withSpring` /
|
|
1384
|
+
* `withTiming` call.
|
|
991
1385
|
*/
|
|
992
1386
|
function useAnimatableSharedValues(
|
|
993
|
-
init: (key: AnimatableKey) =>
|
|
1387
|
+
init: (key: AnimatableKey) => AnimatableSlotValue,
|
|
994
1388
|
): SharedValueMap {
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const rotate = useSharedValue<number | string>(init('rotate'))
|
|
1001
|
-
const rotateX = useSharedValue<number | string>(init('rotateX'))
|
|
1002
|
-
const rotateY = useSharedValue<number | string>(init('rotateY'))
|
|
1003
|
-
const opacity = useSharedValue<number | string>(init('opacity'))
|
|
1004
|
-
const width = useSharedValue<number | string>(init('width'))
|
|
1005
|
-
const height = useSharedValue<number | string>(init('height'))
|
|
1006
|
-
const borderRadius = useSharedValue<number | string>(init('borderRadius'))
|
|
1007
|
-
const shadowOpacity = useSharedValue<number | string>(init('shadowOpacity'))
|
|
1008
|
-
const shadowRadius = useSharedValue<number | string>(init('shadowRadius'))
|
|
1009
|
-
const elevation = useSharedValue<number | string>(init('elevation'))
|
|
1010
|
-
const backgroundColor = useSharedValue<number | string>(
|
|
1011
|
-
init('backgroundColor'),
|
|
1012
|
-
)
|
|
1013
|
-
const borderColor = useSharedValue<number | string>(init('borderColor'))
|
|
1014
|
-
const color = useSharedValue<number | string>(init('color'))
|
|
1015
|
-
const tintColor = useSharedValue<number | string>(init('tintColor'))
|
|
1016
|
-
const shadowColor = useSharedValue<number | string>(init('shadowColor'))
|
|
1017
|
-
const shadowOffsetWidth = useSharedValue<number | string>(
|
|
1018
|
-
init('shadowOffsetWidth'),
|
|
1019
|
-
)
|
|
1020
|
-
const shadowOffsetHeight = useSharedValue<number | string>(
|
|
1021
|
-
init('shadowOffsetHeight'),
|
|
1022
|
-
)
|
|
1389
|
+
const entries: [AnimatableKey, SharedValue<AnimatableSlotValue>][] = []
|
|
1390
|
+
for (const key of ALL_KEYS) {
|
|
1391
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1392
|
+
entries.push([key, useSharedValue<AnimatableSlotValue>(init(key))])
|
|
1393
|
+
}
|
|
1023
1394
|
|
|
1024
1395
|
const ref = useRef<SharedValueMap | null>(null)
|
|
1025
1396
|
if (ref.current === null) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
scale,
|
|
1030
|
-
scaleX,
|
|
1031
|
-
scaleY,
|
|
1032
|
-
rotate,
|
|
1033
|
-
rotateX,
|
|
1034
|
-
rotateY,
|
|
1035
|
-
opacity,
|
|
1036
|
-
width,
|
|
1037
|
-
height,
|
|
1038
|
-
borderRadius,
|
|
1039
|
-
shadowOpacity,
|
|
1040
|
-
shadowRadius,
|
|
1041
|
-
elevation,
|
|
1042
|
-
backgroundColor,
|
|
1043
|
-
borderColor,
|
|
1044
|
-
color,
|
|
1045
|
-
tintColor,
|
|
1046
|
-
shadowColor,
|
|
1047
|
-
shadowOffsetWidth,
|
|
1048
|
-
shadowOffsetHeight,
|
|
1049
|
-
}
|
|
1397
|
+
const map = {} as SharedValueMap
|
|
1398
|
+
for (const [key, sv] of entries) map[key] = sv
|
|
1399
|
+
ref.current = map
|
|
1050
1400
|
}
|
|
1051
1401
|
|
|
1052
1402
|
// Cancel every in-flight per-key animation when the primitive unmounts, so
|
|
@@ -1084,8 +1434,11 @@ function useAnimatableSharedValues(
|
|
|
1084
1434
|
*/
|
|
1085
1435
|
function makeKeyCallbackFactory(
|
|
1086
1436
|
key: string,
|
|
1087
|
-
sharedValue: SharedValue<
|
|
1088
|
-
|
|
1437
|
+
sharedValue: SharedValue<AnimatableSlotValue>,
|
|
1438
|
+
// Widened past the scalar surface for `boxShadow`, whose declared target is
|
|
1439
|
+
// a CSS string or a layer array. Reaches the consumer as
|
|
1440
|
+
// `AnimationCallbackInfo.target`, which is `unknown`.
|
|
1441
|
+
target: number | string | BoxShadowInput | undefined,
|
|
1089
1442
|
onAnimationEndRef: {
|
|
1090
1443
|
current: ((info: AnimationCallbackInfo<unknown>) => void) | undefined
|
|
1091
1444
|
},
|
|
@@ -1105,7 +1458,9 @@ function makeKeyCallbackFactory(
|
|
|
1105
1458
|
rawPhase: 'step' | 'animation',
|
|
1106
1459
|
step: number | undefined,
|
|
1107
1460
|
finished: boolean,
|
|
1108
|
-
value
|
|
1461
|
+
// Widened for `boxShadow`, whose settled value is a layer array. Surfaces
|
|
1462
|
+
// on `AnimationCallbackInfo.value`, which is `unknown` either way.
|
|
1463
|
+
value: AnimatableSlotValue | undefined,
|
|
1109
1464
|
) => {
|
|
1110
1465
|
const isLastIteration = state.iteration >= meta.totalIterations - 1
|
|
1111
1466
|
let phase: 'step' | 'sequence' | 'repeat' | 'animation'
|
|
@@ -1191,18 +1546,25 @@ function makeKeyCallbackFactory(
|
|
|
1191
1546
|
* nested object decomposes into the `shadowOffsetWidth` / `shadowOffsetHeight`
|
|
1192
1547
|
* synthetic axes downstream; consumers don't see the synthetics.
|
|
1193
1548
|
*
|
|
1194
|
-
*
|
|
1195
|
-
* (no sequences, no `{ to }` step objects, no array keyframes).
|
|
1196
|
-
* forms on the nested axes can land
|
|
1549
|
+
* Contract since `0.0.1`: `shadowOffset` accepts a single `{ width, height }`
|
|
1550
|
+
* literal (no sequences, no `{ to }` step objects, no array keyframes).
|
|
1551
|
+
* Sequence forms on the nested axes can land later if real consumers ask.
|
|
1197
1552
|
*/
|
|
1553
|
+
type ScalarKey = Exclude<AnimatableKey, (typeof STRUCTURED_KEYS)[number]>
|
|
1554
|
+
|
|
1198
1555
|
type InternalAnimateRecord = Partial<
|
|
1199
|
-
Record<
|
|
1556
|
+
Record<ScalarKey, AnimatableValue<number | string>>
|
|
1200
1557
|
> & {
|
|
1201
1558
|
shadowOffset?: { width?: number; height?: number }
|
|
1559
|
+
// Structured keys depart from `AnimatableValue` — see `STRUCTURED_KEYS`.
|
|
1560
|
+
// Kept out of the mapped half above so the two don't intersect into an
|
|
1561
|
+
// uninhabitable `AnimatableValue<number | string> & BoxShadowInput`.
|
|
1562
|
+
boxShadow?: BoxShadowInput
|
|
1202
1563
|
}
|
|
1203
1564
|
|
|
1204
|
-
type InternalInitialRecord = Partial<Record<
|
|
1565
|
+
type InternalInitialRecord = Partial<Record<ScalarKey, number | string>> & {
|
|
1205
1566
|
shadowOffset?: { width?: number; height?: number }
|
|
1567
|
+
boxShadow?: BoxShadowInput
|
|
1206
1568
|
}
|
|
1207
1569
|
|
|
1208
1570
|
/**
|
|
@@ -1225,6 +1587,46 @@ function shadowOffsetAxisValue(
|
|
|
1225
1587
|
return source?.[axis]
|
|
1226
1588
|
}
|
|
1227
1589
|
|
|
1590
|
+
/**
|
|
1591
|
+
* Drive the `boxShadow` slot toward a new target.
|
|
1592
|
+
*
|
|
1593
|
+
* Two things make this different from the scalar path:
|
|
1594
|
+
*
|
|
1595
|
+
* 1. **Both endpoints are padded to a common layer count first.** Reanimated's
|
|
1596
|
+
* array driver iterates the *current* value's indices and pairs each with
|
|
1597
|
+
* `toValue[i]`, so a target with a different layer count either strands
|
|
1598
|
+
* leaves at `toValue: undefined` or never animates the extras. When the
|
|
1599
|
+
* padding changes the slot's shape, we snap it to the padded base before
|
|
1600
|
+
* starting the animation — otherwise the animation would begin from a
|
|
1601
|
+
* differently-shaped value and the pairing would be lost again.
|
|
1602
|
+
* 2. **`inset` travels separately**, in its own slot, because Reanimated
|
|
1603
|
+
* would drive the boolean down the numeric path.
|
|
1604
|
+
*/
|
|
1605
|
+
function driveBoxShadow(
|
|
1606
|
+
slot: SharedValue<AnimatableSlotValue>,
|
|
1607
|
+
insetSlot: SharedValue<boolean[] | null>,
|
|
1608
|
+
target: BoxShadowInput,
|
|
1609
|
+
cfg: TransitionConfig | undefined,
|
|
1610
|
+
factory: CallbackFactory | undefined,
|
|
1611
|
+
): void {
|
|
1612
|
+
const currentLayers = Array.isArray(slot.value)
|
|
1613
|
+
? (slot.value as readonly AnimatedBoxShadowLayer[])
|
|
1614
|
+
: NO_BOX_SHADOW
|
|
1615
|
+
const { from, to, insets } = prepareBoxShadowAnimation(
|
|
1616
|
+
{ layers: [...currentLayers], insets: insetSlot.value },
|
|
1617
|
+
target,
|
|
1618
|
+
)
|
|
1619
|
+
insetSlot.value = insets
|
|
1620
|
+
if (from.length !== currentLayers.length) slot.value = from
|
|
1621
|
+
// `resolveTransition` is typed for the scalar surface it was written for;
|
|
1622
|
+
// Reanimated itself accepts the structured target and recurses into it.
|
|
1623
|
+
slot.value = resolveTransition(
|
|
1624
|
+
cfg,
|
|
1625
|
+
to as unknown as number,
|
|
1626
|
+
factory?.('animation', undefined),
|
|
1627
|
+
) as AnimatableSlotValue
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1228
1630
|
/**
|
|
1229
1631
|
* Read an animatable key's resting value out of a **flattened style object**.
|
|
1230
1632
|
*
|
|
@@ -1460,6 +1862,27 @@ function resolveGestureLayers(
|
|
|
1460
1862
|
if (!subState) continue
|
|
1461
1863
|
const resolved: Record<string, number | string> = {}
|
|
1462
1864
|
for (const key of ALL_KEYS) {
|
|
1865
|
+
// Structured keys have no place in the layer cascade: compositing them
|
|
1866
|
+
// would mean per-layer, per-field interpolation on the UI thread for
|
|
1867
|
+
// every primitive, whether or not it animates a shadow. Rejected for
|
|
1868
|
+
// now rather than half-supported — a silently-ignored `boxShadow` in a
|
|
1869
|
+
// `gesture` sub-state is exactly the class of bug this library exists
|
|
1870
|
+
// to avoid, so say so.
|
|
1871
|
+
if (STRUCTURED_KEY_SET.has(key)) {
|
|
1872
|
+
if (
|
|
1873
|
+
__DEV__ &&
|
|
1874
|
+
(subState as Record<string, unknown>)[key] !== undefined
|
|
1875
|
+
) {
|
|
1876
|
+
warnOnce(
|
|
1877
|
+
`gesture-structured-${layer}-${key}`,
|
|
1878
|
+
`[inertia] \`${key}\` is not supported inside \`gesture.${layer}\` ` +
|
|
1879
|
+
'and will be ignored. Drive it from `animate` (optionally via a ' +
|
|
1880
|
+
'variant keyed off the same state), or interpolate it yourself ' +
|
|
1881
|
+
'with `useShadow`.',
|
|
1882
|
+
)
|
|
1883
|
+
}
|
|
1884
|
+
continue
|
|
1885
|
+
}
|
|
1463
1886
|
// Shadow offset synthetics decompose from the nested `shadowOffset:
|
|
1464
1887
|
// { width, height }` source on the sub-state, the same as on `animate`.
|
|
1465
1888
|
if (SHADOW_OFFSET_KEY_SET.has(key)) {
|