@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
package/CHANGELOG.md CHANGED
@@ -4,6 +4,90 @@ All notable changes to `@rootnative/inertia` are documented here. The format fol
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.0.6] - 2026-08-08
8
+
9
+ **Defect release: two animations that never ran.** Both trace to one assumption about what Reanimated accepts as an animatable value, and both were hidden by the same thing — `type: 'timing'` snaps to its target when the duration elapses, so a total interpolation failure still produced the right end state. `animate={{ boxShadow }}` was inert under the default spring; every colour key was inert when it rested at its default, which is the far more common path. Neither is caught by a mocked test suite, so this release also adds a test file that runs Reanimated's real drivers.
10
+
11
+ ### Fixed
12
+
13
+ - **`animate={{ boxShadow }}` now animates under `type: 'spring'`** — the library default, and the documented recommendation, so `animate={{ boxShadow }}` with no `transition` prop at all was the broken path. The key worked under `type: 'timing'` and was inert under spring, from its introduction in `0.0.4` until now.
14
+
15
+ The cause was the shape of the value handed to Reanimated. Its animation drivers walk a structured value by dispatching on each leaf's runtime shape, but the two container branches are **not symmetrical** (`animation/util.ts`): `objectOnStart` re-assigns the decorated `onStart` onto each child, so a child that is itself an object or a colour is dispatched again, recursively — while `arrayOnStart` does not, handing every element straight to the scalar spring/timing maths. `boxShadow`'s payload was an array of layer objects, so each layer was evaluated as `object - object` → `NaN`.
16
+
17
+ Why only spring broke: `withTiming` snaps to its target once the duration elapses, whatever the interpolation produced along the way, so the shadow still arrived and the fault stayed hidden. `withSpring` decides it has settled via `isAnimationTerminatingCalculation`, and every comparison against `NaN` is false — so the animation never finished, the shared value held `'[object Object]NaN'` indefinitely, `onAnimationEnd` never fired, and the frame loop never stopped.
18
+
19
+ The payload is now keyed by index (`{ 0: layer, 1: layer }`), which routes it down the object branch where the recursion works and each layer's colour reaches the RGBA-channel path. The change is internal: `animate={{ boxShadow }}` accepts the same CSS string and `BoxShadowValue[]` forms, endpoint padding is unchanged, and `onAnimationEnd`'s `value` is still reported as a layer list.
20
+
21
+ - **Colour keys can now animate away from their resting default.** `'transparent'` is the one CSS colour name Reanimated's `isColor()` rejects — its colour table maps the keyword to `undefined` while every other name maps to a packed integer. A value that fails that gate but is still a string falls to the prefix-number-suffix branch built for values like `'100%'`, producing `'transparentNaN'` and, under spring, never settling.
22
+
23
+ `'transparent'` was Inertia's resting default for `backgroundColor`, `borderColor`, `color`, `tintColor`, and `shadowColor`, so `<Motion.View animate={{ backgroundColor: '#4f46e5' }} />` on an element with no static colour and no `initial` never animated. All five defaults are now `'rgba(0, 0, 0, 0)'` — the identical colour, recognised by the gate — and the keyword is rewritten to it wherever it would enter a colour slot: `initial`, `animate` (including keyframe sequences and `{ to }` steps), and the static `style` a never-driven key rests at.
24
+
25
+ Only values handed to `withSpring` / `withTiming` are affected. `interpolateColor` parses the keyword correctly, so the `gesture` cascade, the `layoutId` style carry, and `useShadow` were never affected and keep the consumer's own spelling.
26
+
27
+ **Visible difference:** a colour key resting at its default now renders `'rgba(0, 0, 0, 0)'` where it used to render `'transparent'`. Same colour, but a snapshot test asserting the literal string will need updating.
28
+
29
+ - **`boxShadow`'s transparent padding layer** — added when the two endpoints have different layer counts — used the same keyword, so a single padding layer was enough to hang a whole shadow animation. It is now `'rgba(0, 0, 0, 0)'`.
30
+
31
+ ### Added
32
+
33
+ - **`TRANSPARENT`** — the colour a custom animated component should seed a colour shared value with when it has no other source. Exported alongside `resolveTransition` and the other building blocks for custom components, because the obvious choice (`'transparent'`) is the one spelling Reanimated cannot animate away from, and nothing about the failure points at the cause.
34
+
35
+ **Bundle cost: +0.13 kB (+1.5%) per primitive subpath**, 8.92 → 9.05 kB brotlied, +0.18 kB on the root entry (12.10 → 12.28 kB). No `size-limit` cap moved. The payload reshape is close to free — one index-keying pass on the JS thread and one loop in the worklet, replacing an array that crossed over by reference — and most of the delta is the colour normalization, which has to run on four separate paths into a slot.
36
+
37
+ ### Internal
38
+
39
+ - **`reanimated-drivers.test.ts` runs Reanimated's real animation drivers**, imported by deep path, instead of the static mock the rest of the suite uses. Both defects above were invisible to that mock — `withSpring` and `withTiming` are the identity function there, so the payload Inertia produced looked correct in every assertion while being unanimatable in fact. The new file drives Reanimated's own `onStart` / `onFrame` protocol (the one its `valueSetter` runs) and asserts that leaves converge, that a spring settles, and that mid-flight frames carry numbers. It also pins the two upstream behaviours the fixes work around, so a Reanimated upgrade that changes either fails loudly rather than silently leaving dead workarounds behind.
40
+
41
+ ## [0.0.5] - 2026-07-31
42
+
43
+ **Correctness release for the `animate` type surface.** The 40 layout and text-metric style keys that typechecked but were silently dropped at runtime now either animate or fail to compile. No API removals; the type narrowing is technically breaking for code that passed a never-driven key, but that code was already a no-op.
44
+
45
+ ### Added
46
+
47
+ - **Layout numerics are animatable.** Forty keys join the `animate` surface, all riding the existing generic numeric path — one shared value each, resolved on the JS thread, emitted through the same branch as `opacity`:
48
+
49
+ | Group | Keys |
50
+ | ----------------- | --------------------------------------------------------------------------------------------------------------- |
51
+ | Per-corner radius | `borderTopLeftRadius`, `borderTopRightRadius`, `borderBottomLeftRadius`, `borderBottomRightRadius` |
52
+ | Border width | `borderWidth`, `borderTopWidth`, `borderRightWidth`, `borderBottomWidth`, `borderLeftWidth` |
53
+ | Absolute inset | `top`, `right`, `bottom`, `left` |
54
+ | Padding | `padding`, `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, `paddingHorizontal`, `paddingVertical` |
55
+ | Margin | `margin`, `marginTop`, `marginRight`, `marginBottom`, `marginLeft`, `marginHorizontal`, `marginVertical` |
56
+ | Flex sizing | `flex`, `flexGrow`, `flexShrink` |
57
+ | Gap | `gap`, `rowGap`, `columnGap` |
58
+ | Stacking | `zIndex` |
59
+ | Text metrics | `fontSize`, `letterSpacing`, `lineHeight` — `Motion.Text` only, since they live on `TextStyle` |
60
+
61
+ Every one of these previously **typechecked and then did nothing**: `AnimateStyle<C>` mapped over the component's entire inferred style, so `animate={{ paddingTop: 40 }}` compiled cleanly, allocated nothing, and never appeared in the rendered style. The `Motion.Text` case was the sharpest — the docs told consumers to hand-roll `useAnimatedStyle` for `fontSize` while the type-test asserted `fontSize` was accepted, so the two halves of the contract actively disagreed. Sequences, `{ to }` step objects, per-property transitions, `gesture` sub-states, `exit`, variants, and the `layoutId` resting logic all work on the new keys with no special-casing.
62
+
63
+ **These drive real layout, so they reflow every frame.** Prefer `scale` / `translate*` for pure motion — those composite without touching layout. Reach for these when the layout genuinely changes: a drawer sliding via `left`, a card squaring off one corner as it expands, a pane resize via `flex`. The Fabric jitter caveat that has always applied to `width` / `height` on non-`flex: 1` containers applies here too.
64
+
65
+ **Resting values behave as they have since `0.0.3`.** A key declared only in a `gesture` sub-state, an `exit` target, or a non-active variant joins the active set, so the worklet emits it every frame including at rest — and since the animated style merges _after_ `style`, a non-identity resting default would silently stomp the consumer's layout. The existing style-scan handles this: such a key rests at whatever `StyleSheet.flatten(style)` says, falling back to the type default only when the style is silent. All 40 defaults are `0`, which is RN's own unset value for each, so a key that does fall back lands where the element already was. Guarded by four dedicated cases in `animatable-keys.test.tsx` alongside the existing `style-resting.test.tsx`.
66
+
67
+ **Bundle cost: +0.11 kB (+1.3%) per primitive subpath**, 8.80 → 8.91 kB brotlied, +0.08 kB on the root entry. No `size-limit` cap moved. Nearly free because the factory's per-key machinery was already generic: the only structural change was replacing 23 hand-written `useSharedValue` calls in `useAnimatableSharedValues` with a loop over `ALL_KEYS`, which offset almost the entire cost of the 40 new table entries. Hook order stays stable because `ALL_KEYS` is a module-level `as const` whose length is fixed at module evaluation.
68
+
69
+ ### Changed
70
+
71
+ - **`animate` now rejects style keys Inertia doesn't drive, at compile time.** `AnimateStyle<C>` is narrowed to an intersection of the component's style keys and the set the runtime actually animates, so `animate={{ alignItems: 'center' }}` is a type error instead of a silent no-op. This restores the library's headline differentiator — the first row of the sharp-edges table promises that wrong props error rather than going unnoticed — which the previous whole-style mapping had quietly broken for every non-animatable key.
72
+
73
+ **Technically breaking, practically not:** code that passed one of these keys stops compiling, but it was already doing nothing at runtime, so nothing that worked before stops working. Fixing a break means either deleting the key or moving it to the static `style` prop, which is where it belonged.
74
+
75
+ Two categories are rejected on purpose. **Enum-valued keys** (`position`, `overflow`, `alignItems`, `flexDirection`, `display`) have no interpolable midpoint. **`minWidth` / `maxWidth` / `minHeight` / `maxHeight` / `aspectRatio`** are excluded for a subtler reason worth recording: their unset state isn't a number, so there is no value they could rest at when declared only in a `gesture` sub-state or a non-active variant — any numeric default would change the layout the instant the key activated, which is exactly the `0.0.3` resting-value regression. Animate `width` / `height` instead, or drop to the value-layer hooks. Per-primitive gating is unchanged and still enforced: `tintColor` stays Image-only, and the text metrics are rejected on `Motion.View` and `Motion.Image`.
76
+
77
+ `AnimatableStyleKey` (the new type) and `ALL_KEYS` (the runtime array) are necessarily separate declarations — one is a type, the other a value in a module that imports it — so the pairing is pinned by tests on both sides: `__type-tests__/animate-keys.test-d.tsx` (23 compile-time assertions) proves the type accepts and rejects the right keys, and `__tests__/animatable-keys.test.tsx` (17 cases) proves every accepted key reaches the rendered style. A key added to one but not the other fails one of the two.
78
+
79
+ ### Fixed
80
+
81
+ - **Reduced motion is no longer bypassed by a sequence step that declares its own `type`.** `<MotionConfig reducedMotion="always">` (and the `'user'` default under an OS reduce-motion setting) swaps every per-key transition for `{ type: 'no-animation' }`, but `mergeTransition` in `resolveSequence.ts` let a step-declared `type` win outright over the base whenever the two differed — so `animate={{ translateX: [0, { to: 100, type: 'timing', duration: 200 }, 0] }}` still called `withTiming` for a user who had asked the OS for no motion. Both sequence arrays and the single `{ to, ...override }` form were affected, for every step type.
82
+
83
+ A `no-animation` base is now treated as a **ceiling rather than a default**: it short-circuits the merge before the step-type rule runs. That rule is unchanged and still correct for ordinary bases — mixing a spring base's `tension` into a timing override produces garbage, which is why it exists.
84
+
85
+ This contradicted the contract stated in `createMotionComponent.tsx`, which had promised since the gate was written that reduced motion "overrides every per-key transition (and any nested sequence-step transition)". The code was the half that was wrong.
86
+
87
+ **Why it went unnoticed:** `reduced-motion.test.tsx` asserted only on per-key transitions, where the base reaches Reanimated unmodified — sequence steps were the one path that could talk its way past the gate, and nothing covered them. Three cases now do, including a `reducedMotion="never"` positive control so a future regression can't pass by asserting nothing. Dropping a step `delay` alongside the type matches how the rest of the resolver already treats this transition type (`delayOf` and `repeatOf` both return `undefined` for `no-animation`), so a snap is never deferred or repeated anywhere in the library.
88
+
89
+ Surfaced by the `@rootnative/ui` validation consumer, whose `LinearProgress` / `CircularProgress` both use an inline-typed snap-back step inside an infinite keyframe loop. Their local workaround — not mounting the `Motion.View` at all under reduced motion — remains the better render and is unaffected. No bundle-size change (one early-return branch; primitives stay at 8.9 kB against the 10.3 kB cap).
90
+
7
91
  ## [0.0.4] - 2026-07-29
8
92
 
9
93
  **Feature release.** The three deferred items the `0.0.3` audit left on the roadmap land together: `boxShadow` joins the declarative `animate` surface, and `layoutId` shared-element transitions gain window-coordinate measurement and a style carry. No breaking changes; the three adapters are lockstep-bumped with no runtime changes of their own.
@@ -141,7 +225,9 @@ Initial alpha publish. The full initial surface is in place; APIs are still subj
141
225
  - SVG path morphing, gradient interpolation, and shared-element transitions across screens are out of scope until `0.2.x` / `1.x` per the roadmap.
142
226
  - `react-native-gesture-handler` integration (drag, pan, swipe sub-states) lands in `0.2` via the optional `@rootnative/inertia-gestures` adapter.
143
227
 
144
- [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.4...HEAD
228
+ [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.6...HEAD
229
+ [0.0.6]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.6
230
+ [0.0.5]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.5
145
231
  [0.0.4]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.4
146
232
  [0.0.3]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.3
147
233
  [0.0.2]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.2
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Declarative animation primitives for React Native, built as a thin wrapper around [`react-native-reanimated`](https://docs.swmansion.com/react-native-reanimated/). Inspired by Framer Motion (web) and react-spring (cross-platform).
8
8
 
9
- > **Status:** `0.0.4` — stable. Pre-`1.0.0` minor versions may break — see the root [README](https://github.com/rootnative/inertia#versioning--release).
9
+ > **Status:** `0.0.6` — stable. Pre-`1.0.0` minor versions may break — see the root [README](https://github.com/rootnative/inertia#versioning--release).
10
10
 
11
11
  ## Install
12
12
 
@@ -88,6 +88,10 @@ Plus, on any transition: `delay`, `repeat`. Per-property transitions take preced
88
88
 
89
89
  Numeric: `opacity`, `translateX`, `translateY`, `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY`, `width`, `height`, `borderRadius`, `shadowOpacity`, `shadowRadius`, `elevation`. Color: `backgroundColor`, `borderColor`, `color`, `shadowColor`, `tintColor` (Image only — `Motion.View` rejects it at compile time). Layout transforms via `transform: [...]`. Color targets are forwarded straight through `withSpring` / `withTiming`; Reanimated's value setter packs the string to RGBA and interpolates on the UI thread.
90
90
 
91
+ Layout numerics (since `0.0.5`): per-corner radii (`borderTopLeftRadius` …), border widths (`borderWidth`, `borderTopWidth` …), absolute insets (`top` / `right` / `bottom` / `left`), padding and margin including the `*Horizontal` / `*Vertical` shorthands, `flex` / `flexGrow` / `flexShrink`, `gap` / `rowGap` / `columnGap`, `zIndex`, and the `Motion.Text`-only metrics `fontSize` / `letterSpacing` / `lineHeight`. These drive real layout, so they reflow each frame — prefer `scale` / `translate*` for pure motion.
92
+
93
+ `animate` is narrowed to the keys Inertia actually drives, so an unsupported style key (`alignItems`, `position`, `minWidth`, …) is a compile error instead of a silent no-op.
94
+
91
95
  Nested object: `shadowOffset: { width, height }` — single-value form only (no sequences, no array keyframes). Structured: `boxShadow` — the cross-platform CSS shadow, as a string (`'0px 4px 8px rgba(0,0,0,0.3)'`) or RN's `BoxShadowValue[]`. Endpoints are padded to a common layer count, so layer counts may differ between `initial` and `animate`; sequences and `gesture` sub-states are not supported on this key.
92
96
 
93
97
  SVG path morphing ships in the [`@rootnative/inertia-svg`](../svg) adapter (`MotionPath`). Shared-element transitions across screens are wired through the `layoutId` prop — pair the same id on a source and target `Motion.*` and Inertia FLIPs between them on mount, carrying `opacity`, `borderRadius`, and the color keys across alongside the rect.
@@ -1,10 +1,34 @@
1
- import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition, useShouldReduceMotion, isFocusVisible } from './chunk-QKC3APP4.mjs';
2
- import { warnOnce, stableSig, resolveAnimatableValue, isTopLevelTransition, resolveTransition, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-TU2XFPAK.mjs';
1
+ import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition, useShouldReduceMotion, isFocusVisible } from './chunk-Z3HCJ43H.mjs';
2
+ import { warnOnce, stableSig, resolveAnimatableValue, isTopLevelTransition, resolveTransition, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-REYL77RE.mjs';
3
3
  import { createContext, useContext, useMemo, Children, isValidElement, useState, useRef, useCallback, forwardRef, useEffect } from 'react';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
  import Animated, { useSharedValue, cancelAnimation, useAnimatedStyle, interpolateColor, reanimatedVersion, runOnJS, LinearTransition, withSequence, withTiming, withSpring } from 'react-native-reanimated';
6
6
  import { StyleSheet } from 'react-native';
7
7
 
8
+ // src/internal/color.ts
9
+ var TRANSPARENT = "rgba(0, 0, 0, 0)";
10
+ function normalizeAnimatableColor(value) {
11
+ return value === "transparent" ? TRANSPARENT : value;
12
+ }
13
+ function normalizeAnimatableColorTarget(target) {
14
+ if (typeof target === "string") {
15
+ return normalizeAnimatableColor(target);
16
+ }
17
+ if (!Array.isArray(target)) return target;
18
+ let changed = false;
19
+ const next = target.map((step) => {
20
+ if (step === "transparent") {
21
+ changed = true;
22
+ return TRANSPARENT;
23
+ }
24
+ if (step !== null && typeof step === "object" && step.to === "transparent") {
25
+ changed = true;
26
+ return { ...step, to: TRANSPARENT };
27
+ }
28
+ return step;
29
+ });
30
+ return changed ? next : target;
31
+ }
8
32
  var PresenceContext = createContext(null);
9
33
  function usePresence() {
10
34
  return useContext(PresenceContext);
@@ -244,7 +268,7 @@ function invisibleLayer(inset) {
244
268
  offsetY: 0,
245
269
  blurRadius: 0,
246
270
  spreadDistance: 0,
247
- color: "transparent",
271
+ color: TRANSPARENT,
248
272
  inset
249
273
  };
250
274
  }
@@ -260,8 +284,8 @@ function coerceLength(value, field) {
260
284
  return parseFloat(trimmed);
261
285
  }
262
286
  function strip(layer) {
263
- const { inset: _inset, ...rest } = layer;
264
- return rest;
287
+ const { inset: _inset, color, ...rest } = layer;
288
+ return { ...rest, color: normalizeAnimatableColor(color) };
265
289
  }
266
290
  function markInset(insets, i, count) {
267
291
  const list = insets ?? new Array(count).fill(false);
@@ -298,6 +322,17 @@ function prepareBoxShadowAnimation(current, target) {
298
322
  }
299
323
  return { from, to, insets };
300
324
  }
325
+ function layersToPayload(layers) {
326
+ const payload = {};
327
+ for (let i = 0; i < layers.length; i++) payload[i] = layers[i];
328
+ return payload;
329
+ }
330
+ function payloadToLayers(payload) {
331
+ "worklet";
332
+ const layers = [];
333
+ for (let i = 0; payload[i] !== void 0; i++) layers.push(payload[i]);
334
+ return layers;
335
+ }
301
336
  function resolveLayoutTransition(layout) {
302
337
  if (!layout) return void 0;
303
338
  const cfg = layout === true ? { type: "spring" } : layout;
@@ -594,7 +629,79 @@ var NUMERIC_TOP_LEVEL_KEYS = [
594
629
  "borderRadius",
595
630
  "shadowOpacity",
596
631
  "shadowRadius",
597
- "elevation"
632
+ "elevation",
633
+ // ─── Added in 0.0.5 ──────────────────────────────────────────────────────
634
+ //
635
+ // Every key below rides the generic numeric path: one shared value, resolved
636
+ // on the JS thread, emitted by the worklet through the same `out[key] = v`
637
+ // branch as `opacity`. None needs a new interpolation mode, so the cost is
638
+ // the shared value plus a `DEFAULT_RESTING` entry.
639
+ //
640
+ // The load-bearing constraint is the resting default, not the plumbing. A
641
+ // key declared only in `gesture` / `exit` / a non-active variant rests at
642
+ // `DEFAULT_RESTING` unless the static `style` names it, and the animated
643
+ // style merges *after* `style` — so a non-identity default silently stomps
644
+ // the layout. That is the `0.0.3` P0 class, and it is why the sizing and
645
+ // spacing keys below default to `0` only in the sense RN itself does (an
646
+ // unset `padding` *is* 0); `minWidth`/`maxWidth`/`aspectRatio` are
647
+ // deliberately excluded — their "unset" is not a number, so no default can
648
+ // stand in for it without changing layout on activation.
649
+ //
650
+ // Per-corner radii. Animating one corner is common (a card expanding into a
651
+ // sheet keeps its top corners and squares off the bottom); `borderRadius`
652
+ // alone can't express it.
653
+ "borderTopLeftRadius",
654
+ "borderTopRightRadius",
655
+ "borderBottomLeftRadius",
656
+ "borderBottomRightRadius",
657
+ // Border widths. `borderWidth` is the common case; the per-edge keys matter
658
+ // for underline-style focus affordances (`borderBottomWidth` on a field).
659
+ "borderWidth",
660
+ "borderTopWidth",
661
+ "borderRightWidth",
662
+ "borderBottomWidth",
663
+ "borderLeftWidth",
664
+ // Absolute-position insets. Bread-and-butter for drawers, tooltips, and
665
+ // anything pinned to an edge that slides in.
666
+ "top",
667
+ "right",
668
+ "bottom",
669
+ "left",
670
+ // Spacing. Padding animates the box from the inside (a button growing its
671
+ // hit area on press); margin shifts it within its parent's flow.
672
+ "padding",
673
+ "paddingTop",
674
+ "paddingRight",
675
+ "paddingBottom",
676
+ "paddingLeft",
677
+ "paddingHorizontal",
678
+ "paddingVertical",
679
+ "margin",
680
+ "marginTop",
681
+ "marginRight",
682
+ "marginBottom",
683
+ "marginLeft",
684
+ "marginHorizontal",
685
+ "marginVertical",
686
+ // Flex sizing. `flex` / `flexGrow` interpolate the split between siblings —
687
+ // the idiomatic RN way to animate a pane resize without measuring.
688
+ "flex",
689
+ "flexGrow",
690
+ "flexShrink",
691
+ // Text metrics. `TextStyle`-only at the type level (`AnimateStyle<C>` gates
692
+ // them per primitive), but the runtime table is shared across primitives —
693
+ // an unused slot is one ref, same as `tintColor` has always been.
694
+ "fontSize",
695
+ "letterSpacing",
696
+ "lineHeight",
697
+ // Stacking. Integer-valued in practice, and interpolating it mid-flight is
698
+ // rarely what you want, but a `no-animation` transition on `zIndex` is the
699
+ // clean way to reorder at a step boundary in a sequence.
700
+ "zIndex",
701
+ // Gap. Row/column gaps animate a list's density without touching children.
702
+ "gap",
703
+ "rowGap",
704
+ "columnGap"
598
705
  ];
599
706
  var COLOR_KEYS = [
600
707
  "backgroundColor",
@@ -625,7 +732,7 @@ var COLOR_KEY_SET = new Set(COLOR_KEYS);
625
732
  var SHADOW_OFFSET_KEY_SET = new Set(SHADOW_OFFSET_KEYS);
626
733
  var STRUCTURED_KEY_SET = new Set(STRUCTURED_KEYS);
627
734
  var SHARED_STYLE_KEY_SET = new Set(SHARED_STYLE_KEYS);
628
- var NO_BOX_SHADOW = Object.freeze([]);
735
+ var NO_BOX_SHADOW = Object.freeze({});
629
736
  var GESTURE_LAYER_NAMES = [
630
737
  "hovered",
631
738
  "focused",
@@ -650,15 +757,69 @@ var DEFAULT_RESTING = {
650
757
  shadowOpacity: 0,
651
758
  shadowRadius: 0,
652
759
  elevation: 0,
653
- // 'transparent' is the only safe universal default for colors: it works as
654
- // an initial seed for any color animation (no jarring opaque flash on mount
655
- // when `initial` is omitted) and rgba(0,0,0,0) interpolates cleanly into
656
- // any opaque target via Reanimated's color util.
657
- backgroundColor: "transparent",
658
- borderColor: "transparent",
659
- color: "transparent",
660
- tintColor: "transparent",
661
- shadowColor: "transparent",
760
+ // 0.0.5 keys. Every one of these is 0 because that is RN's own unset value
761
+ // for it an element with no `paddingTop` has 0 padding, no `borderWidth`
762
+ // has none, no `top` is unpinned at 0 within its positioning context. So a
763
+ // key that reaches its resting default lands where the element already was.
764
+ //
765
+ // The exception worth naming: `flex` / `flexGrow` / `flexShrink` do NOT have
766
+ // 0 as a universal identity (`flexShrink` defaults to 1 on the web, and RN's
767
+ // own default differs by axis). They are still 0 here, because the style-scan
768
+ // in the resting pass reads the real value off the static `style` whenever it
769
+ // is declared — and a consumer animating flex without declaring it is asking
770
+ // for the from-0 growth, which is the readable interpretation.
771
+ borderTopLeftRadius: 0,
772
+ borderTopRightRadius: 0,
773
+ borderBottomLeftRadius: 0,
774
+ borderBottomRightRadius: 0,
775
+ borderWidth: 0,
776
+ borderTopWidth: 0,
777
+ borderRightWidth: 0,
778
+ borderBottomWidth: 0,
779
+ borderLeftWidth: 0,
780
+ top: 0,
781
+ right: 0,
782
+ bottom: 0,
783
+ left: 0,
784
+ padding: 0,
785
+ paddingTop: 0,
786
+ paddingRight: 0,
787
+ paddingBottom: 0,
788
+ paddingLeft: 0,
789
+ paddingHorizontal: 0,
790
+ paddingVertical: 0,
791
+ margin: 0,
792
+ marginTop: 0,
793
+ marginRight: 0,
794
+ marginBottom: 0,
795
+ marginLeft: 0,
796
+ marginHorizontal: 0,
797
+ marginVertical: 0,
798
+ flex: 0,
799
+ flexGrow: 0,
800
+ flexShrink: 0,
801
+ fontSize: 0,
802
+ letterSpacing: 0,
803
+ lineHeight: 0,
804
+ zIndex: 0,
805
+ gap: 0,
806
+ rowGap: 0,
807
+ columnGap: 0,
808
+ // Fully transparent black is the only safe universal default for colors: it
809
+ // works as an initial seed for any color animation (no jarring opaque flash
810
+ // on mount when `initial` is omitted) and interpolates cleanly into any
811
+ // opaque target.
812
+ //
813
+ // Spelled `rgba(0, 0, 0, 0)` and NOT `'transparent'`, which is not optional.
814
+ // Reanimated's color-name table maps `transparent` to `undefined`, so it is
815
+ // the one named color `isColor()` rejects — a slot resting at the keyword
816
+ // takes the prefix-number-suffix branch on its next animation and produces
817
+ // `NaN` instead of a color. See `TRANSPARENT` in `internal/boxShadow.ts`.
818
+ backgroundColor: TRANSPARENT,
819
+ borderColor: TRANSPARENT,
820
+ color: TRANSPARENT,
821
+ tintColor: TRANSPARENT,
822
+ shadowColor: TRANSPARENT,
662
823
  shadowOffsetWidth: 0,
663
824
  shadowOffsetHeight: 0,
664
825
  boxShadow: NO_BOX_SHADOW
@@ -845,25 +1006,28 @@ function createMotionComponent(Component) {
845
1006
  const raw = flat.boxShadow;
846
1007
  if (raw !== void 0) {
847
1008
  styleRestingShadow = normalizeBoxShadow(raw);
848
- styleResting.boxShadow = styleRestingShadow.layers;
1009
+ styleResting.boxShadow = layersToPayload(
1010
+ styleRestingShadow.layers
1011
+ );
849
1012
  }
850
1013
  continue;
851
1014
  }
852
1015
  const v = styleValueFor(flat, key);
853
- if (v !== void 0) styleResting[key] = v;
1016
+ if (v !== void 0) {
1017
+ styleResting[key] = COLOR_KEY_SET.has(key) ? normalizeAnimatableColor(v) : v;
1018
+ }
854
1019
  }
855
1020
  }
856
1021
  }
857
1022
  const boxShadowSeedRef = useRef(null);
858
1023
  if (boxShadowSeedRef.current === null) {
859
1024
  const source = initial === false ? animateRecord.boxShadow : initialRecord?.boxShadow ?? animateRecord.boxShadow;
860
- boxShadowSeedRef.current = source !== void 0 ? normalizeBoxShadow(source) : styleRestingShadow ?? {
861
- layers: [...NO_BOX_SHADOW],
862
- insets: null
863
- };
1025
+ boxShadowSeedRef.current = source !== void 0 ? normalizeBoxShadow(source) : styleRestingShadow ?? { layers: [], insets: null };
864
1026
  }
865
1027
  const sharedValues = useAnimatableSharedValues((key) => {
866
- if (key === "boxShadow") return boxShadowSeedRef.current.layers;
1028
+ if (key === "boxShadow") {
1029
+ return layersToPayload(boxShadowSeedRef.current.layers);
1030
+ }
867
1031
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
868
1032
  const axis = shadowOffsetAxisFor(key);
869
1033
  if (initial === false) {
@@ -876,9 +1040,11 @@ function createMotionComponent(Component) {
876
1040
  }
877
1041
  if (initial === false) {
878
1042
  const a = animateRecord[key];
879
- return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
1043
+ const seed2 = restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
1044
+ return COLOR_KEY_SET.has(key) ? normalizeAnimatableColor(seed2) : seed2;
880
1045
  }
881
- return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
1046
+ const seed = initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
1047
+ return COLOR_KEY_SET.has(key) ? normalizeAnimatableColor(seed) : seed;
882
1048
  });
883
1049
  const boxShadowInsets = useSharedValue(
884
1050
  boxShadowSeedRef.current.insets
@@ -991,7 +1157,7 @@ function createMotionComponent(Component) {
991
1157
  TRANSFORM_KEY_SET.has(key) ? transformGroup : void 0
992
1158
  );
993
1159
  sharedValues[key].value = resolveAnimatableValue(
994
- target,
1160
+ COLOR_KEY_SET.has(key) ? normalizeAnimatableColorTarget(target) : target,
995
1161
  cfg,
996
1162
  factory
997
1163
  );
@@ -1128,14 +1294,14 @@ function createMotionComponent(Component) {
1128
1294
  out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH };
1129
1295
  }
1130
1296
  if (hasBoxShadow) {
1131
- const layers2 = sharedValues.boxShadow.value;
1297
+ const payload = sharedValues.boxShadow.value;
1132
1298
  const insets = boxShadowInsets.value;
1133
1299
  if (insets === null) {
1134
- out.boxShadow = layers2;
1300
+ out.boxShadow = payloadToLayers(payload);
1135
1301
  } else {
1136
1302
  const withInset = [];
1137
- for (let i = 0; i < layers2.length; i++) {
1138
- withInset.push({ ...layers2[i], inset: insets[i] });
1303
+ for (let i = 0; payload[i] !== void 0; i++) {
1304
+ withInset.push({ ...payload[i], inset: insets[i] });
1139
1305
  }
1140
1306
  out.boxShadow = withInset;
1141
1307
  }
@@ -1202,64 +1368,15 @@ function hasMotionProps(props) {
1202
1368
  return false;
1203
1369
  }
1204
1370
  function useAnimatableSharedValues(init) {
1205
- const translateX = useSharedValue(init("translateX"));
1206
- const translateY = useSharedValue(init("translateY"));
1207
- const scale = useSharedValue(init("scale"));
1208
- const scaleX = useSharedValue(init("scaleX"));
1209
- const scaleY = useSharedValue(init("scaleY"));
1210
- const rotate = useSharedValue(init("rotate"));
1211
- const rotateX = useSharedValue(init("rotateX"));
1212
- const rotateY = useSharedValue(init("rotateY"));
1213
- const opacity = useSharedValue(init("opacity"));
1214
- const width = useSharedValue(init("width"));
1215
- const height = useSharedValue(init("height"));
1216
- const borderRadius = useSharedValue(init("borderRadius"));
1217
- const shadowOpacity = useSharedValue(
1218
- init("shadowOpacity")
1219
- );
1220
- const shadowRadius = useSharedValue(init("shadowRadius"));
1221
- const elevation = useSharedValue(init("elevation"));
1222
- const backgroundColor = useSharedValue(
1223
- init("backgroundColor")
1224
- );
1225
- const borderColor = useSharedValue(init("borderColor"));
1226
- const color = useSharedValue(init("color"));
1227
- const tintColor = useSharedValue(init("tintColor"));
1228
- const shadowColor = useSharedValue(init("shadowColor"));
1229
- const shadowOffsetWidth = useSharedValue(
1230
- init("shadowOffsetWidth")
1231
- );
1232
- const shadowOffsetHeight = useSharedValue(
1233
- init("shadowOffsetHeight")
1234
- );
1235
- const boxShadow = useSharedValue(init("boxShadow"));
1371
+ const entries = [];
1372
+ for (const key of ALL_KEYS) {
1373
+ entries.push([key, useSharedValue(init(key))]);
1374
+ }
1236
1375
  const ref = useRef(null);
1237
1376
  if (ref.current === null) {
1238
- ref.current = {
1239
- translateX,
1240
- translateY,
1241
- scale,
1242
- scaleX,
1243
- scaleY,
1244
- rotate,
1245
- rotateX,
1246
- rotateY,
1247
- opacity,
1248
- width,
1249
- height,
1250
- borderRadius,
1251
- shadowOpacity,
1252
- shadowRadius,
1253
- elevation,
1254
- backgroundColor,
1255
- borderColor,
1256
- color,
1257
- tintColor,
1258
- shadowColor,
1259
- shadowOffsetWidth,
1260
- shadowOffsetHeight,
1261
- boxShadow
1262
- };
1377
+ const map2 = {};
1378
+ for (const [key, sv] of entries) map2[key] = sv;
1379
+ ref.current = map2;
1263
1380
  }
1264
1381
  const map = ref.current;
1265
1382
  useEffect(
@@ -1300,6 +1417,7 @@ function makeKeyCallbackFactory(key, sharedValue, target, onAnimationEndRef, met
1300
1417
  }
1301
1418
  const reportedIteration = state.iteration;
1302
1419
  if (phase === "sequence" || phase === "repeat") state.iteration++;
1420
+ const reportedValue = key === "boxShadow" && value !== void 0 ? payloadToLayers(value) : value;
1303
1421
  const fn = onAnimationEndRef.current;
1304
1422
  if (fn) {
1305
1423
  if (isTransformKey && transformGroup && phase === "animation") {
@@ -1308,7 +1426,7 @@ function makeKeyCallbackFactory(key, sharedValue, target, onAnimationEndRef, met
1308
1426
  fn({
1309
1427
  key: "transform",
1310
1428
  finished,
1311
- value,
1429
+ value: reportedValue,
1312
1430
  target,
1313
1431
  phase,
1314
1432
  step,
@@ -1319,7 +1437,7 @@ function makeKeyCallbackFactory(key, sharedValue, target, onAnimationEndRef, met
1319
1437
  fn({
1320
1438
  key,
1321
1439
  finished,
1322
- value,
1440
+ value: reportedValue,
1323
1441
  target,
1324
1442
  phase,
1325
1443
  step,
@@ -1344,16 +1462,16 @@ function shadowOffsetAxisValue(source, axis) {
1344
1462
  return source?.[axis];
1345
1463
  }
1346
1464
  function driveBoxShadow(slot, insetSlot, target, cfg, factory) {
1347
- const currentLayers = Array.isArray(slot.value) ? slot.value : NO_BOX_SHADOW;
1465
+ const currentLayers = payloadToLayers(slot.value);
1348
1466
  const { from, to, insets } = prepareBoxShadowAnimation(
1349
- { layers: [...currentLayers], insets: insetSlot.value },
1467
+ { layers: currentLayers, insets: insetSlot.value },
1350
1468
  target
1351
1469
  );
1352
1470
  insetSlot.value = insets;
1353
- if (from.length !== currentLayers.length) slot.value = from;
1471
+ if (from.length !== currentLayers.length) slot.value = layersToPayload(from);
1354
1472
  slot.value = resolveTransition(
1355
1473
  cfg,
1356
- to,
1474
+ layersToPayload(to),
1357
1475
  factory?.("animation", void 0)
1358
1476
  );
1359
1477
  }
@@ -1571,4 +1689,4 @@ function compose(user, ours) {
1571
1689
  };
1572
1690
  }
1573
1691
 
1574
- export { Presence, createMotionComponent, pairBoxShadowLayers, resolveBoxShadowInput, usePresence };
1692
+ export { Presence, TRANSPARENT, createMotionComponent, pairBoxShadowLayers, resolveBoxShadowInput, usePresence };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkWVRCKOPO_js = require('./chunk-WVRCKOPO.js');
4
- var chunk7A6WTXNK_js = require('./chunk-7A6WTXNK.js');
3
+ var chunkOBRGJAST_js = require('./chunk-OBRGJAST.js');
4
+ var chunkPM6CVGXJ_js = require('./chunk-PM6CVGXJ.js');
5
5
  var react = require('react');
6
6
  var reactNativeReanimated = require('react-native-reanimated');
7
7
 
@@ -10,12 +10,12 @@ function useGesture(transition) {
10
10
  const focused = reactNativeReanimated.useSharedValue(0);
11
11
  const focusVisible = reactNativeReanimated.useSharedValue(0);
12
12
  const hovered = reactNativeReanimated.useSharedValue(0);
13
- const shouldReduceMotion = chunkWVRCKOPO_js.useShouldReduceMotion();
14
- const resolved = chunkWVRCKOPO_js.resolveNamedTransitionProp(transition, chunkWVRCKOPO_js.useNamedTransitions());
13
+ const shouldReduceMotion = chunkOBRGJAST_js.useShouldReduceMotion();
14
+ const resolved = chunkOBRGJAST_js.resolveNamedTransitionProp(transition, chunkOBRGJAST_js.useNamedTransitions());
15
15
  const setLayer = react.useCallback(
16
16
  (sv, layer, target) => {
17
17
  const cfg = shouldReduceMotion ? { type: "no-animation" } : layerTransition(layer, resolved) ?? { type: "spring" };
18
- sv.value = chunk7A6WTXNK_js.resolveTransition(cfg, target);
18
+ sv.value = chunkPM6CVGXJ_js.resolveTransition(cfg, target);
19
19
  },
20
20
  // The transition is intentionally read on every call rather than cooked
21
21
  // into the dep array — a fresh literal each render would otherwise
@@ -34,7 +34,7 @@ function useGesture(transition) {
34
34
  onHoverOut: () => setLayer(hovered, "hovered", 0),
35
35
  onFocus: () => {
36
36
  setLayer(focused, "focused", 1);
37
- if (chunkWVRCKOPO_js.isFocusVisible()) setLayer(focusVisible, "focusVisible", 1);
37
+ if (chunkOBRGJAST_js.isFocusVisible()) setLayer(focusVisible, "focusVisible", 1);
38
38
  },
39
39
  onBlur: () => {
40
40
  setLayer(focused, "focused", 0);
@@ -47,7 +47,7 @@ function useGesture(transition) {
47
47
  }
48
48
  function layerTransition(layer, transition) {
49
49
  if (!transition) return void 0;
50
- if (chunk7A6WTXNK_js.isTopLevelTransition(transition)) return transition;
50
+ if (chunkPM6CVGXJ_js.isTopLevelTransition(transition)) return transition;
51
51
  return transition[layer];
52
52
  }
53
53
 
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkMYE5NMXW_js = require('./chunk-MYE5NMXW.js');
3
+ var chunk767UKZXG_js = require('./chunk-767UKZXG.js');
4
4
  var reactNative = require('react-native');
5
5
 
6
- var MotionPressable = chunkMYE5NMXW_js.createMotionComponent(reactNative.Pressable);
6
+ var MotionPressable = chunk767UKZXG_js.createMotionComponent(reactNative.Pressable);
7
7
 
8
8
  exports.MotionPressable = MotionPressable;