@rootnative/inertia 0.0.2 → 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.
Files changed (77) hide show
  1. package/CHANGELOG.md +63 -5
  2. package/README.md +5 -3
  3. package/dist/{chunk-ZN3PNGHQ.mjs → chunk-4ASOELLA.mjs} +1 -1
  4. package/dist/{chunk-LYMLQ6WQ.mjs → chunk-55W7BNO2.mjs} +604 -106
  5. package/dist/chunk-5SRD2WCO.js +8 -0
  6. package/dist/{chunk-PTRF47DA.js → chunk-7A6WTXNK.js} +10 -3
  7. package/dist/{chunk-NKA6XR77.js → chunk-CENTULW6.js} +2 -2
  8. package/dist/{chunk-4PEHWDAZ.js → chunk-D5KAHWMC.js} +7 -7
  9. package/dist/{chunk-2DHKV2XY.mjs → chunk-J5Q46OPW.mjs} +1 -1
  10. package/dist/{chunk-RGNX6UZN.mjs → chunk-K3CLP74S.mjs} +2 -2
  11. package/dist/{chunk-IWLU3VDE.js → chunk-MYE5NMXW.js} +623 -123
  12. package/dist/{chunk-HFDZAHWP.mjs → chunk-NAOXQJ7U.mjs} +1 -1
  13. package/dist/{chunk-R63GIUNU.mjs → chunk-QKC3APP4.mjs} +1 -1
  14. package/dist/{chunk-V6BTXHZK.mjs → chunk-RF7J5FRM.mjs} +1 -1
  15. package/dist/{chunk-CY7Y64C3.mjs → chunk-TU2XFPAK.mjs} +10 -4
  16. package/dist/chunk-TVFNJ25Z.js +8 -0
  17. package/dist/chunk-VLDIFL3F.js +8 -0
  18. package/dist/{chunk-7AOERN53.js → chunk-WVRCKOPO.js} +3 -3
  19. package/dist/{chunk-TUYHTHVV.js → chunk-XMEVPEMH.js} +2 -2
  20. package/dist/{chunk-TUMNV4P7.mjs → chunk-YKGY22SB.mjs} +1 -1
  21. package/dist/gestureLayer/index.d.mts +2 -2
  22. package/dist/gestureLayer/index.d.ts +2 -2
  23. package/dist/gestureLayer/index.js +10 -10
  24. package/dist/gestureLayer/index.mjs +3 -3
  25. package/dist/index.d.mts +14 -12
  26. package/dist/index.d.ts +14 -12
  27. package/dist/index.js +68 -176
  28. package/dist/index.mjs +40 -147
  29. package/dist/motion/Image.d.mts +1 -1
  30. package/dist/motion/Image.d.ts +1 -1
  31. package/dist/motion/Image.js +5 -5
  32. package/dist/motion/Image.mjs +4 -4
  33. package/dist/motion/Pressable.d.mts +1 -1
  34. package/dist/motion/Pressable.d.ts +1 -1
  35. package/dist/motion/Pressable.js +5 -5
  36. package/dist/motion/Pressable.mjs +4 -4
  37. package/dist/motion/ScrollView.d.mts +1 -1
  38. package/dist/motion/ScrollView.d.ts +1 -1
  39. package/dist/motion/ScrollView.js +5 -5
  40. package/dist/motion/ScrollView.mjs +4 -4
  41. package/dist/motion/Text.d.mts +1 -1
  42. package/dist/motion/Text.d.ts +1 -1
  43. package/dist/motion/Text.js +5 -5
  44. package/dist/motion/Text.mjs +4 -4
  45. package/dist/motion/View.d.mts +1 -1
  46. package/dist/motion/View.d.ts +1 -1
  47. package/dist/motion/View.js +5 -5
  48. package/dist/motion/View.mjs +4 -4
  49. package/dist/touch/index.d.mts +1 -1
  50. package/dist/touch/index.d.ts +1 -1
  51. package/dist/touch/index.js +3 -3
  52. package/dist/touch/index.mjs +1 -1
  53. package/dist/{types-G1dBU2IQ.d.mts → types-37pN82yh.d.mts} +49 -14
  54. package/dist/{types-G1dBU2IQ.d.ts → types-37pN82yh.d.ts} +49 -14
  55. package/dist/{useGesture-D1e4ixJo.d.ts → useGesture-Cl4uzZIr.d.ts} +1 -1
  56. package/dist/{useGesture-RFY6kTL3.d.mts → useGesture-_IKkS8Dv.d.mts} +1 -1
  57. package/llms.txt +3 -1
  58. package/package.json +1 -1
  59. package/src/index.ts +2 -1
  60. package/src/internal/boxShadow.ts +177 -6
  61. package/src/internal/nonWorkletWarning.ts +4 -6
  62. package/src/internal/warnOnce.ts +28 -0
  63. package/src/layout/index.ts +11 -0
  64. package/src/layout/measureWindow.ts +135 -0
  65. package/src/layout/sharedRegistry.ts +156 -18
  66. package/src/layout/useSharedLayout.ts +256 -91
  67. package/src/motion/createMotionComponent.tsx +550 -55
  68. package/src/motion/installCheck.ts +1 -1
  69. package/src/presence/Presence.tsx +73 -10
  70. package/src/transitions/index.ts +1 -1
  71. package/src/types.ts +52 -12
  72. package/src/values/useAnimator.ts +25 -14
  73. package/src/values/useColorCascade.ts +34 -16
  74. package/src/values/useColorTransition.ts +1 -1
  75. package/dist/chunk-34Q4UM6V.js +0 -8
  76. package/dist/chunk-KYJROYCG.js +0 -8
  77. package/dist/chunk-X5J5M3K3.js +0 -8
