@rootnative/inertia 0.0.4 → 0.0.6

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 (67) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +5 -1
  3. package/dist/{chunk-55W7BNO2.mjs → chunk-2ICQLWH2.mjs} +211 -93
  4. package/dist/{chunk-D5KAHWMC.js → chunk-4NAMD62I.js} +7 -7
  5. package/dist/{chunk-CENTULW6.js → chunk-5MGWHOSV.js} +2 -2
  6. package/dist/{chunk-MYE5NMXW.js → chunk-767UKZXG.js} +235 -116
  7. package/dist/{chunk-NAOXQJ7U.mjs → chunk-BAAQI37F.mjs} +1 -1
  8. package/dist/{chunk-RF7J5FRM.mjs → chunk-C3EDC5ZW.mjs} +1 -1
  9. package/dist/{chunk-YKGY22SB.mjs → chunk-CMHVF6F4.mjs} +1 -1
  10. package/dist/{chunk-4ASOELLA.mjs → chunk-F7LJX56B.mjs} +1 -1
  11. package/dist/chunk-FWWVHLXB.js +8 -0
  12. package/dist/chunk-K5SI6VXS.js +8 -0
  13. package/dist/{chunk-WVRCKOPO.js → chunk-OBRGJAST.js} +3 -3
  14. package/dist/{chunk-K3CLP74S.mjs → chunk-OW5XTGVN.mjs} +2 -2
  15. package/dist/{chunk-J5Q46OPW.mjs → chunk-PFPO7DX2.mjs} +1 -1
  16. package/dist/{chunk-7A6WTXNK.js → chunk-PM6CVGXJ.js} +1 -0
  17. package/dist/{chunk-TU2XFPAK.mjs → chunk-REYL77RE.mjs} +1 -0
  18. package/dist/chunk-STARIT6W.js +8 -0
  19. package/dist/{chunk-XMEVPEMH.js → chunk-X7B5WR5A.js} +2 -2
  20. package/dist/{chunk-QKC3APP4.mjs → chunk-Z3HCJ43H.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 +32 -4
  26. package/dist/index.d.ts +32 -4
  27. package/dist/index.js +52 -48
  28. package/dist/index.mjs +17 -17
  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-37pN82yh.d.mts → types-BQgLJeQG.d.mts} +39 -3
  54. package/dist/{types-37pN82yh.d.ts → types-BQgLJeQG.d.ts} +39 -3
  55. package/dist/{useGesture-Cl4uzZIr.d.ts → useGesture-B0_CzOUz.d.ts} +1 -1
  56. package/dist/{useGesture-_IKkS8Dv.d.mts → useGesture-DfaTx-3t.d.mts} +1 -1
  57. package/llms.txt +1 -0
  58. package/package.json +1 -1
  59. package/src/index.ts +4 -0
  60. package/src/internal/boxShadow.ts +71 -9
  61. package/src/internal/color.ts +70 -0
  62. package/src/motion/createMotionComponent.tsx +238 -108
  63. package/src/transitions/resolveSequence.ts +12 -0
  64. package/src/types.ts +106 -3
  65. package/dist/chunk-5SRD2WCO.js +0 -8
  66. package/dist/chunk-TVFNJ25Z.js +0 -8
  67. package/dist/chunk-VLDIFL3F.js +0 -8
@@ -23,11 +23,18 @@ import {
23
23
  } from '../config'
24
24
  import { isFocusVisible } from '../gestures'
25
25
  import {
26
+ layersToPayload,
26
27
  normalizeBoxShadow,
28
+ payloadToLayers,
27
29
  prepareBoxShadowAnimation,
28
- type AnimatedBoxShadowLayer,
30
+ type BoxShadowPayload,
29
31
  type SplitBoxShadow,
30
32
  } from '../internal/boxShadow'
33
+ import {
34
+ normalizeAnimatableColor,
35
+ normalizeAnimatableColorTarget,
36
+ TRANSPARENT,
37
+ } from '../internal/color'
31
38
  import { warnOnce } from '../internal/warnOnce'
