@pixodesk/svg-animator-react 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ const api = useRef<ReactAnimatorApi>(null);
60
60
  <button onClick={() => api.current?.finish()}>Finish</button>
61
61
  ```
62
62
 
63
- `ReactAnimatorApi` methods: `play()`, `pause()`, `cancel()`, `finish()`, `isPlaying()`, `getCurrentTime()`, `setCurrentTime(ms)`.
63
+ `ReactAnimatorApi` methods: `play()`, `pause()`, `cancel()`, `finish()`, `isPlaying()`, `setPlaybackRate(rate)`, `getCurrentTime()`, `setCurrentTime(ms)`.
64
64
 
65
65
  ### Controlled time
66
66
 
@@ -89,7 +89,7 @@ const [timeMs, setTimeMs] = useState(0);
89
89
  | `play` | `boolean` | Start playback, ignoring document triggers |
90
90
  | `pause` | `boolean` | Pause current playback |
91
91
  | `apiRef` | `RefObject<ReactAnimatorApi>` | Ref for imperative control |
92
- | `time` | `number` | Seek to a fractional position |
92
+ | `time` | `number` | Seek to a fraction (0–1) of the whole timeline (duration × iterations) |
93
93
  | `timeMs` | `number` | Seek to a time in milliseconds |
94
94
  | `mode` | `'auto' \| 'webapi' \| 'frames'` | Animation engine |
95
95
  | `duration` | `number` | Duration override (ms) |
@@ -104,5 +104,11 @@ const [timeMs, setTimeMs] = useState(0);
104
104
  | `onPause` | `() => void` | Called on pause |
105
105
  | `onFinish` | `() => void` | Called on natural finish |
106
106
  | `onCancel` | `() => void` | Called on cancel |
107
- | `className` | `string` | CSS class |
108
- | `style` | `CSSProperties` | Inline styles |
107
+ | `onRemove` | `() => void` | Called when the animation is destroyed (e.g. unmount) |
108
+ | `onStop` | `() => void` | Called whenever playback halts (pause / cancel / finish / remove) |
109
+ | `className` | `string` | CSS class applied to the rendered root `<svg>` |
110
+ | `style` | `CSSProperties` | Inline styles applied to the rendered root `<svg>` |
111
+
112
+ With none of `autoplay` / `play` / `pause` / `apiRef` / `time` / `timeMs` set, the component renders the animation statically (initial state, no playback).
113
+
114
+ Note: recreating the animator (unmount or a `doc` swap) emits `onCancel`, `onRemove`, and `onStop` for the torn-down instance. Scrubbing `time` / `timeMs` does **not** recreate the animator — it seeks the existing one.
package/dist/index.cjs CHANGED
@@ -136,28 +136,46 @@ var PixodeskSvgAnimatorImpl = ({
136
136
  style,
137
137
  doc,
138
138
  compMode,
139
- apiHolderRef
139
+ apiHolderRef,
140
+ callbacksRef
140
141
  }) => {
141
142
  doc = (0, import_svg_animator_web.generateNewIds)(doc);
142
143
  const elementRefs = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
143
- const renderNode = (node) => {
144
+ const renderNode = (node, isRoot = false) => {
144
145
  if (!node) return null;
145
146
  const { type, animate, meta, children, ...props } = node;
146
147
  const normProps = (0, import_svg_animator_web.getNormalizedProps)(props);
147
148
  normProps["ref"] = (domEl) => {
148
149
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
149
150
  };
151
+ if (isRoot) {
152
+ if (className) {
153
+ normProps["className"] = normProps["className"] ? normProps["className"] + " " + className : className;
154
+ }
155
+ if (style) normProps["style"] = style;
156
+ }
150
157
  return (0, import_react2.createElement)(type, normProps, children?.map((child) => renderNode(child)));
151
158
  };
152
- const root = doc ? renderNode(doc) : null;
159
+ const root = doc ? renderNode(doc, true) : null;
153
160
  (0, import_react2.useEffect)(() => {
154
- let api = (0, import_svg_animator_web.createAnimator)({ data: doc, adapter: createReactAdapter(elementRefs) });
161
+ const cb = (name, alsoStop = false) => () => {
162
+ callbacksRef.current?.[name]?.();
163
+ if (alsoStop) callbacksRef.current?.onStop?.();
164
+ };
165
+ const callbacks = {
166
+ onPlay: cb("onPlay"),
167
+ onPause: cb("onPause", true),
168
+ onCancel: cb("onCancel", true),
169
+ onFinish: cb("onFinish", true),
170
+ onRemove: cb("onRemove", true)
171
+ };
172
+ let api = (0, import_svg_animator_web.createAnimator)({ data: doc, adapter: createReactAdapter(elementRefs), callbacks });
155
173
  apiHolderRef.current = api;
156
174
  return () => {
157
175
  api?.destroy();
158
176
  apiHolderRef.current = null;
159
177
  };
160
- }, [doc, apiHolderRef]);
178
+ }, [doc, apiHolderRef, callbacksRef]);
161
179
  return root;
162
180
  };
163
181
  var PixodeskSvgAnimatorImplOnce = import_react2.default.memo(
@@ -175,7 +193,6 @@ var PixodeskSvgAnimator = ({
175
193
  time,
176
194
  timeMs,
177
195
  apiRef,
178
- timeline,
179
196
  // Overrides
180
197
  mode,
181
198
  delay,
@@ -186,7 +203,13 @@ var PixodeskSvgAnimator = ({
186
203
  frameRate,
187
204
  startOn,
188
205
  outAction,
189
- scrollIntoViewThreshold
206
+ scrollIntoViewThreshold,
207
+ onPlay,
208
+ onStop,
209
+ onPause,
210
+ onCancel,
211
+ onFinish,
212
+ onRemove
190
213
  }) => {
191
214
  let compMode = "static" /* static */;
192
215
  if (apiRef) {
@@ -195,7 +218,7 @@ var PixodeskSvgAnimator = ({
195
218
  compMode = "autoplay" /* autoplay */;
196
219
  } else if (time !== void 0 || timeMs !== void 0) {
197
220
  compMode = "fixedTime" /* fixedTime */;
198
- } else {
221
+ } else if (play !== void 0 || pause !== void 0) {
199
222
  compMode = "play" /* play */;
200
223
  }
201
224
  if (compMode !== "autoplay" /* autoplay */) {
@@ -245,20 +268,20 @@ var PixodeskSvgAnimator = ({
245
268
  }
246
269
  };
247
270
  }
271
+ let seekMs;
248
272
  if (compMode === "fixedTime" /* fixedTime */) {
249
- let delay2 = 0;
250
- if (time !== void 0) delay2 = -time;
251
- if (timeMs !== void 0) delay2 = -timeMs;
252
273
  const animator = doc.animator || {};
253
- doc = {
254
- ...doc,
255
- animator: {
256
- ...animator,
257
- delay: delay2
258
- }
259
- };
274
+ if (time !== void 0) {
275
+ const iterationsValue = iterations ?? animator.iterations;
276
+ const iterationsCount = typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
277
+ const singleDuration = duration ?? animator.duration ?? 1e3;
278
+ seekMs = time * singleDuration * iterationsCount;
279
+ }
280
+ if (timeMs !== void 0) seekMs = timeMs;
260
281
  }
261
282
  const apiHolderRef = (0, import_react2.useRef)(null);
283
+ const callbacksRef = (0, import_react2.useRef)({});
284
+ callbacksRef.current = { onPlay, onStop, onPause, onCancel, onFinish, onRemove };
262
285
  (0, import_react2.useImperativeHandle)(apiRef, () => {
263
286
  return {
264
287
  isPlaying: () => apiHolderRef.current?.isPlaying() || false,
@@ -266,18 +289,22 @@ var PixodeskSvgAnimator = ({
266
289
  pause: () => apiHolderRef.current?.pause(),
267
290
  cancel: () => apiHolderRef.current?.cancel(),
268
291
  finish: () => apiHolderRef.current?.finish(),
269
- getCurrentTime: () => apiHolderRef.current?.getCurrentTime() || null,
292
+ setPlaybackRate: (rate) => apiHolderRef.current?.setPlaybackRate(rate),
293
+ getCurrentTime: () => apiHolderRef.current?.getCurrentTime() ?? null,
270
294
  setCurrentTime: (time2) => apiHolderRef.current?.setCurrentTime(time2)
271
295
  };
272
296
  }, []);
297
+ const key = useDepsVersion(compMode, doc, className, style);
273
298
  (0, import_react2.useEffect)(() => {
274
299
  if (compMode === "play" /* play */) {
275
300
  if (play && !pause) {
276
301
  apiHolderRef.current?.play();
277
302
  } else if (pause) {
278
303
  apiHolderRef.current?.pause();
279
- } else {
304
+ } else if (play === false) {
280
305
  apiHolderRef.current?.finish();
306
+ } else {
307
+ apiHolderRef.current?.play();
281
308
  }
282
309
  }
283
310
  return () => {
@@ -285,14 +312,22 @@ var PixodeskSvgAnimator = ({
285
312
  apiHolderRef.current?.pause();
286
313
  }
287
314
  };
288
- }, [compMode, play, pause]);
289
- const key = useDepsVersion(compMode, doc);
315
+ }, [compMode, play, pause, key]);
316
+ (0, import_react2.useEffect)(() => {
317
+ if (compMode === "fixedTime" /* fixedTime */ && seekMs !== void 0) {
318
+ apiHolderRef.current?.setCurrentTime(seekMs);
319
+ apiHolderRef.current?.pause();
320
+ }
321
+ }, [compMode, seekMs, key]);
290
322
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
291
323
  PixodeskSvgAnimatorImplOnce,
292
324
  {
325
+ className,
326
+ style,
293
327
  compMode,
294
328
  doc,
295
- apiHolderRef
329
+ apiHolderRef,
330
+ callbacksRef
296
331
  },
297
332
  key
298
333
  );
@@ -325,7 +360,10 @@ var PixodeskSvgCssAnimator = ({ className, style, children, startOn = "load", ou
325
360
  "div",
326
361
  {
327
362
  ref,
328
- className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
363
+ className: [
364
+ className,
365
+ state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""
366
+ ].filter(Boolean).join(" "),
329
367
  style,
330
368
  ...handlers,
331
369
  children
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/PixodeskSvgAnimator.tsx","../src/Utils.ts","../src/PixodeskSvgCssAnimator.tsx"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport PixodeskSvgAnimator from './PixodeskSvgAnimator';\nexport type { ReactAnimatorApi } from './PixodeskSvgAnimator';\nexport { PixodeskSvgAnimator };\n\nimport PixodeskSvgCssAnimator from './PixodeskSvgCssAnimator';\nexport { PixodeskSvgCssAnimator };\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { JsMode, OutAction, PxAnimatedSvgDocument, PxAnimatorAPI, PxNode, PxPlatformAdapter, PxTrigger, StartOn } from '@pixodesk/svg-animator-web';\nimport { camelCaseToKebabWordIfNeeded, createAnimator, FillMode, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES } from '@pixodesk/svg-animator-web';\nimport type { CSSProperties, FC, ReactElement } from 'react';\nimport React, { createElement, useEffect, useImperativeHandle, useRef } from 'react';\nimport { useDepsVersion } from './Utils';\n\n\n// -- Public types -----------------------------------------------------------\n\nexport interface ReactAnimatorApi {\n /** Returns true if the animation is currently running. */\n isPlaying(): boolean;\n\n /** Starts or resumes the animation. */\n play(): void;\n\n /** Pauses the animation at its current state. */\n pause(): void;\n\n /** Stops the animation and resets it to its initial state. */\n cancel(): void;\n\n /** Jumps to the end of the animation and holds the final state. */\n finish(): void;\n\n /** Returns the current playback time in milliseconds. */\n getCurrentTime(): number | null;\n\n /** Jumps to a specific time (in milliseconds) in the animation. */\n setCurrentTime(time: number): void;\n}\n\nexport interface PixodeskSvgAnimatorImplProps {\n className?: string;\n style?: CSSProperties;\n doc: PxAnimatedSvgDocument;\n compMode: PixodeskSvgAnimatorCompMode;\n\n /** Imperative API handle populated by the inner component. */\n apiHolderRef: React.RefObject<PxAnimatorAPI | null>;\n}\n\nexport interface PixodeskSvgAnimatorProps {\n\n className?: string;\n\n style?: CSSProperties;\n\n // -- Source ---------------------------------------------------------------\n\n /**\n * The animation document to render.\n *\n * TODO: Accept PxFileConfig | string to support URL-based loading, e.g.\n * <PixodeskSvgAnimator doc=\"/animation.json\" />\n */\n doc: PxAnimatedSvgDocument;\n\n // -- Timeline ------------------------------------------------------------\n\n timeline?: 'time' | 'scroll';\n\n // -- Rendering mode ------------------------------------------------------\n\n /** Forces a specific rendering engine. Defaults to 'auto'. */\n mode?: JsMode;\n\n // -- Timing overrides ----------------------------------------------------\n\n /** Delay before the animation starts, in milliseconds. */\n delay?: number;\n\n /** Defines the element's style when the animation is not active. */\n fill?: FillMode;\n\n /** Number of iterations, or 'infinite' for endless looping. */\n iterations?: number | 'infinite';\n\n /** Duration of a single iteration in milliseconds. */\n duration?: number;\n\n /** Playback direction. */\n direction?: PlaybackDirection;\n\n /** Target frame rate (frames per second). */\n frameRate?: number;\n\n // -- Trigger overrides ---------------------------------------------------\n\n /** The event that starts the animation. When omitted, uses the value from the document config. */\n startOn?: StartOn;\n\n /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */\n outAction?: OutAction;\n\n /** Visibility ratio (0.0–1.0) required to trigger a scrollIntoView animation. Defaults to 0.5. */\n scrollIntoViewThreshold?: number;\n\n // -- Declarative control -------------------------------------------------\n\n /** When true, uses triggers defined in the animation document. */\n autoplay?: boolean;\n\n /**\n * Starts the animation unconditionally, ignoring document triggers.\n * Equivalent to `startOn=\"load\"` / `outAction=\"continue\"`.\n */\n play?: boolean;\n\n /** Pauses current playback. Only meaningful when `play` or `autoplay` is set. */\n pause?: boolean;\n\n // -- Imperative control --------------------------------------------------\n\n /** Ref populated with the imperative playback API. */\n apiRef?: React.RefObject<ReactAnimatorApi | null>;\n\n // -- Controlled (external) time ------------------------------------------\n\n /** Seek to a specific point in the animation (fractional). */\n time?: number;\n\n /** Seek to a specific point in the animation (milliseconds). */\n timeMs?: number;\n\n // -- Callbacks -----------------------------------------------------------\n\n /** Called when the animation starts or resumes. */\n onPlay?: () => void;\n\n /** Called when the animation stops for any reason (pause / cancel / finish / removal). */\n onStop?: () => void; // FIXME: consider a more descriptive name\n\n /** Called when the animation is paused. */\n onPause?: () => void;\n\n /** Called when the animation is cancelled. */\n onCancel?: () => void;\n\n /** Called when the animation finishes naturally. */\n onFinish?: () => void;\n\n /** Called when the animation is removed. */\n onRemove?: () => void;\n\n // -- Diagnostics ---------------------------------------------------------\n\n onWarning?: (msg: string) => void;\n onError?: (msg: string) => void;\n}\n\n\n// -- Internal types ---------------------------------------------------------\n\nenum PixodeskSvgAnimatorCompMode {\n static = 'static',\n autoplay = 'autoplay',\n play = 'play',\n imperativeApi = 'imperativeApi',\n fixedTime = 'fixedTime'\n}\n\n\n// -- React ↔ Animator bridge ------------------------------------------------\n\n/**\n * Creates a platform adapter that routes animator attribute updates\n * to the corresponding React-managed DOM refs.\n */\nexport function createReactAdapter(elementRefs: React.RefObject<Map<string, any>>) {\n const warnedSelectors = new Set<string>();\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => {\n return true;\n },\n setAttribute: (id, attrName, value) => {\n\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const selector = getSelector(id);\n\n const element = elementRefs.current.get(id);\n\n if (!element && !warnedSelectors.has(selector)) {\n warnedSelectors.add(selector);\n console.warn('setAttribute: No elements found for selector \"' + selector + '\"');\n console.warn(elementRefs.current);\n }\n\n if (element) {\n element.setAttribute(attrName, value);\n if (STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}\n\nexport function getSelector(id: string) {\n return '#' + id;\n}\n\n// FIXME: add model validation (e.g. isElementFileJson check)\n\n\n// -- Inner component (memoised, never re-renders) ---------------------------\n\nconst PixodeskSvgAnimatorImpl: FC<PixodeskSvgAnimatorImplProps> = ({\n className, style, doc, compMode, apiHolderRef\n}) => {\n\n doc = generateNewIds(doc);\n\n const elementRefs = useRef(new Map<string, any>());\n\n const renderNode = (node: PxNode | undefined): ReactElement | null => {\n if (!node) return null;\n\n const { type, animate, meta, children, ...props } = node;\n\n const normProps = getNormalizedProps(props);\n\n normProps['ref'] = (domEl: any) => {\n if (node['id']) elementRefs.current.set(node['id'], domEl);\n // return () => {};\n };\n\n return createElement(type, normProps, children?.map(child => renderNode(child)));\n };\n\n const root = doc ? renderNode(doc) : null;\n\n // Create the animator once per document and tear it down on unmount.\n useEffect(() => {\n\n let api: PxAnimatorAPI | undefined = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs) });\n apiHolderRef.current = api;\n\n return () => {\n api?.destroy();\n apiHolderRef.current = null;\n };\n }, [doc, apiHolderRef]);\n\n return root;\n};\n\n/** Memoised wrapper — the inner component never re-renders (props are stable by design). */\nconst PixodeskSvgAnimatorImplOnce = React.memo(\n PixodeskSvgAnimatorImpl,\n () => true // Don't re-render\n);\n\n\n// -- Main public component --------------------------------------------------\n\n/**\n * React component for rendering and controlling Pixodesk SVG animations.\n *\n * Supports four mutually-exclusive control modes:\n *\n * 1. **Autoplay** – uses triggers from the animation document.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} autoplay />\n * ```\n *\n * 2. **Declarative play/pause** – controlled via boolean props.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} play pause={false} />\n * ```\n *\n * 3. **Imperative** – exposes a ref-based API for full programmatic control.\n * ```tsx\n * const api = useRef<ReactAnimatorApi>(null);\n * <PixodeskSvgAnimator doc={animation} apiRef={api} />\n * <button onClick={() => api.current?.play()}>Play</button>\n * ```\n *\n * 4. **Controlled time** – renders a single frame at a given time.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} time={0.5} />\n * <PixodeskSvgAnimator doc={animation} timeMs={500} />\n * ```\n */\nconst PixodeskSvgAnimator: FC<PixodeskSvgAnimatorProps> = ({\n className, style,\n doc, autoplay, play, pause, time, timeMs, apiRef,\n timeline,\n\n // Overrides\n mode, delay, fill, iterations, duration, direction, frameRate,\n\n startOn, outAction, scrollIntoViewThreshold\n}) => {\n\n // Determine which control mode is active.\n let compMode = PixodeskSvgAnimatorCompMode.static;\n if (apiRef) {\n compMode = PixodeskSvgAnimatorCompMode.imperativeApi;\n } else if (autoplay) {\n compMode = PixodeskSvgAnimatorCompMode.autoplay;\n } else if (time !== undefined || timeMs !== undefined) {\n compMode = PixodeskSvgAnimatorCompMode.fixedTime;\n } else {\n compMode = PixodeskSvgAnimatorCompMode.play;\n }\n\n // In non-autoplay modes, override the document trigger to 'programmatic'\n // so the component can manage playback itself.\n if (compMode !== PixodeskSvgAnimatorCompMode.autoplay) {\n const startOn = doc.animator?.trigger?.startOn;\n if (\n startOn &&\n startOn !== 'programmatic' // FIXME: use enum\n ) {\n doc = {\n ...doc,\n animator: {\n ...doc.animator,\n trigger: {\n ...doc.animator?.trigger,\n startOn: 'programmatic' // FIXME: use enum\n }\n }\n };\n }\n }\n\n // Apply timing overrides from props onto the document config.\n if (\n mode !== undefined ||\n duration !== undefined ||\n delay !== undefined ||\n iterations !== undefined ||\n fill !== undefined ||\n direction !== undefined ||\n frameRate !== undefined\n ) {\n const animator = doc.animator || {};\n doc = {\n ...doc,\n animator: {\n ...animator,\n mode: mode !== undefined ? mode : animator.mode,\n duration: duration !== undefined ? duration : animator.duration,\n delay: delay !== undefined ? delay : animator.delay,\n iterations: iterations !== undefined ? iterations : animator.iterations,\n fill: fill !== undefined ? fill : animator.fill,\n direction: direction !== undefined ? direction : animator.direction,\n frameRate: frameRate !== undefined ? frameRate : animator.frameRate\n }\n };\n }\n\n // Apply trigger overrides from props.\n if (\n startOn !== undefined ||\n outAction !== undefined ||\n scrollIntoViewThreshold !== undefined\n ) {\n const trigger: PxTrigger = doc.animator?.trigger || {};\n doc = {\n ...doc,\n animator: {\n ...doc.animator,\n trigger: {\n ...trigger,\n startOn: startOn !== undefined ? startOn : trigger.startOn,\n outAction: outAction !== undefined ? outAction : trigger.outAction,\n scrollIntoViewThreshold: scrollIntoViewThreshold !== undefined ? scrollIntoViewThreshold : trigger.scrollIntoViewThreshold\n }\n }\n };\n }\n\n // In controlled-time mode, use a negative delay to seek to the given frame.\n if (compMode === PixodeskSvgAnimatorCompMode.fixedTime) {\n let delay = 0;\n if (time !== undefined) delay = -time; // FIXME: time as a fraction of total duration?\n if (timeMs !== undefined) delay = -timeMs;\n const animator = doc.animator || {};\n doc = {\n ...doc,\n animator: {\n ...animator,\n delay\n }\n };\n }\n\n const apiHolderRef = useRef<PxAnimatorAPI | null>(null);\n\n // Expose the imperative API via the consumer-provided ref.\n useImperativeHandle(apiRef, () => {\n return {\n isPlaying: () => apiHolderRef.current?.isPlaying() || false,\n play: () => apiHolderRef.current?.play(),\n pause: () => apiHolderRef.current?.pause(),\n cancel: () => apiHolderRef.current?.cancel(),\n finish: () => apiHolderRef.current?.finish(),\n getCurrentTime: () => apiHolderRef.current?.getCurrentTime() || null,\n setCurrentTime: (time: number) => apiHolderRef.current?.setCurrentTime(time),\n };\n }, []);\n\n // Sync declarative play/pause props with the animator.\n useEffect(() => {\n\n if (compMode === PixodeskSvgAnimatorCompMode.play) {\n if (play && !pause) {\n apiHolderRef.current?.play();\n } else if (pause) {\n apiHolderRef.current?.pause();\n } else {\n apiHolderRef.current?.finish();\n }\n }\n\n return () => {\n if (compMode === PixodeskSvgAnimatorCompMode.play) {\n apiHolderRef.current?.pause(); // FIXME - implement\n }\n };\n }, [compMode, play, pause]);\n\n // Increment key when the document or mode changes to force a full remount.\n const key = useDepsVersion(compMode, doc);\n\n return (\n <PixodeskSvgAnimatorImplOnce\n key={key}\n compMode={compMode}\n doc={doc}\n apiHolderRef={apiHolderRef}\n />\n );\n};\n\nexport default PixodeskSvgAnimator;","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { useRef } from 'react';\n\nexport function deepEquals(a: any, b: any): boolean {\n // Handle primitives and null\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== 'object' || typeof b !== 'object') return false;\n\n // Check for cycles using WeakSet\n const seen = new WeakSet();\n\n function equals(x: any, y: any): boolean {\n // Primitive/null check\n if (x === y) return true;\n if (x == null || y == null) return false;\n if (typeof x !== 'object' || typeof y !== 'object') return false;\n\n // Cycle detection\n if (seen.has(x)) {\n throw new Error('Circular reference detected');\n }\n seen.add(x);\n\n // Array check\n const xIsArray = Array.isArray(x);\n const yIsArray = Array.isArray(y);\n if (xIsArray !== yIsArray) return false;\n\n if (xIsArray) {\n if (x.length !== y.length) return false;\n for (let i = 0; i < x.length; i++) {\n if (!equals(x[i], y[i])) return false;\n }\n return true;\n }\n\n // Object check\n const xKeys = Object.keys(x);\n const yKeys = Object.keys(y);\n if (xKeys.length !== yKeys.length) return false;\n\n for (const key of xKeys) {\n if (!yKeys.includes(key)) return false;\n if (!equals(x[key], y[key])) return false;\n }\n\n return true;\n }\n\n return equals(a, b);\n}\n\n\n/**\n * Compares two arrays of dependencies using identity check first (===),\n * falling back to deep equality if references differ.\n * This is optimized for immutable data that may be recreated with same values.\n */\nfunction areDepsEqual(prevDeps: Array<any>, nextDeps: Array<any>): boolean {\n if (prevDeps.length !== nextDeps.length) return false;\n\n for (let i = 0; i < prevDeps.length; i++) {\n // Fast path: same reference (common for immutable data)\n if (prevDeps[i] === nextDeps[i]) continue;\n\n // Slow path: different reference but possibly same content\n if (!deepEquals(prevDeps[i], nextDeps[i])) return false;\n }\n\n return true;\n}\n\n/**\n * Returns a stable numeric version ID that only increments when dependencies\n * actually change (by reference or deep equality).\n */\nexport function useDepsVersion(...deps: Array<any>): number {\n const cache = useRef<{\n versionId: number;\n deps: Array<any>;\n } | null>(null);\n\n // First render - initialize with version 1\n if (cache.current === null) {\n cache.current = {\n versionId: 1,\n deps: deps\n };\n return 1;\n }\n\n // Check if any dependency changed (by reference or value)\n if (!areDepsEqual(cache.current.deps, deps)) {\n // Dependencies changed - increment version and update cache\n cache.current = {\n versionId: cache.current.versionId + 1,\n deps: deps\n };\n }\n\n return cache.current.versionId;\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { OutAction, StartOn } from \"@pixodesk/svg-animator-web\";\nimport { CSSProperties, FC, ReactNode, useEffect, useRef, useState } from \"react\";\n\n\ntype AnimState = 'idle' | 'paused' | 'playing';\n\n/**\n * Controls playback of a SVG+CSS animated files by toggling class names on a wrapper div.\n *\n * Intended for use with SVG files exported from the Pixodesk editor using the\n * **CSS Keyframes** (no `<script>` tag). Import the SVG as a React component\n * via SVGR (`vite-plugin-svgr` or `@svgr/webpack`) and pass it as `children`:\n *\n * ```tsx\n * import AnimationSvg from './animation.svg?react'; // SVGR\n *\n * <PixodeskSvgCssAnimator startOn=\"mouseOver\" outAction=\"pause\">\n * <AnimationSvg />\n * </PixodeskSvgCssAnimator>\n * ```\n *\n * The wrapper div carries one of three animation states via CSS class names:\n * - *(no class)* — idle, animation not started\n * - `px-anim-enabled` — started but paused\n * - `px-anim-enabled px-anim-playing` — actively playing\n *\n * @param children - The SVGR-imported SVG component to animate.\n * @param startOn - What triggers the animation to start:\n * - `'load'` — plays immediately on mount (default)\n * - `'mouseOver'` — plays on hover\n * - `'click'` — plays on click, toggles on second click\n * - `'scrollIntoView'` — plays when the element enters the viewport\n * @param outAction - What happens when the trigger ends (hover/scroll out, second click):\n * - `'continue'` — keeps playing (default)\n * - `'pause'` — pauses at the current frame\n * - `'reset'` — resets to the beginning\n * @param className - Additional CSS class names to apply to the wrapper div.\n * @param style - Inline styles for the wrapper div (e.g. `{ width: 400, height: 400 }`).\n */\nconst PixodeskSvgCssAnimator: FC<{\n className?: string;\n style?: CSSProperties;\n children: ReactNode;\n startOn?: StartOn;\n outAction?: OutAction;\n}> = ({ className, style, children, startOn = 'load', outAction = 'continue' }) => {\n\n const [state, setState] = useState<AnimState>(startOn === 'load' ? 'playing' : 'idle');\n\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (startOn !== 'scrollIntoView') return;\n const el = ref.current;\n if (!el) return;\n const outState: AnimState = (\n outAction === 'reset' ? 'idle' :\n outAction === 'pause' ? 'paused' : 'playing'\n );\n const observer = new IntersectionObserver(\n ([entry]) => setState(entry.isIntersecting ? 'playing' : outState),\n { threshold: 0.1 }\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [startOn, outAction]);\n\n const goOut = () => setState(\n outAction === 'reset' ? 'idle' :\n outAction === 'pause' ? 'paused' : 'playing'\n );\n\n const handlers =\n startOn === 'mouseOver' ? { onMouseEnter: () => setState('playing'), onMouseLeave: goOut } :\n startOn === 'click' ? { onClick: () => state === 'playing' ? goOut() : setState('playing') } :\n {};\n\n return (\n <div\n ref={ref}\n className={className + (\n state === 'playing' ? 'px-anim-enabled px-anim-playing' :\n state === 'paused' ? 'px-anim-enabled' : ''\n )}\n style={style}\n {...handlers}\n >\n {children}\n </div>\n );\n};\nexport default PixodeskSvgCssAnimator;"],"mappings":"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","names":["import_react","React","startOn","delay","time","import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/PixodeskSvgAnimator.tsx","../src/Utils.ts","../src/PixodeskSvgCssAnimator.tsx"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport PixodeskSvgAnimator from './PixodeskSvgAnimator';\nexport type { PixodeskSvgAnimatorCallbacks, PixodeskSvgAnimatorProps, ReactAnimatorApi } from './PixodeskSvgAnimator';\nexport { PixodeskSvgAnimator };\n\nimport PixodeskSvgCssAnimator from './PixodeskSvgCssAnimator';\nexport { PixodeskSvgCssAnimator };\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { JsMode, OutAction, PxAnimatedSvgDocument, PxAnimatorAPI, PxNode, PxPlatformAdapter, PxTrigger, StartOn } from '@pixodesk/svg-animator-web';\nimport { camelCaseToKebabWordIfNeeded, createAnimator, FillMode, generateNewIds, getNormalizedProps, STYLE_ATTR_NAMES } from '@pixodesk/svg-animator-web';\nimport type { CSSProperties, FC, ReactElement } from 'react';\nimport React, { createElement, useEffect, useImperativeHandle, useRef } from 'react';\nimport { useDepsVersion } from './Utils';\n\n\n// -- Public types -----------------------------------------------------------\n\nexport interface ReactAnimatorApi {\n /** Returns true if the animation is currently running. */\n isPlaying(): boolean;\n\n /** Starts or resumes the animation. */\n play(): void;\n\n /** Pauses the animation at its current state. */\n pause(): void;\n\n /** Stops the animation and resets it to its initial state. */\n cancel(): void;\n\n /** Jumps to the end of the animation and holds the final state. */\n finish(): void;\n\n /** Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse. */\n setPlaybackRate(rate: number): void;\n\n /** Returns the current playback time in milliseconds. */\n getCurrentTime(): number | null;\n\n /** Jumps to a specific time (in milliseconds) in the animation. */\n setCurrentTime(time: number): void;\n}\n\nexport interface PixodeskSvgAnimatorImplProps {\n className?: string;\n style?: CSSProperties;\n doc: PxAnimatedSvgDocument;\n compMode: PixodeskSvgAnimatorCompMode;\n\n /** Imperative API handle populated by the inner component. */\n apiHolderRef: React.RefObject<PxAnimatorAPI | null>;\n\n /**\n * Latest lifecycle callbacks, read at invocation time so the memoised\n * inner component always calls the current props even though it never\n * re-renders.\n */\n callbacksRef: React.RefObject<PixodeskSvgAnimatorCallbacks>;\n}\n\n/** Lifecycle callback props (subset of {@link PixodeskSvgAnimatorProps}). */\nexport interface PixodeskSvgAnimatorCallbacks {\n onPlay?: () => void;\n onStop?: () => void;\n onPause?: () => void;\n onCancel?: () => void;\n onFinish?: () => void;\n onRemove?: () => void;\n}\n\nexport interface PixodeskSvgAnimatorProps {\n\n className?: string;\n\n style?: CSSProperties;\n\n // -- Source ---------------------------------------------------------------\n\n /**\n * The animation document to render.\n *\n * TODO: Accept PxFileConfig | string to support URL-based loading, e.g.\n * <PixodeskSvgAnimator doc=\"/animation.json\" />\n */\n doc: PxAnimatedSvgDocument;\n\n // -- Rendering mode ------------------------------------------------------\n\n /** Forces a specific rendering engine. Defaults to 'auto'. */\n mode?: JsMode;\n\n // -- Timing overrides ----------------------------------------------------\n\n /** Delay before the animation starts, in milliseconds. */\n delay?: number;\n\n /** Defines the element's style when the animation is not active. */\n fill?: FillMode;\n\n /** Number of iterations, or 'infinite' for endless looping. */\n iterations?: number | 'infinite';\n\n /** Duration of a single iteration in milliseconds. */\n duration?: number;\n\n /** Playback direction. */\n direction?: PlaybackDirection;\n\n /** Target frame rate (frames per second). */\n frameRate?: number;\n\n // -- Trigger overrides ---------------------------------------------------\n\n /** The event that starts the animation. When omitted, uses the value from the document config. */\n startOn?: StartOn;\n\n /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */\n outAction?: OutAction;\n\n /** Visibility ratio (0.0–1.0) required to trigger a scrollIntoView animation. Defaults to 0.5. */\n scrollIntoViewThreshold?: number;\n\n // -- Declarative control -------------------------------------------------\n\n /** When true, uses triggers defined in the animation document. */\n autoplay?: boolean;\n\n /**\n * Starts the animation unconditionally, ignoring document triggers.\n * Equivalent to `startOn=\"load\"` / `outAction=\"continue\"`.\n */\n play?: boolean;\n\n /** Pauses current playback. Only meaningful when `play` or `autoplay` is set. */\n pause?: boolean;\n\n // -- Imperative control --------------------------------------------------\n\n /** Ref populated with the imperative playback API. */\n apiRef?: React.RefObject<ReactAnimatorApi | null>;\n\n // -- Controlled (external) time ------------------------------------------\n\n /** Seek to a specific point in the animation, as a fraction (0–1) of the whole timeline (duration × iterations). */\n time?: number;\n\n /** Seek to a specific point in the animation (milliseconds). */\n timeMs?: number;\n\n // -- Callbacks -----------------------------------------------------------\n\n /** Called when the animation starts or resumes. */\n onPlay?: () => void;\n\n /** Called when the animation stops for any reason (pause / cancel / finish / removal). */\n onStop?: () => void;\n\n /** Called when the animation is paused. */\n onPause?: () => void;\n\n /** Called when the animation is cancelled. */\n onCancel?: () => void;\n\n /** Called when the animation finishes naturally. */\n onFinish?: () => void;\n\n /** Called when the animation is removed. */\n onRemove?: () => void;\n}\n\n\n// -- Internal types ---------------------------------------------------------\n\nenum PixodeskSvgAnimatorCompMode {\n static = 'static',\n autoplay = 'autoplay',\n play = 'play',\n imperativeApi = 'imperativeApi',\n fixedTime = 'fixedTime'\n}\n\n\n// -- React ↔ Animator bridge ------------------------------------------------\n\n/**\n * Creates a platform adapter that routes animator attribute updates\n * to the corresponding React-managed DOM refs.\n */\nexport function createReactAdapter(elementRefs: React.RefObject<Map<string, any>>) {\n const warnedSelectors = new Set<string>();\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => {\n return true;\n },\n setAttribute: (id, attrName, value) => {\n\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const selector = getSelector(id);\n\n const element = elementRefs.current.get(id);\n\n if (!element && !warnedSelectors.has(selector)) {\n warnedSelectors.add(selector);\n console.warn('setAttribute: No elements found for selector \"' + selector + '\"');\n console.warn(elementRefs.current);\n }\n\n if (element) {\n element.setAttribute(attrName, value);\n if (STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}\n\nexport function getSelector(id: string) {\n return '#' + id;\n}\n\n// FIXME: add model validation (e.g. isElementFileJson check)\n\n\n// -- Inner component (memoised, never re-renders) ---------------------------\n\nconst PixodeskSvgAnimatorImpl: FC<PixodeskSvgAnimatorImplProps> = ({\n className, style, doc, compMode, apiHolderRef, callbacksRef\n}) => {\n\n doc = generateNewIds(doc);\n\n const elementRefs = useRef(new Map<string, any>());\n\n const renderNode = (node: PxNode | undefined, isRoot = false): ReactElement | null => {\n if (!node) return null;\n\n const { type, animate, meta, children, ...props } = node;\n\n const normProps = getNormalizedProps(props);\n\n normProps['ref'] = (domEl: any) => {\n if (node['id']) elementRefs.current.set(node['id'], domEl);\n // return () => {};\n };\n\n // Apply the component's className/style props to the root SVG element.\n if (isRoot) {\n if (className) {\n normProps['className'] = normProps['className']\n ? normProps['className'] + ' ' + className\n : className;\n }\n if (style) normProps['style'] = style;\n }\n\n return createElement(type, normProps, children?.map(child => renderNode(child)));\n };\n\n const root = doc ? renderNode(doc, true) : null;\n\n // Create the animator once per document and tear it down on unmount.\n useEffect(() => {\n\n // Route lifecycle events through `callbacksRef` so the latest callback\n // props are invoked even though this component never re-renders.\n const cb = (name: keyof PixodeskSvgAnimatorCallbacks, alsoStop = false) => () => {\n callbacksRef.current?.[name]?.();\n if (alsoStop) callbacksRef.current?.onStop?.();\n };\n const callbacks = {\n onPlay: cb('onPlay'),\n onPause: cb('onPause', true),\n onCancel: cb('onCancel', true),\n onFinish: cb('onFinish', true),\n onRemove: cb('onRemove', true),\n };\n\n let api: PxAnimatorAPI | undefined = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs), callbacks });\n apiHolderRef.current = api;\n\n return () => {\n api?.destroy();\n apiHolderRef.current = null;\n };\n }, [doc, apiHolderRef, callbacksRef]);\n\n return root;\n};\n\n/** Memoised wrapper — the inner component never re-renders (props are stable by design). */\nconst PixodeskSvgAnimatorImplOnce = React.memo(\n PixodeskSvgAnimatorImpl,\n () => true // Don't re-render\n);\n\n\n// -- Main public component --------------------------------------------------\n\n/**\n * React component for rendering and controlling Pixodesk SVG animations.\n *\n * Supports four mutually-exclusive control modes:\n *\n * 1. **Autoplay** – uses triggers from the animation document.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} autoplay />\n * ```\n *\n * 2. **Declarative play/pause** – controlled via boolean props.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} play pause={false} />\n * ```\n *\n * 3. **Imperative** – exposes a ref-based API for full programmatic control.\n * ```tsx\n * const api = useRef<ReactAnimatorApi>(null);\n * <PixodeskSvgAnimator doc={animation} apiRef={api} />\n * <button onClick={() => api.current?.play()}>Play</button>\n * ```\n *\n * 4. **Controlled time** – renders a single frame at a given time.\n * ```tsx\n * <PixodeskSvgAnimator doc={animation} time={0.5} />\n * <PixodeskSvgAnimator doc={animation} timeMs={500} />\n * ```\n */\nconst PixodeskSvgAnimator: FC<PixodeskSvgAnimatorProps> = ({\n className, style,\n doc, autoplay, play, pause, time, timeMs, apiRef,\n\n // Overrides\n mode, delay, fill, iterations, duration, direction, frameRate,\n\n startOn, outAction, scrollIntoViewThreshold,\n\n onPlay, onStop, onPause, onCancel, onFinish, onRemove\n}) => {\n\n // Determine which control mode is active.\n let compMode = PixodeskSvgAnimatorCompMode.static;\n if (apiRef) {\n compMode = PixodeskSvgAnimatorCompMode.imperativeApi;\n } else if (autoplay) {\n compMode = PixodeskSvgAnimatorCompMode.autoplay;\n } else if (time !== undefined || timeMs !== undefined) {\n compMode = PixodeskSvgAnimatorCompMode.fixedTime;\n } else if (play !== undefined || pause !== undefined) {\n compMode = PixodeskSvgAnimatorCompMode.play;\n }\n\n // In non-autoplay modes, override the document trigger to 'programmatic'\n // so the component can manage playback itself.\n if (compMode !== PixodeskSvgAnimatorCompMode.autoplay) {\n const startOn = doc.animator?.trigger?.startOn;\n if (\n startOn &&\n startOn !== 'programmatic' // FIXME: use enum\n ) {\n doc = {\n ...doc,\n animator: {\n ...doc.animator,\n trigger: {\n ...doc.animator?.trigger,\n startOn: 'programmatic' // FIXME: use enum\n }\n }\n };\n }\n }\n\n // Apply timing overrides from props onto the document config.\n if (\n mode !== undefined ||\n duration !== undefined ||\n delay !== undefined ||\n iterations !== undefined ||\n fill !== undefined ||\n direction !== undefined ||\n frameRate !== undefined\n ) {\n const animator = doc.animator || {};\n doc = {\n ...doc,\n animator: {\n ...animator,\n mode: mode !== undefined ? mode : animator.mode,\n duration: duration !== undefined ? duration : animator.duration,\n delay: delay !== undefined ? delay : animator.delay,\n iterations: iterations !== undefined ? iterations : animator.iterations,\n fill: fill !== undefined ? fill : animator.fill,\n direction: direction !== undefined ? direction : animator.direction,\n frameRate: frameRate !== undefined ? frameRate : animator.frameRate\n }\n };\n }\n\n // Apply trigger overrides from props.\n if (\n startOn !== undefined ||\n outAction !== undefined ||\n scrollIntoViewThreshold !== undefined\n ) {\n const trigger: PxTrigger = doc.animator?.trigger || {};\n doc = {\n ...doc,\n animator: {\n ...doc.animator,\n trigger: {\n ...trigger,\n startOn: startOn !== undefined ? startOn : trigger.startOn,\n outAction: outAction !== undefined ? outAction : trigger.outAction,\n scrollIntoViewThreshold: scrollIntoViewThreshold !== undefined ? scrollIntoViewThreshold : trigger.scrollIntoViewThreshold\n }\n }\n };\n }\n\n // Controlled-time mode: compute the absolute seek target. `time` is a\n // fraction (0–1) of the WHOLE timeline (duration × iterations); `timeMs`\n // is absolute milliseconds. The seek is applied through the animator API\n // (setCurrentTime) below — the document itself stays stable, so scrubbing\n // does NOT recreate the animator.\n let seekMs: number | undefined;\n if (compMode === PixodeskSvgAnimatorCompMode.fixedTime) {\n const animator = doc.animator || {};\n if (time !== undefined) {\n const iterationsValue = iterations ?? animator.iterations;\n const iterationsCount = typeof iterationsValue === 'number' && iterationsValue >= 1 ? iterationsValue : 1;\n const singleDuration = duration ?? animator.duration ?? 1000; // engine default duration\n seekMs = time * singleDuration * iterationsCount;\n }\n if (timeMs !== undefined) seekMs = timeMs;\n }\n\n const apiHolderRef = useRef<PxAnimatorAPI | null>(null);\n\n // Keep the latest callback props readable by the memoised inner component.\n const callbacksRef = useRef<PixodeskSvgAnimatorCallbacks>({});\n callbacksRef.current = { onPlay, onStop, onPause, onCancel, onFinish, onRemove };\n\n // Expose the imperative API via the consumer-provided ref.\n useImperativeHandle(apiRef, () => {\n return {\n isPlaying: () => apiHolderRef.current?.isPlaying() || false,\n play: () => apiHolderRef.current?.play(),\n pause: () => apiHolderRef.current?.pause(),\n cancel: () => apiHolderRef.current?.cancel(),\n finish: () => apiHolderRef.current?.finish(),\n setPlaybackRate: (rate: number) => apiHolderRef.current?.setPlaybackRate(rate),\n getCurrentTime: () => apiHolderRef.current?.getCurrentTime() ?? null,\n setCurrentTime: (time: number) => apiHolderRef.current?.setCurrentTime(time),\n };\n }, []);\n\n // Increment key when the document, mode, or root styling changes to force\n // a full remount (the inner component is memoised and never re-renders).\n const key = useDepsVersion(compMode, doc, className, style);\n\n // Sync declarative play/pause props with the animator. Re-runs after a\n // remount (`key` in deps) so a doc swap re-applies the current state.\n useEffect(() => {\n\n if (compMode === PixodeskSvgAnimatorCompMode.play) {\n if (play && !pause) {\n apiHolderRef.current?.play();\n } else if (pause) {\n apiHolderRef.current?.pause();\n } else if (play === false) {\n // explicit play=false → jump to the end state\n apiHolderRef.current?.finish();\n } else {\n // pause-only usage: pause switched off → resume\n apiHolderRef.current?.play();\n }\n }\n\n return () => {\n if (compMode === PixodeskSvgAnimatorCompMode.play) {\n // Intentionally read at cleanup time (NOT snapshotted at effect\n // time): the inner component nulls the ref when it destroys the\n // animator, so this pauses only a still-live instance. A\n // snapshot would call pause() on a destroyed animator and emit\n // a spurious onPause after teardown.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n apiHolderRef.current?.pause();\n }\n };\n }, [compMode, play, pause, key]);\n\n // Controlled-time mode: seek through the animator API. Scrubbing `time` /\n // `timeMs` only re-runs this effect — the animator is NOT recreated.\n useEffect(() => {\n if (compMode === PixodeskSvgAnimatorCompMode.fixedTime && seekMs !== undefined) {\n apiHolderRef.current?.setCurrentTime(seekMs);\n apiHolderRef.current?.pause();\n }\n }, [compMode, seekMs, key]);\n\n return (\n <PixodeskSvgAnimatorImplOnce\n key={key}\n className={className}\n style={style}\n compMode={compMode}\n doc={doc}\n apiHolderRef={apiHolderRef}\n callbacksRef={callbacksRef}\n />\n );\n};\n\nexport default PixodeskSvgAnimator;","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { useRef } from 'react';\n\nexport function deepEquals(a: any, b: any): boolean {\n // Handle primitives and null\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== 'object' || typeof b !== 'object') return false;\n\n // Check for cycles using WeakSet\n const seen = new WeakSet();\n\n function equals(x: any, y: any): boolean {\n // Primitive/null check\n if (x === y) return true;\n if (x == null || y == null) return false;\n if (typeof x !== 'object' || typeof y !== 'object') return false;\n\n // Cycle detection\n if (seen.has(x)) {\n throw new Error('Circular reference detected');\n }\n seen.add(x);\n\n // Array check\n const xIsArray = Array.isArray(x);\n const yIsArray = Array.isArray(y);\n if (xIsArray !== yIsArray) return false;\n\n if (xIsArray) {\n if (x.length !== y.length) return false;\n for (let i = 0; i < x.length; i++) {\n if (!equals(x[i], y[i])) return false;\n }\n return true;\n }\n\n // Object check\n const xKeys = Object.keys(x);\n const yKeys = Object.keys(y);\n if (xKeys.length !== yKeys.length) return false;\n\n for (const key of xKeys) {\n if (!yKeys.includes(key)) return false;\n if (!equals(x[key], y[key])) return false;\n }\n\n return true;\n }\n\n return equals(a, b);\n}\n\n\n/**\n * Compares two arrays of dependencies using identity check first (===),\n * falling back to deep equality if references differ.\n * This is optimized for immutable data that may be recreated with same values.\n */\nfunction areDepsEqual(prevDeps: Array<any>, nextDeps: Array<any>): boolean {\n if (prevDeps.length !== nextDeps.length) return false;\n\n for (let i = 0; i < prevDeps.length; i++) {\n // Fast path: same reference (common for immutable data)\n if (prevDeps[i] === nextDeps[i]) continue;\n\n // Slow path: different reference but possibly same content\n if (!deepEquals(prevDeps[i], nextDeps[i])) return false;\n }\n\n return true;\n}\n\n/**\n * Returns a stable numeric version ID that only increments when dependencies\n * actually change (by reference or deep equality).\n */\nexport function useDepsVersion(...deps: Array<any>): number {\n const cache = useRef<{\n versionId: number;\n deps: Array<any>;\n } | null>(null);\n\n // First render - initialize with version 1\n if (cache.current === null) {\n cache.current = {\n versionId: 1,\n deps: deps\n };\n return 1;\n }\n\n // Check if any dependency changed (by reference or value)\n if (!areDepsEqual(cache.current.deps, deps)) {\n // Dependencies changed - increment version and update cache\n cache.current = {\n versionId: cache.current.versionId + 1,\n deps: deps\n };\n }\n\n return cache.current.versionId;\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { OutAction, StartOn } from \"@pixodesk/svg-animator-web\";\nimport { CSSProperties, FC, ReactNode, useEffect, useRef, useState } from \"react\";\n\n\ntype AnimState = 'idle' | 'paused' | 'playing';\n\n/**\n * Controls playback of a SVG+CSS animated files by toggling class names on a wrapper div.\n *\n * Intended for use with SVG files exported from the Pixodesk editor using the\n * **CSS Keyframes** (no `<script>` tag). Import the SVG as a React component\n * via SVGR (`vite-plugin-svgr` or `@svgr/webpack`) and pass it as `children`:\n *\n * ```tsx\n * import AnimationSvg from './animation.svg?react'; // SVGR\n *\n * <PixodeskSvgCssAnimator startOn=\"mouseOver\" outAction=\"pause\">\n * <AnimationSvg />\n * </PixodeskSvgCssAnimator>\n * ```\n *\n * The wrapper div carries one of three animation states via CSS class names:\n * - *(no class)* — idle, animation not started\n * - `px-anim-enabled` — started but paused\n * - `px-anim-enabled px-anim-playing` — actively playing\n *\n * @param children - The SVGR-imported SVG component to animate.\n * @param startOn - What triggers the animation to start:\n * - `'load'` — plays immediately on mount (default)\n * - `'mouseOver'` — plays on hover\n * - `'click'` — plays on click, toggles on second click\n * - `'scrollIntoView'` — plays when the element enters the viewport\n * @param outAction - What happens when the trigger ends (hover/scroll out, second click):\n * - `'continue'` — keeps playing (default)\n * - `'pause'` — pauses at the current frame\n * - `'reset'` — resets to the beginning\n * @param className - Additional CSS class names to apply to the wrapper div.\n * @param style - Inline styles for the wrapper div (e.g. `{ width: 400, height: 400 }`).\n */\nconst PixodeskSvgCssAnimator: FC<{\n className?: string;\n style?: CSSProperties;\n children: ReactNode;\n startOn?: StartOn;\n outAction?: OutAction;\n}> = ({ className, style, children, startOn = 'load', outAction = 'continue' }) => {\n\n const [state, setState] = useState<AnimState>(startOn === 'load' ? 'playing' : 'idle');\n\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (startOn !== 'scrollIntoView') return;\n const el = ref.current;\n if (!el) return;\n const outState: AnimState = (\n outAction === 'reset' ? 'idle' :\n outAction === 'pause' ? 'paused' : 'playing'\n );\n const observer = new IntersectionObserver(\n ([entry]) => setState(entry.isIntersecting ? 'playing' : outState),\n { threshold: 0.1 }\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [startOn, outAction]);\n\n const goOut = () => setState(\n outAction === 'reset' ? 'idle' :\n outAction === 'pause' ? 'paused' : 'playing'\n );\n\n const handlers =\n startOn === 'mouseOver' ? { onMouseEnter: () => setState('playing'), onMouseLeave: goOut } :\n startOn === 'click' ? { onClick: () => state === 'playing' ? goOut() : setState('playing') } :\n {};\n\n return (\n <div\n ref={ref}\n className={[\n className,\n state === 'playing' ? 'px-anim-enabled px-anim-playing' :\n state === 'paused' ? 'px-anim-enabled' : ''\n ].filter(Boolean).join(' ')}\n style={style}\n {...handlers}\n >\n {children}\n </div>\n );\n};\nexport default PixodeskSvgCssAnimator;"],"mappings":"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","names":["import_react","React","startOn","time","import_react","import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -12,11 +12,22 @@ interface ReactAnimatorApi {
12
12
  cancel(): void;
13
13
  /** Jumps to the end of the animation and holds the final state. */
14
14
  finish(): void;
15
+ /** Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse. */
16
+ setPlaybackRate(rate: number): void;
15
17
  /** Returns the current playback time in milliseconds. */
16
18
  getCurrentTime(): number | null;
17
19
  /** Jumps to a specific time (in milliseconds) in the animation. */
18
20
  setCurrentTime(time: number): void;
19
21
  }
