@pixodesk/svg-animator-rn 1.0.21 → 1.0.22

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.js CHANGED
@@ -34,13 +34,16 @@ var __objRest = (source, exclude) => {
34
34
  import {
35
35
  generateNewIds,
36
36
  getAnimatorConfig as getAnimatorConfig2,
37
+ getDefs,
37
38
  materialiseAllInTree,
38
39
  validateNodeEffects,
39
40
  PxAnimatorEngine as PxAnimatorEngine2
40
41
  } from "@pixodesk/svg-animator-core";
41
- import { useEffect, useImperativeHandle, useMemo, useRef } from "react";
42
+ import { createElement as createElement2, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
43
+ import { Dimensions, Platform, Pressable, View } from "react-native";
42
44
  import Animated, {
43
45
  cancelAnimation,
46
+ useAnimatedReaction,
44
47
  Easing,
45
48
  runOnJS,
46
49
  useAnimatedProps,
@@ -61,6 +64,74 @@ import {
61
64
 
62
65
  // src/PxRnPropNames.ts
63
66
  import { kebabToCamelCaseWord } from "@pixodesk/svg-animator-core";
67
+
68
+ // src/PxRnMatrix.ts
69
+ var DEG = Math.PI / 180;
70
+ function multiply(m1, m2) {
71
+ const [a1, b1, c1, d1, e1, f1] = m1;
72
+ const [a2, b2, c2, d2, e2, f2] = m2;
73
+ return [
74
+ a1 * a2 + c1 * b2,
75
+ b1 * a2 + d1 * b2,
76
+ a1 * c2 + c1 * d2,
77
+ b1 * c2 + d1 * d2,
78
+ a1 * e2 + c1 * f2 + e1,
79
+ b1 * e2 + d1 * f2 + f1
80
+ ];
81
+ }
82
+ var FN_RE = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
83
+ function numbers(raw) {
84
+ return raw.split(/[\s,]+/).filter((s) => s.length > 0).map(Number).filter((n) => Number.isFinite(n));
85
+ }
86
+ function svgTransformToMatrix(value) {
87
+ var _a;
88
+ let m;
89
+ FN_RE.lastIndex = 0;
90
+ let match;
91
+ while ((match = FN_RE.exec(value)) !== null) {
92
+ const fn = match[1].toLowerCase();
93
+ const n = numbers(match[2]);
94
+ let step;
95
+ switch (fn) {
96
+ case "translate":
97
+ step = [1, 0, 0, 1, n[0] || 0, n[1] || 0];
98
+ break;
99
+ case "scale": {
100
+ const sx = (_a = n[0]) != null ? _a : 1;
101
+ const sy = n.length > 1 ? n[1] : sx;
102
+ step = [sx, 0, 0, sy, 0, 0];
103
+ break;
104
+ }
105
+ case "rotate": {
106
+ const rad = (n[0] || 0) * DEG;
107
+ const cos = Math.cos(rad), sin = Math.sin(rad);
108
+ const rot = [cos, sin, -sin, cos, 0, 0];
109
+ if (n.length >= 3) {
110
+ const cx = n[1], cy = n[2];
111
+ step = multiply(multiply([1, 0, 0, 1, cx, cy], rot), [1, 0, 0, 1, -cx, -cy]);
112
+ } else {
113
+ step = rot;
114
+ }
115
+ break;
116
+ }
117
+ case "skewx":
118
+ step = [1, 0, Math.tan((n[0] || 0) * DEG), 1, 0, 0];
119
+ break;
120
+ case "skewy":
121
+ step = [1, Math.tan((n[0] || 0) * DEG), 0, 1, 0, 0];
122
+ break;
123
+ case "matrix":
124
+ if (n.length >= 6) step = [n[0], n[1], n[2], n[3], n[4], n[5]];
125
+ break;
126
+ default:
127
+ step = void 0;
128
+ }
129
+ if (step) m = m ? multiply(m, step) : step;
130
+ }
131
+ return m;
132
+ }
133
+
134
+ // src/PxRnPropNames.ts
64
135
  var ATTR_NAME_OVERRIDES = {
65
136
  "xlink:href": "href"
66
137
  };
@@ -72,7 +143,11 @@ function toRnPropName(attrName) {
72
143
  return kebabToCamelCaseWord(attrName);
73
144
  }
74
145
  var LENGTH_LIST_PROPS = /* @__PURE__ */ new Set(["strokeDasharray"]);
75
- function toRnPropValue(rnPropName, value) {
146
+ function toRnPropValue(rnPropName, value, tag, native = false) {
147
+ if (native && rnPropName === "transform" && typeof value === "string" && tag !== "svg") {
148
+ const m = svgTransformToMatrix(value);
149
+ if (m) return m;
150
+ }
76
151
  if (LENGTH_LIST_PROPS.has(rnPropName)) {
77
152
  const parts = String(value).trim().replace(/,/g, " ").split(/\s+/).map(Number).filter((n) => Number.isFinite(n));
78
153
  return parts.length % 2 === 1 ? parts.concat(parts) : parts;
@@ -84,7 +159,7 @@ function toRnPropValue(rnPropName, value) {
84
159
 
85
160
  // src/PxRnTracks.ts
86
161
  function compileTracks(doc, opts) {
87
- var _a, _b, _c;
162
+ var _a, _b, _c, _d;
88
163
  const config = getAnimatorConfig(doc) || {};
89
164
  const duration = +(config.duration || DEFAULT_DURATION_MS);
90
165
  const _iterations = config.iterations;
@@ -92,11 +167,19 @@ function compileTracks(doc, opts) {
92
167
  if (typeof _iterations === "number") iterations = _iterations || 1;
93
168
  if (_iterations === "infinite") iterations = Infinity;
94
169
  if (iterations < 1) iterations = 1;
95
- const sampleRate = (_a = opts == null ? void 0 : opts.sampleRate) != null ? _a : 60;
96
- const maxSamples = (_b = opts == null ? void 0 : opts.maxSamples) != null ? _b : 600;
170
+ const native = (_a = opts == null ? void 0 : opts.native) != null ? _a : false;
171
+ const sampleRate = (_b = opts == null ? void 0 : opts.sampleRate) != null ? _b : 60;
172
+ const maxSamples = (_c = opts == null ? void 0 : opts.maxSamples) != null ? _c : 600;
97
173
  let sampleCount = Math.max(2, Math.round(duration / 1e3 * sampleRate) + 1);
98
174
  if (sampleCount > maxSamples) sampleCount = maxSamples;
99
175
  const stepMs = duration / (sampleCount - 1);
176
+ const tagById = /* @__PURE__ */ new Map();
177
+ const indexTags = (n) => {
178
+ var _a2;
179
+ if (n && typeof n.id === "string") tagById.set(n.id, String(n.type));
180
+ ((_a2 = n == null ? void 0 : n.children) != null ? _a2 : []).forEach(indexTags);
181
+ };
182
+ indexTags(doc);
100
183
  const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames) || [];
101
184
  const elements = [];
102
185
  for (const binding of bindings) {
@@ -112,9 +195,9 @@ function compileTracks(doc, opts) {
112
195
  let arr = props[rnProp];
113
196
  if (!arr) {
114
197
  arr = props[rnProp] = new Array(sampleCount);
115
- for (let j = 0; j < i; j++) arr[j] = toRnPropValue(rnProp, value);
198
+ for (let j = 0; j < i; j++) arr[j] = toRnPropValue(rnProp, value, tagById.get(binding.id), native);
116
199
  }
117
- arr[i] = toRnPropValue(rnProp, value);
200
+ arr[i] = toRnPropValue(rnProp, value, tagById.get(binding.id), native);
118
201
  }
119
202
  }
120
203
  for (const arr of Object.values(props)) {
@@ -131,20 +214,27 @@ function compileTracks(doc, opts) {
131
214
  iterations,
132
215
  direction: config.direction || "normal",
133
216
  delay: config.delay || 0,
134
- fill: (_c = config.fill) != null ? _c : "forwards",
217
+ fill: (_d = config.fill) != null ? _d : "forwards",
218
+ resetOnFinish: !!config.resetOnFinish,
135
219
  stepMs,
136
220
  sampleCount,
137
221
  elements
138
222
  };
139
223
  }
140
- function sampleProps(tracks, tMs, stepMs, sampleCount) {
224
+ function sampleProps(tracks, tMs, stepMs, sampleCount, native = false) {
141
225
  "worklet";
226
+ const out = {};
227
+ if (!tracks || !tracks.props || !(stepMs > 0)) return out;
142
228
  let idx = Math.round(tMs / stepMs);
143
229
  if (idx < 0) idx = 0;
144
230
  if (idx >= sampleCount) idx = sampleCount - 1;
145
- const out = {};
146
231
  for (const key in tracks.props) {
147
- out[key] = tracks.props[key][idx];
232
+ const values = tracks.props[key];
233
+ if (!values) continue;
234
+ const value = values[idx];
235
+ if (value === void 0) continue;
236
+ const name = native && key === "transform" ? "matrix" : key;
237
+ out[name] = value;
148
238
  }
149
239
  return out;
150
240
  }
@@ -152,6 +242,7 @@ function sampleProps(tracks, tMs, stepMs, sampleCount) {
152
242
  // src/PxRnRender.tsx
153
243
  import {
154
244
  getNormalizedProps,
245
+ resolveStyle,
155
246
  sanitiseAttributeValue,
156
247
  DISALLOWED_SVG_TAGS_LOWER,
157
248
  TEXT_ATTR,
@@ -165,6 +256,32 @@ import {
165
256
  ClipPath,
166
257
  Defs,
167
258
  Ellipse,
259
+ FeBlend,
260
+ FeColorMatrix,
261
+ FeComponentTransfer,
262
+ FeComposite,
263
+ FeConvolveMatrix,
264
+ FeDiffuseLighting,
265
+ FeDisplacementMap,
266
+ FeDistantLight,
267
+ FeDropShadow,
268
+ FeFlood,
269
+ FeFuncA,
270
+ FeFuncB,
271
+ FeFuncG,
272
+ FeFuncR,
273
+ FeGaussianBlur,
274
+ FeImage,
275
+ FeMerge,
276
+ FeMergeNode,
277
+ FeMorphology,
278
+ FeOffset,
279
+ FePointLight,
280
+ FeSpecularLighting,
281
+ FeSpotLight,
282
+ FeTile,
283
+ FeTurbulence,
284
+ Filter,
168
285
  G,
169
286
  Image,
170
287
  Line,
@@ -186,8 +303,13 @@ import {
186
303
  Use
187
304
  } from "react-native-svg";
188
305
  var RN_SVG_COMPONENTS = {
306
+ // Root & containers
189
307
  svg: Svg,
190
308
  g: G,
309
+ defs: Defs,
310
+ symbol: SvgSymbol,
311
+ use: Use,
312
+ // Shapes
191
313
  rect: Rect,
192
314
  circle: Circle,
193
315
  ellipse: Ellipse,
@@ -195,24 +317,53 @@ var RN_SVG_COMPONENTS = {
195
317
  path: Path,
196
318
  polygon: Polygon,
197
319
  polyline: Polyline,
320
+ image: Image,
321
+ // Text
198
322
  text: SvgText,
199
323
  tspan: TSpan,
200
324
  textPath: TextPath,
201
- defs: Defs,
325
+ // Paint servers & clipping
202
326
  linearGradient: LinearGradient,
203
327
  radialGradient: RadialGradient,
204
328
  stop: Stop,
205
- use: Use,
206
- symbol: SvgSymbol,
329
+ pattern: Pattern,
207
330
  mask: Mask,
208
331
  clipPath: ClipPath,
209
- pattern: Pattern,
210
332
  marker: Marker,
211
- image: Image
333
+ // Filters — react-native-svg implements the full primitive set.
334
+ // NOTE: filter rendering requires the New Architecture (Fabric) and is
335
+ // newer than the rest of react-native-svg; treat visual parity with the
336
+ // web player as unverified until checked on a device.
337
+ filter: Filter,
338
+ feBlend: FeBlend,
339
+ feColorMatrix: FeColorMatrix,
340
+ feComponentTransfer: FeComponentTransfer,
341
+ feComposite: FeComposite,
342
+ feConvolveMatrix: FeConvolveMatrix,
343
+ feDiffuseLighting: FeDiffuseLighting,
344
+ feDisplacementMap: FeDisplacementMap,
345
+ feDistantLight: FeDistantLight,
346
+ feDropShadow: FeDropShadow,
347
+ feFlood: FeFlood,
348
+ feFuncA: FeFuncA,
349
+ feFuncB: FeFuncB,
350
+ feFuncG: FeFuncG,
351
+ feFuncR: FeFuncR,
352
+ feGaussianBlur: FeGaussianBlur,
353
+ feImage: FeImage,
354
+ feMerge: FeMerge,
355
+ feMergeNode: FeMergeNode,
356
+ feMorphology: FeMorphology,
357
+ feOffset: FeOffset,
358
+ fePointLight: FePointLight,
359
+ feSpecularLighting: FeSpecularLighting,
360
+ feSpotLight: FeSpotLight,
361
+ feTile: FeTile,
362
+ feTurbulence: FeTurbulence
212
363
  };
213
364
 
214
365
  // src/PxRnRender.tsx
215
- function toRnProps(props, warnings) {
366
+ function toRnProps(props, warnings, tag) {
216
367
  const normalised = getNormalizedProps(props);
217
368
  const out = {};
218
369
  for (const key of Object.keys(normalised)) {
@@ -220,7 +371,7 @@ function toRnProps(props, warnings) {
220
371
  if (sanitised === void 0) continue;
221
372
  const rnKey = toRnPropName(key);
222
373
  if (!rnKey) continue;
223
- out[rnKey] = toRnPropValue(rnKey, String(sanitised));
374
+ out[rnKey] = toRnPropValue(rnKey, String(sanitised), tag);
224
375
  }
225
376
  return out;
226
377
  }
@@ -229,42 +380,164 @@ function renderRnNode(node, opts = {}, key) {
229
380
  if (!node) return null;
230
381
  const _a = node, { type, children, style, animate, meta, effects } = _a, props = __objRest(_a, ["type", "children", "style", "animate", "meta", "effects"]);
231
382
  const tag = String(type || "g");
383
+ const feFuncType = props.funcType;
384
+ if (feFuncType !== void 0) delete props.funcType;
232
385
  if (DISALLOWED_SVG_TAGS_LOWER.has(tag.toLowerCase())) {
233
386
  (_b = opts.warnings) == null ? void 0 : _b.push("tag blocked (dangerous): " + tag);
234
387
  return null;
235
388
  }
236
- const Component = RN_SVG_COMPONENTS[tag];
237
- if (!Component) {
389
+ const Component2 = RN_SVG_COMPONENTS[tag];
390
+ if (!Component2) {
238
391
  (_c = opts.warnings) == null ? void 0 : _c.push("tag not supported in react-native-svg mapping: " + tag);
239
392
  return null;
240
393
  }
241
- const rnProps = toRnProps(props, opts.warnings);
242
- if (key !== void 0) rnProps.key = key;
394
+ const rnProps = toRnProps(props, opts.warnings, tag);
395
+ if (feFuncType !== void 0) rnProps.type = feFuncType;
396
+ const resolved = resolveStyle(style, opts.defs);
397
+ if (resolved) {
398
+ for (const [k, v] of Object.entries(resolved)) {
399
+ const rnKey = toRnPropName(k);
400
+ 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);
404
+ }
405
+ }
243
406
  const textContent = props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR];
244
407
  let childElements = void 0;
245
408
  if (Array.isArray(children) && children.length > 0) {
246
- childElements = children.map((ch, i) => renderRnNode(ch, opts, i)).filter(Boolean);
409
+ childElements = children.map((ch, i) => {
410
+ var _a2;
411
+ try {
412
+ return renderRnNode(ch, opts, i);
413
+ } catch (e) {
414
+ (_a2 = opts.warnings) == null ? void 0 : _a2.push("child failed to render: " + (e instanceof Error ? e.message : String(e)));
415
+ return null;
416
+ }
417
+ }).filter(Boolean);
247
418
  } else if (textContent !== void 0) {
248
419
  childElements = String(textContent);
249
420
  }
250
- const decorated = (_d = opts.decorate) == null ? void 0 : _d.call(opts, node, Component, rnProps, childElements);
421
+ const decorated = (_d = opts.decorate) == null ? void 0 : _d.call(opts, node, Component2, rnProps, childElements, key);
251
422
  if (decorated !== void 0) return decorated;
252
- return createElement(Component, rnProps, childElements);
423
+ return createElement(
424
+ Component2,
425
+ key !== void 0 ? __spreadProps(__spreadValues({}, rnProps), { key }) : rnProps,
426
+ childElements
427
+ );
428
+ }
429
+
430
+ // src/PxRnErrorBoundary.tsx
431
+ import { Component } from "react";
432
+ var PxRnErrorBoundary = class extends Component {
433
+ constructor() {
434
+ super(...arguments);
435
+ this.state = { error: null };
436
+ }
437
+ static getDerivedStateFromError(error) {
438
+ return { error };
439
+ }
440
+ componentDidCatch(error, info) {
441
+ var _a, _b, _c, _d;
442
+ (_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);
444
+ }
445
+ componentDidUpdate(prev) {
446
+ if (this.state.error && prev.children !== this.props.children) {
447
+ this.setState({ error: null });
448
+ }
449
+ }
450
+ render() {
451
+ const { error } = this.state;
452
+ if (error) return this.props.fallback ? this.props.fallback(error) : null;
453
+ return this.props.children;
454
+ }
455
+ };
456
+
457
+ // src/PxRnSafety.ts
458
+ import { deepClone, generateUniqueId } from "@pixodesk/svg-animator-core";
459
+ function isClosedPath(d) {
460
+ return typeof d === "string" && /[zZ]/.test(d);
461
+ }
462
+ function openPath(d) {
463
+ return d.replace(/[zZ]/g, "").trimEnd();
464
+ }
465
+ function localRef(value) {
466
+ if (typeof value !== "string") return void 0;
467
+ const trimmed = value.trim();
468
+ return trimmed.startsWith("#") ? trimmed.slice(1) : void 0;
469
+ }
470
+ function forEachNode(node, visit, parent) {
471
+ var _a;
472
+ if (!node) return;
473
+ visit(node, parent);
474
+ for (const child of (_a = node.children) != null ? _a : []) forEachNode(child, visit, node);
475
+ }
476
+ function openClosedTextPathTargets(doc, warnings) {
477
+ var _a;
478
+ let hasTextPath = false;
479
+ forEachNode(doc, (n) => {
480
+ if (String(n.type) === "textPath") hasTextPath = true;
481
+ });
482
+ if (!hasTextPath) return doc;
483
+ const result = deepClone(doc);
484
+ const byId = /* @__PURE__ */ new Map();
485
+ forEachNode(result, (n) => {
486
+ const id = n.id;
487
+ if (typeof id === "string") byId.set(id, n);
488
+ });
489
+ let defs = ((_a = result.children) != null ? _a : []).find((c) => String(c.type) === "defs");
490
+ const ensureDefs = () => {
491
+ var _a2;
492
+ if (!defs) {
493
+ defs = { type: "defs", children: [] };
494
+ ((_a2 = result.children) != null ? _a2 : result.children = []).unshift(defs);
495
+ }
496
+ return defs;
497
+ };
498
+ const openCopies = /* @__PURE__ */ new Map();
499
+ forEachNode(result, (node) => {
500
+ var _a2, _b;
501
+ if (String(node.type) !== "textPath") return;
502
+ const anyNode = node;
503
+ const attr = anyNode.href !== void 0 ? "href" : "xlink:href";
504
+ const targetId = localRef(anyNode[attr]);
505
+ if (!targetId) return;
506
+ const target = byId.get(targetId);
507
+ if (!target || !isClosedPath(target.d)) return;
508
+ let copyId = openCopies.get(targetId);
509
+ if (!copyId) {
510
+ const copy = deepClone(target);
511
+ copyId = "px_open_" + generateUniqueId();
512
+ copy.id = copyId;
513
+ copy.d = openPath(String(copy.d));
514
+ delete copy.animate;
515
+ delete copy.effects;
516
+ ((_b = (_a2 = ensureDefs()).children) != null ? _b : _a2.children = []).push(copy);
517
+ openCopies.set(targetId, copyId);
518
+ warnings == null ? void 0 : warnings.push(
519
+ "textPath follows a closed path (#" + targetId + "); using an open copy to avoid a react-native-svg native crash"
520
+ );
521
+ }
522
+ anyNode[attr] = "#" + copyId;
523
+ });
524
+ return result;
253
525
  }
254
526
 
255
527
  // src/PixodeskSvgAnimator.tsx
256
528
  import { jsx } from "react/jsx-runtime";
529
+ var NATIVE_SVG_VIEWS = Platform.OS !== "web";
257
530
  var animatedComponentCache = /* @__PURE__ */ new Map();
258
- function getAnimatedComponent(Component) {
259
- let cached = animatedComponentCache.get(Component);
531
+ function getAnimatedComponent(Component2) {
532
+ let cached = animatedComponentCache.get(Component2);
260
533
  if (!cached) {
261
- cached = Animated.createAnimatedComponent(Component);
262
- animatedComponentCache.set(Component, cached);
534
+ cached = Animated.createAnimatedComponent(Component2);
535
+ animatedComponentCache.set(Component2, cached);
263
536
  }
264
537
  return cached;
265
538
  }
266
539
  function AnimatedPxElement({
267
- Component,
540
+ Component: Component2,
268
541
  staticProps,
269
542
  children,
270
543
  tracks,
@@ -272,12 +545,83 @@ function AnimatedPxElement({
272
545
  stepMs,
273
546
  sampleCount
274
547
  }) {
275
- const AnimatedComponent = useMemo(() => getAnimatedComponent(Component), [Component]);
548
+ const AnimatedComponent = useMemo(() => getAnimatedComponent(Component2), [Component2]);
276
549
  const animatedProps = useAnimatedProps(() => {
277
- return sampleProps(tracks, progress.value, stepMs, sampleCount);
550
+ return sampleProps(tracks, progress.value, stepMs, sampleCount, NATIVE_SVG_VIEWS);
278
551
  }, [tracks, stepMs, sampleCount]);
279
552
  return /* @__PURE__ */ jsx(AnimatedComponent, __spreadProps(__spreadValues({}, staticProps), { animatedProps, children }));
280
553
  }
554
+ var NON_HOST_TAGS = /* @__PURE__ */ new Set(["stop", "feMergeNode"]);
555
+ var SAMPLED_DEF_TAGS = /* @__PURE__ */ new Set(["linearGradient", "radialGradient", ...NON_HOST_TAGS]);
556
+ function needsJsSampling(node, trackById) {
557
+ var _a;
558
+ const id = node.id;
559
+ if (SAMPLED_DEF_TAGS.has(String(node.type)) && id && trackById.has(id)) return true;
560
+ return ((_a = node.children) != null ? _a : []).some((c) => needsJsSampling(c, trackById));
561
+ }
562
+ function SampledSubtree({
563
+ node,
564
+ trackById,
565
+ progress,
566
+ stepMs,
567
+ sampleCount,
568
+ renderOpts
569
+ }) {
570
+ const [idx, setIdx] = useState(0);
571
+ useAnimatedReaction(
572
+ () => Math.floor(Math.round(progress.value / stepMs) / 2) * 2,
573
+ // half-rate
574
+ (next, prev) => {
575
+ if (next !== prev) runOnJS(setIdx)(next);
576
+ },
577
+ [stepMs]
578
+ );
579
+ const tMs = Math.min(Math.max(idx, 0), sampleCount - 1) * stepMs;
580
+ return renderRnNode(node, __spreadProps(__spreadValues({}, renderOpts), {
581
+ // Sampled values are baked in as PLAIN props — nothing reanimated-driven.
582
+ decorate: (n, Component2, staticProps, children, key) => {
583
+ const id = n.id;
584
+ const tracks = id ? trackById.get(id) : void 0;
585
+ if (!tracks) return void 0;
586
+ const sampled = sampleProps(tracks, tMs, stepMs, sampleCount);
587
+ return createElement2(Component2, __spreadProps(__spreadValues(__spreadValues({}, staticProps), sampled), { key }), children);
588
+ }
589
+ }));
590
+ }
591
+ var EMPTY_TRACKS = {
592
+ duration: 1,
593
+ iterations: 1,
594
+ direction: "normal",
595
+ delay: 0,
596
+ fill: "forwards",
597
+ resetOnFinish: false,
598
+ stepMs: 1,
599
+ sampleCount: 2,
600
+ elements: []
601
+ };
602
+ function compileDocument(doc, overrides) {
603
+ const { duration, delay, iterations, fill, direction, resetOnFinish } = overrides;
604
+ const warnings = validateNodeEffects(doc);
605
+ for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
606
+ let prepared = materialiseAllInTree(doc, PxAnimatorEngine2.webapi);
607
+ if (NATIVE_SVG_VIEWS) {
608
+ prepared = openClosedTextPathTargets(prepared);
609
+ }
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
+ prepared = generateNewIds(prepared);
622
+ const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
623
+ return { doc: prepared, tracks, error: null };
624
+ }
281
625
  function PixodeskSvgAnimator({
282
626
  doc,
283
627
  duration,
@@ -285,6 +629,8 @@ function PixodeskSvgAnimator({
285
629
  iterations,
286
630
  fill,
287
631
  direction,
632
+ resetOnFinish,
633
+ outAction: outActionProp,
288
634
  autoplay,
289
635
  play,
290
636
  pause,
@@ -295,49 +641,54 @@ function PixodeskSvgAnimator({
295
641
  onStop,
296
642
  onPause,
297
643
  onCancel,
298
- onFinish
644
+ onFinish,
645
+ onError,
646
+ fallback
299
647
  }) {
300
- var _a, _b, _c;
648
+ var _a, _b, _c, _d;
301
649
  const compiled = useMemo(() => {
302
- const warnings = validateNodeEffects(doc);
303
- for (const w of warnings) console.warn("[PixodeskSvgAnimator] effects shape warning:", w);
304
- let prepared = materialiseAllInTree(doc, PxAnimatorEngine2.webapi);
305
- const animator = getAnimatorConfig2(prepared) || {};
306
- prepared = __spreadProps(__spreadValues({}, prepared), {
307
- animator: __spreadProps(__spreadValues({}, animator), {
308
- duration: duration !== void 0 ? duration : animator.duration,
309
- delay: delay !== void 0 ? delay : animator.delay,
310
- iterations: iterations !== void 0 ? iterations : animator.iterations,
311
- fill: fill !== void 0 ? fill : animator.fill,
312
- direction: direction !== void 0 ? direction : animator.direction
313
- })
314
- });
315
- prepared = generateNewIds(prepared);
316
- const tracks2 = compileTracks(prepared);
317
- return { doc: prepared, tracks: tracks2 };
318
- }, [doc, duration, delay, iterations, fill, direction]);
650
+ try {
651
+ return compileDocument(
652
+ doc,
653
+ { duration, delay, iterations, fill, direction, resetOnFinish }
654
+ );
655
+ } catch (e) {
656
+ const error = e instanceof Error ? e : new Error(String(e));
657
+ console.warn("[PixodeskSvgAnimator] could not compile the document:", error.message);
658
+ return { doc: null, tracks: EMPTY_TRACKS, error };
659
+ }
660
+ }, [doc, duration, delay, iterations, fill, direction, resetOnFinish]);
319
661
  const tracks = compiled.tracks;
320
662
  const totalDuration = tracks.duration * (tracks.iterations === Infinity ? 1 : tracks.iterations);
321
663
  const progress = useSharedValue(0);
322
664
  const playingRef = useRef(false);
323
665
  const rateRef = useRef(1);
666
+ const reversePlayback = () => rateRef.current < 0;
667
+ const restingPosition = () => {
668
+ if (tracks.resetOnFinish) return 0;
669
+ if (tracks.fill === "none" || tracks.fill === "backwards") return 0;
670
+ return reversePlayback() ? 0 : tracks.duration;
671
+ };
324
672
  const notifyFinish = () => {
325
673
  playingRef.current = false;
326
- if (tracks.fill === "none" || tracks.fill === "backwards") progress.value = 0;
674
+ progress.value = restingPosition();
327
675
  onFinish == null ? void 0 : onFinish();
328
676
  onStop == null ? void 0 : onStop();
329
677
  };
330
678
  const startFrom = (fromMs) => {
331
679
  const dur = tracks.duration;
332
680
  const rate = rateRef.current || 1;
333
- const reversedStart = tracks.direction === "reverse" || tracks.direction === "alternate-reverse";
681
+ const backwards = rate < 0;
682
+ const speed = Math.abs(rate);
683
+ const directionReversed = tracks.direction === "reverse" || tracks.direction === "alternate-reverse";
684
+ const reversedStart = backwards ? !directionReversed : directionReversed;
334
685
  const alternates = tracks.direction === "alternate" || tracks.direction === "alternate-reverse";
335
686
  const from = Math.max(0, Math.min(fromMs, dur));
336
687
  const legTarget = reversedStart ? 0 : dur;
337
- const legRemaining = Math.abs(legTarget - from) / rate;
688
+ const legRemaining = Math.abs(legTarget - from) / speed;
338
689
  const repeats = tracks.iterations === Infinity ? -1 : tracks.iterations;
339
690
  cancelAnimation(progress);
340
- progress.value = reversedStart ? from === 0 ? dur : from : from;
691
+ progress.value = reversedStart ? from <= 0 ? dur : from : from >= dur ? 0 : from;
341
692
  const animation = repeats === 1 ? withTiming(legTarget, { duration: legRemaining, easing: Easing.linear }, (finished) => {
342
693
  "worklet";
343
694
  if (finished) runOnJS(notifyFinish)();
@@ -350,14 +701,13 @@ function PixodeskSvgAnimator({
350
701
  if (finished) runOnJS(notifyFinish)();
351
702
  }
352
703
  );
353
- progress.value = tracks.delay > 0 && from === 0 ? withDelay(tracks.delay / rate, animation) : animation;
704
+ progress.value = tracks.delay > 0 && from === 0 ? withDelay(tracks.delay / speed, animation) : animation;
354
705
  playingRef.current = true;
355
706
  };
356
707
  const api = {
357
708
  isPlaying: () => playingRef.current,
358
709
  play: () => {
359
- const from = playingRef.current ? progress.value : progress.value >= tracks.duration ? 0 : progress.value;
360
- startFrom(from);
710
+ startFrom(progress.value);
361
711
  onPlay == null ? void 0 : onPlay();
362
712
  },
363
713
  pause: () => {
@@ -375,14 +725,14 @@ function PixodeskSvgAnimator({
375
725
  },
376
726
  finish: () => {
377
727
  cancelAnimation(progress);
378
- progress.value = tracks.fill === "none" || tracks.fill === "backwards" ? 0 : tracks.duration;
379
728
  playingRef.current = false;
729
+ progress.value = restingPosition();
380
730
  onFinish == null ? void 0 : onFinish();
381
731
  onStop == null ? void 0 : onStop();
382
732
  },
383
733
  setPlaybackRate: (rate) => {
384
- if (!isFinite(rate) || rate <= 0) {
385
- console.warn("setPlaybackRate: only finite positive rates are supported in the RN player (reverse is on the feature-gap list)");
734
+ if (!isFinite(rate) || rate === 0) {
735
+ console.warn("setPlaybackRate: rate must be finite and non-zero");
386
736
  return;
387
737
  }
388
738
  rateRef.current = rate;
@@ -390,14 +740,19 @@ function PixodeskSvgAnimator({
390
740
  },
391
741
  getCurrentTime: () => progress.value,
392
742
  setCurrentTime: (t) => {
743
+ const wasPlaying = playingRef.current;
393
744
  cancelAnimation(progress);
394
745
  playingRef.current = false;
395
746
  const clamped = Math.max(0, Math.min(t, totalDuration));
396
- progress.value = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
747
+ const withinIteration = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
748
+ progress.value = withinIteration;
749
+ if (wasPlaying) startFrom(withinIteration);
397
750
  }
398
751
  };
399
752
  useImperativeHandle(apiRef, () => api, [compiled]);
400
- const startOn = (_c = (_b = (_a = getAnimatorConfig2(compiled.doc)) == null ? void 0 : _a.trigger) == null ? void 0 : _b.startOn) != null ? _c : "load";
753
+ const trigger = compiled.doc ? (_a = getAnimatorConfig2(compiled.doc)) == null ? void 0 : _a.trigger : void 0;
754
+ const startOn = (_b = trigger == null ? void 0 : trigger.startOn) != null ? _b : "load";
755
+ const outAction = (_c = outActionProp != null ? outActionProp : trigger == null ? void 0 : trigger.outAction) != null ? _c : "pause";
401
756
  useEffect(() => {
402
757
  if (time !== void 0 || timeMs !== void 0) {
403
758
  const seekMs = timeMs !== void 0 ? timeMs : (time != null ? time : 0) * totalDuration;
@@ -415,6 +770,38 @@ function PixodeskSvgAnimator({
415
770
  api.play();
416
771
  }
417
772
  }, [compiled, autoplay, play, pause, time, timeMs]);
773
+ const scrollRef = useRef(null);
774
+ const inViewRef = useRef(false);
775
+ useEffect(() => {
776
+ var _a2;
777
+ if (!autoplay || startOn !== "scrollIntoView") return;
778
+ const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
779
+ inViewRef.current = false;
780
+ const check = () => {
781
+ const node = scrollRef.current;
782
+ if (!node) return;
783
+ node.measureInWindow((_x, y, _w, h) => {
784
+ if (!h) return;
785
+ const screen = Dimensions.get("window").height;
786
+ const visible = Math.max(0, Math.min(y + h, screen) - Math.max(y, 0));
787
+ const ratio = visible / h;
788
+ const isIn = ratio > 0 && ratio >= threshold;
789
+ if (isIn === inViewRef.current) return;
790
+ inViewRef.current = isIn;
791
+ if (isIn) {
792
+ if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
793
+ api.play();
794
+ } else if (outAction === "reset") api.cancel();
795
+ else if (outAction === "reverse") {
796
+ api.setPlaybackRate(-Math.abs(rateRef.current || 1));
797
+ api.play();
798
+ } else if (outAction !== "continue") api.pause();
799
+ });
800
+ };
801
+ check();
802
+ const id = setInterval(check, 200);
803
+ return () => clearInterval(id);
804
+ }, [compiled, autoplay, startOn, outAction]);
418
805
  useEffect(() => {
419
806
  return () => {
420
807
  cancelAnimation(progress);
@@ -427,41 +814,98 @@ function PixodeskSvgAnimator({
427
814
  return map;
428
815
  }, [tracks]);
429
816
  const warningsRef = useRef([]);
817
+ const renderErrorRef = useRef(null);
430
818
  const root = useMemo(() => {
431
819
  warningsRef.current = [];
432
- return renderRnNode(compiled.doc, {
820
+ renderErrorRef.current = null;
821
+ if (!compiled.doc) return null;
822
+ const renderOpts = {
433
823
  warnings: warningsRef.current,
434
- decorate: (node, Component, staticProps, children) => {
435
- const id = node.id;
436
- const elTracks = id ? trackById.get(id) : void 0;
437
- if (!elTracks) return void 0;
438
- return /* @__PURE__ */ jsx(
439
- AnimatedPxElement,
440
- {
441
- Component,
442
- staticProps,
443
- tracks: elTracks,
444
- progress,
445
- stepMs: tracks.stepMs,
446
- sampleCount: tracks.sampleCount,
447
- children
448
- },
449
- staticProps.key
450
- );
451
- }
452
- });
824
+ defs: getDefs(compiled.doc)
825
+ };
826
+ try {
827
+ return renderRnNode(compiled.doc, __spreadProps(__spreadValues({}, renderOpts), {
828
+ decorate: (node, Component2, staticProps, children, key) => {
829
+ if (needsJsSampling(node, trackById)) {
830
+ return /* @__PURE__ */ jsx(
831
+ SampledSubtree,
832
+ {
833
+ node,
834
+ trackById,
835
+ progress,
836
+ stepMs: tracks.stepMs,
837
+ sampleCount: tracks.sampleCount,
838
+ renderOpts
839
+ },
840
+ key
841
+ );
842
+ }
843
+ const id = node.id;
844
+ const elTracks = id ? trackById.get(id) : void 0;
845
+ if (!elTracks) return void 0;
846
+ return /* @__PURE__ */ jsx(
847
+ AnimatedPxElement,
848
+ {
849
+ Component: Component2,
850
+ staticProps,
851
+ tracks: elTracks,
852
+ progress,
853
+ stepMs: tracks.stepMs,
854
+ sampleCount: tracks.sampleCount,
855
+ children
856
+ },
857
+ key
858
+ );
859
+ }
860
+ }));
861
+ } catch (e) {
862
+ const error = e instanceof Error ? e : new Error(String(e));
863
+ renderErrorRef.current = error;
864
+ console.warn("[PixodeskSvgAnimator] could not render the document:", error.message);
865
+ return null;
866
+ }
453
867
  }, [compiled, trackById]);
454
868
  useEffect(() => {
455
869
  for (const w of warningsRef.current) console.warn("[PixodeskSvgAnimator]", w);
456
870
  }, [root]);
457
- return root;
871
+ const failure = (_d = compiled.error) != null ? _d : renderErrorRef.current;
872
+ useEffect(() => {
873
+ if (failure) onError == null ? void 0 : onError(failure);
874
+ }, [failure]);
875
+ if (failure) return fallback ? fallback(failure) : null;
876
+ let content = root;
877
+ if (autoplay && startOn === "scrollIntoView" && root) {
878
+ content = /* @__PURE__ */ jsx(View, { ref: scrollRef, collapsable: false, children: root });
879
+ } else if (autoplay && startOn === "click" && root) {
880
+ content = /* @__PURE__ */ jsx(
881
+ Pressable,
882
+ {
883
+ onPress: () => {
884
+ if (playingRef.current) {
885
+ if (outAction === "reset") api.cancel();
886
+ else if (outAction === "reverse") {
887
+ api.setPlaybackRate(-Math.abs(rateRef.current || 1));
888
+ api.play();
889
+ } else if (outAction !== "continue") api.pause();
890
+ } else {
891
+ if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
892
+ api.play();
893
+ }
894
+ },
895
+ children: root
896
+ }
897
+ );
898
+ }
899
+ return /* @__PURE__ */ jsx(PxRnErrorBoundary, { onError, fallback, children: content });
458
900
  }
459
901
  var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
460
902
  export {
461
903
  PixodeskSvgAnimator,
904
+ PxRnErrorBoundary,
462
905
  RN_SVG_COMPONENTS,
463
906
  compileTracks,
464
907
  PixodeskSvgAnimator_default as default,
908
+ openClosedTextPathTargets,
465
909
  renderRnNode,
466
910
  sampleProps,
467
911
  toRnPropName,