@rootnative/inertia 0.0.0-alpha.4 → 0.0.0-alpha.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 (50) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{chunk-5I3G43XA.js → chunk-2HYD2ZBK.js} +2 -2
  3. package/dist/chunk-3UTJJ4A3.js +8 -0
  4. package/dist/{chunk-Q6ZTMLTI.js → chunk-4PEHWDAZ.js} +7 -7
  5. package/dist/chunk-4QGXK6TF.js +8 -0
  6. package/dist/{chunk-WPPLZNM4.mjs → chunk-6SMPIOIC.mjs} +1 -1
  7. package/dist/{chunk-6EP3S2PN.js → chunk-7AOERN53.js} +3 -3
  8. package/dist/{chunk-6UQ4KA6V.js → chunk-7UDYEFBU.js} +22 -22
  9. package/dist/{chunk-K4KR5BIS.mjs → chunk-ALRHDFZE.mjs} +1 -1
  10. package/dist/{chunk-QQJSBIXV.mjs → chunk-CWLFUYIY.mjs} +1 -1
  11. package/dist/{chunk-6FENLMCA.mjs → chunk-CY7Y64C3.mjs} +17 -1
  12. package/dist/{chunk-I76OC6RX.mjs → chunk-DWCLIBYO.mjs} +2 -2
  13. package/dist/{chunk-IJNVUM5U.mjs → chunk-JVBXPF2G.mjs} +1 -1
  14. package/dist/{chunk-OBE7KTMK.mjs → chunk-NXDJZD6A.mjs} +1 -1
  15. package/dist/{chunk-SFRNO6AW.js → chunk-PTRF47DA.js} +17 -0
  16. package/dist/{chunk-L2EVRKSC.mjs → chunk-R63GIUNU.mjs} +1 -1
  17. package/dist/{chunk-ZQUCQRZT.mjs → chunk-RGNX6UZN.mjs} +2 -2
  18. package/dist/{chunk-OM5FXU4I.js → chunk-TDSO63CJ.js} +2 -2
  19. package/dist/chunk-Z7HIOFKQ.js +8 -0
  20. package/dist/gestureLayer/index.js +10 -10
  21. package/dist/gestureLayer/index.mjs +3 -3
  22. package/dist/index.d.mts +78 -13
  23. package/dist/index.d.ts +78 -13
  24. package/dist/index.js +202 -44
  25. package/dist/index.mjs +178 -20
  26. package/dist/motion/Image.js +5 -5
  27. package/dist/motion/Image.mjs +4 -4
  28. package/dist/motion/Pressable.js +5 -5
  29. package/dist/motion/Pressable.mjs +4 -4
  30. package/dist/motion/ScrollView.js +5 -5
  31. package/dist/motion/ScrollView.mjs +4 -4
  32. package/dist/motion/Text.js +5 -5
  33. package/dist/motion/Text.mjs +4 -4
  34. package/dist/motion/View.js +5 -5
  35. package/dist/motion/View.mjs +4 -4
  36. package/dist/touch/index.js +3 -3
  37. package/dist/touch/index.mjs +1 -1
  38. package/llms.txt +1 -1
  39. package/package.json +2 -2
  40. package/src/index.ts +1 -0
  41. package/src/internal/boxShadow.ts +204 -0
  42. package/src/internal/nonWorkletWarning.ts +41 -0
  43. package/src/layout/resolveLayout.ts +3 -2
  44. package/src/transitions/easing.ts +13 -8
  45. package/src/values/index.ts +1 -0
  46. package/src/values/useShadow.ts +76 -1
  47. package/src/values/useTransform.ts +28 -12
  48. package/dist/chunk-NDKVHL3N.js +0 -8
  49. package/dist/chunk-SCTX5Z7I.js +0 -8
  50. package/dist/chunk-WZGMAXKC.js +0 -8
