@rootnative/inertia 0.0.1 → 0.0.3

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 (44) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/dist/{chunk-TDSO63CJ.js → chunk-3XTVY34H.js} +2 -2
  3. package/dist/{chunk-NXDJZD6A.mjs → chunk-46P57VMY.mjs} +1 -1
  4. package/dist/{chunk-7UDYEFBU.js → chunk-BP3Y2SHQ.js} +477 -298
  5. package/dist/{chunk-6SMPIOIC.mjs → chunk-BQQTHG2V.mjs} +1 -1
  6. package/dist/{chunk-DWCLIBYO.mjs → chunk-CSODMRJ7.mjs} +478 -299
  7. package/dist/chunk-FNVFV4EY.js +8 -0
  8. package/dist/chunk-FWQOXA43.js +8 -0
  9. package/dist/chunk-KBP4LR75.js +8 -0
  10. package/dist/{chunk-ALRHDFZE.mjs → chunk-O22NXXCZ.mjs} +1 -1
  11. package/dist/{chunk-CWLFUYIY.mjs → chunk-OQV66TBQ.mjs} +1 -1
  12. package/dist/{chunk-JVBXPF2G.mjs → chunk-SGUHE5CX.mjs} +1 -1
  13. package/dist/{chunk-2HYD2ZBK.js → chunk-W5MC3P4N.js} +2 -2
  14. package/dist/index.d.mts +231 -43
  15. package/dist/index.d.ts +231 -43
  16. package/dist/index.js +212 -23
  17. package/dist/index.mjs +204 -18
  18. package/dist/motion/Image.js +3 -3
  19. package/dist/motion/Image.mjs +2 -2
  20. package/dist/motion/Pressable.js +3 -3
  21. package/dist/motion/Pressable.mjs +2 -2
  22. package/dist/motion/ScrollView.js +3 -3
  23. package/dist/motion/ScrollView.mjs +2 -2
  24. package/dist/motion/Text.js +3 -3
  25. package/dist/motion/Text.mjs +2 -2
  26. package/dist/motion/View.js +3 -3
  27. package/dist/motion/View.mjs +2 -2
  28. package/llms.txt +5 -0
  29. package/package.json +1 -1
  30. package/src/index.ts +10 -0
  31. package/src/layout/index.ts +1 -0
  32. package/src/layout/sharedRegistry.ts +51 -2
  33. package/src/motion/createMotionComponent.tsx +781 -502
  34. package/src/presence/Presence.tsx +73 -10
  35. package/src/values/index.ts +13 -0
  36. package/src/values/useAnimation.ts +15 -1
  37. package/src/values/useAnimator.ts +83 -0
  38. package/src/values/useColorCascade.ts +125 -0
  39. package/src/values/useInterpolatedStyle.ts +319 -0
  40. package/src/values/useMotionValue.ts +20 -2
  41. package/src/values/useSpring.ts +12 -0
  42. package/dist/chunk-3UTJJ4A3.js +0 -8
  43. package/dist/chunk-4QGXK6TF.js +0 -8
  44. package/dist/chunk-Z7HIOFKQ.js +0 -8
