@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
@@ -1,4 +1,4 @@
1
- export { MotionPressable } from '../chunk-RF7J5FRM.mjs';
2
- import '../chunk-55W7BNO2.mjs';
3
- import '../chunk-QKC3APP4.mjs';
4
- import '../chunk-TU2XFPAK.mjs';
1
+ export { MotionPressable } from '../chunk-C3EDC5ZW.mjs';
2
+ import '../chunk-2ICQLWH2.mjs';
3
+ import '../chunk-Z3HCJ43H.mjs';
4
+ import '../chunk-REYL77RE.mjs';
@@ -1,5 +1,5 @@
1
1
  import { ScrollView } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.mjs';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.mjs';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { ScrollView } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.js';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.js';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkXMEVPEMH_js = require('../chunk-XMEVPEMH.js');
4
- require('../chunk-MYE5NMXW.js');
5
- require('../chunk-WVRCKOPO.js');
6
- require('../chunk-7A6WTXNK.js');
3
+ var chunkX7B5WR5A_js = require('../chunk-X7B5WR5A.js');
4
+ require('../chunk-767UKZXG.js');
5
+ require('../chunk-OBRGJAST.js');
6
+ require('../chunk-PM6CVGXJ.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionScrollView", {
11
11
  enumerable: true,
12
- get: function () { return chunkXMEVPEMH_js.MotionScrollView; }
12
+ get: function () { return chunkX7B5WR5A_js.MotionScrollView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionScrollView } from '../chunk-NAOXQJ7U.mjs';
2
- import '../chunk-55W7BNO2.mjs';
3
- import '../chunk-QKC3APP4.mjs';
4
- import '../chunk-TU2XFPAK.mjs';
1
+ export { MotionScrollView } from '../chunk-BAAQI37F.mjs';
2
+ import '../chunk-2ICQLWH2.mjs';
3
+ import '../chunk-Z3HCJ43H.mjs';
4
+ import '../chunk-REYL77RE.mjs';
@@ -1,5 +1,5 @@
1
1
  import { Text } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.mjs';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.mjs';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { Text } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.js';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.js';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkTVFNJ25Z_js = require('../chunk-TVFNJ25Z.js');
4
- require('../chunk-MYE5NMXW.js');
5
- require('../chunk-WVRCKOPO.js');
6
- require('../chunk-7A6WTXNK.js');
3
+ var chunkSTARIT6W_js = require('../chunk-STARIT6W.js');
4
+ require('../chunk-767UKZXG.js');
5
+ require('../chunk-OBRGJAST.js');
6
+ require('../chunk-PM6CVGXJ.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionText", {
11
11
  enumerable: true,
12
- get: function () { return chunkTVFNJ25Z_js.MotionText; }
12
+ get: function () { return chunkSTARIT6W_js.MotionText; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionText } from '../chunk-4ASOELLA.mjs';
2
- import '../chunk-55W7BNO2.mjs';
3
- import '../chunk-QKC3APP4.mjs';
4
- import '../chunk-TU2XFPAK.mjs';
1
+ export { MotionText } from '../chunk-F7LJX56B.mjs';
2
+ import '../chunk-2ICQLWH2.mjs';
3
+ import '../chunk-Z3HCJ43H.mjs';
4
+ import '../chunk-REYL77RE.mjs';
@@ -1,5 +1,5 @@
1
1
  import { View } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.mjs';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.mjs';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { View } from 'react-native';
2
- import { M as MotionComponent } from '../types-37pN82yh.js';
2
+ import { M as MotionComponent } from '../types-BQgLJeQG.js';
3
3
  import 'react';
4
4
 
5
5
  /**
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkVLDIFL3F_js = require('../chunk-VLDIFL3F.js');
4
- require('../chunk-MYE5NMXW.js');
5
- require('../chunk-WVRCKOPO.js');
6
- require('../chunk-7A6WTXNK.js');
3
+ var chunkK5SI6VXS_js = require('../chunk-K5SI6VXS.js');
4
+ require('../chunk-767UKZXG.js');
5
+ require('../chunk-OBRGJAST.js');
6
+ require('../chunk-PM6CVGXJ.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionView", {
11
11
  enumerable: true,
12
- get: function () { return chunkVLDIFL3F_js.MotionView; }
12
+ get: function () { return chunkK5SI6VXS_js.MotionView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionView } from '../chunk-YKGY22SB.mjs';
2
- import '../chunk-55W7BNO2.mjs';
3
- import '../chunk-QKC3APP4.mjs';
4
- import '../chunk-TU2XFPAK.mjs';
1
+ export { MotionView } from '../chunk-CMHVF6F4.mjs';
2
+ import '../chunk-2ICQLWH2.mjs';
3
+ import '../chunk-Z3HCJ43H.mjs';
4
+ import '../chunk-REYL77RE.mjs';
@@ -1,6 +1,6 @@
1
1
  import { PanResponderInstance } from 'react-native';
2
2
  import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
- import { T as TransitionConfig } from '../types-37pN82yh.mjs';
3
+ import { T as TransitionConfig } from '../types-BQgLJeQG.mjs';
4
4
  import 'react';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { PanResponderInstance } from 'react-native';
2
2
  import { useAnimatedStyle, SharedValue } from 'react-native-reanimated';
3
- import { T as TransitionConfig } from '../types-37pN82yh.js';
3
+ import { T as TransitionConfig } from '../types-BQgLJeQG.js';
4
4
  import 'react';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk7A6WTXNK_js = require('../chunk-7A6WTXNK.js');
3
+ var chunkPM6CVGXJ_js = require('../chunk-PM6CVGXJ.js');
4
4
  var react = require('react');
5
5
  var reactNative = require('react-native');
6
6
  var reactNativeReanimated = require('react-native-reanimated');
@@ -48,14 +48,14 @@ function useTouchDrag(options = {}) {
48
48
  if (result) {
49
49
  if (result.x && lockX) {
50
50
  const toX = "to" in result.x ? result.x.to : x;
51
- dragX.value = chunk7A6WTXNK_js.buildReleaseAnimation(
51
+ dragX.value = chunkPM6CVGXJ_js.buildReleaseAnimation(
52
52
  result.x,
53
53
  toX
54
54
  );
55
55
  }
56
56
  if (result.y && lockY) {
57
57
  const toY = "to" in result.y ? result.y.to : y;
58
- dragY.value = chunk7A6WTXNK_js.buildReleaseAnimation(
58
+ dragY.value = chunkPM6CVGXJ_js.buildReleaseAnimation(
59
59
  result.y,
60
60
  toY
61
61
  );
@@ -1,4 +1,4 @@
1
- import { buildReleaseAnimation } from '../chunk-TU2XFPAK.mjs';
1
+ import { buildReleaseAnimation } from '../chunk-REYL77RE.mjs';
2
2
  import { useMemo } from 'react';
3
3
  import { PanResponder } from 'react-native';
4
4
  import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
@@ -184,6 +184,20 @@ type BoxShadowInput = string | ReadonlyArray<BoxShadowValue>;
184
184
  type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S ? {
185
185
  boxShadow?: BoxShadowInput;
186
186
  } : {};
187
+ /**
188
+ * The `transform: [...]` array form, passed through as the underlying style
189
+ * types it. Inertia reads transform entries out of the array (see
190
+ * `styleValueFor` in `createMotionComponent`) rather than treating the array as
191
+ * an animatable scalar, so it is exempted from the `AnimatableValue` mapping —
192
+ * wrapping it there would offer a nonsensical `transform: [[...], [...]]`
193
+ * keyframe form.
194
+ *
195
+ * Conditional on `S` for the same reason as `boxShadow`: only appears where the
196
+ * underlying style actually has it.
197
+ */
198
+ type AnimatableTransformArray<S> = 'transform' extends keyof S ? {
199
+ transform?: S['transform'];
200
+ } : {};
187
201
  /**
188
202
  * The animation state shape inferred from the underlying component's style
189
203
  * prop. We narrow to the value side of `style` so consumers see ViewStyle on
@@ -196,11 +210,33 @@ type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S ? {
196
210
  * accepts any key. Excluding functions first keeps inference tight.
197
211
  */
198
212
  type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
213
+ /**
214
+ * The style keys Inertia's runtime actually drives — the type-level mirror of
215
+ * `ALL_KEYS` in `createMotionComponent`, minus the `shadowOffset*` synthetics
216
+ * (consumers write the nested `shadowOffset` object, not the axes) and minus
217
+ * `boxShadow` (handled by `AnimatableStructuredExtras`, whose value shape
218
+ * deliberately departs from `AnimatableValue`).
219
+ *
220
+ * **This list and `ALL_KEYS` must stay in sync.** They are separate because one
221
+ * is a type and the other is a runtime array, and TypeScript can't derive the
222
+ * former from a value living in a module that imports this one. The direction
223
+ * that matters: a key here but not in `ALL_KEYS` typechecks and then silently
224
+ * does nothing at runtime — which is precisely the defect this type was
225
+ * introduced to kill. `animate-keys.test-d.tsx` pins the two together.
226
+ *
227
+ * Before 0.0.5 this type mapped over the component's *entire* style surface, so
228
+ * `animate={{ paddingTop: 40 }}` compiled cleanly and was then dropped on the
229
+ * floor — no error, no warning. That contradicted the library's own headline
230
+ * differentiator (wrong props should error, not go unnoticed), so the mapping is
231
+ * now an intersection: a key must be both animatable by Inertia *and* present on
232
+ * the underlying component's style prop.
233
+ */
234
+ type AnimatableStyleKey = 'translateX' | 'translateY' | 'scaleX' | 'scaleY' | 'opacity' | 'width' | 'height' | 'borderRadius' | 'shadowOpacity' | 'shadowRadius' | 'elevation' | 'borderTopLeftRadius' | 'borderTopRightRadius' | 'borderBottomLeftRadius' | 'borderBottomRightRadius' | 'borderWidth' | 'borderTopWidth' | 'borderRightWidth' | 'borderBottomWidth' | 'borderLeftWidth' | 'top' | 'right' | 'bottom' | 'left' | 'padding' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft' | 'paddingHorizontal' | 'paddingVertical' | 'margin' | 'marginTop' | 'marginRight' | 'marginBottom' | 'marginLeft' | 'marginHorizontal' | 'marginVertical' | 'flex' | 'flexGrow' | 'flexShrink' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'zIndex' | 'gap' | 'rowGap' | 'columnGap' | 'backgroundColor' | 'borderColor' | 'color' | 'tintColor' | 'shadowColor' | 'shadowOffset';
199
235
  type AnimateStyle<C> = C extends {
200
236
  style?: infer Raw;
201
- } ? _StyleValue<Raw> extends StyleProp<infer S> ? Omit<{
202
- [K in keyof S]?: AnimatableValue<S[K]>;
203
- }, 'boxShadow'> & AnimatableTransformExtras & AnimatableStructuredExtras<S> : never : never;
237
+ } ? _StyleValue<Raw> extends StyleProp<infer S> ? {
238
+ [K in keyof S as K extends AnimatableStyleKey ? K : never]?: K extends 'boxShadow' | 'transform' ? never : AnimatableValue<S[K]>;
239
+ } & AnimatableTransformExtras & AnimatableStructuredExtras<S> & AnimatableTransformArray<S> : never : never;
204
240
  interface AnimationCallbackInfo<S> {
205
241
  /**
206
242
  * The animatable key that just settled — typically a `keyof S` (e.g.
@@ -184,6 +184,20 @@ type BoxShadowInput = string | ReadonlyArray<BoxShadowValue>;
184
184
  type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S ? {
185
185
  boxShadow?: BoxShadowInput;
186
186
  } : {};
187
+ /**
188
+ * The `transform: [...]` array form, passed through as the underlying style
189
+ * types it. Inertia reads transform entries out of the array (see
190
+ * `styleValueFor` in `createMotionComponent`) rather than treating the array as
191
+ * an animatable scalar, so it is exempted from the `AnimatableValue` mapping —
192
+ * wrapping it there would offer a nonsensical `transform: [[...], [...]]`
193
+ * keyframe form.
194
+ *
195
+ * Conditional on `S` for the same reason as `boxShadow`: only appears where the
196
+ * underlying style actually has it.
197
+ */
198
+ type AnimatableTransformArray<S> = 'transform' extends keyof S ? {
199
+ transform?: S['transform'];
200
+ } : {};
187
201
  /**
188
202
  * The animation state shape inferred from the underlying component's style
189
203
  * prop. We narrow to the value side of `style` so consumers see ViewStyle on
@@ -196,11 +210,33 @@ type AnimatableStructuredExtras<S> = 'boxShadow' extends keyof S ? {
196
210
  * accepts any key. Excluding functions first keeps inference tight.
197
211
  */
198
212
  type _StyleValue<T> = Exclude<T, (...args: any[]) => any>;
213
+ /**
214
+ * The style keys Inertia's runtime actually drives — the type-level mirror of
215
+ * `ALL_KEYS` in `createMotionComponent`, minus the `shadowOffset*` synthetics
216
+ * (consumers write the nested `shadowOffset` object, not the axes) and minus
217
+ * `boxShadow` (handled by `AnimatableStructuredExtras`, whose value shape
218
+ * deliberately departs from `AnimatableValue`).
219
+ *
220
+ * **This list and `ALL_KEYS` must stay in sync.** They are separate because one
221
+ * is a type and the other is a runtime array, and TypeScript can't derive the
222
+ * former from a value living in a module that imports this one. The direction
223
+ * that matters: a key here but not in `ALL_KEYS` typechecks and then silently
224
+ * does nothing at runtime — which is precisely the defect this type was
225
+ * introduced to kill. `animate-keys.test-d.tsx` pins the two together.
226
+ *
227
+ * Before 0.0.5 this type mapped over the component's *entire* style surface, so
228
+ * `animate={{ paddingTop: 40 }}` compiled cleanly and was then dropped on the
229
+ * floor — no error, no warning. That contradicted the library's own headline
230
+ * differentiator (wrong props should error, not go unnoticed), so the mapping is
231
+ * now an intersection: a key must be both animatable by Inertia *and* present on
232
+ * the underlying component's style prop.
233
+ */
234
+ type AnimatableStyleKey = 'translateX' | 'translateY' | 'scaleX' | 'scaleY' | 'opacity' | 'width' | 'height' | 'borderRadius' | 'shadowOpacity' | 'shadowRadius' | 'elevation' | 'borderTopLeftRadius' | 'borderTopRightRadius' | 'borderBottomLeftRadius' | 'borderBottomRightRadius' | 'borderWidth' | 'borderTopWidth' | 'borderRightWidth' | 'borderBottomWidth' | 'borderLeftWidth' | 'top' | 'right' | 'bottom' | 'left' | 'padding' | 'paddingTop' | 'paddingRight' | 'paddingBottom' | 'paddingLeft' | 'paddingHorizontal' | 'paddingVertical' | 'margin' | 'marginTop' | 'marginRight' | 'marginBottom' | 'marginLeft' | 'marginHorizontal' | 'marginVertical' | 'flex' | 'flexGrow' | 'flexShrink' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'zIndex' | 'gap' | 'rowGap' | 'columnGap' | 'backgroundColor' | 'borderColor' | 'color' | 'tintColor' | 'shadowColor' | 'shadowOffset';
199
235
  type AnimateStyle<C> = C extends {
200
236
  style?: infer Raw;
201
- } ? _StyleValue<Raw> extends StyleProp<infer S> ? Omit<{
202
- [K in keyof S]?: AnimatableValue<S[K]>;
203
- }, 'boxShadow'> & AnimatableTransformExtras & AnimatableStructuredExtras<S> : never : never;
237
+ } ? _StyleValue<Raw> extends StyleProp<infer S> ? {
238
+ [K in keyof S as K extends AnimatableStyleKey ? K : never]?: K extends 'boxShadow' | 'transform' ? never : AnimatableValue<S[K]>;
239
+ } & AnimatableTransformExtras & AnimatableStructuredExtras<S> & AnimatableTransformArray<S> : never : never;
204
240
  interface AnimationCallbackInfo<S> {
205
241
  /**
206
242
  * The animatable key that just settled — typically a `keyof S` (e.g.
@@ -1,5 +1,5 @@
1
1
  import { SharedValue } from 'react-native-reanimated';
2
- import { a as TransitionInput, G as GestureLayerTransitions } from './types-37pN82yh.js';
2
+ import { a as TransitionInput, G as GestureLayerTransitions } from './types-BQgLJeQG.js';
3
3
 
4
4
  /**
5
5
  * Handler bag returned by `useGesture`. Spread on a `Pressable` to drive the
@@ -1,5 +1,5 @@
1
1
  import { SharedValue } from 'react-native-reanimated';
2
- import { a as TransitionInput, G as GestureLayerTransitions } from './types-37pN82yh.mjs';
2
+ import { a as TransitionInput, G as GestureLayerTransitions } from './types-BQgLJeQG.mjs';
3
3
 
4
4
  /**
5
5
  * Handler bag returned by `useGesture`. Spread on a `Pressable` to drive the
package/llms.txt CHANGED
@@ -67,6 +67,7 @@ import { MotionScrollView } from '@rootnative/inertia/scroll-view'
67
67
  - `resolveAnimatableValue(value, base, factory?)` — one level up: resolves a full per-property `animate` value (plain target, `{ to, ...override }` step, or keyframe array → `withSequence` with sequence-level `repeat`) into one animation. Gives custom components the complete `animate` grammar.
68
68
  - `cubicBezier(x1, y1, x2, y2)` / `cubicBezier(css)` — builds a `timing.easing` value from cubic-bezier control points: four numbers, a W3C CSS `cubic-bezier(0.2, 0, 0, 1)` string (design-token form), or a CSS keyword (`'linear'` | `'ease'` | `'ease-in'` | `'ease-out'` | `'ease-in-out'`). Makes CSS-format easing tokens directly consumable — pair with named transitions to feed a theme's motion tokens into the registry. Invalid input throws (`x1`/`x2` must be within [0, 1]; `step-*` and `linear(...)` unsupported).
69
69
  - `ensureWorkletEasing(easing)` — normalizes any accepted easing input (plain function, worklet, or Reanimated 4 `EasingFunctionFactory`) into a worklet `withTiming` can consume. Only needed for manual `withTiming` calls — the resolvers and primitives apply it automatically.
70
+ - `TRANSPARENT` — `'rgba(0, 0, 0, 0)'`, the value to seed a colour shared value with when a custom component has no other source. Not `'transparent'`: Reanimated's colour table maps that keyword to `undefined`, so it is the one CSS colour name `isColor()` rejects, and a slot resting at it cannot be animated away from — under `withTiming` the target still snaps in at the end, under `withSpring` (the default) the animation never settles. Only affects values passed to `withSpring` / `withTiming`; `interpolateColor` parses the keyword fine, so hooks that interpolate colours themselves are unaffected.
70
71
  - `SharedValue<T>` (type) — re-exported from the root entry so props/refs that carry a value-layer hook result can be typed without importing from `react-native-reanimated`.
71
72
  - `@rootnative/inertia/reanimated` — render-layer interop subpath: pure re-exports of `Animated`, `createAnimatedComponent`, `useAnimatedStyle`, `useAnimatedProps`, `useDerivedValue`, `interpolate`, `interpolateColor`, `Extrapolation`, `cancelAnimation` (plus `SharedValue` / `DerivedValue` / `AnimatedStyle` / `AnimatedProps` types) under their original Reanimated names, for components that own their own `useAnimatedStyle` worklet over Inertia-driven shared values. Names must stay original — the Reanimated Babel plugin auto-workletizes callbacks by callee name. The `with*` animation factories are deliberately not re-exported; start animations with `useAnimation` / `resolveTransition` / `buildReleaseAnimation`.
72
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/inertia",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Declarative animation primitives for React Native, built on react-native-reanimated.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
package/src/index.ts CHANGED
@@ -39,6 +39,10 @@ export {
39
39
  resolveAnimatableValue,
40
40
  ensureWorkletEasing,
41
41
  } from './transitions'
42
+ // Seed value for any color shared value a custom animated component drives
43
+ // through `resolveTransition`. Exported because the obvious choice —
44
+ // `'transparent'` — cannot be animated away from; see the symbol's own docs.
45
+ export { TRANSPARENT } from './internal/color'
42
46
  export {
43
47
  useAnimation,
44
48
  useAnimator,
@@ -12,6 +12,8 @@
12
12
  * loudly there, not silently render the wrong elevation.
13
13
  */
14
14
 
15
+ import { normalizeAnimatableColor, TRANSPARENT } from './color'
16
+
15
17
  /**
16
18
  * One layer of a `box-shadow`, structurally mirroring React Native's
17
19
  * `BoxShadowValue` (RN 0.76+ `boxShadow` style). Lengths are px numbers.
@@ -205,7 +207,7 @@ function invisibleLayer(inset: boolean): ResolvedBoxShadowLayer {
205
207
  offsetY: 0,
206
208
  blurRadius: 0,
207
209
  spreadDistance: 0,
208
- color: 'transparent',
210
+ color: TRANSPARENT,
209
211
  inset,
210
212
  }
211
213
  }
@@ -225,10 +227,10 @@ function invisibleLayer(inset: boolean): ResolvedBoxShadowLayer {
225
227
  * drive, and nothing it can't.
226
228
  *
227
229
  * `inset` is deliberately absent. `withTiming` / `withSpring` recurse into
228
- * arrays and objects and animate each leaf, and a boolean leaf falls through
229
- * to the plain numeric path — `false + (false - false) * p` evaluates to `0`,
230
- * so an in-flight frame hands the native shadow a number where it expects a
231
- * boolean. Inset is instead carried alongside as a static per-layer flag (see
230
+ * objects and animate each leaf, and a boolean leaf falls through to the plain
231
+ * numeric path — `false + (false - false) * p` evaluates to `0`, so an
232
+ * in-flight frame hands the native shadow a number where it expects a boolean.
233
+ * Inset is instead carried alongside as a static per-layer flag (see
232
234
  * `SplitBoxShadow.insets`) and reattached when the style is emitted.
233
235
  */
234
236
  export interface AnimatedBoxShadowLayer {
@@ -295,10 +297,16 @@ function coerceLength(value: unknown, field: string): number {
295
297
  return parseFloat(trimmed)
296
298
  }
297
299
 
298
- /** Drop `inset`, leaving only the fields Reanimated may drive. */
300
+ /**
301
+ * Drop `inset`, leaving only the fields Reanimated may drive, and rewrite a
302
+ * `'transparent'` colour to its RGBA spelling — see {@link TRANSPARENT} for
303
+ * why the CSS keyword cannot be animated. Done here rather than at parse time
304
+ * so the `useShadow` path, which interpolates colours itself and accepts the
305
+ * keyword happily, keeps the consumer's own spelling.
306
+ */
299
307
  function strip(layer: ResolvedBoxShadowLayer): AnimatedBoxShadowLayer {
300
- const { inset: _inset, ...rest } = layer
301
- return rest
308
+ const { inset: _inset, color, ...rest } = layer
309
+ return { ...rest, color: normalizeAnimatableColor(color) }
302
310
  }
303
311
 
304
312
  /**
@@ -335,7 +343,7 @@ export function normalizeBoxShadow(input: BoxShadowInput): SplitBoxShadow {
335
343
  * Prepare a from/to pair for a Reanimated-driven `boxShadow` animation.
336
344
  *
337
345
  * Both sides come back padded to the same layer count, because Reanimated's
338
- * `arrayOnStart` walks the **current** value's indices and reads `toValue[i]`
346
+ * `objectOnStart` walks the **current** value's keys and reads `toValue[key]`
339
347
  * for each — a target with fewer layers leaves the surplus leaves with
340
348
  * `toValue: undefined`, and a target with more never animates the extras at
341
349
  * all. `pairBoxShadowLayers` supplies the padding (a transparent zero layer,
@@ -373,3 +381,57 @@ export function prepareBoxShadowAnimation(
373
381
  }
374
382
  return { from, to, insets }
375
383
  }
384
+
385
+ /**
386
+ * The shape a `boxShadow` shared value actually holds: layers keyed by their
387
+ * index (`{ 0: layer, 1: layer }`) rather than an array.
388
+ *
389
+ * **This is not cosmetic, and an array does not work here.** Reanimated's
390
+ * animation drivers walk a structured value by dispatching on each leaf's
391
+ * runtime shape, but the two container branches are not symmetrical
392
+ * (`animation/util.ts`):
393
+ *
394
+ * - `objectOnStart` re-assigns the **decorated** `onStart` onto each child
395
+ * (`animation[key].onStart = animation.onStart`), so a child that is itself
396
+ * an object or a colour is dispatched again, recursively.
397
+ * - `arrayOnStart` does not. Each element gets the **base** scalar `onStart`,
398
+ * so anything other than a number is handed straight to the spring/timing
399
+ * maths. A layer object yields `object - object` → `NaN`; a colour string
400
+ * yields the same.
401
+ *
402
+ * The failure is silent and total: `withTiming` still snaps to the target when
403
+ * its duration elapses, so an array *looks* like it works, while `withSpring`
404
+ * — Inertia's default — decides it has settled only when
405
+ * `isAnimationTerminatingCalculation` says so, and every comparison against
406
+ * `NaN` is false. The animation never finishes, the shared value holds
407
+ * `'[object Object]NaN'` forever, `onAnimationEnd` never fires, and the frame
408
+ * loop never stops.
409
+ *
410
+ * Keying by index sends the layer list down the object branch, where the
411
+ * recursion works and each layer's colour reaches the RGBA-channel path.
412
+ */
413
+ export type BoxShadowPayload = Record<string, AnimatedBoxShadowLayer>
414
+
415
+ /** Index-key a layer list into the shape a shared value can animate. */
416
+ export function layersToPayload(
417
+ layers: readonly AnimatedBoxShadowLayer[],
418
+ ): BoxShadowPayload {
419
+ const payload: BoxShadowPayload = {}
420
+ for (let i = 0; i < layers.length; i++) payload[i] = layers[i]!
421
+ return payload
422
+ }
423
+
424
+ /**
425
+ * Read an index-keyed payload back out as a layer list.
426
+ *
427
+ * Callable from a worklet: the loop stops at the first missing index rather
428
+ * than asking for the key set, so no `Object.keys` runs at frame time.
429
+ */
430
+ export function payloadToLayers(
431
+ payload: BoxShadowPayload,
432
+ ): AnimatedBoxShadowLayer[] {
433
+ 'worklet'
434
+ const layers: AnimatedBoxShadowLayer[] = []
435
+ for (let i = 0; payload[i] !== undefined; i++) layers.push(payload[i]!)
436
+ return layers
437
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The one colour spelling Reanimated's animation drivers cannot accept.
3
+ *
4
+ * Reanimated dispatches an animation on the runtime shape of its **source**
5
+ * value, and `isColor()` is the gate for the RGBA-channel path. That gate is
6
+ * `processColorInitially(value) != null`, and Reanimated's colour-name table
7
+ * (`Colors.ts`) maps `transparent` to `undefined` — every other CSS colour
8
+ * name maps to a packed integer. So `'transparent'` is the single named colour
9
+ * that reports **false**.
10
+ *
11
+ * A value that fails the gate but is still a string falls to the
12
+ * prefix-number-suffix branch, which exists for values like `'100%'`. It runs
13
+ * `parseFloat('transparent')` → `NaN`, animates that, and re-attaches the
14
+ * prefix, so the slot fills with `'transparentNaN'`. Under `withTiming` the
15
+ * animation still snaps to its target when the duration elapses, which hides
16
+ * the fault; under `withSpring` — the library default — settling is decided by
17
+ * comparisons against `NaN`, which are all false, so the animation runs
18
+ * forever and the colour never appears.
19
+ *
20
+ * `'rgba(0, 0, 0, 0)'` is the same colour and passes the gate.
21
+ *
22
+ * This only applies to values handed to `withSpring` / `withTiming`.
23
+ * `interpolateColor` parses `'transparent'` correctly, which is why the
24
+ * gesture cascade, the shared-element carry, and `useShadow` are unaffected
25
+ * and keep the consumer's own spelling.
26
+ */
27
+ export const TRANSPARENT = 'rgba(0, 0, 0, 0)'
28
+
29
+ /**
30
+ * Rewrite `'transparent'` to a spelling Reanimated recognises. Any other value
31
+ * — a different colour, a number, a non-string — is returned untouched, by
32
+ * reference, so this costs nothing on the overwhelmingly common path.
33
+ */
34
+ export function normalizeAnimatableColor<T>(value: T): T | string {
35
+ return value === 'transparent' ? TRANSPARENT : value
36
+ }
37
+
38
+ /**
39
+ * Apply {@link normalizeAnimatableColor} across a whole `animate` target for a
40
+ * colour key, including the sequence forms — a keyframe array whose entries are
41
+ * either raw values or `{ to, ...transitionOverride }` step objects.
42
+ *
43
+ * Returns the input by reference when nothing needed rewriting, so an
44
+ * unaffected target neither allocates nor breaks any identity a caller relies
45
+ * on.
46
+ */
47
+ export function normalizeAnimatableColorTarget<T>(target: T): T {
48
+ if (typeof target === 'string') {
49
+ return normalizeAnimatableColor(target) as T
50
+ }
51
+ if (!Array.isArray(target)) return target
52
+
53
+ let changed = false
54
+ const next = target.map((step) => {
55
+ if (step === 'transparent') {
56
+ changed = true
57
+ return TRANSPARENT
58
+ }
59
+ if (
60
+ step !== null &&
61
+ typeof step === 'object' &&
62
+ (step as { to?: unknown }).to === 'transparent'
63
+ ) {
64
+ changed = true
65
+ return { ...(step as object), to: TRANSPARENT }
66
+ }
67
+ return step
68
+ })
69
+ return (changed ? next : target) as T
70
+ }