@pixodesk/svg-animator-rn 1.0.41 → 1.0.44
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/README.md +6 -6
- package/dist/index.cjs +27 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +29 -34
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/PixodeskSvgAnimator.tsx +43 -41
- package/src/PxRnErrorBoundary.tsx +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixodesk/svg-animator-rn",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
4
4
|
"description": "Pixodesk SVG animator for React Native — renders animator documents via react-native-svg, driven natively by react-native-reanimated",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"src"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pixodesk/svg-animator-core": "^1.0.
|
|
30
|
+
"@pixodesk/svg-animator-core": "^1.0.44"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": ">=18",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Licensed under the MIT License. See the LICENSE file in the project root for details.
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import { clampSeekMs, createRunClock, isValidPlaybackRate, progressSpanMs, progressToTimeMs,
|
|
6
|
+
import { clampSeekMs, createRunClock, isValidPlaybackRate, progressSpanMs, progressToTimeMs, PxDiagnosticCode, PxDiagnosticKind, seekCeilingMs, timeToProgress, type PxAnimatorHandle, type PxAnimatorCallbacks, type PxControlProps, type PxPlaybackOverride, type PxDiagnostics, generateNewIds, getAnimatorConfig, getDefinitions, materializeAllInTree, resolveTrigger, validateNodeEffects, PxTimelineEngine, PxControlMode, resolveControlMode, type PxFillMode, type PxMouseOutAction, type PxPlaybackDirection, type PxAnimatedSvgDocument, type PxTimelinePatch, type PxNode, applyAnimatorConfig, foldTimelineOverride } from '@pixodesk/svg-animator-core';
|
|
7
7
|
import { createDiagnostics, reportDocumentDiagnostics } from '@pixodesk/svg-animator-core/internal';
|
|
8
8
|
import React, { createElement, useEffect, useImperativeHandle, useMemo, useRef, useState, type ComponentType, type ReactElement, type ReactNode } from 'react';
|
|
9
9
|
import { Dimensions, Platform, Pressable, View } from 'react-native';
|
|
@@ -70,7 +70,7 @@ export interface PixodeskSvgAnimatorProps
|
|
|
70
70
|
// -- Failure handling -----------------------------------------------------
|
|
71
71
|
// A document that cannot be compiled or rendered is reported through `onError` — the same
|
|
72
72
|
// `(diagnostic) => void` as every other player (review §25.1), with `diagnostic.error` the
|
|
73
|
-
// thrown Error and, when the error boundary caught it, `diagnostic.
|
|
73
|
+
// thrown Error and, when the error boundary caught it, the component stack in `diagnostic.data`.
|
|
74
74
|
// The component renders `fallback` instead of throwing, so one broken animation never
|
|
75
75
|
// takes down the screen around it. Only JavaScript failures reach this — a crash inside
|
|
76
76
|
// react-native-svg's native renderer bypasses JavaScript entirely.
|
|
@@ -238,19 +238,19 @@ const EMPTY_TRACKS: PxCompiledTracks = {
|
|
|
238
238
|
* whole thing sits behind one try/catch, and so it can be tested directly.
|
|
239
239
|
*/
|
|
240
240
|
function compileDocument(doc: PxAnimatedSvgDocument, overrides: ConfigOverrides, diag: PxDiagnostics): Compiled {
|
|
241
|
-
const { timeline, resetTimeline, duration, delay, iterations,
|
|
241
|
+
const { timeline, resetTimeline, duration, delay, iterations, start } = overrides;
|
|
242
242
|
const warnings = validateNodeEffects(doc as PxNode);
|
|
243
|
-
for (const w of warnings) diag.warn(PxDiagnosticKind.document,
|
|
243
|
+
for (const w of warnings) diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.effectsShape, w);
|
|
244
244
|
// The whole-document boundary diagnostic — see the note in the web player's entry.
|
|
245
245
|
reportDocumentDiagnostics(doc, '[PixodeskSvgAnimator]');
|
|
246
246
|
|
|
247
247
|
// The per-instance override, applied to the WIRE document BEFORE anything reads the
|
|
248
248
|
// config — `materializeAllInTree` samples motion paths against `duration`, so a later
|
|
249
249
|
// patch would be read by none of the pipeline. Same call, same rules, on every surface.
|
|
250
|
-
const patch = foldTimelineOverride(timeline, { duration, delay, iterations,
|
|
250
|
+
const patch = foldTimelineOverride(timeline, { duration, delay, iterations, start });
|
|
251
251
|
if (patch !== undefined || resetTimeline) {
|
|
252
252
|
const applied = applyAnimatorConfig(doc, patch ?? {}, { resetTimeline: !!resetTimeline });
|
|
253
|
-
for (const w of applied.warnings) diag.warn(PxDiagnosticKind.usage,
|
|
253
|
+
for (const w of applied.warnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.timelineOverrideIgnored, w);
|
|
254
254
|
doc = applied.doc;
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -291,7 +291,7 @@ function compileDocument(doc: PxAnimatedSvgDocument, overrides: ConfigOverrides,
|
|
|
291
291
|
* @public
|
|
292
292
|
*/
|
|
293
293
|
export function PixodeskSvgAnimator({
|
|
294
|
-
doc, timeline, resetTimeline, duration, delay, iterations,
|
|
294
|
+
doc, timeline, resetTimeline, duration, delay, iterations, start,
|
|
295
295
|
// (`progress` prop aliased — the name is taken by the internal reanimated SharedValue)
|
|
296
296
|
autoplay, play, pause, apiRef, progress: progressProp, time,
|
|
297
297
|
onPlay, onStop, onPause, onCancel, onFinish, onRemove, onError, fallback, onWarn, muteWarn, muteError,
|
|
@@ -318,19 +318,19 @@ export function PixodeskSvgAnimator({
|
|
|
318
318
|
try {
|
|
319
319
|
return compileDocument(
|
|
320
320
|
doc,
|
|
321
|
-
{ timeline, resetTimeline, duration, delay, iterations,
|
|
321
|
+
{ timeline, resetTimeline, duration, delay, iterations, start },
|
|
322
322
|
makeDiag(),
|
|
323
323
|
);
|
|
324
324
|
} catch (e) {
|
|
325
325
|
// A malformed document must not take the host screen down with it. This instance
|
|
326
326
|
// will not play — an ERROR, reported once, here (review §25.1).
|
|
327
327
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
328
|
-
makeDiag().error(PxDiagnosticKind.internal,
|
|
328
|
+
makeDiag().error(PxDiagnosticKind.internal, PxDiagnosticCode.rnCompileFailed, error);
|
|
329
329
|
return { doc: null, tracks: EMPTY_TRACKS, error };
|
|
330
330
|
}
|
|
331
331
|
// `timeline` is an object prop, so a fresh literal each render would recompile the whole
|
|
332
332
|
// document. Key on its CONTENT — the override is small, unlike the document.
|
|
333
|
-
}, [doc, timelineKey, resetTimeline, duration, delay, iterations,
|
|
333
|
+
}, [doc, timelineKey, resetTimeline, duration, delay, iterations, start]);
|
|
334
334
|
|
|
335
335
|
const tracks: PxCompiledTracks = compiled.tracks;
|
|
336
336
|
// The span `progress` 0–1 covers (ONE iteration when endless) — NOT the seek ceiling.
|
|
@@ -450,7 +450,7 @@ export function PixodeskSvgAnimator({
|
|
|
450
450
|
},
|
|
451
451
|
setPlaybackRate: (rate: number) => {
|
|
452
452
|
if (!isValidPlaybackRate(rate)) {
|
|
453
|
-
makeDiag().warn(PxDiagnosticKind.usage,
|
|
453
|
+
makeDiag().warn(PxDiagnosticKind.usage, PxDiagnosticCode.rateRejected);
|
|
454
454
|
return;
|
|
455
455
|
}
|
|
456
456
|
rateRef.current = rate;
|
|
@@ -495,12 +495,14 @@ export function PixodeskSvgAnimator({
|
|
|
495
495
|
// -- Declarative control --------------------------------------------------
|
|
496
496
|
|
|
497
497
|
// The EFFECTIVE trigger, read back off the COMPILED document — so it already reflects the
|
|
498
|
-
// `timeline` override and the `
|
|
498
|
+
// `timeline` override and the `start` shortcut, both merged in before compilation.
|
|
499
499
|
// Resolved through core's one table, so a document means the same here as on the web:
|
|
500
|
-
// no `
|
|
500
|
+
// no `start` = 'load', no `offScreen` = 'pause'.
|
|
501
501
|
const trigger = resolveTrigger(compiled.doc ? getAnimatorConfig(compiled.doc)?.trigger : undefined);
|
|
502
|
-
const
|
|
503
|
-
const
|
|
502
|
+
const effectiveStart = trigger.start;
|
|
503
|
+
const effectiveOffScreen = trigger.offScreen;
|
|
504
|
+
/** Whether visibility governs this document. `continue` means "run wherever it is". */
|
|
505
|
+
const gated = effectiveOffScreen !== 'continue';
|
|
504
506
|
|
|
505
507
|
// ONE control-mode rule, decided in core and shared with React and Vue (API review §1/§7).
|
|
506
508
|
// This component always had the right ORDER but no name for it, and never told anyone when
|
|
@@ -510,7 +512,7 @@ export function PixodeskSvgAnimator({
|
|
|
510
512
|
|
|
511
513
|
useEffect(() => {
|
|
512
514
|
const diag = makeDiag();
|
|
513
|
-
for (const w of modeWarnings) diag.warn(PxDiagnosticKind.usage, w);
|
|
515
|
+
for (const w of modeWarnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.controlPropsConflict, w);
|
|
514
516
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
515
517
|
}, [modeWarnings.join('|')]);
|
|
516
518
|
|
|
@@ -529,22 +531,23 @@ export function PixodeskSvgAnimator({
|
|
|
529
531
|
else api.play();
|
|
530
532
|
return;
|
|
531
533
|
}
|
|
532
|
-
// 'click'
|
|
533
|
-
|
|
534
|
+
// 'click' starts from its own handler below, and a GATED document starts from the
|
|
535
|
+
// visibility poll — calling play() here as well would defeat the gate.
|
|
536
|
+
if (compMode === PxControlMode.autoplay && effectiveStart === 'load' && !gated) {
|
|
534
537
|
api.play();
|
|
535
538
|
}
|
|
536
539
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
537
540
|
}, [compiled, compMode, autoplay, play, pause, progressProp, time]);
|
|
538
541
|
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
// poll is cheap (a native measure every
|
|
542
|
-
//
|
|
542
|
+
// THE VISIBILITY GATE — permission to run, whatever started the animation, exactly as on the
|
|
543
|
+
// web (`PxVisibilityGate`). React Native has no IntersectionObserver, so visibility is sampled
|
|
544
|
+
// by measuring the view against the window box. The poll is cheap (a native measure every
|
|
545
|
+
// 200ms) and only runs while the gate is active, which `offScreen: 'continue'` turns off.
|
|
543
546
|
const scrollRef = useRef<View | null>(null);
|
|
544
547
|
const inViewRef = useRef(false);
|
|
545
548
|
useEffect(() => {
|
|
546
|
-
if (!autoplay ||
|
|
547
|
-
const threshold = trigger.
|
|
549
|
+
if (!autoplay || !gated) return;
|
|
550
|
+
const threshold = trigger.visibilityThreshold;
|
|
548
551
|
inViewRef.current = false;
|
|
549
552
|
|
|
550
553
|
const check = () => {
|
|
@@ -561,9 +564,8 @@ export function PixodeskSvgAnimator({
|
|
|
561
564
|
if (isIn) {
|
|
562
565
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
563
566
|
api.play();
|
|
564
|
-
} else if (
|
|
565
|
-
else
|
|
566
|
-
else if (effectiveOutAction !== 'continue') api.pause();
|
|
567
|
+
} else if (effectiveOffScreen === 'reset') api.cancel();
|
|
568
|
+
else api.pause(); // 'continue' never reaches here — the poll does not run
|
|
567
569
|
});
|
|
568
570
|
};
|
|
569
571
|
|
|
@@ -571,7 +573,7 @@ export function PixodeskSvgAnimator({
|
|
|
571
573
|
const id = setInterval(check, 200);
|
|
572
574
|
return () => clearInterval(id);
|
|
573
575
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
574
|
-
}, [compiled, autoplay,
|
|
576
|
+
}, [compiled, autoplay, gated, effectiveOffScreen]);
|
|
575
577
|
|
|
576
578
|
// Stop cleanly on unmount / doc swap — and say so, the way the web's destroy() does (§18):
|
|
577
579
|
// both are "the animator was thrown away", so `onRemove` fires, and `onStop` with it.
|
|
@@ -648,7 +650,7 @@ export function PixodeskSvgAnimator({
|
|
|
648
650
|
// (an ERROR, review §25.1) and render nothing rather than unmount the host screen.
|
|
649
651
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
650
652
|
renderErrorRef.current = error;
|
|
651
|
-
makeDiag().error(PxDiagnosticKind.internal,
|
|
653
|
+
makeDiag().error(PxDiagnosticKind.internal, PxDiagnosticCode.rnRenderFailed, error);
|
|
652
654
|
return null;
|
|
653
655
|
}
|
|
654
656
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -658,7 +660,7 @@ export function PixodeskSvgAnimator({
|
|
|
658
660
|
const diag = makeDiag();
|
|
659
661
|
// `platform`: these come from the react-native-svg prop mapper — shapes this renderer
|
|
660
662
|
// cannot express, rather than anything wrong with the file.
|
|
661
|
-
for (const w of warningsRef.current) diag.warn(PxDiagnosticKind.platform, w);
|
|
663
|
+
for (const w of warningsRef.current) diag.warn(PxDiagnosticKind.platform, PxDiagnosticCode.rnUnsupported, w);
|
|
662
664
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
663
665
|
}, [root]);
|
|
664
666
|
|
|
@@ -667,23 +669,17 @@ export function PixodeskSvgAnimator({
|
|
|
667
669
|
const failure = compiled.error ?? renderErrorRef.current;
|
|
668
670
|
if (failure) return fallback ? fallback(failure) : null;
|
|
669
671
|
|
|
670
|
-
// `
|
|
671
|
-
// tap to
|
|
672
|
-
//
|
|
673
|
-
// so both are left to the host app.
|
|
672
|
+
// `start: 'click'` — the touch analogue of the web player's click trigger: a plain toggle,
|
|
673
|
+
// tap to play and tap again to pause. Hover (`mouseOver`) has no touch equivalent, so it is
|
|
674
|
+
// left to the host app.
|
|
674
675
|
let content: ReactElement | null = root;
|
|
675
676
|
|
|
676
|
-
if (autoplay &&
|
|
677
|
-
// `collapsable={false}` keeps the view in the native tree so it can be measured.
|
|
678
|
-
content = <View ref={scrollRef} collapsable={false}>{root}</View>;
|
|
679
|
-
} else if (autoplay && effectiveStartOn === 'click' && root) {
|
|
677
|
+
if (autoplay && effectiveStart === 'click' && root) {
|
|
680
678
|
content = (
|
|
681
679
|
<Pressable
|
|
682
680
|
onPress={() => {
|
|
683
681
|
if (playingRef.current) {
|
|
684
|
-
|
|
685
|
-
else if (effectiveOutAction === 'reverse') { api.setPlaybackRate(-Math.abs(rateRef.current || 1)); api.play(); }
|
|
686
|
-
else if (effectiveOutAction !== 'continue') api.pause();
|
|
682
|
+
api.pause();
|
|
687
683
|
} else {
|
|
688
684
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
689
685
|
api.play();
|
|
@@ -695,6 +691,12 @@ export function PixodeskSvgAnimator({
|
|
|
695
691
|
);
|
|
696
692
|
}
|
|
697
693
|
|
|
694
|
+
// The gate measures the view, so it must survive in the native tree:
|
|
695
|
+
// `collapsable={false}` keeps it there.
|
|
696
|
+
if (autoplay && gated && content) {
|
|
697
|
+
content = <View ref={scrollRef} collapsable={false}>{content}</View>;
|
|
698
|
+
}
|
|
699
|
+
|
|
698
700
|
// Catches what the try/catch above cannot: throws during React's own render
|
|
699
701
|
// and commit of the tree — react-native-svg internals, reanimated failing
|
|
700
702
|
// to attach to a component that turns out not to be a host view, and so on.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*---------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
6
|
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
7
|
-
import { PxDiagnosticKind, type PxDiagnostics } from '@pixodesk/svg-animator-core';
|
|
7
|
+
import { PxDiagnosticCode, PxDiagnosticKind, type PxDiagnostics } from '@pixodesk/svg-animator-core';
|
|
8
8
|
import { createDiagnostics } from '@pixodesk/svg-animator-core/internal';
|
|
9
9
|
|
|
10
10
|
/** @public @advanced */
|
|
@@ -14,7 +14,7 @@ export interface PxRnErrorBoundaryProps {
|
|
|
14
14
|
fallback?: (error: Error) => ReactNode;
|
|
15
15
|
/**
|
|
16
16
|
* Where the failure is reported (API review §5, §25.1): `error(internal, …)` with
|
|
17
|
-
*
|
|
17
|
+
* the component stack passed as data — so it reaches `onError` like every other failure.
|
|
18
18
|
* Defaults to the console.
|
|
19
19
|
*/
|
|
20
20
|
diag?: PxDiagnostics;
|
|
@@ -47,7 +47,7 @@ export class PxRnErrorBoundary extends Component<PxRnErrorBoundaryProps, State>
|
|
|
47
47
|
|
|
48
48
|
override componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
49
49
|
(this.props.diag ?? createDiagnostics(undefined, '[PixodeskSvgAnimator]'))
|
|
50
|
-
.error(PxDiagnosticKind.internal, error,
|
|
50
|
+
.error(PxDiagnosticKind.internal, PxDiagnosticCode.rnBoundaryCaught, error, info?.componentStack ?? undefined);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
override componentDidUpdate(prev: PxRnErrorBoundaryProps): void {
|