@@ -0,0 +1,41 @@
1
+ declare const __DEV__: boolean
2
+
3
+ // The core package intentionally has no Node types — declare the minimal
4
+ // shape needed for the Jest detection below (guarded by a typeof check, so
5
+ // environments without `process` are fine).
6
+ declare const process: { env?: Record<string, string | undefined> } | undefined
7
+
8
+ const warned = new Set<string>()
9
+
10
+ /**
11
+ * Dev-only, once-per-key warning for plain (non-worklet) functions handed to
12
+ * surfaces that need real worklets — `useTransform` transformers and custom
13
+ * `timing.easing` functions.
14
+ *
15
+ * Why this can't be papered over with an auto-wrap: wrapping a plain
16
+ * function in a `'worklet'`-directive closure captures the *function
17
+ * reference*, not the shared values it reads. Even when the consumer's
18
+ * Babel plugin workletizes the wrapper, its `__closure` contains only the
19
+ * opaque JS function — Reanimated cannot extract the shared values read
20
+ * inside it as dependencies (so derived values never refresh), and native
21
+ * builds reject the plain function when the closure is serialized to the UI
22
+ * thread. The only correct authoring is the `'worklet'` directive on the
23
+ * consumer's own function, where *their* Babel pass captures the real
24
+ * closure.
25
+ *
26
+ * Suppressed under Jest: the shared Reanimated/worklets test stubs report
27
+ * every function as non-worklet, which would turn the warning into noise in
28
+ * every consumer's test suite.
29
+ */
30
+ export function warnNonWorkletOnce(key: string, message: string): void {
31
+ if (!__DEV__) return
32
+ if (typeof process !== 'undefined' && process.env?.JEST_WORKER_ID) return
33
+ if (warned.has(key)) return
34
+ warned.add(key)
35
+ console.warn(message)
36
+ }
37
+
38
+ /** @internal — test-only hook to reset the once-per-key state. */
39
+ export function __resetNonWorkletWarningsForTests(): void {
40
+ warned.clear()
41
+ }
@@ -13,8 +13,9 @@ export type LayoutProp = boolean | TransitionConfig | undefined
13
13
  * - `true` → default spring with the library's tuned tension / friction / mass.
14
14
  * - `{ type: 'spring', ... }` → spring with react-spring vocabulary, bridged
15
15
  * into `springify().damping().stiffness().mass()` via `springToReanimated`.
16
- * - `{ type: 'timing', ... }` → `.duration().easing()`. User easing fns are
17
- * auto-wrapped as worklets (Reanimated 3.9+ validates this).
16
+ * - `{ type: 'timing', ... }` → `.duration().easing()`. Custom easing fns
17
+ * must be worklets (Reanimated 3.9+ validates this); plain functions
18
+ * dev-warn via `ensureWorkletEasing`.
18
19
  * - `{ type: 'decay', ... }` → silently downgrades to spring; decay doesn't
19
20
  * have a clear target for a layout transition.
20
21
  *
@@ -1,20 +1,21 @@
1
1
  // `isWorkletFunction` lives in `react-native-worklets` (the Reanimated 4 peer
2
2
  // dep); Reanimated's own re-export is deprecated.
3
3
  import { isWorkletFunction } from 'react-native-worklets'
4
+ import { warnNonWorkletOnce } from '../internal/nonWorkletWarning'
4
5
  import { type EasingInput } from '../types'
5
6
 
