@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/README.md +31 -21
- package/dist/index.cjs +104 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -64
- package/dist/index.d.ts +39 -64
- package/dist/index.js +121 -53
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/PixodeskSvgAnimator.tsx +153 -136
- package/src/PxRnErrorBoundary.tsx +5 -1
- package/src/PxRnMatrix.test.ts +2 -2
- package/src/PxRnMatrix.ts +1 -1
- package/src/PxRnPropNames.ts +2 -2
- package/src/PxRnRender.tsx +14 -15
- package/src/PxRnTracks.test.ts +12 -12
- package/src/PxRnTracks.ts +4 -4
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}
|
|
54
|
+
<PixodeskSvgAnimator doc={doc} autoplay iterations={1} timeline={{ fillMode: 'forwards' }} />
|
|
55
55
|
|
|
56
56
|
// Loop forever regardless of what the document says
|
|
57
57
|
<PixodeskSvgAnimator doc={doc} autoplay iterations="infinite" />
|
|
@@ -166,6 +166,7 @@ const [time, setTime] = useState(0);
|
|
|
166
166
|
|
|
167
167
|
## Props
|
|
168
168
|
|
|
169
|
+
<!-- px-check props PixodeskSvgAnimatorProps pkg=rn -->
|
|
169
170
|
| Prop | Type | Description |
|
|
170
171
|
|---|---|---|
|
|
171
172
|
| `doc` | `PxAnimatedSvgDocument` | The animation document to render (required) |
|
|
@@ -175,18 +176,21 @@ const [time, setTime] = useState(0);
|
|
|
175
176
|
| `apiRef` | `RefObject<RnAnimatorApi>` | Ref for imperative control |
|
|
176
177
|
| `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
178
|
| `time` | `number` | show the frame at that time, in milliseconds from the start |
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `duration` | `number` | Shortcut for `
|
|
181
|
-
| `delay` | `number` | Shortcut for `
|
|
182
|
-
| `iterations` | `number \| 'infinite'` | Shortcut for `
|
|
183
|
-
| `startOn` | `
|
|
179
|
+
| `timeline` | `object \| string` | per-instance override of the document's `timeline` block, deep-merged over it — same shape as the file; `null` at any slot deletes that key. A JSON string is accepted too. See [Playback overrides](#playback-overrides) |
|
|
180
|
+
| `resetTimeline` | `boolean` | ignore the document's own timeline and start from the player's default timeline, with `timeline` on top |
|
|
181
|
+
| `duration` | `number` | Shortcut for `timeline.duration` (ms) |
|
|
182
|
+
| `delay` | `number` | Shortcut for `timeline.delay` (ms) |
|
|
183
|
+
| `iterations` | `number \| 'infinite'` | Shortcut for `timeline.iterations` |
|
|
184
|
+
| `startOn` | `PxStartOn` | Shortcut for `timeline.trigger.startOn`. `mouseOver` has no touch equivalent and is ignored |
|
|
184
185
|
| `onPlay` | `() => void` | Called on play/resume |
|
|
185
186
|
| `onPause` | `() => void` | Called on pause |
|
|
186
|
-
| `onFinish` | `() => void` | Called
|
|
187
|
+
| `onFinish` | `() => void` | Called when the animation reaches its end — every iteration played, or `finish()` was called |
|
|
187
188
|
| `onCancel` | `() => void` | Called on cancel |
|
|
188
|
-
| `
|
|
189
|
+
| `onRemove` | `() => void` | Called when the animator is thrown away — the component unmounted, or a new `doc` replaced it |
|
|
190
|
+
| `onStop` | `() => void` | Called whenever playback halts (pause / cancel / finish / remove) |
|
|
189
191
|
| `onError` | `(error, componentStack?) => void` | Called when a document cannot be compiled or rendered |
|
|
192
|
+
| `onWarn` | `(diagnostic) => void` | something is off but the animation still plays; without it → `console.warn` |
|
|
193
|
+
| `silent` | `boolean \| PxDiagnosticKind[]` | silences the console fallback — everything, or just the kinds listed |
|
|
190
194
|
| `fallback` | `(error) => ReactElement \| null` | Rendered in place of a failed animation (default: nothing) |
|
|
191
195
|
|
|
192
196
|
With none of `autoplay` / `play` / `pause` / `progress` / `time` set, the component
|
|
@@ -214,11 +218,12 @@ renderer never reaches JavaScript and cannot be caught — see
|
|
|
214
218
|
|
|
215
219
|
### Differences from the React package
|
|
216
220
|
|
|
221
|
+
<!-- px-check off differences from React, prose -->
|
|
217
222
|
| Prop | Why it differs |
|
|
218
223
|
|---|---|
|
|
219
|
-
| `timeline.engine` | Accepted inside `
|
|
224
|
+
| `timeline.engine` | Accepted inside `timeline` but ignored. There is no Web Animations API on React Native; playback is always native-driven. |
|
|
220
225
|
| `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
|
|
226
|
+
| `startOn: 'mouseOver'` | Has no touch equivalent, so it is not honored. The other four values (`load`, `click`, `scrollIntoView`, `programmatic`) work as they do on the web, from the file or from the `startOn` prop. |
|
|
222
227
|
| `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. |
|
|
223
228
|
| `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. |
|
|
224
229
|
|
|
@@ -228,9 +233,9 @@ There is **no JavaScript frame loop** — the JS thread is idle while an
|
|
|
228
233
|
animation runs.
|
|
229
234
|
|
|
230
235
|
1. **Once per document:** the shared core flattens it
|
|
231
|
-
(`
|
|
236
|
+
(`materializeAllInTree` → effects, loops, motion-path sampling, animated
|
|
232
237
|
`<use>` inlining), then a track compiler densely samples every animated
|
|
233
|
-
property with `calcAnimationValues` — the same function the web
|
|
238
|
+
property with `calcAnimationValues` — the same function the web frame-loop engine
|
|
234
239
|
renders with, so values match the web player exactly.
|
|
235
240
|
2. **Per frame:** one reanimated progress value, driven by
|
|
236
241
|
`withTiming`/`withRepeat` on the UI thread, and a tiny worklet per animated
|
|
@@ -243,29 +248,31 @@ converts it into plain values ahead of time instead of fighting the platform.
|
|
|
243
248
|
## Feature support
|
|
244
249
|
|
|
245
250
|
Every row below was verified by running the document through the real
|
|
246
|
-
pipeline (`
|
|
251
|
+
pipeline (`materializeAllInTree` → track compilation) and checking that the
|
|
247
252
|
element maps to a `react-native-svg` component and that its animated
|
|
248
253
|
properties actually change over time.
|
|
249
254
|
|
|
250
255
|
### Elements
|
|
251
256
|
|
|
257
|
+
<!-- px-check off support matrix, prose -->
|
|
252
258
|
| Element | Renders | Notes |
|
|
253
259
|
|---|---|---|
|
|
254
260
|
| `svg`, `g`, `defs` | ✅ | |
|
|
255
261
|
| `rect`, `circle`, `ellipse`, `line`, `path`, `polygon`, `polyline` | ✅ | |
|
|
256
262
|
| `text`, `tspan` | ✅ | content via the `text` attribute |
|
|
257
263
|
| `textPath` | ✅ | see *Text along a path* below |
|
|
258
|
-
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the
|
|
264
|
+
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the sanitizer |
|
|
259
265
|
| `use`, `symbol` | ✅ | animated targets are **inlined into real clones** before render — `<use>` does not propagate animation natively in React Native |
|
|
260
266
|
| `linearGradient`, `radialGradient`, `stop` | ✅ | |
|
|
261
267
|
| `pattern`, `marker` | ✅ | static geometry verified; complex cases unverified on device |
|
|
262
268
|
| `mask`, `clipPath` | ✅ | |
|
|
263
269
|
| `filter` + all 22 `fe*` primitives | ✅ | `feGaussianBlur`, `feDropShadow`, `feColorMatrix`, `feMerge`, `feComponentTransfer` + `feFunc*`, … **The visual result has not yet been checked on a real device** |
|
|
264
|
-
| `foreignObject` | ❌ | blocked by the shared
|
|
265
|
-
| `script` | ❌ | blocked by the shared
|
|
270
|
+
| `foreignObject` | ❌ | blocked by the shared sanitizer (embeds arbitrary host content) |
|
|
271
|
+
| `script` | ❌ | blocked by the shared sanitizer |
|
|
266
272
|
|
|
267
273
|
### Animatable attributes
|
|
268
274
|
|
|
275
|
+
<!-- px-check off support matrix, prose -->
|
|
269
276
|
| Attribute | Animates | Notes |
|
|
270
277
|
|---|---|---|
|
|
271
278
|
| `opacity`, `fill-opacity`, `stroke-opacity` | ✅ | |
|
|
@@ -282,23 +289,25 @@ properties actually change over time.
|
|
|
282
289
|
|
|
283
290
|
### Effects (`node.effects`)
|
|
284
291
|
|
|
285
|
-
All effects are
|
|
292
|
+
All effects are materialized by the shared core before rendering, so the React Native
|
|
286
293
|
player sees plain nodes. **All are supported:**
|
|
287
294
|
|
|
295
|
+
<!-- px-check schema PxEffectsSchema -->
|
|
288
296
|
| Effect | Status | Notes |
|
|
289
297
|
|---|---|---|
|
|
290
298
|
| `transformBy` | ✅ | all parts animatable, including `skew` |
|
|
291
|
-
| `repeater` | ✅ | copies
|
|
299
|
+
| `repeater` | ✅ | copies materialized as real elements; per-copy params animatable |
|
|
292
300
|
| `maskedBy` | ✅ | including an animated mask source |
|
|
293
301
|
| `clipPath` | ✅ | including animated clip geometry |
|
|
294
302
|
| `strokeTrim` | ✅ | incl. `offset` and `subPaths: 'combined'` |
|
|
295
303
|
| `clone` + `retime` | ✅ | each clone keeps its own time shift, incl. `retime.timeCrop` (a visibility window on the document timeline) |
|
|
296
|
-
| `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) |
|
|
304
|
+
| `fillGradient` / `strokeGradient` | ✅ | animated stops **and animated geometry** (`animate.gradientX1`/`Cx`/`R`, …); `gradientTransform` is static (core-wide) | <!-- px names=fillGradient,strokeGradient -->
|
|
297
305
|
| `textPath` | ✅ | incl. animated `startOffset` |
|
|
298
306
|
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.fonts` — no font needed |
|
|
299
307
|
|
|
300
308
|
### Motion, timing and references
|
|
301
309
|
|
|
310
|
+
<!-- px-check off support matrix, prose -->
|
|
302
311
|
| Feature | Status | Notes |
|
|
303
312
|
|---|---|---|
|
|
304
313
|
| **Motion along a path** + `autoOrient` | ✅ | **sampled** by the core into plain transform keyframes — `react-native-svg` has no native path motion |
|
|
@@ -310,6 +319,7 @@ player sees plain nodes. **All are supported:**
|
|
|
310
319
|
|
|
311
320
|
### Playback and triggers
|
|
312
321
|
|
|
322
|
+
<!-- px-check off support matrix, prose -->
|
|
313
323
|
| Feature | Status | Notes |
|
|
314
324
|
|---|---|---|
|
|
315
325
|
| `duration`, `delay`, `iterations` (incl. `'infinite'`) | ✅ | |
|
|
@@ -321,7 +331,7 @@ player sees plain nodes. **All are supported:**
|
|
|
321
331
|
| `setPlaybackRate` — faster, slower and **reverse** (negative) | ✅ | composes with `direction` |
|
|
322
332
|
| Trigger `load` / `programmatic` | ✅ | |
|
|
323
333
|
| Trigger `click` | ✅ | wrapped in a `Pressable`; a second tap applies `outAction` |
|
|
324
|
-
| Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`);
|
|
334
|
+
| Trigger `scrollIntoView` | ✅ | visibility sampled by measuring against the window (React Native has no `IntersectionObserver`); honors `scrollIntoViewThreshold` and `outAction` |
|
|
325
335
|
| Trigger `mouseOver` | ❌ | no touch equivalent — use `click`, or drive `play` yourself |
|
|
326
336
|
| `timeline.frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
|
|
327
337
|
| `timeline.engine` (`auto` / `native` / `js`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
|
package/dist/index.cjs
CHANGED
|
@@ -73,7 +73,7 @@ __export(index_exports, {
|
|
|
73
73
|
module.exports = __toCommonJS(index_exports);
|
|
74
74
|
|
|
75
75
|
// src/PixodeskSvgAnimator.tsx
|
|
76
|
-
var
|
|
76
|
+
var import_svg_animator_core6 = require("@pixodesk/svg-animator-core");
|
|
77
77
|
var import_react3 = require("react");
|
|
78
78
|
var import_react_native = require("react-native");
|
|
79
79
|
var import_react_native_reanimated = __toESM(require("react-native-reanimated"), 1);
|
|
@@ -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.
|
|
202
|
+
const bindings = (0, import_svg_animator_core2.getNormalizedBindings)(doc, import_svg_animator_core2.PxTimelineEngine.js) || [];
|
|
203
203
|
const elements = [];
|
|
204
204
|
for (const binding of bindings) {
|
|
205
205
|
const animDef = binding.animate;
|
|
@@ -326,14 +326,14 @@ var RN_SVG_COMPONENTS = {
|
|
|
326
326
|
|
|
327
327
|
// src/PxRnRender.tsx
|
|
328
328
|
function toRnProps(props, warnings, tag) {
|
|
329
|
-
const
|
|
329
|
+
const normalized = (0, import_svg_animator_core3.getNormalizedProps)(props);
|
|
330
330
|
const out = {};
|
|
331
|
-
for (const key of Object.keys(
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
331
|
+
for (const key of Object.keys(normalized)) {
|
|
332
|
+
const sanitized = (0, import_svg_animator_core3.sanitizeAttributeValue)(key, normalized[key]);
|
|
333
|
+
if (sanitized === void 0) continue;
|
|
334
334
|
const rnKey = toRnPropName(key);
|
|
335
335
|
if (!rnKey) continue;
|
|
336
|
-
out[rnKey] = toRnPropValue(rnKey, String(
|
|
336
|
+
out[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
337
337
|
}
|
|
338
338
|
return out;
|
|
339
339
|
}
|
|
@@ -360,12 +360,12 @@ function renderRnNode(node, opts = {}, key) {
|
|
|
360
360
|
for (const [k, v] of Object.entries(resolved)) {
|
|
361
361
|
const rnKey = toRnPropName(k);
|
|
362
362
|
if (!rnKey || rnKey in rnProps) continue;
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
365
|
-
rnProps[rnKey] = toRnPropValue(rnKey, String(
|
|
363
|
+
const sanitized = (0, import_svg_animator_core3.sanitizeAttributeValue)(rnKey, v);
|
|
364
|
+
if (sanitized === void 0) continue;
|
|
365
|
+
rnProps[rnKey] = toRnPropValue(rnKey, String(sanitized), tag);
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
-
const textContent = props[import_svg_animator_core3.
|
|
368
|
+
const textContent = props[import_svg_animator_core3.TEXT_CONTENT_ATTR];
|
|
369
369
|
let childElements = void 0;
|
|
370
370
|
if (Array.isArray(children) && children.length > 0) {
|
|
371
371
|
childElements = children.map((ch, i) => {
|
|
@@ -391,6 +391,7 @@ function renderRnNode(node, opts = {}, key) {
|
|
|
391
391
|
|
|
392
392
|
// src/PxRnErrorBoundary.tsx
|
|
393
393
|
var import_react2 = require("react");
|
|
394
|
+
var import_svg_animator_core4 = require("@pixodesk/svg-animator-core");
|
|
394
395
|
var PxRnErrorBoundary = class extends import_react2.Component {
|
|
395
396
|
constructor() {
|
|
396
397
|
super(...arguments);
|
|
@@ -400,9 +401,9 @@ var PxRnErrorBoundary = class extends import_react2.Component {
|
|
|
400
401
|
return { error };
|
|
401
402
|
}
|
|
402
403
|
componentDidCatch(error, info) {
|
|
403
|
-
var _a, _b, _c, _d;
|
|
404
|
+
var _a, _b, _c, _d, _e;
|
|
404
405
|
(_c = (_b = this.props).onError) == null ? void 0 : _c.call(_b, error, (_a = info == null ? void 0 : info.componentStack) != null ? _a : void 0);
|
|
405
|
-
|
|
406
|
+
((_d = this.props.diag) != null ? _d : (0, import_svg_animator_core4.createDiagnostics)(void 0, "[PixodeskSvgAnimator]")).warn(import_svg_animator_core4.PxDiagnosticKind.internal, "render failed: " + ((_e = error == null ? void 0 : error.message) != null ? _e : String(error)));
|
|
406
407
|
}
|
|
407
408
|
componentDidUpdate(prev) {
|
|
408
409
|
if (this.state.error && prev.children !== this.props.children) {
|
|
@@ -417,7 +418,7 @@ var PxRnErrorBoundary = class extends import_react2.Component {
|
|
|
417
418
|
};
|
|
418
419
|
|
|
419
420
|
// src/PxRnSafety.ts
|
|
420
|
-
var
|
|
421
|
+
var import_svg_animator_core5 = require("@pixodesk/svg-animator-core");
|
|
421
422
|
function isClosedPath(d) {
|
|
422
423
|
return typeof d === "string" && /[zZ]/.test(d);
|
|
423
424
|
}
|
|
@@ -442,7 +443,7 @@ function openClosedTextPathTargets(doc, warnings) {
|
|
|
442
443
|
if (String(n.type) === "textPath") hasTextPath = true;
|
|
443
444
|
});
|
|
444
445
|
if (!hasTextPath) return doc;
|
|
445
|
-
const result = (0,
|
|
446
|
+
const result = (0, import_svg_animator_core5.deepClone)(doc);
|
|
446
447
|
const byId = /* @__PURE__ */ new Map();
|
|
447
448
|
forEachNode(result, (n) => {
|
|
448
449
|
const id = n.id;
|
|
@@ -469,8 +470,8 @@ function openClosedTextPathTargets(doc, warnings) {
|
|
|
469
470
|
if (!target || !isClosedPath(target.d)) return;
|
|
470
471
|
let copyId = openCopies.get(targetId);
|
|
471
472
|
if (!copyId) {
|
|
472
|
-
const copy = (0,
|
|
473
|
-
copyId = "px_open_" + (0,
|
|
473
|
+
const copy = (0, import_svg_animator_core5.deepClone)(target);
|
|
474
|
+
copyId = "px_open_" + (0, import_svg_animator_core5.generateUniqueId)();
|
|
474
475
|
copy.id = copyId;
|
|
475
476
|
copy.d = openPath(String(copy.d));
|
|
476
477
|
delete copy.animate;
|
|
@@ -561,29 +562,29 @@ var EMPTY_TRACKS = {
|
|
|
561
562
|
sampleCount: 2,
|
|
562
563
|
elements: []
|
|
563
564
|
};
|
|
564
|
-
function compileDocument(doc, overrides) {
|
|
565
|
-
const {
|
|
566
|
-
const warnings = (0,
|
|
567
|
-
for (const w of warnings)
|
|
568
|
-
(0,
|
|
569
|
-
const patch = (0,
|
|
570
|
-
if (patch !== void 0 ||
|
|
571
|
-
const applied = (0,
|
|
572
|
-
for (const w of applied.warnings)
|
|
565
|
+
function compileDocument(doc, overrides, diag) {
|
|
566
|
+
const { timeline, resetTimeline, duration, delay, iterations, startOn } = overrides;
|
|
567
|
+
const warnings = (0, import_svg_animator_core6.validateNodeEffects)(doc);
|
|
568
|
+
for (const w of warnings) diag.warn(import_svg_animator_core6.PxDiagnosticKind.document, "effects shape: " + w);
|
|
569
|
+
(0, import_svg_animator_core6.reportDocumentDiagnostics)(doc, "[PixodeskSvgAnimator]");
|
|
570
|
+
const patch = (0, import_svg_animator_core6.foldTimelineOverride)(timeline, { duration, delay, iterations, startOn });
|
|
571
|
+
if (patch !== void 0 || resetTimeline) {
|
|
572
|
+
const applied = (0, import_svg_animator_core6.applyAnimatorConfig)(doc, patch != null ? patch : {}, { resetDefaults: !!resetTimeline });
|
|
573
|
+
for (const w of applied.warnings) diag.warn(import_svg_animator_core6.PxDiagnosticKind.usage, "timeline override: " + w);
|
|
573
574
|
doc = applied.doc;
|
|
574
575
|
}
|
|
575
|
-
let prepared = (0,
|
|
576
|
+
let prepared = (0, import_svg_animator_core6.materializeAllInTree)(doc, import_svg_animator_core6.PxTimelineEngine.native);
|
|
576
577
|
if (NATIVE_SVG_VIEWS) {
|
|
577
578
|
prepared = openClosedTextPathTargets(prepared);
|
|
578
579
|
}
|
|
579
|
-
prepared = (0,
|
|
580
|
+
prepared = (0, import_svg_animator_core6.generateNewIds)(prepared);
|
|
580
581
|
const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
|
|
581
582
|
return { doc: prepared, tracks, error: null };
|
|
582
583
|
}
|
|
583
584
|
function PixodeskSvgAnimator({
|
|
584
585
|
doc,
|
|
585
|
-
|
|
586
|
-
|
|
586
|
+
timeline,
|
|
587
|
+
resetTimeline,
|
|
587
588
|
duration,
|
|
588
589
|
delay,
|
|
589
590
|
iterations,
|
|
@@ -600,25 +601,42 @@ function PixodeskSvgAnimator({
|
|
|
600
601
|
onPause,
|
|
601
602
|
onCancel,
|
|
602
603
|
onFinish,
|
|
604
|
+
onRemove,
|
|
603
605
|
onError,
|
|
604
|
-
fallback
|
|
606
|
+
fallback,
|
|
607
|
+
onWarn,
|
|
608
|
+
silent
|
|
605
609
|
}) {
|
|
606
|
-
var _a, _b
|
|
607
|
-
const
|
|
610
|
+
var _a, _b;
|
|
611
|
+
const timelineKey = typeof timeline === "string" ? timeline : JSON.stringify(timeline != null ? timeline : null);
|
|
612
|
+
const makeDiag = () => (0, import_svg_animator_core6.createDiagnostics)({
|
|
613
|
+
onWarn,
|
|
614
|
+
// This component's public `onError` is richer — `(error, componentStack?)` — and the
|
|
615
|
+
// error boundary hands it a component stack. Adapt rather than narrow it; the ternary
|
|
616
|
+
// keeps "not given" as undefined, so the console fallback still fires.
|
|
617
|
+
onError: onError ? (d) => {
|
|
618
|
+
var _a2;
|
|
619
|
+
return onError((_a2 = d.error) != null ? _a2 : new Error(d.message));
|
|
620
|
+
} : void 0,
|
|
621
|
+
silent
|
|
622
|
+
}, "[PixodeskSvgAnimator]");
|
|
608
623
|
const compiled = (0, import_react3.useMemo)(() => {
|
|
609
624
|
try {
|
|
610
625
|
return compileDocument(
|
|
611
626
|
doc,
|
|
612
|
-
{
|
|
627
|
+
{ timeline, resetTimeline, duration, delay, iterations, startOn },
|
|
628
|
+
makeDiag()
|
|
613
629
|
);
|
|
614
630
|
} catch (e) {
|
|
615
631
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
616
|
-
|
|
632
|
+
makeDiag().warn(import_svg_animator_core6.PxDiagnosticKind.internal, "could not compile the document: " + error.message);
|
|
617
633
|
return { doc: null, tracks: EMPTY_TRACKS, error };
|
|
618
634
|
}
|
|
619
|
-
}, [doc,
|
|
635
|
+
}, [doc, timelineKey, resetTimeline, duration, delay, iterations, startOn]);
|
|
620
636
|
const tracks = compiled.tracks;
|
|
621
|
-
const totalDuration =
|
|
637
|
+
const totalDuration = (0, import_svg_animator_core6.progressSpanMs)(tracks.duration, tracks.iterations);
|
|
638
|
+
const seekCeiling = (0, import_svg_animator_core6.seekCeilingMs)(tracks.duration, tracks.iterations);
|
|
639
|
+
const runClock = (0, import_react3.useMemo)(() => (0, import_svg_animator_core6.createRunClock)(seekCeiling), [seekCeiling]);
|
|
622
640
|
const progress = (0, import_react_native_reanimated.useSharedValue)(0);
|
|
623
641
|
const playingRef = (0, import_react3.useRef)(false);
|
|
624
642
|
const rateRef = (0, import_react3.useRef)(1);
|
|
@@ -630,6 +648,8 @@ function PixodeskSvgAnimator({
|
|
|
630
648
|
};
|
|
631
649
|
const notifyFinish = () => {
|
|
632
650
|
playingRef.current = false;
|
|
651
|
+
runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
|
|
652
|
+
runClock.stop();
|
|
633
653
|
progress.value = restingPosition();
|
|
634
654
|
onFinish == null ? void 0 : onFinish();
|
|
635
655
|
onStop == null ? void 0 : onStop();
|
|
@@ -666,12 +686,15 @@ function PixodeskSvgAnimator({
|
|
|
666
686
|
const api = {
|
|
667
687
|
isPlaying: () => playingRef.current,
|
|
668
688
|
play: () => {
|
|
689
|
+
if (Number.isFinite(seekCeiling) && runClock.now() >= seekCeiling) runClock.seek(0);
|
|
690
|
+
runClock.start(rateRef.current);
|
|
669
691
|
startFrom(progress.value);
|
|
670
692
|
onPlay == null ? void 0 : onPlay();
|
|
671
693
|
},
|
|
672
694
|
pause: () => {
|
|
673
695
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
674
696
|
playingRef.current = false;
|
|
697
|
+
runClock.stop();
|
|
675
698
|
onPause == null ? void 0 : onPause();
|
|
676
699
|
onStop == null ? void 0 : onStop();
|
|
677
700
|
},
|
|
@@ -679,62 +702,82 @@ function PixodeskSvgAnimator({
|
|
|
679
702
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
680
703
|
progress.value = 0;
|
|
681
704
|
playingRef.current = false;
|
|
705
|
+
runClock.seek(0);
|
|
706
|
+
runClock.stop();
|
|
682
707
|
onCancel == null ? void 0 : onCancel();
|
|
683
708
|
onStop == null ? void 0 : onStop();
|
|
684
709
|
},
|
|
685
710
|
finish: () => {
|
|
686
711
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
687
712
|
playingRef.current = false;
|
|
713
|
+
runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
|
|
714
|
+
runClock.stop();
|
|
688
715
|
progress.value = restingPosition();
|
|
689
716
|
onFinish == null ? void 0 : onFinish();
|
|
690
717
|
onStop == null ? void 0 : onStop();
|
|
691
718
|
},
|
|
692
719
|
setPlaybackRate: (rate) => {
|
|
693
|
-
if (!
|
|
694
|
-
|
|
720
|
+
if (!(0, import_svg_animator_core6.isValidPlaybackRate)(rate)) {
|
|
721
|
+
makeDiag().warn(import_svg_animator_core6.PxDiagnosticKind.usage, import_svg_animator_core6.PX_RATE_REJECTED);
|
|
695
722
|
return;
|
|
696
723
|
}
|
|
697
724
|
rateRef.current = rate;
|
|
698
|
-
if (playingRef.current)
|
|
725
|
+
if (playingRef.current) {
|
|
726
|
+
runClock.start(rate);
|
|
727
|
+
startFrom(progress.value);
|
|
728
|
+
}
|
|
699
729
|
},
|
|
700
|
-
|
|
730
|
+
// Ms from the start of the WHOLE run, like every other engine (review §3). This used
|
|
731
|
+
// to return `progress.value`, which is ms within the current iteration — so a slider
|
|
732
|
+
// built on it jumped back to 0 every time the animation repeated.
|
|
733
|
+
getCurrentTime: () => runClock.now(),
|
|
701
734
|
setCurrentTime: (t) => {
|
|
702
735
|
const wasPlaying = playingRef.current;
|
|
703
736
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
704
737
|
playingRef.current = false;
|
|
705
|
-
const clamped =
|
|
738
|
+
const clamped = (0, import_svg_animator_core6.clampSeekMs)(t, seekCeiling);
|
|
739
|
+
runClock.seek(clamped);
|
|
706
740
|
const withinIteration = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
|
|
707
741
|
progress.value = withinIteration;
|
|
708
742
|
if (wasPlaying) startFrom(withinIteration);
|
|
743
|
+
else runClock.stop();
|
|
744
|
+
},
|
|
745
|
+
getCurrentProgress: () => (0, import_svg_animator_core6.timeToProgress)(runClock.now(), tracks.duration, tracks.iterations),
|
|
746
|
+
setCurrentProgress: (p) => {
|
|
747
|
+
api.setCurrentTime((0, import_svg_animator_core6.progressToTimeMs)(p, tracks.duration, tracks.iterations));
|
|
709
748
|
}
|
|
710
749
|
};
|
|
711
750
|
(0, import_react3.useImperativeHandle)(apiRef, () => api, [compiled]);
|
|
712
|
-
const trigger = compiled.doc ? (_a = (0,
|
|
713
|
-
const effectiveStartOn =
|
|
714
|
-
const effectiveOutAction =
|
|
751
|
+
const trigger = (0, import_svg_animator_core6.resolveTrigger)(compiled.doc ? (_a = (0, import_svg_animator_core6.getAnimatorConfig)(compiled.doc)) == null ? void 0 : _a.trigger : void 0);
|
|
752
|
+
const effectiveStartOn = trigger.startOn;
|
|
753
|
+
const effectiveOutAction = trigger.outAction;
|
|
754
|
+
const { mode: compMode, warnings: modeWarnings } = (0, import_svg_animator_core6.resolveControlMode)({ progress: progressProp, time, play, pause, autoplay });
|
|
755
|
+
(0, import_react3.useEffect)(() => {
|
|
756
|
+
const diag = makeDiag();
|
|
757
|
+
for (const w of modeWarnings) diag.warn(import_svg_animator_core6.PxDiagnosticKind.usage, w);
|
|
758
|
+
}, [modeWarnings.join("|")]);
|
|
715
759
|
(0, import_react3.useEffect)(() => {
|
|
716
|
-
if (
|
|
717
|
-
const seekMs = time !== void 0 ? time : (progressProp != null ? progressProp : 0
|
|
760
|
+
if (compMode === import_svg_animator_core6.PxControlMode.fixedTime) {
|
|
761
|
+
const seekMs = time !== void 0 ? time : (0, import_svg_animator_core6.progressToTimeMs)(progressProp != null ? progressProp : 0, tracks.duration, tracks.iterations);
|
|
718
762
|
api.setCurrentTime(seekMs);
|
|
719
763
|
return;
|
|
720
764
|
}
|
|
721
|
-
if (
|
|
765
|
+
if (compMode === import_svg_animator_core6.PxControlMode.play) {
|
|
722
766
|
if (play && !pause) api.play();
|
|
723
767
|
else if (pause) api.pause();
|
|
724
|
-
else if (play === false) api.
|
|
768
|
+
else if (play === false) api.pause();
|
|
725
769
|
else api.play();
|
|
726
770
|
return;
|
|
727
771
|
}
|
|
728
|
-
if (autoplay && effectiveStartOn === "load") {
|
|
772
|
+
if (compMode === import_svg_animator_core6.PxControlMode.autoplay && effectiveStartOn === "load") {
|
|
729
773
|
api.play();
|
|
730
774
|
}
|
|
731
|
-
}, [compiled, autoplay, play, pause, progressProp, time]);
|
|
775
|
+
}, [compiled, compMode, autoplay, play, pause, progressProp, time]);
|
|
732
776
|
const scrollRef = (0, import_react3.useRef)(null);
|
|
733
777
|
const inViewRef = (0, import_react3.useRef)(false);
|
|
734
778
|
(0, import_react3.useEffect)(() => {
|
|
735
|
-
var _a2;
|
|
736
779
|
if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
|
|
737
|
-
const threshold =
|
|
780
|
+
const threshold = trigger.scrollIntoViewThreshold;
|
|
738
781
|
inViewRef.current = false;
|
|
739
782
|
const check = () => {
|
|
740
783
|
const node = scrollRef.current;
|
|
@@ -765,6 +808,8 @@ function PixodeskSvgAnimator({
|
|
|
765
808
|
return () => {
|
|
766
809
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
767
810
|
playingRef.current = false;
|
|
811
|
+
onRemove == null ? void 0 : onRemove();
|
|
812
|
+
onStop == null ? void 0 : onStop();
|
|
768
813
|
};
|
|
769
814
|
}, [compiled]);
|
|
770
815
|
const trackById = (0, import_react3.useMemo)(() => {
|
|
@@ -780,7 +825,7 @@ function PixodeskSvgAnimator({
|
|
|
780
825
|
if (!compiled.doc) return null;
|
|
781
826
|
const renderOpts = {
|
|
782
827
|
warnings: warningsRef.current,
|
|
783
|
-
defs: (0,
|
|
828
|
+
defs: (0, import_svg_animator_core6.getDefs)(compiled.doc)
|
|
784
829
|
};
|
|
785
830
|
try {
|
|
786
831
|
return renderRnNode(compiled.doc, __spreadProps(__spreadValues({}, renderOpts), {
|
|
@@ -820,14 +865,15 @@ function PixodeskSvgAnimator({
|
|
|
820
865
|
} catch (e) {
|
|
821
866
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
822
867
|
renderErrorRef.current = error;
|
|
823
|
-
|
|
868
|
+
makeDiag().warn(import_svg_animator_core6.PxDiagnosticKind.internal, "could not render the document: " + error.message);
|
|
824
869
|
return null;
|
|
825
870
|
}
|
|
826
871
|
}, [compiled, trackById]);
|
|
827
872
|
(0, import_react3.useEffect)(() => {
|
|
828
|
-
|
|
873
|
+
const diag = makeDiag();
|
|
874
|
+
for (const w of warningsRef.current) diag.warn(import_svg_animator_core6.PxDiagnosticKind.platform, w);
|
|
829
875
|
}, [root]);
|
|
830
|
-
const failure = (
|
|
876
|
+
const failure = (_b = compiled.error) != null ? _b : renderErrorRef.current;
|
|
831
877
|
(0, import_react3.useEffect)(() => {
|
|
832
878
|
if (failure) onError == null ? void 0 : onError(failure);
|
|
833
879
|
}, [failure]);
|
|
@@ -855,7 +901,7 @@ function PixodeskSvgAnimator({
|
|
|
855
901
|
}
|
|
856
902
|
);
|
|
857
903
|
}
|
|
858
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PxRnErrorBoundary, { onError, fallback, children: content });
|
|
904
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PxRnErrorBoundary, { onError, fallback, diag: makeDiag(), children: content });
|
|
859
905
|
}
|
|
860
906
|
var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
|
|
861
907
|
// Annotate the CommonJS export names for ESM import in node:
|