@@ -2,7 +2,8 @@ import { useNamedTransitions, resolveNamedTransitionProp, lookupNamedTransition,
2
2
  import { stableSig, resolveAnimatableValue, isTopLevelTransition, resolveTransition, ensureWorkletEasing, springToReanimated, DEFAULT_SPRING } from './chunk-CY7Y64C3.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
- import Animated, { useSharedValue, useAnimatedStyle, interpolateColor, reanimatedVersion, runOnJS, LinearTransition, withSequence, withTiming, withSpring } from 'react-native-reanimated';
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);
94
+ }
95
+ for (const [key, el] of activeExiting) {
96
+ if (byKey.has(key)) continue;
97
+ byKey.set(key, el);
98
+ order.push(key);
75
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
  {
@@ -124,11 +154,23 @@ function resolveLayoutTransition(layout) {
124
154
  var REGISTRY = /* @__PURE__ */ new Map();
125
155
  var SHARED_LAYOUT_TTL_MS = 1e3;
126
156
  var now = () => Date.now();
157
+ var lastSweep = 0;
158
+ function sweepExpired(at) {
159
+ if (at - lastSweep < SHARED_LAYOUT_TTL_MS) return;
160
+ lastSweep = at;
161
+ for (const [id, entry] of REGISTRY) {
162
+ if (entry.expiresAt < at) REGISTRY.delete(id);
163
+ }
164
+ }
127
165
  function registerLayout(id, rect) {
128
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
166
+ const at = now();
167
+ sweepExpired(at);
168
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
129
169
  }
130
170
  function releaseLayout(id, rect) {
131
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
171
+ const at = now();
172
+ sweepExpired(at);
173
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
132
174
  }
133
175
  function consumeLayout(id) {
134
176
  const entry = REGISTRY.get(id);
@@ -389,28 +431,8 @@ function createMotionComponent(Component) {
389
431
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
390
432
  Component
391
433
  );
392
- const Motion = forwardRef(function Motion2(props, ref) {
393
- const {
394
- initial,
395
- animate,
396
- exit,
397
- transition: transitionProp,
398
- variants,
399
- controller,
400
- gesture,
401
- layout: layoutProp,
402
- layoutId,
403
- onAnimationEnd,
404
- style,
405
- onLayout: userOnLayout,
406
- ...rest
407
- } = props;
408
- const namedTransitions = useNamedTransitions();
409
- const transition = resolveNamedTransitionProp(
410
- transitionProp,
411
- namedTransitions
412
- );
413
- const layout = typeof layoutProp === "string" ? lookupNamedTransition(layoutProp, namedTransitions) : layoutProp;
434
+ const PlainHost = forwardRef(function PlainHost2(props, ref) {
435
+ const { style, ...rest } = props;
414
436
  if (__DEV__ && typeof style === "function") {
415
437
  throw new Error(
416
438
  "[inertia] `style` must be a style object or array of style objects, not a function. The function-form `style={(state) => ...}` Pressable API is not supported \u2014 use `gesture.pressed` (or `gesture.focused`, etc.) to drive state-dependent styling instead."
@@ -418,293 +440,402 @@ function createMotionComponent(Component) {
418
440
  }
419
441
  const presence = usePresence();
420
442
  const isExiting = presence !== null && presence.isPresent === false;
421
- const shouldReduceMotion = useShouldReduceMotion();
422
- const onAnimationEndRef = useRef(onAnimationEnd);
423
- onAnimationEndRef.current = onAnimationEnd;
424
- const variantKey = useControllerKey(controller);
425
- const resolvedAnimate = resolveAnimateInput(
426
- animate,
427
- variants,
428
- variantKey
443
+ const safeToRemoveRef = useRef(void 0);
444
+ safeToRemoveRef.current = presence?.safeToRemove;
445
+ useEffect(() => {
446
+ if (isExiting) safeToRemoveRef.current?.();
447
+ }, [isExiting]);
448
+ return /* @__PURE__ */ jsx(
449
+ AnimatedComponent,
450
+ {
451
+ ref,
452
+ ...rest,
453
+ style
454
+ }
429
455
  );
430
- const animateRecord = resolvedAnimate ?? {};
431
- const initialRecord = initial && initial !== false ? initial : void 0;
432
- const exitRecord = exit ? exit : void 0;
433
- const [pressed, setPressed] = useState(false);
434
- const [focused, setFocused] = useState(false);
435
- const [focusVisible, setFocusVisible] = useState(false);
436
- const [hovered, setHovered] = useState(false);
437
- const touched = /* @__PURE__ */ new Set();
438
- collectTouchedKeys(touched, animateRecord);
439
- if (initialRecord) collectTouchedKeys(touched, initialRecord);
440
- if (variants) {
441
- for (const variant of Object.values(variants)) {
442
- if (!variant) continue;
443
- collectTouchedKeys(touched, variant);
456
+ });
457
+ PlainHost.displayName = `MotionPlain(${Component.displayName ?? Component.name ?? "Component"})`;
458
+ const MotionAnimated = forwardRef(
459
+ function MotionAnimated2(props, ref) {
460
+ const {
461
+ initial,
462
+ animate,
463
+ exit,
464
+ transition: transitionProp,
465
+ variants,
466
+ controller,
467
+ gesture,
468
+ layout: layoutProp,
469
+ layoutId,
470
+ onAnimationEnd,
471
+ style,
472
+ onLayout: userOnLayout,
473
+ ...rest
474
+ } = props;
475
+ const namedTransitions = useNamedTransitions();
476
+ const transition = resolveNamedTransitionProp(
477
+ transitionProp,
478
+ namedTransitions
479
+ );
480
+ const layout = typeof layoutProp === "string" ? lookupNamedTransition(layoutProp, namedTransitions) : layoutProp;
481
+ if (__DEV__ && typeof style === "function") {
482
+ throw new Error(
483
+ "[inertia] `style` must be a style object or array of style objects, not a function. The function-form `style={(state) => ...}` Pressable API is not supported \u2014 use `gesture.pressed` (or `gesture.focused`, etc.) to drive state-dependent styling instead."
484
+ );
444
485
  }
445
- }
446
- if (gesture) {
447
- for (const subState of [
448
- gesture.pressed,
449
- gesture.focused,
450
- gesture.focusVisible,
451
- gesture.hovered
452
- ]) {
453
- if (!subState) continue;
454
- collectTouchedKeys(touched, subState);
486
+ const presence = usePresence();
487
+ const isExiting = presence !== null && presence.isPresent === false;
488
+ const shouldReduceMotion = useShouldReduceMotion();
489
+ const onAnimationEndRef = useRef(onAnimationEnd);
490
+ onAnimationEndRef.current = onAnimationEnd;
491
+ const variantKey = useControllerKey(controller);
492
+ const resolvedAnimate = resolveAnimateInput(
493
+ animate,
494
+ variants,
495
+ variantKey
496
+ );
497
+ const animateRecord = resolvedAnimate ?? {};
498
+ const initialRecord = initial && initial !== false ? initial : void 0;
499
+ const exitRecord = exit ? exit : void 0;
500
+ const [pressed, setPressed] = useState(false);
501
+ const [focused, setFocused] = useState(false);
502
+ const [focusVisible, setFocusVisible] = useState(false);
503
+ const [hovered, setHovered] = useState(false);
504
+ const touched = /* @__PURE__ */ new Set();
505
+ collectTouchedKeys(touched, animateRecord);
506
+ if (initialRecord) collectTouchedKeys(touched, initialRecord);
507
+ if (variants) {
508
+ for (const variant of Object.values(variants)) {
509
+ if (!variant) continue;
510
+ collectTouchedKeys(touched, variant);
511
+ }
455
512
  }
456
- }
457
- if (exitRecord) collectTouchedKeys(touched, exitRecord);
458
- const activeKeysRef = useRef(null);
459
- const hasTransformRef = useRef(false);
460
- const hasShadowOffsetRef = useRef(false);
461
- const prevActive = activeKeysRef.current;
462
- let grew = prevActive === null;
463
- if (!grew && prevActive) {
464
- for (const k of touched) {
465
- if (!prevActive.includes(k)) {
466
- grew = true;
467
- break;
513
+ if (gesture) {
514
+ for (const subState of [
515
+ gesture.pressed,
516
+ gesture.focused,
517
+ gesture.focusVisible,
518
+ gesture.hovered
519
+ ]) {
520
+ if (!subState) continue;
521
+ collectTouchedKeys(touched, subState);
468
522
  }
469
523
  }
470
- }
471
- if (grew) {
472
- const merged = new Set(prevActive ?? []);
473
- for (const k of touched) merged.add(k);
474
- activeKeysRef.current = ALL_KEYS.filter((k) => merged.has(k));
475
- hasTransformRef.current = activeKeysRef.current.some(
476
- (k) => TRANSFORM_KEY_SET.has(k)
477
- );
478
- hasShadowOffsetRef.current = activeKeysRef.current.some(
479
- (k) => SHADOW_OFFSET_KEY_SET.has(k)
480
- );
481
- }
482
- const sharedValues = useAnimatableSharedValues((key) => {
483
- if (SHADOW_OFFSET_KEY_SET.has(key)) {
484
- const axis = shadowOffsetAxisFor(key);
485
- if (initial === false) {
486
- return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
524
+ if (exitRecord) collectTouchedKeys(touched, exitRecord);
525
+ const activeKeysRef = useRef(null);
526
+ const hasTransformRef = useRef(false);
527
+ const hasShadowOffsetRef = useRef(false);
528
+ const prevActive = activeKeysRef.current;
529
+ let grew = prevActive === null;
530
+ if (!grew && prevActive) {
531
+ for (const k of touched) {
532
+ if (!prevActive.includes(k)) {
533
+ grew = true;
534
+ break;
535
+ }
487
536
  }
488
- return shadowOffsetAxisValue(
489
- initialRecord?.shadowOffset,
490
- axis
491
- ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
492
537
  }
493
- if (initial === false) {
494
- const a = animateRecord[key];
495
- return restValue(a) ?? DEFAULT_RESTING[key];
538
+ if (grew) {
539
+ const merged = new Set(prevActive ?? []);
540
+ for (const k of touched) merged.add(k);
541
+ activeKeysRef.current = ALL_KEYS.filter((k) => merged.has(k));
542
+ hasTransformRef.current = activeKeysRef.current.some(
543
+ (k) => TRANSFORM_KEY_SET.has(k)
544
+ );
545
+ hasShadowOffsetRef.current = activeKeysRef.current.some(
546
+ (k) => SHADOW_OFFSET_KEY_SET.has(k)
547
+ );
496
548
  }
497
- return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
498
- });
499
- const pressedProgress = useSharedValue(0);
500
- const focusedProgress = useSharedValue(0);
501
- const focusVisibleProgress = useSharedValue(0);
502
- const hoveredProgress = useSharedValue(0);
503
- const gestureSV = useSharedValue(
504
- resolveGestureLayers(gesture)
505
- );
506
- const gestureTargetsSig = stableSig(gesture);
507
- useEffect(() => {
508
- gestureSV.value = resolveGestureLayers(gesture);
509
- }, [gestureTargetsSig]);
510
- const baseRecord = isExiting && exitRecord ? { ...animateRecord, ...exitRecord } : animateRecord;
511
- const baseSig = stableSig(baseRecord) + (isExiting ? "|exit" : "") + (shouldReduceMotion ? "|rm" : "");
512
- const transitionSig = stableSig(transition);
513
- const safeToRemoveRef = useRef(void 0);
514
- safeToRemoveRef.current = presence?.safeToRemove;
515
- useEffect(() => {
516
- if (isExiting && (!exitRecord || Object.keys(exitRecord).length === 0)) {
517
- safeToRemoveRef.current?.();
518
- return;
549
+ const drivenNow = /* @__PURE__ */ new Set();
550
+ collectTouchedKeys(drivenNow, animateRecord);
551
+ if (initialRecord) collectTouchedKeys(drivenNow, initialRecord);
552
+ if (isExiting && exitRecord) collectTouchedKeys(drivenNow, exitRecord);
553
+ const everDrivenRef = useRef(/* @__PURE__ */ new Set());
554
+ for (const k of drivenNow) everDrivenRef.current.add(k);
555
+ const activeKeys = activeKeysRef.current;
556
+ const everDriven = everDrivenRef.current;
557
+ const styleResting = {};
558
+ if (activeKeys.some((k) => !everDriven.has(k))) {
559
+ const flat = StyleSheet.flatten(style);
560
+ if (flat) {
561
+ for (const key of activeKeys) {
562
+ if (everDriven.has(key)) continue;
563
+ const v = styleValueFor(flat, key);
564
+ if (v !== void 0) styleResting[key] = v;
565
+ }
566
+ }
519
567
  }
520
- let pending = 0;
521
- let done = false;
522
- const onSettle = () => {
523
- if (done) return;
524
- pending--;
525
- if (pending <= 0) {
526
- done = true;
527
- if (isExiting) safeToRemoveRef.current?.();
568
+ const sharedValues = useAnimatableSharedValues((key) => {
569
+ if (SHADOW_OFFSET_KEY_SET.has(key)) {
570
+ const axis = shadowOffsetAxisFor(key);
571
+ if (initial === false) {
572
+ return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
573
+ }
574
+ return shadowOffsetAxisValue(
575
+ initialRecord?.shadowOffset,
576
+ axis
577
+ ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
528
578
  }
529
- };
530
- let transformPending = 0;
531
- for (const k of ALL_KEYS) {
532
- if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
533
- transformPending++;
579
+ if (initial === false) {
580
+ const a = animateRecord[key];
581
+ return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
534
582
  }
535
- }
536
- const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
537
- for (const key of ALL_KEYS) {
538
- const target = SHADOW_OFFSET_KEY_SET.has(key) ? shadowOffsetAxisValue(
539
- baseRecord.shadowOffset,
540
- shadowOffsetAxisFor(key)
541
- ) : baseRecord[key];
542
- if (target === void 0) continue;
543
- const cfg = shouldReduceMotion ? { type: "no-animation" } : transitionFor(
583
+ return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
584
+ });
585
+ const styleRestingSig = stableSig(styleResting);
586
+ useEffect(() => {
587
+ for (const key of activeKeysRef.current) {
588
+ if (everDrivenRef.current.has(key)) continue;
589
+ sharedValues[key].value = styleResting[key] ?? DEFAULT_RESTING[key];
590
+ }
591
+ }, [styleRestingSig]);
592
+ const pressedProgress = useSharedValue(0);
593
+ const focusedProgress = useSharedValue(0);
594
+ const focusVisibleProgress = useSharedValue(0);
595
+ const hoveredProgress = useSharedValue(0);
596
+ useEffect(
597
+ () => () => {
598
+ cancelAnimation(pressedProgress);
599
+ cancelAnimation(focusedProgress);
600
+ cancelAnimation(focusVisibleProgress);
601
+ cancelAnimation(hoveredProgress);
602
+ },
603
+ // The progress SVs are identity-stable per hook instance.
604
+ // eslint-disable-next-line react-hooks/exhaustive-deps
605
+ []
606
+ );
607
+ const gestureSV = useSharedValue(
608
+ resolveGestureLayers(gesture)
609
+ );
610
+ const gestureTargetsSig = stableSig(gesture);
611
+ useEffect(() => {
612
+ gestureSV.value = resolveGestureLayers(gesture);
613
+ }, [gestureTargetsSig]);
614
+ const baseRecord = isExiting && exitRecord ? { ...animateRecord, ...exitRecord } : animateRecord;
615
+ const baseSig = stableSig(baseRecord) + (isExiting ? "|exit" : "") + (shouldReduceMotion ? "|rm" : "");
616
+ const transitionSig = stableSig(transition);
617
+ const safeToRemoveRef = useRef(void 0);
618
+ safeToRemoveRef.current = presence?.safeToRemove;
619
+ useEffect(() => {
620
+ if (isExiting && (!exitRecord || Object.keys(exitRecord).length === 0)) {
621
+ safeToRemoveRef.current?.();
622
+ return;
623
+ }
624
+ let pending = 0;
625
+ let done = false;
626
+ const onSettle = () => {
627
+ if (done) return;
628
+ pending--;
629
+ if (pending <= 0) {
630
+ done = true;
631
+ if (isExiting) safeToRemoveRef.current?.();
632
+ }
633
+ };
634
+ const configFor = (key) => shouldReduceMotion ? { type: "no-animation" } : transitionFor(
544
635
  SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
545
636
  transition
546
637
  );
547
- if (isExiting) pending++;
548
- const factory = makeKeyCallbackFactory(
549
- key,
550
- sharedValues[key],
551
- targetEndValue(target),
552
- onAnimationEndRef,
553
- {
554
- stepCount: stepCountOf(target),
555
- totalIterations: totalIterationsOf(cfg)
556
- },
557
- isExiting ? onSettle : void 0,
558
- TRANSFORM_KEY_SET.has(key) ? transformGroup : void 0
559
- );
560
- sharedValues[key].value = resolveAnimatableValue(
561
- target,
562
- cfg,
563
- factory
564
- );
565
- }
566
- if (isExiting && pending === 0) {
567
- safeToRemoveRef.current?.();
568
- }
569
- }, [baseSig, transitionSig]);
570
- useGestureLayerProgress(
571
- pressedProgress,
572
- pressed,
573
- gesture?.pressed != null,
574
- "pressed",
575
- transition,
576
- isExiting,
577
- shouldReduceMotion
578
- );
579
- useGestureLayerProgress(
580
- focusedProgress,
581
- focused,
582
- gesture?.focused != null,
583
- "focused",
584
- transition,
585
- isExiting,
586
- shouldReduceMotion
587
- );
588
- useGestureLayerProgress(
589
- focusVisibleProgress,
590
- focusVisible,
591
- gesture?.focusVisible != null,
592
- "focusVisible",
593
- transition,
594
- isExiting,
595
- shouldReduceMotion
596
- );
597
- useGestureLayerProgress(
598
- hoveredProgress,
599
- hovered,
600
- gesture?.hovered != null,
601
- "hovered",
602
- transition,
603
- isExiting,
604
- shouldReduceMotion
605
- );
606
- const sharedLayout = useSharedLayout({
607
- layoutId,
608
- userRef: ref,
609
- transition: isTopLevelTransition(transition) ? transition : void 0,
610
- shouldReduceMotion,
611
- userOnLayout
612
- });
613
- const flip = sharedLayout.flip;
614
- const hasLayoutId = layoutId !== void 0;
615
- const animatedStyle = useAnimatedStyle(() => {
616
- const activeKeys = activeKeysRef.current;
617
- const hasTransform = hasTransformRef.current;
618
- const hasShadowOffset = hasShadowOffsetRef.current;
619
- const out = {};
620
- const transform = [];
621
- let shadowOffsetW = 0;
622
- let shadowOffsetH = 0;
623
- const ph = hoveredProgress.value;
624
- const pf = focusedProgress.value;
625
- const pfv = focusVisibleProgress.value;
626
- const pp = pressedProgress.value;
627
- const layers = gestureSV.value;
628
- const hoveredLayer = layers ? layers.hovered : null;
629
- const focusedLayer = layers ? layers.focused : null;
630
- const focusVisibleLayer = layers ? layers.focusVisible : null;
631
- const pressedLayer = layers ? layers.pressed : null;
632
- for (const key of activeKeys) {
633
- let v = sharedValues[key].value;
634
- const isColor = COLOR_KEY_SET.has(key);
635
- if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
636
- const t = hoveredLayer[key];
637
- v = isColor ? interpolateColor(ph, [0, 1], [v, t]) : v + (t - v) * ph;
638
+ let transformPending = 0;
639
+ for (const k of ALL_KEYS) {
640
+ if (!TRANSFORM_KEY_SET.has(k) || baseRecord[k] === void 0) continue;
641
+ if (!reachesTerminalPhase(configFor(k))) continue;
642
+ transformPending++;
638
643
  }
639
- if (focusedLayer && pf > 0 && focusedLayer[key] !== void 0) {
640
- const t = focusedLayer[key];
641
- v = isColor ? interpolateColor(pf, [0, 1], [v, t]) : v + (t - v) * pf;
644
+ const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
645
+ for (const key of ALL_KEYS) {
646
+ const target = SHADOW_OFFSET_KEY_SET.has(key) ? shadowOffsetAxisValue(
647
+ baseRecord.shadowOffset,
648
+ shadowOffsetAxisFor(key)
649
+ ) : baseRecord[key];
650
+ if (target === void 0) continue;
651
+ const cfg = configFor(key);
652
+ if (isExiting && reachesTerminalPhase(cfg)) pending++;
653
+ const factory = makeKeyCallbackFactory(
654
+ key,
655
+ sharedValues[key],
656
+ targetEndValue(target),
657
+ onAnimationEndRef,
658
+ {
659
+ stepCount: stepCountOf(target),
660
+ totalIterations: totalIterationsOf(cfg)
661
+ },
662
+ isExiting ? onSettle : void 0,
663
+ TRANSFORM_KEY_SET.has(key) ? transformGroup : void 0
664
+ );
665
+ sharedValues[key].value = resolveAnimatableValue(
666
+ target,
667
+ cfg,
668
+ factory
669
+ );
642
670
  }
643
- if (focusVisibleLayer && pfv > 0 && focusVisibleLayer[key] !== void 0) {
644
- const t = focusVisibleLayer[key];
645
- v = isColor ? interpolateColor(pfv, [0, 1], [v, t]) : v + (t - v) * pfv;
671
+ if (isExiting && pending === 0) {
672
+ safeToRemoveRef.current?.();
646
673
  }
647
- if (pressedLayer && pp > 0 && pressedLayer[key] !== void 0) {
648
- const t = pressedLayer[key];
649
- v = isColor ? interpolateColor(pp, [0, 1], [v, t]) : v + (t - v) * pp;
674
+ }, [baseSig, transitionSig]);
675
+ useGestureLayerProgress(
676
+ pressedProgress,
677
+ pressed,
678
+ gesture?.pressed != null,
679
+ "pressed",
680
+ transition,
681
+ isExiting,
682
+ shouldReduceMotion
683
+ );
684
+ useGestureLayerProgress(
685
+ focusedProgress,
686
+ focused,
687
+ gesture?.focused != null,
688
+ "focused",
689
+ transition,
690
+ isExiting,
691
+ shouldReduceMotion
692
+ );
693
+ useGestureLayerProgress(
694
+ focusVisibleProgress,
695
+ focusVisible,
696
+ gesture?.focusVisible != null,
697
+ "focusVisible",
698
+ transition,
699
+ isExiting,
700
+ shouldReduceMotion
701
+ );
702
+ useGestureLayerProgress(
703
+ hoveredProgress,
704
+ hovered,
705
+ gesture?.hovered != null,
706
+ "hovered",
707
+ transition,
708
+ isExiting,
709
+ shouldReduceMotion
710
+ );
711
+ const sharedLayout = useSharedLayout({
712
+ layoutId,
713
+ userRef: ref,
714
+ transition: isTopLevelTransition(transition) ? transition : void 0,
715
+ shouldReduceMotion,
716
+ userOnLayout
717
+ });
718
+ const flip = sharedLayout.flip;
719
+ const hasLayoutId = layoutId !== void 0;
720
+ const animatedStyle = useAnimatedStyle(() => {
721
+ const activeKeys2 = activeKeysRef.current;
722
+ const hasTransform = hasTransformRef.current;
723
+ const hasShadowOffset = hasShadowOffsetRef.current;
724
+ const out = {};
725
+ const transform = [];
726
+ let shadowOffsetW = 0;
727
+ let shadowOffsetH = 0;
728
+ const ph = hoveredProgress.value;
729
+ const pf = focusedProgress.value;
730
+ const pfv = focusVisibleProgress.value;
731
+ const pp = pressedProgress.value;
732
+ const layers = gestureSV.value;
733
+ const hoveredLayer = layers ? layers.hovered : null;
734
+ const focusedLayer = layers ? layers.focused : null;
735
+ const focusVisibleLayer = layers ? layers.focusVisible : null;
736
+ const pressedLayer = layers ? layers.pressed : null;
737
+ for (const key of activeKeys2) {
738
+ let v = sharedValues[key].value;
739
+ const isColor = COLOR_KEY_SET.has(key);
740
+ if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
741
+ const t = hoveredLayer[key];
742
+ v = isColor ? interpolateColor(ph, [0, 1], [v, t]) : v + (t - v) * ph;
743
+ }
744
+ if (focusedLayer && pf > 0 && focusedLayer[key] !== void 0) {
745
+ const t = focusedLayer[key];
746
+ v = isColor ? interpolateColor(pf, [0, 1], [v, t]) : v + (t - v) * pf;
747
+ }
748
+ if (focusVisibleLayer && pfv > 0 && focusVisibleLayer[key] !== void 0) {
749
+ const t = focusVisibleLayer[key];
750
+ v = isColor ? interpolateColor(pfv, [0, 1], [v, t]) : v + (t - v) * pfv;
751
+ }
752
+ if (pressedLayer && pp > 0 && pressedLayer[key] !== void 0) {
753
+ const t = pressedLayer[key];
754
+ v = isColor ? interpolateColor(pp, [0, 1], [v, t]) : v + (t - v) * pp;
755
+ }
756
+ if (TRANSFORM_KEY_SET.has(key)) {
757
+ transform.push(
758
+ ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v }
759
+ );
760
+ } else if (key === "shadowOffsetWidth") {
761
+ shadowOffsetW = v;
762
+ } else if (key === "shadowOffsetHeight") {
763
+ shadowOffsetH = v;
764
+ } else {
765
+ out[key] = v;
766
+ }
650
767
  }
651
- if (TRANSFORM_KEY_SET.has(key)) {
652
- transform.push(
653
- ROTATION_KEYS.has(key) ? { [key]: `${v}deg` } : { [key]: v }
654
- );
655
- } else if (key === "shadowOffsetWidth") {
656
- shadowOffsetW = v;
657
- } else if (key === "shadowOffsetHeight") {
658
- shadowOffsetH = v;
659
- } else {
660
- out[key] = v;
768
+ if (hasLayoutId) {
769
+ transform.push({ translateX: flip.dx.value });
770
+ transform.push({ translateY: flip.dy.value });
771
+ transform.push({ scaleX: flip.sx.value });
772
+ transform.push({ scaleY: flip.sy.value });
661
773
  }
662
- }
663
- if (hasLayoutId) {
664
- transform.push({ translateX: flip.dx.value });
665
- transform.push({ translateY: flip.dy.value });
666
- transform.push({ scaleX: flip.sx.value });
667
- transform.push({ scaleY: flip.sy.value });
668
- }
669
- if (hasTransform || hasLayoutId) out.transform = transform;
670
- if (hasShadowOffset) {
671
- out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH };
672
- }
673
- return out;
674
- });
675
- const mergedStyle = useMemo(
676
- () => isExiting ? [style, animatedStyle, EXITING_POINTER_EVENTS_STYLE] : [style, animatedStyle],
677
- [style, animatedStyle, isExiting]
678
- );
679
- const gestureHandlers = useGestureHandlers(
680
- gesture,
681
- rest,
682
- setPressed,
683
- setFocused,
684
- setFocusVisible,
685
- setHovered
686
- );
687
- const layoutSig = stableSig(layout);
688
- const layoutTransition = useMemo(
689
- () => shouldReduceMotion ? void 0 : resolveLayoutTransition(layout),
690
- // eslint-disable-next-line react-hooks/exhaustive-deps
691
- [layoutSig, shouldReduceMotion]
692
- );
693
- return /* @__PURE__ */ jsx(
694
- AnimatedComponent,
695
- {
696
- ref: sharedLayout.setRef,
697
- ...rest,
698
- ...gestureHandlers,
699
- onLayout: sharedLayout.onLayout,
700
- layout: layoutTransition,
701
- style: mergedStyle
702
- }
703
- );
774
+ if (hasTransform || hasLayoutId) out.transform = transform;
775
+ if (hasShadowOffset) {
776
+ out.shadowOffset = { width: shadowOffsetW, height: shadowOffsetH };
777
+ }
778
+ return out;
779
+ });
780
+ const mergedStyle = useMemo(
781
+ () => isExiting ? [style, animatedStyle, EXITING_POINTER_EVENTS_STYLE] : [style, animatedStyle],
782
+ [style, animatedStyle, isExiting]
783
+ );
784
+ const gestureHandlers = useGestureHandlers(
785
+ gesture,
786
+ rest,
787
+ setPressed,
788
+ setFocused,
789
+ setFocusVisible,
790
+ setHovered
791
+ );
792
+ const layoutSig = stableSig(layout);
793
+ const layoutTransition = useMemo(
794
+ () => shouldReduceMotion ? void 0 : resolveLayoutTransition(layout),
795
+ // eslint-disable-next-line react-hooks/exhaustive-deps
796
+ [layoutSig, shouldReduceMotion]
797
+ );
798
+ return /* @__PURE__ */ jsx(
799
+ AnimatedComponent,
800
+ {
801
+ ref: sharedLayout.setRef,
802
+ ...rest,
803
+ ...gestureHandlers,
804
+ onLayout: sharedLayout.onLayout,
805
+ layout: layoutTransition,
806
+ style: mergedStyle
807
+ }
808
+ );
809
+ }
810
+ );
811
+ MotionAnimated.displayName = `Motion(${Component.displayName ?? Component.name ?? "Component"})`;
812
+ const Motion = forwardRef(function Motion2(props, ref) {
813
+ if (hasMotionProps(props)) {
814
+ return /* @__PURE__ */ jsx(MotionAnimated, { ref, ...props });
815
+ }
816
+ return /* @__PURE__ */ jsx(PlainHost, { ref, ...props });
704
817
  });
705
818
  Motion.displayName = `Motion(${Component.displayName ?? Component.name ?? "Component"})`;
706
819
  return Motion;
707
820
  }
821
+ var MOTION_PROP_KEYS = [
822
+ "initial",
823
+ "animate",
824
+ "exit",
825
+ "transition",
826
+ "variants",
827
+ "controller",
828
+ "gesture",
829
+ "layout",
830
+ "layoutId",
831
+ "onAnimationEnd"
832
+ ];
833
+ function hasMotionProps(props) {
834
+ for (const key of MOTION_PROP_KEYS) {
835
+ if (props[key] !== void 0) return true;
836
+ }
837
+ return false;
838
+ }
708
839
  function useAnimatableSharedValues(init) {
709
840
  const translateX = useSharedValue(init("translateX"));
710
841
  const translateY = useSharedValue(init("translateY"));
@@ -761,6 +892,17 @@ function useAnimatableSharedValues(init) {
761
892
  shadowOffsetHeight
762
893
  };
763
894
  }
895
+ const map = ref.current;
896
+ useEffect(
897
+ () => () => {
898
+ for (const key in map) {
899
+ cancelAnimation(map[key]);
900
+ }
901
+ },
902
+ // `map` is identity-stable per hook instance.
903
+ // eslint-disable-next-line react-hooks/exhaustive-deps
904
+ []
905
+ );
764
906
  return ref.current;
765
907
  }
766
908
  function makeKeyCallbackFactory(key, sharedValue, target, onAnimationEndRef, meta, onSettle, transformGroup) {
@@ -832,6 +974,40 @@ function shadowOffsetAxisFor(key) {
832
974
  function shadowOffsetAxisValue(source, axis) {
833
975
  return source?.[axis];
834
976
  }
977
+ function styleValueFor(flat, key) {
978
+ if (SHADOW_OFFSET_KEY_SET.has(key)) {
979
+ return shadowOffsetAxisValue(
980
+ flat.shadowOffset,
981
+ shadowOffsetAxisFor(key)
982
+ );
983
+ }
984
+ if (TRANSFORM_KEY_SET.has(key)) {
985
+ const list = flat.transform;
986
+ if (!Array.isArray(list)) return void 0;
987
+ for (const entry of list) {
988
+ if (!entry || typeof entry !== "object") continue;
989
+ const v2 = entry[key];
990
+ if (v2 === void 0) continue;
991
+ if (ROTATION_KEYS.has(key)) return parseAngleDegrees(v2);
992
+ return typeof v2 === "number" ? v2 : void 0;
993
+ }
994
+ return void 0;
995
+ }
996
+ const v = flat[key];
997
+ if (COLOR_KEY_SET.has(key)) {
998
+ return typeof v === "string" || typeof v === "number" ? v : void 0;
999
+ }
1000
+ return typeof v === "number" ? v : void 0;
1001
+ }
1002
+ function parseAngleDegrees(v) {
1003
+ if (typeof v === "number") return v;
1004
+ if (typeof v !== "string") return void 0;
1005
+ const deg = /^(-?\d*\.?\d+)deg$/.exec(v);
1006
+ if (deg) return Number(deg[1]);
1007
+ const rad = /^(-?\d*\.?\d+)rad$/.exec(v);
1008
+ if (rad) return Number(rad[1]) * 180 / Math.PI;
1009
+ return void 0;
1010
+ }
835
1011
  function collectTouchedKeys(touched, record) {
836
1012
  for (const k of ALL_KEYS) {
837
1013
  if (k in record) touched.add(k);
@@ -855,6 +1031,9 @@ function totalIterationsOf(cfg) {
855
1031
  if (r.count === "infinite") return Number.POSITIVE_INFINITY;
856
1032
  return r.count;
857
1033
  }
1034
+ function reachesTerminalPhase(cfg) {
1035
+ return Number.isFinite(totalIterationsOf(cfg));
1036
+ }
858
1037
  function targetEndValue(v) {
859
1038
  if (v === void 0) return void 0;
860
1039
  if (typeof v === "number" || typeof v === "string") return v;