@rootnative/inertia 0.0.3 → 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 (74) hide show
  1. package/CHANGELOG.md +41 -5
  2. package/README.md +5 -3
  3. package/dist/{chunk-SGUHE5CX.mjs → chunk-4ASOELLA.mjs} +1 -1
  4. package/dist/{chunk-CSODMRJ7.mjs → chunk-55W7BNO2.mjs} +475 -83
  5. package/dist/chunk-5SRD2WCO.js +8 -0
  6. package/dist/{chunk-PTRF47DA.js → chunk-7A6WTXNK.js} +10 -3
  7. package/dist/{chunk-3XTVY34H.js → chunk-CENTULW6.js} +2 -2
  8. package/dist/{chunk-4PEHWDAZ.js → chunk-D5KAHWMC.js} +7 -7
  9. package/dist/{chunk-O22NXXCZ.mjs → chunk-J5Q46OPW.mjs} +1 -1
  10. package/dist/{chunk-RGNX6UZN.mjs → chunk-K3CLP74S.mjs} +2 -2
  11. package/dist/{chunk-BP3Y2SHQ.js → chunk-MYE5NMXW.js} +495 -101
  12. package/dist/{chunk-OQV66TBQ.mjs → chunk-NAOXQJ7U.mjs} +1 -1
  13. package/dist/{chunk-R63GIUNU.mjs → chunk-QKC3APP4.mjs} +1 -1
  14. package/dist/{chunk-46P57VMY.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-W5MC3P4N.js → chunk-XMEVPEMH.js} +2 -2
  20. package/dist/{chunk-BQQTHG2V.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 +4 -4
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +48 -169
  28. package/dist/index.mjs +17 -137
  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 +10 -0
  64. package/src/layout/measureWindow.ts +135 -0
  65. package/src/layout/sharedRegistry.ts +107 -18
  66. package/src/layout/useSharedLayout.ts +256 -91
  67. package/src/motion/createMotionComponent.tsx +378 -39
  68. package/src/motion/installCheck.ts +1 -1
  69. package/src/transitions/index.ts +1 -1
  70. package/src/types.ts +52 -12
  71. package/src/values/useColorTransition.ts +1 -1
  72. package/dist/chunk-FNVFV4EY.js +0 -8
  73. package/dist/chunk-FWQOXA43.js +0 -8
  74. package/dist/chunk-KBP4LR75.js +0 -8
@@ -1,5 +1,5 @@
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';
@@ -129,6 +129,175 @@ function PresenceItem({
129
129
  );
130
130
  return /* @__PURE__ */ jsx(PresenceContext.Provider, { value, children });
131
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
+ }
132
301
  function resolveLayoutTransition(layout) {
133
302
  if (!layout) return void 0;
134
303
  const cfg = layout === true ? { type: "spring" } : layout;
@@ -162,72 +331,138 @@ function sweepExpired(at) {
162
331
  if (entry.expiresAt < at) REGISTRY.delete(id);
163
332
  }
164
333
  }
165
- function registerLayout(id, rect) {
334
+ function registerLayout(id, rect, remeasure, readStyles) {
166
335
  const at = now();
167
336
  sweepExpired(at);
168
- REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
337
+ REGISTRY.set(id, {
338
+ rect,
339
+ expiresAt: at + SHARED_LAYOUT_TTL_MS,
340
+ remeasure,
341
+ readStyles
342
+ });
169
343
  }
170
- function releaseLayout(id, rect) {
344
+ function releaseLayout(id, rect, styles) {
171
345
  const at = now();
172
346
  sweepExpired(at);
173
- REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
347
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS, styles });
174
348
  }
175
349
  function consumeLayout(id) {
176
350
  const entry = REGISTRY.get(id);
177
351
  if (!entry) return void 0;
178
352
  REGISTRY.delete(id);
179
353
  if (entry.expiresAt < now()) return void 0;
180
- 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 };
181
395
  }
