@pixodesk/svg-animator-rn 1.0.34 → 1.0.35

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 CHANGED
@@ -51,7 +51,7 @@ document says so.
51
51
 
52
52
  ```tsx
53
53
  // Play once when a screen opens, then hold the last frame
54
- <PixodeskSvgAnimator doc={doc} autoplay iterations={1} fill="forwards" />
54
+ <PixodeskSvgAnimator doc={doc} autoplay iterations={1} config={{ timeline: { fillMode: 'forwards' } }} />
55
55
 
56
56
  // Loop forever regardless of what the document says
57
57
  <PixodeskSvgAnimator doc={doc} autoplay iterations="infinite" />
@@ -175,13 +175,12 @@ const [time, setTime] = useState(0);
175
175
  | `apiRef` | `RefObject<RnAnimatorApi>` | Ref for imperative control |
176
176
  | `progress` | `number` | show the frame at this position in the whole timeline (duration × iterations): `0` is the first frame, `0.5` the middle, `1` the last |
177
177
  | `time` | `number` | show the frame at that time, in milliseconds from the start |
178
- | `duration` | `number` | Duration override (ms) |
179
- | `delay` | `number` | Delay before start (ms) |
180
- | `iterations` | `number \| 'infinite'` | Loop count |
181
- | `fill` | `FillMode` | Fill behaviour |
182
- | `direction` | `PlaybackDirection` | Playback direction |
183
- | `resetOnFinish` | `boolean` | Snap back to the start after a natural finish |
184
- | `outAction` | `OutAction` | What a second tap does with the `click` trigger (default: the document's, else `pause`) |
178
+ | `config` | `object \| string` | Per-instance override of the document's `animator` block, deep-merged over it — same shape as the file (`{ timeline: { fillMode, direction, trigger: { outAction, finishAction, … } } }`); `null` at a slot deletes that key. A JSON string is accepted too. `timeline.engine` is accepted but ignored here |
179
+ | `resetDocDefaults` | `boolean` | Ignore the document's playback settings and start from the player's defaults, with `config` on top |
180
+ | `duration` | `number` | Shortcut for `config.timeline.duration` (ms) |
181
+ | `delay` | `number` | Shortcut for `config.timeline.delay` (ms) |
182
+ | `iterations` | `number \| 'infinite'` | Shortcut for `config.timeline.iterations` |
183
+ | `startOn` | `StartOn` | Shortcut for `config.timeline.trigger.startOn`. `mouseOver` has no touch equivalent and is ignored |
185
184
  | `onPlay` | `() => void` | Called on play/resume |
186
185
  | `onPause` | `() => void` | Called on pause |
187
186
  | `onFinish` | `() => void` | Called on natural finish |
@@ -217,9 +216,9 @@ renderer never reaches JavaScript and cannot be caught — see
217
216
 
218
217
  | Prop | Why it differs |
219
218
  |---|---|
220
- | `mode` | Not accepted. There is no Web Animations API on React Native; playback is always native-driven. |
221
- | `frameRate` | Not accepted. The screen's own refresh rate is used. The player does not compute values frame by frame: when the document loads it works out the animated values in advance, as a list of snapshots (60 per second of animation), and each screen refresh shows the nearest one. The closest thing to a frame rate is how many snapshots per second are prepared — `compileTracks({ sampleRate })`, only available when you use the lower-level API instead of the component. |
222
- | `startOn` | Not accepted as a prop — the document's trigger is honoured via `autoplay` (`load`, `click`, `scrollIntoView`, `programmatic`). `mouseOver` has no touch equivalent. |
219
+ | `timeline.engine` | Accepted inside `config` but ignored. There is no Web Animations API on React Native; playback is always native-driven. |
220
+ | `timeline.frameRate` | Ignored. The screen's own refresh rate is used. The player does not compute values frame by frame: when the document loads it works out the animated values in advance, as a list of snapshots (60 per second of animation), and each screen refresh shows the nearest one. The closest thing to a frame rate is how many snapshots per second are prepared — `compileTracks({ sampleRate })`, only available when you use the lower-level API instead of the component. |
221
+ | `startOn: 'mouseOver'` | Has no touch equivalent, so it is not honoured. The other four values (`load`, `click`, `scrollIntoView`, `programmatic`) work as they do on the web, from the file or from the `startOn` prop. |
223
222
  | `className` / `style` | Not accepted — you cannot style the component itself. It fills whatever `View` you put it in, so to set its size, give that `View` a `width` and `height`. Styling *inside* the document (`style` on an element in the JSON) is supported. |
224
223
  | `onRemove` | Never called. On the web it tells you the animator was thrown away; here there is nothing to tell — when the component leaves the screen, React removes it and everything it created. To run code at that moment, use a `useEffect` cleanup function in your own component. |
225
224
 
@@ -296,8 +295,7 @@ player sees plain nodes. **All are supported:**
296
295
  | `clone` + `retime` | ✅ | each clone keeps its own time shift, incl. `retime.timeCrop` (a visibility window on the document timeline) |
297
296
  | `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) |
