@pixodesk/svg-animator-web 1.0.43 → 1.0.45

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.
@@ -58,7 +58,9 @@ var PixodeskAnimator = (() => {
58
58
  PxTimelineEngineSetting: () => PxTimelineEngineSetting,
59
59
  createAnimator: () => createAnimator,
60
60
  generateNewIds: () => generateNewIds,
61
+ getAllAnimators: () => getAllAnimators,
61
62
  loadTagAnimators: () => loadTagAnimators,
63
+ onAnimatorsChange: () => onAnimatorsChange,
62
64
  setupAnimationTriggers: () => setupAnimationTriggers,
63
65
  validateDocument: () => validateDocument
64
66
  });
@@ -571,7 +573,7 @@ var PixodeskAnimator = (() => {
571
573
  };
572
574
 
573
575
  // ../svg-animator-core/src/version/PxSchemaVersion.ts
574
- var PX_WIRE_SCHEMA_VERSION = "1.1";
576
+ var PX_WIRE_SCHEMA_VERSION = "1.2";
575
577
 
576
578
  // ../svg-animator-core/src/version/PxWireVersion.ts
577
579
  var VERSION_RE = /^(\d+)\.(\d+)(?:\.(\d+))?$/;
@@ -599,14 +601,18 @@ var PixodeskAnimator = (() => {
599
601
  };
600
602
  var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
601
603
  var PX_ANIM_ATTR_NAME = "_px_animator";
602
- var PxStartOn = {
604
+ var PxTriggerStart = {
603
605
  load: "load",
604
606
  mouseOver: "mouseOver",
605
607
  click: "click",
606
- scrollIntoView: "scrollIntoView",
607
- programmatic: "programmatic"
608
+ none: "none"
608
609
  };
609
- var PxOutAction = {
610
+ var PxOffScreenAction = {
611
+ pause: "pause",
612
+ continue: "continue",
613
+ reset: "reset"
614
+ };
615
+ var PxMouseOutAction = {
610
616
  continue: "continue",
611
617
  pause: "pause",
612
618
  reset: "reset",
@@ -674,16 +680,20 @@ var PixodeskAnimator = (() => {
674
680
  return engine !== PxTimelineEngineSetting.js;
675
681
  }
676
682
  var PX_TRIGGER_DEFAULTS = {
677
- startOn: "load",
678
- outAction: "continue",
679
- scrollIntoViewThreshold: 0
683
+ start: "load",
684
+ offScreen: "pause",
685
+ mouseOut: "continue",
686
+ visibilityThreshold: 0.5,
687
+ visibilityDebounce: 150
680
688
  };
681
689
  function resolveTrigger(trigger) {
682
- var _a2, _b, _c;
690
+ var _a2, _b, _c, _d, _e;
683
691
  return {
684
- startOn: (_a2 = trigger == null ? void 0 : trigger.startOn) != null ? _a2 : PX_TRIGGER_DEFAULTS.startOn,
685
- outAction: (_b = trigger == null ? void 0 : trigger.outAction) != null ? _b : PX_TRIGGER_DEFAULTS.outAction,
686
- scrollIntoViewThreshold: (_c = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _c : PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold
692
+ start: (_a2 = trigger == null ? void 0 : trigger.start) != null ? _a2 : PX_TRIGGER_DEFAULTS.start,
693
+ offScreen: (_b = trigger == null ? void 0 : trigger.offScreen) != null ? _b : PX_TRIGGER_DEFAULTS.offScreen,
694
+ mouseOut: (_c = trigger == null ? void 0 : trigger.mouseOut) != null ? _c : PX_TRIGGER_DEFAULTS.mouseOut,
695
+ visibilityThreshold: (_d = trigger == null ? void 0 : trigger.visibilityThreshold) != null ? _d : PX_TRIGGER_DEFAULTS.visibilityThreshold,
696
+ visibilityDebounce: (_e = trigger == null ? void 0 : trigger.visibilityDebounce) != null ? _e : PX_TRIGGER_DEFAULTS.visibilityDebounce
687
697
  };
688
698
  }
689
699
  var PxLoopRepeatAt = {
@@ -819,9 +829,9 @@ var PixodeskAnimator = (() => {
819
829
  } else {
820
830
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
821
831
  if (timeline.trigger !== void 0) {
822
- const _b = timeline.trigger, { finishAction } = _b, restTrigger = __objRest(_b, ["finishAction"]);
832
+ const _b = timeline.trigger, { finish } = _b, restTrigger = __objRest(_b, ["finish"]);
823
833
  if (Object.keys(restTrigger).length) flat.trigger = restTrigger;
824
- if (finishAction !== void 0) flat.resetOnFinish = finishAction === "reset";
834
+ if (finish !== void 0) flat.resetOnFinish = finish === "reset";
825
835
  }
826
836
  if (timeline.delay !== void 0) flat.delay = timeline.delay;
827
837
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
@@ -922,13 +932,15 @@ var PixodeskAnimator = (() => {
922
932
  PxAnimationDefinitionSchema
923
933
  ]));
924
934
  var PxTriggerSchema = implementsInterface()(px.object({
925
- startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic], PX_TRIGGER_DEFAULTS.startOn).optional(),
926
- outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse], PX_TRIGGER_DEFAULTS.outAction).optional(),
935
+ start: px.enum([PxTriggerStart.load, PxTriggerStart.mouseOver, PxTriggerStart.click, PxTriggerStart.none], PX_TRIGGER_DEFAULTS.start).optional(),
936
+ offScreen: px.enum([PxOffScreenAction.pause, PxOffScreenAction.continue, PxOffScreenAction.reset], PX_TRIGGER_DEFAULTS.offScreen).optional(),
937
+ mouseOut: px.enum([PxMouseOutAction.continue, PxMouseOutAction.pause, PxMouseOutAction.reset, PxMouseOutAction.reverse], PX_TRIGGER_DEFAULTS.mouseOut).optional(),
927
938
  // What happens after a NATURAL finish — `'hold'` (default: keep the end state per
928
- // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` ("what
929
- // happens when the trigger condition ends"); both end-of-life knobs now read alike.
930
- finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
931
- scrollIntoViewThreshold: px.number().optional()
939
+ // `fill`) or `'reset'` (snap back to the start state). One of four occasion keys
940
+ // (`start`, `offScreen`, `mouseOut`, `finish`), all named the same way.
941
+ finish: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
942
+ visibilityThreshold: px.number().optional(),
943
+ visibilityDebounce: px.number().optional()
932
944
  }));
933
945
  var PxGlyphSchema = implementsInterface()(px.object({
934
946
  width: px.number(),
@@ -1957,6 +1969,38 @@ var PixodeskAnimator = (() => {
1957
1969
  return propsCopy;
1958
1970
  }
1959
1971
 
1972
+ // ../svg-animator-core/src/animation/PxStaticTransformMerge.ts
1973
+ function mergeStaticTransformIntoAnimDef(animDef, staticTransform) {
1974
+ if (!animDef) return animDef;
1975
+ const staticParts = staticTransform && typeof staticTransform === "object" && !Array.isArray(staticTransform) ? staticTransform : parseTransformParts(staticTransform);
1976
+ if (!staticParts || !Object.keys(staticParts).length) return animDef;
1977
+ const mergeKfValue = (v) => v && typeof v === "object" && !Array.isArray(v) ? __spreadValues(__spreadValues({}, staticParts), v) : v;
1978
+ const transformAnim = animDef[TRANSFORM_ATTR];
1979
+ if (transformAnim && typeof transformAnim === "object") {
1980
+ const anim = transformAnim;
1981
+ if (Array.isArray(anim.keyframes)) {
1982
+ const out = __spreadProps(__spreadValues({}, anim), {
1983
+ keyframes: anim.keyframes.map((kf) => __spreadProps(__spreadValues({}, kf), { value: mergeKfValue(kf.value) }))
1984
+ });
1985
+ if (out.value !== void 0) out.value = mergeKfValue(out.value);
1986
+ return __spreadProps(__spreadValues({}, animDef), { transform: out });
1987
+ }
1988
+ return animDef;
1989
+ }
1990
+ const channels = Object.keys(animDef).filter((k) => PX_TRANSFORM_FN_NAMES.has(k));
1991
+ if (channels.length !== 1) return animDef;
1992
+ const ch = channels[0];
1993
+ const chAnim = animDef[ch];
1994
+ if (!chAnim || typeof chAnim !== "object" || !Array.isArray(chAnim.keyframes)) return animDef;
1995
+ const lifted = __spreadProps(__spreadValues({}, chAnim), {
1996
+ keyframes: chAnim.keyframes.map((kf) => __spreadProps(__spreadValues({}, kf), { value: __spreadProps(__spreadValues({}, staticParts), { [ch]: kf.value }) }))
1997
+ });
1998
+ if (lifted.value !== void 0) lifted.value = __spreadProps(__spreadValues({}, staticParts), { [ch]: lifted.value });
1999
+ const rest = __spreadValues({}, animDef);
2000
+ delete rest[ch];
2001
+ return __spreadProps(__spreadValues({}, rest), { transform: lifted });
2002
+ }
2003
+
1960
2004
  // ../svg-animator-core/src/materialize/PxMotionPath.ts
1961
2005
  function getKfTranslate(kf) {
1962
2006
  const v = keyframeValue(kf);
@@ -2326,7 +2370,7 @@ var PixodeskAnimator = (() => {
2326
2370
  let newAnimate;
2327
2371
  const animBucket = node.animate;
2328
2372
  if (animBucket && typeof animBucket === "object" && !Array.isArray(animBucket)) {
2329
- const animDef = animBucket;
2373
+ const animDef = mergeStaticTransformIntoAnimDef(animBucket, node.transform);
2330
2374
  const transformAnim = animDef.transform;
2331
2375
  if (transformAnim && typeof transformAnim === "object" && propAnimIsMotionPath(transformAnim)) {
2332
2376
  const materialized = materializeMotionPathInPropAnim(transformAnim, opts);
@@ -2840,36 +2884,6 @@ var PixodeskAnimator = (() => {
2840
2884
  function generateElementId() {
2841
2885
  return "_px_el_" + ++_elementIdCounter;
2842
2886
  }
2843
- function mergeStaticTransformIntoAnimDef(animDef, staticTransform) {
2844
- if (!animDef) return animDef;
2845
- const staticParts = staticTransform && typeof staticTransform === "object" && !Array.isArray(staticTransform) ? staticTransform : parseTransformParts(staticTransform);
2846
- if (!staticParts || !Object.keys(staticParts).length) return animDef;
2847
- const mergeKfValue = (v) => v && typeof v === "object" && !Array.isArray(v) ? __spreadValues(__spreadValues({}, staticParts), v) : v;
2848
- const transformAnim = animDef[TRANSFORM_ATTR];
2849
- if (transformAnim && typeof transformAnim === "object") {
2850
- const anim = transformAnim;
2851
- if (Array.isArray(anim.keyframes)) {
2852
- const out = __spreadProps(__spreadValues({}, anim), {
2853
- keyframes: anim.keyframes.map((kf) => __spreadProps(__spreadValues({}, kf), { value: mergeKfValue(kf.value) }))
2854
- });
2855
- if (out.value !== void 0) out.value = mergeKfValue(out.value);
2856
- return __spreadProps(__spreadValues({}, animDef), { transform: out });
2857
- }
2858
- return animDef;
2859
- }
2860
- const channels = Object.keys(animDef).filter((k) => PX_TRANSFORM_FN_NAMES.has(k));
2861
- if (channels.length !== 1) return animDef;
2862
- const ch = channels[0];
2863
- const chAnim = animDef[ch];
2864
- if (!chAnim || typeof chAnim !== "object" || !Array.isArray(chAnim.keyframes)) return animDef;
2865
- const lifted = __spreadProps(__spreadValues({}, chAnim), {
2866
- keyframes: chAnim.keyframes.map((kf) => __spreadProps(__spreadValues({}, kf), { value: __spreadProps(__spreadValues({}, staticParts), { [ch]: kf.value }) }))
2867
- });
2868
- if (lifted.value !== void 0) lifted.value = __spreadProps(__spreadValues({}, staticParts), { [ch]: lifted.value });
2869
- const rest = __spreadValues({}, animDef);
2870
- delete rest[ch];
2871
- return __spreadProps(__spreadValues({}, rest), { transform: lifted });
2872
- }
2873
2887
  function normalizeAnimationDefinition(animDef, duration, defs, engine = PxTimelineEngine.native) {
2874
2888
  const normalized = {};
2875
2889
  for (const [propName, propAnim] of Object.entries(animDef)) {
@@ -2952,7 +2966,7 @@ var PixodeskAnimator = (() => {
2952
2966
  return { prevKf, nextKf };
2953
2967
  }
2954
2968
  function calcPropertyValue(propName, propAnim, progress) {
2955
- var _a2, _b, _c, _d;
2969
+ var _a2, _b, _c, _d, _e;
2956
2970
  const keyframes = propAnim.keyframes || [];
2957
2971
  if (keyframes.length === 0) return null;
2958
2972
  const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);
@@ -3021,7 +3035,7 @@ var PixodeskAnimator = (() => {
3021
3035
  !!propAnim.autoOrient
3022
3036
  );
3023
3037
  partsResult.translate = [sample.translate[0], sample.translate[1]];
3024
- if (sample.rotateDeg !== void 0) partsResult.rotate = sample.rotateDeg;
3038
+ if (sample.rotateDeg !== void 0) partsResult.rotate = sample.rotateDeg + ((_e = partsResult.rotate) != null ? _e : 0);
3025
3039
  }
3026
3040
  }
3027
3041
  cssValue = composeTransformParts(partsResult, { withUnits: false });
@@ -5504,12 +5518,17 @@ var PixodeskAnimator = (() => {
5504
5518
  const first = keyframeValue(kfs[0]);
5505
5519
  const anchor = (first == null ? void 0 : first.origin) && first.origin.length >= 2 ? [first.origin[0], first.origin[1]] : [0, 0];
5506
5520
  const points = [];
5521
+ let keyframesCarryRotate = false;
5507
5522
  for (const kf of kfs) {
5508
5523
  const v = keyframeValue(kf);
5509
5524
  const tr = v == null ? void 0 : v.translate;
5510
5525
  if (!tr || tr.length < 2) return void 0;
5511
5526
  const parts = Object.keys(v);
5512
- if (parts.some((p) => p !== "translate" && p !== "origin")) return void 0;
5527
+ if (parts.some((p) => p !== TRANSFORM_PART.translate && p !== TRANSFORM_PART.origin && p !== TRANSFORM_PART.rotate)) return void 0;
5528
+ if ((v == null ? void 0 : v.rotate) !== void 0) {
5529
+ if (v.rotate !== 0) return void 0;
5530
+ keyframesCarryRotate = true;
5531
+ }
5513
5532
  const o = (_a2 = v == null ? void 0 : v.origin) != null ? _a2 : [0, 0];
5514
5533
  if (o[0] !== anchor[0] || o[1] !== anchor[1]) return void 0;
5515
5534
  points.push([tr[0] + anchor[0], tr[1] + anchor[1]]);
@@ -5537,7 +5556,7 @@ var PixodeskAnimator = (() => {
5537
5556
  if (e !== void 0) out.e = e;
5538
5557
  distanceKfs.push(out);
5539
5558
  }
5540
- return { pathStr: d, distanceKfs, autoOrient: !!propAnim.autoOrient, anchor };
5559
+ return { pathStr: d, distanceKfs, autoOrient: !!propAnim.autoOrient, anchor, keyframesCarryRotate };
5541
5560
  }
5542
5561
  function materializeOffsetPathsInTree(root) {
5543
5562
  const walk = (node) => {
@@ -5559,6 +5578,7 @@ var PixodeskAnimator = (() => {
5559
5578
  const t = __spreadValues({}, staticTr);
5560
5579
  delete t[TRANSFORM_PART.translate];
5561
5580
  delete t[TRANSFORM_PART.origin];
5581
+ if (built.keyframesCarryRotate) delete t[TRANSFORM_PART.rotate];
5562
5582
  newTransform = Object.keys(t).length ? t : void 0;
5563
5583
  }
5564
5584
  out = __spreadProps(__spreadValues({}, node), {
@@ -5731,6 +5751,78 @@ var PixodeskAnimator = (() => {
5731
5751
  return changed ? __spreadProps(__spreadValues({}, node), { children: newChildren }) : node;
5732
5752
  }
5733
5753
 
5754
+ // ../svg-animator-core/src/materialize/PxRestPose.ts
5755
+ var TRANSFORM_CHANNEL = "transform";
5756
+ var REVERSED_DIRECTIONS = /* @__PURE__ */ new Set(["reverse", "alternate-reverse"]);
5757
+ var SCRATCH_ID_PREFIX = "__px_rest_";
5758
+ function materializeRestPosesInTree(root, engine) {
5759
+ var _a2;
5760
+ const out = deepClone(root);
5761
+ const config = getAnimatorConfig(out) || {};
5762
+ const duration = +(config.duration || PX_DEFAULT_DURATION_MS);
5763
+ const firstFrameTime = REVERSED_DIRECTIONS.has(String(config.direction)) ? duration : 0;
5764
+ const nodes = animatedNodes(out);
5765
+ if (!nodes.size) return out;
5766
+ const before = bindingsOf(out, engine);
5767
+ const added = /* @__PURE__ */ new Map();
5768
+ for (const [key, animate] of before) {
5769
+ const node = nodes.get(key);
5770
+ if (!node) continue;
5771
+ for (const channel of Object.keys(animate)) {
5772
+ if (!mayCarryRestPose(node, channel)) continue;
5773
+ const value = firstFrameValue(animate, channel, firstFrameTime);
5774
+ if (value === void 0) continue;
5775
+ node[channel] = value;
5776
+ added.set(key, [...(_a2 = added.get(key)) != null ? _a2 : [], channel]);
5777
+ }
5778
+ }
5779
+ if (added.size) {
5780
+ const after = bindingsOf(out, engine);
5781
+ for (const [key, channels] of added) {
5782
+ if (JSON.stringify(after.get(key)) === JSON.stringify(before.get(key))) continue;
5783
+ const node = nodes.get(key);
5784
+ if (node) for (const channel of channels) delete node[channel];
5785
+ }
5786
+ }
5787
+ return out;
5788
+ }
5789
+ function mayCarryRestPose(node, channel) {
5790
+ if (node[channel] !== void 0) return false;
5791
+ if (PX_TRANSFORM_FN_NAMES.has(channel)) return false;
5792
+ if (channel === TRANSFORM_CHANNEL) {
5793
+ for (const part of PX_TRANSFORM_FN_NAMES) if (node[part] !== void 0) return false;
5794
+ }
5795
+ return true;
5796
+ }
5797
+ function firstFrameValue(animate, channel, timeMs) {
5798
+ const values = Object.values(calcAnimationValues({ [channel]: animate[channel] }, timeMs));
5799
+ return values.length === 1 && values[0] !== "" ? values[0] : void 0;
5800
+ }
5801
+ function bindingsOf(tree, engine) {
5802
+ const scratch = deepClone(tree);
5803
+ const keyById = /* @__PURE__ */ new Map();
5804
+ for (const [key, node] of animatedNodes(scratch)) {
5805
+ if (node.id === void 0) node.id = SCRATCH_ID_PREFIX + key;
5806
+ keyById.set(String(node.id), key);
5807
+ }
5808
+ const out = /* @__PURE__ */ new Map();
5809
+ for (const binding of normalizeBindings(scratch, engine)) {
5810
+ const key = keyById.get(binding.id);
5811
+ if (key !== void 0) out.set(key, binding.animate);
5812
+ }
5813
+ return out;
5814
+ }
5815
+ function animatedNodes(tree) {
5816
+ const out = /* @__PURE__ */ new Map();
5817
+ let counter = 0;
5818
+ const visit = (node) => {
5819
+ if (node.animate) out.set(String(counter++), node);
5820
+ if (node.children) for (const child of node.children) visit(child);
5821
+ };
5822
+ if (tree.children) for (const child of tree.children) visit(child);
5823
+ return out;
5824
+ }
5825
+
5734
5826
  // ../svg-animator-core/src/materialize/PxAnimatorMaterializeAll.ts
5735
5827
  function materializeAllInTree(doc, engine, options) {
5736
5828
  var _a2, _b;
@@ -5743,6 +5835,7 @@ var PixodeskAnimator = (() => {
5743
5835
  root = materializeAnimatedUseInstances(root);
5744
5836
  root = pruneUnreferencedDefs(root);
5745
5837
  }
5838
+ root = materializeRestPosesInTree(root, engine);
5746
5839
  return root;
5747
5840
  }
5748
5841
  function pruneUnreferencedDefs(root) {
@@ -6210,16 +6303,16 @@ var PixodeskAnimator = (() => {
6210
6303
  } else {
6211
6304
  base = timeline != null ? timeline : void 0;
6212
6305
  }
6213
- const { duration, delay, iterations, startOn } = shortcuts;
6214
- if (duration === void 0 && delay === void 0 && iterations === void 0 && startOn === void 0) {
6306
+ const { duration, delay, iterations, start } = shortcuts;
6307
+ if (duration === void 0 && delay === void 0 && iterations === void 0 && start === void 0) {
6215
6308
  return base === void 0 ? void 0 : { timeline: base };
6216
6309
  }
6217
6310
  const out = isPlainObject(base) ? __spreadValues({}, base) : {};
6218
6311
  if (duration !== void 0) out.duration = duration;
6219
6312
  if (delay !== void 0) out.delay = delay;
6220
6313
  if (iterations !== void 0) out.iterations = iterations;
6221
- if (startOn !== void 0) {
6222
- out.trigger = __spreadProps(__spreadValues({}, out.trigger), { startOn });
6314
+ if (start !== void 0) {
6315
+ out.trigger = __spreadProps(__spreadValues({}, out.trigger), { start });
6223
6316
  }
6224
6317
  return { timeline: out };
6225
6318
  }
@@ -6306,6 +6399,52 @@ var PixodeskAnimator = (() => {
6306
6399
  );
6307
6400
  }
6308
6401
 
6402
+ // ../svg-animator-core/src/render/PxRenderTree.ts
6403
+ var CLASS_NAME_KEY = "className";
6404
+ var CLASS_ATTR2 = "class";
6405
+ var DOM_TYPE_KEY = "domType";
6406
+ var TYPE_ATTR = "type";
6407
+ var DEFAULT_TAG = "g";
6408
+ function renderPxTree(node, factory, diag) {
6409
+ return node ? renderOne(node, factory, diag, true, 0) : null;
6410
+ }
6411
+ function renderOne(node, factory, diag, isRoot, index) {
6412
+ const _a2 = node, { type, children, style } = _a2, props = __objRest(_a2, ["type", "children", "style"]);
6413
+ const tag = type || DEFAULT_TAG;
6414
+ if (PX_DISALLOWED_SVG_TAGS_LOWER.has(tag.toLowerCase())) {
6415
+ (diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]")).warn(PxDiagnosticKind.document, 1103 /* blockedTag */, tag);
6416
+ return null;
6417
+ }
6418
+ const domType = props[DOM_TYPE_KEY];
6419
+ if (domType !== void 0) delete props[DOM_TYPE_KEY];
6420
+ const attrs = {};
6421
+ let inlineStyle;
6422
+ const domProps = toDomProps(props);
6423
+ for (const propName of Object.keys(domProps)) {
6424
+ const sanitized = sanitizeAttributeValue(propName, domProps[propName]);
6425
+ if (sanitized === void 0) continue;
6426
+ if (PX_CSS_ONLY_STYLE_PROPS.has(propName)) {
6427
+ (inlineStyle != null ? inlineStyle : inlineStyle = {})[propName] = String(sanitized);
6428
+ continue;
6429
+ }
6430
+ attrs[propName === CLASS_NAME_KEY ? CLASS_ATTR2 : camelCaseToKebabWordIfNeeded(propName)] = sanitized;
6431
+ }
6432
+ if (domType !== void 0) attrs[TYPE_ATTR] = String(domType);
6433
+ if (style) {
6434
+ for (const styleProp of Object.keys(style)) (inlineStyle != null ? inlineStyle : inlineStyle = {})[styleProp] = String(style[styleProp]);
6435
+ }
6436
+ const rendered = [];
6437
+ if (children) {
6438
+ children.forEach((child, i) => {
6439
+ const el = renderOne(child, factory, diag, false, i);
6440
+ if (el !== null) rendered.push(el);
6441
+ });
6442
+ }
6443
+ const ownText = props[PX_TEXT_CONTENT_ATTR];
6444
+ const text = !rendered.length && typeof ownText === "string" && ownText ? ownText : void 0;
6445
+ return factory({ tag, attrs, style: inlineStyle, children: rendered, text, node, isRoot, index });
6446
+ }
6447
+
6309
6448
  // ../svg-animator-core/src/playback/PxScrollMath.ts
6310
6449
  function isScrollTimeline(config) {
6311
6450
  return (config == null ? void 0 : config.timelineSource) === "scroll";
@@ -6385,6 +6524,202 @@ var PixodeskAnimator = (() => {
6385
6524
  return e instanceof Error ? e : new Error(String(e));
6386
6525
  }
6387
6526
 
6527
+ // src/registry/PxAnimatorRegistry.ts
6528
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@pixodesk/svg-animator-web:animators");
6529
+ var GLOBAL_NAME = "__pixodeskAnimators";
6530
+ function store() {
6531
+ const g = globalThis;
6532
+ let s = g[STORE_KEY];
6533
+ if (!s) {
6534
+ const animators = /* @__PURE__ */ new Set();
6535
+ const listeners = /* @__PURE__ */ new Set();
6536
+ s = {
6537
+ animators,
6538
+ listeners,
6539
+ getAll: () => Array.from(animators),
6540
+ subscribe: (listener) => {
6541
+ listeners.add(listener);
6542
+ return () => {
6543
+ listeners.delete(listener);
6544
+ };
6545
+ }
6546
+ };
6547
+ g[STORE_KEY] = s;
6548
+ if (g[GLOBAL_NAME] === void 0) g[GLOBAL_NAME] = s;
6549
+ }
6550
+ return s;
6551
+ }
6552
+ function getAllAnimators() {
6553
+ return store().getAll();
6554
+ }
6555
+ function onAnimatorsChange(listener) {
6556
+ return store().subscribe(listener);
6557
+ }
6558
+ function notify(event, animator) {
6559
+ for (const listener of store().listeners) {
6560
+ try {
6561
+ listener(event, animator);
6562
+ } catch (e) {
6563
+ setTimeout(() => {
6564
+ throw e;
6565
+ }, 0);
6566
+ }
6567
+ }
6568
+ }
6569
+ function registerAnimator(animator) {
6570
+ const s = store();
6571
+ if (s.animators.has(animator)) return;
6572
+ s.animators.add(animator);
6573
+ notify("add", animator);
6574
+ const destroy = animator.destroy.bind(animator);
6575
+ animator.destroy = () => {
6576
+ destroy();
6577
+ if (s.animators.delete(animator)) notify("remove", animator);
6578
+ };
6579
+ }
6580
+ function withRegistryEvents(callbacks, api) {
6581
+ const fire = (event) => {
6582
+ const a = api();
6583
+ if (a) notify(event, a);
6584
+ };
6585
+ return __spreadProps(__spreadValues({}, callbacks), {
6586
+ onPlay: () => {
6587
+ var _a2;
6588
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6589
+ fire("play");
6590
+ },
6591
+ onPause: () => {
6592
+ var _a2;
6593
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
6594
+ fire("pause");
6595
+ },
6596
+ onCancel: () => {
6597
+ var _a2;
6598
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6599
+ fire("cancel");
6600
+ },
6601
+ onFinish: () => {
6602
+ var _a2;
6603
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6604
+ fire("finish");
6605
+ }
6606
+ });
6607
+ }
6608
+
6609
+ // src/triggers/PxVisibilityGate.ts
6610
+ var PLAY_WHEN_VISIBLE_DEFAULTS = {
6611
+ offScreen: PX_TRIGGER_DEFAULTS.offScreen,
6612
+ visibilityThreshold: PX_TRIGGER_DEFAULTS.visibilityThreshold,
6613
+ visibilityDebounce: PX_TRIGGER_DEFAULTS.visibilityDebounce
6614
+ };
6615
+ var THRESHOLD_STEPS = Array.from({ length: 21 }, (_, i) => i / 20);
6616
+ function effectiveRatio(entry) {
6617
+ var _a2, _b;
6618
+ const target = entry.boundingClientRect;
6619
+ const visible = entry.intersectionRect;
6620
+ if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
6621
+ const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
6622
+ const declared = (_b = (_a2 = entry.rootBounds) == null ? void 0 : _a2.height) != null ? _b : Infinity;
6623
+ const viewport = Math.min(live, declared);
6624
+ const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
6625
+ return denom > 0 ? visible.height / denom : entry.intersectionRatio;
6626
+ }
6627
+ function openGate(host) {
6628
+ return {
6629
+ requestStart: () => host.play(),
6630
+ dispose: () => {
6631
+ }
6632
+ };
6633
+ }
6634
+ function isGated(offScreen) {
6635
+ return offScreen !== PxOffScreenAction.continue;
6636
+ }
6637
+ function createVisibilityGate(root, trigger, host) {
6638
+ if (!isGated(trigger.offScreen)) return openGate(host);
6639
+ if (typeof IntersectionObserver === "undefined") return openGate(host);
6640
+ const threshold = trigger.visibilityThreshold;
6641
+ const debounceMs = trigger.visibilityDebounce;
6642
+ let isOpen = void 0;
6643
+ let startPending = false;
6644
+ let pausedByGate = false;
6645
+ let lastRatio = 0;
6646
+ let openTimer;
6647
+ const cancelPendingOpen = () => {
6648
+ if (openTimer !== void 0) {
6649
+ clearTimeout(openTimer);
6650
+ openTimer = void 0;
6651
+ }
6652
+ };
6653
+ const open = () => {
6654
+ openTimer = void 0;
6655
+ if (isOpen === true) return;
6656
+ isOpen = true;
6657
+ if (startPending || pausedByGate) {
6658
+ startPending = false;
6659
+ pausedByGate = false;
6660
+ host.play();
6661
+ }
6662
+ };
6663
+ const close = () => {
6664
+ cancelPendingOpen();
6665
+ if (isOpen === false) return;
6666
+ isOpen = false;
6667
+ if (!host.isPlaying()) return;
6668
+ if (trigger.offScreen === PxOffScreenAction.reset) {
6669
+ host.cancel();
6670
+ } else {
6671
+ host.pause();
6672
+ }
6673
+ pausedByGate = true;
6674
+ };
6675
+ const apply2 = (ratio) => {
6676
+ lastRatio = ratio;
6677
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
6678
+ close();
6679
+ return;
6680
+ }
6681
+ if (ratio >= threshold) {
6682
+ if (isOpen === true || openTimer !== void 0) return;
6683
+ if (debounceMs > 0) openTimer = setTimeout(open, debounceMs);
6684
+ else open();
6685
+ return;
6686
+ }
6687
+ if (ratio <= 0) {
6688
+ close();
6689
+ return;
6690
+ }
6691
+ if (isOpen !== true) cancelPendingOpen();
6692
+ };
6693
+ const observer = new IntersectionObserver((entries) => {
6694
+ const last = entries[entries.length - 1];
6695
+ if (last) apply2(last.isIntersecting ? effectiveRatio(last) : 0);
6696
+ }, { threshold: THRESHOLD_STEPS });
6697
+ observer.observe(root);
6698
+ const onVisibilityChange = () => {
6699
+ apply2(lastRatio);
6700
+ };
6701
+ const hasDocument = typeof document !== "undefined";
6702
+ if (hasDocument) document.addEventListener("visibilitychange", onVisibilityChange);
6703
+ return {
6704
+ requestStart: (immediate) => {
6705
+ if (immediate || isOpen === true) {
6706
+ cancelPendingOpen();
6707
+ isOpen = true;
6708
+ startPending = false;
6709
+ pausedByGate = false;
6710
+ host.play();
6711
+ return;
6712
+ }
6713
+ startPending = true;
6714
+ },
6715
+ dispose: () => {
6716
+ cancelPendingOpen();
6717
+ observer.disconnect();
6718
+ if (hasDocument) document.removeEventListener("visibilitychange", onVisibilityChange);
6719
+ }
6720
+ };
6721
+ }
6722
+
6388
6723
  // src/triggers/PxAnimatorTriggers.ts
6389
6724
  function setupAnimationTriggers(api, trigger, diag) {
6390
6725
  const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
@@ -6392,7 +6727,7 @@ var PixodeskAnimator = (() => {
6392
6727
  const dispose = () => {
6393
6728
  for (const undo of cleanups.splice(0)) undo();
6394
6729
  };
6395
- const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);
6730
+ const resolved = resolveTrigger(trigger);
6396
6731
  const root = api.getRootElement();
6397
6732
  if (!root) {
6398
6733
  report.warn(PxDiagnosticKind.host, 1201 /* triggersNoRoot */);
@@ -6406,27 +6741,34 @@ var PixodeskAnimator = (() => {
6406
6741
  }
6407
6742
  api.play();
6408
6743
  };
6409
- const handleEndAction = () => {
6410
- switch (outAction) {
6411
- case "pause":
6744
+ const gate = createVisibilityGate(root, resolved, {
6745
+ isPlaying: () => api.isPlaying(),
6746
+ play: start,
6747
+ pause: () => api.pause(),
6748
+ cancel: () => api.cancel()
6749
+ });
6750
+ cleanups.push(() => gate.dispose());
6751
+ const handleMouseOut = () => {
6752
+ switch (resolved.mouseOut) {
6753
+ case PxMouseOutAction.pause:
6412
6754
  api.pause();
6413
6755
  break;
6414
- case "reset":
6756
+ case PxMouseOutAction.reset:
6415
6757
  api.cancel();
6416
6758
  break;
6417
- case "reverse":
6759
+ case PxMouseOutAction.reverse:
6418
6760
  reversed = true;
6419
6761
  api.setPlaybackRate(-1);
6420
6762
  api.play();
6421
6763
  break;
6422
- case "continue":
6764
+ case PxMouseOutAction.continue:
6423
6765
  default:
6424
6766
  break;
6425
6767
  }
6426
6768
  };
6427
- switch (startOn) {
6428
- case "load": {
6429
- const startHandler = () => start();
6769
+ switch (resolved.start) {
6770
+ case PxTriggerStart.load: {
6771
+ const startHandler = () => gate.requestStart(false);
6430
6772
  if (document.readyState === "complete") {
6431
6773
  startHandler();
6432
6774
  } else {
@@ -6435,14 +6777,14 @@ var PixodeskAnimator = (() => {
6435
6777
  }
6436
6778
  break;
6437
6779
  }
6438
- case "mouseOver": {
6780
+ case PxTriggerStart.mouseOver: {
6439
6781
  let enteredOnce = false;
6440
6782
  const mouseOverHandler = () => {
6441
6783
  enteredOnce = true;
6442
- start();
6784
+ gate.requestStart(true);
6443
6785
  };
6444
6786
  const mouseOutHandler = () => {
6445
- if (enteredOnce) handleEndAction();
6787
+ if (enteredOnce) handleMouseOut();
6446
6788
  };
6447
6789
  root.addEventListener("mouseenter", mouseOverHandler);
6448
6790
  root.addEventListener("mouseleave", mouseOutHandler);
@@ -6452,51 +6794,16 @@ var PixodeskAnimator = (() => {
6452
6794
  });
6453
6795
  break;
6454
6796
  }
6455
- case "click": {
6797
+ case PxTriggerStart.click: {
6456
6798
  const clickHandler = () => {
6457
- if (api.isPlaying()) {
6458
- handleEndAction();
6459
- } else {
6460
- start();
6461
- }
6799
+ if (api.isPlaying()) api.pause();
6800
+ else gate.requestStart(true);
6462
6801
  };
6463
6802
  root.addEventListener("click", clickHandler);
6464
6803
  cleanups.push(() => root.removeEventListener("click", clickHandler));
6465
6804
  break;
6466
6805
  }
6467
- case "scrollIntoView": {
6468
- const effectiveRatio = (entry) => {
6469
- var _a2, _b;
6470
- const target = entry.boundingClientRect;
6471
- const visible = entry.intersectionRect;
6472
- if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
6473
- const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
6474
- const declared = (_b = (_a2 = entry.rootBounds) == null ? void 0 : _a2.height) != null ? _b : Infinity;
6475
- const viewport = Math.min(live, declared);
6476
- const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
6477
- return denom > 0 ? visible.height / denom : entry.intersectionRatio;
6478
- };
6479
- const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);
6480
- let wasIntersecting = false;
6481
- const observer = new IntersectionObserver(
6482
- (entries) => {
6483
- entries.forEach((entry) => {
6484
- if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {
6485
- wasIntersecting = true;
6486
- start();
6487
- } else if (wasIntersecting) {
6488
- wasIntersecting = false;
6489
- handleEndAction();
6490
- }
6491
- });
6492
- },
6493
- { threshold: thresholdSteps }
6494
- );
6495
- observer.observe(root);
6496
- cleanups.push(() => observer.disconnect());
6497
- break;
6498
- }
6499
- case "programmatic":
6806
+ case PxTriggerStart.none:
6500
6807
  break;
6501
6808
  }
6502
6809
  return dispose;
@@ -6504,7 +6811,12 @@ var PixodeskAnimator = (() => {
6504
6811
 
6505
6812
  // src/engines/PxAnimatorFrameLoop.ts
6506
6813
  function getSelector(id) {
6507
- return "#" + id;
6814
+ return "#" + escapeCssId(id);
6815
+ }
6816
+ function escapeCssId(id) {
6817
+ const css = globalThis.CSS;
6818
+ if (typeof (css == null ? void 0 : css.escape) === "function") return css.escape(id);
6819
+ return id.replace(/^([0-9])/, (_all, digit) => "\\3" + digit + " ").replace(/([^\w\-\\ ])/g, "\\$1");
6508
6820
  }
6509
6821
  function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6510
6822
  var _a2;
@@ -7199,62 +7511,19 @@ var PixodeskAnimator = (() => {
7199
7511
 
7200
7512
  // src/dom/PxAnimatorDOM.ts
7201
7513
  var SVG_NS = "http://www.w3.org/2000/svg";
7202
- function createElement(tagName, normalizedProps, style, children, textContent, diag) {
7203
- if (PX_DISALLOWED_SVG_TAGS_LOWER.has(tagName.toLowerCase())) {
7204
- (diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]")).warn(PxDiagnosticKind.document, 1103 /* blockedTag */, tagName);
7205
- return null;
7206
- }
7207
- const element = document.createElementNS(SVG_NS, tagName);
7208
- for (const propName in normalizedProps) {
7209
- const sanitized = sanitizeAttributeValue(propName, normalizedProps[propName]);
7210
- if (sanitized === void 0) continue;
7211
- if (PX_CSS_ONLY_STYLE_PROPS.has(propName)) {
7212
- element.style[propName] = String(sanitized);
7213
- continue;
7214
- }
7215
- element.setAttribute(camelCaseToKebabWordIfNeeded(propName), sanitized);
7216
- }
7514
+ var createDomElement = ({ tag, attrs, style, children, text }) => {
7515
+ const element = document.createElementNS(SVG_NS, tag);
7516
+ for (const name of Object.keys(attrs)) element.setAttribute(name, attrs[name]);
7217
7517
  if (style) {
7218
- for (const styleProp in style) {
7219
- element.style[styleProp] = String(style[styleProp]);
7220
- }
7518
+ const target = element.style;
7519
+ for (const prop of Object.keys(style)) target[prop] = style[prop];
7221
7520
  }
7222
- if (children == null ? void 0 : children.length) {
7223
- for (const child of children) {
7224
- element.appendChild(child);
7225
- }
7226
- } else if (textContent) {
7227
- element.textContent = textContent;
7228
- }
7229
- return element;
7230
- }
7231
- function renderNode(node, defs, diag) {
7232
- if (!node) return null;
7233
- const _a2 = node, { type, children, style } = _a2, props = __objRest(_a2, ["type", "children", "style"]);
7234
- const domType = props.domType;
7235
- if (domType !== void 0) delete props.domType;
7236
- const nodeDefs = getDefinitions(node) || defs;
7237
- const resolvedStyle = style;
7238
- let childElements;
7239
- if (children) {
7240
- for (const ch of children) {
7241
- const child = renderNode(ch, nodeDefs, diag);
7242
- if (child) {
7243
- if (!childElements) childElements = [];
7244
- childElements.push(child);
7245
- }
7246
- }
7247
- }
7248
- const element = createElement(
7249
- type || "g",
7250
- toDomProps(props),
7251
- resolvedStyle,
7252
- childElements,
7253
- props[PX_TEXT_CONTENT_ATTR],
7254
- diag
7255
- );
7256
- if (element && domType !== void 0) element.setAttribute("type", domType);
7521
+ for (const child of children) element.appendChild(child);
7522
+ if (text !== void 0) element.textContent = text;
7257
7523
  return element;
7524
+ };
7525
+ function renderNode(node, _defs, diag) {
7526
+ return renderPxTree(node, createDomElement, diag);
7258
7527
  }
7259
7528
 
7260
7529
  // src/shared/PxAnimatorKeys.ts
@@ -7264,7 +7533,7 @@ var PixodeskAnimator = (() => {
7264
7533
  function createAnimatorFromConfig(doc, adapter, callbacks, rootElement) {
7265
7534
  return bindWithEngineChoice(doc, adapter, callbacks, rootElement);
7266
7535
  }
7267
- function createAnimatorImpl(doc, adapter, callbacks, containerElement, patch, resetTimeline) {
7536
+ function createAnimatorImpl(doc, adapter, callbacks, containerElement, patch, resetTimeline, providedRoot) {
7268
7537
  const diag = createDiagnostics(callbacks, "[PxAnimator]");
7269
7538
  const effectsWarnings = validateNodeEffects(doc);
7270
7539
  for (const w of effectsWarnings) diag.warn(PxDiagnosticKind.document, 1101 /* effectsShape */, w);
@@ -7288,6 +7557,7 @@ var PixodeskAnimator = (() => {
7288
7557
  }
7289
7558
  }
7290
7559
  }
7560
+ if (!rootElement && providedRoot) rootElement = providedRoot;
7291
7561
  const api = createAnimatorFromConfig(doc, adapter, callbacks, rootElement);
7292
7562
  if (containerElement && rootElement) {
7293
7563
  const rendered = rootElement;
@@ -7300,17 +7570,19 @@ var PixodeskAnimator = (() => {
7300
7570
  return api;
7301
7571
  }
7302
7572
  function isInternalOptions(options) {
7303
- return "adapter" in options;
7573
+ return "adapter" in options || "rootElement" in options;
7304
7574
  }
7305
7575
  function resolveTimelineOption(options) {
7306
- const { timeline, duration, delay, iterations, startOn } = options;
7307
- return foldTimelineOverride(timeline, { duration, delay, iterations, startOn });
7576
+ const { timeline, duration, delay, iterations, start } = options;
7577
+ return foldTimelineOverride(timeline, { duration, delay, iterations, start });
7308
7578
  }
7309
7579
  function createAnimator(options) {
7310
7580
  const { src, doc, container, resetTimeline } = options;
7311
7581
  const adapter = isInternalOptions(options) ? options.adapter : void 0;
7582
+ const providedRoot = isInternalOptions(options) ? options.rootElement : void 0;
7312
7583
  const patch = resolveTimelineOption(options);
7313
- const callbacks = toEngineCallbacks(options);
7584
+ let proxy;
7585
+ const callbacks = withRegistryEvents(toEngineCallbacks(options), () => proxy);
7314
7586
  if (doc !== void 0 && src !== void 0) {
7315
7587
  throw new Error("createAnimator: provide either `src` or `doc`, not both");
7316
7588
  }
@@ -7340,25 +7612,13 @@ var PixodeskAnimator = (() => {
7340
7612
  };
7341
7613
  const build = (document2) => {
7342
7614
  try {
7343
- ready(createAnimatorImpl(document2, adapter, callbacks, container, patch, resetTimeline));
7615
+ ready(createAnimatorImpl(document2, adapter, callbacks, container, patch, resetTimeline, providedRoot));
7344
7616
  } catch (e) {
7345
7617
  const err = asThrownError(e);
7346
7618
  failed(PxDiagnosticKind.internal, 1001 /* buildFailed */, err);
7347
7619
  }
7348
7620
  };
7349
- if (doc !== void 0) {
7350
- build(doc);
7351
- } else {
7352
- fetch(src).then((res) => res.json()).then((json) => {
7353
- if (destroyed) return;
7354
- if (isPxDocument(json)) build(json);
7355
- else failed(PxDiagnosticKind.document, 1002 /* invalidDocumentAtSrc */, src);
7356
- }).catch((err) => {
7357
- var _a2;
7358
- failed(PxDiagnosticKind.host, 1003 /* loadFailed */, src, (_a2 = err == null ? void 0 : err.message) != null ? _a2 : String(err));
7359
- });
7360
- }
7361
- return {
7621
+ proxy = {
7362
7622
  "isReady": () => !!animator,
7363
7623
  "getRootElement": () => animator ? animator.getRootElement() : null,
7364
7624
  "isPlaying": () => (animator == null ? void 0 : animator.isPlaying()) || false,
@@ -7391,6 +7651,20 @@ var PixodeskAnimator = (() => {
7391
7651
  animator == null ? void 0 : animator.destroy();
7392
7652
  }
7393
7653
  };
7654
+ registerAnimator(proxy);
7655
+ if (doc !== void 0) {
7656
+ build(doc);
7657
+ } else {
7658
+ fetch(src).then((res) => res.json()).then((json) => {
7659
+ if (destroyed) return;
7660
+ if (isPxDocument(json)) build(json);
7661
+ else failed(PxDiagnosticKind.document, 1002 /* invalidDocumentAtSrc */, src);
7662
+ }).catch((err) => {
7663
+ var _a2;
7664
+ failed(PxDiagnosticKind.host, 1003 /* loadFailed */, src, (_a2 = err == null ? void 0 : err.message) != null ? _a2 : String(err));
7665
+ });
7666
+ }
7667
+ return proxy;
7394
7668
  }
7395
7669
  function loadTagAnimators(options) {
7396
7670
  const elements = document.querySelectorAll("[" + PX_ANIM_SRC_ATTR_NAME + "]");