22
+ /** Lifecycle callback props (subset of {@link PixodeskSvgAnimatorProps}). */
23
+ interface PixodeskSvgAnimatorCallbacks {
24
+ onPlay?: () => void;
25
+ onStop?: () => void;
26
+ onPause?: () => void;
27
+ onCancel?: () => void;
28
+ onFinish?: () => void;
29
+ onRemove?: () => void;
30
+ }
20
31
  interface PixodeskSvgAnimatorProps {
21
32
  className?: string;
22
33
  style?: CSSProperties;
@@ -27,7 +38,6 @@ interface PixodeskSvgAnimatorProps {
27
38
  * <PixodeskSvgAnimator doc="/animation.json" />
28
39
  */
29
40
  doc: PxAnimatedSvgDocument;
30
- timeline?: 'time' | 'scroll';
31
41
  /** Forces a specific rendering engine. Defaults to 'auto'. */
32
42
  mode?: JsMode;
33
43
  /** Delay before the animation starts, in milliseconds. */
@@ -59,7 +69,7 @@ interface PixodeskSvgAnimatorProps {
59
69
  pause?: boolean;
60
70
  /** Ref populated with the imperative playback API. */
61
71
  apiRef?: React.RefObject<ReactAnimatorApi | null>;
62
- /** Seek to a specific point in the animation (fractional). */
72
+ /** Seek to a specific point in the animation, as a fraction (0–1) of the whole timeline (duration × iterations). */
63
73
  time?: number;
64
74
  /** Seek to a specific point in the animation (milliseconds). */
65
75
  timeMs?: number;
@@ -75,8 +85,6 @@ interface PixodeskSvgAnimatorProps {
75
85
  onFinish?: () => void;
76
86
  /** Called when the animation is removed. */
77
87
  onRemove?: () => void;
78
- onWarning?: (msg: string) => void;
79
- onError?: (msg: string) => void;
80
88
  }
81
89
  /**
82
90
  * React component for rendering and controlling Pixodesk SVG animations.
@@ -149,4 +157,4 @@ declare const PixodeskSvgCssAnimator: FC<{
149
157
  outAction?: OutAction;
150
158
  }>;
151
159
 
152
- export { PixodeskSvgAnimator, PixodeskSvgCssAnimator, type ReactAnimatorApi };
160
+ export { PixodeskSvgAnimator, type PixodeskSvgAnimatorCallbacks, type PixodeskSvgAnimatorProps, PixodeskSvgCssAnimator, type ReactAnimatorApi };
package/dist/index.d.ts CHANGED
@@ -12,11 +12,22 @@ interface ReactAnimatorApi {
12
12
  cancel(): void;
13
13
  /** Jumps to the end of the animation and holds the final state. */
14
14
  finish(): void;
15
+ /** Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse. */
16
+ setPlaybackRate(rate: number): void;
15
17
  /** Returns the current playback time in milliseconds. */
16
18
  getCurrentTime(): number | null;
17
19
  /** Jumps to a specific time (in milliseconds) in the animation. */
18
20
  setCurrentTime(time: number): void;
19
21
  }
22
+ /** Lifecycle callback props (subset of {@link PixodeskSvgAnimatorProps}). */
23
+ interface PixodeskSvgAnimatorCallbacks {
24
+ onPlay?: () => void;
25
+ onStop?: () => void;
26
+ onPause?: () => void;
27
+ onCancel?: () => void;
28
+ onFinish?: () => void;
29
+ onRemove?: () => void;
30
+ }
20
31
  interface PixodeskSvgAnimatorProps {
21
32
  className?: string;
22
33
  style?: CSSProperties;
@@ -27,7 +38,6 @@ interface PixodeskSvgAnimatorProps {
27
38
  * <PixodeskSvgAnimator doc="/animation.json" />
28
39
  */
29
40
  doc: PxAnimatedSvgDocument;
30
- timeline?: 'time' | 'scroll';
31
41
  /** Forces a specific rendering engine. Defaults to 'auto'. */
32
42
  mode?: JsMode;
33
43
  /** Delay before the animation starts, in milliseconds. */
@@ -59,7 +69,7 @@ interface PixodeskSvgAnimatorProps {
59
69
  pause?: boolean;
60
70
  /** Ref populated with the imperative playback API. */
61
71
  apiRef?: React.RefObject<ReactAnimatorApi | null>;
62
- /** Seek to a specific point in the animation (fractional). */
72
+ /** Seek to a specific point in the animation, as a fraction (0–1) of the whole timeline (duration × iterations). */
63
73
  time?: number;
64
74
  /** Seek to a specific point in the animation (milliseconds). */
65
75
  timeMs?: number;
@@ -75,8 +85,6 @@ interface PixodeskSvgAnimatorProps {
75
85
  onFinish?: () => void;
76
86
  /** Called when the animation is removed. */
77
87
  onRemove?: () => void;
78
- onWarning?: (msg: string) => void;
79
- onError?: (msg: string) => void;
80
88
  }
81
89
  /**
82
90
  * React component for rendering and controlling Pixodesk SVG animations.
@@ -149,4 +157,4 @@ declare const PixodeskSvgCssAnimator: FC<{
149
157
  outAction?: OutAction;
150
158
  }>;
151
159
 
152
- export { PixodeskSvgAnimator, PixodeskSvgCssAnimator, type ReactAnimatorApi };
160
+ export { PixodeskSvgAnimator, type PixodeskSvgAnimatorCallbacks, type PixodeskSvgAnimatorProps, PixodeskSvgCssAnimator, type ReactAnimatorApi };
package/dist/index.js CHANGED
@@ -99,28 +99,46 @@ var PixodeskSvgAnimatorImpl = ({
99
99
  style,
100
100
  doc,
101
101
  compMode,
102
- apiHolderRef
102
+ apiHolderRef,
103
+ callbacksRef
103
104
  }) => {
104
105
  doc = generateNewIds(doc);
105
106
  const elementRefs = useRef2(/* @__PURE__ */ new Map());
106
- const renderNode = (node) => {
107
+ const renderNode = (node, isRoot = false) => {
107
108
  if (!node) return null;
108
109
  const { type, animate, meta, children, ...props } = node;
109
110
  const normProps = getNormalizedProps(props);
110
111
  normProps["ref"] = (domEl) => {
111
112
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
112
113
  };
114
+ if (isRoot) {
115
+ if (className) {
116
+ normProps["className"] = normProps["className"] ? normProps["className"] + " " + className : className;
117
+ }
118
+ if (style) normProps["style"] = style;
119
+ }
113
120
  return createElement(type, normProps, children?.map((child) => renderNode(child)));
114
121
  };
115
- const root = doc ? renderNode(doc) : null;
122
+ const root = doc ? renderNode(doc, true) : null;
116
123
  useEffect(() => {
117
- let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs) });
124
+ const cb = (name, alsoStop = false) => () => {
125
+ callbacksRef.current?.[name]?.();
126
+ if (alsoStop) callbacksRef.current?.onStop?.();
127
+ };
128
+ const callbacks = {
129
+ onPlay: cb("onPlay"),
130
+ onPause: cb("onPause", true),
131
+ onCancel: cb("onCancel", true),
132
+ onFinish: cb("onFinish", true),
133
+ onRemove: cb("onRemove", true)
134
+ };
135
+ let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs), callbacks });
118
136
  apiHolderRef.current = api;
119
137
  return () => {
120
138
  api?.destroy();
121
139
  apiHolderRef.current = null;
122
140
  };
123
- }, [doc, apiHolderRef]);
141
+ }, [doc, apiHolderRef, callbacksRef]);
124
142
  return root;