298
297
  | `textPath` | ✅ | incl. animated `startOffset` |
299
- | `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.glyphs` — no font needed |
300
- | `isCombinedShape` | ✅ | |
298
+ | `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.fonts` — no font needed |
301
299
 
302
300
  ### Motion, timing and references
303
301
 
@@ -307,7 +305,7 @@ player sees plain nodes. **All are supported:**
307
305
  | **Text along a path** | ✅ two ways | native `textPath` (incl. animated `startOffset`), or **per-letter motion paths** for smooth results — the example app uses the latter, since animating native `startOffset` is janky in `react-native-svg` |
308
306
  | Per-property `loop` (incl. `alternate` pingpong) | ✅ | expanded before playback |
309
307
  | Easing (cubic-bezier and named refs) | ✅ | baked into the sampled tracks |
310
- | `definitions.animations` / `easings` / `styles` / `glyphs` | ✅ | named refs resolved; `style` presets applied as props |
308
+ | `definitions.animations` / `easings` / `styles` / `fonts` | ✅ | named refs resolved; `style` presets applied as props |
311
309
  | `node.style` (inline or named) | ✅ | resolved to props — React Native has no CSS, so explicit attributes win |
312
310
 
313
311
  ### Playback and triggers
@@ -316,8 +314,8 @@ player sees plain nodes. **All are supported:**
316
314
  |---|---|---|
317
315
  | `duration`, `delay`, `iterations` (incl. `'infinite'`) | ✅ | |
318
316
  | `direction` — all four values | ✅ | |
319
- | `fill` — `forwards` / `backwards` / `both` / `none` | ✅ | |
320
- | `resetOnFinish` | ✅ | |
317
+ | `fillMode` — `forwards` / `backwards` / `both` / `none` | ✅ | |
318
+ | `trigger.finishAction: 'reset'` | ✅ | |
321
319
  | `play` / `pause` / `cancel` / `finish` | ✅ | |
322
320
  | `setCurrentTime` — jump to a time, including **while playing** | ✅ | playback continues from the new point |
323
321
  | `setPlaybackRate` — faster, slower and **reverse** (negative) | ✅ | composes with `direction` |
@@ -325,8 +323,8 @@ player sees plain nodes. **All are supported:**
325
323
  | Trigger `click` | ✅ | wrapped in a `Pressable`; a second tap applies `outAction` |