@@ -8,6 +8,7 @@ import {
8
8
  } from 'react'
9
9
  import { type LayoutChangeEvent } from 'react-native'
10
10
  import {
11
+ cancelAnimation,
11
12
  type SharedValue,
12
13
  useSharedValue,
13
14
  withSequence,
@@ -16,11 +17,14 @@ import {
16
17
  } from 'react-native-reanimated'
17
18
  import { DEFAULT_SPRING, springToReanimated } from '../transitions/spring'
18
19
  import { type SpringTransition, type TransitionConfig } from '../types'
20
+ import { measureWindowRect } from './measureWindow'
19
21
  import {
20
22
  consumeLayout,
21
23
  registerLayout,
22
24
  releaseLayout,
25
+ type SharedLayoutSource,
23
26
  type SharedRect,
27
+ type SharedStyleSnapshot,
24
28
  } from './sharedRegistry'
25
29
 
26
30
  /**
@@ -43,9 +47,29 @@ export interface SharedLayoutValues {
43
47
  sy: SharedValue<number>
44
48
  }
45
49
 
50
+ /**
51
+ * Shared values carrying the *style* half of a shared-element transition — the
52
+ * part the rect FLIP can't express.
53
+ *
54
+ * `snapshot` parks the source element's values for the carried keys; `progress`
55
+ * runs 1 → 0 on the same transition as the FLIP. The host's worklet reads both
56
+ * and pulls each carried key from the snapshot toward its own resolved value,
57
+ * so at the first frame the arriving element wears the source's colors and by
58
+ * the last it wears its own.
59
+ *
60
+ * At rest `snapshot` is `null` and `progress` is 0, so the worklet's whole
61
+ * contribution is one comparison.
62
+ */
63
+ export interface SharedLayoutStyleValues {
64
+ snapshot: SharedValue<SharedStyleSnapshot | null>
65
+ progress: SharedValue<number>
66
+ }
67
+
46
68
  /** What the host component needs to wire into its rendered tree. */
47
69
  export interface SharedLayoutBindings {
48
70
  flip: SharedLayoutValues
71
+ /** Source-style carry for the same transition. See `SharedLayoutStyleValues`. */
72
+ carry: SharedLayoutStyleValues
49
73
  /**
50
74
  * Composite ref the consumer attaches to the rendered animated
51
75
  * component. Forwards the underlying ref to the user-supplied `ref`
@@ -72,14 +96,32 @@ export interface SharedLayoutBindings {
72
96
  * visually places the new element at the source position, then
73
97
  * animate back to identity. `withSequence(snap, animate)` keeps the
74
98
  * animation starting from the snapped delta rather than from zero.
99
+ * 5. Carry the source's style values for the keys the rect can't express
100
+ * (colors, `opacity`, `borderRadius`) by parking them in a snapshot and
101
+ * running a crossfade progress 1 → 0 on the same transition. The blend
102
+ * happens in the host's worklet; see `SharedLayoutStyleValues`.
103
+ *
104
+ * Coordinate space: rects are recorded in **window** coordinates when the host
105
+ * can be measured synchronously (Fabric), and in the parent-relative
106
+ * coordinates `onLayout` reports when it can't (Paper, or a detached node).
107
+ * Window coordinates are what let a source and target sitting under containers
108
+ * at different screen offsets FLIP correctly — the case the original
109
+ * parent-relative implementation got wrong. A source and target that ended up
110
+ * in *different* spaces are not comparable, so the transition is skipped rather
111
+ * than played wrongly. See `measureWindow.ts` for why there is no third,
112
+ * asynchronous option.
113
+ *
114
+ * The source is re-measured at *consume* time when it is still mounted (the
115
+ * usual case — a stack navigator keeps the previous screen alive underneath).
116
+ * That is what stops a scrolled list from offsetting every transition by its
117
+ * scroll distance: `onLayout` never fires for an ancestor's scroll, so the
118
+ * stored rect drifts while the element itself has not moved relative to its
119
+ * parent. See `SharedLayoutSource`.
75
120
  *
76
- * Coordinate space note: rects are in parent-relative coordinates (what
77
- * `onLayout` reports). For the common cross-screen navigator pattern
78
- * both screens share an outer content container parent-relative deltas
79
- * match what the user perceives. Nested-parent setups where the source
80
- * and target screens sit under containers at different screen offsets
81
- * will be off by that offset; v1 documents this and leaves a precise
82
- * window-coords path for v2.
121
+ * Known limitation: if the navigator has already begun translating the outgoing
122
+ * screen by the time the target first lays out, re-measuring catches the source
123
+ * mid-transition. The error is bounded by however far the transition has
124
+ * progressed, which is far smaller than an unbounded scroll offset.
83
125
  *
84
126
  * When `layoutId` is `undefined`, every callback is a no-op and the FLIP
85
127
  * shared values stay at identity — the host's worklet then skips the
@@ -91,15 +133,33 @@ export function useSharedLayout(options: {
91
133
  transition: TransitionConfig | undefined
92
134
  shouldReduceMotion: boolean
93
135
  userOnLayout: ((event: LayoutChangeEvent) => void) | undefined
136
+ /**
137
+ * Read this element's current values for the carried style keys. Supplied by
138
+ * the host (which owns the per-key shared values); `undefined` when the host
139
+ * has nothing to carry. Identity may change every render — it is read through
140
+ * a ref, so it never churns `onLayout`.
141
+ */
142
+ readStyles?: () => SharedStyleSnapshot | undefined
94
143
  }): SharedLayoutBindings {
95
- const { layoutId, userRef, transition, shouldReduceMotion, userOnLayout } =
96
- options
144
+ const {
145
+ layoutId,
146
+ userRef,
147
+ transition,
148
+ shouldReduceMotion,
149
+ userOnLayout,
150
+ readStyles,
151
+ } = options
97
152
 
98
153
  const dx = useSharedValue(0)
99
154
  const dy = useSharedValue(0)
100
155
  const sx = useSharedValue(1)
101
156
  const sy = useSharedValue(1)
102
157
 
158
+ // Style-carry pair. `null` / 0 at rest means the host worklet skips the
159
+ // whole branch on any element that isn't mid-transition.
160
+ const snapshot = useSharedValue<SharedStyleSnapshot | null>(null)
161
+ const carryProgress = useSharedValue(0)
162
+
103
163
  // Most-recent rect for this primitive. Updated on every layout commit;
104
164
  // read on unmount to populate the registry as the FLIP source for the
105
165
  // next mount with the same id.
@@ -110,55 +170,100 @@ export function useSharedLayout(options: {
110
170
  // refresh the registry but never re-trigger a FLIP from an old source.
111
171
  const consumedRef = useRef(false)
112
172
 
173
+ // The host node, captured off the composite ref so this element can be
174
+ // measured in window coordinates — both for its own registry entry and when
175
+ // a later mount asks it to re-measure as a FLIP source.
176
+ const nodeRef = useRef<unknown>(null)
177
+
113
178
  const transitionRef = useRef(transition)
114
179
  transitionRef.current = transition
115
180
  const reducedMotionRef = useRef(shouldReduceMotion)
116
181
  reducedMotionRef.current = shouldReduceMotion
182
+ const readStylesRef = useRef(readStyles)
183
+ readStylesRef.current = readStyles
117
184
 
118
185
  const setRef = useCallback(
119
186
  (node: unknown) => {
187
+ nodeRef.current = node
120
188
  if (typeof userRef === 'function') userRef(node)
121
189
  else if (userRef) (userRef as MutableRefObject<unknown>).current = node
122
190
  },
123
191
  [userRef],
124
192
  )
125
193
 
194
+ // Offered to the registry while mounted so a later mount can ask for a
195
+ // current measurement instead of trusting the one from our last layout.
196
+ const remeasure = useCallback(() => measureWindowRect(nodeRef.current), [])
197
+
198
+ // Same idea for the carried style keys: the host's values move without a
199
+ // layout pass, so a consumer should read them now rather than trust the
200
+ // snapshot from whenever we last laid out. Stable identity — the host's
201
+ // reader is reached through a ref.
202
+ const snapshotStyles = useCallback(() => readStylesRef.current?.(), [])
203
+
204
+ const startTransition = useCallback(
205
+ (
206
+ source: SharedLayoutSource,
207
+ sourceRect: SharedRect,
208
+ target: SharedRect,
209
+ ) => {
210
+ // Mixed coordinate spaces are not comparable — the delta would be off by
211
+ // the parent's window offset. Skipping is the honest outcome; playing
212
+ // the animation anyway would fling the element in from the wrong place.
213
+ // The style carry goes with it: half a shared-element transition reads
214
+ // as a glitch, not as a graceful degradation.
215
+ if (sourceRect.space !== target.space) return
216
+ const shared = {
217
+ transition: transitionRef.current,
218
+ shouldReduceMotion: reducedMotionRef.current,
219
+ }
220
+ applyFlip({ source: sourceRect, target, dx, dy, sx, sy, ...shared })
221
+ applyStyleCarry({
222
+ snapshot,
223
+ progress: carryProgress,
224
+ // Prefer a live read for the same reason the rect is re-measured: a
225
+ // still-mounted source may have moved on since its last layout.
226
+ styles: source.readStyles?.() ?? source.styles,
227
+ ...shared,
228
+ })
229
+ },
230
+ [dx, dy, sx, sy, snapshot, carryProgress],
231
+ )
232
+
126
233
  const onLayout = useCallback(
127
234
  (event: LayoutChangeEvent) => {
128
235
  userOnLayout?.(event)
129
236
  if (!layoutId) return
130
237
 
131
238
  const { x, y, width, height } = event.nativeEvent.layout
132
- const rect: SharedRect = { x, y, width, height }
133
- lastRectRef.current = rect
134
239
 
135
240
  // First-layout-only: read the registry BEFORE writing our own rect
136
241
  // so a previously-released source rect can be consumed cleanly
137
242
  // without being overwritten by the current rect first.
138
- let source: SharedRect | undefined
243
+ let source: SharedLayoutSource | undefined
139
244
  if (!consumedRef.current) {
140
245
  consumedRef.current = true
141
246
  source = consumeLayout(layoutId)
142
247
  }
143
- registerLayout(layoutId, rect)
144
-
145
- if (source) {
146
- applyFlip({
147
- source,
148
- target: rect,
149
- dx,
150
- dy,
151
- sx,
152
- sy,
153
- transition: transitionRef.current,
154
- shouldReduceMotion: reducedMotionRef.current,
155
- })
156
- }
248
+
249
+ // Window coordinates when the host can supply them synchronously,
250
+ // otherwise the parent-relative rect this event carries. See
251
+ // `measureWindow.ts` for why there is no third, asynchronous option.
252
+ const rect: SharedRect =
253
+ measureWindowRect(nodeRef.current) ??
254
+ ({ x, y, width, height, space: 'parent' } as SharedRect)
255
+
256
+ lastRectRef.current = rect
257
+ registerLayout(layoutId, rect, remeasure, snapshotStyles)
258
+
259
+ if (!source) return
260
+ // Prefer a fresh measurement of the source over its stored rect: it may
261
+ // have scrolled since its last layout, and `onLayout` never reports an
262
+ // ancestor's scroll. Falls back to the stored rect once the source has
263
+ // unmounted or when it can't be measured.
264
+ startTransition(source, source.remeasure?.() ?? source.rect, rect)
157
265
  },
158
- // dx/dy/sx/sy are stable refs from useSharedValue, but eslint's
159
- // exhaustive-deps would flag them — including them is harmless and
160
- // silences the warning.
161
- [layoutId, userOnLayout, dx, dy, sx, sy],
266
+ [layoutId, userOnLayout, remeasure, snapshotStyles, startTransition],
162
267
  )
163
268
 
164
269
  // Reset the first-layout latch when the id changes — a new id is logically
@@ -167,33 +272,104 @@ export function useSharedLayout(options: {
167
272
  consumedRef.current = false
168
273
  }, [layoutId])
169
274
 
170
- // On unmount, hand the latest rect to the registry under this id so the
171
- // next mount can consume it as a FLIP source.
275
+ // On unmount, hand the latest rect and a final snapshot of the carried
276
+ // style keys to the registry under this id so the next mount can consume
277
+ // them. The styles are read by value here rather than left behind as a
278
+ // callback: after this the node is gone, and a later read would be measuring
279
+ // a component nobody owns.
172
280
  useEffect(() => {
173
281
  return () => {
174
282
  if (!layoutId) return
175
283
  const rect = lastRectRef.current
176
284
  if (!rect) return
177
- releaseLayout(layoutId, rect)
285
+ releaseLayout(layoutId, rect, snapshotStyles())
178
286
  }
179
- }, [layoutId])
287
+ }, [layoutId, snapshotStyles])
288
+
289
+ // Mark unmounted and stop any in-flight FLIP. The mounted flag is what keeps
290
+ // an asynchronous measurement callback from writing to shared values the
291
+ // component no longer owns; cancelling matches the guard the factory's own
292
+ // per-key values and the value-layer hooks got in `0.0.2` — these four were
293
+ // missed at the time.
294
+ // Stop any in-flight FLIP on unmount. Matches the guard the factory's own
295
+ // per-key values and the value-layer hooks got in `0.0.2` — these four were
296
+ // missed at the time.
297
+ useEffect(
298
+ () => () => {
299
+ cancelAnimation(dx)
300
+ cancelAnimation(dy)
301
+ cancelAnimation(sx)
302
+ cancelAnimation(sy)
303
+ // `snapshot` is assigned, never animated, so there is nothing to cancel
304
+ // on it — only the progress value drives.
305
+ cancelAnimation(carryProgress)
306
+ },
307
+ // The FLIP shared values are identity-stable per hook instance.
308
+ // eslint-disable-next-line react-hooks/exhaustive-deps
309
+ [],
310
+ )
180
311
 
181
312
  return useMemo<SharedLayoutBindings>(
182
313
  () => ({
183
314
  flip: { dx, dy, sx, sy },
315
+ carry: { snapshot, progress: carryProgress },
184
316
  setRef,
185
317
  onLayout,
186
318
  }),
187
- [dx, dy, sx, sy, setRef, onLayout],
319
+ [dx, dy, sx, sy, snapshot, carryProgress, setRef, onLayout],
188
320
  )
189
321
  }
