@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.
- package/CHANGELOG.md +24 -2
- package/dist/{chunk-NKA6XR77.js → chunk-3XTVY34H.js} +2 -2
- package/dist/{chunk-V6BTXHZK.mjs → chunk-46P57VMY.mjs} +1 -1
- package/dist/{chunk-IWLU3VDE.js → chunk-BP3Y2SHQ.js} +132 -26
- package/dist/{chunk-TUMNV4P7.mjs → chunk-BQQTHG2V.mjs} +1 -1
- package/dist/{chunk-LYMLQ6WQ.mjs → chunk-CSODMRJ7.mjs} +132 -26
- package/dist/chunk-FNVFV4EY.js +8 -0
- package/dist/chunk-FWQOXA43.js +8 -0
- package/dist/chunk-KBP4LR75.js +8 -0
- package/dist/{chunk-2DHKV2XY.mjs → chunk-O22NXXCZ.mjs} +1 -1
- package/dist/{chunk-HFDZAHWP.mjs → chunk-OQV66TBQ.mjs} +1 -1
- package/dist/{chunk-ZN3PNGHQ.mjs → chunk-SGUHE5CX.mjs} +1 -1
- package/dist/{chunk-TUYHTHVV.js → chunk-W5MC3P4N.js} +2 -2
- package/dist/index.d.mts +10 -8
- package/dist/index.d.ts +10 -8
- package/dist/index.js +41 -28
- package/dist/index.mjs +34 -21
- package/dist/motion/Image.js +3 -3
- package/dist/motion/Image.mjs +2 -2
- package/dist/motion/Pressable.js +3 -3
- package/dist/motion/Pressable.mjs +2 -2
- package/dist/motion/ScrollView.js +3 -3
- package/dist/motion/ScrollView.mjs +2 -2
- package/dist/motion/Text.js +3 -3
- package/dist/motion/Text.mjs +2 -2
- package/dist/motion/View.js +3 -3
- package/dist/motion/View.mjs +2 -2
- package/package.json +1 -1
- package/src/layout/index.ts +1 -0
- package/src/layout/sharedRegistry.ts +51 -2
- package/src/motion/createMotionComponent.tsx +176 -20
- package/src/presence/Presence.tsx +73 -10
- package/src/values/useAnimator.ts +25 -14
- package/src/values/useColorCascade.ts +34 -16
- package/dist/chunk-34Q4UM6V.js +0 -8
- package/dist/chunk-KYJROYCG.js +0 -8
- 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.
|
|
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/
|
|
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
|
|
3
|
+
var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
|
|
4
4
|
var reactNative = require('react-native');
|
|
5
5
|
|
|
6
|
-
var MotionPressable =
|
|
6
|
+
var MotionPressable = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.Pressable);
|
|
7
7
|
|
|
8
8
|
exports.MotionPressable = MotionPressable;
|
|
@@ -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)
|
|
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
|
|
76
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
77
|
+
const presentKeys = /* @__PURE__ */ new Set();
|
|
78
|
+
const order = [];
|
|
70
79
|
for (const el of incoming) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
80
|
+
const key = el.key;
|
|
81
|
+
byKey.set(key, el);
|
|
82
|
+
presentKeys.add(key);
|
|
83
|
+
order.push(key);
|
|
76
84
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
575
|
-
|
|
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 =
|
|
586
|
-
|
|
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
|
|
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
|
|
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;
|
|
@@ -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)
|
|
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
|
|
70
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
71
|
+
const presentKeys = /* @__PURE__ */ new Set();
|
|
72
|
+
const order = [];
|
|
64
73
|
for (const el of incoming) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
74
|
+
const key = el.key;
|
|
75
|
+
byKey.set(key, el);
|
|
76
|
+
presentKeys.add(key);
|
|
77
|
+
order.push(key);
|
|
70
78
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
569
|
-
|
|
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 =
|
|
580
|
-
|
|
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
|
|
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
|
|
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;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkBP3Y2SHQ_js = require('./chunk-BP3Y2SHQ.js');
|
|
4
4
|
var reactNative = require('react-native');
|
|
5
5
|
|
|
6
|
-
var MotionScrollView =
|
|
6
|
+
var MotionScrollView = chunkBP3Y2SHQ_js.createMotionComponent(reactNative.ScrollView);
|
|
7
7
|
|
|
8
8
|
exports.MotionScrollView = MotionScrollView;
|