326
324
  | Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`); honours `scrollIntoViewThreshold` and `outAction` |
327
325
  | Trigger `mouseOver` | ❌ | no touch equivalent — use `click`, or drive `play` yourself |
328
- | `frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
329
- | `mode` (`waapi` / `frames`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
326
+ | `timeline.frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
327
+ | `timeline.engine` (`auto` / `native` / `js`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
330
328
 
331
329
  ### Known limitations
332
330
 
package/dist/index.cjs CHANGED
@@ -199,7 +199,7 @@ function compileTracks(doc, opts) {
199
199
  ((_a2 = n == null ? void 0 : n.children) != null ? _a2 : []).forEach(indexTags);
200
200
  };
201
201
  indexTags(doc);
202
- const bindings = (0, import_svg_animator_core2.getNormalisedBindings)(doc, import_svg_animator_core2.PxAnimatorEngine.frames) || [];
202
+ const bindings = (0, import_svg_animator_core2.getNormalisedBindings)(doc, import_svg_animator_core2.PxTimelineEngine.js) || [];
203
203
  const elements = [];
204
204
  for (const binding of bindings) {
205
205
  const animDef = binding.animate;
@@ -562,37 +562,32 @@ var EMPTY_TRACKS = {
562
562
  elements: []
563
563
  };
564
564
  function compileDocument(doc, overrides) {
565
- const { duration, delay, iterations, fill, direction, resetOnFinish } = overrides;
565
+ const { config, resetDocDefaults, duration, delay, iterations, startOn } = overrides;
566
566
  const warnings = (0, import_svg_animator_core5.validateNodeEffects)(doc);
567
567
  for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
568
- let prepared = (0, import_svg_animator_core5.materialiseAllInTree)(doc, import_svg_animator_core5.PxAnimatorEngine.waapi);
568
+ (0, import_svg_animator_core5.reportDocumentDiagnostics)(doc, "[PixodeskSvgAnimator]");
569
+ const patch = (0, import_svg_animator_core5.foldAnimatorConfigShortcuts)(config, { duration, delay, iterations, startOn });
570
+ if (patch !== void 0 || resetDocDefaults) {
571
+ const applied = (0, import_svg_animator_core5.applyAnimatorConfig)(doc, patch != null ? patch : {}, { resetDefaults: !!resetDocDefaults });
572
+ for (const w of applied.warnings) console.warn("[PixodeskSvgAnimator] config override:", w);
573
+ doc = applied.doc;
574
+ }
575
+ let prepared = (0, import_svg_animator_core5.materialiseAllInTree)(doc, import_svg_animator_core5.PxTimelineEngine.native);
569
576
  if (NATIVE_SVG_VIEWS) {
570
577
  prepared = openClosedTextPathTargets(prepared);
571
578
  }
572
- const animator = (0, import_svg_animator_core5.getAnimatorConfig)(prepared) || {};
573
- prepared = __spreadProps(__spreadValues({}, prepared), {
574
- animator: __spreadProps(__spreadValues({}, animator), {
575
- duration: duration !== void 0 ? duration : animator.duration,
576
- delay: delay !== void 0 ? delay : animator.delay,
577
- iterations: iterations !== void 0 ? iterations : animator.iterations,
578
- fill: fill !== void 0 ? fill : animator.fill,
579
- direction: direction !== void 0 ? direction : animator.direction,
580
- resetOnFinish: resetOnFinish !== void 0 ? resetOnFinish : animator.resetOnFinish
581
- })
582
- });
583
579
  prepared = (0, import_svg_animator_core5.generateNewIds)(prepared);
584
580
  const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
585
581
  return { doc: prepared, tracks, error: null };
586
582
  }
587
583
  function PixodeskSvgAnimator({
588
584
  doc,
585
+ config,
586
+ resetDocDefaults,
589
587
  duration,
590
588
  delay,
591
589
  iterations,
592
- fill,
593
- direction,
594
- resetOnFinish,
595
- outAction: outActionProp,
590
+ startOn,
596
591
  // (`progress` prop aliased — the name is taken by the internal reanimated SharedValue)
597
592
  autoplay,
598
593
  play,
@@ -609,18 +604,19 @@ function PixodeskSvgAnimator({
609
604
  fallback
610
605
  }) {
611
606
  var _a, _b, _c, _d;
607
+ const configKey = typeof config === "string" ? config : JSON.stringify(config != null ? config : null);
612
608
  const compiled = (0, import_react3.useMemo)(() => {
613
609
  try {
614
610
  return compileDocument(
615
611
  doc,
616
- { duration, delay, iterations, fill, direction, resetOnFinish }
612
+ { config, resetDocDefaults, duration, delay, iterations, startOn }
617
613
  );
618
614
  } catch (e) {
619
615
  const error = e instanceof Error ? e : new Error(String(e));
620
616
  console.warn("[PixodeskSvgAnimator] could not compile the document:", error.message);
621
617
  return { doc: null, tracks: EMPTY_TRACKS, error };
622
618
  }
623
- }, [doc, duration, delay, iterations, fill, direction, resetOnFinish]);
619
+ }, [doc, configKey, resetDocDefaults, duration, delay, iterations, startOn]);
624
620
  const tracks = compiled.tracks;
625
621
  const totalDuration = tracks.duration * (tracks.iterations === Infinity ? 1 : tracks.iterations);
626
622
  const progress = (0, import_react_native_reanimated.useSharedValue)(0);
@@ -714,8 +710,8 @@ function PixodeskSvgAnimator({
714
710
  };
715
711
  (0, import_react3.useImperativeHandle)(apiRef, () => api, [compiled]);
716
712
  const trigger = compiled.doc ? (_a = (0, import_svg_animator_core5.getAnimatorConfig)(compiled.doc)) == null ? void 0 : _a.trigger : void 0;
717
- const startOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
718
- const outAction = (_c = outActionProp != null ? outActionProp : trigger == null ? void 0 : trigger.outAction) != null ? _c : "pause";
713
+ const effectiveStartOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
714
+ const effectiveOutAction = (_c = trigger == null ? void 0 : trigger.outAction) != null ? _c : "pause";
719
715
  (0, import_react3.useEffect)(() => {
720
716
  if (progressProp !== void 0 || time !== void 0) {
721
717
  const seekMs = time !== void 0 ? time : (progressProp != null ? progressProp : 0) * totalDuration;
@@ -729,7 +725,7 @@ function PixodeskSvgAnimator({
729
725
  else api.play();
730
726
  return;
731
727
  }
732
- if (autoplay && startOn === "load") {
728
+ if (autoplay && effectiveStartOn === "load") {
733
729
  api.play();
734
730
  }
735
731
  }, [compiled, autoplay, play, pause, progressProp, time]);
@@ -737,7 +733,7 @@ function PixodeskSvgAnimator({
737
733
  const inViewRef = (0, import_react3.useRef)(false);
738
734
  (0, import_react3.useEffect)(() => {
739
735
  var _a2;
740
- if (!autoplay || startOn !== "scrollIntoView") return;
736
+ if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
741
737
  const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
742
738
  inViewRef.current = false;
743
739
  const check = () => {
@@ -754,17 +750,17 @@ function PixodeskSvgAnimator({
754
750
  if (isIn) {
755
751
  if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
756
752
  api.play();
757
- } else if (outAction === "reset") api.cancel();
758
- else if (outAction === "reverse") {
753
+ } else if (effectiveOutAction === "reset") api.cancel();
754
+ else if (effectiveOutAction === "reverse") {
759
755
  api.setPlaybackRate(-Math.abs(rateRef.current || 1));
760
756
  api.play();
761
- } else if (outAction !== "continue") api.pause();
757
+ } else if (effectiveOutAction !== "continue") api.pause();
762
758
  });
763
759
  };
764
760
  check();
765
761
  const id = setInterval(check, 200);
766
762
  return () => clearInterval(id);
767
- }, [compiled, autoplay, startOn, outAction]);
763
+ }, [compiled, autoplay, effectiveStartOn, effectiveOutAction]);
768
764
  (0, import_react3.useEffect)(() => {
769
765
  return () => {
770
766
  (0, import_react_native_reanimated.cancelAnimation)(progress);
@@ -837,19 +833,19 @@ function PixodeskSvgAnimator({
837
833
  }, [failure]);
838
834
  if (failure) return fallback ? fallback(failure) : null;
839
835
  let content = root;
840
- if (autoplay && startOn === "scrollIntoView" && root) {
836
+ if (autoplay && effectiveStartOn === "scrollIntoView" && root) {
841
837
  content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { ref: scrollRef, collapsable: false, children: root });
842
- } else if (autoplay && startOn === "click" && root) {
838
+ } else if (autoplay && effectiveStartOn === "click" && root) {
843
839
  content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
844
840
  import_react_native.Pressable,
845
841
  {
846
842
  onPress: () => {
847
843
  if (playingRef.current) {
848
- if (outAction === "reset") api.cancel();
849
- else if (outAction === "reverse") {
844
+ if (effectiveOutAction === "reset") api.cancel();
845
+ else if (effectiveOutAction === "reverse") {
850
846
  api.setPlaybackRate(-Math.abs(rateRef.current || 1));
851
847
  api.play();
852
- } else if (outAction !== "continue") api.pause();
848
+ } else if (effectiveOutAction !== "continue") api.pause();
853
849
  } else {
854
850
  if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
855
851
  api.play();