@pixodesk/svg-animator-react 1.0.6 → 1.0.10

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
@@ -1,4 +1,8 @@
1
1
  # animator-react
2
+
3
+ [![CI](https://github.com/pixodesk/pixodesk-svg-animator/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/pixodesk/pixodesk-svg-animator/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
2
6
  React component for rendering and controlling Pixodesk SVG animations.
3
7
 
4
8
 
package/dist/index.cjs CHANGED
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- PixodeskSvgAnimator: () => PixodeskSvgAnimator_default
33
+ PixodeskSvgAnimator: () => PixodeskSvgAnimator_default,
34
+ PixodeskSvgCssAnimator: () => PixodeskSvgCssAnimator_default
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
 
@@ -144,11 +145,7 @@ var PixodeskSvgAnimatorImpl = ({
144
145
  const { type, animate, meta, children, ...props } = node;
145
146
  const normProps = (0, import_svg_animator_web.getNormalizedProps)(props);
146
147
  normProps["ref"] = (domEl) => {
147
- console.log("MOUNTED");
148
148
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
149
- return () => {
150
- console.log("UN-MOUNTED");
151
- };
152
149
  };
153
150
  return (0, import_react2.createElement)(type, normProps, children?.map((child) => renderNode(child)));
154
151
  };
@@ -160,7 +157,7 @@ var PixodeskSvgAnimatorImpl = ({
160
157
  api?.destroy();
161
158
  apiHolderRef.current = null;
162
159
  };
163
- }, [doc]);
160
+ }, [doc, apiHolderRef]);
164
161
  return root;
165
162
  };
166
163
  var PixodeskSvgAnimatorImplOnce = import_react2.default.memo(
@@ -261,7 +258,6 @@ var PixodeskSvgAnimator = ({
261
258
  }
262
259
  };
263
260
  }
264
- console.log("doc", doc);
265
261
  const apiHolderRef = (0, import_react2.useRef)(null);