6
7
  /**
7
8
  * Reanimated 3.9+ validates that easing functions used in nested-transition
8
9
  * contexts (variants, sequences, per-property maps) are worklets, and crashes
9
- * with `[Reanimated] The easing function is not a worklet` otherwise. The
10
- * library accepts plain functions on the public surface; this helper wraps
11
- * them so consumers don't have to think about the worklet boundary.
10
+ * with `[Reanimated] The easing function is not a worklet` otherwise.
12
11
  *
13
- * If the input is already a worklet (has been processed by the worklets babel
14
- * plugin), it's returned as-is. Otherwise it's wrapped in a function whose
15
- * body declares the `'worklet'` directive when our source is processed by
16
- * the consumer's worklets babel plugin (the default Expo/RN setup), the
17
- * wrapper becomes a real worklet that captures the user fn via closure.
12
+ * Custom easing functions MUST therefore be worklets put the `'worklet'`
13
+ * directive as the function's first statement (Reanimated's built-in
14
+ * `Easing.*` helpers and inertia's `cubicBezier()` already are). A plain
15
+ * function warns in dev and falls back to a directive-wrapped call-through;
16
+ * that wrapper works on web (single-threaded) but its closure holds the
17
+ * opaque plain function, which native builds reject when the transition's
18
+ * config is serialized to the UI thread.
18
19
  *
19
20
  * Reanimated 4 changed `Easing.bezier(...)` to return an
20
21
  * `EasingFunctionFactory` (`{ factory: () => EasingFunction }`) rather than
@@ -34,6 +35,10 @@ export function ensureWorkletEasing(
34
35
  // ends up in the transition config.
35
36
  const fn = isEasingFactory(easing) ? easing.factory() : easing
36
37
  if (isWorkletFunction(fn)) return fn
38
+ warnNonWorkletOnce(
39
+ 'timing-easing',
40
+ "[inertia] timing easing: the provided easing function is not a worklet. The fallback wrapper works on web but native builds reject it when the transition runs on the UI thread. Add the 'worklet' directive as the first statement of the easing function, or use Reanimated's Easing.* helpers / inertia's cubicBezier(), which are already worklets.",
41
+ )
37
42
  const wrapped = (t: number) => {
38
43
  'worklet'
39
44
  return fn(t)
@@ -20,6 +20,7 @@ export {
20
20
  export { useScroll, type UseScrollResult } from './useScroll'
21
21
  export {
22
22
  useShadow,
23
+ type BoxShadowLayer,
23
24
  type ShadowConfig,
24
25
  type UseShadowOptions,
25
26
  } from './useShadow'
@@ -4,12 +4,20 @@ import {
4
4
  useAnimatedStyle,
5
5
  type SharedValue,
6
6
  } from 'react-native-reanimated'
7
+ import {
8
+ pairBoxShadowLayers,
9
+ resolveBoxShadowInput,
10
+ type BoxShadowLayer,
11
+ } from '../internal/boxShadow'
12
+
13
+ export type { BoxShadowLayer }
7
14
 
8
15
  /**
9
16
  * Shape accepted on either end of a `useShadow` tween. Every field is
10
17
  * optional — only keys present on at least one side participate in the
11
18
  * output style. Mirrors the flat shadow keys on `Motion.View`'s `animate`
12
- * surface, plus the nested `shadowOffset` source.
19
+ * surface, plus the nested `shadowOffset` source and the CSS `boxShadow`
20
+ * surface.
13
21
  */