125
143
  };
126
144
  var PixodeskSvgAnimatorImplOnce = React.memo(
@@ -138,7 +156,6 @@ var PixodeskSvgAnimator = ({
138
156
  time,
139
157
  timeMs,
140
158
  apiRef,
141
- timeline,
142
159
  // Overrides
143
160
  mode,
144
161
  delay,
@@ -149,7 +166,13 @@ var PixodeskSvgAnimator = ({
149
166
  frameRate,
150
167
  startOn,
151
168
  outAction,
152
- scrollIntoViewThreshold
169
+ scrollIntoViewThreshold,
170
+ onPlay,
171
+ onStop,
172
+ onPause,
173
+ onCancel,
174
+ onFinish,
175
+ onRemove
153
176
  }) => {
154
177
  let compMode = "static" /* static */;
155
178
  if (apiRef) {
@@ -158,7 +181,7 @@ var PixodeskSvgAnimator = ({
158
181
  compMode = "autoplay" /* autoplay */;
159
182
  } else if (time !== void 0 || timeMs !== void 0) {
160
183
  compMode = "fixedTime" /* fixedTime */;
161
- } else {
184
+ } else if (play !== void 0 || pause !== void 0) {
162
185
  compMode = "play" /* play */;
163
186
  }
164
187
  if (compMode !== "autoplay" /* autoplay */) {
@@ -208,20 +231,20 @@ var PixodeskSvgAnimator = ({
208
231
  }
209
232
  };
210
233
  }
234
+ let seekMs;
211
235
  if (compMode === "fixedTime" /* fixedTime */) {
212
- let delay2 = 0;
213
- if (time !== void 0) delay2 = -time;
214
- if (timeMs !== void 0) delay2 = -timeMs;
215
236
  const animator = doc.animator || {};
216
- doc = {
217
- ...doc,
218
- animator: {
219
- ...animator,
220
- delay: delay2
221
- }
222
- };
237
+ if (time !== void 0) {
238
+ const iterationsValue = iterations ?? animator.iterations;
239
+ const iterationsCount = typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
240
+ const singleDuration = duration ?? animator.duration ?? 1e3;
241
+ seekMs = time * singleDuration * iterationsCount;
242
+ }
243
+ if (timeMs !== void 0) seekMs = timeMs;
223
244
  }
224
245
  const apiHolderRef = useRef2(null);
246
+ const callbacksRef = useRef2({});
247
+ callbacksRef.current = { onPlay, onStop, onPause, onCancel, onFinish, onRemove };
225
248
  useImperativeHandle(apiRef, () => {
226
249
  return {
227
250
  isPlaying: () => apiHolderRef.current?.isPlaying() || false,
@@ -229,18 +252,22 @@ var PixodeskSvgAnimator = ({
229
252
  pause: () => apiHolderRef.current?.pause(),
230
253
  cancel: () => apiHolderRef.current?.cancel(),
231
254
  finish: () => apiHolderRef.current?.finish(),
232
- getCurrentTime: () => apiHolderRef.current?.getCurrentTime() || null,
255
+ setPlaybackRate: (rate) => apiHolderRef.current?.setPlaybackRate(rate),
256
+ getCurrentTime: () => apiHolderRef.current?.getCurrentTime() ?? null,
233
257
  setCurrentTime: (time2) => apiHolderRef.current?.setCurrentTime(time2)
234
258
  };
235
259
  }, []);
260
+ const key = useDepsVersion(compMode, doc, className, style);
236
261
  useEffect(() => {
237
262
  if (compMode === "play" /* play */) {
238
263
  if (play && !pause) {
239
264
  apiHolderRef.current?.play();
240
265
  } else if (pause) {
241
266
  apiHolderRef.current?.pause();
242
- } else {
267
+ } else if (play === false) {
243
268
  apiHolderRef.current?.finish();
269
+ } else {
270
+ apiHolderRef.current?.play();
244
271
  }
245
272
  }
246
273
  return () => {
@@ -248,14 +275,22 @@ var PixodeskSvgAnimator = ({
248
275
  apiHolderRef.current?.pause();
249
276
  }
250
277
  };
251
- }, [compMode, play, pause]);
252
- const key = useDepsVersion(compMode, doc);
278
+ }, [compMode, play, pause, key]);
279
+ useEffect(() => {
280
+ if (compMode === "fixedTime" /* fixedTime */ && seekMs !== void 0) {
281
+ apiHolderRef.current?.setCurrentTime(seekMs);
282
+ apiHolderRef.current?.pause();
283
+ }
284
+ }, [compMode, seekMs, key]);
253
285
  return /* @__PURE__ */ jsx(
254
286
  PixodeskSvgAnimatorImplOnce,
255
287
  {
288
+ className,
289
+ style,
256
290
  compMode,
257
291
  doc,
258
- apiHolderRef
292
+ apiHolderRef,
293
+ callbacksRef
259
294
  },
260
295
  key
261
296
  );
@@ -288,7 +323,10 @@ var PixodeskSvgCssAnimator = ({ className, style, children, startOn = "load", ou
288
323
  "div",
289
324
  {
290
325
  ref,
291
- className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
326
+ className: [
327
+ className,
328
+ state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""
329
+ ].filter(Boolean).join(" "),
292
330
  style,
293
331
  ...handlers,
294
332
  children