266
262
  (0, import_react2.useImperativeHandle)(apiRef, () => {
267
263
  return {
@@ -302,8 +298,44 @@ var PixodeskSvgAnimator = ({
302
298
  );
303
299
  };
304
300
  var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
301
+
302
+ // src/PixodeskSvgCssAnimator.tsx
303
+ var import_react3 = require("react");
304
+ var import_jsx_runtime2 = require("react/jsx-runtime");
305
+ var PixodeskSvgCssAnimator = ({ className, style, children, startOn = "load", outAction = "continue" }) => {
306
+ const [state, setState] = (0, import_react3.useState)(startOn === "load" ? "playing" : "idle");
307
+ const ref = (0, import_react3.useRef)(null);
308
+ (0, import_react3.useEffect)(() => {
309
+ if (startOn !== "scrollIntoView") return;
310
+ const el = ref.current;
311
+ if (!el) return;
312
+ const outState = outAction === "reset" ? "idle" : outAction === "pause" ? "paused" : "playing";
313
+ const observer = new IntersectionObserver(
314
+ ([entry]) => setState(entry.isIntersecting ? "playing" : outState),
315
+ { threshold: 0.1 }
316
+ );
317
+ observer.observe(el);
318
+ return () => observer.disconnect();
319
+ }, [startOn, outAction]);
320
+ const goOut = () => setState(
321
+ outAction === "reset" ? "idle" : outAction === "pause" ? "paused" : "playing"
322
+ );
323
+ const handlers = startOn === "mouseOver" ? { onMouseEnter: () => setState("playing"), onMouseLeave: goOut } : startOn === "click" ? { onClick: () => state === "playing" ? goOut() : setState("playing") } : {};
324
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
325
+ "div",
326
+ {
327
+ ref,
328
+ className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
329
+ style,
330
+ ...handlers,
331
+ children
332
+ }
333
+ );
334
+ };
335
+ var PixodeskSvgCssAnimator_default = PixodeskSvgCssAnimator;
305
336
  // Annotate the CommonJS export names for ESM import in node:
306
337
  0 && (module.exports = {
307
- PixodeskSvgAnimator
338
+ PixodeskSvgAnimator,
339
+ PixodeskSvgCssAnimator
308
340
  });
309
341
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/PixodeskSvgAnimator.tsx","../src/Utils.ts"],"sourcesContent":["import PixodeskSvgAnimator from './PixodeskSvgAnimator';\nexport { PixodeskSvgAnimator as PixodeskSvgAnimator };\nexport type { ReactAnimatorApi } from './PixodeskSvgAnimator';","import type { PxAnimatedSvgDocument, PxAnimatorAPI, PxNode, PxPlatformAdapter, PxTrigger } 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?: 'webapi' | 'frames' | 'auto';\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?: 'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic';\n\n /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */\n outAction?: 'continue' | 'pause' | 'reset' | 'reverse';\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 console.log('MOUNTED');\n\n if (node['id']) elementRefs.current.set(node['id'], domEl);\n\n return () => {\n console.log('UN-MOUNTED');\n };\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(doc, createReactAdapter(elementRefs));\n apiHolderRef.current = api;\n\n return () => {\n api?.destroy();\n apiHolderRef.current = null;\n };\n }, [doc]);\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 console.log('doc', doc);\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;","import { 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}"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,8BAA6H;AAE7H,IAAAA,gBAA6E;;;ACH7E,mBAAuB;AAEhB,SAAS,WAAW,GAAQ,GAAiB;AAEhD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAG3D,QAAM,OAAO,oBAAI,QAAQ;AAEzB,WAAS,OAAO,GAAQ,GAAiB;AAErC,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAG3D,QAAI,KAAK,IAAI,CAAC,GAAG;AACb,YAAM,IAAI,MAAM,6BAA6B;AAAA,IACjD;AACA,SAAK,IAAI,CAAC;AAGV,UAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,UAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,QAAI,aAAa,SAAU,QAAO;AAElC,QAAI,UAAU;AACV,UAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,eAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,YAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAG,QAAO;AAAA,MACpC;AACA,aAAO;AAAA,IACX;AAGA,UAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,UAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,QAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAE1C,eAAW,OAAO,OAAO;AACrB,UAAI,CAAC,MAAM,SAAS,GAAG,EAAG,QAAO;AACjC,UAAI,CAAC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,EAAG,QAAO;AAAA,IACxC;AAEA,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,GAAG,CAAC;AACtB;AAQA,SAAS,aAAa,UAAsB,UAA+B;AACvE,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAEhD,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AAEtC,QAAI,SAAS,CAAC,MAAM,SAAS,CAAC,EAAG;AAGjC,QAAI,CAAC,WAAW,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC,EAAG,QAAO;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,kBAAkB,MAA0B;AACxD,QAAM,YAAQ,qBAGJ,IAAI;AAGd,MAAI,MAAM,YAAY,MAAM;AACxB,UAAM,UAAU;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAGA,MAAI,CAAC,aAAa,MAAM,QAAQ,MAAM,IAAI,GAAG;AAEzC,UAAM,UAAU;AAAA,MACZ,WAAW,MAAM,QAAQ,YAAY;AAAA,MACrC;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO,MAAM,QAAQ;AACzB;;;ADkVQ;AA9QD,SAAS,mBAAmB,aAAgD;AAC/E,QAAM,kBAAkB,oBAAI,IAAY;AAExC,QAAM,UAA6B;AAAA,IAC/B,aAAa,MAAM;AACf,aAAO;AAAA,IACX;AAAA,IACA,cAAc,CAAC,IAAI,UAAU,UAAU;AAEnC,qBAAW,sDAA6B,QAAQ;AAEhD,YAAM,WAAW,YAAY,EAAE;AAE/B,YAAM,UAAU,YAAY,QAAQ,IAAI,EAAE;AAE1C,UAAI,CAAC,WAAW,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AAC5C,wBAAgB,IAAI,QAAQ;AAC5B,gBAAQ,KAAK,mDAAmD,WAAW,GAAG;AAC9E,gBAAQ,KAAK,YAAY,OAAO;AAAA,MACpC;AAEA,UAAI,SAAS;AACT,gBAAQ,aAAa,UAAU,KAAK;AACpC,YAAI,yCAAiB,IAAI,QAAQ,GAAG;AAChC,UAAC,QAAwB,MAAM,QAAe,IAAI;AAAA,QACtD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAEO,SAAS,YAAY,IAAY;AACpC,SAAO,MAAM;AACjB;AAOA,IAAM,0BAA4D,CAAC;AAAA,EAC/D;AAAA,EAAW;AAAA,EAAO;AAAA,EAAK;AAAA,EAAU;AACrC,MAAM;AAEF,YAAM,wCAAe,GAAG;AAExB,QAAM,kBAAc,sBAAO,oBAAI,IAAiB,CAAC;AAEjD,QAAM,aAAa,CAAC,SAAkD;AAClE,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,MAAM,IAAI;AAEpD,UAAM,gBAAY,4CAAmB,KAAK;AAE1C,cAAU,KAAK,IAAI,CAAC,UAAe;AAC/B,cAAQ,IAAI,SAAS;AAErB,UAAI,KAAK,IAAI,EAAG,aAAY,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK;AAEzD,aAAO,MAAM;AACT,gBAAQ,IAAI,YAAY;AAAA,MAC5B;AAAA,IACJ;AAEA,eAAO,6BAAc,MAAM,WAAW,UAAU,IAAI,WAAS,WAAW,KAAK,CAAC,CAAC;AAAA,EACnF;AAEA,QAAM,OAAO,MAAM,WAAW,GAAG,IAAI;AAGrC,+BAAU,MAAM;AAEZ,QAAI,UAAiC,wCAAe,KAAK,mBAAmB,WAAW,CAAC;AACxF,iBAAa,UAAU;AAEvB,WAAO,MAAM;AACT,WAAK,QAAQ;AACb,mBAAa,UAAU;AAAA,IAC3B;AAAA,EACJ,GAAG,CAAC,GAAG,CAAC;AAER,SAAO;AACX;AAGA,IAAM,8BAA8B,cAAAC,QAAM;AAAA,EACtC;AAAA,EACA,MAAM;AAAA;AACV;AAiCA,IAAM,sBAAoD,CAAC;AAAA,EACvD;AAAA,EAAW;AAAA,EACX;AAAA,EAAK;AAAA,EAAU;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM;AAAA,EAAQ;AAAA,EAC1C;AAAA;AAAA,EAGA;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EAEpD;AAAA,EAAS;AAAA,EAAW;AACxB,MAAM;AAGF,MAAI,WAAW;AACf,MAAI,QAAQ;AACR,eAAW;AAAA,EACf,WAAW,UAAU;AACjB,eAAW;AAAA,EACf,WAAW,SAAS,UAAa,WAAW,QAAW;AACnD,eAAW;AAAA,EACf,OAAO;AACH,eAAW;AAAA,EACf;AAIA,MAAI,aAAa,2BAAsC;AACnD,UAAMC,WAAU,IAAI,UAAU,SAAS;AACvC,QACIA,YACAA,aAAY,gBACd;AACE,YAAM;AAAA,QACF,GAAG;AAAA,QACH,UAAU;AAAA,UACN,GAAG,IAAI;AAAA,UACP,SAAS;AAAA,YACL,GAAG,IAAI,UAAU;AAAA,YACjB,SAAS;AAAA;AAAA,UACb;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MACI,SAAS,UACT,aAAa,UACb,UAAU,UACV,eAAe,UACf,SAAS,UACT,cAAc,UACd,cAAc,QAChB;AACE,UAAM,WAAW,IAAI,YAAY,CAAC;AAClC,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG;AAAA,QACH,MAAM,SAAS,SAAY,OAAO,SAAS;AAAA,QAC3C,UAAU,aAAa,SAAY,WAAW,SAAS;AAAA,QACvD,OAAO,UAAU,SAAY,QAAQ,SAAS;AAAA,QAC9C,YAAY,eAAe,SAAY,aAAa,SAAS;AAAA,QAC7D,MAAM,SAAS,SAAY,OAAO,SAAS;AAAA,QAC3C,WAAW,cAAc,SAAY,YAAY,SAAS;AAAA,QAC1D,WAAW,cAAc,SAAY,YAAY,SAAS;AAAA,MAC9D;AAAA,IACJ;AAAA,EACJ;AAGA,MACI,YAAY,UACZ,cAAc,UACd,4BAA4B,QAC9B;AACE,UAAM,UAAqB,IAAI,UAAU,WAAW,CAAC;AACrD,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG,IAAI;AAAA,QACP,SAAS;AAAA,UACL,GAAG;AAAA,UACH,SAAS,YAAY,SAAY,UAAU,QAAQ;AAAA,UACnD,WAAW,cAAc,SAAY,YAAY,QAAQ;AAAA,UACzD,yBAAyB,4BAA4B,SAAY,0BAA0B,QAAQ;AAAA,QACvG;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,aAAa,6BAAuC;AACpD,QAAIC,SAAQ;AACZ,QAAI,SAAS,OAAW,CAAAA,SAAQ,CAAC;AACjC,QAAI,WAAW,OAAW,CAAAA,SAAQ,CAAC;AACnC,UAAM,WAAW,IAAI,YAAY,CAAC;AAClC,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG;AAAA,QACH,OAAAA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,UAAQ,IAAI,OAAO,GAAG;AAEtB,QAAM,mBAAe,sBAA6B,IAAI;AAGtD,yCAAoB,QAAQ,MAAM;AAC9B,WAAO;AAAA,MACH,WAAW,MAAM,aAAa,SAAS,UAAU,KAAK;AAAA,MACtD,MAAM,MAAM,aAAa,SAAS,KAAK;AAAA,MACvC,OAAO,MAAM,aAAa,SAAS,MAAM;AAAA,MACzC,QAAQ,MAAM,aAAa,SAAS,OAAO;AAAA,MAC3C,QAAQ,MAAM,aAAa,SAAS,OAAO;AAAA,MAC3C,gBAAgB,MAAM,aAAa,SAAS,eAAe,KAAK;AAAA,MAChE,gBAAgB,CAACC,UAAiB,aAAa,SAAS,eAAeA,KAAI;AAAA,IAC/E;AAAA,EACJ,GAAG,CAAC,CAAC;AAGL,+BAAU,MAAM;AAEZ,QAAI,aAAa,mBAAkC;AAC/C,UAAI,QAAQ,CAAC,OAAO;AAChB,qBAAa,SAAS,KAAK;AAAA,MAC/B,WAAW,OAAO;AACd,qBAAa,SAAS,MAAM;AAAA,MAChC,OAAO;AACH,qBAAa,SAAS,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,WAAO,MAAM;AACT,UAAI,aAAa,mBAAkC;AAC/C,qBAAa,SAAS,MAAM;AAAA,MAChC;AAAA,IACJ;AAAA,EACJ,GAAG,CAAC,UAAU,MAAM,KAAK,CAAC;AAG1B,QAAM,MAAM,eAAe,UAAU,GAAG;AAExC,SACI;AAAA,IAAC;AAAA;AAAA,MAEG;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IAHK;AAAA,EAIT;AAER;AAEA,IAAO,8BAAQ;","names":["import_react","React","startOn","delay","time"]}
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(doc, 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMA,8BAA6H;AAE7H,IAAAA,gBAA6E;;;ACH7E,mBAAuB;AAEhB,SAAS,WAAW,GAAQ,GAAiB;AAEhD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAG3D,QAAM,OAAO,oBAAI,QAAQ;AAEzB,WAAS,OAAO,GAAQ,GAAiB;AAErC,QAAI,MAAM,EAAG,QAAO;AACpB,QAAI,KAAK,QAAQ,KAAK,KAAM,QAAO;AACnC,QAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAG3D,QAAI,KAAK,IAAI,CAAC,GAAG;AACb,YAAM,IAAI,MAAM,6BAA6B;AAAA,IACjD;AACA,SAAK,IAAI,CAAC;AAGV,UAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,UAAM,WAAW,MAAM,QAAQ,CAAC;AAChC,QAAI,aAAa,SAAU,QAAO;AAElC,QAAI,UAAU;AACV,UAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,eAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,YAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAG,QAAO;AAAA,MACpC;AACA,aAAO;AAAA,IACX;AAGA,UAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,UAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,QAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAE1C,eAAW,OAAO,OAAO;AACrB,UAAI,CAAC,MAAM,SAAS,GAAG,EAAG,QAAO;AACjC,UAAI,CAAC,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,EAAG,QAAO;AAAA,IACxC;AAEA,WAAO;AAAA,EACX;AAEA,SAAO,OAAO,GAAG,CAAC;AACtB;AAQA,SAAS,aAAa,UAAsB,UAA+B;AACvE,MAAI,SAAS,WAAW,SAAS,OAAQ,QAAO;AAEhD,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AAEtC,QAAI,SAAS,CAAC,MAAM,SAAS,CAAC,EAAG;AAGjC,QAAI,CAAC,WAAW,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC,EAAG,QAAO;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,kBAAkB,MAA0B;AACxD,QAAM,YAAQ,qBAGJ,IAAI;AAGd,MAAI,MAAM,YAAY,MAAM;AACxB,UAAM,UAAU;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAGA,MAAI,CAAC,aAAa,MAAM,QAAQ,MAAM,IAAI,GAAG;AAEzC,UAAM,UAAU;AAAA,MACZ,WAAW,MAAM,QAAQ,YAAY;AAAA,MACrC;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO,MAAM,QAAQ;AACzB;;;AD2UQ;AAvQD,SAAS,mBAAmB,aAAgD;AAC/E,QAAM,kBAAkB,oBAAI,IAAY;AAExC,QAAM,UAA6B;AAAA,IAC/B,aAAa,MAAM;AACf,aAAO;AAAA,IACX;AAAA,IACA,cAAc,CAAC,IAAI,UAAU,UAAU;AAEnC,qBAAW,sDAA6B,QAAQ;AAEhD,YAAM,WAAW,YAAY,EAAE;AAE/B,YAAM,UAAU,YAAY,QAAQ,IAAI,EAAE;AAE1C,UAAI,CAAC,WAAW,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AAC5C,wBAAgB,IAAI,QAAQ;AAC5B,gBAAQ,KAAK,mDAAmD,WAAW,GAAG;AAC9E,gBAAQ,KAAK,YAAY,OAAO;AAAA,MACpC;AAEA,UAAI,SAAS;AACT,gBAAQ,aAAa,UAAU,KAAK;AACpC,YAAI,yCAAiB,IAAI,QAAQ,GAAG;AAChC,UAAC,QAAwB,MAAM,QAAe,IAAI;AAAA,QACtD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;AAEO,SAAS,YAAY,IAAY;AACpC,SAAO,MAAM;AACjB;AAOA,IAAM,0BAA4D,CAAC;AAAA,EAC/D;AAAA,EAAW;AAAA,EAAO;AAAA,EAAK;AAAA,EAAU;AACrC,MAAM;AAEF,YAAM,wCAAe,GAAG;AAExB,QAAM,kBAAc,sBAAO,oBAAI,IAAiB,CAAC;AAEjD,QAAM,aAAa,CAAC,SAAkD;AAClE,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,EAAE,MAAM,SAAS,MAAM,UAAU,GAAG,MAAM,IAAI;AAEpD,UAAM,gBAAY,4CAAmB,KAAK;AAE1C,cAAU,KAAK,IAAI,CAAC,UAAe;AAC/B,UAAI,KAAK,IAAI,EAAG,aAAY,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK;AAAA,IAE7D;AAEA,eAAO,6BAAc,MAAM,WAAW,UAAU,IAAI,WAAS,WAAW,KAAK,CAAC,CAAC;AAAA,EACnF;AAEA,QAAM,OAAO,MAAM,WAAW,GAAG,IAAI;AAGrC,+BAAU,MAAM;AAEZ,QAAI,UAAiC,wCAAe,KAAK,mBAAmB,WAAW,CAAC;AACxF,iBAAa,UAAU;AAEvB,WAAO,MAAM;AACT,WAAK,QAAQ;AACb,mBAAa,UAAU;AAAA,IAC3B;AAAA,EACJ,GAAG,CAAC,KAAK,YAAY,CAAC;AAEtB,SAAO;AACX;AAGA,IAAM,8BAA8B,cAAAC,QAAM;AAAA,EACtC;AAAA,EACA,MAAM;AAAA;AACV;AAiCA,IAAM,sBAAoD,CAAC;AAAA,EACvD;AAAA,EAAW;AAAA,EACX;AAAA,EAAK;AAAA,EAAU;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM;AAAA,EAAQ;AAAA,EAC1C;AAAA;AAAA,EAGA;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM;AAAA,EAAY;AAAA,EAAU;AAAA,EAAW;AAAA,EAEpD;AAAA,EAAS;AAAA,EAAW;AACxB,MAAM;AAGF,MAAI,WAAW;AACf,MAAI,QAAQ;AACR,eAAW;AAAA,EACf,WAAW,UAAU;AACjB,eAAW;AAAA,EACf,WAAW,SAAS,UAAa,WAAW,QAAW;AACnD,eAAW;AAAA,EACf,OAAO;AACH,eAAW;AAAA,EACf;AAIA,MAAI,aAAa,2BAAsC;AACnD,UAAMC,WAAU,IAAI,UAAU,SAAS;AACvC,QACIA,YACAA,aAAY,gBACd;AACE,YAAM;AAAA,QACF,GAAG;AAAA,QACH,UAAU;AAAA,UACN,GAAG,IAAI;AAAA,UACP,SAAS;AAAA,YACL,GAAG,IAAI,UAAU;AAAA,YACjB,SAAS;AAAA;AAAA,UACb;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MACI,SAAS,UACT,aAAa,UACb,UAAU,UACV,eAAe,UACf,SAAS,UACT,cAAc,UACd,cAAc,QAChB;AACE,UAAM,WAAW,IAAI,YAAY,CAAC;AAClC,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG;AAAA,QACH,MAAM,SAAS,SAAY,OAAO,SAAS;AAAA,QAC3C,UAAU,aAAa,SAAY,WAAW,SAAS;AAAA,QACvD,OAAO,UAAU,SAAY,QAAQ,SAAS;AAAA,QAC9C,YAAY,eAAe,SAAY,aAAa,SAAS;AAAA,QAC7D,MAAM,SAAS,SAAY,OAAO,SAAS;AAAA,QAC3C,WAAW,cAAc,SAAY,YAAY,SAAS;AAAA,QAC1D,WAAW,cAAc,SAAY,YAAY,SAAS;AAAA,MAC9D;AAAA,IACJ;AAAA,EACJ;AAGA,MACI,YAAY,UACZ,cAAc,UACd,4BAA4B,QAC9B;AACE,UAAM,UAAqB,IAAI,UAAU,WAAW,CAAC;AACrD,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG,IAAI;AAAA,QACP,SAAS;AAAA,UACL,GAAG;AAAA,UACH,SAAS,YAAY,SAAY,UAAU,QAAQ;AAAA,UACnD,WAAW,cAAc,SAAY,YAAY,QAAQ;AAAA,UACzD,yBAAyB,4BAA4B,SAAY,0BAA0B,QAAQ;AAAA,QACvG;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,aAAa,6BAAuC;AACpD,QAAIC,SAAQ;AACZ,QAAI,SAAS,OAAW,CAAAA,SAAQ,CAAC;AACjC,QAAI,WAAW,OAAW,CAAAA,SAAQ,CAAC;AACnC,UAAM,WAAW,IAAI,YAAY,CAAC;AAClC,UAAM;AAAA,MACF,GAAG;AAAA,MACH,UAAU;AAAA,QACN,GAAG;AAAA,QACH,OAAAA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,mBAAe,sBAA6B,IAAI;AAGtD,yCAAoB,QAAQ,MAAM;AAC9B,WAAO;AAAA,MACH,WAAW,MAAM,aAAa,SAAS,UAAU,KAAK;AAAA,MACtD,MAAM,MAAM,aAAa,SAAS,KAAK;AAAA,MACvC,OAAO,MAAM,aAAa,SAAS,MAAM;AAAA,MACzC,QAAQ,MAAM,aAAa,SAAS,OAAO;AAAA,MAC3C,QAAQ,MAAM,aAAa,SAAS,OAAO;AAAA,MAC3C,gBAAgB,MAAM,aAAa,SAAS,eAAe,KAAK;AAAA,MAChE,gBAAgB,CAACC,UAAiB,aAAa,SAAS,eAAeA,KAAI;AAAA,IAC/E;AAAA,EACJ,GAAG,CAAC,CAAC;AAGL,+BAAU,MAAM;AAEZ,QAAI,aAAa,mBAAkC;AAC/C,UAAI,QAAQ,CAAC,OAAO;AAChB,qBAAa,SAAS,KAAK;AAAA,MAC/B,WAAW,OAAO;AACd,qBAAa,SAAS,MAAM;AAAA,MAChC,OAAO;AACH,qBAAa,SAAS,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,WAAO,MAAM;AACT,UAAI,aAAa,mBAAkC;AAC/C,qBAAa,SAAS,MAAM;AAAA,MAChC;AAAA,IACJ;AAAA,EACJ,GAAG,CAAC,UAAU,MAAM,KAAK,CAAC;AAG1B,QAAM,MAAM,eAAe,UAAU,GAAG;AAExC,SACI;AAAA,IAAC;AAAA;AAAA,MAEG;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IAHK;AAAA,EAIT;AAER;AAEA,IAAO,8BAAQ;;;AExbf,IAAAC,gBAA0E;AA6ElE,IAAAC,sBAAA;AAvCR,IAAM,yBAMD,CAAC,EAAE,WAAW,OAAO,UAAU,UAAU,QAAQ,YAAY,WAAW,MAAM;AAE/E,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAoB,YAAY,SAAS,YAAY,MAAM;AAErF,QAAM,UAAM,sBAAuB,IAAI;AAEvC,+BAAU,MAAM;AACZ,QAAI,YAAY,iBAAkB;AAClC,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,WACF,cAAc,UAAU,SACpB,cAAc,UAAU,WAAW;AAE3C,UAAM,WAAW,IAAI;AAAA,MACjB,CAAC,CAAC,KAAK,MAAM,SAAS,MAAM,iBAAiB,YAAY,QAAQ;AAAA,MACjE,EAAE,WAAW,IAAI;AAAA,IACrB;AACA,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACrC,GAAG,CAAC,SAAS,SAAS,CAAC;AAEvB,QAAM,QAAQ,MAAM;AAAA,IAChB,cAAc,UAAU,SACpB,cAAc,UAAU,WAAW;AAAA,EAC3C;AAEA,QAAM,WACF,YAAY,cAAc,EAAE,cAAc,MAAM,SAAS,SAAS,GAAG,cAAc,MAAM,IACrF,YAAY,UAAU,EAAE,SAAS,MAAM,UAAU,YAAY,MAAM,IAAI,SAAS,SAAS,EAAE,IACvF,CAAC;AAEb,SACI;AAAA,IAAC;AAAA;AAAA,MACG;AAAA,MACA,WAAW,aACP,UAAU,YAAY,oCAClB,UAAU,WAAW,oBAAoB;AAAA,MAEjD;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACL;AAER;AACA,IAAO,iCAAQ;","names":["import_react","React","startOn","delay","time","import_react","import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { PxAnimatedSvgDocument, FillMode } from '@pixodesk/svg-animator-web';
2
- import React, { FC, CSSProperties } from 'react';
1
+ import { PxAnimatedSvgDocument, JsMode, FillMode, StartOn, OutAction } from '@pixodesk/svg-animator-web';
2
+ import React, { FC, CSSProperties, ReactNode } from 'react';
3
3
 
4
4
  interface ReactAnimatorApi {
5
5
  /** Returns true if the animation is currently running. */
@@ -29,7 +29,7 @@ interface PixodeskSvgAnimatorProps {
29
29
  doc: PxAnimatedSvgDocument;
30
30
  timeline?: 'time' | 'scroll';
31
31
  /** Forces a specific rendering engine. Defaults to 'auto'. */
32
- mode?: 'webapi' | 'frames' | 'auto';
32
+ mode?: JsMode;
33
33
  /** Delay before the animation starts, in milliseconds. */
34
34
  delay?: number;
35
35
  /** Defines the element's style when the animation is not active. */
@@ -43,9 +43,9 @@ interface PixodeskSvgAnimatorProps {
43
43
  /** Target frame rate (frames per second). */
44
44
  frameRate?: number;
45
45
  /** The event that starts the animation. When omitted, uses the value from the document config. */
46
- startOn?: 'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic';
46
+ startOn?: StartOn;
47
47
  /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */
48
- outAction?: 'continue' | 'pause' | 'reset' | 'reverse';
48
+ outAction?: OutAction;
49
49
  /** Visibility ratio (0.0–1.0) required to trigger a scrollIntoView animation. Defaults to 0.5. */
50
50
  scrollIntoViewThreshold?: number;
51
51
  /** When true, uses triggers defined in the animation document. */
@@ -108,4 +108,45 @@ interface PixodeskSvgAnimatorProps {
108
108
  */
109
109
  declare const PixodeskSvgAnimator: FC<PixodeskSvgAnimatorProps>;
110
110
 
111
- export { PixodeskSvgAnimator, type ReactAnimatorApi };
111
+ /**
112
+ * Controls playback of a SVG+CSS animated files by toggling class names on a wrapper div.
113
+ *
114
+ * Intended for use with SVG files exported from the Pixodesk editor using the
115
+ * **CSS Keyframes** (no `<script>` tag). Import the SVG as a React component
116
+ * via SVGR (`vite-plugin-svgr` or `@svgr/webpack`) and pass it as `children`:
117
+ *
118
+ * ```tsx
119
+ * import AnimationSvg from './animation.svg?react'; // SVGR
120
+ *
121
+ * <PixodeskSvgCssAnimator startOn="mouseOver" outAction="pause">
122
+ * <AnimationSvg />
123
+ * </PixodeskSvgCssAnimator>
124
+ * ```
125
+ *
126
+ * The wrapper div carries one of three animation states via CSS class names:
127
+ * - *(no class)* — idle, animation not started
128
+ * - `px-anim-enabled` — started but paused
129
+ * - `px-anim-enabled px-anim-playing` — actively playing
130
+ *
131
+ * @param children - The SVGR-imported SVG component to animate.
132
+ * @param startOn - What triggers the animation to start:
133
+ * - `'load'` — plays immediately on mount (default)
134
+ * - `'mouseOver'` — plays on hover
135
+ * - `'click'` — plays on click, toggles on second click
136
+ * - `'scrollIntoView'` — plays when the element enters the viewport
137
+ * @param outAction - What happens when the trigger ends (hover/scroll out, second click):
138
+ * - `'continue'` — keeps playing (default)
139
+ * - `'pause'` — pauses at the current frame
140
+ * - `'reset'` — resets to the beginning
141
+ * @param className - Additional CSS class names to apply to the wrapper div.
142
+ * @param style - Inline styles for the wrapper div (e.g. `{ width: 400, height: 400 }`).
143
+ */
144
+ declare const PixodeskSvgCssAnimator: FC<{
145
+ className?: string;
146
+ style?: CSSProperties;
147
+ children: ReactNode;
148
+ startOn?: StartOn;
149
+ outAction?: OutAction;
150
+ }>;
151
+
152
+ export { PixodeskSvgAnimator, PixodeskSvgCssAnimator, type ReactAnimatorApi };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { PxAnimatedSvgDocument, FillMode } from '@pixodesk/svg-animator-web';
2
- import React, { FC, CSSProperties } from 'react';
1
+ import { PxAnimatedSvgDocument, JsMode, FillMode, StartOn, OutAction } from '@pixodesk/svg-animator-web';
2
+ import React, { FC, CSSProperties, ReactNode } from 'react';
3
3
 
4
4
  interface ReactAnimatorApi {
5
5
  /** Returns true if the animation is currently running. */
@@ -29,7 +29,7 @@ interface PixodeskSvgAnimatorProps {
29
29
  doc: PxAnimatedSvgDocument;
30
30
  timeline?: 'time' | 'scroll';
31
31
  /** Forces a specific rendering engine. Defaults to 'auto'. */
32
- mode?: 'webapi' | 'frames' | 'auto';
32
+ mode?: JsMode;
33
33
  /** Delay before the animation starts, in milliseconds. */
34
34
  delay?: number;
35
35
  /** Defines the element's style when the animation is not active. */
@@ -43,9 +43,9 @@ interface PixodeskSvgAnimatorProps {
43
43
  /** Target frame rate (frames per second). */
44
44
  frameRate?: number;
45
45
  /** The event that starts the animation. When omitted, uses the value from the document config. */
46
- startOn?: 'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic';
46
+ startOn?: StartOn;
47
47
  /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */
48
- outAction?: 'continue' | 'pause' | 'reset' | 'reverse';
48
+ outAction?: OutAction;
49
49
  /** Visibility ratio (0.0–1.0) required to trigger a scrollIntoView animation. Defaults to 0.5. */
50
50
  scrollIntoViewThreshold?: number;
51
51
  /** When true, uses triggers defined in the animation document. */
@@ -108,4 +108,45 @@ interface PixodeskSvgAnimatorProps {
108
108
  */
109
109
  declare const PixodeskSvgAnimator: FC<PixodeskSvgAnimatorProps>;
110
110
 
111
- export { PixodeskSvgAnimator, type ReactAnimatorApi };
111
+ /**
112
+ * Controls playback of a SVG+CSS animated files by toggling class names on a wrapper div.
113
+ *
114
+ * Intended for use with SVG files exported from the Pixodesk editor using the
115
+ * **CSS Keyframes** (no `<script>` tag). Import the SVG as a React component
116
+ * via SVGR (`vite-plugin-svgr` or `@svgr/webpack`) and pass it as `children`:
117
+ *
118
+ * ```tsx
119
+ * import AnimationSvg from './animation.svg?react'; // SVGR
120
+ *
121
+ * <PixodeskSvgCssAnimator startOn="mouseOver" outAction="pause">
122
+ * <AnimationSvg />
123
+ * </PixodeskSvgCssAnimator>
124
+ * ```
125
+ *
126
+ * The wrapper div carries one of three animation states via CSS class names:
127
+ * - *(no class)* — idle, animation not started
128
+ * - `px-anim-enabled` — started but paused
129
+ * - `px-anim-enabled px-anim-playing` — actively playing
130
+ *
131
+ * @param children - The SVGR-imported SVG component to animate.
132
+ * @param startOn - What triggers the animation to start:
133
+ * - `'load'` — plays immediately on mount (default)
134
+ * - `'mouseOver'` — plays on hover
135
+ * - `'click'` — plays on click, toggles on second click
136
+ * - `'scrollIntoView'` — plays when the element enters the viewport
137
+ * @param outAction - What happens when the trigger ends (hover/scroll out, second click):
138
+ * - `'continue'` — keeps playing (default)
139
+ * - `'pause'` — pauses at the current frame
140
+ * - `'reset'` — resets to the beginning
141
+ * @param className - Additional CSS class names to apply to the wrapper div.
142
+ * @param style - Inline styles for the wrapper div (e.g. `{ width: 400, height: 400 }`).
143
+ */
144
+ declare const PixodeskSvgCssAnimator: FC<{
145
+ className?: string;
146
+ style?: CSSProperties;
147
+ children: ReactNode;
148
+ startOn?: StartOn;
149
+ outAction?: OutAction;
150
+ }>;
151
+
152
+ export { PixodeskSvgAnimator, PixodeskSvgCssAnimator, type ReactAnimatorApi };
package/dist/index.js CHANGED
@@ -108,11 +108,7 @@ var PixodeskSvgAnimatorImpl = ({
108
108
  const { type, animate, meta, children, ...props } = node;
109
109
  const normProps = getNormalizedProps(props);
110
110
  normProps["ref"] = (domEl) => {
111
- console.log("MOUNTED");
112
111
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
113
- return () => {
114
- console.log("UN-MOUNTED");
115
- };
116
112
  };
117
113
  return createElement(type, normProps, children?.map((child) => renderNode(child)));
118
114
  };
@@ -124,7 +120,7 @@ var PixodeskSvgAnimatorImpl = ({
124
120
  api?.destroy();
125
121
  apiHolderRef.current = null;
126
122
  };
127
- }, [doc]);
123
+ }, [doc, apiHolderRef]);
128
124
  return root;
129
125
  };
130
126
  var PixodeskSvgAnimatorImplOnce = React.memo(
@@ -225,7 +221,6 @@ var PixodeskSvgAnimator = ({
225
221
  }
226
222
  };
227
223
  }
228
- console.log("doc", doc);
229
224
  const apiHolderRef = useRef2(null);
230
225
  useImperativeHandle(apiRef, () => {
231
226
  return {
@@ -266,7 +261,43 @@ var PixodeskSvgAnimator = ({
266
261
  );
267
262
  };
268
263
  var PixodeskSvgAnimator_default = PixodeskSvgAnimator;
264
+
265
+ // src/PixodeskSvgCssAnimator.tsx
266
+ import { useEffect as useEffect2, useRef as useRef3, useState } from "react";
267
+ import { jsx as jsx2 } from "react/jsx-runtime";
268
+ var PixodeskSvgCssAnimator = ({ className, style, children, startOn = "load", outAction = "continue" }) => {
269
+ const [state, setState] = useState(startOn === "load" ? "playing" : "idle");
270
+ const ref = useRef3(null);
271
+ useEffect2(() => {
272
+ if (startOn !== "scrollIntoView") return;
273
+ const el = ref.current;
274
+ if (!el) return;
275
+ const outState = outAction === "reset" ? "idle" : outAction === "pause" ? "paused" : "playing";
276
+ const observer = new IntersectionObserver(
277
+ ([entry]) => setState(entry.isIntersecting ? "playing" : outState),
278
+ { threshold: 0.1 }
279
+ );
280
+ observer.observe(el);
281
+ return () => observer.disconnect();
282
+ }, [startOn, outAction]);
283
+ const goOut = () => setState(
284
+ outAction === "reset" ? "idle" : outAction === "pause" ? "paused" : "playing"
285
+ );
286
+ const handlers = startOn === "mouseOver" ? { onMouseEnter: () => setState("playing"), onMouseLeave: goOut } : startOn === "click" ? { onClick: () => state === "playing" ? goOut() : setState("playing") } : {};
287
+ return /* @__PURE__ */ jsx2(
288
+ "div",
289
+ {
290
+ ref,
291
+ className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
292
+ style,
293
+ ...handlers,
294
+ children
295
+ }
296
+ );
297
+ };
298
+ var PixodeskSvgCssAnimator_default = PixodeskSvgCssAnimator;
269
299
  export {
270
- PixodeskSvgAnimator_default as PixodeskSvgAnimator
300
+ PixodeskSvgAnimator_default as PixodeskSvgAnimator,
301
+ PixodeskSvgCssAnimator_default as PixodeskSvgCssAnimator
271
302
  };
272
303
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/PixodeskSvgAnimator.tsx","../src/Utils.ts"],"sourcesContent":["import type { PxAnimatedSvgDocument, PxAnimatorAPI, PxNode, PxPlatformAdapter, PxTrigger } 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?: 'webapi' | 'frames' | 'auto';\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?: 'load' | 'mouseOver' | 'click' | 'scrollIntoView' | 'programmatic';\n\n /** Behaviour when the trigger condition ends (e.g. mouse-out, second click). */\n outAction?: 'continue' | 'pause' | 'reset' | 'reverse';\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 console.log('MOUNTED');\n\n if (node['id']) elementRefs.current.set(node['id'], domEl);\n\n return () => {\n console.log('UN-MOUNTED');\n };\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(doc, createReactAdapter(elementRefs));\n apiHolderRef.current = api;\n\n return () => {\n api?.destroy();\n apiHolderRef.current = null;\n };\n }, [doc]);\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 console.log('doc', doc);\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;","import { 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}"],"mappings":"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","names":["useRef","useRef","startOn","delay","time"]}
1
+ {"version":3,"sources":["../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 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(doc, 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":["useRef","useRef","startOn","delay","time","useEffect","useRef","jsx"]}