190
322
 
323
+ /**
324
+ * Build the `withSequence(snap, animate)` pair every leg of a shared-element
325
+ * transition uses: jump to `from` instantly, then animate to `to`. The snap leg
326
+ * is what makes the animation start from the source — a plain `withSpring(to)`
327
+ * off the resting base would animate from wherever the value already sat.
328
+ *
329
+ * Returned as a closure so the spring params are converted once and reused
330
+ * across all five legs (four transforms plus the style crossfade), which also
331
+ * guarantees the rect and the style land together.
332
+ */
333
+ function legBuilder(
334
+ transition: TransitionConfig | undefined,
335
+ ): (from: number, to: number) => number {
336
+ if (transition?.type === 'timing') {
337
+ const duration = transition.duration ?? 300
338
+ return (from, to) =>
339
+ withSequence(
340
+ withTiming(from, { duration: 0 }),
341
+ withTiming(to, { duration }),
342
+ )
343
+ }
344
+ // Spring path (default, and the fallback for `'decay'` which doesn't have a
345
+ // meaningful target value for a FLIP transition).
346
+ const springCfg: SpringTransition =
347
+ transition?.type === 'spring'
348
+ ? { ...DEFAULT_SPRING, ...transition }
349
+ : { type: 'spring', ...DEFAULT_SPRING }
350
+ const springParams = springToReanimated(springCfg)
351
+ return (from, to) =>
352
+ withSequence(
353
+ withTiming(from, { duration: 0 }),
354
+ withSpring(to, springParams),
355
+ )
356
+ }
357
+
358
+ /**
359
+ * Whether a shared-element transition should play at all, or snap. Reduced
360
+ * motion and an explicit `'no-animation'` transition are the same answer for
361
+ * both the rect and the style halves.
362
+ */
363
+ function shouldSnap(
364
+ transition: TransitionConfig | undefined,
365
+ shouldReduceMotion: boolean,
366
+ ): boolean {
367
+ return shouldReduceMotion || transition?.type === 'no-animation'
368
+ }
369
+
191
370
  /**
192
371
  * Snap the FLIP shared values so the new element visually overlays its
193
- * source rect, then animate back to identity. The `withSequence(snap,
194
- * animate)` shape is what makes the spring start from the snapped delta —
195
- * a plain `withSpring(0)` from a zero base would animate from-zero, not
196
- * from-source.
372
+ * source rect, then animate back to identity.
197
373
  */
