@rootnative/inertia 0.0.2 → 0.0.4

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 (77) hide show
  1. package/CHANGELOG.md +63 -5
  2. package/README.md +5 -3
  3. package/dist/{chunk-ZN3PNGHQ.mjs → chunk-4ASOELLA.mjs} +1 -1
  4. package/dist/{chunk-LYMLQ6WQ.mjs → chunk-55W7BNO2.mjs} +604 -106
  5. package/dist/chunk-5SRD2WCO.js +8 -0
  6. package/dist/{chunk-PTRF47DA.js → chunk-7A6WTXNK.js} +10 -3
  7. package/dist/{chunk-NKA6XR77.js → chunk-CENTULW6.js} +2 -2
  8. package/dist/{chunk-4PEHWDAZ.js → chunk-D5KAHWMC.js} +7 -7
  9. package/dist/{chunk-2DHKV2XY.mjs → chunk-J5Q46OPW.mjs} +1 -1
  10. package/dist/{chunk-RGNX6UZN.mjs → chunk-K3CLP74S.mjs} +2 -2
  11. package/dist/{chunk-IWLU3VDE.js → chunk-MYE5NMXW.js} +623 -123
  12. package/dist/{chunk-HFDZAHWP.mjs → chunk-NAOXQJ7U.mjs} +1 -1
  13. package/dist/{chunk-R63GIUNU.mjs → chunk-QKC3APP4.mjs} +1 -1
  14. package/dist/{chunk-V6BTXHZK.mjs → chunk-RF7J5FRM.mjs} +1 -1
  15. package/dist/{chunk-CY7Y64C3.mjs → chunk-TU2XFPAK.mjs} +10 -4
  16. package/dist/chunk-TVFNJ25Z.js +8 -0
  17. package/dist/chunk-VLDIFL3F.js +8 -0
  18. package/dist/{chunk-7AOERN53.js → chunk-WVRCKOPO.js} +3 -3
  19. package/dist/{chunk-TUYHTHVV.js → chunk-XMEVPEMH.js} +2 -2
  20. package/dist/{chunk-TUMNV4P7.mjs → chunk-YKGY22SB.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 +14 -12
  26. package/dist/index.d.ts +14 -12
  27. package/dist/index.js +68 -176
  28. package/dist/index.mjs +40 -147
  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-G1dBU2IQ.d.mts → types-37pN82yh.d.mts} +49 -14
  54. package/dist/{types-G1dBU2IQ.d.ts → types-37pN82yh.d.ts} +49 -14
  55. package/dist/{useGesture-D1e4ixJo.d.ts → useGesture-Cl4uzZIr.d.ts} +1 -1
  56. package/dist/{useGesture-RFY6kTL3.d.mts → useGesture-_IKkS8Dv.d.mts} +1 -1
  57. package/llms.txt +3 -1
  58. package/package.json +1 -1
  59. package/src/index.ts +2 -1
  60. package/src/internal/boxShadow.ts +177 -6
  61. package/src/internal/nonWorkletWarning.ts +4 -6
  62. package/src/internal/warnOnce.ts +28 -0
  63. package/src/layout/index.ts +11 -0
  64. package/src/layout/measureWindow.ts +135 -0
  65. package/src/layout/sharedRegistry.ts +156 -18
  66. package/src/layout/useSharedLayout.ts +256 -91
  67. package/src/motion/createMotionComponent.tsx +550 -55
  68. package/src/motion/installCheck.ts +1 -1
  69. package/src/presence/Presence.tsx +73 -10
  70. package/src/transitions/index.ts +1 -1
  71. package/src/types.ts +52 -12
  72. package/src/values/useAnimator.ts +25 -14
  73. package/src/values/useColorCascade.ts +34 -16
  74. package/src/values/useColorTransition.ts +1 -1
  75. package/dist/chunk-34Q4UM6V.js +0 -8
  76. package/dist/chunk-KYJROYCG.js +0 -8
  77. package/dist/chunk-X5J5M3K3.js +0 -8
