@rootnative/inertia-svg 0.0.9 → 0.0.10
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 +18 -1
- package/dist/index.js +15 -13
- package/dist/index.mjs +16 -14
- package/llms.txt +2 -2
- package/package.json +3 -3
- package/src/MotionPath.tsx +21 -9
- package/src/createMotionSvgComponent.tsx +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ This package ships in lockstep with `@rootnative/inertia` — version numbers tr
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.0.10] - 2026-09-06
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **The `@rootnative/inertia` peer range has an upper bound: `>=0.0.9 <0.1.0`.** Pre-`1.0.0`, a minor bump of core is where a breaking change lands, so this adapter must not accept it. Enforced by `check:versions`. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- A test for the `initial.d` template-mismatch throw. Found by the `1.0.0` readiness audit (2026-09-05), Phase 3.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **A spring config without a `type` key is now honoured as the top-level transition.** `SpringTransition.type` is optional, so `transition={{ tension: 300, friction: 20 }}` is valid. The adapter's `'type' in transition` check read it as a per-property map, looked up the prop name, found nothing, and ran the library default spring with no warning. The check is now `isTopLevelTransition`, exported from `@rootnative/inertia`, which is the same structural discriminator the core factory uses. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
22
|
+
|
|
23
|
+
- **An inline `transition` literal no longer restarts the animation on every parent re-render.** The value-driving effects keyed on the `transition` object's identity. The documented call shape passes `transition={{ type: 'timing', duration: 600 }}` inline, so each parent render made a new object, re-fired every effect, and re-assigned the animation from its current value with the full duration; a frequently re-rendering parent never let the animation finish. The effects now key on `stableSig(transition)`, exported from `@rootnative/inertia`, so only a change to the config's content re-resolves. Found by the `1.0.0` readiness audit (2026-09-05).
|
|
24
|
+
|
|
9
25
|
## [0.0.9] - 2026-08-22
|
|
10
26
|
|
|
11
27
|
**Lockstep version bump** alongside `@rootnative/inertia@0.0.9` (`useInterpolatedStyle` types its return against the map it was given, so a `style` array no longer needs a cast). No runtime changes in this adapter; the `@rootnative/inertia` peer range moves to `>=0.0.9`.
|
|
@@ -65,7 +81,8 @@ _No git tag was cut for this release; the published artifact is on npm as [`@roo
|
|
|
65
81
|
|
|
66
82
|
- `MotionPath` over `react-native-svg`. Animatable: `d` (element-wise scalar interpolation on structurally-compatible paths), `fill`, `stroke`, `strokeWidth`, opacities, `strokeDashoffset`. Source and target paths must share the same command sequence after implicit-repeat expansion; remount with `key` to switch shape.
|
|
67
83
|
|
|
68
|
-
[unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.
|
|
84
|
+
[unreleased]: https://github.com/rootnative/inertia/compare/core+gestures+gradients+svg@0.0.10...HEAD
|
|
85
|
+
[0.0.10]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.10
|
|
69
86
|
[0.0.9]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.9
|
|
70
87
|
[0.0.8]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.8
|
|
71
88
|
[0.0.7]: https://github.com/rootnative/inertia/releases/tag/core+gestures+gradients+svg@0.0.7
|
package/dist/index.js
CHANGED
|
@@ -180,7 +180,7 @@ var AnimatedPath = Animated__default.default.createAnimatedComponent(reactNative
|
|
|
180
180
|
var NO_ANIMATION = { type: "no-animation" };
|
|
181
181
|
function pickTransition(per, key) {
|
|
182
182
|
if (!per) return void 0;
|
|
183
|
-
if (
|
|
183
|
+
if (inertia.isTopLevelTransition(per)) return per;
|
|
184
184
|
return per[key];
|
|
185
185
|
}
|
|
186
186
|
function MotionPath(props) {
|
|
@@ -254,6 +254,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
254
254
|
seedSource?.strokeDashoffset ?? strokeDashoffset ?? 0
|
|
255
255
|
);
|
|
256
256
|
const reduce = inertia.useShouldReduceMotion();
|
|
257
|
+
const transitionSig = inertia.stableSig(transition);
|
|
257
258
|
const animateD = animate?.d;
|
|
258
259
|
const animateFill = animate?.fill;
|
|
259
260
|
const animateStroke = animate?.stroke;
|
|
@@ -278,22 +279,22 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
278
279
|
for (let i = 0; i < paramSvs.length; i++) {
|
|
279
280
|
paramSvs[i].value = inertia.resolveTransition(cfg, target[i] ?? 0);
|
|
280
281
|
}
|
|
281
|
-
}, [animateD, reduce,
|
|
282
|
+
}, [animateD, reduce, transitionSig]);
|
|
282
283
|
react.useEffect(() => {
|
|
283
284
|
if (animateFill === void 0) return;
|
|
284
285
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "fill");
|
|
285
286
|
fillSv.value = inertia.resolveTransition(cfg, animateFill);
|
|
286
|
-
}, [animateFill, reduce,
|
|
287
|
+
}, [animateFill, reduce, transitionSig]);
|
|
287
288
|
react.useEffect(() => {
|
|
288
289
|
if (animateStroke === void 0) return;
|
|
289
290
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "stroke");
|
|
290
291
|
strokeSv.value = inertia.resolveTransition(cfg, animateStroke);
|
|
291
|
-
}, [animateStroke, reduce,
|
|
292
|
+
}, [animateStroke, reduce, transitionSig]);
|
|
292
293
|
react.useEffect(() => {
|
|
293
294
|
if (animateStrokeWidth === void 0) return;
|
|
294
295
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeWidth");
|
|
295
296
|
strokeWidthSv.value = inertia.resolveTransition(cfg, animateStrokeWidth);
|
|
296
|
-
}, [animateStrokeWidth, reduce,
|
|
297
|
+
}, [animateStrokeWidth, reduce, transitionSig]);
|
|
297
298
|
react.useEffect(() => {
|
|
298
299
|
if (animateStrokeOpacity === void 0) return;
|
|
299
300
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeOpacity");
|
|
@@ -301,17 +302,17 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
301
302
|
cfg,
|
|
302
303
|
animateStrokeOpacity
|
|
303
304
|
);
|
|
304
|
-
}, [animateStrokeOpacity, reduce,
|
|
305
|
+
}, [animateStrokeOpacity, reduce, transitionSig]);
|
|
305
306
|
react.useEffect(() => {
|
|
306
307
|
if (animateFillOpacity === void 0) return;
|
|
307
308
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "fillOpacity");
|
|
308
309
|
fillOpacitySv.value = inertia.resolveTransition(cfg, animateFillOpacity);
|
|
309
|
-
}, [animateFillOpacity, reduce,
|
|
310
|
+
}, [animateFillOpacity, reduce, transitionSig]);
|
|
310
311
|
react.useEffect(() => {
|
|
311
312
|
if (animateOpacity === void 0) return;
|
|
312
313
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "opacity");
|
|
313
314
|
opacitySv.value = inertia.resolveTransition(cfg, animateOpacity);
|
|
314
|
-
}, [animateOpacity, reduce,
|
|
315
|
+
}, [animateOpacity, reduce, transitionSig]);
|
|
315
316
|
react.useEffect(() => {
|
|
316
317
|
if (animateStrokeDashoffset === void 0) return;
|
|
317
318
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeDashoffset");
|
|
@@ -319,7 +320,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
319
320
|
cfg,
|
|
320
321
|
animateStrokeDashoffset
|
|
321
322
|
);
|
|
322
|
-
}, [animateStrokeDashoffset, reduce,
|
|
323
|
+
}, [animateStrokeDashoffset, reduce, transitionSig]);
|
|
323
324
|
const animatedProps = Animated.useAnimatedProps(() => {
|
|
324
325
|
"worklet";
|
|
325
326
|
const params = new Array(paramSvs.length);
|
|
@@ -354,7 +355,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
354
355
|
var NO_ANIMATION2 = { type: "no-animation" };
|
|
355
356
|
function pickTransition2(transition, key, registry) {
|
|
356
357
|
if (!transition) return void 0;
|
|
357
|
-
if (typeof transition === "string" ||
|
|
358
|
+
if (typeof transition === "string" || inertia.isTopLevelTransition(transition)) {
|
|
358
359
|
return inertia.resolveNamedTransition(transition, registry);
|
|
359
360
|
}
|
|
360
361
|
return inertia.resolveNamedTransition(
|
|
@@ -447,6 +448,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
450
|
}
|
|
451
|
+
const transitionSig = inertia.stableSig(transition);
|
|
450
452
|
for (const k of numericKeys) {
|
|
451
453
|
const target = anim?.[k];
|
|
452
454
|
react.useEffect(() => {
|
|
@@ -457,7 +459,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
457
459
|
}
|
|
458
460
|
const cfg = reduce ? NO_ANIMATION2 : pickTransition2(transition, k, registry);
|
|
459
461
|
numericSvs[k].value = inertia.resolveTransition(cfg, target);
|
|
460
|
-
}, [target, reduce,
|
|
462
|
+
}, [target, reduce, transitionSig]);
|
|
461
463
|
}
|
|
462
464
|
for (const k of colorKeys) {
|
|
463
465
|
const target = anim?.[k];
|
|
@@ -469,7 +471,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
469
471
|
}
|
|
470
472
|
const cfg = reduce ? NO_ANIMATION2 : pickTransition2(transition, k, registry);
|
|
471
473
|
colorSvs[k].value = inertia.resolveTransition(cfg, target);
|
|
472
|
-
}, [target, reduce,
|
|
474
|
+
}, [target, reduce, transitionSig]);
|
|
473
475
|
}
|
|
474
476
|
for (const k of arrayKeys) {
|
|
475
477
|
const target = anim?.[k];
|
|
@@ -494,7 +496,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
494
496
|
for (let i = 0; i < len; i++) {
|
|
495
497
|
svs[i].value = inertia.resolveTransition(cfg, target[i] ?? 0);
|
|
496
498
|
}
|
|
497
|
-
}, [sig, reduce,
|
|
499
|
+
}, [sig, reduce, transitionSig]);
|
|
498
500
|
}
|
|
499
501
|
const animatedProps = Animated.useAnimatedProps(() => {
|
|
500
502
|
"worklet";
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef, useEffect, useMemo } from 'react';
|
|
2
2
|
import { Path, Circle, Rect, Line } from 'react-native-svg';
|
|
3
3
|
import Animated, { useSharedValue, useAnimatedProps } from 'react-native-reanimated';
|
|
4
|
-
import { useShouldReduceMotion, useNamedTransitions, TRANSPARENT, resolveTransition, resolveNamedTransition } from '@rootnative/inertia';
|
|
4
|
+
import { useShouldReduceMotion, useNamedTransitions, TRANSPARENT, stableSig, resolveTransition, isTopLevelTransition, resolveNamedTransition } from '@rootnative/inertia';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
// src/MotionPath.tsx
|
|
@@ -174,7 +174,7 @@ var AnimatedPath = Animated.createAnimatedComponent(Path);
|
|
|
174
174
|
var NO_ANIMATION = { type: "no-animation" };
|
|
175
175
|
function pickTransition(per, key) {
|
|
176
176
|
if (!per) return void 0;
|
|
177
|
-
if (
|
|
177
|
+
if (isTopLevelTransition(per)) return per;
|
|
178
178
|
return per[key];
|
|
179
179
|
}
|
|
180
180
|
function MotionPath(props) {
|
|
@@ -248,6 +248,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
248
248
|
seedSource?.strokeDashoffset ?? strokeDashoffset ?? 0
|
|
249
249
|
);
|
|
250
250
|
const reduce = useShouldReduceMotion();
|
|
251
|
+
const transitionSig = stableSig(transition);
|
|
251
252
|
const animateD = animate?.d;
|
|
252
253
|
const animateFill = animate?.fill;
|
|
253
254
|
const animateStroke = animate?.stroke;
|
|
@@ -272,22 +273,22 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
272
273
|
for (let i = 0; i < paramSvs.length; i++) {
|
|
273
274
|
paramSvs[i].value = resolveTransition(cfg, target[i] ?? 0);
|
|
274
275
|
}
|
|
275
|
-
}, [animateD, reduce,
|
|
276
|
+
}, [animateD, reduce, transitionSig]);
|
|
276
277
|
useEffect(() => {
|
|
277
278
|
if (animateFill === void 0) return;
|
|
278
279
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "fill");
|
|
279
280
|
fillSv.value = resolveTransition(cfg, animateFill);
|
|
280
|
-
}, [animateFill, reduce,
|
|
281
|
+
}, [animateFill, reduce, transitionSig]);
|
|
281
282
|
useEffect(() => {
|
|
282
283
|
if (animateStroke === void 0) return;
|
|
283
284
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "stroke");
|
|
284
285
|
strokeSv.value = resolveTransition(cfg, animateStroke);
|
|
285
|
-
}, [animateStroke, reduce,
|
|
286
|
+
}, [animateStroke, reduce, transitionSig]);
|
|
286
287
|
useEffect(() => {
|
|
287
288
|
if (animateStrokeWidth === void 0) return;
|
|
288
289
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeWidth");
|
|
289
290
|
strokeWidthSv.value = resolveTransition(cfg, animateStrokeWidth);
|
|
290
|
-
}, [animateStrokeWidth, reduce,
|
|
291
|
+
}, [animateStrokeWidth, reduce, transitionSig]);
|
|
291
292
|
useEffect(() => {
|
|
292
293
|
if (animateStrokeOpacity === void 0) return;
|
|
293
294
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeOpacity");
|
|
@@ -295,17 +296,17 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
295
296
|
cfg,
|
|
296
297
|
animateStrokeOpacity
|
|
297
298
|
);
|
|
298
|
-
}, [animateStrokeOpacity, reduce,
|
|
299
|
+
}, [animateStrokeOpacity, reduce, transitionSig]);
|
|
299
300
|
useEffect(() => {
|
|
300
301
|
if (animateFillOpacity === void 0) return;
|
|
301
302
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "fillOpacity");
|
|
302
303
|
fillOpacitySv.value = resolveTransition(cfg, animateFillOpacity);
|
|
303
|
-
}, [animateFillOpacity, reduce,
|
|
304
|
+
}, [animateFillOpacity, reduce, transitionSig]);
|
|
304
305
|
useEffect(() => {
|
|
305
306
|
if (animateOpacity === void 0) return;
|
|
306
307
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "opacity");
|
|
307
308
|
opacitySv.value = resolveTransition(cfg, animateOpacity);
|
|
308
|
-
}, [animateOpacity, reduce,
|
|
309
|
+
}, [animateOpacity, reduce, transitionSig]);
|
|
309
310
|
useEffect(() => {
|
|
310
311
|
if (animateStrokeDashoffset === void 0) return;
|
|
311
312
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, "strokeDashoffset");
|
|
@@ -313,7 +314,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
313
314
|
cfg,
|
|
314
315
|
animateStrokeDashoffset
|
|
315
316
|
);
|
|
316
|
-
}, [animateStrokeDashoffset, reduce,
|
|
317
|
+
}, [animateStrokeDashoffset, reduce, transitionSig]);
|
|
317
318
|
const animatedProps = useAnimatedProps(() => {
|
|
318
319
|
"worklet";
|
|
319
320
|
const params = new Array(paramSvs.length);
|
|
@@ -348,7 +349,7 @@ If you need to swap to a structurally different path, remount with key={...}.`
|
|
|
348
349
|
var NO_ANIMATION2 = { type: "no-animation" };
|
|
349
350
|
function pickTransition2(transition, key, registry) {
|
|
350
351
|
if (!transition) return void 0;
|
|
351
|
-
if (typeof transition === "string" ||
|
|
352
|
+
if (typeof transition === "string" || isTopLevelTransition(transition)) {
|
|
352
353
|
return resolveNamedTransition(transition, registry);
|
|
353
354
|
}
|
|
354
355
|
return resolveNamedTransition(
|
|
@@ -441,6 +442,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
}
|
|
445
|
+
const transitionSig = stableSig(transition);
|
|
444
446
|
for (const k of numericKeys) {
|
|
445
447
|
const target = anim?.[k];
|
|
446
448
|
useEffect(() => {
|
|
@@ -451,7 +453,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
451
453
|
}
|
|
452
454
|
const cfg = reduce ? NO_ANIMATION2 : pickTransition2(transition, k, registry);
|
|
453
455
|
numericSvs[k].value = resolveTransition(cfg, target);
|
|
454
|
-
}, [target, reduce,
|
|
456
|
+
}, [target, reduce, transitionSig]);
|
|
455
457
|
}
|
|
456
458
|
for (const k of colorKeys) {
|
|
457
459
|
const target = anim?.[k];
|
|
@@ -463,7 +465,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
463
465
|
}
|
|
464
466
|
const cfg = reduce ? NO_ANIMATION2 : pickTransition2(transition, k, registry);
|
|
465
467
|
colorSvs[k].value = resolveTransition(cfg, target);
|
|
466
|
-
}, [target, reduce,
|
|
468
|
+
}, [target, reduce, transitionSig]);
|
|
467
469
|
}
|
|
468
470
|
for (const k of arrayKeys) {
|
|
469
471
|
const target = anim?.[k];
|
|
@@ -488,7 +490,7 @@ function createMotionSvgComponent(Component, config) {
|
|
|
488
490
|
for (let i = 0; i < len; i++) {
|
|
489
491
|
svs[i].value = resolveTransition(cfg, target[i] ?? 0);
|
|
490
492
|
}
|
|
491
|
-
}, [sig, reduce,
|
|
493
|
+
}, [sig, reduce, transitionSig]);
|
|
492
494
|
}
|
|
493
495
|
const animatedProps = useAnimatedProps(() => {
|
|
494
496
|
"worklet";
|
package/llms.txt
CHANGED
|
@@ -99,7 +99,7 @@ Path normalization that resamples between arbitrary shapes (the flubber-style ap
|
|
|
99
99
|
| Key | Shape | Notes |
|
|
100
100
|
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
101
101
|
| `d` | `string` | Path data. Source and target must share the same template — see above. Each scalar param springs / times independently. |
|
|
102
|
-
| `fill` | `string` | Color, interpolated via Reanimated's color setter. Defaults to `
|
|
102
|
+
| `fill` | `string` | Color, interpolated via Reanimated's color setter. Defaults to `TRANSPARENT` (`rgba(0, 0, 0, 0)`) if neither static nor `initial` is supplied. |
|
|
103
103
|
| `stroke` | `string` | Same as `fill`. |
|
|
104
104
|
| `strokeWidth` | `number` | Numeric. Default seed `1`. |
|
|
105
105
|
| `strokeOpacity` | `number` | Numeric, 0–1. |
|
|
@@ -187,7 +187,7 @@ Shape-specific rules (they mirror `MotionPath`'s command-sequence lock):
|
|
|
187
187
|
|
|
188
188
|
- **Array props lock their length at first render.** `strokeDasharray` interpolates element-wise, so a target with a different length throws in dev and is ignored in production. Remount with a new `key` to change the length.
|
|
189
189
|
- **A key only animates when it's present at mount** — in the static props, `initial`, or `animate`. Keys introduced into `animate` after mount warn in dev and are ignored. Keys never mentioned pass through as ordinary static props, so un-animated attributes keep their element defaults.
|
|
190
|
-
- Numeric keys engaged only via `animate` seed from `0`; colors seed from `
|
|
190
|
+
- Numeric keys engaged only via `animate` seed from `0`; colors seed from `TRANSPARENT` (`rgba(0, 0, 0, 0)`, exported from `@rootnative/inertia`). Give the key a static prop or an `initial` value when the mount animation should start elsewhere.
|
|
191
191
|
|
|
192
192
|
## createMotionSvgComponent {#factory}
|
|
193
193
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootnative/inertia-svg",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Animatable SVG primitives (path morphing, fill/stroke) for @rootnative/inertia, built on react-native-svg.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "RootNative",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"!**/*.test.*"
|
|
51
51
|
],
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@rootnative/inertia": ">=0.0.
|
|
53
|
+
"@rootnative/inertia": ">=0.0.10 <0.1.0",
|
|
54
54
|
"react": ">=19.0.0",
|
|
55
55
|
"react-native": ">=0.81.0",
|
|
56
56
|
"react-native-reanimated": ">=4.0.0",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"react-test-renderer": "19.1.0",
|
|
70
70
|
"tsup": "^8.3.5",
|
|
71
71
|
"typescript": "^5.7.3",
|
|
72
|
-
"@rootnative/inertia": "0.0.
|
|
72
|
+
"@rootnative/inertia": "0.0.10"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|
package/src/MotionPath.tsx
CHANGED
|
@@ -6,7 +6,9 @@ import Animated, {
|
|
|
6
6
|
type SharedValue,
|
|
7
7
|
} from 'react-native-reanimated'
|
|
8
8
|
import {
|
|
9
|
+
isTopLevelTransition,
|
|
9
10
|
resolveTransition,
|
|
11
|
+
stableSig,
|
|
10
12
|
TRANSPARENT,
|
|
11
13
|
useShouldReduceMotion,
|
|
12
14
|
type TransitionConfig,
|
|
@@ -34,7 +36,10 @@ function pickTransition(
|
|
|
34
36
|
key: keyof PathPerPropertyTransition,
|
|
35
37
|
): TransitionConfig | undefined {
|
|
36
38
|
if (!per) return undefined
|
|
37
|
-
|
|
39
|
+
// Structural check, not `'type' in per`: `SpringTransition.type` is
|
|
40
|
+
// optional, so `{ tension: 300 }` is a valid top-level config with no
|
|
41
|
+
// `type` key and must not be read as a per-property map.
|
|
42
|
+
if (isTopLevelTransition(per)) return per
|
|
38
43
|
return (per as PathPerPropertyTransition)[key]
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -209,6 +214,13 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
209
214
|
// Serialize scalar targets into stable keys so effects re-run on value
|
|
210
215
|
// change, not on every parent re-render (a fresh `animate` literal each
|
|
211
216
|
// render is the common case).
|
|
217
|
+
// Effects below key on this signature, not on the `transition` object. A
|
|
218
|
+
// parent that re-renders with an inline `transition={{ ... }}` literal makes
|
|
219
|
+
// a new object each time; keying on identity re-fired every effect and
|
|
220
|
+
// restarted the in-flight animation from its current value with the full
|
|
221
|
+
// duration. The signature only changes when the config itself changes.
|
|
222
|
+
const transitionSig = stableSig(transition)
|
|
223
|
+
|
|
212
224
|
const animateD = animate?.d
|
|
213
225
|
const animateFill = animate?.fill
|
|
214
226
|
const animateStroke = animate?.stroke
|
|
@@ -236,21 +248,21 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
236
248
|
}
|
|
237
249
|
// paramSvs / template are stable across renders by the locks above.
|
|
238
250
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
239
|
-
}, [animateD, reduce,
|
|
251
|
+
}, [animateD, reduce, transitionSig])
|
|
240
252
|
|
|
241
253
|
useEffect(() => {
|
|
242
254
|
if (animateFill === undefined) return
|
|
243
255
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'fill')
|
|
244
256
|
fillSv.value = resolveTransition(cfg, animateFill) as string
|
|
245
257
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
246
|
-
}, [animateFill, reduce,
|
|
258
|
+
}, [animateFill, reduce, transitionSig])
|
|
247
259
|
|
|
248
260
|
useEffect(() => {
|
|
249
261
|
if (animateStroke === undefined) return
|
|
250
262
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'stroke')
|
|
251
263
|
strokeSv.value = resolveTransition(cfg, animateStroke) as string
|
|
252
264
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
253
|
-
}, [animateStroke, reduce,
|
|
265
|
+
}, [animateStroke, reduce, transitionSig])
|
|
254
266
|
|
|
255
267
|
useEffect(() => {
|
|
256
268
|
if (animateStrokeWidth === undefined) return
|
|
@@ -259,7 +271,7 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
259
271
|
: pickTransition(transition, 'strokeWidth')
|
|
260
272
|
strokeWidthSv.value = resolveTransition(cfg, animateStrokeWidth) as number
|
|
261
273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
262
|
-
}, [animateStrokeWidth, reduce,
|
|
274
|
+
}, [animateStrokeWidth, reduce, transitionSig])
|
|
263
275
|
|
|
264
276
|
useEffect(() => {
|
|
265
277
|
if (animateStrokeOpacity === undefined) return
|
|
@@ -271,7 +283,7 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
271
283
|
animateStrokeOpacity,
|
|
272
284
|
) as number
|
|
273
285
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
274
|
-
}, [animateStrokeOpacity, reduce,
|
|
286
|
+
}, [animateStrokeOpacity, reduce, transitionSig])
|
|
275
287
|
|
|
276
288
|
useEffect(() => {
|
|
277
289
|
if (animateFillOpacity === undefined) return
|
|
@@ -280,14 +292,14 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
280
292
|
: pickTransition(transition, 'fillOpacity')
|
|
281
293
|
fillOpacitySv.value = resolveTransition(cfg, animateFillOpacity) as number
|
|
282
294
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
283
|
-
}, [animateFillOpacity, reduce,
|
|
295
|
+
}, [animateFillOpacity, reduce, transitionSig])
|
|
284
296
|
|
|
285
297
|
useEffect(() => {
|
|
286
298
|
if (animateOpacity === undefined) return
|
|
287
299
|
const cfg = reduce ? NO_ANIMATION : pickTransition(transition, 'opacity')
|
|
288
300
|
opacitySv.value = resolveTransition(cfg, animateOpacity) as number
|
|
289
301
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
290
|
-
}, [animateOpacity, reduce,
|
|
302
|
+
}, [animateOpacity, reduce, transitionSig])
|
|
291
303
|
|
|
292
304
|
useEffect(() => {
|
|
293
305
|
if (animateStrokeDashoffset === undefined) return
|
|
@@ -299,7 +311,7 @@ export function MotionPath(props: MotionPathProps) {
|
|
|
299
311
|
animateStrokeDashoffset,
|
|
300
312
|
) as number
|
|
301
313
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
302
|
-
}, [animateStrokeDashoffset, reduce,
|
|
314
|
+
}, [animateStrokeDashoffset, reduce, transitionSig])
|
|
303
315
|
|
|
304
316
|
const animatedProps = useAnimatedProps(() => {
|
|
305
317
|
'worklet'
|
|
@@ -5,8 +5,10 @@ import Animated, {
|
|
|
5
5
|
type SharedValue,
|
|
6
6
|
} from 'react-native-reanimated'
|
|
7
7
|
import {
|
|
8
|
+
isTopLevelTransition,
|
|
8
9
|
resolveNamedTransition,
|
|
9
10
|
resolveTransition,
|
|
11
|
+
stableSig,
|
|
10
12
|
TRANSPARENT,
|
|
11
13
|
useNamedTransitions,
|
|
12
14
|
useShouldReduceMotion,
|
|
@@ -112,7 +114,10 @@ function pickTransition(
|
|
|
112
114
|
registry: NamedTransitions,
|
|
113
115
|
): TransitionConfig | undefined {
|
|
114
116
|
if (!transition) return undefined
|
|
115
|
-
|
|
117
|
+
// Structural check, not `'type' in transition`: `SpringTransition.type` is
|
|
118
|
+
// optional, so `{ tension: 300 }` is a valid top-level config with no
|
|
119
|
+
// `type` key and must not be read as a per-property map.
|
|
120
|
+
if (typeof transition === 'string' || isTopLevelTransition(transition)) {
|
|
116
121
|
return resolveNamedTransition(transition as TransitionInput, registry)
|
|
117
122
|
}
|
|
118
123
|
return resolveNamedTransition(
|
|
@@ -299,6 +304,12 @@ export function createMotionSvgComponent<
|
|
|
299
304
|
}
|
|
300
305
|
}
|
|
301
306
|
|
|
307
|
+
// Effects below key on this signature, not on the `transition` object. A
|
|
308
|
+
// parent that re-renders with an inline `transition={{ ... }}` literal makes
|
|
309
|
+
// a new object each time; keying on identity re-fired every effect and
|
|
310
|
+
// restarted the in-flight animation from its current value with the full
|
|
311
|
+
// duration. The signature only changes when the config itself changes.
|
|
312
|
+
const transitionSig = stableSig(transition)
|
|
302
313
|
// One effect per configured key (fixed count). Targets are read into
|
|
303
314
|
// locals so the dep arrays key on the values, not on a fresh `animate`
|
|
304
315
|
// literal each render.
|
|
@@ -318,7 +329,7 @@ export function createMotionSvgComponent<
|
|
|
318
329
|
// SVs / engaged set are mount-stable; registry changes re-resolve via
|
|
319
330
|
// the transition dep on the next animate change.
|
|
320
331
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
321
|
-
}, [target, reduce,
|
|
332
|
+
}, [target, reduce, transitionSig])
|
|
322
333
|
}
|
|
323
334
|
|
|
324
335
|
for (const k of colorKeys) {
|
|
@@ -335,7 +346,7 @@ export function createMotionSvgComponent<
|
|
|
335
346
|
: pickTransition(transition, k, registry)
|
|
336
347
|
colorSvs[k]!.value = resolveTransition(cfg, target) as string
|
|
337
348
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
338
|
-
}, [target, reduce,
|
|
349
|
+
}, [target, reduce, transitionSig])
|
|
339
350
|
}
|
|
340
351
|
|
|
341
352
|
for (const k of arrayKeys) {
|
|
@@ -367,7 +378,7 @@ export function createMotionSvgComponent<
|
|
|
367
378
|
svs[i]!.value = resolveTransition(cfg, target[i] ?? 0) as number
|
|
368
379
|
}
|
|
369
380
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
370
|
-
}, [sig, reduce,
|
|
381
|
+
}, [sig, reduce, transitionSig])
|
|
371
382
|
}
|
|
372
383
|
|
|
373
384
|
const animatedProps = useAnimatedProps(() => {
|