32
39
  import {
33
40
  resolveLayoutTransition,
@@ -91,6 +98,78 @@ const NUMERIC_TOP_LEVEL_KEYS = [
91
98
  'shadowOpacity',
92
99
  'shadowRadius',
93
100
  'elevation',
101
+ // ─── Added in 0.0.5 ──────────────────────────────────────────────────────
102
+ //
103
+ // Every key below rides the generic numeric path: one shared value, resolved
104
+ // on the JS thread, emitted by the worklet through the same `out[key] = v`
105
+ // branch as `opacity`. None needs a new interpolation mode, so the cost is
106
+ // the shared value plus a `DEFAULT_RESTING` entry.
107
+ //
108
+ // The load-bearing constraint is the resting default, not the plumbing. A
109
+ // key declared only in `gesture` / `exit` / a non-active variant rests at
110
+ // `DEFAULT_RESTING` unless the static `style` names it, and the animated
111
+ // style merges *after* `style` — so a non-identity default silently stomps
112
+ // the layout. That is the `0.0.3` P0 class, and it is why the sizing and
113
+ // spacing keys below default to `0` only in the sense RN itself does (an
114
+ // unset `padding` *is* 0); `minWidth`/`maxWidth`/`aspectRatio` are
115
+ // deliberately excluded — their "unset" is not a number, so no default can
116
+ // stand in for it without changing layout on activation.
117
+ //
118
+ // Per-corner radii. Animating one corner is common (a card expanding into a
119
+ // sheet keeps its top corners and squares off the bottom); `borderRadius`
120
+ // alone can't express it.
121
+ 'borderTopLeftRadius',
122
+ 'borderTopRightRadius',
123
+ 'borderBottomLeftRadius',
124
+ 'borderBottomRightRadius',
125
+ // Border widths. `borderWidth` is the common case; the per-edge keys matter
126
+ // for underline-style focus affordances (`borderBottomWidth` on a field).
127
+ 'borderWidth',
128
+ 'borderTopWidth',
129
+ 'borderRightWidth',
130
+ 'borderBottomWidth',
131
+ 'borderLeftWidth',
132
+ // Absolute-position insets. Bread-and-butter for drawers, tooltips, and
133
+ // anything pinned to an edge that slides in.
134
+ 'top',
135
+ 'right',
136
+ 'bottom',
137
+ 'left',
138
+ // Spacing. Padding animates the box from the inside (a button growing its
139
+ // hit area on press); margin shifts it within its parent's flow.
140
+ 'padding',
141
+ 'paddingTop',
142
+ 'paddingRight',
143
+ 'paddingBottom',
144
+ 'paddingLeft',
145
+ 'paddingHorizontal',
146
+ 'paddingVertical',
147
+ 'margin',
148
+ 'marginTop',
149
+ 'marginRight',
150
+ 'marginBottom',
151
+ 'marginLeft',
152
+ 'marginHorizontal',
153
+ 'marginVertical',
154
+ // Flex sizing. `flex` / `flexGrow` interpolate the split between siblings —
155
+ // the idiomatic RN way to animate a pane resize without measuring.
156
+ 'flex',
157
+ 'flexGrow',
158
+ 'flexShrink',
159
+ // Text metrics. `TextStyle`-only at the type level (`AnimateStyle<C>` gates
160
+ // them per primitive), but the runtime table is shared across primitives —
161
+ // an unused slot is one ref, same as `tintColor` has always been.
162
+ 'fontSize',
163
+ 'letterSpacing',
164
+ 'lineHeight',
165
+ // Stacking. Integer-valued in practice, and interpolating it mid-flight is
166
+ // rarely what you want, but a `no-animation` transition on `zIndex` is the
167
+ // clean way to reorder at a step boundary in a sequence.
168
+ 'zIndex',
169
+ // Gap. Row/column gaps animate a list's density without touching children.
170
+ 'gap',
171
+ 'rowGap',
172
+ 'columnGap',
94
173
  ] as const
95
174
 
96
175
  // Color-valued keys. Reanimated's value setter detects color strings and
@@ -119,16 +198,20 @@ const COLOR_KEYS = [
119
198
  const SHADOW_OFFSET_KEYS = ['shadowOffsetWidth', 'shadowOffsetHeight'] as const
120
199
 
121
200
  // 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
201
+ // is the only one: its slot carries the layers keyed by index, which
123
202
  // Reanimated's animation drivers recurse into, animating each leaf number and
124
- // color independently (`arrayOnStart` → `objectOnStart` in Reanimated's
125
- // `animation/util.ts`).
203
+ // color independently (`objectOnStart` in Reanimated's `animation/util.ts`).
204
+ //
205
+ // Index-keyed and not an array, which is load-bearing — Reanimated recurses
206
+ // into objects but not into arrays. See `BoxShadowPayload` for the mechanism
207
+ // and for what it looked like when this was an array (`0.0.4`/`0.0.5`: dead
208
+ // under the default spring, silently correct-looking under timing).
126
209
  //
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`.
210
+ // Passing the CSS string through instead would NOT work either — a box-shadow
211
+ // string isn't a color, so it lands in Reanimated's prefix-number-suffix
212
+ // branch, which is built for values like '100%' and would pull a single number
213
+ // out of a four-value shadow. All string parsing therefore happens on the JS
214
+ // thread, once per change, in `internal/boxShadow.ts`.
132
215
  const STRUCTURED_KEYS = ['boxShadow'] as const
133
216
 
134
217
  // Keys a shared-element transition (`layoutId`) carries from the source
@@ -181,15 +264,16 @@ const SHARED_STYLE_KEY_SET = new Set<AnimatableKey>(SHARED_STYLE_KEYS)
181
264
 
182
265
  /**
183
266
  * What a per-key shared value can hold. Scalars for every key except
184
- * `boxShadow`, whose slot carries the layer array (see `STRUCTURED_KEYS`).
267
+ * `boxShadow`, whose slot carries the index-keyed layer payload (see
268
+ * `STRUCTURED_KEYS`).
185
269
  */
186
- type AnimatableSlotValue = number | string | readonly AnimatedBoxShadowLayer[]
270
+ type AnimatableSlotValue = number | string | BoxShadowPayload
187
271
 
188
272
  /**
189
273
  * Resting `boxShadow` — no shadow at all. Frozen and hoisted so every
190
274
  * primitive shares one reference and an untouched slot never allocates.
191
275
  */
192
- const NO_BOX_SHADOW: readonly AnimatedBoxShadowLayer[] = Object.freeze([])
276
+ const NO_BOX_SHADOW: BoxShadowPayload = Object.freeze({})
193
277
 
194
278
  const GESTURE_LAYER_NAMES = [
195
279
  'hovered',
@@ -221,15 +305,69 @@ const DEFAULT_RESTING: Record<AnimatableKey, AnimatableSlotValue> = {
221
305
  shadowOpacity: 0,
222
306
  shadowRadius: 0,
223
307
  elevation: 0,
224
- // 'transparent' is the only safe universal default for colors: it works as
225
- // an initial seed for any color animation (no jarring opaque flash on mount
226
- // when `initial` is omitted) and rgba(0,0,0,0) interpolates cleanly into
227
- // any opaque target via Reanimated's color util.
228
- backgroundColor: 'transparent',
229
- borderColor: 'transparent',
230
- color: 'transparent',
231
- tintColor: 'transparent',
232
- shadowColor: 'transparent',
308
+ // 0.0.5 keys. Every one of these is 0 because that is RN's own unset value
309
+ // for it an element with no `paddingTop` has 0 padding, no `borderWidth`
310
+ // has none, no `top` is unpinned at 0 within its positioning context. So a
311
+ // key that reaches its resting default lands where the element already was.
312
+ //
313
+ // The exception worth naming: `flex` / `flexGrow` / `flexShrink` do NOT have
314
+ // 0 as a universal identity (`flexShrink` defaults to 1 on the web, and RN's
315
+ // own default differs by axis). They are still 0 here, because the style-scan
316
+ // in the resting pass reads the real value off the static `style` whenever it
317
+ // is declared — and a consumer animating flex without declaring it is asking
318
+ // for the from-0 growth, which is the readable interpretation.
319
+ borderTopLeftRadius: 0,
320
+ borderTopRightRadius: 0,
321
+ borderBottomLeftRadius: 0,
322
+ borderBottomRightRadius: 0,
323
+ borderWidth: 0,
324
+ borderTopWidth: 0,
325
+ borderRightWidth: 0,
326
+ borderBottomWidth: 0,
327
+ borderLeftWidth: 0,
328
+ top: 0,
329
+ right: 0,
330
+ bottom: 0,
331
+ left: 0,
332
+ padding: 0,
333
+ paddingTop: 0,
334
+ paddingRight: 0,
335
+ paddingBottom: 0,
336
+ paddingLeft: 0,
337
+ paddingHorizontal: 0,
338
+ paddingVertical: 0,
339
+ margin: 0,
340
+ marginTop: 0,
341
+ marginRight: 0,
342
+ marginBottom: 0,
343
+ marginLeft: 0,
344
+ marginHorizontal: 0,
345
+ marginVertical: 0,
346
+ flex: 0,
347
+ flexGrow: 0,
348
+ flexShrink: 0,
349
+ fontSize: 0,
350
+ letterSpacing: 0,
351
+ lineHeight: 0,
352
+ zIndex: 0,
353
+ gap: 0,
354
+ rowGap: 0,
355
+ columnGap: 0,
356
+ // Fully transparent black is the only safe universal default for colors: it
357
+ // works as an initial seed for any color animation (no jarring opaque flash
358
+ // on mount when `initial` is omitted) and interpolates cleanly into any
359
+ // opaque target.
360
+ //
361
+ // Spelled `rgba(0, 0, 0, 0)` and NOT `'transparent'`, which is not optional.
362
+ // Reanimated's color-name table maps `transparent` to `undefined`, so it is
363
+ // the one named color `isColor()` rejects — a slot resting at the keyword
364
+ // takes the prefix-number-suffix branch on its next animation and produces
365
+ // `NaN` instead of a color. See `TRANSPARENT` in `internal/boxShadow.ts`.
366
+ backgroundColor: TRANSPARENT,
367
+ borderColor: TRANSPARENT,
368
+ color: TRANSPARENT,
369
+ tintColor: TRANSPARENT,
370
+ shadowColor: TRANSPARENT,
233
371
  shadowOffsetWidth: 0,
234
372
  shadowOffsetHeight: 0,
235
373
  boxShadow: NO_BOX_SHADOW,
@@ -610,12 +748,20 @@ export function createMotionComponent<C extends ComponentType<any>>(
610
748
  const raw = flat.boxShadow as BoxShadowInput | undefined
611
749
  if (raw !== undefined) {
612
750
  styleRestingShadow = normalizeBoxShadow(raw)
613
- styleResting.boxShadow = styleRestingShadow.layers
751
+ styleResting.boxShadow = layersToPayload(
752
+ styleRestingShadow.layers,
753
+ )
614
754
  }
615
755
  continue
616
756
  }
617
757
  const v = styleValueFor(flat, key)
618
- if (v !== undefined) styleResting[key] = v
758
+ // A static `style` may legitimately say `'transparent'`; the slot
759
+ // it seeds must not, or the next animation off that value dies.
760
+ if (v !== undefined) {
761
+ styleResting[key] = COLOR_KEY_SET.has(key)
762
+ ? normalizeAnimatableColor(v)
763
+ : v
764
+ }
619
765
  }
620
766
  }
621
767
  }
@@ -634,14 +780,13 @@ export function createMotionComponent<C extends ComponentType<any>>(
634
780
  boxShadowSeedRef.current =
635
781
  source !== undefined
636
782
  ? normalizeBoxShadow(source)
637
- : (styleRestingShadow ?? {
638
- layers: [...NO_BOX_SHADOW],
639
- insets: null,
640
- })
783
+ : (styleRestingShadow ?? { layers: [], insets: null })
641
784
  }
642
785
 
643
786
  const sharedValues = useAnimatableSharedValues((key) => {
644
- if (key === 'boxShadow') return boxShadowSeedRef.current!.layers
787
+ if (key === 'boxShadow') {
788
+ return layersToPayload(boxShadowSeedRef.current!.layers)
789
+ }
645
790
  // Shadow offset synthetics seed from the corresponding axis on the
646
791
  // `shadowOffset: { width, height }` source — the consumer doesn't write
647
792
  // `shadowOffsetWidth` / `shadowOffsetHeight` directly. Fall back to the
@@ -668,16 +813,21 @@ export function createMotionComponent<C extends ComponentType<any>>(
668
813
  DEFAULT_RESTING[key]
669
814
  )
670
815
  }
816
+ // Colors are normalized on the way in: `initial` and `animate` are
817
+ // consumer strings and may say `'transparent'`, which Reanimated
818
+ // cannot animate *away from* (see `internal/color.ts`). The two
819
+ // fallbacks below are already normalized.
671
820
  if (initial === false) {
672
821
  const a = animateRecord[key]
673
- return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key]
822
+ const seed = restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key]
823
+ return COLOR_KEY_SET.has(key) ? normalizeAnimatableColor(seed) : seed
674
824
  }
675
- return (
825
+ const seed =
676
826
  initialRecord?.[key] ??
677
827
  restValue(animateRecord[key]) ??
678
828
  styleResting[key] ??
679
829
  DEFAULT_RESTING[key]
680
- )
830
+ return COLOR_KEY_SET.has(key) ? normalizeAnimatableColor(seed) : seed
681
831
  })
682
832
 
683
833
  // Keep never-driven keys tracking the *live* static style. The seed above
@@ -894,8 +1044,17 @@ export function createMotionComponent<C extends ComponentType<any>>(
894
1044
  isExiting ? onSettle : undefined,
895
1045
  TRANSFORM_KEY_SET.has(key) ? transformGroup : undefined,
896
1046
  )
1047
+ // Normalized for the driver only, and after `targetEndValue` above:
1048
+ // `onAnimationEnd` reports the target the consumer declared, not our
1049
+ // rewriting of it. A color target of `'transparent'` animates fine
1050
+ // — Reanimated dispatches on the *source* — but under
1051
+ // `'no-animation'` (and so under reduced motion) it is assigned
1052
+ // straight into the slot, and the next animation would start from a
1053
+ // value that can't be parsed.
897
1054
  sharedValues[key].value = resolveAnimatableValue(
898
- target,
1055
+ COLOR_KEY_SET.has(key)
1056
+ ? normalizeAnimatableColorTarget(target)
1057
+ : target,
899
1058
  cfg,
900
1059
  factory,
901
1060
  ) as never
@@ -1123,17 +1282,19 @@ export function createMotionComponent<C extends ComponentType<any>>(
1123
1282
  out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH }
1124
1283
  }
1125
1284
  if (hasBoxShadow) {
1126
- const layers = sharedValues.boxShadow
1127
- .value as readonly AnimatedBoxShadowLayer[]
1285
+ // The slot holds the layers keyed by index (Reanimated recurses into
1286
+ // objects, not arrays — see `BoxShadowPayload`); RN wants an array,
1287
+ // so this is where the two shapes meet.
1288
+ const payload = sharedValues.boxShadow.value as BoxShadowPayload
1128
1289
  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.
1290
+ // Fast path — no inset layer anywhere, so each layer crosses over by
1291
+ // reference and only the array itself is allocated.
1131
1292
  if (insets === null) {
1132
- out.boxShadow = layers
1293
+ out.boxShadow = payloadToLayers(payload)
1133
1294
  } else {
1134
1295
  const withInset = []
1135
- for (let i = 0; i < layers.length; i++) {
1136
- withInset.push({ ...layers[i], inset: insets[i] })
1296
+ for (let i = 0; payload[i] !== undefined; i++) {
1297
+ withInset.push({ ...payload[i], inset: insets[i] })
1137
1298
  }
1138
1299
  out.boxShadow = withInset
1139
1300
  }
@@ -1245,74 +1406,38 @@ type SharedValueMap = Record<AnimatableKey, SharedValue<AnimatableSlotValue>>
1245
1406
  * are stable across renders (Reanimated's `useSharedValue` is a `useRef`
1246
1407
  * under the hood), so snapshotting the wrapping object once is safe.
1247
1408
  *
1248
- * Hooks are called in a stable, lexical order fine for rules-of-hooks.
1249
- * Unused shared values are cheap; the worklet skips them via
1250
- * `activeKeysRef`. Color keys are seeded with the initial color string so
1251
- * Reanimated's value setter recognizes the slot as a color from the first
1252
- * `withSpring` / `withTiming` call.
1409
+ * Hooks are called by iterating `ALL_KEYS`, which is a module-level `as const`
1410
+ * array its length and order are fixed at module evaluation, so the hook
1411
+ * sequence is identical on every render of every instance. That satisfies
1412
+ * rules-of-hooks exactly as a hand-written list does, and it is what keeps this
1413
+ * function from growing a line per key: the 0.0.5 additions took the table from
1414
+ * 23 keys to 63, and 63 near-identical `useSharedValue` lines is both a bigger
1415
+ * bundle and a standing invitation to add a key in one list but not the other
1416
+ * (the failure mode is a `sharedValues[key]` of `undefined` inside the worklet,
1417
+ * which is exactly how the first draft of that change broke).
1418
+ *
1419
+ * The ESLint exhaustive-deps rule can't see through the loop, hence the
1420
+ * disable — the invariant it would check for us is asserted above instead.
1421
+ *
1422
+ * Unused shared values are cheap; the worklet skips them via `activeKeysRef`.
1423
+ * Color keys are seeded with the initial color string so Reanimated's value
1424
+ * setter recognizes the slot as a color from the first `withSpring` /
1425
+ * `withTiming` call.
1253
1426
  */
1254
1427
  function useAnimatableSharedValues(
1255
1428
  init: (key: AnimatableKey) => AnimatableSlotValue,
1256
1429
  ): SharedValueMap {
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>(
1275
- init('backgroundColor'),
1276
- )
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>(
1282
- init('shadowOffsetWidth'),
1283
- )
1284
- const shadowOffsetHeight = useSharedValue<AnimatableSlotValue>(
1285
- init('shadowOffsetHeight'),
1286
- )
1287
- const boxShadow = useSharedValue<AnimatableSlotValue>(init('boxShadow'))
1430
+ const entries: [AnimatableKey, SharedValue<AnimatableSlotValue>][] = []
1431
+ for (const key of ALL_KEYS) {
1432
+ // eslint-disable-next-line react-hooks/rules-of-hooks
1433
+ entries.push([key, useSharedValue<AnimatableSlotValue>(init(key))])
1434
+ }
1288
1435
 
1289
1436
  const ref = useRef<SharedValueMap | null>(null)
1290
1437
  if (ref.current === null) {
1291
- ref.current = {
1292
- translateX,
1293
- translateY,
1294
- scale,
1295
- scaleX,
1296
- scaleY,
1297
- rotate,
1298
- rotateX,
1299
- rotateY,
1300
- opacity,
1301
- width,
1302
- height,
1303
- borderRadius,
1304
- shadowOpacity,
1305
- shadowRadius,
1306
- elevation,
1307
- backgroundColor,
1308
- borderColor,
1309
- color,
1310
- tintColor,
1311
- shadowColor,
1312
- shadowOffsetWidth,
1313
- shadowOffsetHeight,
1314
- boxShadow,
1315
- }
1438
+ const map = {} as SharedValueMap
1439
+ for (const [key, sv] of entries) map[key] = sv
1440
+ ref.current = map
1316
1441
  }
1317
1442
 
1318
1443
  // Cancel every in-flight per-key animation when the primitive unmounts, so
@@ -1402,6 +1527,13 @@ function makeKeyCallbackFactory(
1402
1527
  const reportedIteration = state.iteration
1403
1528
  if (phase === 'sequence' || phase === 'repeat') state.iteration++
1404
1529
 
1530
+ // `boxShadow`'s slot is keyed by index so Reanimated will recurse into it;
1531
+ // that is an internal shape, and the consumer gets the layer list.
1532
+ const reportedValue =
1533
+ key === 'boxShadow' && value !== undefined
1534
+ ? payloadToLayers(value as BoxShadowPayload)
1535
+ : value
1536
+
1405
1537
  const fn = onAnimationEndRef.current
1406
1538
  if (fn) {
1407
1539
  // Transform-group coalescing: a multi-axis translate / scale /
@@ -1417,7 +1549,7 @@ function makeKeyCallbackFactory(
1417
1549
  fn({
1418
1550
  key: 'transform' as never,
1419
1551
  finished,
1420
- value,
1552
+ value: reportedValue,
1421
1553
  target,
1422
1554
  phase,
1423
1555
  step,
@@ -1428,7 +1560,7 @@ function makeKeyCallbackFactory(
1428
1560
  fn({
1429
1561
  key: key as never,
1430
1562
  finished,
1431
- value,
1563
+ value: reportedValue,
1432
1564
  target,
1433
1565
  phase,
1434
1566
  step,
@@ -1525,20 +1657,18 @@ function driveBoxShadow(
1525
1657
  cfg: TransitionConfig | undefined,
1526
1658
  factory: CallbackFactory | undefined,
1527
1659
  ): void {
1528
- const currentLayers = Array.isArray(slot.value)
1529
- ? (slot.value as readonly AnimatedBoxShadowLayer[])
1530
- : NO_BOX_SHADOW
1660
+ const currentLayers = payloadToLayers(slot.value as BoxShadowPayload)
1531
1661
  const { from, to, insets } = prepareBoxShadowAnimation(
1532
- { layers: [...currentLayers], insets: insetSlot.value },
1662
+ { layers: currentLayers, insets: insetSlot.value },
1533
1663
  target,
1534
1664
  )
1535
1665
  insetSlot.value = insets
1536
- if (from.length !== currentLayers.length) slot.value = from
1666
+ if (from.length !== currentLayers.length) slot.value = layersToPayload(from)
1537
1667
  // `resolveTransition` is typed for the scalar surface it was written for;
1538
1668
  // Reanimated itself accepts the structured target and recurses into it.
1539
1669
  slot.value = resolveTransition(
1540
1670
  cfg,
1541
- to as unknown as number,
1671
+ layersToPayload(to) as unknown as number,
1542
1672
  factory?.('animation', undefined),
1543
1673
  ) as AnimatableSlotValue
1544
1674
  }
@@ -77,6 +77,18 @@ function mergeTransition(
77
77
  base: TransitionConfig | undefined,
78
78
  override: Partial<TransitionConfig>,
79
79
  ): TransitionConfig {
80
+ // A `no-animation` base is a ceiling, not a default, so no step override can
81
+ // talk its way past it. This is the reduced-motion gate: `configFor` in the
82
+ // factory swaps every per-key transition for `{ type: 'no-animation' }`, and
83
+ // the step-type rule below would otherwise hand a step its own `withTiming`
84
+ // back — silently animating for a user who asked the OS not to. Keep this
85
+ // branch first; it is the whole reason the gate holds for sequences.
86
+ //
87
+ // Returning `base` also drops any step `delay`, which matches how the rest
88
+ // of the resolver already treats this type: `delayOf` and `repeatOf` both
89
+ // return `undefined` for `no-animation`, so a snap is never deferred or
90
+ // repeated anywhere in the library.
91
+ if (base?.type === 'no-animation') return base
80
92
  // If the override declares a `type`, it wins outright — mixing fields from
81
93
  // a spring base into a timing override produces garbage. Otherwise inherit
82
94
  // the base's type and shallow-merge the rest.
package/src/types.ts CHANGED
@@ -217,6 +217,22 @@ type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S
217
217
  : // eslint-disable-next-line @typescript-eslint/no-empty-object-type
218
218
  {}
219
219
 
220
+ /**
221
+ * The `transform: [...]` array form, passed through as the underlying style
222
+ * types it. Inertia reads transform entries out of the array (see
223
+ * `styleValueFor` in `createMotionComponent`) rather than treating the array as
224
+ * an animatable scalar, so it is exempted from the `AnimatableValue` mapping —
225
+ * wrapping it there would offer a nonsensical `transform: [[...], [...]]`
226
+ * keyframe form.
227
+ *
228
+ * Conditional on `S` for the same reason as `boxShadow`: only appears where the
229
+ * underlying style actually has it.
230
+ */
231
+ type AnimatableTransformArray<S> = 'transform' extends keyof S
232
+ ? { transform?: S['transform'] }
233
+ : // eslint-disable-next-line @typescript-eslint/no-empty-object-type
234
+ {}
235
+
220
236
  /**
221
237
  * The animation state shape inferred from the underlying component's style
222
238
  * prop. We narrow to the value side of `style` so consumers see ViewStyle on
@@ -231,11 +247,98 @@ type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S
231
247
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
232
248
  type _StyleValue<T> = Exclude<T, (...args: any[]) => any>
233
249
 
250
+ /**
251
+ * The style keys Inertia's runtime actually drives — the type-level mirror of
252
+ * `ALL_KEYS` in `createMotionComponent`, minus the `shadowOffset*` synthetics
253
+ * (consumers write the nested `shadowOffset` object, not the axes) and minus
254
+ * `boxShadow` (handled by `AnimatableStructuredExtras`, whose value shape
255
+ * deliberately departs from `AnimatableValue`).
256
+ *
257
+ * **This list and `ALL_KEYS` must stay in sync.** They are separate because one
258
+ * is a type and the other is a runtime array, and TypeScript can't derive the
259
+ * former from a value living in a module that imports this one. The direction
260
+ * that matters: a key here but not in `ALL_KEYS` typechecks and then silently
261
+ * does nothing at runtime — which is precisely the defect this type was
262
+ * introduced to kill. `animate-keys.test-d.tsx` pins the two together.
263
+ *
264
+ * Before 0.0.5 this type mapped over the component's *entire* style surface, so
265
+ * `animate={{ paddingTop: 40 }}` compiled cleanly and was then dropped on the
266
+ * floor — no error, no warning. That contradicted the library's own headline
267
+ * differentiator (wrong props should error, not go unnoticed), so the mapping is
268
+ * now an intersection: a key must be both animatable by Inertia *and* present on
269
+ * the underlying component's style prop.
270
+ */
271
+ type AnimatableStyleKey =
272
+ // Transform axes surfaced as top-level keys.
273
+ | 'translateX'
274
+ | 'translateY'
275
+ | 'scaleX'
276
+ | 'scaleY'
277
+ // Numeric top-level keys.
278
+ | 'opacity'
279
+ | 'width'
280
+ | 'height'
281
+ | 'borderRadius'
282
+ | 'shadowOpacity'
283
+ | 'shadowRadius'
284
+ | 'elevation'
285
+ | 'borderTopLeftRadius'
286
+ | 'borderTopRightRadius'
287
+ | 'borderBottomLeftRadius'
288
+ | 'borderBottomRightRadius'
289
+ | 'borderWidth'
290
+ | 'borderTopWidth'
291
+ | 'borderRightWidth'
292
+ | 'borderBottomWidth'
293
+ | 'borderLeftWidth'
294
+ | 'top'
295
+ | 'right'
296
+ | 'bottom'
297
+ | 'left'
298
+ | 'padding'
299
+ | 'paddingTop'
300
+ | 'paddingRight'
301
+ | 'paddingBottom'
302
+ | 'paddingLeft'
303
+ | 'paddingHorizontal'
304
+ | 'paddingVertical'
305
+ | 'margin'
306
+ | 'marginTop'
307
+ | 'marginRight'
308
+ | 'marginBottom'
309
+ | 'marginLeft'
310
+ | 'marginHorizontal'
311
+ | 'marginVertical'
312
+ | 'flex'
313
+ | 'flexGrow'
314
+ | 'flexShrink'
315
+ | 'fontSize'
316
+ | 'letterSpacing'
317
+ | 'lineHeight'
318
+ | 'zIndex'
319
+ | 'gap'
320
+ | 'rowGap'
321
+ | 'columnGap'
322
+ // Color keys.
323
+ | 'backgroundColor'
324
+ | 'borderColor'
325
+ | 'color'
326
+ | 'tintColor'
327
+ | 'shadowColor'
328
+ // The one nested-object style prop, written as `{ width, height }`.
329
+ | 'shadowOffset'
330
+
234
331
  export type AnimateStyle<C> = C extends { style?: infer Raw }
235
332
  ? _StyleValue<Raw> extends StyleProp<infer S>
236
- ? Omit<{ [K in keyof S]?: AnimatableValue<S[K]> }, 'boxShadow'> &
237
- AnimatableTransformExtras &
238
- AnimatableStructuredExtras<S>
333
+ ? {
334
+ [K in keyof S as K extends AnimatableStyleKey ? K : never]?: K extends
335
+ | 'boxShadow'
336
+ | 'transform'
337
+ ? never
338
+ : AnimatableValue<S[K]>
339
+ } & AnimatableTransformExtras &
340
+ AnimatableStructuredExtras<S> &
341
+ AnimatableTransformArray<S>
239
342
  : never
240
343
  : never
241
344
 
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkMYE5NMXW_js = require('./chunk-MYE5NMXW.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionImage = chunkMYE5NMXW_js.createMotionComponent(reactNative.Image);
7
-
8
- exports.MotionImage = MotionImage;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkMYE5NMXW_js = require('./chunk-MYE5NMXW.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionText = chunkMYE5NMXW_js.createMotionComponent(reactNative.Text);
7
-
8
- exports.MotionText = MotionText;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkMYE5NMXW_js = require('./chunk-MYE5NMXW.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionView = chunkMYE5NMXW_js.createMotionComponent(reactNative.View);
7
-
8
- exports.MotionView = MotionView;