@rootnative/inertia 0.0.2 → 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 (37) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/{chunk-NKA6XR77.js → chunk-3XTVY34H.js} +2 -2
  3. package/dist/{chunk-V6BTXHZK.mjs → chunk-46P57VMY.mjs} +1 -1
  4. package/dist/{chunk-IWLU3VDE.js → chunk-BP3Y2SHQ.js} +132 -26
  5. package/dist/{chunk-TUMNV4P7.mjs → chunk-BQQTHG2V.mjs} +1 -1
  6. package/dist/{chunk-LYMLQ6WQ.mjs → chunk-CSODMRJ7.mjs} +132 -26
  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-2DHKV2XY.mjs → chunk-O22NXXCZ.mjs} +1 -1
  11. package/dist/{chunk-HFDZAHWP.mjs → chunk-OQV66TBQ.mjs} +1 -1
  12. package/dist/{chunk-ZN3PNGHQ.mjs → chunk-SGUHE5CX.mjs} +1 -1
  13. package/dist/{chunk-TUYHTHVV.js → chunk-W5MC3P4N.js} +2 -2
  14. package/dist/index.d.mts +10 -8
  15. package/dist/index.d.ts +10 -8
  16. package/dist/index.js +41 -28
  17. package/dist/index.mjs +34 -21
  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/package.json +1 -1
  29. package/src/layout/index.ts +1 -0
  30. package/src/layout/sharedRegistry.ts +51 -2
  31. package/src/motion/createMotionComponent.tsx +176 -20
  32. package/src/presence/Presence.tsx +73 -10
  33. package/src/values/useAnimator.ts +25 -14
  34. package/src/values/useColorCascade.ts +34 -16
  35. package/dist/chunk-34Q4UM6V.js +0 -8
  36. package/dist/chunk-KYJROYCG.js +0 -8
  37. package/dist/chunk-X5J5M3K3.js +0 -8
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to `@rootnative/inertia` are documented here. The format fol
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.0.3] - 2026-07-25
8
+
9
+ ### Fixed
10
+
11
+ - **Properties mentioned only by `gesture` / `exit` / a non-current variant no longer stomp the static `style`.** A key touched by any of those joins the animated key set — the worklet has to know about it so the layer or branch can drive it later — but until something actually drove it, it rested at a generic default and the animated style, which merges _after_ `style`, silently overrode the element's own stylesheet. `<Motion.View style={styles.field} gesture={{ focused: { borderColor: '#4f46e5' } }} />` rendered `borderColor: 'transparent'` on the very first frame, before any interaction; `exit={{ width: 0 }}` collapsed the element to zero width while it was still on screen; a `width` present in one variant branch collapsed the element whenever a different branch was active. Affected every property whose default isn't an identity — `width`, `height`, `borderRadius`, `shadowOpacity`, `shadowRadius`, `elevation`, `shadowOffset`, and all five color keys. Transforms and `opacity` were unaffected, which is why this survived so long: their defaults (`0` / `1`) happen to be no-ops. Undriven properties now rest at the value the static `style` sets — read through `StyleSheet.flatten`, so style arrays and registered stylesheet IDs resolve, transform entries are read out of the `transform` array, `shadowOffset` decomposes into both axes, and unit-suffixed rotations (`'45deg'`, `'0.5rad'`) convert to the plain degrees the shared value holds — falling back to the type default only when the style is silent too. Gesture layers now blend from that style-derived base and return to it on release. The base also tracks a `style` that changes after mount (a theme swap no longer gets pinned to the value captured at mount). Non-numeric style values such as `width: '100%'` are deliberately not seeded into numeric slots, since a later `withTiming` can't interpolate them. Values a record has already driven are untouched: switching to a variant branch that omits a key still leaves it where the previous branch put it. Guarded by `style-resting.test.tsx` (18 cases).
12
+
13
+ - **`<Presence>` children now animate out in place instead of jumping to the end of their siblings.** The render list was built as "every present entry, then every exiting entry". React reconciles that array by key, so appending physically moved the node: removing the middle of a three-row list rendered `a, c, b` and the departing row visibly jumped to the bottom before it had finished fading. Absolutely-positioned overlays — popovers, sheets, toasts, the cases `<Presence>` gets used for most and the ones the existing tests covered — never showed it; every list and column did. Exiting entries are now spliced back in at the position they held, anchored immediately after their nearest surviving predecessor (or at the front if there isn't one), so adjacent departures keep their relative order and siblings added or reordered mid-exit are placed around them. `<Presence>` remembers the full previous render order, including entries that were already exiting, since an exiting child is by definition absent from `children`. Note for anyone touching this code: the ordering pass has to read the exiting map that the _current_ render computed, not the `useState` value — on the render that first detects a departure the state update hasn't applied yet, and using the stale map drops the child from the remembered order entirely on the following render, losing the exit animation rather than merely misplacing it. Guarded by `Presence.order.test.tsx` (12 cases; 7 fail against the previous implementation).
14
+
15
+ - **An endless (`repeat: 'infinite'`) animation no longer blocks completion counters.** A callback is only promoted to the terminal `'animation'` phase once `iteration >= totalIterations - 1`, which is unreachable against `Infinity`. Two counters were gated behind that check and both broke in the presence of an endless animation. **(1) The transform group** — a multi-axis transform coalesces its terminal callbacks into one `onAnimationEnd({ key: 'transform' })`, counting the participating axes and firing when the last settles. An endless axis pinned that count above zero forever, so `animate={{ translateX: 100, translateY: 50 }}` with `transition={{ translateX: { repeat: 'infinite' }, translateY: {} }}` settled `translateY`, decremented the counter, and fired **nothing** — the completion was silently swallowed. Endless axes are now excluded from the group, so the finite axes still report; when every transform axis is endless no terminal callback fires at all, which is correct since nothing finishes. **(2) `<Presence>`'s settle counter** — the same unreachable check gated `safeToRemove`, so an endless exit animation left the child **mounted forever**. A top-level `transition={{ repeat: 'infinite' }}` is inherited by `exit`, so a pulsing element inside `<Presence>` would never unmount — a permanent leak, not just a missed callback. Exit keys that can never settle are no longer counted: finite keys still gate the unmount, and if every exiting property is endless the child is released immediately. Scope the repeat to one property (`transition={{ opacity: { repeat: 'infinite' } }}`) when you want a loop _and_ a normal exit. Guarded by `endless-repeat.test.tsx` (8 cases; 4 fail against the previous implementation).
16
+
17
+ - **`useColorCascade` now notices when a layer's `progress` shared value is swapped.** The layer chain is memoized on a structural signature — key, base colour, layer count, layer colours — which can't contain the shared values themselves because they're objects, and they were excluded from change detection entirely. Swapping _which_ value drives a layer while its colour stayed the same therefore did nothing: the worklet went on reading the old shared value, and driving the one no longer referenced by any layer still moved the colour. A conditionally-sourced progress (`{ progress: isError ? errorSpring : hoverSpring, color }`) silently animated off the wrong input. Shared-value references are now compared directly and the array is rebuilt only when one actually differs, so the swap rewires the worklet while a fresh-but-equal `layers` literal still produces no new UI-thread closure.
18
+
19
+ - **`useAnimator()` is now identity-stable by construction, as documented.** Its JSDoc, the hooks reference, and the `0.0.2` changelog all promised an identity-stable setter safe to drop into memoized handlers, and all three explained it as reading the registry "live inside the body" — but the implementation listed `[registry, shouldReduceMotion]` as `useCallback` dependencies, so a provider republishing its `transitions` map or a reduced-motion change handed back a new function and churned every handler built on it. It stayed stable only by accident of `<MotionConfig>` memoizing. Both values now genuinely live in refs read at call time, which also makes writes resolve against the registry current _when the event fires_ rather than the one captured at render.
20
+
21
+ - **The `layoutId` measure registry now evicts expired entries.** Rects only ever left the module-level map through `consumeLayout` / `peekSharedLayout`, so a `layoutId` that unmounted and was never remounted kept its entry for the lifetime of the process. Harmless for a handful of hero images; a slow leak for per-item ids in a long-lived list (`layoutId={`photo-${item.id}`}`), where the release is never consumed. Writes now run an expiry sweep, amortized to at most one full scan per TTL window so a burst of layout events doesn't become a burst of scans. Sweeping a still-mounted element's entry is safe — it re-registers on its next `onLayout`, and unmount re-publishes the rect with a fresh TTL either way.
22
+
7
23
  ## [0.0.2] - 2026-07-24
8
24
 
9
25
  ### Added
@@ -89,7 +105,13 @@ Initial alpha publish. The full v0.1 surface is in place; APIs are still subject
89
105
  - SVG path morphing, gradient interpolation, and shared-element transitions across screens are out of scope until `0.2.x` / `1.x` per the roadmap.
90
106
  - `react-native-gesture-handler` integration (drag, pan, swipe sub-states) lands in `0.2` via the optional `@rootnative/inertia-gestures` adapter.
91
107
 
92
- [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.0-alpha.2...HEAD
108
+ [unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.3...HEAD
109
+ [0.0.3]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.3
110
+ [0.0.2]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.2
111
+ [0.0.1]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.1
112
+ [0.0.0-alpha.5]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.5
113
+ [0.0.0-alpha.4]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.4
114
+ [0.0.0-alpha.3]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.3
93
115
  [0.0.0-alpha.2]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.2
94
116
  [0.0.0-alpha.1]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.1
95
- [0.0.0-alpha.0]: https://github.com/rootnative/inertia/releases/tag/v0.0.0-alpha.0
117
+ [0.0.0-alpha.0]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.0-alpha.0
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkIWLU3VDE_js = require('./chunk-IWLU3VDE.js');
3
+ var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
4
4
  var reactNative = require('react-native');
5
5
 
6
- var MotionPressable = chunkIWLU3VDE_js.createMotionComponent(reactNative.Pressable);
6
+ var MotionPressable = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.Pressable);
7
7
 
8
8
  exports.MotionPressable = MotionPressable;
@@ -1,4 +1,4 @@
1
- import { createMotionComponent } from './chunk-LYMLQ6WQ.mjs';
1
+ import { createMotionComponent } from './chunk-CSODMRJ7.mjs';
2
2
  import { Pressable } from 'react-native';
3
3
 
4
4
  var MotionPressable = createMotionComponent(Pressable);
@@ -5,6 +5,7 @@ var chunkPTRF47DA_js = require('./chunk-PTRF47DA.js');
5
5
  var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var Animated = require('react-native-reanimated');
8
+ var reactNative = require('react-native');
8
9
 
9
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
11
 
@@ -35,6 +36,8 @@ function Presence({ children }) {
35
36
  () => /* @__PURE__ */ new Map()
36
37
  );
37
38
  const prevIncomingRef = react.useRef(incoming);
39
+ const orderRef = react.useRef([]);
40
+ let pendingExiting = null;
38
41
  if (prevIncomingRef.current !== incoming) {
39
42
  const prev = prevIncomingRef.current;
40
43
  prevIncomingRef.current = incoming;
@@ -56,8 +59,12 @@ function Presence({ children }) {
56
59
  ensureMutable().delete(key);
57
60
  }
58
61
  }
59
- if (next) setExiting(next);
62
+ if (next) {
63
+ pendingExiting = next;
64
+ setExiting(next);
65
+ }
60
66
  }
67
+ const activeExiting = pendingExiting ?? exiting;
61
68
  const handleRemove = react.useCallback((key) => {
62
69
  setExiting((prev) => {
63
70
  if (!prev.has(key)) return prev;
@@ -66,19 +73,42 @@ function Presence({ children }) {
66
73
  return next;
67
74
  });
68
75
  }, []);
69
- const renderList = [];
76
+ const byKey = /* @__PURE__ */ new Map();
77
+ const presentKeys = /* @__PURE__ */ new Set();
78
+ const order = [];
70
79
  for (const el of incoming) {
71
- renderList.push({
72
- key: el.key,
73
- element: el,
74
- isPresent: true
75
- });
80
+ const key = el.key;
81
+ byKey.set(key, el);
82
+ presentKeys.add(key);
83
+ order.push(key);
76
84
  }
77
- for (const [key, el] of exiting) {
78
- if (!renderList.some((entry) => entry.key === key)) {
79
- renderList.push({ key, element: el, isPresent: false });
85
+ const prevOrder = orderRef.current;
86
+ for (let i = 0; i < prevOrder.length; i++) {
87
+ const key = prevOrder[i];
88
+ const departing = activeExiting.get(key);
89
+ if (!departing || byKey.has(key)) continue;
90
+ let insertAt = 0;
91
+ for (let j = i - 1; j >= 0; j--) {
92
+ const anchor = order.indexOf(prevOrder[j]);
93
+ if (anchor !== -1) {
94
+ insertAt = anchor + 1;
95
+ break;
96
+ }
80
97
  }
98
+ byKey.set(key, departing);
99
+ order.splice(insertAt, 0, key);
81
100
  }
101
+ for (const [key, el] of activeExiting) {
102
+ if (byKey.has(key)) continue;
103
+ byKey.set(key, el);
104
+ order.push(key);
105
+ }
106
+ orderRef.current = order;
107
+ const renderList = order.map((key) => ({
108
+ key,
109
+ element: byKey.get(key),
110
+ isPresent: presentKeys.has(key)
111
+ }));
82
112
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderList.map(({ key, element, isPresent }) => /* @__PURE__ */ jsxRuntime.jsx(
83
113
  PresenceItem,
84
114
  {
@@ -130,11 +160,23 @@ function resolveLayoutTransition(layout) {
130
160
  var REGISTRY = /* @__PURE__ */ new Map();
131
161
  var SHARED_LAYOUT_TTL_MS = 1e3;
132
162
  var now = () => Date.now();
163
+ var lastSweep = 0;
164
+ function sweepExpired(at) {
165
+ if (at - lastSweep < SHARED_LAYOUT_TTL_MS) return;
166
+ lastSweep = at;
167
+ for (const [id, entry] of REGISTRY) {
168
+ if (entry.expiresAt < at) REGISTRY.delete(id);
169
+ }
170
+ }
133
171
  function registerLayout(id, rect) {
134
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
172
+ const at = now();
173
+ sweepExpired(at);
174
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
135
175
  }
136
176
  function releaseLayout(id, rect) {
137
- REGISTRY.set(id, { rect, expiresAt: now() + SHARED_LAYOUT_TTL_MS });
177
+ const at = now();
178
+ sweepExpired(at);
179
+ REGISTRY.set(id, { rect, expiresAt: at + SHARED_LAYOUT_TTL_MS });
138
180
  }
139
181
  function consumeLayout(id) {
140
182
  const entry = REGISTRY.get(id);
@@ -510,23 +552,49 @@ function createMotionComponent(Component) {
510
552
  (k) => SHADOW_OFFSET_KEY_SET.has(k)
511
553
  );
512
554
  }
555
+ const drivenNow = /* @__PURE__ */ new Set();
556
+ collectTouchedKeys(drivenNow, animateRecord);
557
+ if (initialRecord) collectTouchedKeys(drivenNow, initialRecord);
558
+ if (isExiting && exitRecord) collectTouchedKeys(drivenNow, exitRecord);
559
+ const everDrivenRef = react.useRef(/* @__PURE__ */ new Set());
560
+ for (const k of drivenNow) everDrivenRef.current.add(k);
561
+ const activeKeys = activeKeysRef.current;
562
+ const everDriven = everDrivenRef.current;
563
+ const styleResting = {};
564
+ if (activeKeys.some((k) => !everDriven.has(k))) {
565
+ const flat = reactNative.StyleSheet.flatten(style);
566
+ if (flat) {
567
+ for (const key of activeKeys) {
568
+ if (everDriven.has(key)) continue;
569
+ const v = styleValueFor(flat, key);
570
+ if (v !== void 0) styleResting[key] = v;
571
+ }
572
+ }
573
+ }
513
574
  const sharedValues = useAnimatableSharedValues((key) => {
514
575
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
515
576
  const axis = shadowOffsetAxisFor(key);
516
577
  if (initial === false) {
517
- return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
578
+ return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
518
579
  }
519
580
  return shadowOffsetAxisValue(
520
581
  initialRecord?.shadowOffset,
521
582
  axis
522
- ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
583
+ ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
523
584
  }
524
585
  if (initial === false) {
525
586
  const a = animateRecord[key];
526
- return restValue(a) ?? DEFAULT_RESTING[key];
587
+ return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
527
588
  }
528
- return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
589
+ return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
529
590
  });
591
+ const styleRestingSig = chunkPTRF47DA_js.stableSig(styleResting);
592
+ react.useEffect(() => {
593
+ for (const key of activeKeysRef.current) {
594
+ if (everDrivenRef.current.has(key)) continue;
595
+ sharedValues[key].value = styleResting[key] ?? DEFAULT_RESTING[key];
596
+ }
597
+ }, [styleRestingSig]);
530
598
  const pressedProgress = Animated.useSharedValue(0);
531
599
  const focusedProgress = Animated.useSharedValue(0);
532
600
  const focusVisibleProgress = Animated.useSharedValue(0);
@@ -569,11 +637,15 @@ function createMotionComponent(Component) {
569
637
  if (isExiting) safeToRemoveRef.current?.();
570
638
  }
571
639
  };
640
+ const configFor = (key) => shouldReduceMotion ? { type: "no-animation" } : transitionFor(
641
+ SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
642
+ transition
643
+ );
572
644
  let transformPending = 0;
573
645
  for (const k of ALL_KEYS) {
574
- if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
575
- transformPending++;
576
- }
646
+ if (!TRANSFORM_KEY_SET.has(k) || baseRecord[k] === void 0) continue;
647
+ if (!reachesTerminalPhase(configFor(k))) continue;
648
+ transformPending++;
577
649
  }
578
650
  const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
579
651
  for (const key of ALL_KEYS) {
@@ -582,11 +654,8 @@ function createMotionComponent(Component) {
582
654
  shadowOffsetAxisFor(key)
583
655
  ) : baseRecord[key];
584
656
  if (target === void 0) continue;
585
- const cfg = shouldReduceMotion ? { type: "no-animation" } : transitionFor(
586
- SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
587
- transition
588
- );
589
- if (isExiting) pending++;
657
+ const cfg = configFor(key);
658
+ if (isExiting && reachesTerminalPhase(cfg)) pending++;
590
659
  const factory = makeKeyCallbackFactory(
591
660
  key,
592
661
  sharedValues[key],
@@ -655,7 +724,7 @@ function createMotionComponent(Component) {
655
724
  const flip = sharedLayout.flip;
656
725
  const hasLayoutId = layoutId !== void 0;
657
726
  const animatedStyle = Animated.useAnimatedStyle(() => {
658
- const activeKeys = activeKeysRef.current;
727
+ const activeKeys2 = activeKeysRef.current;
659
728
  const hasTransform = hasTransformRef.current;
660
729
  const hasShadowOffset = hasShadowOffsetRef.current;
661
730
  const out = {};
@@ -671,7 +740,7 @@ function createMotionComponent(Component) {
671
740
  const focusedLayer = layers ? layers.focused : null;
672
741
  const focusVisibleLayer = layers ? layers.focusVisible : null;
673
742
  const pressedLayer = layers ? layers.pressed : null;
674
- for (const key of activeKeys) {
743
+ for (const key of activeKeys2) {
675
744
  let v = sharedValues[key].value;
676
745
  const isColor = COLOR_KEY_SET.has(key);
677
746
  if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
@@ -911,6 +980,40 @@ function shadowOffsetAxisFor(key) {
911
980
  function shadowOffsetAxisValue(source, axis) {
912
981
  return source?.[axis];
913
982
  }
983
+ function styleValueFor(flat, key) {
984
+ if (SHADOW_OFFSET_KEY_SET.has(key)) {
985
+ return shadowOffsetAxisValue(
986
+ flat.shadowOffset,
987
+ shadowOffsetAxisFor(key)
988
+ );
989
+ }
990
+ if (TRANSFORM_KEY_SET.has(key)) {
991
+ const list = flat.transform;
992
+ if (!Array.isArray(list)) return void 0;
993
+ for (const entry of list) {
994
+ if (!entry || typeof entry !== "object") continue;
995
+ const v2 = entry[key];
996
+ if (v2 === void 0) continue;
997
+ if (ROTATION_KEYS.has(key)) return parseAngleDegrees(v2);
998
+ return typeof v2 === "number" ? v2 : void 0;
999
+ }
1000
+ return void 0;
1001
+ }
1002
+ const v = flat[key];
1003
+ if (COLOR_KEY_SET.has(key)) {
1004
+ return typeof v === "string" || typeof v === "number" ? v : void 0;
1005
+ }
1006
+ return typeof v === "number" ? v : void 0;
1007
+ }
1008
+ function parseAngleDegrees(v) {
1009
+ if (typeof v === "number") return v;
1010
+ if (typeof v !== "string") return void 0;
1011
+ const deg = /^(-?\d*\.?\d+)deg$/.exec(v);
1012
+ if (deg) return Number(deg[1]);
1013
+ const rad = /^(-?\d*\.?\d+)rad$/.exec(v);
1014
+ if (rad) return Number(rad[1]) * 180 / Math.PI;
1015
+ return void 0;
1016
+ }
914
1017
  function collectTouchedKeys(touched, record) {
915
1018
  for (const k of ALL_KEYS) {
916
1019
  if (k in record) touched.add(k);
@@ -934,6 +1037,9 @@ function totalIterationsOf(cfg) {
934
1037
  if (r.count === "infinite") return Number.POSITIVE_INFINITY;
935
1038
  return r.count;
936
1039
  }
1040
+ function reachesTerminalPhase(cfg) {
1041
+ return Number.isFinite(totalIterationsOf(cfg));
1042
+ }
937
1043
  function targetEndValue(v) {
938
1044
  if (v === void 0) return void 0;
939
1045
  if (typeof v === "number" || typeof v === "string") return v;
@@ -1,4 +1,4 @@
1
- import { createMotionComponent } from './chunk-LYMLQ6WQ.mjs';
1
+ import { createMotionComponent } from './chunk-CSODMRJ7.mjs';
2
2
  import { View } from 'react-native';
3
3
 
4
4
  var MotionView = createMotionComponent(View);
@@ -3,6 +3,7 @@ import { stableSig, resolveAnimatableValue, isTopLevelTransition, resolveTransit
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
  {
@@ -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);
@@ -504,23 +546,49 @@ function createMotionComponent(Component) {
504
546
  (k) => SHADOW_OFFSET_KEY_SET.has(k)
505
547
  );
506
548
  }
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
+ }
567
+ }
507
568
  const sharedValues = useAnimatableSharedValues((key) => {
508
569
  if (SHADOW_OFFSET_KEY_SET.has(key)) {
509
570
  const axis = shadowOffsetAxisFor(key);
510
571
  if (initial === false) {
511
- return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
572
+ return shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
512
573
  }
513
574
  return shadowOffsetAxisValue(
514
575
  initialRecord?.shadowOffset,
515
576
  axis
516
- ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? DEFAULT_RESTING[key];
577
+ ) ?? shadowOffsetAxisValue(animateRecord.shadowOffset, axis) ?? styleResting[key] ?? DEFAULT_RESTING[key];
517
578
  }
518
579
  if (initial === false) {
519
580
  const a = animateRecord[key];
520
- return restValue(a) ?? DEFAULT_RESTING[key];
581
+ return restValue(a) ?? styleResting[key] ?? DEFAULT_RESTING[key];
521
582
  }
522
- return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? DEFAULT_RESTING[key];
583
+ return initialRecord?.[key] ?? restValue(animateRecord[key]) ?? styleResting[key] ?? DEFAULT_RESTING[key];
523
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]);
524
592
  const pressedProgress = useSharedValue(0);
525
593
  const focusedProgress = useSharedValue(0);
526
594
  const focusVisibleProgress = useSharedValue(0);
@@ -563,11 +631,15 @@ function createMotionComponent(Component) {
563
631
  if (isExiting) safeToRemoveRef.current?.();
564
632
  }
565
633
  };
634
+ const configFor = (key) => shouldReduceMotion ? { type: "no-animation" } : transitionFor(
635
+ SHADOW_OFFSET_KEY_SET.has(key) ? "shadowOffset" : key,
636
+ transition
637
+ );
566
638
  let transformPending = 0;
567
639
  for (const k of ALL_KEYS) {
568
- if (TRANSFORM_KEY_SET.has(k) && baseRecord[k] !== void 0) {
569
- transformPending++;
570
- }
640
+ if (!TRANSFORM_KEY_SET.has(k) || baseRecord[k] === void 0) continue;
641
+ if (!reachesTerminalPhase(configFor(k))) continue;
642
+ transformPending++;
571
643
  }
572
644
  const transformGroup = transformPending > 0 ? { remaining: transformPending } : void 0;
573
645
  for (const key of ALL_KEYS) {
@@ -576,11 +648,8 @@ function createMotionComponent(Component) {
576
648
  shadowOffsetAxisFor(key)
577
649
  ) : baseRecord[key];
578
650
  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++;
651
+ const cfg = configFor(key);
652
+ if (isExiting && reachesTerminalPhase(cfg)) pending++;
584
653
  const factory = makeKeyCallbackFactory(
585
654
  key,
586
655
  sharedValues[key],
@@ -649,7 +718,7 @@ function createMotionComponent(Component) {
649
718
  const flip = sharedLayout.flip;
650
719
  const hasLayoutId = layoutId !== void 0;
651
720
  const animatedStyle = useAnimatedStyle(() => {
652
- const activeKeys = activeKeysRef.current;
721
+ const activeKeys2 = activeKeysRef.current;
653
722
  const hasTransform = hasTransformRef.current;
654
723
  const hasShadowOffset = hasShadowOffsetRef.current;
655
724
  const out = {};
@@ -665,7 +734,7 @@ function createMotionComponent(Component) {
665
734
  const focusedLayer = layers ? layers.focused : null;
666
735
  const focusVisibleLayer = layers ? layers.focusVisible : null;
667
736
  const pressedLayer = layers ? layers.pressed : null;
668
- for (const key of activeKeys) {
737
+ for (const key of activeKeys2) {
669
738
  let v = sharedValues[key].value;
670
739
  const isColor = COLOR_KEY_SET.has(key);
671
740
  if (hoveredLayer && ph > 0 && hoveredLayer[key] !== void 0) {
@@ -905,6 +974,40 @@ function shadowOffsetAxisFor(key) {
905
974
  function shadowOffsetAxisValue(source, axis) {
906
975
  return source?.[axis];
907
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
+ }
908
1011
  function collectTouchedKeys(touched, record) {
909
1012
  for (const k of ALL_KEYS) {
910
1013
  if (k in record) touched.add(k);
@@ -928,6 +1031,9 @@ function totalIterationsOf(cfg) {
928
1031
  if (r.count === "infinite") return Number.POSITIVE_INFINITY;
929
1032
  return r.count;
930
1033
  }
1034
+ function reachesTerminalPhase(cfg) {
1035
+ return Number.isFinite(totalIterationsOf(cfg));
1036
+ }
931
1037
  function targetEndValue(v) {
932
1038
  if (v === void 0) return void 0;
933
1039
  if (typeof v === "number" || typeof v === "string") return v;
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
4
+ var reactNative = require('react-native');
5
+
6
+ var MotionView = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.View);
7
+
8
+ exports.MotionView = MotionView;
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
4
+ var reactNative = require('react-native');
5
+
6
+ var MotionText = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.Text);
7
+
8
+ exports.MotionText = MotionText;
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
4
+ var reactNative = require('react-native');
5
+
6
+ var MotionImage = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.Image);
7
+
8
+ exports.MotionImage = MotionImage;
@@ -1,4 +1,4 @@
1
- import { createMotionComponent } from './chunk-LYMLQ6WQ.mjs';
1
+ import { createMotionComponent } from './chunk-CSODMRJ7.mjs';
2
2
  import { Image } from 'react-native';
3
3
 
4
4
  var MotionImage = createMotionComponent(Image);
@@ -1,4 +1,4 @@
1
- import { createMotionComponent } from './chunk-LYMLQ6WQ.mjs';
1
+ import { createMotionComponent } from './chunk-CSODMRJ7.mjs';
2
2
  import { ScrollView } from 'react-native';
3
3
 
4
4
  var MotionScrollView = createMotionComponent(ScrollView);
@@ -1,4 +1,4 @@
1
- import { createMotionComponent } from './chunk-LYMLQ6WQ.mjs';
1
+ import { createMotionComponent } from './chunk-CSODMRJ7.mjs';
2
2
  import { Text } from 'react-native';
3
3
 
4
4
  var MotionText = createMotionComponent(Text);
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkIWLU3VDE_js = require('./chunk-IWLU3VDE.js');
3
+ var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
4
4
  var reactNative = require('react-native');
5
5
 
6
- var MotionScrollView = chunkIWLU3VDE_js.createMotionComponent(reactNative.ScrollView);
6
+ var MotionScrollView = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.ScrollView);
7
7
 
8
8
  exports.MotionScrollView = MotionScrollView;