182
396
  function useSharedLayout(options) {
183
- const { layoutId, userRef, transition, shouldReduceMotion, userOnLayout } = options;
397
+ const {
398
+ layoutId,
399
+ userRef,
400
+ transition,
401
+ shouldReduceMotion,
402
+ userOnLayout,
403
+ readStyles
404
+ } = options;
184
405
  const dx = useSharedValue(0);
185
406
  const dy = useSharedValue(0);
186
407
  const sx = useSharedValue(1);
187
408
  const sy = useSharedValue(1);
409
+ const snapshot = useSharedValue(null);
410
+ const carryProgress = useSharedValue(0);
188
411
  const lastRectRef = useRef(null);
189
412
  const consumedRef = useRef(false);
413
+ const nodeRef = useRef(null);
190
414
  const transitionRef = useRef(transition);
191
415
  transitionRef.current = transition;
192
416
  const reducedMotionRef = useRef(shouldReduceMotion);
193
417
  reducedMotionRef.current = shouldReduceMotion;
418
+ const readStylesRef = useRef(readStyles);
419
+ readStylesRef.current = readStyles;
194
420
  const setRef = useCallback(
195
421
  (node) => {
422
+ nodeRef.current = node;
196
423
  if (typeof userRef === "function") userRef(node);
197
424
  else if (userRef) userRef.current = node;
198
425
  },
199
426
  [userRef]
200
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
+ );
201
449
  const onLayout = useCallback(
202
450
  (event) => {
203
451
  userOnLayout?.(event);
204
452
  if (!layoutId) return;
205
453
  const { x, y, width, height } = event.nativeEvent.layout;
206
- const rect = { x, y, width, height };
207
- lastRectRef.current = rect;
208
454
  let source;
209
455
  if (!consumedRef.current) {
210
456
  consumedRef.current = true;
211
457
  source = consumeLayout(layoutId);
212
458
  }
213
- registerLayout(layoutId, rect);
214
- if (source) {
215
- applyFlip({
216
- source,
217
- target: rect,
218
- dx,
219
- dy,
220
- sx,
221
- sy,
222
- transition: transitionRef.current,
223
- shouldReduceMotion: reducedMotionRef.current
224
- });
225
- }
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);
226
464
  },
227
- // dx/dy/sx/sy are stable refs from useSharedValue, but eslint's
228
- // exhaustive-deps would flag them — including them is harmless and
229
- // silences the warning.
230
- [layoutId, userOnLayout, dx, dy, sx, sy]
465
+ [layoutId, userOnLayout, remeasure, snapshotStyles, startTransition]
231
466
  );