@@ -1,8 +1,9 @@
1
- import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition, useShouldReduceMotion, isFocusVisible } from './chunk-R63GIUNU.mjs';
2
- import { stableSig, resolveAnimatableValue, isTopLevelTransition, resolveTransition, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-CY7Y64C3.mjs';
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';
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
+ import { StyleSheet } from 'react-native';
6
7
 
7
8
  var PresenceContext = createContext(null);
8
9
  function usePresence() {
@@ -29,6 +30,8 @@ function Presence({ children }) {
29
30
  () => /* @__PURE__ */ new Map()
30
31
  );
31
32
  const prevIncomingRef = useRef(incoming);
33
+ const orderRef = useRef([]);
34
+ let pendingExiting = null;
32
35
  if (prevIncomingRef.current !== incoming) {
33
36
  const prev = prevIncomingRef.current;
34
37
  prevIncomingRef.current = incoming;
@@ -50,8 +53,12 @@ function Presence({ children }) {
50
53
  ensureMutable().delete(key);
51
54
  }
52
55
  }
53
- if (next) setExiting(next);
56
+ if (next) {
57
+ pendingExiting = next;
58
+ setExiting(next);
59
+ }
54
60
  }
61
+ const activeExiting = pendingExiting ?? exiting;
55
62
  const handleRemove = useCallback((key) => {
56
63
  setExiting((prev) => {
57
64
  if (!prev.has(key)) return prev;
@@ -60,19 +67,42 @@ function Presence({ children }) {
60
67
  return next;
61
68
  });
62
69
  }, []);
63
- const renderList = [];
70
+ const byKey = /* @__PURE__ */ new Map();
71
+ const presentKeys = /* @__PURE__ */ new Set();
72
+ const order = [];
64
73
  for (const el of incoming) {
65
- renderList.push({
66
- key: el.key,
67
- element: el,
68
- isPresent: true
69
- });
74
+ const key = el.key;
75
+ byKey.set(key, el);
76
+ presentKeys.add(key);
77
+ order.push(key);
70
78
  }
71
- for (const [key, el] of exiting) {
72
- if (!renderList.some((entry) => entry.key === key)) {
73
- renderList.push({ key, element: el, isPresent: false });
79
+ const prevOrder = orderRef.current;
80
+ for (let i = 0; i < prevOrder.length; i++) {
81
+ const key = prevOrder[i];
82
+ const departing = activeExiting.get(key);
83
+ if (!departing || byKey.has(key)) continue;
84
+ let insertAt = 0;
85
+ for (let j = i - 1; j >= 0; j--) {
86
+ const anchor = order.indexOf(prevOrder[j]);
87
+ if (anchor !== -1) {
88
+ insertAt = anchor + 1;
89
+ break;
90
+ }
74
91
  }
92
+ byKey.set(key, departing);
93
+ order.splice(insertAt, 0, key);
75
94
  }
95
+ for (const [key, el] of activeExiting) {
96
+ if (byKey.has(key)) continue;
97
+ byKey.set(key, el);
98
+ order.push(key);
99
+ }
100
+ orderRef.current = order;
101
+ const renderList = order.map((key) => ({
102
+ key,
103
+ element: byKey.get(key),
104
+ isPresent: presentKeys.has(key)
105
+ }));
76
106
  return /* @__PURE__ */ jsx(Fragment, { children: renderList.map(({ key, element, isPresent }) => /* @__PURE__ */ jsx(
77
107
  PresenceItem,
78
108
  {
@@ -99,6 +129,175 @@ function PresenceItem({
99
129
  );
100
130
  return /* @__PURE__ */ jsx(PresenceContext.Provider, { value, children });
101
131
  }
132
+
133
+ // src/internal/boxShadow.ts
134
+ var LENGTH = /^[+-]?(\d+\.?\d*|\.\d+)(px)?$/i;
135
+ var UNIT_LIKE = /^[+-]?(\d+\.?\d*|\.\d+)[a-z%]+$/i;
136
+ function invalid(input, reason) {
137
+ return new Error(
138
+ `[inertia] parseBoxShadow: ${reason} in ${JSON.stringify(input)}. Expected CSS box-shadow syntax with px lengths: '[inset] <offset-x> <offset-y> [blur] [spread] [color], ...'`
139
+ );
140
+ }
141
+ function splitLayers(input) {
142
+ const layers = [];
143
+ let depth = 0;
144
+ let start = 0;
145
+ for (let i = 0; i < input.length; i++) {
146
+ const ch = input[i];
147
+ if (ch === "(") depth++;
148
+ else if (ch === ")") depth--;
149
+ else if (ch === "," && depth === 0) {
150
+ layers.push(input.slice(start, i));
151
+ start = i + 1;
152
+ }
153
+ }
154
+ layers.push(input.slice(start));
155
+ return layers;
156
+ }
157
+ function tokenize(layer) {
158
+ const tokens = [];
159
+ let depth = 0;
160
+ let current = "";
161
+ for (const ch of layer) {
162
+ if (ch === "(") depth++;
163
+ else if (ch === ")") depth--;
164
+ if (depth === 0 && /\s/.test(ch)) {
165
+ if (current) tokens.push(current);
166
+ current = "";
167
+ } else {
168
+ current += ch;
169
+ }
170
+ }
171
+ if (current) tokens.push(current);
172
+ return tokens;
173
+ }
174
+ function parseBoxShadow(input) {
175
+ const trimmed = input.trim();
176
+ if (trimmed === "") throw invalid(input, "empty string");
177
+ if (trimmed.toLowerCase() === "none") return [];
178
+ return splitLayers(trimmed).map((layerString) => {
179
+ const tokens = tokenize(layerString.trim());
180
+ if (tokens.length === 0) throw invalid(input, "empty layer");
181
+ const lengths = [];
182
+ let color;
183
+ let inset = false;
184
+ for (const token of tokens) {
185
+ if (token.toLowerCase() === "inset") {
186
+ if (inset) throw invalid(input, "duplicate 'inset'");
187
+ inset = true;
188
+ } else if (LENGTH.test(token)) {
189
+ lengths.push(parseFloat(token));
190
+ } else if (UNIT_LIKE.test(token)) {
191
+ throw invalid(input, `unsupported unit in ${JSON.stringify(token)}`);
192
+ } else {
193
+ if (color !== void 0) throw invalid(input, "multiple colors");
194
+ color = token;
195
+ }
196
+ }
197
+ if (lengths.length < 2 || lengths.length > 4) {
198
+ throw invalid(input, `expected 2-4 lengths, got ${lengths.length}`);
199
+ }
200
+ const [offsetX = 0, offsetY = 0, blurRadius = 0, spreadDistance = 0] = lengths;
201
+ if (blurRadius < 0) throw invalid(input, "negative blur radius");
202
+ return {
203
+ offsetX,
204
+ offsetY,
205
+ blurRadius,
206
+ spreadDistance,
207
+ color: color ?? "black",
208
+ inset
209
+ };
210
+ });
211
+ }
212
+ function resolveBoxShadowInput(input) {
213
+ if (input === void 0) return [];
214
+ if (typeof input === "string") return parseBoxShadow(input);
215
+ return input.map((layer) => ({
216
+ offsetX: coerceLength(layer.offsetX, "offsetX"),
217
+ offsetY: coerceLength(layer.offsetY, "offsetY"),
218
+ blurRadius: coerceLength(layer.blurRadius, "blurRadius"),
219
+ spreadDistance: coerceLength(layer.spreadDistance, "spreadDistance"),
220
+ color: layer.color ?? "black",
221
+ inset: layer.inset ?? false
222
+ }));
223
+ }
224
+ function pairBoxShadowLayers(from, to) {
225
+ const count = Math.max(from.length, to.length);
226
+ const pairs = [];
227
+ for (let i = 0; i < count; i++) {
228
+ const a = from[i];
229
+ const b = to[i];
230
+ const fromLayer = a ?? invisibleLayer(b.inset);
231
+ const toLayer = b ?? invisibleLayer(a.inset);
232
+ if (fromLayer.inset !== toLayer.inset) {
233
+ throw new Error(
234
+ `[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).`
235
+ );
236
+ }
237
+ pairs.push({ from: fromLayer, to: toLayer });
238
+ }
239
+ return pairs;
240
+ }
241
+ function invisibleLayer(inset) {
242
+ return {
243
+ offsetX: 0,
244
+ offsetY: 0,
245
+ blurRadius: 0,
246
+ spreadDistance: 0,
247
+ color: "transparent",
248
+ inset
249
+ };
250
+ }
251
+ function coerceLength(value, field) {
252
+ if (value === void 0) return 0;
253
+ if (typeof value === "number") return value;
254
+ const trimmed = typeof value === "string" ? value.trim() : "";
255
+ if (!LENGTH.test(trimmed)) {
256
+ throw new Error(
257
+ `[inertia] boxShadow: ${field} must be a number or a px length, got ${JSON.stringify(value)}.`
258
+ );
259
+ }
260
+ return parseFloat(trimmed);
261
+ }
262
+ function strip(layer) {
263
+ const { inset: _inset, ...rest } = layer;
264
+ return rest;
265
+ }
266
+ function markInset(insets, i, count) {
267
+ const list = insets ?? new Array(count).fill(false);
268
+ list[i] = true;
269
+ return list;
270
+ }
271
+ function normalizeBoxShadow(input) {
272
+ const resolved = resolveBoxShadowInput(input);
273
+ const layers = [];
274
+ let insets = null;
275
+ for (let i = 0; i < resolved.length; i++) {
276
+ const layer = resolved[i];
277
+ layers.push(strip(layer));
278
+ if (layer.inset) insets = markInset(insets, i, resolved.length);
279
+ }
280
+ return { layers, insets };
281
+ }
282
+ function prepareBoxShadowAnimation(current, target) {
283
+ const pairs = pairBoxShadowLayers(
284
+ current.layers.map((layer, i) => ({
285
+ ...layer,
286
+ inset: current.insets?.[i] ?? false
287
+ })),
288
+ resolveBoxShadowInput(target)
289
+ );
290
+ const from = [];
291
+ const to = [];
292
+ let insets = null;
293
+ for (let i = 0; i < pairs.length; i++) {
294
+ const pair = pairs[i];
295
+ from.push(strip(pair.from));
296
+ to.push(strip(pair.to));
297
+ if (pair.to.inset) insets = markInset(insets, i, pairs.length);
298
+ }
299
+ return { from, to, insets };
300
+ }
102
301
  function resolveLayoutTransition(layout) {
103
302
  if (!layout) return void 0;
104
303
  const cfg = layout === true ? { type: "spring" } : layout;
@@ -124,68 +323,146 @@ function resolveLayoutTransition(layout) {
124
323
  var REGISTRY = /* @__PURE__ */ new Map();
125
324
  var SHARED_LAYOUT_TTL_MS = 1e3;
126
325
  var now = () => Date.now();
127
- function registerLayout(id, rect) {
128
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
326
+ var lastSweep = 0;
327
+ function sweepExpired(at) {
328
+ if (at - lastSweep < SHARED_LAYOUT_TTL_MS) return;
329
+ lastSweep = at;
330
+ for (const [id, entry] of REGISTRY) {
331
+ if (entry.expiresAt < at) REGISTRY.delete(id);
332
+ }
333
+ }
334
+ function registerLayout(id, rect, remeasure, readStyles) {
335
+ const at = now();
336
+ sweepExpired(at);
337
+ REGISTRY.set(id, {
338
+ rect,
339
+ expiresAt: at + SHARED_LAYOUT_TTL_MS,
340
+ remeasure,
341
+ readStyles
342
+ });
129
343
  }
130
- function releaseLayout(id, rect) {
131
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
344
+ function releaseLayout(id, rect, styles) {
345
+ const at = now();
346
+ sweepExpired(at);
347
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS, styles });
132
348
  }
133
349
  function consumeLayout(id) {
134
350
  const entry = REGISTRY.get(id);
135
351
  if (!entry) return void 0;
136
352
  REGISTRY.delete(id);
137
353
  if (entry.expiresAt < now()) return void 0;
138
- return entry.rect;
354
+ return {
355
+ rect: entry.rect,
356
+ remeasure: entry.remeasure,
357
+ styles: entry.styles,
358
+ readStyles: entry.readStyles
359
+ };
360
+ }
361
+
362
+ // src/layout/measureWindow.ts
363
+ var defaultMeasurer = (node) => {
364
+ const measure = node?.measureInWindow;
365
+ if (typeof measure !== "function") return void 0;
366
+ let result;
367
+ let returned = false;
368
+ try {
369
+ measure.call(node, (x, y, width, height) => {
370
+ if (returned) return;
371
+ result = { x, y, width, height };
372
+ });
373
+ } catch {
374
+ returned = true;
375
+ return void 0;
376
+ }
377
+ returned = true;
378
+ return result;
379
+ };
380
+ var measurer = defaultMeasurer;
381
+ function usable(rect) {
382
+ const { x, y, width, height } = rect;
383
+ for (const v of [x, y, width, height]) {
384
+ if (typeof v !== "number" || !Number.isFinite(v)) return false;
385
+ }
386
+ return width !== 0 || height !== 0;
387
+ }
388
+ function measureWindowRect(node) {
389
+ const measured = measurer(node);
390
+ if (!measured || !usable(measured)) return void 0;
391
+ return toSharedRect(measured, "window");
392
+ }
393
+ function toSharedRect(rect, space) {
394
+ return { ...rect, space };
139
395
  }
140
396
  function useSharedLayout(options) {
141
- const { layoutId, userRef, transition, shouldReduceMotion, userOnLayout } = options;
397
+ const {
398
+ layoutId,
399
+ userRef,
400
+ transition,
401
+ shouldReduceMotion,
402
+ userOnLayout,
403
+ readStyles
404
+ } = options;
142
405
  const dx = useSharedValue(0);
143
406
  const dy = useSharedValue(0);
144
407
  const sx = useSharedValue(1);
145
408
  const sy = useSharedValue(1);
409
+ const snapshot = useSharedValue(null);
410
+ const carryProgress = useSharedValue(0);
146
411
  const lastRectRef = useRef(null);
147
412
  const consumedRef = useRef(false);
413
+ const nodeRef = useRef(null);
148
414
  const transitionRef = useRef(transition);
149
415
  transitionRef.current = transition;
150
416
  const reducedMotionRef = useRef(shouldReduceMotion);
151
417
  reducedMotionRef.current = shouldReduceMotion;
418
+ const readStylesRef = useRef(readStyles);
419
+ readStylesRef.current = readStyles;
152
420
  const setRef = useCallback(
153
421
  (node) => {
422
+ nodeRef.current = node;
154
423
  if (typeof userRef === "function") userRef(node);
155
424
  else if (userRef) userRef.current = node;
156
425
  },
157
426
  [userRef]
158
427
  );
428
+ const remeasure = useCallback(() => measureWindowRect(nodeRef.current), []);
429
+ const snapshotStyles = useCallback(() => readStylesRef.current?.(), []);
430
+ const startTransition = useCallback(
431
+ (source, sourceRect, target) => {
432
+ if (sourceRect.space !== target.space) return;
433
+ const shared = {
434
+ transition: transitionRef.current,
435
+ shouldReduceMotion: reducedMotionRef.current
436
+ };
437
+ applyFlip({ source: sourceRect, target, dx, dy, sx, sy, ...shared });
438
+ applyStyleCarry({
439
+ snapshot,
440
+ progress: carryProgress,
441
+ // Prefer a live read for the same reason the rect is re-measured: a
442
+ // still-mounted source may have moved on since its last layout.
443
+ styles: source.readStyles?.() ?? source.styles,
444
+ ...shared
445
+ });
446
+ },
447
+ [dx, dy, sx, sy, snapshot, carryProgress]
448
+ );
159
449
  const onLayout = useCallback(
160
450
  (event) => {
161
451
  userOnLayout?.(event);
162
452
  if (!layoutId) return;
163
453
  const { x, y, width, height } = event.nativeEvent.layout;
164
- const rect = { x, y, width, height };
165
- lastRectRef.current = rect;
166
454
  let source;
167
455
  if (!consumedRef.current) {
168
456
  consumedRef.current = true;
169
457
  source = consumeLayout(layoutId);
170
458
  }
171
- registerLayout(layoutId, rect);
172
- if (source) {
173
- applyFlip({
174
- source,
175
- target: rect,
176
- dx,
177
- dy,
178
- sx,
179
- sy,
180
- transition: transitionRef.current,
181
- shouldReduceMotion: reducedMotionRef.current
182
- });
183
- }
459
+ const rect = measureWindowRect(nodeRef.current) ?? { x, y, width, height, space: "parent" };
460
+ lastRectRef.current = rect;
461
+ registerLayout(layoutId, rect, remeasure, snapshotStyles);
462
+ if (!source) return;
463
+ startTransition(source, source.remeasure?.() ?? source.rect, rect);
184
464
  },
185
- // dx/dy/sx/sy are stable refs from useSharedValue, but eslint's
186
- // exhaustive-deps would flag them — including them is harmless and
187
- // silences the warning.
188
- [layoutId, userOnLayout, dx, dy, sx, sy]
465
+ [layoutId, userOnLayout, remeasure, snapshotStyles, startTransition]
189
466
  );
190
467
  useEffect(() => {
191
468
  consumedRef.current = false;
@@ -195,18 +472,49 @@ function useSharedLayout(options) {
195
472
  if (!layoutId) return;
196
473
  const rect = lastRectRef.current;
197
474
  if (!rect) return;
198
- releaseLayout(layoutId, rect);
475
+ releaseLayout(layoutId, rect, snapshotStyles());
199
476
  };
200
- }, [layoutId]);
477
+ }, [layoutId, snapshotStyles]);
478
+ useEffect(
479
+ () => () => {
480
+ cancelAnimation(dx);
481
+ cancelAnimation(dy);
482
+ cancelAnimation(sx);
483
+ cancelAnimation(sy);
484
+ cancelAnimation(carryProgress);
485
+ },
486
+ // The FLIP shared values are identity-stable per hook instance.
487
+ // eslint-disable-next-line react-hooks/exhaustive-deps
488
+ []
489
+ );
201
490
  return useMemo(
202
491
  () => ({
203
492
  flip: { dx, dy, sx, sy },
493
+ carry: { snapshot, progress: carryProgress },
204
494
  setRef,
205
495
  onLayout
206
496
  }),
207
- [dx, dy, sx, sy, setRef, onLayout]
497
+ [dx, dy, sx, sy, snapshot, carryProgress, setRef, onLayout]
498
+ );
499
+ }
500
+ function legBuilder(transition) {
501
+ if (transition?.type === "timing") {
502
+ const duration = transition.duration ?? 300;
503
+ return (from, to) => withSequence(
504
+ withTiming(from, { duration: 0 }),
505
+ withTiming(to, { duration })
506
+ );
507
+ }
508
+ const springCfg = transition?.type === "spring" ? { ...DEFAULT_SPRING, ...transition } : { type: "spring", ...DEFAULT_SPRING };
509
+ const springParams = springToReanimated(springCfg);
510
+ return (from, to) => withSequence(
511
+ withTiming(from, { duration: 0 }),
512
+ withSpring(to, springParams)
208
513
  );
209
514
  }
515
+ function shouldSnap(transition, shouldReduceMotion) {
516
+ return shouldReduceMotion || transition?.type === "no-animation";
517
+ }
210
518
  function applyFlip(args) {
211
519
  const { source, target, dx, dy, sx, sy, transition, shouldReduceMotion } = args;
212
520
  const sourceCenterX = source.x + source.width / 2;
@@ -217,58 +525,29 @@ function applyFlip(args) {
217
525
  const deltaY = sourceCenterY - targetCenterY;
218
526
  const scaleX = target.width > 0 ? source.width / target.width : 1;
219
527
  const scaleY = target.height > 0 ? source.height / target.height : 1;
220
- if (shouldReduceMotion) {
221
- dx.value = 0;
222
- dy.value = 0;
223
- sx.value = 1;
224
- sy.value = 1;
225
- return;
226
- }
227
- if (transition?.type === "no-animation") {
528
+ if (shouldSnap(transition, shouldReduceMotion)) {
228
529
  dx.value = 0;
229
530
  dy.value = 0;
230
531
  sx.value = 1;
231
532
  sy.value = 1;
232
533
  return;
233
534
  }
234
- if (transition?.type === "timing") {
235
- const duration = transition.duration ?? 300;
236
- dx.value = withSequence(
237
- withTiming(deltaX, { duration: 0 }),
238
- withTiming(0, { duration })
239
- );
240
- dy.value = withSequence(
241
- withTiming(deltaY, { duration: 0 }),
242
- withTiming(0, { duration })
243
- );
244
- sx.value = withSequence(
245
- withTiming(scaleX, { duration: 0 }),
246
- withTiming(1, { duration })
247
- );
248
- sy.value = withSequence(
249
- withTiming(scaleY, { duration: 0 }),
250
- withTiming(1, { duration })
251
- );
535
+ const leg = legBuilder(transition);
536
+ dx.value = leg(deltaX, 0);
537
+ dy.value = leg(deltaY, 0);
538
+ sx.value = leg(scaleX, 1);
539
+ sy.value = leg(scaleY, 1);
540
+ }
541
+ function applyStyleCarry(args) {
542
+ const { snapshot, progress, styles, transition, shouldReduceMotion } = args;
543
+ if (!styles) return;
544
+ if (shouldSnap(transition, shouldReduceMotion)) {
545
+ snapshot.value = null;
546
+ progress.value = 0;
252
547
  return;
253
548
  }
254
- const springCfg = transition?.type === "spring" ? { ...DEFAULT_SPRING, ...transition } : { type: "spring", ...DEFAULT_SPRING };
255
- const springParams = springToReanimated(springCfg);
256
- dx.value = withSequence(
257
- withTiming(deltaX, { duration: 0 }),
258
- withSpring(0, springParams)
259
- );
260
- dy.value = withSequence(
261
- withTiming(deltaY, { duration: 0 }),
262
- withSpring(0, springParams)
263
- );
264
- sx.value = withSequence(
265
- withTiming(scaleX, { duration: 0 }),
266
- withSpring(1, springParams)
267
- );
268
- sy.value = withSequence(
269
- withTiming(scaleY, { duration: 0 }),
270
- withSpring(1, springParams)
271
- );
549
+ snapshot.value = styles;
550
+ progress.value = legBuilder(transition)(1, 0);
272
551
  }
273
552
  var alreadyChecked = false;
274
553
  function ensureReanimatedInstalled() {
@@ -282,7 +561,7 @@ function ensureReanimatedInstalled() {
282
561
  const major = parseInt(version.split(".")[0] ?? "0", 10);
283
562
  if (major < 4) {
284
563
  console.error(
285
- `[inertia] react-native-reanimated v${version} is installed, but @rootnative/inertia requires v4.0.0 or later. Upgrade with \`pnpm add react-native-reanimated@^4\` (or your package manager's equivalent).`
564
+ `[inertia] react-native-reanimated ${version} is installed, but @rootnative/inertia requires 4.0.0 or later. Upgrade with \`pnpm add react-native-reanimated@^4\` (or your package manager's equivalent).`
286
565
  );
287
566
  return;
288
567
  }
@@ -325,15 +604,28 @@ var COLOR_KEYS = [
325
604
  "shadowColor"
326
605
  ];
327
606
  var SHADOW_OFFSET_KEYS = ["shadowOffsetWidth", "shadowOffsetHeight"];
607
+ var STRUCTURED_KEYS = ["boxShadow"];
608
+ var SHARED_STYLE_KEYS = [
609
+ "opacity",
610
+ "borderRadius",
611
+ "backgroundColor",
612
+ "borderColor",
613
+ "color",
614
+ "tintColor"
615
+ ];
328
616
  var ALL_KEYS = [
329
617
  ...TRANSFORM_KEYS,
330
618
  ...NUMERIC_TOP_LEVEL_KEYS,
331
619
  ...COLOR_KEYS,
332
- ...SHADOW_OFFSET_KEYS
620
+ ...SHADOW_OFFSET_KEYS,
621
+ ...STRUCTURED_KEYS
333
622
  ];
334
623
  var TRANSFORM_KEY_SET = new Set(TRANSFORM_KEYS);
335
624
  var COLOR_KEY_SET = new Set(COLOR_KEYS);
336
625
  var SHADOW_OFFSET_KEY_SET = new Set(SHADOW_OFFSET_KEYS);
626
+ var STRUCTURED_KEY_SET = new Set(STRUCTURED_KEYS);
627
+ var SHARED_STYLE_KEY_SET = new Set(SHARED_STYLE_KEYS);
628
+ var NO_BOX_SHADOW = Object.freeze([]);
337
629
  var GESTURE_LAYER_NAMES = [
338
630
  "hovered",
339
631
  "focused",
@@ -368,7 +660,8 @@ var DEFAULT_RESTING = {
368
660
  tintColor: "transparent",
369
661
  shadowColor: "transparent",
370
662
  shadowOffsetWidth: 0,
371
- shadowOffsetHeight: 0
663
+ shadowOffsetHeight: 0,
664
+ boxShadow: NO_BOX_SHADOW
372
665
  };
373
666
  function transitionFor(prop, transition) {
374
667
  if (!transition) return void 0;
@@ -459,6 +752,16 @@ function createMotionComponent(Component) {
459
752
  const [focused, setFocused] = useState(false);
460
753
  const [focusVisible, setFocusVisible] = useState(false);
461
754
  const [hovered, setHovered] = useState(false);
755
+ const hasLayoutId = layoutId !== void 0;
756
+ let flatStyle;
757
+ let flatStyleRead = false;
758
+ const getFlatStyle = () => {
759
+ if (!flatStyleRead) {
760
+ flatStyleRead = true;
761
+ flatStyle = StyleSheet.flatten(style);
762
+ }
763
+ return flatStyle;
764
+ };
462
765
  const touched = /* @__PURE__ */ new Set();
463
766
  collectTouchedKeys(touched, animateRecord);
464
767
  if (initialRecord) collectTouchedKeys(touched, initialRecord);
@@ -480,9 +783,19 @@ function createMotionComponent(Component) {
480
783
  }
481
784
  }
482
785
  if (exitRecord) collectTouchedKeys(touched, exitRecord);
786
+ if (hasLayoutId) {
787
+ const flat = getFlatStyle();
788
+ if (flat) {
789
+ for (const key of SHARED_STYLE_KEYS) {
790
+ if (touched.has(key)) continue;
791
+ if (styleValueFor(flat, key) !== void 0) touched.add(key);
792
+ }
793
+ }
794
+ }
483
795
  const activeKeysRef = useRef(null);
484
796
  const hasTransformRef = useRef(false);
485
797
  const hasShadowOffsetRef = useRef(false);
798
+ const hasBoxShadowRef = useRef(false);
486
799
  const prevActive = activeKeysRef.current;
487
800
  let grew = prevActive === null;
488
801
  if (!grew && prevActive) {
@@ -503,24 +816,83 @@ function createMotionComponent(Component) {
503
816
  hasShadowOffsetRef.current = activeKeysRef.current.some(
504
817
  (k) => SHADOW_OFFSET_KEY_SET.has(k)
505
818
  );
819
+ hasBoxShadowRef.current = activeKeysRef.current.includes("boxShadow");
820
+ if (__DEV__ && hasBoxShadowRef.current && activeKeysRef.current.some(
821
+ (k) => k !== "boxShadow" && k.startsWith("shadow")
822
+ )) {
823
+ warnOnce(
824
+ "boxShadow-with-native-shadow",
825
+ "[inertia] `boxShadow` is animated alongside the native `shadow*` keys on the same element, which applies two shadow systems at once. Pick one: `boxShadow` for the cross-platform CSS form, or the native `shadow*` keys."
826
+ );
827
+ }
828
+ }
829
+ const drivenNow = /* @__PURE__ */ new Set();
830
+ collectTouchedKeys(drivenNow, animateRecord);
831
+ if (initialRecord) collectTouchedKeys(drivenNow, initialRecord);
832
+ if (isExiting && exitRecord) collectTouchedKeys(drivenNow, exitRecord);
833
+ const everDrivenRef = useRef(/* @__PURE__ */ new Set());
834
+ for (const k of drivenNow) everDrivenRef.current.add(k);
835
+ const activeKeys = activeKeysRef.current;
836
+ const everDriven = everDrivenRef.current;
837
+ const styleResting = {};
838
+ let styleRestingShadow = null;
839
+ if (activeKeys.some((k) => !everDriven.has(k))) {
840
+ const flat = getFlatStyle();
841
+ if (flat) {
842
+ for (const key of activeKeys) {
843
+ if (everDriven.has(key)) continue;
844
+ if (key === "boxShadow") {
845
+ const raw = flat.boxShadow;
846
+ if (raw !== void 0) {
847
+ styleRestingShadow = normalizeBoxShadow(raw);
848
+ styleResting.boxShadow = styleRestingShadow.layers;
849
+ }
850
+ continue;
851
+ }
852
+ const v = styleValueFor(flat, key);
853
+ if (v !== void 0) styleResting[key] = v;
854
+ }
855
+ }
856
+ }
857
+ const boxShadowSeedRef = useRef(null);
858
+ if (boxShadowSeedRef.current === null) {
859
+ 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
+ };
506
864
  }
507
865
  const sharedValues = useAnimatableSharedValues((key) => {
866
+ if (key === "boxShadow") return boxShadowSeedRef.current.layers;
508
867
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
509
868
  const axis = shadowOffsetAxisFor(key);
510
869
  if (initial === false) {
511
- return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
870
+ return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
512
871
  }
513
872
  return shadowOffsetAxisValue(
514
873
  initialRecord?.shadowOffset,
515
874
  axis
516
- ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
875
+ ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
517
876
  }
518
877
  if (initial === false) {
519
878
  const a = animateRecord[key];
520
- return restValue(a) ?? DEFAULT_RESTING[key];
879
+ return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
521
880
  }
522
- return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
881
+ return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
523
882
  });
883
+ const boxShadowInsets = useSharedValue(
884
+ boxShadowSeedRef.current.insets
885
+ );
886
+ const styleRestingSig = stableSig(styleResting);
887
+ useEffect(() => {
888
+ for (const key of activeKeysRef.current) {
889
+ if (everDrivenRef.current.has(key)) continue;
890
+ sharedValues[key].value = styleResting[key] ?? DEFAULT_RESTING[key];
891
+ if (key === "boxShadow") {
892
+ boxShadowInsets.value = styleRestingShadow?.insets ?? null;
893
+ }
894
+ }
895
+ }, [styleRestingSig]);
524
896
  const pressedProgress = useSharedValue(0);
525
897
  const focusedProgress = useSharedValue(0);
526
898
  const focusVisibleProgress = useSharedValue(0);
@@ -563,24 +935,49 @@ function createMotionComponent(Component) {
563
935
  if (isExiting) safeToRemoveRef.current?.();
564
936
  }
565
937
  };
938
+ const configFor = (key) => shouldReduceMotion ? { type: "no-animation" } : transitionFor(
939
+ SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
940
+ transition
941
+ );
566
942
  let transformPending = 0;
567
943
  for (const k of ALL_KEYS) {
568
- if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
569
- transformPending++;
570
- }
944
+ if (!TRANSFORM_KEY_SET.has(k) || baseRecord[k] === void 0) continue;
945
+ if (!reachesTerminalPhase(configFor(k))) continue;
946
+ transformPending++;
571
947
  }
572
948
  const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
573
949
  for (const key of ALL_KEYS) {
950
+ if (key === "boxShadow") {
951
+ const shadowTarget = baseRecord.boxShadow;
952
+ if (shadowTarget === void 0) continue;
953
+ const cfg2 = configFor("boxShadow");
954
+ if (isExiting && reachesTerminalPhase(cfg2)) pending++;
955
+ driveBoxShadow(
956
+ sharedValues.boxShadow,
957
+ boxShadowInsets,
958
+ shadowTarget,
959
+ cfg2,
960
+ makeKeyCallbackFactory(
961
+ "boxShadow",
962
+ sharedValues.boxShadow,
963
+ // The declared target, not the padded one — the padding is an
964
+ // implementation detail of getting Reanimated to interpolate.
965
+ shadowTarget,
966
+ onAnimationEndRef,
967
+ { stepCount: 1, totalIterations: totalIterationsOf(cfg2) },
968
+ isExiting ? onSettle : void 0,
969
+ void 0
970
+ )
971
+ );
972
+ continue;
973
+ }
574
974
  const target = SHADOW_OFFSET_KEY_SET.has(key) ? shadowOffsetAxisValue(
575
975
  baseRecord.shadowOffset,
576
976
  shadowOffsetAxisFor(key)
577
977
  ) : baseRecord[key];
578
978
  if (target === void 0) continue;
579
- const cfg = shouldReduceMotion ? { type: "no-animation" } : transitionFor(
580
- SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
581
- transition
582
- );
583
- if (isExiting) pending++;
979
+ const cfg = configFor(key);
980
+ if (isExiting && reachesTerminalPhase(cfg)) pending++;
584
981
  const factory = makeKeyCallbackFactory(
585
982
  key,
586
983
  sharedValues[key],
@@ -639,19 +1036,34 @@ function createMotionComponent(Component) {
639
1036
  isExiting,
640
1037
  shouldReduceMotion
641
1038
  );
1039
+ const readSharedStyles = () => {
1040
+ if (!hasLayoutId) return void 0;
1041
+ const out = {};
1042
+ let any = false;
1043
+ for (const key of activeKeysRef.current) {
1044
+ if (!SHARED_STYLE_KEY_SET.has(key)) continue;
1045
+ const v = sharedValues[key].value;
1046
+ if (typeof v !== "number" && typeof v !== "string") continue;
1047
+ out[key] = v;
1048
+ any = true;
1049
+ }
1050
+ return any ? out : void 0;
1051
+ };
642
1052
  const sharedLayout = useSharedLayout({
643
1053
  layoutId,
644
1054
  userRef: ref,
645
1055
  transition: isTopLevelTransition(transition) ? transition : void 0,
646
1056
  shouldReduceMotion,
647
- userOnLayout
1057
+ userOnLayout,
1058
+ readStyles: readSharedStyles
648
1059
  });
649
1060
  const flip = sharedLayout.flip;
650
- const hasLayoutId = layoutId !== void 0;
1061
+ const carry = sharedLayout.carry;
651
1062
  const animatedStyle = useAnimatedStyle(() => {
652
- const activeKeys = activeKeysRef.current;
1063
+ const activeKeys2 = activeKeysRef.current;
653
1064
  const hasTransform = hasTransformRef.current;
654
1065
  const hasShadowOffset = hasShadowOffsetRef.current;
1066
+ const hasBoxShadow = hasBoxShadowRef.current;
655
1067
  const out = {};
656
1068
  const transform = [];
657
1069
  let shadowOffsetW = 0;
@@ -660,12 +1072,15 @@ function createMotionComponent(Component) {
660
1072
  const pf = focusedProgress.value;
661
1073
  const pfv = focusVisibleProgress.value;
662
1074
  const pp = pressedProgress.value;
1075
+ const carried = carry.snapshot.value;
1076
+ const pc = carry.progress.value;
663
1077
  const layers = gestureSV.value;
664
1078
  const hoveredLayer = layers ? layers.hovered : null;
665
1079
  const focusedLayer = layers ? layers.focused : null;
666
1080
  const focusVisibleLayer = layers ? layers.focusVisible : null;
667
1081
  const pressedLayer = layers ? layers.pressed : null;
668
- for (const key of activeKeys) {
1082
+ for (const key of activeKeys2) {
1083
+ if (key === "boxShadow") continue;
669
1084
  let v = sharedValues[key].value;
670
1085
  const isColor = COLOR_KEY_SET.has(key);
671
1086
  if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
@@ -684,6 +1099,12 @@ function createMotionComponent(Component) {
684
1099
  const t = pressedLayer[key];
685
1100
  v = isColor ? interpolateColor(pp, [0, 1], [v, t]) : v + (t - v) * pp;
686
1101
  }
1102
+ if (carried !== null && pc > 0) {
1103
+ const s = carried[key];
1104
+ if (s !== void 0) {
1105
+ v = isColor ? interpolateColor(pc, [0, 1], [v, s]) : v + (s - v) * pc;
1106
+ }
1107
+ }
687
1108
  if (TRANSFORM_KEY_SET.has(key)) {
688
1109
  transform.push(
689
1110
  ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v }
@@ -706,6 +1127,19 @@ function createMotionComponent(Component) {
706
1127
  if (hasShadowOffset) {
707
1128
  out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH };
708
1129
  }
1130
+ if (hasBoxShadow) {
1131
+ const layers2 = sharedValues.boxShadow.value;
1132
+ const insets = boxShadowInsets.value;
1133
+ if (insets === null) {
1134
+ out.boxShadow = layers2;
1135
+ } else {
1136
+ const withInset = [];
1137
+ for (let i = 0; i < layers2.length; i++) {
1138
+ withInset.push({ ...layers2[i], inset: insets[i] });
1139
+ }
1140
+ out.boxShadow = withInset;
1141
+ }
1142
+ }
709
1143
  return out;
710
1144
  });
711
1145
  const mergedStyle = useMemo(
@@ -780,7 +1214,9 @@ function useAnimatableSharedValues(init) {
780
1214
  const width = useSharedValue(init("width"));
781
1215
  const height = useSharedValue(init("height"));
782
1216
  const borderRadius = useSharedValue(init("borderRadius"));
783
- const shadowOpacity = useSharedValue(init("shadowOpacity"));
1217
+ const shadowOpacity = useSharedValue(
1218
+ init("shadowOpacity")
1219
+ );
784
1220
  const shadowRadius = useSharedValue(init("shadowRadius"));
785
1221
  const elevation = useSharedValue(init("elevation"));
786
1222
  const backgroundColor = useSharedValue(
@@ -796,6 +1232,7 @@ function useAnimatableSharedValues(init) {
796
1232
  const shadowOffsetHeight = useSharedValue(
797
1233
  init("shadowOffsetHeight")
798
1234
  );
1235
+ const boxShadow = useSharedValue(init("boxShadow"));
799
1236
  const ref = useRef(null);
800
1237
  if (ref.current === null) {
801
1238
  ref.current = {
@@ -820,7 +1257,8 @@ function useAnimatableSharedValues(init) {
820
1257
  tintColor,
821
1258
  shadowColor,
822
1259
  shadowOffsetWidth,
823
- shadowOffsetHeight
1260
+ shadowOffsetHeight,
1261
+ boxShadow
824
1262
  };
825
1263
  }
826
1264
  const map = ref.current;
@@ -905,6 +1343,54 @@ function shadowOffsetAxisFor(key) {
905
1343
  function shadowOffsetAxisValue(source, axis) {
906
1344
  return source?.[axis];
907
1345
  }
1346
+ function driveBoxShadow(slot, insetSlot, target, cfg, factory) {
1347
+ const currentLayers = Array.isArray(slot.value) ? slot.value : NO_BOX_SHADOW;
1348
+ const { from, to, insets } = prepareBoxShadowAnimation(
1349
+ { layers: [...currentLayers], insets: insetSlot.value },
1350
+ target
1351
+ );
1352
+ insetSlot.value = insets;
1353
+ if (from.length !== currentLayers.length) slot.value = from;
1354
+ slot.value = resolveTransition(
1355
+ cfg,
1356
+ to,
1357
+ factory?.("animation", void 0)
1358
+ );
1359
+ }
1360
+ function styleValueFor(flat, key) {
1361
+ if (SHADOW_OFFSET_KEY_SET.has(key)) {
1362
+ return shadowOffsetAxisValue(
1363
+ flat.shadowOffset,
1364
+ shadowOffsetAxisFor(key)
1365
+ );
1366
+ }
1367
+ if (TRANSFORM_KEY_SET.has(key)) {
1368
+ const list = flat.transform;
1369
+ if (!Array.isArray(list)) return void 0;
1370
+ for (const entry of list) {
1371
+ if (!entry || typeof entry !== "object") continue;
1372
+ const v2 = entry[key];
1373
+ if (v2 === void 0) continue;
1374
+ if (ROTATION_KEYS.has(key)) return parseAngleDegrees(v2);
1375
+ return typeof v2 === "number" ? v2 : void 0;
1376
+ }
1377
+ return void 0;
1378
+ }
1379
+ const v = flat[key];
1380
+ if (COLOR_KEY_SET.has(key)) {
1381
+ return typeof v === "string" || typeof v === "number" ? v : void 0;
1382
+ }
1383
+ return typeof v === "number" ? v : void 0;
1384
+ }
1385
+ function parseAngleDegrees(v) {
1386
+ if (typeof v === "number") return v;
1387
+ if (typeof v !== "string") return void 0;
1388
+ const deg = /^(-?\d*\.?\d+)deg$/.exec(v);
1389
+ if (deg) return Number(deg[1]);
1390
+ const rad = /^(-?\d*\.?\d+)rad$/.exec(v);
1391
+ if (rad) return Number(rad[1]) * 180 / Math.PI;
1392
+ return void 0;
1393
+ }
908
1394
  function collectTouchedKeys(touched, record) {
909
1395
  for (const k of ALL_KEYS) {
910
1396
  if (k in record) touched.add(k);
@@ -928,6 +1414,9 @@ function totalIterationsOf(cfg) {
928
1414
  if (r.count === "infinite") return Number.POSITIVE_INFINITY;
929
1415
  return r.count;
930
1416
  }
1417
+ function reachesTerminalPhase(cfg) {
1418
+ return Number.isFinite(totalIterationsOf(cfg));
1419
+ }
931
1420
  function targetEndValue(v) {
932
1421
  if (v === void 0) return void 0;
933
1422
  if (typeof v === "number" || typeof v === "string") return v;
@@ -984,6 +1473,15 @@ function resolveGestureLayers(gesture) {
984
1473
  if (!subState) continue;
985
1474
  const resolved = {};
986
1475
  for (const key of ALL_KEYS) {
1476
+ if (STRUCTURED_KEY_SET.has(key)) {
1477
+ if (__DEV__ && subState[key] !== void 0) {
1478
+ warnOnce(
1479
+ `gesture-structured-${layer}-${key}`,
1480
+ `[inertia] \`${key}\` is not supported inside \`gesture.${layer}\` and will be ignored. Drive it from \`animate\` (optionally via a variant keyed off the same state), or interpolate it yourself with \`useShadow\`.`
1481
+ );
1482
+ }
1483
+ continue;
1484
+ }
987
1485
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
988
1486
  const axis = shadowOffsetAxisFor(key);
989
1487
  const so = subState.shadowOffset;
@@ -1073,4 +1571,4 @@ function compose(user, ours) {
1073
1571
  };
1074
1572
  }
1075
1573
 
1076
- export { Presence, createMotionComponent, usePresence };
1574
+ export { Presence, createMotionComponent, pairBoxShadowLayers, resolveBoxShadowInput, usePresence };