@pixodesk/svg-animator-rn 1.0.35 → 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,72 +1,44 @@
1
- import { PxAnimatedSvgDocument, StartOn, PxAnimatorConfigPatch, 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
- /** Shortcut for `config.timeline.trigger.startOn`. */
33
- startOn?: StartOn;
34
20
  /**
35
- * Per-instance override of the document's `animator` config — the same shape as `animator`
36
- * in SCHEMA.md, deep-merged over what the document says; `null` at a slot deletes it.
37
- * Replaces the former flat `fill` / `direction` / `resetOnFinish` / `outAction` props, so
38
- * every surface takes one vocabulary. Also accepts a JSON string.
21
+ * Per-instance override of the document's `timeline` — see `PxPlaybackOverrideProps`.
39
22
  *
40
- * `timeline.engine` is accepted but ignored here: React Native always materialises the
41
- * WAAPI-style flattening, because react-native-svg has no `<use>` shadow-tree propagation.
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.
42
31
  */
43
- config?: PxAnimatorConfigPatch | string;
44
- /** Start from the player's defaults instead of the document's playback settings. */
45
- resetDocDefaults?: boolean;
46
- /** When true, honours the document trigger (`startOn: 'load'` plays on mount). */
47
- autoplay?: boolean;
48
- /** Starts playback unconditionally. */
49
- play?: boolean;
50
- /** Pauses current playback. */
51
- pause?: boolean;
52
- /** Ref populated with the imperative playback API. */
53
32
  apiRef?: React.RefObject<RnAnimatorApi | null>;
54
- /** Seek to a fraction (0–1) of the whole timeline (duration × iterations). */
55
- progress?: number;
56
- /** Seek to a specific time in milliseconds. */
57
- time?: number;
58
- onPlay?: () => void;
59
- onStop?: () => void;
60
- onPause?: () => void;
61
- onCancel?: () => void;
62
- onFinish?: () => void;
63
33
  /**
64
34
  * Called when a document cannot be compiled or rendered. The component
65
35
  * renders {@link fallback} instead of throwing, so a single broken
66
36
  * animation never takes down the screen around it.
67
37
  *
68
38
  * Only JavaScript failures reach this — a crash inside react-native-svg's
69
- * 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.
70
42
  */
71
43
  onError?: (error: Error, componentStack?: string) => void;
72
44
  /** Rendered in place of the animation after a failure. Default: nothing. */
@@ -75,14 +47,15 @@ interface PixodeskSvgAnimatorProps {
75
47
  /**
76
48
  * React Native component for rendering and controlling Pixodesk SVG animations.
77
49
  *
78
- * The document is materialised once through the shared core pipeline (effects,
50
+ * The document is materialized once through the shared core pipeline (effects,
79
51
  * loops, motion-path sampling, animated-`<use>` inlining — identical to the
80
- * 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
81
54
  * played back natively: a single reanimated progress value driven by
82
55
  * `withTiming`/`withRepeat` on the UI thread, with per-element worklets
83
56
  * indexing the precompiled tracks. No JS-thread frame loop.
84
57
  */
85
- declare function PixodeskSvgAnimator({ doc, config, resetDocDefaults, duration, delay, iterations, startOn, 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;
86
59
 
87
60
  interface RenderRnNodeOptions {
88
61
  /** Collects non-fatal issues (unsupported tags, dropped attrs). */
@@ -102,13 +75,13 @@ interface RenderRnNodeOptions {
102
75
  decorate?: (node: PxNode, Component: ComponentType<any>, props: Record<string, any>, children: ReactNode, key: string | number | undefined) => ReactElement | undefined;
103
76
  }
104
77
  /**
105
- * Converts core-normalised wire props into react-native-svg props: RN prop
106
- * 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
107
80
  * coercion where possible.
108
81
  */
109
82
  declare function toRnProps(props: Record<string, any>, warnings?: Array<string>, tag?: string): Record<string, any>;
110
83
  /**
111
- * Renders a (materialised) PxNode tree to react-native-svg elements.
84
+ * Renders a (materialized) PxNode tree to react-native-svg elements.
112
85
  * Mirrors the web `renderNode` contract: unsupported/dangerous tags are
113
86
  * skipped with a warning, never a crash.
114
87
  */
@@ -119,6 +92,8 @@ interface PxRnErrorBoundaryProps {
119
92
  /** Rendered instead of the children once something has thrown. */
120
93
  fallback?: (error: Error) => ReactNode;
121
94
  onError?: (error: Error, info?: string) => void;
95
+ /** Where to report the failure (API review §5). Defaults to the console. */
96
+ diag?: PxDiagnostics;
122
97
  }
123
98
  interface State {
124
99
  error: Error | null;
@@ -173,7 +148,7 @@ declare function openClosedTextPathTargets(doc: PxNode, warnings?: Array<string>
173
148
  * The compiler densely samples every animated property through core's
174
149
  * `calcAnimationValues` — the exact function the web frames engine renders
175
150
  * with — so RN playback is value-identical to the web player. Easing, loops,
176
- * transform composition, colour interpolation and path morphing are all baked
151
+ * transform composition, color interpolation and path morphing are all baked
177
152
  * into the samples at compile time; the UI-thread worklet only indexes arrays.
178
153
  */
179
154
  interface PxElementTracks {
@@ -218,7 +193,7 @@ interface CompileTracksOptions {
218
193
  native?: boolean;
219
194
  }
220
195
  /**
221
- * Compiles a MATERIALISED document (run `materialiseAllInTree(doc, 'frames')`
196
+ * Compiles a MATERIALIZED document (run `materializeAllInTree(doc, 'frames')`
222
197
  * + `generateNewIds` first) into densely sampled per-element tracks.
223
198
  */
224
199
  declare function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksOptions): PxCompiledTracks;
@@ -235,7 +210,7 @@ declare function compileTracks(doc: PxAnimatedSvgDocument, opts?: CompileTracksO
235
210
  declare function sampleProps(tracks: PxElementTracks, tMs: number, stepMs: number, sampleCount: number, native?: boolean): Record<string, string | number | Array<number>>;
236
211
 
237
212
  /**
238
- * Converts one normalised wire attribute name (camelCase after core's
213
+ * Converts one normalized wire attribute name (camelCase after core's
239
214
  * `getNormalizedProps`, or kebab-case raw) to a react-native-svg prop name.
240
215
  * Returns undefined for props that must be dropped.
241
216
  *
package/dist/index.js CHANGED
@@ -31,7 +31,30 @@ var __objRest = (source, exclude) => {
31
31
  };
32
32
 
33
33
  // src/PixodeskSvgAnimator.tsx
34
- import { reportDocumentDiagnostics, generateNewIds, getAnimatorConfig as getAnimatorConfig2, getDefs, materialiseAllInTree, validateNodeEffects, PxTimelineEngine as PxTimelineEngine2, applyAnimatorConfig, foldAnimatorConfigShortcuts } from "@pixodesk/svg-animator-core";
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,
46
+ generateNewIds,
47
+ getAnimatorConfig as getAnimatorConfig2,
48
+ getDefs,
49
+ materializeAllInTree,
50
+ resolveTrigger,
51
+ validateNodeEffects,
52
+ PxTimelineEngine as PxTimelineEngine2,
53
+ PxControlMode,
54
+ resolveControlMode,
55
+ applyAnimatorConfig,
56
+ foldTimelineOverride
57
+ } from "@pixodesk/svg-animator-core";
35
58
  import { createElement as createElement2, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
36
59
  import { Dimensions, Platform, Pressable, View } from "react-native";
37
60
  import Animated, {
@@ -50,7 +73,7 @@ import Animated, {
50
73
  import {
51
74
  calcAnimationValues,
52
75
  getAnimatorConfig,
53
- getNormalisedBindings,
76
+ getNormalizedBindings,
54
77
  DEFAULT_DURATION_MS,
55
78
  PxTimelineEngine
56
79
  } from "@pixodesk/svg-animator-core";
@@ -173,7 +196,7 @@ function compileTracks(doc, opts) {
173
196
  ((_a2 = n == null ? void 0 : n.children) != null ? _a2 : []).forEach(indexTags);
174
197
  };
175
198
  indexTags(doc);
176
- const bindings = getNormalisedBindings(doc, PxTimelineEngine.js) || [];
199
+ const bindings = getNormalizedBindings(doc, PxTimelineEngine.js) || [];
177
200
  const elements = [];
178
201
  for (const binding of bindings) {
179
202
  const animDef = binding.animate;
@@ -236,9 +259,8 @@ function sampleProps(tracks, tMs, stepMs, sampleCount, native = false) {
236
259
  import {
237
260
  getNormalizedProps,
238
261
  resolveStyle,
239
- sanitiseAttributeValue,
262
+ sanitizeAttributeValue,
240
263
  DISALLOWED_SVG_TAGS_LOWER,
241
- TEXT_ATTR,
242
264
  TEXT_CONTENT_ATTR
243
265
  } from "@pixodesk/svg-animator-core";
244
266
  import { createElement } from "react";
@@ -357,14 +379,14 @@ var RN_SVG_COMPONENTS = {
357
379
 
358
380
  // src/PxRnRender.tsx
359
381
  function toRnProps(props, warnings, tag) {
360
- const normalised = getNormalizedProps(props);
382
+ const normalized = getNormalizedProps(props);
361
383
  const out = {};
362
- for (const key of Object.keys(normalised)) {
363
- const sanitised = sanitiseAttributeValue(key, normalised[key]);
364
- 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;
365
387
  const rnKey = toRnPropName(key);
366
388
  if (!rnKey) continue;
367
- out[rnKey] = toRnPropValue(rnKey, String(sanitised), tag);
389
+ out[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
368
390
  }
369
391
  return out;
370
392
  }
@@ -391,12 +413,12 @@ function renderRnNode(node, opts = {}, key) {
391
413
  for (const [k, v] of Object.entries(resolved)) {
392
414
  const rnKey = toRnPropName(k);
393
415
  if (!rnKey || rnKey in rnProps) continue;
394
- const sanitised = sanitiseAttributeValue(rnKey, v);
395
- if (sanitised === void 0) continue;
396
- 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);
397
419
  }
398
420
  }
399
- const textContent = props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR];
421
+ const textContent = props[TEXT_CONTENT_ATTR];
400
422
  let childElements = void 0;
401
423
  if (Array.isArray(children) && children.length > 0) {
402
424
  childElements = children.map((ch, i) => {
@@ -422,6 +444,7 @@ function renderRnNode(node, opts = {}, key) {
422
444
 
423
445
  // src/PxRnErrorBoundary.tsx
424
446
  import { Component } from "react";
447
+ import { createDiagnostics, PxDiagnosticKind } from "@pixodesk/svg-animator-core";
425
448
  var PxRnErrorBoundary = class extends Component {
426
449
  constructor() {
427
450
  super(...arguments);
@@ -431,9 +454,9 @@ var PxRnErrorBoundary = class extends Component {
431
454
  return { error };
432
455
  }
433
456
  componentDidCatch(error, info) {
434
- var _a, _b, _c, _d;
457
+ var _a, _b, _c, _d, _e;
435
458
  (_c = (_b = this.props).onError) == null ? void 0 : _c.call(_b, error, (_a = info == null ? void 0 : info.componentStack) != null ? _a : void 0);
436
- 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)));
437
460
  }
438
461
  componentDidUpdate(prev) {
439
462
  if (this.state.error && prev.children !== this.props.children) {
@@ -592,18 +615,18 @@ var EMPTY_TRACKS = {
592
615
  sampleCount: 2,
593
616
  elements: []
594
617
  };
595
- function compileDocument(doc, overrides) {
596
- const { config, resetDocDefaults, duration, delay, iterations, startOn } = overrides;
618
+ function compileDocument(doc, overrides, diag) {
619
+ const { timeline, resetTimeline, duration, delay, iterations, startOn } = overrides;
597
620
  const warnings = validateNodeEffects(doc);
598
- for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
621
+ for (const w of warnings) diag.warn(PxDiagnosticKind2.document, "effects shape: " + w);
599
622
  reportDocumentDiagnostics(doc, "[PixodeskSvgAnimator]");
600
- const patch = foldAnimatorConfigShortcuts(config, { duration, delay, iterations, startOn });
601
- if (patch !== void 0 || resetDocDefaults) {
602
- const applied = applyAnimatorConfig(doc, patch != null ? patch : {}, { resetDefaults: !!resetDocDefaults });
603
- for (const w of applied.warnings) console.warn("[PixodeskSvgAnimator] config override:", w);
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);
604
627
  doc = applied.doc;
605
628
  }
606
- let prepared = materialiseAllInTree(doc, PxTimelineEngine2.native);
629
+ let prepared = materializeAllInTree(doc, PxTimelineEngine2.native);
607
630
  if (NATIVE_SVG_VIEWS) {
608
631
  prepared = openClosedTextPathTargets(prepared);
609
632
  }
@@ -613,8 +636,8 @@ function compileDocument(doc, overrides) {
613
636
  }
614
637
  function PixodeskSvgAnimator({
615
638
  doc,
616
- config,
617
- resetDocDefaults,
639
+ timeline,
640
+ resetTimeline,
618
641
  duration,
619
642
  delay,
620
643
  iterations,
@@ -631,25 +654,42 @@ function PixodeskSvgAnimator({
631
654
  onPause,
632
655
  onCancel,
633
656
  onFinish,
657
+ onRemove,
634
658
  onError,
635
- fallback
659
+ fallback,
660
+ onWarn,
661
+ silent
636
662
  }) {
637
- var _a, _b, _c, _d;
638
- const configKey = typeof config === "string" ? config : JSON.stringify(config != null ? config : null);
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]");
639
676
  const compiled = useMemo(() => {
640
677
  try {
641
678
  return compileDocument(
642
679
  doc,
643
- { config, resetDocDefaults, duration, delay, iterations, startOn }
680
+ { timeline, resetTimeline, duration, delay, iterations, startOn },
681
+ makeDiag()
644
682
  );
645
683
  } catch (e) {
646
684
  const error = e instanceof Error ? e : new Error(String(e));
647
- console.warn("[PixodeskSvgAnimator] could not compile the document:", error.message);
685
+ makeDiag().warn(PxDiagnosticKind2.internal, "could not compile the document: " + error.message);
648
686
  return { doc: null, tracks: EMPTY_TRACKS, error };
649
687
  }
650
- }, [doc, configKey, resetDocDefaults, duration, delay, iterations, startOn]);
688
+ }, [doc, timelineKey, resetTimeline, duration, delay, iterations, startOn]);
651
689
  const tracks = compiled.tracks;
652
- 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]);
653
693
  const progress = useSharedValue(0);
654
694
  const playingRef = useRef(false);
655
695
  const rateRef = useRef(1);
@@ -661,6 +701,8 @@ function PixodeskSvgAnimator({
661
701
  };
662
702
  const notifyFinish = () => {
663
703
  playingRef.current = false;
704
+ runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
705
+ runClock.stop();
664
706
  progress.value = restingPosition();
665
707
  onFinish == null ? void 0 : onFinish();
666
708
  onStop == null ? void 0 : onStop();
@@ -697,12 +739,15 @@ function PixodeskSvgAnimator({
697
739
  const api = {
698
740
  isPlaying: () => playingRef.current,
699
741
  play: () => {
742
+ if (Number.isFinite(seekCeiling) && runClock.now() >= seekCeiling) runClock.seek(0);
743
+ runClock.start(rateRef.current);
700
744
  startFrom(progress.value);
701
745
  onPlay == null ? void 0 : onPlay();
702
746
  },
703
747
  pause: () => {
704
748
  cancelAnimation(progress);
705
749
  playingRef.current = false;
750
+ runClock.stop();
706
751
  onPause == null ? void 0 : onPause();
707
752
  onStop == null ? void 0 : onStop();
708
753
  },
@@ -710,62 +755,82 @@ function PixodeskSvgAnimator({
710
755
  cancelAnimation(progress);
711
756
  progress.value = 0;
712
757
  playingRef.current = false;
758
+ runClock.seek(0);
759
+ runClock.stop();
713
760
  onCancel == null ? void 0 : onCancel();
714
761
  onStop == null ? void 0 : onStop();
715
762
  },
716
763
  finish: () => {
717
764
  cancelAnimation(progress);
718
765
  playingRef.current = false;
766
+ runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
767
+ runClock.stop();
719
768
  progress.value = restingPosition();
720
769
  onFinish == null ? void 0 : onFinish();
721
770
  onStop == null ? void 0 : onStop();
722
771
  },
723
772
  setPlaybackRate: (rate) => {
724
- if (!isFinite(rate) || rate === 0) {
725
- console.warn("setPlaybackRate: rate must be finite and non-zero");
773
+ if (!isValidPlaybackRate(rate)) {
774
+ makeDiag().warn(PxDiagnosticKind2.usage, PX_RATE_REJECTED);
726
775
  return;
727
776
  }
728
777
  rateRef.current = rate;
729
- if (playingRef.current) startFrom(progress.value);
778
+ if (playingRef.current) {
779
+ runClock.start(rate);
780
+ startFrom(progress.value);
781
+ }
730
782
  },
731
- 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(),
732
787
  setCurrentTime: (t) => {
733
788
  const wasPlaying = playingRef.current;
734
789
  cancelAnimation(progress);
735
790
  playingRef.current = false;
736
- const clamped = Math.max(0, Math.min(t, totalDuration));
791
+ const clamped = clampSeekMs(t, seekCeiling);
792
+ runClock.seek(clamped);
737
793
  const withinIteration = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
738
794
  progress.value = withinIteration;
739
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));
740
801
  }
741
802
  };
742
803
  useImperativeHandle(apiRef, () => api, [compiled]);
743
- const trigger = compiled.doc ? (_a = getAnimatorConfig2(compiled.doc)) == null ? void 0 : _a.trigger : void 0;
744
- const effectiveStartOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
745
- const effectiveOutAction = (_c = 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("|")]);
746
812
  useEffect(() => {
747
- if (progressProp !== void 0 || time !== void 0) {
748
- 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);
749
815
  api.setCurrentTime(seekMs);
750
816
  return;
751
817
  }
752
- if (play !== void 0 || pause !== void 0) {
818
+ if (compMode === PxControlMode.play) {
753
819
  if (play && !pause) api.play();
754
820
  else if (pause) api.pause();
755
- else if (play === false) api.finish();
821
+ else if (play === false) api.pause();
756
822
  else api.play();
757
823
  return;
758
824
  }
759
- if (autoplay && effectiveStartOn === "load") {
825
+ if (compMode === PxControlMode.autoplay && effectiveStartOn === "load") {
760
826
  api.play();
761
827
  }
762
- }, [compiled, autoplay, play, pause, progressProp, time]);
828
+ }, [compiled, compMode, autoplay, play, pause, progressProp, time]);
763
829
  const scrollRef = useRef(null);
764
830
  const inViewRef = useRef(false);
765
831
  useEffect(() => {
766
- var _a2;
767
832
  if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
768
- const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
833
+ const threshold = trigger.scrollIntoViewThreshold;
769
834
  inViewRef.current = false;
770
835
  const check = () => {
771
836
  const node = scrollRef.current;
@@ -796,6 +861,8 @@ function PixodeskSvgAnimator({
796
861
  return () => {
797
862
  cancelAnimation(progress);
798
863
  playingRef.current = false;
864
+ onRemove == null ? void 0 : onRemove();
865
+ onStop == null ? void 0 : onStop();
799
866
  };
800
867
  }, [compiled]);
801
868
  const trackById = useMemo(() => {
@@ -851,14 +918,15 @@ function PixodeskSvgAnimator({
851
918
  } catch (e) {
852
919
  const error = e instanceof Error ? e : new Error(String(e));
853
920
  renderErrorRef.current = error;
854
- console.warn("[PixodeskSvgAnimator] could not render the document:", error.message);
921
+ makeDiag().warn(PxDiagnosticKind2.internal, "could not render the document: " + error.message);
855
922
  return null;
856
923
  }
857
924
  }, [compiled, trackById]);
858
925
  useEffect(() => {
859
- 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);
860
928
  }, [root]);
861
- const failure = (_d = compiled.error) != null ? _d : renderErrorRef.current;
929
+ const failure = (_b = compiled.error) != null ? _b : renderErrorRef.current;
862
930
  useEffect(() => {
863
931
  if (failure) onError == null ? void 0 : onError(failure);
864
932
  }, [failure]);
@@ -886,7 +954,7 @@ function PixodeskSvgAnimator({
886
954
  }
887
955
  );
888
956
  }
889
- return /* @__PURE__ */ jsx(PxRnErrorBoundary, { onError, fallback, children: content });
957
+ return /* @__PURE__ */ jsx(PxRnErrorBoundary, { onError, fallback, diag: makeDiag(), children: content });
890
958
  }
891
959
  var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
892
960
  export {