@pixodesk/svg-animator-rn 1.0.34 → 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 +38 -30
- package/dist/index.cjs +117 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -62
- package/dist/index.d.ts +40 -62
- package/dist/index.js +130 -73
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/PixodeskSvgAnimator.tsx +176 -162
- 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 +16 -16
- package/src/PxRnTracks.ts +5 -5
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,19 +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
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `outAction` | `OutAction` | What a second tap does with the `click` trigger (default: the document's, else `pause`) |
|
|
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 |
|
|
185
185
|
| `onPlay` | `() => void` | Called on play/resume |
|
|
186
186
|
| `onPause` | `() => void` | Called on pause |
|
|
187
|
-
| `onFinish` | `() => void` | Called
|
|
187
|
+
| `onFinish` | `() => void` | Called when the animation reaches its end — every iteration played, or `finish()` was called |
|
|
188
188
|
| `onCancel` | `() => void` | Called on cancel |
|
|
189
|
-
| `
|
|
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) |
|
|
190
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 |
|
|
191
194
|
| `fallback` | `(error) => ReactElement \| null` | Rendered in place of a failed animation (default: nothing) |
|
|
192
195
|
|
|
193
196
|
With none of `autoplay` / `play` / `pause` / `progress` / `time` set, the component
|
|
@@ -215,11 +218,12 @@ renderer never reaches JavaScript and cannot be caught — see
|
|
|
215
218
|
|
|
216
219
|
### Differences from the React package
|
|
217
220
|
|
|
221
|
+
<!-- px-check off differences from React, prose -->
|
|
218
222
|
| Prop | Why it differs |
|
|
219
223
|
|---|---|
|
|
220
|
-
| `
|
|
221
|
-
| `frameRate` |
|
|
222
|
-
| `startOn` |
|
|
224
|
+
| `timeline.engine` | Accepted inside `timeline` but ignored. There is no Web Animations API on React Native; playback is always native-driven. |
|
|
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. |
|
|
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. |
|
|
223
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. |
|
|
224
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. |
|
|
225
229
|
|
|
@@ -229,9 +233,9 @@ There is **no JavaScript frame loop** — the JS thread is idle while an
|
|
|
229
233
|
animation runs.
|
|
230
234
|
|
|
231
235
|
1. **Once per document:** the shared core flattens it
|
|
232
|
-
(`
|
|
236
|
+
(`materializeAllInTree` → effects, loops, motion-path sampling, animated
|
|
233
237
|
`<use>` inlining), then a track compiler densely samples every animated
|
|
234
|
-
property with `calcAnimationValues` — the same function the web
|
|
238
|
+
property with `calcAnimationValues` — the same function the web frame-loop engine
|
|
235
239
|
renders with, so values match the web player exactly.
|
|
236
240
|
2. **Per frame:** one reanimated progress value, driven by
|
|
237
241
|
`withTiming`/`withRepeat` on the UI thread, and a tiny worklet per animated
|
|
@@ -244,29 +248,31 @@ converts it into plain values ahead of time instead of fighting the platform.
|
|
|
244
248
|
## Feature support
|
|
245
249
|
|
|
246
250
|
Every row below was verified by running the document through the real
|
|
247
|
-
pipeline (`
|
|
251
|
+
pipeline (`materializeAllInTree` → track compilation) and checking that the
|
|
248
252
|
element maps to a `react-native-svg` component and that its animated
|
|
249
253
|
properties actually change over time.
|
|
250
254
|
|
|
251
255
|
### Elements
|
|
252
256
|
|
|
257
|
+
<!-- px-check off support matrix, prose -->
|
|
253
258
|
| Element | Renders | Notes |
|
|
254
259
|
|---|---|---|
|
|
255
260
|
| `svg`, `g`, `defs` | ✅ | |
|
|
256
261
|
| `rect`, `circle`, `ellipse`, `line`, `path`, `polygon`, `polyline` | ✅ | |
|
|
257
262
|
| `text`, `tspan` | ✅ | content via the `text` attribute |
|
|
258
263
|
| `textPath` | ✅ | see *Text along a path* below |
|
|
259
|
-
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the
|
|
264
|
+
| `image` | ✅ | `href` accepts `data:` URIs; remote URLs are blocked by the sanitizer |
|
|
260
265
|
| `use`, `symbol` | ✅ | animated targets are **inlined into real clones** before render — `<use>` does not propagate animation natively in React Native |
|
|
261
266
|
| `linearGradient`, `radialGradient`, `stop` | ✅ | |
|
|
262
267
|
| `pattern`, `marker` | ✅ | static geometry verified; complex cases unverified on device |
|
|
263
268
|
| `mask`, `clipPath` | ✅ | |
|
|
264
269
|
| `filter` + all 22 `fe*` primitives | ✅ | `feGaussianBlur`, `feDropShadow`, `feColorMatrix`, `feMerge`, `feComponentTransfer` + `feFunc*`, … **The visual result has not yet been checked on a real device** |
|
|
265
|
-
| `foreignObject` | ❌ | blocked by the shared
|
|
266
|
-
| `script` | ❌ | blocked by the shared
|
|
270
|
+
| `foreignObject` | ❌ | blocked by the shared sanitizer (embeds arbitrary host content) |
|
|
271
|
+
| `script` | ❌ | blocked by the shared sanitizer |
|
|
267
272
|
|
|
268
273
|
### Animatable attributes
|
|
269
274
|
|
|
275
|
+
<!-- px-check off support matrix, prose -->
|
|
270
276
|
| Attribute | Animates | Notes |
|
|
271
277
|
|---|---|---|
|
|
272
278
|
| `opacity`, `fill-opacity`, `stroke-opacity` | ✅ | |
|
|
@@ -283,50 +289,52 @@ properties actually change over time.
|
|
|
283
289
|
|
|
284
290
|
### Effects (`node.effects`)
|
|
285
291
|
|
|
286
|
-
All effects are
|
|
292
|
+
All effects are materialized by the shared core before rendering, so the React Native
|
|
287
293
|
player sees plain nodes. **All are supported:**
|
|
288
294
|
|
|
295
|
+
<!-- px-check schema PxEffectsSchema -->
|
|
289
296
|
| Effect | Status | Notes |
|
|
290
297
|
|---|---|---|
|
|
291
298
|
| `transformBy` | ✅ | all parts animatable, including `skew` |
|
|
292
|
-
| `repeater` | ✅ | copies
|
|
299
|
+
| `repeater` | ✅ | copies materialized as real elements; per-copy params animatable |
|
|
293
300
|
| `maskedBy` | ✅ | including an animated mask source |
|
|
294
301
|
| `clipPath` | ✅ | including animated clip geometry |
|
|
295
302
|
| `strokeTrim` | ✅ | incl. `offset` and `subPaths: 'combined'` |
|
|
296
303
|
| `clone` + `retime` | ✅ | each clone keeps its own time shift, incl. `retime.timeCrop` (a visibility window on the document timeline) |
|
|
297
|
-
| `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 -->
|
|
298
305
|
| `textPath` | ✅ | incl. animated `startOffset` |
|
|
299
|
-
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.
|
|
300
|
-
| `isCombinedShape` | ✅ | |
|
|
306
|
+
| `text.useGlyphs` | ✅ | text becomes `<path>` outlines from `definitions.fonts` — no font needed |
|
|
301
307
|
|
|
302
308
|
### Motion, timing and references
|
|
303
309
|
|
|
310
|
+
<!-- px-check off support matrix, prose -->
|
|
304
311
|
| Feature | Status | Notes |
|
|
305
312
|
|---|---|---|
|
|
306
313
|
| **Motion along a path** + `autoOrient` | ✅ | **sampled** by the core into plain transform keyframes — `react-native-svg` has no native path motion |
|
|
307
314
|
| **Text along a path** | ✅ two ways | native `textPath` (incl. animated `startOffset`), or **per-letter motion paths** for smooth results — the example app uses the latter, since animating native `startOffset` is janky in `react-native-svg` |
|
|
308
315
|
| Per-property `loop` (incl. `alternate` pingpong) | ✅ | expanded before playback |
|
|
309
316
|
| Easing (cubic-bezier and named refs) | ✅ | baked into the sampled tracks |
|
|
310
|
-
| `definitions.animations` / `easings` / `styles` / `
|
|
317
|
+
| `definitions.animations` / `easings` / `styles` / `fonts` | ✅ | named refs resolved; `style` presets applied as props |
|
|
311
318
|
| `node.style` (inline or named) | ✅ | resolved to props — React Native has no CSS, so explicit attributes win |
|
|
312
319
|
|
|
313
320
|
### Playback and triggers
|
|
314
321
|
|
|
322
|
+
<!-- px-check off support matrix, prose -->
|
|
315
323
|
| Feature | Status | Notes |
|
|
316
324
|
|---|---|---|
|
|
317
325
|
| `duration`, `delay`, `iterations` (incl. `'infinite'`) | ✅ | |
|
|
318
326
|
| `direction` — all four values | ✅ | |
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
327
|
+
| `fillMode` — `forwards` / `backwards` / `both` / `none` | ✅ | |
|
|
328
|
+
| `trigger.finishAction: 'reset'` | ✅ | |
|
|
321
329
|
| `play` / `pause` / `cancel` / `finish` | ✅ | |
|
|
322
330
|
| `setCurrentTime` — jump to a time, including **while playing** | ✅ | playback continues from the new point |
|
|
323
331
|
| `setPlaybackRate` — faster, slower and **reverse** (negative) | ✅ | composes with `direction` |
|
|
324
332
|
| Trigger `load` / `programmatic` | ✅ | |
|
|
325
333
|
| Trigger `click` | ✅ | wrapped in a `Pressable`; a second tap applies `outAction` |
|
|
326
|
-
| 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` |
|
|
327
335
|
| Trigger `mouseOver` | ❌ | no touch equivalent — use `click`, or drive `play` yourself |
|
|
328
|
-
| `frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
|
|
329
|
-
| `
|
|
336
|
+
| `timeline.frameRate` | n/a | reanimated runs at the display refresh rate; use `compileTracks({sampleRate})` to trade memory for temporal precision |
|
|
337
|
+
| `timeline.engine` (`auto` / `native` / `js`) | n/a | there is no Web Animations API on React Native — playback is always native-driven |
|
|
330
338
|
|
|
331
339
|
### Known limitations
|
|
332
340
|
|
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,38 +562,33 @@ 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
|
-
|
|
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);
|
|
574
|
+
doc = applied.doc;
|
|
575
|
+
}
|
|
576
|
+
let prepared = (0, import_svg_animator_core6.materializeAllInTree)(doc, import_svg_animator_core6.PxTimelineEngine.native);
|
|
569
577
|
if (NATIVE_SVG_VIEWS) {
|
|
570
578
|
prepared = openClosedTextPathTargets(prepared);
|
|
571
579
|
}
|
|
572
|
-
|
|
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);
|
|
580
|
+
prepared = (0, import_svg_animator_core6.generateNewIds)(prepared);
|
|
584
581
|
const tracks = compileTracks(prepared, { native: NATIVE_SVG_VIEWS });
|
|
585
582
|
return { doc: prepared, tracks, error: null };
|
|
586
583
|
}
|
|
587
584
|
function PixodeskSvgAnimator({
|
|
588
585
|
doc,
|
|
586
|
+
timeline,
|
|
587
|
+
resetTimeline,
|
|
589
588
|
duration,
|
|
590
589
|
delay,
|
|
591
590
|
iterations,
|
|
592
|
-
|
|
593
|
-
direction,
|
|
594
|
-
resetOnFinish,
|
|
595
|
-
outAction: outActionProp,
|
|
591
|
+
startOn,
|
|
596
592
|
// (`progress` prop aliased — the name is taken by the internal reanimated SharedValue)
|
|
597
593
|
autoplay,
|
|
598
594
|
play,
|
|
@@ -605,24 +601,42 @@ function PixodeskSvgAnimator({
|
|
|
605
601
|
onPause,
|
|
606
602
|
onCancel,
|
|
607
603
|
onFinish,
|
|
604
|
+
onRemove,
|
|
608
605
|
onError,
|
|
609
|
-
fallback
|
|
606
|
+
fallback,
|
|
607
|
+
onWarn,
|
|
608
|
+
silent
|
|
610
609
|
}) {
|
|
611
|
-
var _a, _b
|
|
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]");
|
|
612
623
|
const compiled = (0, import_react3.useMemo)(() => {
|
|
613
624
|
try {
|
|
614
625
|
return compileDocument(
|
|
615
626
|
doc,
|
|
616
|
-
{
|
|
627
|
+
{ timeline, resetTimeline, duration, delay, iterations, startOn },
|
|
628
|
+
makeDiag()
|
|
617
629
|
);
|
|
618
630
|
} catch (e) {
|
|
619
631
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
620
|
-
|
|
632
|
+
makeDiag().warn(import_svg_animator_core6.PxDiagnosticKind.internal, "could not compile the document: " + error.message);
|
|
621
633
|
return { doc: null, tracks: EMPTY_TRACKS, error };
|
|
622
634
|
}
|
|
623
|
-
}, [doc,
|
|
635
|
+
}, [doc, timelineKey, resetTimeline, duration, delay, iterations, startOn]);
|
|
624
636
|
const tracks = compiled.tracks;
|
|
625
|
-
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]);
|
|
626
640
|
const progress = (0, import_react_native_reanimated.useSharedValue)(0);
|
|
627
641
|
const playingRef = (0, import_react3.useRef)(false);
|
|
628
642
|
const rateRef = (0, import_react3.useRef)(1);
|
|
@@ -634,6 +648,8 @@ function PixodeskSvgAnimator({
|
|
|
634
648
|
};
|
|
635
649
|
const notifyFinish = () => {
|
|
636
650
|
playingRef.current = false;
|
|
651
|
+
runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
|
|
652
|
+
runClock.stop();
|
|
637
653
|
progress.value = restingPosition();
|
|
638
654
|
onFinish == null ? void 0 : onFinish();
|
|
639
655
|
onStop == null ? void 0 : onStop();
|
|
@@ -670,12 +686,15 @@ function PixodeskSvgAnimator({
|
|
|
670
686
|
const api = {
|
|
671
687
|
isPlaying: () => playingRef.current,
|
|
672
688
|
play: () => {
|
|
689
|
+
if (Number.isFinite(seekCeiling) && runClock.now() >= seekCeiling) runClock.seek(0);
|
|
690
|
+
runClock.start(rateRef.current);
|
|
673
691
|
startFrom(progress.value);
|
|
674
692
|
onPlay == null ? void 0 : onPlay();
|
|
675
693
|
},
|
|
676
694
|
pause: () => {
|
|
677
695
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
678
696
|
playingRef.current = false;
|
|
697
|
+
runClock.stop();
|
|
679
698
|
onPause == null ? void 0 : onPause();
|
|
680
699
|
onStop == null ? void 0 : onStop();
|
|
681
700
|
},
|
|
@@ -683,62 +702,82 @@ function PixodeskSvgAnimator({
|
|
|
683
702
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
684
703
|
progress.value = 0;
|
|
685
704
|
playingRef.current = false;
|
|
705
|
+
runClock.seek(0);
|
|
706
|
+
runClock.stop();
|
|
686
707
|
onCancel == null ? void 0 : onCancel();
|
|
687
708
|
onStop == null ? void 0 : onStop();
|
|
688
709
|
},
|
|
689
710
|
finish: () => {
|
|
690
711
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
691
712
|
playingRef.current = false;
|
|
713
|
+
runClock.seek(Number.isFinite(seekCeiling) ? seekCeiling : tracks.duration);
|
|
714
|
+
runClock.stop();
|
|
692
715
|
progress.value = restingPosition();
|
|
693
716
|
onFinish == null ? void 0 : onFinish();
|
|
694
717
|
onStop == null ? void 0 : onStop();
|
|
695
718
|
},
|
|
696
719
|
setPlaybackRate: (rate) => {
|
|
697
|
-
if (!
|
|
698
|
-
|
|
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);
|
|
699
722
|
return;
|
|
700
723
|
}
|
|
701
724
|
rateRef.current = rate;
|
|
702
|
-
if (playingRef.current)
|
|
725
|
+
if (playingRef.current) {
|
|
726
|
+
runClock.start(rate);
|
|
727
|
+
startFrom(progress.value);
|
|
728
|
+
}
|
|
703
729
|
},
|
|
704
|
-
|
|
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(),
|
|
705
734
|
setCurrentTime: (t) => {
|
|
706
735
|
const wasPlaying = playingRef.current;
|
|
707
736
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
708
737
|
playingRef.current = false;
|
|
709
|
-
const clamped =
|
|
738
|
+
const clamped = (0, import_svg_animator_core6.clampSeekMs)(t, seekCeiling);
|
|
739
|
+
runClock.seek(clamped);
|
|
710
740
|
const withinIteration = tracks.duration > 0 ? clamped % tracks.duration || (clamped === 0 ? 0 : tracks.duration) : 0;
|
|
711
741
|
progress.value = withinIteration;
|
|
712
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));
|
|
713
748
|
}
|
|
714
749
|
};
|
|
715
750
|
(0, import_react3.useImperativeHandle)(apiRef, () => api, [compiled]);
|
|
716
|
-
const trigger = compiled.doc ? (_a = (0,
|
|
717
|
-
const
|
|
718
|
-
const
|
|
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 });
|
|
719
755
|
(0, import_react3.useEffect)(() => {
|
|
720
|
-
|
|
721
|
-
|
|
756
|
+
const diag = makeDiag();
|
|
757
|
+
for (const w of modeWarnings) diag.warn(import_svg_animator_core6.PxDiagnosticKind.usage, w);
|
|
758
|
+
}, [modeWarnings.join("|")]);
|
|
759
|
+
(0, import_react3.useEffect)(() => {
|
|
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);
|
|
722
762
|
api.setCurrentTime(seekMs);
|
|
723
763
|
return;
|
|
724
764
|
}
|
|
725
|
-
if (
|
|
765
|
+
if (compMode === import_svg_animator_core6.PxControlMode.play) {
|
|
726
766
|
if (play && !pause) api.play();
|
|
727
767
|
else if (pause) api.pause();
|
|
728
|
-
else if (play === false) api.
|
|
768
|
+
else if (play === false) api.pause();
|
|
729
769
|
else api.play();
|
|
730
770
|
return;
|
|
731
771
|
}
|
|
732
|
-
if (autoplay &&
|
|
772
|
+
if (compMode === import_svg_animator_core6.PxControlMode.autoplay && effectiveStartOn === "load") {
|
|
733
773
|
api.play();
|
|
734
774
|
}
|
|
735
|
-
}, [compiled, autoplay, play, pause, progressProp, time]);
|
|
775
|
+
}, [compiled, compMode, autoplay, play, pause, progressProp, time]);
|
|
736
776
|
const scrollRef = (0, import_react3.useRef)(null);
|
|
737
777
|
const inViewRef = (0, import_react3.useRef)(false);
|
|
738
778
|
(0, import_react3.useEffect)(() => {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const threshold = (_a2 = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _a2 : 0;
|
|
779
|
+
if (!autoplay || effectiveStartOn !== "scrollIntoView") return;
|
|
780
|
+
const threshold = trigger.scrollIntoViewThreshold;
|
|
742
781
|
inViewRef.current = false;
|
|
743
782
|
const check = () => {
|
|
744
783
|
const node = scrollRef.current;
|
|
@@ -754,21 +793,23 @@ function PixodeskSvgAnimator({
|
|
|
754
793
|
if (isIn) {
|
|
755
794
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
756
795
|
api.play();
|
|
757
|
-
} else if (
|
|
758
|
-
else if (
|
|
796
|
+
} else if (effectiveOutAction === "reset") api.cancel();
|
|
797
|
+
else if (effectiveOutAction === "reverse") {
|
|
759
798
|
api.setPlaybackRate(-Math.abs(rateRef.current || 1));
|
|
760
799
|
api.play();
|
|
761
|
-
} else if (
|
|
800
|
+
} else if (effectiveOutAction !== "continue") api.pause();
|
|
762
801
|
});
|
|
763
802
|
};
|
|
764
803
|
check();
|
|
765
804
|
const id = setInterval(check, 200);
|
|
766
805
|
return () => clearInterval(id);
|
|
767
|
-
}, [compiled, autoplay,
|
|
806
|
+
}, [compiled, autoplay, effectiveStartOn, effectiveOutAction]);
|
|
768
807
|
(0, import_react3.useEffect)(() => {
|
|
769
808
|
return () => {
|
|
770
809
|
(0, import_react_native_reanimated.cancelAnimation)(progress);
|
|
771
810
|
playingRef.current = false;
|
|
811
|
+
onRemove == null ? void 0 : onRemove();
|
|
812
|
+
onStop == null ? void 0 : onStop();
|
|
772
813
|
};
|
|
773
814
|
}, [compiled]);
|
|
774
815
|
const trackById = (0, import_react3.useMemo)(() => {
|
|
@@ -784,7 +825,7 @@ function PixodeskSvgAnimator({
|
|
|
784
825
|
if (!compiled.doc) return null;
|
|
785
826
|
const renderOpts = {
|
|
786
827
|
warnings: warningsRef.current,
|
|
787
|
-
defs: (0,
|
|
828
|
+
defs: (0, import_svg_animator_core6.getDefs)(compiled.doc)
|
|
788
829
|
};
|
|
789
830
|
try {
|
|
790
831
|
return renderRnNode(compiled.doc, __spreadProps(__spreadValues({}, renderOpts), {
|
|
@@ -824,32 +865,33 @@ function PixodeskSvgAnimator({
|
|
|
824
865
|
} catch (e) {
|
|
825
866
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
826
867
|
renderErrorRef.current = error;
|
|
827
|
-
|
|
868
|
+
makeDiag().warn(import_svg_animator_core6.PxDiagnosticKind.internal, "could not render the document: " + error.message);
|
|
828
869
|
return null;
|
|
829
870
|
}
|
|
830
871
|
}, [compiled, trackById]);
|
|
831
872
|
(0, import_react3.useEffect)(() => {
|
|
832
|
-
|
|
873
|
+
const diag = makeDiag();
|
|
874
|
+
for (const w of warningsRef.current) diag.warn(import_svg_animator_core6.PxDiagnosticKind.platform, w);
|
|
833
875
|
}, [root]);
|
|
834
|
-
const failure = (
|
|
876
|
+
const failure = (_b = compiled.error) != null ? _b : renderErrorRef.current;
|
|
835
877
|
(0, import_react3.useEffect)(() => {
|
|
836
878
|
if (failure) onError == null ? void 0 : onError(failure);
|
|
837
879
|
}, [failure]);
|
|
838
880
|
if (failure) return fallback ? fallback(failure) : null;
|
|
839
881
|
let content = root;
|
|
840
|
-
if (autoplay &&
|
|
882
|
+
if (autoplay && effectiveStartOn === "scrollIntoView" && root) {
|
|
841
883
|
content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { ref: scrollRef, collapsable: false, children: root });
|
|
842
|
-
} else if (autoplay &&
|
|
884
|
+
} else if (autoplay && effectiveStartOn === "click" && root) {
|
|
843
885
|
content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
844
886
|
import_react_native.Pressable,
|
|
845
887
|
{
|
|
846
888
|
onPress: () => {
|
|
847
889
|
if (playingRef.current) {
|
|
848
|
-
if (
|
|
849
|
-
else if (
|
|
890
|
+
if (effectiveOutAction === "reset") api.cancel();
|
|
891
|
+
else if (effectiveOutAction === "reverse") {
|
|
850
892
|
api.setPlaybackRate(-Math.abs(rateRef.current || 1));
|
|
851
893
|
api.play();
|
|
852
|
-
} else if (
|
|
894
|
+
} else if (effectiveOutAction !== "continue") api.pause();
|
|
853
895
|
} else {
|
|
854
896
|
if (rateRef.current < 0) api.setPlaybackRate(Math.abs(rateRef.current));
|
|
855
897
|
api.play();
|
|
@@ -859,7 +901,7 @@ function PixodeskSvgAnimator({
|
|
|
859
901
|
}
|
|
860
902
|
);
|
|
861
903
|
}
|
|
862
|
-
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 });
|
|
863
905
|
}
|
|
864
906
|
var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
|
|
865
907
|
// Annotate the CommonJS export names for ESM import in node:
|