@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
package/dist/index.mjs CHANGED
@@ -1,19 +1,19 @@
1
- export { useGesture } from './chunk-ZQUCQRZT.mjs';
2
- import { MotionView } from './chunk-WPPLZNM4.mjs';
3
- export { MotionView } from './chunk-WPPLZNM4.mjs';
4
- import { MotionText } from './chunk-IJNVUM5U.mjs';
5
- export { MotionText } from './chunk-IJNVUM5U.mjs';
6
- import { MotionImage } from './chunk-K4KR5BIS.mjs';
7
- export { MotionImage } from './chunk-K4KR5BIS.mjs';
8
- import { MotionPressable } from './chunk-OBE7KTMK.mjs';
9
- export { MotionPressable } from './chunk-OBE7KTMK.mjs';
10
- import { MotionScrollView } from './chunk-QQJSBIXV.mjs';
11
- export { MotionScrollView } from './chunk-QQJSBIXV.mjs';
12
- export { Presence, createMotionComponent, usePresence } from './chunk-I76OC6RX.mjs';
13
- import { useShouldReduceMotion, resolveNamedTransition, useNamedTransitions, lookupNamedTransition } from './chunk-L2EVRKSC.mjs';
14
- export { MotionConfig, resolveNamedTransition, useMotionConfig, useNamedTransitions, useShouldReduceMotion } from './chunk-L2EVRKSC.mjs';
15
- import { stableSig, resolveTransition, springToReanimated } from './chunk-6FENLMCA.mjs';
16
- export { buildReleaseAnimation, cubicBezier, ensureWorkletEasing, resolveAnimatableValue, resolveTransition } from './chunk-6FENLMCA.mjs';
1
+ export { useGesture } from './chunk-RGNX6UZN.mjs';
2
+ import { MotionView } from './chunk-6SMPIOIC.mjs';
3
+ export { MotionView } from './chunk-6SMPIOIC.mjs';
4
+ import { MotionText } from './chunk-JVBXPF2G.mjs';
5
+ export { MotionText } from './chunk-JVBXPF2G.mjs';
6
+ import { MotionImage } from './chunk-ALRHDFZE.mjs';
7
+ export { MotionImage } from './chunk-ALRHDFZE.mjs';
8
+ import { MotionPressable } from './chunk-NXDJZD6A.mjs';
9
+ export { MotionPressable } from './chunk-NXDJZD6A.mjs';
10
+ import { MotionScrollView } from './chunk-CWLFUYIY.mjs';
11
+ export { MotionScrollView } from './chunk-CWLFUYIY.mjs';
12
+ export { Presence, createMotionComponent, usePresence } from './chunk-DWCLIBYO.mjs';
13
+ import { useShouldReduceMotion, resolveNamedTransition, useNamedTransitions, lookupNamedTransition } from './chunk-R63GIUNU.mjs';
14
+ export { MotionConfig, resolveNamedTransition, useMotionConfig, useNamedTransitions, useShouldReduceMotion } from './chunk-R63GIUNU.mjs';
15
+ import { stableSig, resolveTransition, springToReanimated, warnNonWorkletOnce } from './chunk-CY7Y64C3.mjs';
16
+ export { buildReleaseAnimation, cubicBezier, ensureWorkletEasing, resolveAnimatableValue, resolveTransition } from './chunk-CY7Y64C3.mjs';
17
17
  import { useEffect, useMemo, useRef } from 'react';
18
18
  import { useSharedValue, withSpring, useAnimatedReaction, useAnimatedStyle, interpolateColor, useDerivedValue, Extrapolation, useAnimatedScrollHandler, interpolate } from 'react-native-reanimated';
19
19
  import { isWorkletFunction } from 'react-native-worklets';