232
467
  useEffect(() => {
233
468
  consumedRef.current = false;
@@ -237,18 +472,49 @@ function useSharedLayout(options) {
237
472
  if (!layoutId) return;
238
473
  const rect = lastRectRef.current;
239
474
  if (!rect) return;
240
- releaseLayout(layoutId, rect);
475
+ releaseLayout(layoutId, rect, snapshotStyles());
241
476
  };
242
- }, [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
+ );
243
490
  return useMemo(
244
491
  () => ({
245
492
  flip: { dx, dy, sx, sy },
493
+ carry: { snapshot, progress: carryProgress },
246
494
  setRef,
247
495
  onLayout
248
496
  }),
249
- [dx, dy, sx, sy, setRef, onLayout]
497
+ [dx, dy, sx, sy, snapshot, carryProgress, setRef, onLayout]
250
498
  );
251
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)
513
+ );
514
+ }
515
+ function shouldSnap(transition, shouldReduceMotion) {
516
+ return shouldReduceMotion || transition?.type === "no-animation";
517
+ }
252
518
  function applyFlip(args) {
253
519
  const { source, target, dx, dy, sx, sy, transition, shouldReduceMotion } = args;
254
520
  const sourceCenterX = source.x + source.width / 2;
@@ -259,58 +525,29 @@ function applyFlip(args) {
259
525
  const deltaY = sourceCenterY - targetCenterY;
260
526
  const scaleX = target.width > 0 ? source.width / target.width : 1;
261
527
  const scaleY = target.height > 0 ? source.height / target.height : 1;
262
- if (shouldReduceMotion) {
263
- dx.value = 0;
264
- dy.value = 0;
265
- sx.value = 1;
266
- sy.value = 1;
267
- return;
268
- }
269
- if (transition?.type === "no-animation") {
528
+ if (shouldSnap(transition, shouldReduceMotion)) {
270
529
  dx.value = 0;
271
530
  dy.value = 0;
272
531
  sx.value = 1;
273
532
  sy.value = 1;
274
533
  return;
275
534
  }
276
- if (transition?.type === "timing") {
277
- const duration = transition.duration ?? 300;
278
- dx.value = withSequence(
279
- withTiming(deltaX, { duration: 0 }),
280
- withTiming(0, { duration })
281
- );
282
- dy.value = withSequence(
283
- withTiming(deltaY, { duration: 0 }),
284
- withTiming(0, { duration })
285
- );
286
- sx.value = withSequence(
287
- withTiming(scaleX, { duration: 0 }),
288
- withTiming(1, { duration })
289
- );
290
- sy.value = withSequence(
291
- withTiming(scaleY, { duration: 0 }),
292
- withTiming(1, { duration })
293
- );
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;
294
547
  return;
295
548
  }
296
- const springCfg = transition?.type === "spring" ? { ...DEFAULT_SPRING, ...transition } : { type: "spring", ...DEFAULT_SPRING };
297
- const springParams = springToReanimated(springCfg);
298
- dx.value = withSequence(
299
- withTiming(deltaX, { duration: 0 }),
300
- withSpring(0, springParams)
301
- );
302
- dy.value = withSequence(
303
- withTiming(deltaY, { duration: 0 }),
304
- withSpring(0, springParams)
305
- );
306
- sx.value = withSequence(
307
- withTiming(scaleX, { duration: 0 }),
308
- withSpring(1, springParams)
309
- );
310
- sy.value = withSequence(
311
- withTiming(scaleY, { duration: 0 }),
312
- withSpring(1, springParams)
313
- );
549
+ snapshot.value = styles;
550
+ progress.value = legBuilder(transition)(1, 0);
314
551
  }
315
552
  var alreadyChecked = false;
316
553
  function ensureReanimatedInstalled() {
@@ -324,7 +561,7 @@ function ensureReanimatedInstalled() {
324
561
  const major = parseInt(version.split(".")[0] ?? "0", 10);
325
562
  if (major < 4) {
326
563
  console.error(
327
- `[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).`
328
565
  );
329
566
  return;
330
567
  }
@@ -367,15 +604,28 @@ var COLOR_KEYS = [
367
604
  "shadowColor"
368
605
  ];
369
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
+ ];
370
616
  var ALL_KEYS = [
371
617
  ...TRANSFORM_KEYS,
372
618
  ...NUMERIC_TOP_LEVEL_KEYS,
373
619
  ...COLOR_KEYS,
374
- ...SHADOW_OFFSET_KEYS
620
+ ...SHADOW_OFFSET_KEYS,
621
+ ...STRUCTURED_KEYS
375
622
  ];
376
623
  var TRANSFORM_KEY_SET = new Set(TRANSFORM_KEYS);
377
624
  var COLOR_KEY_SET = new Set(COLOR_KEYS);
378
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([]);
379
629
  var GESTURE_LAYER_NAMES = [
380
630
  "hovered",
381
631
  "focused",
@@ -410,7 +660,8 @@ var DEFAULT_RESTING = {
410
660
  tintColor: "transparent",
411
661
  shadowColor: "transparent",
412
662
  shadowOffsetWidth: 0,
413
- shadowOffsetHeight: 0
663
+ shadowOffsetHeight: 0,
664
+ boxShadow: NO_BOX_SHADOW
414
665
  };
415
666
  function transitionFor(prop, transition) {
416
667
  if (!transition) return void 0;
@@ -501,6 +752,16 @@ function createMotionComponent(Component) {
501
752
  const [focused, setFocused] = useState(false);
502
753
  const [focusVisible, setFocusVisible] = useState(false);
503
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
+ };
504
765
  const touched = /* @__PURE__ */ new Set();
505
766
  collectTouchedKeys(touched, animateRecord);
506
767
  if (initialRecord) collectTouchedKeys(touched, initialRecord);
@@ -522,9 +783,19 @@ function createMotionComponent(Component) {
522
783
  }
523
784
  }
524
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
+ }
525
795
  const activeKeysRef = useRef(null);
526
796
  const hasTransformRef = useRef(false);
527
797
  const hasShadowOffsetRef = useRef(false);
798
+ const hasBoxShadowRef = useRef(false);
528
799
  const prevActive = activeKeysRef.current;
529
800
  let grew = prevActive === null;
530
801
  if (!grew && prevActive) {
@@ -545,6 +816,15 @@ function createMotionComponent(Component) {
545
816
  hasShadowOffsetRef.current = activeKeysRef.current.some(
546
817
  (k) => SHADOW_OFFSET_KEY_SET.has(k)
547
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
+ }
548
828
  }
549
829
  const drivenNow = /* @__PURE__ */ new Set();
550
830
  collectTouchedKeys(drivenNow, animateRecord);
@@ -555,17 +835,35 @@ function createMotionComponent(Component) {
555
835
  const activeKeys = activeKeysRef.current;
556
836
  const everDriven = everDrivenRef.current;
557
837
  const styleResting = {};
838
+ let styleRestingShadow = null;
558
839
  if (activeKeys.some((k) => !everDriven.has(k))) {
559
- const flat = StyleSheet.flatten(style);
840
+ const flat = getFlatStyle();
560
841
  if (flat) {
561
842
  for (const key of activeKeys) {
562
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
+ }
563
852
  const v = styleValueFor(flat, key);
564
853
  if (v !== void 0) styleResting[key] = v;
565
854
  }
566
855
  }
567
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
+ };
864
+ }
568
865
  const sharedValues = useAnimatableSharedValues((key) => {
866
+ if (key === "boxShadow") return boxShadowSeedRef.current.layers;
569
867
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
570
868
  const axis = shadowOffsetAxisFor(key);
571
869
  if (initial === false) {
@@ -582,11 +880,17 @@ function createMotionComponent(Component) {
582
880
  }
583
881
  return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
584
882
  });
883
+ const boxShadowInsets = useSharedValue(
884
+ boxShadowSeedRef.current.insets
885
+ );
585
886
  const styleRestingSig = stableSig(styleResting);
586
887
  useEffect(() => {
587
888
  for (const key of activeKeysRef.current) {
588
889
  if (everDrivenRef.current.has(key)) continue;
589
890
  sharedValues[key].value = styleResting[key] ?? DEFAULT_RESTING[key];
891
+ if (key === "boxShadow") {
892
+ boxShadowInsets.value = styleRestingShadow?.insets ?? null;
893
+ }
590
894
  }
591
895
  }, [styleRestingSig]);
592
896
  const pressedProgress = useSharedValue(0);
@@ -643,6 +947,30 @@ function createMotionComponent(Component) {
643
947
  }
644
948
  const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
645
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
+ }
646
974
  const target = SHADOW_OFFSET_KEY_SET.has(key) ? shadowOffsetAxisValue(
647
975
  baseRecord.shadowOffset,
648
976
  shadowOffsetAxisFor(key)
@@ -708,19 +1036,34 @@ function createMotionComponent(Component) {
708
1036
  isExiting,
709
1037
  shouldReduceMotion
710
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
+ };
711
1052
  const sharedLayout = useSharedLayout({
712
1053
  layoutId,
713
1054
  userRef: ref,
714
1055
  transition: isTopLevelTransition(transition) ? transition : void 0,
715
1056
  shouldReduceMotion,
716
- userOnLayout
1057
+ userOnLayout,
1058
+ readStyles: readSharedStyles
717
1059
  });
718
1060
  const flip = sharedLayout.flip;
719
- const hasLayoutId = layoutId !== void 0;
1061
+ const carry = sharedLayout.carry;
720
1062
  const animatedStyle = useAnimatedStyle(() => {
721
1063
  const activeKeys2 = activeKeysRef.current;
722
1064
  const hasTransform = hasTransformRef.current;
723
1065
  const hasShadowOffset = hasShadowOffsetRef.current;
1066
+ const hasBoxShadow = hasBoxShadowRef.current;
724
1067
  const out = {};
725
1068
  const transform = [];
726
1069
  let shadowOffsetW = 0;
@@ -729,12 +1072,15 @@ function createMotionComponent(Component) {
729
1072
  const pf = focusedProgress.value;
730
1073
  const pfv = focusVisibleProgress.value;
731
1074
  const pp = pressedProgress.value;
1075
+ const carried = carry.snapshot.value;
1076
+ const pc = carry.progress.value;
732
1077
  const layers = gestureSV.value;
733
1078
  const hoveredLayer = layers ? layers.hovered : null;
734
1079
  const focusedLayer = layers ? layers.focused : null;
735
1080
  const focusVisibleLayer = layers ? layers.focusVisible : null;
736
1081
  const pressedLayer = layers ? layers.pressed : null;
737
1082
  for (const key of activeKeys2) {
1083
+ if (key === "boxShadow") continue;
738
1084
  let v = sharedValues[key].value;
739
1085
  const isColor = COLOR_KEY_SET.has(key);
740
1086
  if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
@@ -753,6 +1099,12 @@ function createMotionComponent(Component) {
753
1099
  const t = pressedLayer[key];
754
1100
  v = isColor ? interpolateColor(pp, [0, 1], [v, t]) : v + (t - v) * pp;
755
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
+ }
756
1108
  if (TRANSFORM_KEY_SET.has(key)) {
757
1109
  transform.push(
758
1110
  ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v }
@@ -775,6 +1127,19 @@ function createMotionComponent(Component) {
775
1127
  if (hasShadowOffset) {
776
1128
  out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH };
777
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
+ }
778
1143
  return out;
779
1144
  });
780
1145
  const mergedStyle = useMemo(
@@ -849,7 +1214,9 @@ function useAnimatableSharedValues(init) {
849
1214
  const width = useSharedValue(init("width"));
850
1215
  const height = useSharedValue(init("height"));
851
1216
  const borderRadius = useSharedValue(init("borderRadius"));
852
- const shadowOpacity = useSharedValue(init("shadowOpacity"));
1217
+ const shadowOpacity = useSharedValue(
1218
+ init("shadowOpacity")
1219
+ );
853
1220
  const shadowRadius = useSharedValue(init("shadowRadius"));
854
1221
  const elevation = useSharedValue(init("elevation"));
855
1222
  const backgroundColor = useSharedValue(
@@ -865,6 +1232,7 @@ function useAnimatableSharedValues(init) {
865
1232
  const shadowOffsetHeight = useSharedValue(
866
1233
  init("shadowOffsetHeight")
867
1234
  );
1235
+ const boxShadow = useSharedValue(init("boxShadow"));
868
1236
  const ref = useRef(null);
869
1237
  if (ref.current === null) {
870
1238
  ref.current = {
@@ -889,7 +1257,8 @@ function useAnimatableSharedValues(init) {
889
1257
  tintColor,
890
1258
  shadowColor,
891
1259
  shadowOffsetWidth,
892
- shadowOffsetHeight
1260
+ shadowOffsetHeight,
1261
+ boxShadow
893
1262
  };
894
1263
  }
895
1264
  const map = ref.current;
@@ -974,6 +1343,20 @@ function shadowOffsetAxisFor(key) {
974
1343
  function shadowOffsetAxisValue(source, axis) {
975
1344
  return source?.[axis];
976
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
+ }
977
1360
  function styleValueFor(flat, key) {
978
1361
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
979
1362
  return shadowOffsetAxisValue(
@@ -1090,6 +1473,15 @@ function resolveGestureLayers(gesture) {
1090
1473
  if (!subState) continue;
1091
1474
  const resolved = {};
1092
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
+ }
1093
1485
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
1094
1486
  const axis = shadowOffsetAxisFor(key);
1095
1487
  const so = subState.shadowOffset;
@@ -1179,4 +1571,4 @@ function compose(user, ours) {
1179
1571
  };
1180
1572
  }
1181
1573
 
1182
- export { Presence, createMotionComponent, usePresence };
1574
+ export { Presence, createMotionComponent, pairBoxShadowLayers, resolveBoxShadowInput, usePresence };