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