@pixodesk/svg-animator-rn 1.0.34 → 1.0.39

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/dist/index.d.ts CHANGED
@@ -1,69 +1,44 @@
1
- import { PxAnimatedSvgDocument, FillMode, PlaybackDirection, OutAction, PxDefs, PxNode } from '@pixodesk/svg-animator-core';
1
+ import { PxPlaybackOverrideProps, PxControlProps, PxComponentCallbacks, PxAnimatedSvgDocument, PxTimelinePatch, PxAnimatorHandle, PxDefs, PxNode, PxDiagnostics } from '@pixodesk/svg-animator-core';
2
2
  import React, { ReactElement, ComponentType, ReactNode, Component, ErrorInfo } from 'react';
3
3
 
4
- /** Imperative playback API — mirrors ReactAnimatorApi from svg-animator-react. */
5
- interface RnAnimatorApi {
6
- /** Returns true if the animation is currently running. */
7
- isPlaying(): boolean;
8
- /** Starts or resumes the animation. */
9
- play(): void;
10
- /** Pauses the animation at its current state. */
11
- pause(): void;
12
- /** Stops the animation and resets it to its initial state. */
13
- cancel(): void;
14
- /** Jumps to the end of the animation and holds the final state. */
15
- finish(): void;
16
- /** Changes the speed of the animation. 1 is normal, 2 is double. */
17
- setPlaybackRate(rate: number): void;
18
- /** Returns the current playback time in milliseconds. */
19
- getCurrentTime(): number | null;
20
- /** Jumps to a specific time (in milliseconds) in the animation. */
21
- setCurrentTime(time: number): void;
22
- }
23
- interface PixodeskSvgAnimatorProps {
4
+ /**
5
+ * The imperative handle `apiRef` is filled with — core's `PxAnimatorHandle` under this package's
6
+ * name (review §9). It no longer "mirrors" the React one by hand: the local copy had already
7
+ * drifted (its `setPlaybackRate` comment lost "negative plays backwards"). One definition.
8
+ */
9
+ type RnAnimatorApi = PxAnimatorHandle;
10
+ /**
11
+ * The component's props. The playback override, the control props and the callbacks are core's
12
+ * shared shapes (review §9) — `PxPlaybackOverrideProps`, `PxControlProps` and
13
+ * `PxComponentCallbacks` — so React, Vue and React Native cannot drift apart. Only what differs
14
+ * on this platform is declared here: `onError` keeps its richer signature (the error boundary
15
+ * hands it a component stack), and `fallback` has no web counterpart.
16
+ */
17
+ interface PixodeskSvgAnimatorProps extends PxPlaybackOverrideProps, PxControlProps, Omit<PxComponentCallbacks, 'onError'> {
24
18
  /** The animation document to render. */
25
19
  doc: PxAnimatedSvgDocument;
26
- /** Duration of a single iteration in milliseconds. */
27
- duration?: number;
28
- /** Delay before the animation starts, in milliseconds. */
29
- delay?: number;
30
- /** Number of iterations, or 'infinite' for endless looping. */
31
- iterations?: number | 'infinite';
32
- /** Defines the element's state when the animation is not active. */
33
- fill?: FillMode;
34
- /** Playback direction. */
35
- direction?: PlaybackDirection;
36
- /** Snap back to the start state after a natural finish. */
37
- resetOnFinish?: boolean;
38
20
  /**
39
- * What a second tap does when `startOn: 'click'` is active.
40
- * Defaults to the document's `trigger.outAction`, else `'pause'`.
21
+ * Per-instance override of the document's `timeline` — see `PxPlaybackOverrideProps`.
22
+ *
23
+ * `engine` is accepted but ignored here: React Native always uses the `native` (fully
24
+ * flattened) materialization, because react-native-svg has no `<use>` shadow-tree
25
+ * propagation.
26
+ */
27
+ timeline?: PxTimelinePatch | string;
28
+ /**
29
+ * Ref populated with the imperative playback API. Filled in EVERY mode and never picks one
30
+ * (review §1): `autoplay` next to it still autoplays.
41
31
  */
42
- outAction?: OutAction;
43
- /** When true, honours the document trigger (`startOn: 'load'` plays on mount). */
44
- autoplay?: boolean;
45
- /** Starts playback unconditionally. */
46
- play?: boolean;
47
- /** Pauses current playback. */
48
- pause?: boolean;
49
- /** Ref populated with the imperative playback API. */
50
32
  apiRef?: React.RefObject<RnAnimatorApi | null>;
51
- /** Seek to a fraction (0–1) of the whole timeline (duration × iterations). */
52
- progress?: number;
53
- /** Seek to a specific time in milliseconds. */
54
- time?: number;
55
- onPlay?: () => void;
56
- onStop?: () => void;
57
- onPause?: () => void;
58
- onCancel?: () => void;
59
- onFinish?: () => void;
60
33
  /**
61
34
  * Called when a document cannot be compiled or rendered. The component
62
35
  * renders {@link fallback} instead of throwing, so a single broken
63
36
  * animation never takes down the screen around it.
64
37
  *
65
38
  * Only JavaScript failures reach this — a crash inside react-native-svg's
66
- * native renderer bypasses JavaScript entirely.
39
+ * native renderer bypasses JavaScript entirely. Richer than the web's
40
+ * `onError(diagnostic)` because the error boundary hands it a component stack; it is
41
+ * adapted into the shared diagnostics channel rather than narrowed to match it.
67
42
  */
68
43
  onError?: (error: Error, componentStack?: string) => void;
69
44
  /** Rendered in place of the animation after a failure. Default: nothing. */
@@ -72,14 +47,15 @@ interface PixodeskSvgAnimatorProps {
72
47
  /**
73
48
  * React Native component for rendering and controlling Pixodesk SVG animations.
74
49
  *
75
- * The document is materialised once through the shared core pipeline (effects,
50
+ * The document is materialized once through the shared core pipeline (effects,
76
51
  * loops, motion-path sampling, animated-`<use>` inlining — identical to the
77
- * web frames engine), compiled into densely sampled per-element tracks, and
52
+ * web's `native` engine, NOT the frame loop, which keeps `<use>` live), compiled
53
+ * into densely sampled per-element tracks, and
78
54
  * played back natively: a single reanimated progress value driven by
79
55
  * `withTiming`/`withRepeat` on the UI thread, with per-element worklets
80
56
  * indexing the precompiled tracks. No JS-thread frame loop.
81
57
  */
82
- declare function PixodeskSvgAnimator({ doc, duration, delay, iterations, fill, direction, resetOnFinish, outAction: outActionProp, autoplay, play, pause, apiRef, progress: progressProp, time, onPlay, onStop, onPause, onCancel, onFinish, onError, fallback, }: PixodeskSvgAnimatorProps): ReactElement | null;
58
+ declare function PixodeskSvgAnimator({ doc, timeline, resetTimeline, duration, delay, iterations, startOn, autoplay, play, pause, apiRef, progress: progressProp, time, onPlay, onStop, onPause, onCancel, onFinish, onRemove, onError, fallback, onWarn, silent, }: PixodeskSvgAnimatorProps): ReactElement | null;
83
59
 
84
60
  interface RenderRnNodeOptions {
85
61
  /** Collects non-fatal issues (unsupported tags, dropped attrs). */
@@ -99,13 +75,13 @@ interface RenderRnNodeOptions {
99
75
  decorate?: (node: PxNode, Component: ComponentType<any>, props: Record<string, any>, children: ReactNode, key: string | number | undefined) => ReactElement | undefined;
100
76
  }
101
77
  /**
102
- * Converts core-normalised wire props into react-native-svg props: RN prop
103
- * naming, sanitisation (same security rules as the web renderer), numeric
78
+ * Converts core-normalized wire props into react-native-svg props: RN prop
79
+ * naming, sanitization (same security rules as the web renderer), numeric
104
80
  * coercion where possible.
105
81
  */
106
82
  declare function toRnProps(props: Record<string, any>, warnings?: Array<string>, tag?: string): Record<string, any>;
107
83
  /**
108
- * Renders a (materialised) PxNode tree to react-native-svg elements.
84
+ * Renders a (materialized) PxNode tree to react-native-svg elements.
109
85
  * Mirrors the web `renderNode` contract: unsupported/dangerous tags are
110
86
  * skipped with a warning, never a crash.
111
87
  */
@@ -116,6 +92,8 @@ interface PxRnErrorBoundaryProps {
116
92
  /** Rendered instead of the children once something has thrown. */
117
93
  fallback?: (error: Error) => ReactNode;
118
94
  onError?: (error: Error, info?: string) => void;
95
+ /** Where to report the failure (API review §5). Defaults to the console. */
96
+ diag?: PxDiagnostics;
119
97
  }
120
98
  interface State {
121
99
  error: Error | null;
@@ -170,7 +148,7 @@ declare function openClosedTextPathTargets(doc: PxNode, warnings?: Array<string>
170
148
  * The compiler densely samples every animated property through core's
171
149
  * `calcAnimationValues` — the exact function the web frames engine renders
172
150
  * with — so RN playback is value-identical to the web player. Easing, loops,
173
- * transform composition, colour interpolation and path morphing are all baked
151
+ * transform composition, color interpolation and path morphing are all baked
174
152
  * into the samples at compile time; the UI-thread worklet only indexes arrays.
175
153
  */
176
154
  interface PxElementTracks {
@@ -215,7 +193,7 @@ interface CompileTracksOptions {
215
193
  native?: boolean;
216
194
  }
217
195
  /**
218
- * Compiles a MATERIALISED document (run `materialiseAllInTree(doc, 'frames')`
196
+ * Compiles a MATERIALIZED document (run `materializeAllInTree(doc, 'frames')`
219
197
  * + `generateNewIds` first) into densely sampled per-element tracks.
220
198
  */
221
199
  declare function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOptions): PxCompiledTracks;
@@ -232,7 +210,7 @@ declare function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksO
232
210
  declare function sampleProps(tracks: PxElementTracks, tMs: number, stepMs: number, sampleCount: number, native?: boolean): Record<string, string | number | Array<number>>;
233
211
 
234
212
  /**
235
- * Converts one normalised wire attribute name (camelCase after core's
213
+ * Converts one normalized wire attribute name (camelCase after core's
236
214
  * `getNormalizedProps`, or kebab-case raw) to a react-native-svg prop name.
237
215
  * Returns undefined for props that must be dropped.
238
216
  *
package/dist/index.js CHANGED
@@ -32,12 +32,28 @@ var __objRest = (source, exclude) => {
32
32
 
33
33
  // src/PixodeskSvgAnimator.tsx
34
34
  import {
35
+ clampSeekMs,
36
+ createDiagnostics as createDiagnostics2,
37
+ createRunClock,
38
+ isValidPlaybackRate,
39
+ progressSpanMs,
40
+ progressToTimeMs,
41
+ PX_RATE_REJECTED,
42
+ PxDiagnosticKind as PxDiagnosticKind2,
43
+ seekCeilingMs,
44
+ timeToProgress,
45
+ reportDocumentDiagnostics,
35
46
  generateNewIds,
36
47
  getAnimatorConfig as getAnimatorConfig2,
37
48
  getDefs,
38
- materialiseAllInTree,
49
+ materializeAllInTree,
50
+ resolveTrigger,
39
51
  validateNodeEffects,
40
- PxAnimatorEngine as PxAnimatorEngine2
52
+ PxTimelineEngine as PxTimelineEngine2,
53
+ PxControlMode,
54
+ resolveControlMode,
55
+ applyAnimatorConfig,
56
+ foldTimelineOverride
41
57
  } from "@pixodesk/svg-animator-core";
42
58
  import { createElement as createElement2, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
43
59
  import { Dimensions, Platform, Pressable, View } from "react-native";
@@ -57,9 +73,9 @@ import Animated, {
57
73
  import {
58
74
  calcAnimationValues,
59
75
  getAnimatorConfig,
60
- getNormalisedBindings,
76
+ getNormalizedBindings,
61
77
  DEFAULT_DURATION_MS,
62
- PxAnimatorEngine
78
+ PxTimelineEngine
63
79
  } from "@pixodesk/svg-animator-core";
64
80
 
65
81
  // src/PxRnPropNames.ts
@@ -180,7 +196,7 @@ function compileTracks(doc, opts) {
180
196
  ((_a2 = n == null ? void 0 : n.children) != null ? _a2 : []).forEach(indexTags);
181
197
  };
182
198
  indexTags(doc);
183
- const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames) || [];
199
+ const bindings = getNormalizedBindings(doc, PxTimelineEngine.js) || [];
184
200
  const elements = [];
185
201
  for (const binding of bindings) {
186
202
  const animDef = binding.animate;
@@ -243,9 +259,8 @@ function sampleProps(tracks, tMs, stepMs, sampleCount, native = false) {
243
259
  import {
244
260
  getNormalizedProps,
245
261
  resolveStyle,
246
- sanitiseAttributeValue,
262
+ sanitizeAttributeValue,
247
263
  DISALLOWED_SVG_TAGS_LOWER,
248
- TEXT_ATTR,
249
264
  TEXT_CONTENT_ATTR
250
265
  } from "@pixodesk/svg-animator-core";
251
266
  import { createElement } from "react";
@@ -364,14 +379,14 @@ var RN_SVG_COMPONENTS = {
364
379
 
365
380
  // src/PxRnRender.tsx
366
381
  function toRnProps(props, warnings, tag) {
367
- const normalised = getNormalizedProps(props);
382
+ const normalized = getNormalizedProps(props);
368
383
  const out = {};
369
- for (const key of Object.keys(normalised)) {
370
- const sanitised = sanitiseAttributeValue(key, normalised[key]);
371
- if (sanitised === void 0) continue;
384
+ for (const key of Object.keys(normalized)) {
385
+ const sanitized = sanitizeAttributeValue(key, normalized[key]);
386
+ if (sanitized === void 0) continue;
372
387
  const rnKey = toRnPropName(key);
373
388
  if (!rnKey) continue;
374
- out[rnKey] = toRnPropValue(rnKey, String(sanitised), tag);
389
+ out[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
375
390
  }
376
391
  return out;
377
392
  }
@@ -398,12 +413,12 @@ function renderRnNode(node, opts = {}, key) {
398
413
  for (const [k, v] of Object.entries(resolved)) {
399
414
  const rnKey = toRnPropName(k);
400
415
  if (!rnKey || rnKey in rnProps) continue;
401
- const sanitised = sanitiseAttributeValue(rnKey, v);
402
- if (sanitised === void 0) continue;
403
- rnProps[rnKey] = toRnPropValue(rnKey, String(sanitised), tag);
416
+ const sanitized = sanitizeAttributeValue(rnKey, v);
417
+ if (sanitized === void 0) continue;
418
+ rnProps[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
404
419
  }
405
420
  }
406
- const textContent = props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR];
421
+ const textContent = props[TEXT_CONTENT_ATTR];
407
422
  let childElements = void 0;
408
423
  if (Array.isArray(children) && children.length > 0) {
409
424
  childElements = children.map((ch, i) => {
@@ -429,6 +444,7 @@ function renderRnNode(node, opts = {}, key) {
429
444
 
430
445
  // src/PxRnErrorBoundary.tsx
431
446
  import { Component } from "react";
447
+ import { createDiagnostics, PxDiagnosticKind } from "@pixodesk/svg-animator-core";
432
448
  var PxRnErrorBoundary = class extends Component {
433
449
  constructor() {
434
450
  super(...arguments);
@@ -438,9 +454,9 @@ var PxRnErrorBoundary = class extends Component {
438
454
  return { error };
439
455
  }
440
456
  componentDidCatch(error, info) {
441
- var _a, _b, _c, _d;
457
+ var _a, _b, _c, _d, _e;
442
458
  (_c = (_b = this.props).onError) == null ? void 0 : _c.call(_b, error, (_a = info == null ? void 0 : info.componentStack) != null ? _a : void 0);
443
- console.warn("[PixodeskSvgAnimator] render failed:", (_d = error == null ? void 0 : error.message) != null ? _d : error);
459
+ ((_d = this.props.diag) != null ? _d : createDiagnostics(void 0, "[PixodeskSvgAnimator]")).warn(PxDiagnosticKind.internal, "render failed: " + ((_e = error == null ? void 0 : error.message) != null ? _e : String(error)));
444
460
  }
445
461
  componentDidUpdate(prev) {
446
462
  if (this.state.error && prev.children !== this.props.children) {
@@ -599,38 +615,33 @@ var EMPTY_TRACKS = {
599
615
  sampleCount: 2,
600
616
  elements: []
601
617
  };
602
- function compileDocument(doc, overrides) {
603
- const { duration, delay, iterations, fill, direction, resetOnFinish } = overrides;
618
+ function compileDocument(doc, overrides, diag) {
619
+ const { timeline, resetTimeline, duration, delay, iterations, startOn } = overrides;
604
620
  const warnings = validateNodeEffects(doc);
605
- for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
606
- let prepared = materialiseAllInTree(doc, PxAnimatorEngine2.waapi);
621
+ for (const w of warnings) diag.warn(PxDiagnosticKind2.document, "effects shape: " + w);
622
+ reportDocumentDiagnostics(doc, "[PixodeskSvgAnimator]");
623
+ const patch = foldTimelineOverride(timeline, { duration, delay, iterations, startOn });
624
+ if (patch !== void 0 || resetTimeline) {
625
+ const applied = applyAnimatorConfig(doc, patch != null ? patch : {}, { resetDefaults: !!resetTimeline });
626
+ for (const w of applied.warnings) diag.warn(PxDiagnosticKind2.usage, "timeline override: " + w);
627
+ doc = applied.doc;
628
+ }
629
+ let prepared = materializeAllInTree(doc, PxTimelineEngine2.native);
607
630
  if (NATIVE_SVG_VIEWS) {
608
631
  prepared = openClosedTextPathTargets(prepared);
609
632
  }
610
- const animator = getAnimatorConfig2(prepared) || {};
611
- prepared = __spreadProps(__spreadValues({}, prepared), {
612
- animator: __spreadProps(__spreadValues({}, animator), {
613
- duration: duration !== void 0 ? duration : animator.duration,
614
- delay: delay !== void 0 ? delay : animator.delay,
615
- iterations: iterations !== void 0 ? iterations : animator.iterations,
616
- fill: fill !== void 0 ? fill : animator.fill,
617
- direction: direction !== void 0 ? direction : animator.direction,
618
- resetOnFinish: resetOnFinish !== void 0 ? resetOnFinish : animator.resetOnFinish
619
- })
620
- });
621
633
  prepared = generateNewIds(prepared);
622
634
  const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
623
635
  return { doc: prepared, tracks, error: null };
624
636
  }
625
637
  function PixodeskSvgAnimator({
626
638
  doc,
639
+ timeline,
640
+ resetTimeline,
627
641
  duration,
628
642
  delay,
629
643
  iterations,
630
- fill,
631
- direction,
632
- resetOnFinish,
633
- outAction: outActionProp,
644
+ startOn,
634
645
  // (`progress` prop aliased — the name is taken by the internal reanimated SharedValue)
635
646
  autoplay,
636
647
  play,
@@ -643,24 +654,42 @@ function PixodeskSvgAnimator({
643
654
  onPause,
644
655
  onCancel,
645
656
  onFinish,
657
+ onRemove,
646
658
  onError,
647
- fallback
659
+ fallback,
660
+ onWarn,
661
+ silent
648
662
  }) {
649
- var _a, _b, _c, _d;
663
+ var _a, _b;
664
+ const timelineKey = typeof timeline === "string" ? timeline : JSON.stringify(timeline != null ? timeline : null);
665
+ const makeDiag = () => createDiagnostics2({
666
+ onWarn,
667
+ // This component's public `onError` is richer — `(error, componentStack?)` — and the
668
+ // error boundary hands it a component stack. Adapt rather than narrow it; the ternary
669
+ // keeps "not given" as undefined, so the console fallback still fires.
670
+ onError: onError ? (d) => {
671
+ var _a2;
672
+ return onError((_a2 = d.error) != null ? _a2 : new Error(d.message));
673
+ } : void 0,
674
+ silent
675
+ }, "[PixodeskSvgAnimator]");
650
676
  const compiled = useMemo(() => {
651
677
  try {
652
678
  return compileDocument(
653
679
  doc,
654
- { duration, delay, iterations, fill, direction, resetOnFinish }
680
+ { timeline, resetTimeline, duration, delay, iterations, startOn },
681
+ makeDiag()
655
682
  );
656
683
  } catch (e) {
657
684
  const error = e instanceof Error ? e : new Error(String(e));
658
- console.warn("[PixodeskSvgAnimator] could not compile the document:", error.message);
685
+ makeDiag().warn(PxDiagnosticKind2.internal, "could not compile the document: " + error.message);
659
686
  return { doc: null, tracks: EMPTY_TRACKS, error };
660
687
  }
661
- }, [doc, duration, delay, iterations, fill, direction, resetOnFinish]);
688
+ }, [doc, timelineKey, resetTimeline, duration, delay, iterations, startOn]);
662
689
  const tracks = compiled.tracks;
663
- const totalDuration = tracks.duration * (tracks.iterations === Infinity ? 1 : tracks.iterations);
690
+ const totalDuration = progressSpanMs(tracks.duration, tracks.iterations);
691
+ const seekCeiling = seekCeilingMs(tracks.duration, tracks.iterations);
692
+ const runClock = useMemo(() => createRunClock(seekCeiling), [seekCeiling]);
664
693
  const progress = useSharedValue(0);
665
694
  const playingRef = useRef(false);
666
695
  const rateRef = useRef(1);
@@ -672,6 +701,8 @@ function PixodeskSvgAnimator({
672
701
  };
673
702
  const notifyFinish = () => {
674
703
  playingRef.current = false;
704
+ runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
705
+ runClock.stop();
675
706
  progress.value = restingPosition();
676
707
  onFinish == null ? void 0 : onFinish();
677
708
  onStop == null ? void 0 : onStop();
@@ -708,12 +739,15 @@ function PixodeskSvgAnimator({
708
739
  const api = {
709
740
  isPlaying: () => playingRef.current,
710
741
  play: () => {
742
+ if (Number.isFinite(seekCeiling) && runClock.now() >= seekCeiling) runClock.seek(0);
743
+ runClock.start(rateRef.current);
711
744
  startFrom(progress.value);
712
745
  onPlay == null ? void 0 : onPlay();
713
746
  },
714
747
  pause: () => {
715
748
  cancelAnimation(progress);
716
749
  playingRef.current = false;
750
+ runClock.stop();
717
751
  onPause == null ? void 0 : onPause();
718
752
  onStop == null ? void 0 : onStop();
719
753
  },
@@ -721,62 +755,82 @@ function PixodeskSvgAnimator({
721
755
  cancelAnimation(progress);
722
756
  progress.value = 0;
723
757
  playingRef.current = false;
758
+ runClock.seek(0);
759
+ runClock.stop();
724
760
  onCancel == null ? void 0 : onCancel();
725
761
  onStop == null ? void 0 : onStop();
726
762
  },
727
763
  finish: () => {
728
764
  cancelAnimation(progress);
729
765
  playingRef.current = false;
766
+ runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
767
+ runClock.stop();
730
768
  progress.value = restingPosition();
731
769
  onFinish == null ? void 0 : onFinish();
732
770
  onStop == null ? void 0 : onStop();
733
771
  },
734
772
  setPlaybackRate: (rate) => {
735
- if (!isFinite(rate) || rate === 0) {
736
- console.warn("setPlaybackRate: rate must be finite and non-zero");
773
+ if (!isValidPlaybackRate(rate)) {
774
+ makeDiag().warn(PxDiagnosticKind2.usage, PX_RATE_REJECTED);
737
775
  return;
738
776
  }
739
777
  rateRef.current = rate;
740
- if (playingRef.current) startFrom(progress.value);
778
+ if (playingRef.current) {
779
+ runClock.start(rate);
780
+ startFrom(progress.value);
781
+ }
741
782
  },
742
- getCurrentTime: () => progress.value,
783
+ // Ms from the start of the WHOLE run, like every other engine (review §3). This used
784
+ // to return `progress.value`, which is ms within the current iteration — so a slider
785
+ // built on it jumped back to 0 every time the animation repeated.
786
+ getCurrentTime: () => runClock.now(),
743
787
  setCurrentTime: (t) => {
744
788
  const wasPlaying = playingRef.current;
745
789
  cancelAnimation(progress);
746
790
  playingRef.current = false;
747
- const clamped = Math.max(0, Math.min(t, totalDuration));
791
+ const clamped = clampSeekMs(t, seekCeiling);
792
+ runClock.seek(clamped);
748
793
  const withinIteration = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
749
794
  progress.value = withinIteration;
750
795
  if (wasPlaying) startFrom(withinIteration);
796
+ else runClock.stop();
797
+ },
798
+ getCurrentProgress: () => timeToProgress(runClock.now(), tracks.duration, tracks.iterations),
799
+ setCurrentProgress: (p) => {
800
+ api.setCurrentTime(progressToTimeMs(p, tracks.duration, tracks.iterations));
751
801
  }
752
802
  };
753
803
  useImperativeHandle(apiRef, () => api, [compiled]);
754
- const trigger = compiled.doc ? (_a = getAnimatorConfig2(compiled.doc)) == null ? void 0 : _a.trigger : void 0;
755
- const startOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
756
- const outAction = (_c = outActionProp != null ? outActionProp : trigger == null ? void 0 : trigger.outAction) != null ? _c : "pause";
804
+ const trigger = resolveTrigger(compiled.doc ? (_a = getAnimatorConfig2(compiled.doc)) == null ? void 0 : _a.trigger : void 0);
805
+ const effectiveStartOn = trigger.startOn;
806
+ const effectiveOutAction = trigger.outAction;
807
+ const { mode: compMode, warnings: modeWarnings } = resolveControlMode({ progress: progressProp, time, play, pause, autoplay });
808
+ useEffect(() => {
809
+ const diag = makeDiag();
810
+ for (const w of modeWarnings) diag.warn(PxDiagnosticKind2.usage, w);
811
+ }, [modeWarnings.join("|")]);
757
812
  useEffect(() => {
758
- if (progressProp !== void 0 || time !== void 0) {
759
- const seekMs = time !== void 0 ? time : (progressProp != null ? progressProp : 0) * totalDuration;
813
+ if (compMode === PxControlMode.fixedTime) {
814
+ const seekMs = time !== void 0 ? time : progressToTimeMs(progressProp != null ? progressProp : 0, tracks.duration, tracks.iterations);
760
815
  api.setCurrentTime(seekMs);
761
816
  return;
762
817
  }
763
- if (play !== void 0 || pause !== void 0) {
818
+ if (compMode === PxControlMode.play) {
764
819
  if (play && !pause) api.play();
765
820
  else if (pause) api.pause();
766
- else if (play === false) api.finish();
821
+ else if (play === false) api.pause();
767
822
  else api.play();
768
823
  return;
769
824
  }
770
- if (autoplay && startOn === "load") {
825
+ if (compMode === PxControlMode.autoplay && effectiveStartOn === "load") {
771
826
  api.play();
772
827
  }
773
- }, [compiled, autoplay, play, pause, progressProp, time]);
828
+ }, [compiled, compMode, autoplay, play, pause, progressProp, time]);
774
829
  const scrollRef = useRef(null);
775
830
  const inViewRef = useRef(false);
776
831
  useEffect(() => {
777
- var _a2;
778
- if (!autoplay || startOn !== "scrollIntoView") return;
779
- const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
832
+ if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
833
+ const threshold = trigger.scrollIntoViewThreshold;
780
834
  inViewRef.current = false;
781
835
  const check = () => {
782
836
  const node = scrollRef.current;
@@ -792,21 +846,23 @@ function PixodeskSvgAnimator({
792
846
  if (isIn) {
793
847
  if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
794
848
  api.play();
795
- } else if (outAction === "reset") api.cancel();
796
- else if (outAction === "reverse") {
849
+ } else if (effectiveOutAction === "reset") api.cancel();
850
+ else if (effectiveOutAction === "reverse") {
797
851
  api.setPlaybackRate(-Math.abs(rateRef.current || 1));
798
852
  api.play();
799
- } else if (outAction !== "continue") api.pause();
853
+ } else if (effectiveOutAction !== "continue") api.pause();
800
854
  });
801
855
  };
802
856
  check();
803
857
  const id = setInterval(check, 200);
804
858
  return () => clearInterval(id);
805
- }, [compiled, autoplay, startOn, outAction]);
859
+ }, [compiled, autoplay, effectiveStartOn, effectiveOutAction]);
806
860
  useEffect(() => {
807
861
  return () => {
808
862
  cancelAnimation(progress);
809
863
  playingRef.current = false;
864
+ onRemove == null ? void 0 : onRemove();
865
+ onStop == null ? void 0 : onStop();
810
866
  };
811
867
  }, [compiled]);
812
868
  const trackById = useMemo(() => {
@@ -862,32 +918,33 @@ function PixodeskSvgAnimator({
862
918
  } catch (e) {
863
919
  const error = e instanceof Error ? e : new Error(String(e));
864
920
  renderErrorRef.current = error;
865
- console.warn("[PixodeskSvgAnimator] could not render the document:", error.message);
921
+ makeDiag().warn(PxDiagnosticKind2.internal, "could not render the document: " + error.message);
866
922
  return null;
867
923
  }
868
924
  }, [compiled, trackById]);
869
925
  useEffect(() => {
870
- for (const w of warningsRef.current) console.warn("[PixodeskSvgAnimator]", w);
926
+ const diag = makeDiag();
927
+ for (const w of warningsRef.current) diag.warn(PxDiagnosticKind2.platform, w);
871
928
  }, [root]);
872
- const failure = (_d = compiled.error) != null ? _d : renderErrorRef.current;
929
+ const failure = (_b = compiled.error) != null ? _b : renderErrorRef.current;
873
930
  useEffect(() => {
874
931
  if (failure) onError == null ? void 0 : onError(failure);
875
932
  }, [failure]);
876
933
  if (failure) return fallback ? fallback(failure) : null;
877
934
  let content = root;
878
- if (autoplay && startOn === "scrollIntoView" && root) {
935
+ if (autoplay && effectiveStartOn === "scrollIntoView" && root) {
879
936
  content = /* @__PURE__ */ jsx(View, { ref: scrollRef, collapsable: false, children: root });
880
- } else if (autoplay && startOn === "click" && root) {
937
+ } else if (autoplay && effectiveStartOn === "click" && root) {
881
938
  content = /* @__PURE__ */ jsx(
882
939
  Pressable,
883
940
  {
884
941
  onPress: () => {
885
942
  if (playingRef.current) {
886
- if (outAction === "reset") api.cancel();
887
- else if (outAction === "reverse") {
943
+ if (effectiveOutAction === "reset") api.cancel();
944
+ else if (effectiveOutAction === "reverse") {
888
945
  api.setPlaybackRate(-Math.abs(rateRef.current || 1));
889
946
  api.play();
890
- } else if (outAction !== "continue") api.pause();
947
+ } else if (effectiveOutAction !== "continue") api.pause();
891
948
  } else {
892
949
  if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
893
950
  api.play();
@@ -897,7 +954,7 @@ function PixodeskSvgAnimator({
897
954
  }
898
955
  );
899
956
  }
900
- return /* @__PURE__ */ jsx(PxRnErrorBoundary, { onError, fallback, children: content });
957
+ return /* @__PURE__ */ jsx(PxRnErrorBoundary, { onError, fallback, diag: makeDiag(), children: content });
901
958
  }
902
959
  var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
903
960
  export {