@pixodesk/svg-animator-rn 1.0.43 → 1.0.45
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 +19 -24
- 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 +19 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/PixodeskSvgAnimator.tsx +35 -33
|
@@ -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, 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
|
|
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';
|
|
@@ -238,7 +238,7 @@ 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
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.
|
|
@@ -247,7 +247,7 @@ function compileDocument(doc: PxAnimatedSvgDocument, overrides: ConfigOverrides,
|
|
|
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
253
|
for (const w of applied.warnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.timelineOverrideIgnored, w);
|
|
@@ -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,7 +318,7 @@ 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) {
|
|
@@ -330,7 +330,7 @@ export function PixodeskSvgAnimator({
|
|
|
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.
|
|
@@ -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
|
|
@@ -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.
|
|
@@ -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.
|