@@ -112,10 +112,18 @@ function useTransform(arg1, inputRange, outputRange, options) {
112
112
  let producer;
113
113
  if (typeof arg1 === "function") {
114
114
  const userFn = arg1;
115
- producer = isWorkletFunction(userFn) ? userFn : () => {
116
- "worklet";
117
- return userFn();
118
- };
115
+ if (isWorkletFunction(userFn)) {
116
+ producer = userFn;
117
+ } else {
118
+ warnNonWorkletOnce(
119
+ "useTransform-transformer",
120
+ "[inertia] useTransform: the transformer is not a worklet, so the shared values it reads can't be tracked as dependencies \u2014 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."
121
+ );
122
+ producer = () => {
123
+ "worklet";
124
+ return userFn();
125
+ };
126
+ }
119
127
  } else {
120
128
  const source = arg1;
121
129
  const input = inputRange;
@@ -163,6 +171,127 @@ function useScroll() {
163
171
  onScroll: handler
164
172
  };
165
173
  }
174
+
175
+ // src/internal/boxShadow.ts
176
+ var LENGTH = /^[+-]?(\d+\.?\d*|\.\d+)(px)?$/i;
177
+ var UNIT_LIKE = /^[+-]?(\d+\.?\d*|\.\d+)[a-z%]+$/i;
178
+ function invalid(input, reason) {
179
+ return new Error(
180
+ `[inertia] parseBoxShadow: ${reason} in ${JSON.stringify(input)}. Expected CSS box-shadow syntax with px lengths: '[inset] <offset-x> <offset-y> [blur] [spread] [color], ...'`
181
+ );
182
+ }
183
+ function splitLayers(input) {
184
+ const layers = [];
185
+ let depth = 0;
186
+ let start = 0;
187
+ for (let i = 0; i < input.length; i++) {
188
+ const ch = input[i];
189
+ if (ch === "(") depth++;
190
+ else if (ch === ")") depth--;
191
+ else if (ch === "," && depth === 0) {
192
+ layers.push(input.slice(start, i));
193
+ start = i + 1;
194
+ }
195
+ }
196
+ layers.push(input.slice(start));
197
+ return layers;
198
+ }
199
+ function tokenize(layer) {
200
+ const tokens = [];
201
+ let depth = 0;
202
+ let current = "";
203
+ for (const ch of layer) {
204
+ if (ch === "(") depth++;
205
+ else if (ch === ")") depth--;
206
+ if (depth === 0 && /\s/.test(ch)) {
207
+ if (current) tokens.push(current);
208
+ current = "";
209
+ } else {
210
+ current += ch;
211
+ }
212
+ }
213
+ if (current) tokens.push(current);
214
+ return tokens;
215
+ }
216
+ function parseBoxShadow(input) {
217
+ const trimmed = input.trim();
218
+ if (trimmed === "") throw invalid(input, "empty string");
219
+ if (trimmed.toLowerCase() === "none") return [];
220
+ return splitLayers(trimmed).map((layerString) => {
221
+ const tokens = tokenize(layerString.trim());
222
+ if (tokens.length === 0) throw invalid(input, "empty layer");
223
+ const lengths = [];
224
+ let color;
225
+ let inset = false;
226
+ for (const token of tokens) {
227
+ if (token.toLowerCase() === "inset") {
228
+ if (inset) throw invalid(input, "duplicate 'inset'");
229
+ inset = true;
230
+ } else if (LENGTH.test(token)) {
231
+ lengths.push(parseFloat(token));
232
+ } else if (UNIT_LIKE.test(token)) {
233
+ throw invalid(input, `unsupported unit in ${JSON.stringify(token)}`);
234
+ } else {
235
+ if (color !== void 0) throw invalid(input, "multiple colors");
236
+ color = token;
237
+ }
238
+ }
239
+ if (lengths.length < 2 || lengths.length > 4) {
240
+ throw invalid(input, `expected 2-4 lengths, got ${lengths.length}`);
241
+ }
242
+ const [offsetX = 0, offsetY = 0, blurRadius = 0, spreadDistance = 0] = lengths;
243
+ if (blurRadius < 0) throw invalid(input, "negative blur radius");
244
+ return {
245
+ offsetX,
246
+ offsetY,
247
+ blurRadius,
248
+ spreadDistance,
249
+ color: color ?? "black",
250
+ inset
251
+ };
252
+ });
253
+ }
254
+ function resolveBoxShadowInput(input) {
255
+ if (input === void 0) return [];
256
+ if (typeof input === "string") return parseBoxShadow(input);
257
+ return input.map((layer) => ({
258
+ offsetX: layer.offsetX,
259
+ offsetY: layer.offsetY,
260
+ blurRadius: layer.blurRadius ?? 0,
261
+ spreadDistance: layer.spreadDistance ?? 0,
262
+ color: layer.color ?? "black",
263
+ inset: layer.inset ?? false
264
+ }));
265
+ }
266
+ function pairBoxShadowLayers(from, to) {
267
+ const count = Math.max(from.length, to.length);
268
+ const pairs = [];
269
+ for (let i = 0; i < count; i++) {
270
+ const a = from[i];
271
+ const b = to[i];
272
+ const fromLayer = a ?? invisibleLayer(b.inset);
273
+ const toLayer = b ?? invisibleLayer(a.inset);
274
+ if (fromLayer.inset !== toLayer.inset) {
275
+ throw new Error(
276
+ `[inertia] useShadow: boxShadow layer ${i} is 'inset' on one side but not the other \u2014 inset cannot be interpolated. Give both sides the same inset-ness (pad with a transparent layer if needed).`
277
+ );
278
+ }
279
+ pairs.push({ from: fromLayer, to: toLayer });
280
+ }
281
+ return pairs;
282
+ }
283
+ function invisibleLayer(inset) {
284
+ return {
285
+ offsetX: 0,
286
+ offsetY: 0,
287
+ blurRadius: 0,
288
+ spreadDistance: 0,
289
+ color: "transparent",
290
+ inset
291
+ };
292
+ }
293
+
294
+ // src/values/useShadow.ts
166
295
  function useShadow({
167
296
  from,
168
297
  to,
@@ -173,6 +302,10 @@ function useShadow({
173
302
  const hasElevation = from.elevation !== void 0 || to.elevation !== void 0;
174
303
  const hasColor = from.shadowColor !== void 0 || to.shadowColor !== void 0;
175
304
  const hasOffset = from.shadowOffset !== void 0 || to.shadowOffset !== void 0;
305
+ const boxShadowPairs = from.boxShadow !== void 0 || to.boxShadow !== void 0 ? pairBoxShadowLayers(
306
+ resolveBoxShadowInput(from.boxShadow),
307
+ resolveBoxShadowInput(to.boxShadow)
308
+ ) : [];
176
309
  const opacityFrom = from.shadowOpacity ?? 0;
177
310
  const opacityTo = to.shadowOpacity ?? 0;
178
311
  const radiusFrom = from.shadowRadius ?? 0;
@@ -207,6 +340,31 @@ function useShadow({
207
340
  height: interpolate(t, [0, 1], [offsetHFrom, offsetHTo])
208
341
  };
209
342
  }
343
+ if (boxShadowPairs.length > 0) {
344
+ let css = "";
345
+ let first = true;
346
+ for (const pair of boxShadowPairs) {
347
+ const x = interpolate(t, [0, 1], [pair.from.offsetX, pair.to.offsetX]);
348
+ const y = interpolate(t, [0, 1], [pair.from.offsetY, pair.to.offsetY]);
349
+ const blur = Math.max(
350
+ 0,
351
+ interpolate(t, [0, 1], [pair.from.blurRadius, pair.to.blurRadius])
352
+ );
353
+ const spread = interpolate(
354
+ t,
355
+ [0, 1],
356
+ [pair.from.spreadDistance, pair.to.spreadDistance]
357
+ );
358
+ const color = interpolateColor(
359
+ t,
360
+ [0, 1],
361
+ [pair.from.color, pair.to.color]
362
+ );
363
+ css += (first ? "" : ", ") + (pair.from.inset ? "inset " : "") + `${x}px ${y}px ${blur}px ${spread}px ${color}`;
364
+ first = false;
365
+ }
366
+ out.boxShadow = css;
367
+ }
210
368
  return out;
211
369
  });
212
370
  }
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkNDKVHL3N_js = require('../chunk-NDKVHL3N.js');
4
- require('../chunk-6UQ4KA6V.js');
5
- require('../chunk-6EP3S2PN.js');
6
- require('../chunk-SFRNO6AW.js');
3
+ var chunk3UTJJ4A3_js = require('../chunk-3UTJJ4A3.js');
4
+ require('../chunk-7UDYEFBU.js');
5
+ require('../chunk-7AOERN53.js');
6
+ require('../chunk-PTRF47DA.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionImage", {
11
11
  enumerable: true,
12
- get: function () { return chunkNDKVHL3N_js.MotionImage; }
12
+ get: function () { return chunk3UTJJ4A3_js.MotionImage; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionImage } from '../chunk-K4KR5BIS.mjs';
2
- import '../chunk-I76OC6RX.mjs';
3
- import '../chunk-L2EVRKSC.mjs';
4
- import '../chunk-6FENLMCA.mjs';
1
+ export { MotionImage } from '../chunk-ALRHDFZE.mjs';
2
+ import '../chunk-DWCLIBYO.mjs';
3
+ import '../chunk-R63GIUNU.mjs';
4
+ import '../chunk-CY7Y64C3.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkOM5FXU4I_js = require('../chunk-OM5FXU4I.js');
4
- require('../chunk-6UQ4KA6V.js');
5
- require('../chunk-6EP3S2PN.js');
6
- require('../chunk-SFRNO6AW.js');
3
+ var chunkTDSO63CJ_js = require('../chunk-TDSO63CJ.js');
4
+ require('../chunk-7UDYEFBU.js');
5
+ require('../chunk-7AOERN53.js');
6
+ require('../chunk-PTRF47DA.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionPressable", {
11
11
  enumerable: true,
12
- get: function () { return chunkOM5FXU4I_js.MotionPressable; }
12
+ get: function () { return chunkTDSO63CJ_js.MotionPressable; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionPressable } from '../chunk-OBE7KTMK.mjs';
2
- import '../chunk-I76OC6RX.mjs';
3
- import '../chunk-L2EVRKSC.mjs';
4
- import '../chunk-6FENLMCA.mjs';
1
+ export { MotionPressable } from '../chunk-NXDJZD6A.mjs';
2
+ import '../chunk-DWCLIBYO.mjs';
3
+ import '../chunk-R63GIUNU.mjs';
4
+ import '../chunk-CY7Y64C3.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunk5I3G43XA_js = require('../chunk-5I3G43XA.js');
4
- require('../chunk-6UQ4KA6V.js');
5
- require('../chunk-6EP3S2PN.js');
6
- require('../chunk-SFRNO6AW.js');
3
+ var chunk2HYD2ZBK_js = require('../chunk-2HYD2ZBK.js');
4
+ require('../chunk-7UDYEFBU.js');
5
+ require('../chunk-7AOERN53.js');
6
+ require('../chunk-PTRF47DA.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionScrollView", {
11
11
  enumerable: true,
12
- get: function () { return chunk5I3G43XA_js.MotionScrollView; }
12
+ get: function () { return chunk2HYD2ZBK_js.MotionScrollView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionScrollView } from '../chunk-QQJSBIXV.mjs';
2
- import '../chunk-I76OC6RX.mjs';
3
- import '../chunk-L2EVRKSC.mjs';
4
- import '../chunk-6FENLMCA.mjs';
1
+ export { MotionScrollView } from '../chunk-CWLFUYIY.mjs';
2
+ import '../chunk-DWCLIBYO.mjs';
3
+ import '../chunk-R63GIUNU.mjs';
4
+ import '../chunk-CY7Y64C3.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkWZGMAXKC_js = require('../chunk-WZGMAXKC.js');
4
- require('../chunk-6UQ4KA6V.js');
5
- require('../chunk-6EP3S2PN.js');
6
- require('../chunk-SFRNO6AW.js');
3
+ var chunkZ7HIOFKQ_js = require('../chunk-Z7HIOFKQ.js');
4
+ require('../chunk-7UDYEFBU.js');
5
+ require('../chunk-7AOERN53.js');
6
+ require('../chunk-PTRF47DA.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionText", {
11
11
  enumerable: true,
12
- get: function () { return chunkWZGMAXKC_js.MotionText; }
12
+ get: function () { return chunkZ7HIOFKQ_js.MotionText; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionText } from '../chunk-IJNVUM5U.mjs';
2
- import '../chunk-I76OC6RX.mjs';
3
- import '../chunk-L2EVRKSC.mjs';
4
- import '../chunk-6FENLMCA.mjs';
1
+ export { MotionText } from '../chunk-JVBXPF2G.mjs';
2
+ import '../chunk-DWCLIBYO.mjs';
3
+ import '../chunk-R63GIUNU.mjs';
4
+ import '../chunk-CY7Y64C3.mjs';
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunkSCTX5Z7I_js = require('../chunk-SCTX5Z7I.js');
4
- require('../chunk-6UQ4KA6V.js');
5
- require('../chunk-6EP3S2PN.js');
6
- require('../chunk-SFRNO6AW.js');
3
+ var chunk4QGXK6TF_js = require('../chunk-4QGXK6TF.js');
4
+ require('../chunk-7UDYEFBU.js');
5
+ require('../chunk-7AOERN53.js');
6
+ require('../chunk-PTRF47DA.js');
7
7
 
8
8
 
9
9
 
10
10
  Object.defineProperty(exports, "MotionView", {
11
11
  enumerable: true,
12
- get: function () { return chunkSCTX5Z7I_js.MotionView; }
12
+ get: function () { return chunk4QGXK6TF_js.MotionView; }
13
13
  });
@@ -1,4 +1,4 @@
1
- export { MotionView } from '../chunk-WPPLZNM4.mjs';
2
- import '../chunk-I76OC6RX.mjs';
3
- import '../chunk-L2EVRKSC.mjs';
4
- import '../chunk-6FENLMCA.mjs';
1
+ export { MotionView } from '../chunk-6SMPIOIC.mjs';
2
+ import '../chunk-DWCLIBYO.mjs';
3
+ import '../chunk-R63GIUNU.mjs';
4
+ import '../chunk-CY7Y64C3.mjs';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkSFRNO6AW_js = require('../chunk-SFRNO6AW.js');
3
+ var chunkPTRF47DA_js = require('../chunk-PTRF47DA.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 = chunkSFRNO6AW_js.buildReleaseAnimation(
51
+ dragX.value = chunkPTRF47DA_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 = chunkSFRNO6AW_js.buildReleaseAnimation(
58
+ dragY.value = chunkPTRF47DA_js.buildReleaseAnimation(
59
59
  result.y,
60
60
  toY
61
61
  );
@@ -1,4 +1,4 @@
1
- import { buildReleaseAnimation } from '../chunk-6FENLMCA.mjs';
1
+ import { buildReleaseAnimation } from '../chunk-CY7Y64C3.mjs';
2
2
  import { useMemo } from 'react';
3
3
  import { PanResponder } from 'react-native';
4
4
  import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
package/llms.txt CHANGED
@@ -51,7 +51,7 @@ import { MotionScrollView } from '@rootnative/inertia/scroll-view'
51
51
  - `useSpring(target, config?)` — spring-only shorthand. Animates a `SharedValue<number>` toward `target` with react-spring vocab (or a spring-typed registered `TransitionName`; non-spring names warn and fall back to the default spring). `target` may be a plain number (effect-driven) or a `SharedValue<number>` (UI-thread reaction); the latter is the gesture-smoothing path.
52
52
  - `useBooleanSpring(active, config?)` — sugar over `useSpring` for the recurring "spring 0↔1 progress from a boolean" shape (checkbox checks, accordion expansions, drawer open/closed). Identical mechanics to `useSpring(active ? 1 : 0, config)` — the named form so the call site reads as the boolean it represents.
53
53
  - `useTransform(value, inputRange, outputRange, options?)` / `useTransform(transformer)` — interpolate a numeric shared value onto a number or color range, or derive any value from any number of shared values via a worklet. Non-worklet transformers are auto-wrapped.
54
- - `useShadow({ from, to, progress })` — pure value-layer interpolator between two `ShadowConfig`s (`shadowOpacity` / `shadowRadius` / `shadowOffset` / `elevation` / `shadowColor`) driven by a `SharedValue<number>` (0→1). Returns an animated style fragment to spread onto `style`; only emits keys present on either side, absent sides default to natural zero. The hook does not animate on its own — drive `progress` with a spring, a scroll-derived `useTransform`, or any other shared value source.
54
+ - `useShadow({ from, to, progress })` — pure value-layer interpolator between two `ShadowConfig`s (`shadowOpacity` / `shadowRadius` / `shadowOffset` / `elevation` / `shadowColor`, plus `boxShadow` as a CSS string or `BoxShadowLayer[]` — the shadow surface on web and RN 0.76+ new-arch; multi-layer, CSS-transition padding semantics, malformed strings throw at setup) driven by a `SharedValue<number>` (0→1). Returns an animated style fragment to spread onto `style`; only emits keys present on either side, absent sides default to natural zero. The hook does not animate on its own — drive `progress` with a spring, a scroll-derived `useTransform`, or any other shared value source.
55
55
  - `useColorTransition(progress, [from, to], options?)` — pure value-layer interpolator for a single color channel, driven by a `SharedValue<number>` (0→1). Returns an animated style fragment with one color key (default `backgroundColor`; configurable via `options.key` to `color` / `borderColor` / `tintColor` / `shadowColor` / per-side border colors). For raw `SharedValue<string>` output, use `useTransform(progress, [0, 1], [from, to])` instead.
56
56
  - `useScroll()` — returns `{ scrollX, scrollY, onScroll }` for use with `Motion.ScrollView`. Scroll events fire on the UI thread.
57
57
  - `createMotionComponent<C>(C)` — wrap any component with the same Motion prop surface, inferring style from `C`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rootnative/inertia",
3
- "version": "0.0.0-alpha.4",
3
+ "version": "0.0.0-alpha.6",
4
4
  "description": "Declarative animation primitives for React Native, built on react-native-reanimated.",
5
5
  "license": "MIT",
6
6
  "author": "RootNative",
@@ -142,7 +142,7 @@
142
142
  "scripts": {
143
143
  "build": "tsup",
144
144
  "dev": "tsup --watch",
145
- "typecheck": "tsc --noEmit",
145
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
146
146
  "test": "jest",
147
147
  "size": "size-limit",
148
148
  "size:why": "size-limit --why",
package/src/index.ts CHANGED
@@ -52,6 +52,7 @@ export {
52
52
  useVariants,
53
53
  } from './values'
54
54
  export type {
55
+ BoxShadowLayer,
55
56
  ColorStyleKey,
56
57
  ExtrapolationMode,
57
58
  ShadowConfig,
@@ -0,0 +1,204 @@
1
+ /**
2
+ * CSS `box-shadow` parsing + pairing for `useShadow`.
3
+ *
4
+ * Design systems store web elevation tokens as CSS `box-shadow` strings
5
+ * (`'0px 1px 2px rgba(0,0,0,0.3), 0px 1px 3px 1px rgba(0,0,0,0.15)'`).
6
+ * Like `cubicBezier`, this module makes those tokens directly consumable:
7
+ * strings are parsed once on the JS thread into flat layer records the
8
+ * `useShadow` worklet can interpolate without any frame-time string work.
9
+ *
10
+ * Invalid input **throws** rather than warning — shadow tokens are
11
+ * constructed at theme/module setup, and a malformed token should fail
12
+ * loudly there, not silently render the wrong elevation.
13
+ */
14
+
15
+ /**
16
+ * One layer of a `box-shadow`, structurally mirroring React Native's
17
+ * `BoxShadowValue` (RN 0.76+ `boxShadow` style). Lengths are px numbers.
18
+ */
19
+ export interface BoxShadowLayer {
20
+ offsetX: number
21
+ offsetY: number
22
+ /** Must be >= 0, per CSS. @default 0 */
23
+ blurRadius?: number
24
+ /** @default 0 */
25
+ spreadDistance?: number
26
+ /** Any color string Reanimated's `interpolateColor` accepts. @default 'black' */
27
+ color?: string
28
+ /** @default false */
29
+ inset?: boolean
30
+ }
31
+
32
+ /** A layer with every field resolved to a concrete value. */
33
+ export interface ResolvedBoxShadowLayer {
34
+ offsetX: number
35
+ offsetY: number
36
+ blurRadius: number
37
+ spreadDistance: number
38
+ color: string
39
+ inset: boolean
40
+ }
41
+
42
+ const LENGTH = /^[+-]?(\d+\.?\d*|\.\d+)(px)?$/i
43
+ const UNIT_LIKE = /^[+-]?(\d+\.?\d*|\.\d+)[a-z%]+$/i
44
+
45
+ function invalid(input: string, reason: string): Error {
46
+ return new Error(
47
+ `[inertia] parseBoxShadow: ${reason} in ${JSON.stringify(input)}. ` +
48
+ 'Expected CSS box-shadow syntax with px lengths: ' +
49
+ "'[inset] <offset-x> <offset-y> [blur] [spread] [color], ...'",
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Split a box-shadow string into layer strings on top-level commas —
55
+ * commas inside color functions (`rgba(0, 0, 0, 0.3)`) don't split.
56
+ */
57
+ function splitLayers(input: string): string[] {
58
+ const layers: string[] = []
59
+ let depth = 0
60
+ let start = 0
61
+ for (let i = 0; i < input.length; i++) {
62
+ const ch = input[i]
63
+ if (ch === '(') depth++
64
+ else if (ch === ')') depth--
65
+ else if (ch === ',' && depth === 0) {
66
+ layers.push(input.slice(start, i))
67
+ start = i + 1
68
+ }
69
+ }
70
+ layers.push(input.slice(start))
71
+ return layers
72
+ }
73
+
74
+ /**
75
+ * Split one layer into whitespace-separated tokens, keeping color
76
+ * functions (which may contain spaces: `rgb(0 0 0 / 40%)`) as one token.
77
+ */
78
+ function tokenize(layer: string): string[] {
79
+ const tokens: string[] = []
80
+ let depth = 0
81
+ let current = ''
82
+ for (const ch of layer) {
83
+ if (ch === '(') depth++
84
+ else if (ch === ')') depth--
85
+ if (depth === 0 && /\s/.test(ch)) {
86
+ if (current) tokens.push(current)
87
+ current = ''
88
+ } else {
89
+ current += ch
90
+ }
91
+ }
92
+ if (current) tokens.push(current)
93
+ return tokens
94
+ }
95
+
96
+ /**
97
+ * Parse a CSS `box-shadow` string into resolved layers. `'none'` parses to
98
+ * an empty list. Only px (and unitless) lengths are supported — other units
99
+ * depend on font/viewport context that a style value can't resolve.
100
+ */
101
+ export function parseBoxShadow(input: string): ResolvedBoxShadowLayer[] {
102
+ const trimmed = input.trim()
103
+ if (trimmed === '') throw invalid(input, 'empty string')
104
+ if (trimmed.toLowerCase() === 'none') return []
105
+
106
+ return splitLayers(trimmed).map((layerString) => {
107
+ const tokens = tokenize(layerString.trim())
108
+ if (tokens.length === 0) throw invalid(input, 'empty layer')
109
+
110
+ const lengths: number[] = []
111
+ let color: string | undefined
112
+ let inset = false
113
+ for (const token of tokens) {
114
+ if (token.toLowerCase() === 'inset') {
115
+ if (inset) throw invalid(input, "duplicate 'inset'")
116
+ inset = true
117
+ } else if (LENGTH.test(token)) {
118
+ lengths.push(parseFloat(token))
119
+ } else if (UNIT_LIKE.test(token)) {
120
+ throw invalid(input, `unsupported unit in ${JSON.stringify(token)}`)
121
+ } else {
122
+ if (color !== undefined) throw invalid(input, 'multiple colors')
123
+ color = token
124
+ }
125
+ }
126
+
127
+ if (lengths.length < 2 || lengths.length > 4) {
128
+ throw invalid(input, `expected 2-4 lengths, got ${lengths.length}`)
129
+ }
130
+ // The `= 0` on the offsets never fires (length >= 2 is validated
131
+ // above); it's here for noUncheckedIndexedAccess.
132
+ const [offsetX = 0, offsetY = 0, blurRadius = 0, spreadDistance = 0] =
133
+ lengths
134
+ if (blurRadius < 0) throw invalid(input, 'negative blur radius')
135
+
136
+ return {
137
+ offsetX,
138
+ offsetY,
139
+ blurRadius,
140
+ spreadDistance,
141
+ color: color ?? 'black',
142
+ inset,
143
+ }
144
+ })
145
+ }
146
+
147
+ /** Resolve either input form (CSS string or structured layers) to layers. */
148
+ export function resolveBoxShadowInput(
149
+ input: string | readonly BoxShadowLayer[] | undefined,
150
+ ): ResolvedBoxShadowLayer[] {
151
+ if (input === undefined) return []
152
+ if (typeof input === 'string') return parseBoxShadow(input)
153
+ return input.map((layer) => ({
154
+ offsetX: layer.offsetX,
155
+ offsetY: layer.offsetY,
156
+ blurRadius: layer.blurRadius ?? 0,
157
+ spreadDistance: layer.spreadDistance ?? 0,
158
+ color: layer.color ?? 'black',
159
+ inset: layer.inset ?? false,
160
+ }))
161
+ }
162
+
163
+ /**
164
+ * Pair up `from`/`to` layer lists for interpolation, CSS-transition style:
165
+ * the shorter list is padded with an invisible layer (all lengths 0,
166
+ * transparent color) matching the counterpart's `inset` flag. A genuine
167
+ * `inset` mismatch between paired layers is not interpolable and throws.
168
+ */
169
+ export function pairBoxShadowLayers(
170
+ from: ResolvedBoxShadowLayer[],
171
+ to: ResolvedBoxShadowLayer[],
172
+ ): Array<{ from: ResolvedBoxShadowLayer; to: ResolvedBoxShadowLayer }> {
173
+ const count = Math.max(from.length, to.length)
174
+ const pairs: Array<{
175
+ from: ResolvedBoxShadowLayer
176
+ to: ResolvedBoxShadowLayer
177
+ }> = []
178
+ for (let i = 0; i < count; i++) {
179
+ const a = from[i]
180
+ const b = to[i]
181
+ const fromLayer = a ?? invisibleLayer(b!.inset)
182
+ const toLayer = b ?? invisibleLayer(a!.inset)
183
+ if (fromLayer.inset !== toLayer.inset) {
184
+ throw new Error(
185
+ `[inertia] useShadow: boxShadow layer ${i} is 'inset' on one side ` +
186
+ 'but not the other — inset cannot be interpolated. Give both ' +
187
+ 'sides the same inset-ness (pad with a transparent layer if needed).',
188
+ )
189
+ }
190
+ pairs.push({ from: fromLayer, to: toLayer })
191
+ }
192
+ return pairs
193
+ }
194
+
195
+ function invisibleLayer(inset: boolean): ResolvedBoxShadowLayer {
196
+ return {
197
+ offsetX: 0,
198
+ offsetY: 0,
199
+ blurRadius: 0,
200
+ spreadDistance: 0,
201
+ color: 'transparent',
202
+ inset,
203
+ }
204
+ }