14
22
  export interface ShadowConfig {
15
23
  shadowOpacity?: number
@@ -18,6 +26,19 @@ export interface ShadowConfig {
18
26
  /** Android elevation. iOS shadow consumers can leave this off. */
19
27
  elevation?: number
20
28
  shadowColor?: string
29
+ /**
30
+ * CSS `box-shadow` — the shadow surface on web (react-native-web passes
31
+ * it through as CSS) and on React Native 0.76+ new-architecture native.
32
+ * Accepts the CSS string form design systems store elevation tokens in
33
+ * (`'0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)'`;
34
+ * px lengths only) or structured layers. Multi-layer shadows interpolate
35
+ * per layer; when one side has fewer layers, it is padded with invisible
36
+ * layers, CSS-transition style. A malformed string **throws** at render
37
+ * (like `cubicBezier` — token mistakes should fail loudly at setup).
38
+ * The classic `shadow*`/`elevation` keys don't reach the web renderer —
39
+ * provide `boxShadow` alongside them when the tween must show up there.
40
+ */
41
+ boxShadow?: string | readonly BoxShadowLayer[]
21
42
  }
22
43
 
23
44
  export interface UseShadowOptions {
@@ -56,6 +77,19 @@ export interface UseShadowOptions {
56
77
  * `shadowColor`, `{ width: 0, height: 0 }` for `shadowOffset`). This is a
57
78
  * pure interpolator — to "animate" the shadow, drive `progress` with a
58
79
  * spring, timing, or gesture upstream.
80
+ *
81
+ * The classic `shadow*`/`elevation` keys don't render on web. When the
82
+ * tween must show up there (or on RN 0.76+ new-arch native via the CSS
83
+ * shadow model), provide `boxShadow` on both ends — CSS string tokens or
84
+ * structured layers; multi-layer shadows interpolate per layer:
85
+ *
86
+ * ```tsx
87
+ * const shadowStyle = useShadow({
88
+ * from: { boxShadow: theme.elevation.level1 }, // '0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)'
89
+ * to: { boxShadow: theme.elevation.level2 },
90
+ * progress,
91
+ * })
92
+ * ```
59
93
  */
60
94
  export function useShadow({
61
95
  from,
@@ -76,6 +110,17 @@ export function useShadow({
76
110
  const hasOffset =
77
111
  from.shadowOffset !== undefined || to.shadowOffset !== undefined
78
112
 
113
+ // boxShadow layers: parse/pair once on the JS thread into flat records so
114
+ // the worklet only interpolates numbers/colors and concatenates — no
115
+ // frame-time parsing. `[]` when neither side provides the key.
116
+ const boxShadowPairs =
117
+ from.boxShadow !== undefined || to.boxShadow !== undefined
118
+ ? pairBoxShadowLayers(
119
+ resolveBoxShadowInput(from.boxShadow),
120
+ resolveBoxShadowInput(to.boxShadow),
121
+ )
122
+ : []
123
+
79
124
  const opacityFrom = from.shadowOpacity ?? 0
80
125
  const opacityTo = to.shadowOpacity ?? 0
81
126
  const radiusFrom = from.shadowRadius ?? 0
@@ -111,6 +156,36 @@ export function useShadow({
111
156
  height: interpolate(t, [0, 1], [offsetHFrom, offsetHTo]),
112
157
  }
113
158
  }
159
+ if (boxShadowPairs.length > 0) {
160
+ let css = ''
161
+ let first = true
162
+ for (const pair of boxShadowPairs) {
163
+ const x = interpolate(t, [0, 1], [pair.from.offsetX, pair.to.offsetX])
164
+ const y = interpolate(t, [0, 1], [pair.from.offsetY, pair.to.offsetY])
165
+ // Blur can't go negative (invalid CSS) even if a springy driver
166
+ // overshoots below 0.
167
+ const blur = Math.max(
168
+ 0,
169
+ interpolate(t, [0, 1], [pair.from.blurRadius, pair.to.blurRadius]),
170
+ )
171
+ const spread = interpolate(
172
+ t,
173
+ [0, 1],
174
+ [pair.from.spreadDistance, pair.to.spreadDistance],
175
+ )
176
+ const color = interpolateColor(
177
+ t,
178
+ [0, 1],
179
+ [pair.from.color, pair.to.color],
180
+ )
181
+ css +=
182
+ (first ? '' : ', ') +
183
+ (pair.from.inset ? 'inset ' : '') +
184
+ `${x}px ${y}px ${blur}px ${spread}px ${color}`
185
+ first = false
186
+ }
187
+ out.boxShadow = css
188
+ }
114
189
  return out
115
190
  })
116
191
  }
@@ -10,6 +10,7 @@ import {
10
10
  // `react-native-worklets` is a required peer of Reanimated 4, so the direct
11
11
  // import is always available wherever Inertia is.
12
12
  import { isWorkletFunction } from 'react-native-worklets'
13
+ import { warnNonWorkletOnce } from '../internal/nonWorkletWarning'
13
14
 
14
15
  /**
15
16
  * Extrapolation behavior at the edges of the input range. Mirrors
@@ -36,9 +37,17 @@ export interface UseTransformOptions {
36
37
  * const distance = useTransform(() => Math.sqrt(x.value ** 2 + y.value ** 2))
37
38
  * ```
38
39
  *
39
- * The transformer must be a worklet (or a plain function we auto-wrap
40
- * see the easing wrapper for the rationale). It runs on the UI thread on
41
- * every frame where any read shared value changes.
40
+ * The transformer MUST be a worklet put the `'worklet'` directive as its
41
+ * first statement so the consumer's Babel plugin captures the shared values
42
+ * it reads as its closure. It runs on the UI thread on every frame where
43
+ * any read shared value changes.
44
+ *
45
+ * A plain function cannot work here, and the hook warns in dev when it gets
46
+ * one: the best-effort wrapper it falls back to closes over the opaque
47
+ * function reference, not the shared values read inside it, so Reanimated
48
+ * can't track them as dependencies — the derived value only refreshes on
49
+ * React re-renders, and native builds reject the plain function when the
50
+ * closure crosses to the UI thread.
42
51
  */
43
52
  export function useTransform<T>(transformer: () => T): SharedValue<T>
44
53
 
@@ -80,16 +89,23 @@ export function useTransform<T>(
80
89
  // interpolation) is decided once at JS time, never at frame time.
81
90
  let producer: () => unknown
82
91
  if (typeof arg1 === 'function') {
83
- // Transformer overload. The public surface accepts a plain function;
84
- // Reanimated 3.9+ requires worklets in nested-derivation contexts, so
85
- // we auto-wrap at JS time the same way `ensureWorkletEasing` does.
92
+ // Transformer overload. Must be a worklet the directive-wrapped
93
+ // fallback below is best-effort only (its closure holds the opaque
94
+ // function, not the shared values read inside it, so dependency
95
+ // tracking cannot work — see `warnNonWorkletOnce`).
86
96
  const userFn = arg1 as () => T
87
- producer = isWorkletFunction(userFn)
88
- ? userFn
89
- : () => {
90
- 'worklet'
91
- return userFn()
92
- }
97
+ if (isWorkletFunction(userFn)) {
98
+ producer = userFn
99
+ } else {
100
+ warnNonWorkletOnce(
101
+ 'useTransform-transformer',
102
+ "[inertia] useTransform: the transformer is not a worklet, so the shared values it reads can't be tracked as dependencies — the derived value will only refresh on React re-renders, and native builds reject plain functions on the UI thread. Add the 'worklet' directive as the first statement of the transformer.",
103
+ )
104
+ producer = () => {
105
+ 'worklet'
106
+ return userFn()
107
+ }
108
+ }
93
109
  } else {
94
110
  // Interpolation overload. We pre-resolve everything JS-side so the
95
111
  // worklet body only consumes flat values.
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6UQ4KA6V_js = require('./chunk-6UQ4KA6V.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionImage = chunk6UQ4KA6V_js.createMotionComponent(reactNative.Image);
7
-
8
- exports.MotionImage = MotionImage;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6UQ4KA6V_js = require('./chunk-6UQ4KA6V.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionView = chunk6UQ4KA6V_js.createMotionComponent(reactNative.View);
7
-
8
- exports.MotionView = MotionView;
@@ -1,8 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk6UQ4KA6V_js = require('./chunk-6UQ4KA6V.js');
4
- var reactNative = require('react-native');
5
-
6
- var MotionText = chunk6UQ4KA6V_js.createMotionComponent(reactNative.Text);
7
-
8
- exports.MotionText = MotionText;