198
374
  function applyFlip(args: {
199
375
  source: SharedRect
@@ -223,9 +399,9 @@ function applyFlip(args: {
223
399
  const scaleX = target.width > 0 ? source.width / target.width : 1
224
400
  const scaleY = target.height > 0 ? source.height / target.height : 1
225
401
 
226
- if (shouldReduceMotion) {
227
- // Reduced-motion: skip the visual transition entirely. The element
228
- // appears at its natural position; the source rect is discarded.
402
+ if (shouldSnap(transition, shouldReduceMotion)) {
403
+ // Skip the visual transition entirely. The element appears at its natural
404
+ // position; the source rect is discarded.
229
405
  dx.value = 0
230
406
  dy.value = 0
231
407
  sx.value = 1
@@ -233,57 +409,46 @@ function applyFlip(args: {
233
409
  return
234
410
  }
235
411
 
236
- if (transition?.type === 'no-animation') {
237
- dx.value = 0
238
- dy.value = 0
239
- sx.value = 1
240
- sy.value = 1
241
- return
242
- }
412
+ const leg = legBuilder(transition)
413
+ dx.value = leg(deltaX, 0)
414
+ dy.value = leg(deltaY, 0)
415
+ sx.value = leg(scaleX, 1)
416
+ sy.value = leg(scaleY, 1)
417
+ }
243
418
 
244
- if (transition?.type === 'timing') {
245
- const duration = transition.duration ?? 300
246
- dx.value = withSequence(
247
- withTiming(deltaX, { duration: 0 }),
248
- withTiming(0, { duration }),
249
- )
250
- dy.value = withSequence(
251
- withTiming(deltaY, { duration: 0 }),
252
- withTiming(0, { duration }),
253
- )
254
- sx.value = withSequence(
255
- withTiming(scaleX, { duration: 0 }),
256
- withTiming(1, { duration }),
257
- )
258
- sy.value = withSequence(
259
- withTiming(scaleY, { duration: 0 }),
260
- withTiming(1, { duration }),
261
- )
419
+ /**
420
+ * Park the source element's carried style values and run the crossfade
421
+ * progress 1 → 0 on the same transition as the rect FLIP.
422
+ *
423
+ * The blend itself happens in the host's worklet, which pulls each carried key
424
+ * from `snapshot[key]` toward its own resolved value by `progress`. Driving
425
+ * only a scalar here is what keeps this cheap: one shared value moves, however
426
+ * many keys are being carried, and colors go through the same
427
+ * `interpolateColor` branch the gesture cascade already uses.
428
+ *
429
+ * A source with nothing to carry leaves both values alone — an element whose
430
+ * counterpart declared no carried keys behaves exactly as it did before style
431
+ * carry existed.
432
+ */
433
+ function applyStyleCarry(args: {
434
+ snapshot: SharedValue<SharedStyleSnapshot | null>
435
+ progress: SharedValue<number>
436
+ styles: SharedStyleSnapshot | undefined
437
+ transition: TransitionConfig | undefined
438
+ shouldReduceMotion: boolean
439
+ }): void {
440
+ const { snapshot, progress, styles, transition, shouldReduceMotion } = args
441
+ if (!styles) return
442
+
443
+ if (shouldSnap(transition, shouldReduceMotion)) {
444
+ // Matching the rect path: the element arrives wearing its own style rather
445
+ // than crossfading into it. Clearing the snapshot too means the worklet's
446
+ // branch stays untaken instead of resting on a dead payload.
447
+ snapshot.value = null
448
+ progress.value = 0
262
449
  return
263
450
  }
264
451
 
265
- // Spring path (default, and the fallback for `'decay'` which doesn't
266
- // have a meaningful target value for a FLIP transition).
267
- const springCfg: SpringTransition =
268
- transition?.type === 'spring'
269
- ? { ...DEFAULT_SPRING, ...transition }
270
- : { type: 'spring', ...DEFAULT_SPRING }
271
- const springParams = springToReanimated(springCfg)
272
-
273
- dx.value = withSequence(
274
- withTiming(deltaX, { duration: 0 }),
275
- withSpring(0, springParams),
276
- )
277
- dy.value = withSequence(
278
- withTiming(deltaY, { duration: 0 }),
279
- withSpring(0, springParams),
280
- )
281
- sx.value = withSequence(
282
- withTiming(scaleX, { duration: 0 }),
283
- withSpring(1, springParams),
284
- )
285
- sy.value = withSequence(
286
- withTiming(scaleY, { duration: 0 }),
287
- withSpring(1, springParams),
288
- )
452
+ snapshot.value = styles
453
+ progress.value = legBuilder(transition)(1, 0)
289
454
  }