@pixodesk/svg-animator-web 1.0.41 → 1.0.44

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/dist/index.cjs CHANGED
@@ -60,7 +60,9 @@ __export(index_exports, {
60
60
  createAdapterAnimator: () => createAdapterAnimator,
61
61
  createAnimator: () => createAnimator,
62
62
  generateNewIds: () => generateNewIds,
63
+ getAllAnimators: () => getAllAnimators,
63
64
  loadTagAnimators: () => loadTagAnimators,
65
+ onAnimatorsChange: () => onAnimatorsChange,
64
66
  px: () => px,
65
67
  renderNode: () => renderNode,
66
68
  setupAnimationTriggers: () => setupAnimationTriggers,
@@ -69,7 +71,7 @@ __export(index_exports, {
69
71
  });
70
72
  module.exports = __toCommonJS(index_exports);
71
73
 
72
- // ../svg-animator-core/dist/chunk-L6GJ6Y2M.js
74
+ // ../svg-animator-core/dist/chunk-EFQLDGFY.js
73
75
  var __defProp2 = Object.defineProperty;
74
76
  var __defProps2 = Object.defineProperties;
75
77
  var __getOwnPropDescs2 = Object.getOwnPropertyDescriptors;
@@ -603,7 +605,7 @@ var px = {
603
605
  /** Defers schema creation — required for recursive types. Must supply a default value. */
604
606
  lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
605
607
  };
606
- var PX_WIRE_SCHEMA_VERSION = "1.1";
608
+ var PX_WIRE_SCHEMA_VERSION = "1.2";
607
609
  var VERSION_RE = /^(\d+)\.(\d+)(?:\.(\d+))?$/;
608
610
  function parseWireVersion(raw) {
609
611
  if (typeof raw !== "string") return void 0;
@@ -627,14 +629,18 @@ var PxPlaybackDirection = {
627
629
  };
628
630
  var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
629
631
  var PX_ANIM_ATTR_NAME = "_px_animator";
630
- var PxStartOn = {
632
+ var PxTriggerStart = {
631
633
  load: "load",
632
634
  mouseOver: "mouseOver",
633
635
  click: "click",
634
- scrollIntoView: "scrollIntoView",
635
- programmatic: "programmatic"
636
+ none: "none"
637
+ };
638
+ var PxOffScreenAction = {
639
+ pause: "pause",
640
+ continue: "continue",
641
+ reset: "reset"
636
642
  };
637
- var PxOutAction = {
643
+ var PxMouseOutAction = {
638
644
  continue: "continue",
639
645
  pause: "pause",
640
646
  reset: "reset",
@@ -702,16 +708,20 @@ function mayUseNativeScrollTimeline(engine) {
702
708
  return engine !== PxTimelineEngineSetting.js;
703
709
  }
704
710
  var PX_TRIGGER_DEFAULTS = {
705
- startOn: "load",
706
- outAction: "continue",
707
- scrollIntoViewThreshold: 0
711
+ start: "load",
712
+ offScreen: "pause",
713
+ mouseOut: "continue",
714
+ visibilityThreshold: 0.5,
715
+ visibilityDebounce: 150
708
716
  };
709
717
  function resolveTrigger(trigger) {
710
- var _a2, _b, _c;
718
+ var _a2, _b, _c, _d, _e;
711
719
  return {
712
- startOn: (_a2 = trigger == null ? void 0 : trigger.startOn) != null ? _a2 : PX_TRIGGER_DEFAULTS.startOn,
713
- outAction: (_b = trigger == null ? void 0 : trigger.outAction) != null ? _b : PX_TRIGGER_DEFAULTS.outAction,
714
- scrollIntoViewThreshold: (_c = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _c : PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold
720
+ start: (_a2 = trigger == null ? void 0 : trigger.start) != null ? _a2 : PX_TRIGGER_DEFAULTS.start,
721
+ offScreen: (_b = trigger == null ? void 0 : trigger.offScreen) != null ? _b : PX_TRIGGER_DEFAULTS.offScreen,
722
+ mouseOut: (_c = trigger == null ? void 0 : trigger.mouseOut) != null ? _c : PX_TRIGGER_DEFAULTS.mouseOut,
723
+ visibilityThreshold: (_d = trigger == null ? void 0 : trigger.visibilityThreshold) != null ? _d : PX_TRIGGER_DEFAULTS.visibilityThreshold,
724
+ visibilityDebounce: (_e = trigger == null ? void 0 : trigger.visibilityDebounce) != null ? _e : PX_TRIGGER_DEFAULTS.visibilityDebounce
715
725
  };
716
726
  }
717
727
  var PxLoopRepeatAt = {
@@ -847,9 +857,9 @@ function flattenAnimatorTimeline(cfg) {
847
857
  } else {
848
858
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
849
859
  if (timeline.trigger !== void 0) {
850
- const _b = timeline.trigger, { finishAction } = _b, restTrigger = __objRest2(_b, ["finishAction"]);
860
+ const _b = timeline.trigger, { finish } = _b, restTrigger = __objRest2(_b, ["finish"]);
851
861
  if (Object.keys(restTrigger).length) flat.trigger = restTrigger;
852
- if (finishAction !== void 0) flat.resetOnFinish = finishAction === "reset";
862
+ if (finish !== void 0) flat.resetOnFinish = finish === "reset";
853
863
  }
854
864
  if (timeline.delay !== void 0) flat.delay = timeline.delay;
855
865
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
@@ -948,13 +958,15 @@ var PxElementAnimationSchema = implementsInterface()(px.union([
948
958
  PxAnimationDefinitionSchema
949
959
  ]));
950
960
  var PxTriggerSchema = implementsInterface()(px.object({
951
- startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic], PX_TRIGGER_DEFAULTS.startOn).optional(),
952
- outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse], PX_TRIGGER_DEFAULTS.outAction).optional(),
961
+ start: px.enum([PxTriggerStart.load, PxTriggerStart.mouseOver, PxTriggerStart.click, PxTriggerStart.none], PX_TRIGGER_DEFAULTS.start).optional(),
962
+ offScreen: px.enum([PxOffScreenAction.pause, PxOffScreenAction.continue, PxOffScreenAction.reset], PX_TRIGGER_DEFAULTS.offScreen).optional(),
963
+ mouseOut: px.enum([PxMouseOutAction.continue, PxMouseOutAction.pause, PxMouseOutAction.reset, PxMouseOutAction.reverse], PX_TRIGGER_DEFAULTS.mouseOut).optional(),
953
964
  // What happens after a NATURAL finish — `'hold'` (default: keep the end state per
954
- // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` ("what
955
- // happens when the trigger condition ends"); both end-of-life knobs now read alike.
956
- finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
957
- scrollIntoViewThreshold: px.number().optional()
965
+ // `fill`) or `'reset'` (snap back to the start state). One of four occasion keys
966
+ // (`start`, `offScreen`, `mouseOut`, `finish`), all named the same way.
967
+ finish: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
968
+ visibilityThreshold: px.number().optional(),
969
+ visibilityDebounce: px.number().optional()
958
970
  }));
959
971
  var PxGlyphSchema = implementsInterface()(px.object({
960
972
  width: px.number(),
@@ -3986,32 +3998,34 @@ var PxDiagnosticKind = {
3986
3998
  /** The player failed where it did not expect to — report it to us. */
3987
3999
  internal: "internal"
3988
4000
  };
3989
- function asError(error) {
3990
- return typeof error === "string" ? new Error(error) : error;
4001
+ var DOCS_URL = "https://github.com/pixodesk/pixodesk-svg-animator/blob/main/docs/diagnostics.md";
4002
+ function codeLine(code) {
4003
+ return "PX" + code + " " + DOCS_URL + "#px" + code;
4004
+ }
4005
+ function errorIn(data) {
4006
+ return data.find((d) => d instanceof Error);
3991
4007
  }
3992
4008
  function createDiagnostics(config, prefix) {
3993
4009
  const tag = prefix ? prefix + " " : "";
3994
4010
  return {
3995
- warn: (kind, message, detail) => {
4011
+ warn: (kind, code, ...data) => {
4012
+ const message = codeLine(code);
3996
4013
  if (config == null ? void 0 : config.onWarn) {
3997
- config.onWarn({ kind, message, detail });
4014
+ config.onWarn({ code, kind, data, message });
3998
4015
  return;
3999
4016
  }
4000
4017
  if (config == null ? void 0 : config.muteWarn) return;
4001
- const line = tag + kind + ": " + message;
4002
- if (detail === void 0) console.warn(line);
4003
- else console.warn(line, detail);
4018
+ console.warn(tag + kind + " " + message, ...data);
4004
4019
  },
4005
- error: (kind, error, detail) => {
4006
- const err = asError(error);
4020
+ error: (kind, code, ...data) => {
4021
+ const message = codeLine(code);
4022
+ const error = errorIn(data);
4007
4023
  if (config == null ? void 0 : config.onError) {
4008
- config.onError({ kind, message: err.message, error: err, detail });
4024
+ config.onError({ code, kind, data, message, error });
4009
4025
  return;
4010
4026
  }
4011
4027
  if (config == null ? void 0 : config.muteError) return;
4012
- const line = tag + kind + ": " + err.message;
4013
- if (detail === void 0) console.error(line);
4014
- else console.error(line, detail);
4028
+ console.error(tag + kind + " " + message, ...data);
4015
4029
  }
4016
4030
  };
4017
4031
  }
@@ -6128,6 +6142,36 @@ function createAdapterAnimator(doc, adapter, callbacks) {
6128
6142
  };
6129
6143
  return api;
6130
6144
  }
6145
+ var PxDiagnosticCode = /* @__PURE__ */ ((PxDiagnosticCode2) => {
6146
+ PxDiagnosticCode2[PxDiagnosticCode2["buildFailed"] = 1001] = "buildFailed";
6147
+ PxDiagnosticCode2[PxDiagnosticCode2["invalidDocumentAtSrc"] = 1002] = "invalidDocumentAtSrc";
6148
+ PxDiagnosticCode2[PxDiagnosticCode2["loadFailed"] = 1003] = "loadFailed";
6149
+ PxDiagnosticCode2[PxDiagnosticCode2["animationBuildFailed"] = 1004] = "animationBuildFailed";
6150
+ PxDiagnosticCode2[PxDiagnosticCode2["effectsShape"] = 1101] = "effectsShape";
6151
+ PxDiagnosticCode2[PxDiagnosticCode2["timelineOverrideIgnored"] = 1102] = "timelineOverrideIgnored";
6152
+ PxDiagnosticCode2[PxDiagnosticCode2["blockedTag"] = 1103] = "blockedTag";
6153
+ PxDiagnosticCode2[PxDiagnosticCode2["unsupportedAnimatedAttrs"] = 1104] = "unsupportedAnimatedAttrs";
6154
+ PxDiagnosticCode2[PxDiagnosticCode2["noBindings"] = 1105] = "noBindings";
6155
+ PxDiagnosticCode2[PxDiagnosticCode2["unresolvedBinding"] = 1106] = "unresolvedBinding";
6156
+ PxDiagnosticCode2[PxDiagnosticCode2["triggersNoRoot"] = 1201] = "triggersNoRoot";
6157
+ PxDiagnosticCode2[PxDiagnosticCode2["noRootForSelector"] = 1202] = "noRootForSelector";
6158
+ PxDiagnosticCode2[PxDiagnosticCode2["noRootElement"] = 1203] = "noRootElement";
6159
+ PxDiagnosticCode2[PxDiagnosticCode2["noElementsForSelector"] = 1206] = "noElementsForSelector";
6160
+ PxDiagnosticCode2[PxDiagnosticCode2["setAttributeNoElement"] = 1207] = "setAttributeNoElement";
6161
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollSmoothingNeedsOwnDriver"] = 1301] = "scrollSmoothingNeedsOwnDriver";
6162
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollNativeUnavailable"] = 1302] = "scrollNativeUnavailable";
6163
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollSubjectInvalid"] = 1303] = "scrollSubjectInvalid";
6164
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollSubjectNoMatch"] = 1304] = "scrollSubjectNoMatch";
6165
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollNoRootToObserve"] = 1305] = "scrollNoRootToObserve";
6166
+ PxDiagnosticCode2[PxDiagnosticCode2["scrollTriggerIgnored"] = 1306] = "scrollTriggerIgnored";
6167
+ PxDiagnosticCode2[PxDiagnosticCode2["rateRejected"] = 1401] = "rateRejected";
6168
+ PxDiagnosticCode2[PxDiagnosticCode2["controlPropsConflict"] = 1501] = "controlPropsConflict";
6169
+ PxDiagnosticCode2[PxDiagnosticCode2["rnCompileFailed"] = 1601] = "rnCompileFailed";
6170
+ PxDiagnosticCode2[PxDiagnosticCode2["rnRenderFailed"] = 1602] = "rnRenderFailed";
6171
+ PxDiagnosticCode2[PxDiagnosticCode2["rnBoundaryCaught"] = 1603] = "rnBoundaryCaught";
6172
+ PxDiagnosticCode2[PxDiagnosticCode2["rnUnsupported"] = 1604] = "rnUnsupported";
6173
+ return PxDiagnosticCode2;
6174
+ })(PxDiagnosticCode || {});
6131
6175
  var FLAT_ONLY_KEYS = [
6132
6176
  "timelineSource",
6133
6177
  "scroll",
@@ -6202,16 +6246,16 @@ function foldTimelineOverride(timeline, shortcuts) {
6202
6246
  } else {
6203
6247
  base = timeline != null ? timeline : void 0;
6204
6248
  }
6205
- const { duration, delay, iterations, startOn } = shortcuts;
6206
- if (duration === void 0 && delay === void 0 && iterations === void 0 && startOn === void 0) {
6249
+ const { duration, delay, iterations, start } = shortcuts;
6250
+ if (duration === void 0 && delay === void 0 && iterations === void 0 && start === void 0) {
6207
6251
  return base === void 0 ? void 0 : { timeline: base };
6208
6252
  }
6209
6253
  const out = isPlainObject(base) ? __spreadValues2({}, base) : {};
6210
6254
  if (duration !== void 0) out.duration = duration;
6211
6255
  if (delay !== void 0) out.delay = delay;
6212
6256
  if (iterations !== void 0) out.iterations = iterations;
6213
- if (startOn !== void 0) {
6214
- out.trigger = __spreadProps2(__spreadValues2({}, out.trigger), { startOn });
6257
+ if (start !== void 0) {
6258
+ out.trigger = __spreadProps2(__spreadValues2({}, out.trigger), { start });
6215
6259
  }
6216
6260
  return { timeline: out };
6217
6261
  }
@@ -6358,6 +6402,202 @@ function asThrownError(e) {
6358
6402
  return e instanceof Error ? e : new Error(String(e));
6359
6403
  }
6360
6404
 
6405
+ // src/registry/PxAnimatorRegistry.ts
6406
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@pixodesk/svg-animator-web:animators");
6407
+ var GLOBAL_NAME = "__pixodeskAnimators";
6408
+ function store() {
6409
+ const g = globalThis;
6410
+ let s = g[STORE_KEY];
6411
+ if (!s) {
6412
+ const animators = /* @__PURE__ */ new Set();
6413
+ const listeners = /* @__PURE__ */ new Set();
6414
+ s = {
6415
+ animators,
6416
+ listeners,
6417
+ getAll: () => Array.from(animators),
6418
+ subscribe: (listener) => {
6419
+ listeners.add(listener);
6420
+ return () => {
6421
+ listeners.delete(listener);
6422
+ };
6423
+ }
6424
+ };
6425
+ g[STORE_KEY] = s;
6426
+ if (g[GLOBAL_NAME] === void 0) g[GLOBAL_NAME] = s;
6427
+ }
6428
+ return s;
6429
+ }
6430
+ function getAllAnimators() {
6431
+ return store().getAll();
6432
+ }
6433
+ function onAnimatorsChange(listener) {
6434
+ return store().subscribe(listener);
6435
+ }
6436
+ function notify(event, animator) {
6437
+ for (const listener of store().listeners) {
6438
+ try {
6439
+ listener(event, animator);
6440
+ } catch (e) {
6441
+ setTimeout(() => {
6442
+ throw e;
6443
+ }, 0);
6444
+ }
6445
+ }
6446
+ }
6447
+ function registerAnimator(animator) {
6448
+ const s = store();
6449
+ if (s.animators.has(animator)) return;
6450
+ s.animators.add(animator);
6451
+ notify("add", animator);
6452
+ const destroy = animator.destroy.bind(animator);
6453
+ animator.destroy = () => {
6454
+ destroy();
6455
+ if (s.animators.delete(animator)) notify("remove", animator);
6456
+ };
6457
+ }
6458
+ function withRegistryEvents(callbacks, api) {
6459
+ const fire = (event) => {
6460
+ const a = api();
6461
+ if (a) notify(event, a);
6462
+ };
6463
+ return __spreadProps(__spreadValues({}, callbacks), {
6464
+ onPlay: () => {
6465
+ var _a2;
6466
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6467
+ fire("play");
6468
+ },
6469
+ onPause: () => {
6470
+ var _a2;
6471
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
6472
+ fire("pause");
6473
+ },
6474
+ onCancel: () => {
6475
+ var _a2;
6476
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6477
+ fire("cancel");
6478
+ },
6479
+ onFinish: () => {
6480
+ var _a2;
6481
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6482
+ fire("finish");
6483
+ }
6484
+ });
6485
+ }
6486
+
6487
+ // src/triggers/PxVisibilityGate.ts
6488
+ var PLAY_WHEN_VISIBLE_DEFAULTS = {
6489
+ offScreen: PX_TRIGGER_DEFAULTS.offScreen,
6490
+ visibilityThreshold: PX_TRIGGER_DEFAULTS.visibilityThreshold,
6491
+ visibilityDebounce: PX_TRIGGER_DEFAULTS.visibilityDebounce
6492
+ };
6493
+ var THRESHOLD_STEPS = Array.from({ length: 21 }, (_, i) => i / 20);
6494
+ function effectiveRatio(entry) {
6495
+ var _a2, _b;
6496
+ const target = entry.boundingClientRect;
6497
+ const visible = entry.intersectionRect;
6498
+ if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
6499
+ const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
6500
+ const declared = (_b = (_a2 = entry.rootBounds) == null ? void 0 : _a2.height) != null ? _b : Infinity;
6501
+ const viewport = Math.min(live, declared);
6502
+ const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
6503
+ return denom > 0 ? visible.height / denom : entry.intersectionRatio;
6504
+ }
6505
+ function openGate(host) {
6506
+ return {
6507
+ requestStart: () => host.play(),
6508
+ dispose: () => {
6509
+ }
6510
+ };
6511
+ }
6512
+ function isGated(offScreen) {
6513
+ return offScreen !== PxOffScreenAction.continue;
6514
+ }
6515
+ function createVisibilityGate(root, trigger, host) {
6516
+ if (!isGated(trigger.offScreen)) return openGate(host);
6517
+ if (typeof IntersectionObserver === "undefined") return openGate(host);
6518
+ const threshold = trigger.visibilityThreshold;
6519
+ const debounceMs = trigger.visibilityDebounce;
6520
+ let isOpen = void 0;
6521
+ let startPending = false;
6522
+ let pausedByGate = false;
6523
+ let lastRatio = 0;
6524
+ let openTimer;
6525
+ const cancelPendingOpen = () => {
6526
+ if (openTimer !== void 0) {
6527
+ clearTimeout(openTimer);
6528
+ openTimer = void 0;
6529
+ }
6530
+ };
6531
+ const open = () => {
6532
+ openTimer = void 0;
6533
+ if (isOpen === true) return;
6534
+ isOpen = true;
6535
+ if (startPending || pausedByGate) {
6536
+ startPending = false;
6537
+ pausedByGate = false;
6538
+ host.play();
6539
+ }
6540
+ };
6541
+ const close = () => {
6542
+ cancelPendingOpen();
6543
+ if (isOpen === false) return;
6544
+ isOpen = false;
6545
+ if (!host.isPlaying()) return;
6546
+ if (trigger.offScreen === PxOffScreenAction.reset) {
6547
+ host.cancel();
6548
+ } else {
6549
+ host.pause();
6550
+ }
6551
+ pausedByGate = true;
6552
+ };
6553
+ const apply2 = (ratio) => {
6554
+ lastRatio = ratio;
6555
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
6556
+ close();
6557
+ return;
6558
+ }
6559
+ if (ratio >= threshold) {
6560
+ if (isOpen === true || openTimer !== void 0) return;
6561
+ if (debounceMs > 0) openTimer = setTimeout(open, debounceMs);
6562
+ else open();
6563
+ return;
6564
+ }
6565
+ if (ratio <= 0) {
6566
+ close();
6567
+ return;
6568
+ }
6569
+ if (isOpen !== true) cancelPendingOpen();
6570
+ };
6571
+ const observer = new IntersectionObserver((entries) => {
6572
+ const last = entries[entries.length - 1];
6573
+ if (last) apply2(last.isIntersecting ? effectiveRatio(last) : 0);
6574
+ }, { threshold: THRESHOLD_STEPS });
6575
+ observer.observe(root);
6576
+ const onVisibilityChange = () => {
6577
+ apply2(lastRatio);
6578
+ };
6579
+ const hasDocument = typeof document !== "undefined";
6580
+ if (hasDocument) document.addEventListener("visibilitychange", onVisibilityChange);
6581
+ return {
6582
+ requestStart: (immediate) => {
6583
+ if (immediate || isOpen === true) {
6584
+ cancelPendingOpen();
6585
+ isOpen = true;
6586
+ startPending = false;
6587
+ pausedByGate = false;
6588
+ host.play();
6589
+ return;
6590
+ }
6591
+ startPending = true;
6592
+ },
6593
+ dispose: () => {
6594
+ cancelPendingOpen();
6595
+ observer.disconnect();
6596
+ if (hasDocument) document.removeEventListener("visibilitychange", onVisibilityChange);
6597
+ }
6598
+ };
6599
+ }
6600
+
6361
6601
  // src/triggers/PxAnimatorTriggers.ts
6362
6602
  function setupAnimationTriggers(api, trigger, diag) {
6363
6603
  const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
@@ -6365,10 +6605,10 @@ function setupAnimationTriggers(api, trigger, diag) {
6365
6605
  const dispose = () => {
6366
6606
  for (const undo of cleanups.splice(0)) undo();
6367
6607
  };
6368
- const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);
6608
+ const resolved = resolveTrigger(trigger);
6369
6609
  const root = api.getRootElement();
6370
6610
  if (!root) {
6371
- report.warn(PxDiagnosticKind.host, "setupAnimationTriggers: No root element found for animation.");
6611
+ report.warn(PxDiagnosticKind.host, PxDiagnosticCode.triggersNoRoot);
6372
6612
  return dispose;
6373
6613
  }
6374
6614
  let reversed = false;
@@ -6379,27 +6619,34 @@ function setupAnimationTriggers(api, trigger, diag) {
6379
6619
  }
6380
6620
  api.play();
6381
6621
  };
6382
- const handleEndAction = () => {
6383
- switch (outAction) {
6384
- case "pause":
6622
+ const gate = createVisibilityGate(root, resolved, {
6623
+ isPlaying: () => api.isPlaying(),
6624
+ play: start,
6625
+ pause: () => api.pause(),
6626
+ cancel: () => api.cancel()
6627
+ });
6628
+ cleanups.push(() => gate.dispose());
6629
+ const handleMouseOut = () => {
6630
+ switch (resolved.mouseOut) {
6631
+ case PxMouseOutAction.pause:
6385
6632
  api.pause();
6386
6633
  break;
6387
- case "reset":
6634
+ case PxMouseOutAction.reset:
6388
6635
  api.cancel();
6389
6636
  break;
6390
- case "reverse":
6637
+ case PxMouseOutAction.reverse:
6391
6638
  reversed = true;
6392
6639
  api.setPlaybackRate(-1);
6393
6640
  api.play();
6394
6641
  break;
6395
- case "continue":
6642
+ case PxMouseOutAction.continue:
6396
6643
  default:
6397
6644
  break;
6398
6645
  }
6399
6646
  };
6400
- switch (startOn) {
6401
- case "load": {
6402
- const startHandler = () => start();
6647
+ switch (resolved.start) {
6648
+ case PxTriggerStart.load: {
6649
+ const startHandler = () => gate.requestStart(false);
6403
6650
  if (document.readyState === "complete") {
6404
6651
  startHandler();
6405
6652
  } else {
@@ -6408,14 +6655,14 @@ function setupAnimationTriggers(api, trigger, diag) {
6408
6655
  }
6409
6656
  break;
6410
6657
  }
6411
- case "mouseOver": {
6658
+ case PxTriggerStart.mouseOver: {
6412
6659
  let enteredOnce = false;
6413
6660
  const mouseOverHandler = () => {
6414
6661
  enteredOnce = true;
6415
- start();
6662
+ gate.requestStart(true);
6416
6663
  };
6417
6664
  const mouseOutHandler = () => {
6418
- if (enteredOnce) handleEndAction();
6665
+ if (enteredOnce) handleMouseOut();
6419
6666
  };
6420
6667
  root.addEventListener("mouseenter", mouseOverHandler);
6421
6668
  root.addEventListener("mouseleave", mouseOutHandler);
@@ -6425,51 +6672,16 @@ function setupAnimationTriggers(api, trigger, diag) {
6425
6672
  });
6426
6673
  break;
6427
6674
  }
6428
- case "click": {
6675
+ case PxTriggerStart.click: {
6429
6676
  const clickHandler = () => {
6430
- if (api.isPlaying()) {
6431
- handleEndAction();
6432
- } else {
6433
- start();
6434
- }
6677
+ if (api.isPlaying()) api.pause();
6678
+ else gate.requestStart(true);
6435
6679
  };
6436
6680
  root.addEventListener("click", clickHandler);
6437
6681
  cleanups.push(() => root.removeEventListener("click", clickHandler));
6438
6682
  break;
6439
6683
  }
6440
- case "scrollIntoView": {
6441
- const effectiveRatio = (entry) => {
6442
- var _a2, _b;
6443
- const target = entry.boundingClientRect;
6444
- const visible = entry.intersectionRect;
6445
- if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
6446
- const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
6447
- const declared = (_b = (_a2 = entry.rootBounds) == null ? void 0 : _a2.height) != null ? _b : Infinity;
6448
- const viewport = Math.min(live, declared);
6449
- const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
6450
- return denom > 0 ? visible.height / denom : entry.intersectionRatio;
6451
- };
6452
- const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);
6453
- let wasIntersecting = false;
6454
- const observer = new IntersectionObserver(
6455
- (entries) => {
6456
- entries.forEach((entry) => {
6457
- if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {
6458
- wasIntersecting = true;
6459
- start();
6460
- } else if (wasIntersecting) {
6461
- wasIntersecting = false;
6462
- handleEndAction();
6463
- }
6464
- });
6465
- },
6466
- { threshold: thresholdSteps }
6467
- );
6468
- observer.observe(root);
6469
- cleanups.push(() => observer.disconnect());
6470
- break;
6471
- }
6472
- case "programmatic":
6684
+ case PxTriggerStart.none:
6473
6685
  break;
6474
6686
  }
6475
6687
  return dispose;
@@ -6477,7 +6689,12 @@ function setupAnimationTriggers(api, trigger, diag) {
6477
6689
 
6478
6690
  // src/engines/PxAnimatorFrameLoop.ts
6479
6691
  function getSelector(id) {
6480
- return "#" + id;
6692
+ return "#" + escapeCssId(id);
6693
+ }
6694
+ function escapeCssId(id) {
6695
+ const css = globalThis.CSS;
6696
+ if (typeof (css == null ? void 0 : css.escape) === "function") return css.escape(id);
6697
+ return id.replace(/^([0-9])/, (_all, digit) => "\\3" + digit + " ").replace(/([^\w\-\\ ])/g, "\\$1");
6481
6698
  }
6482
6699
  function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6483
6700
  var _a2;
@@ -6487,9 +6704,9 @@ function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6487
6704
  if (doc.id) {
6488
6705
  const rootSelector = getSelector(doc.id);
6489
6706
  rootElement = document.querySelector(rootSelector);
6490
- if (!rootElement) diag.warn(PxDiagnosticKind.host, "createFrameLoopAnimator: No root element found for selector: " + rootSelector);
6707
+ if (!rootElement) diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootForSelector, rootSelector);
6491
6708
  } else {
6492
- diag.warn(PxDiagnosticKind.host, "createFrameLoopAnimator: No root element provided");
6709
+ diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootElement);
6493
6710
  }
6494
6711
  }
6495
6712
  const basicApi = createAdapterAnimator(
@@ -6501,7 +6718,7 @@ function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6501
6718
  "getRootElement": () => rootElement || null
6502
6719
  });
6503
6720
  if (isScrollTimeline(config)) {
6504
- if (config.trigger) diag.warn(PxDiagnosticKind.usage, "scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
6721
+ if (config.trigger) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.scrollTriggerIgnored);
6505
6722
  } else {
6506
6723
  const detachTriggers = setupAnimationTriggers(api, (_a2 = config.trigger) != null ? _a2 : {}, diag);
6507
6724
  const destroyEngine = api.destroy.bind(api);
@@ -6526,7 +6743,7 @@ function createDomAdapter(rootElement, diag) {
6526
6743
  const elements = (rootElement == null ? void 0 : rootElement.querySelectorAll(selector)) || document.querySelectorAll(selector);
6527
6744
  if (elements.length === 0 && !warnedSelectors.has(selector)) {
6528
6745
  warnedSelectors.add(selector);
6529
- report.warn(PxDiagnosticKind.host, 'setAttribute: No elements found for selector "' + selector + '"');
6746
+ report.warn(PxDiagnosticKind.host, PxDiagnosticCode.setAttributeNoElement, selector);
6530
6747
  }
6531
6748
  for (let i = 0; i < elements.length; i++) {
6532
6749
  const element = elements[i];
@@ -6645,9 +6862,9 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6645
6862
  if (doc.id) {
6646
6863
  const rootSelector = getSelector(doc.id);
6647
6864
  rootElement = document.querySelector(rootSelector);
6648
- if (!rootElement) diag.warn(PxDiagnosticKind.host, "createWebApiAnimator: No root element found for selector: " + rootSelector);
6865
+ if (!rootElement) diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootForSelector, rootSelector);
6649
6866
  } else {
6650
- diag.warn(PxDiagnosticKind.host, "createWebApiAnimator: No root element provided");
6867
+ diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootElement);
6651
6868
  }
6652
6869
  }
6653
6870
  const bindings = normalizeBindings(doc, PxTimelineEngine.native);
@@ -6660,18 +6877,18 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6660
6877
  let finishNotified = false;
6661
6878
  let removeCalled = false;
6662
6879
  if (!(bindings == null ? void 0 : bindings.length)) {
6663
- diag.warn(PxDiagnosticKind.document, "createWebApiAnimator: No animation bindings defined");
6880
+ diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.noBindings);
6664
6881
  }
6665
6882
  for (const binding of bindings || []) {
6666
6883
  const animDef = binding.animate;
6667
6884
  if (!animDef || typeof animDef !== "object" || Array.isArray(animDef)) {
6668
- diag.warn(PxDiagnosticKind.document, "createWebApiAnimator: Empty or unresolved binding", binding);
6885
+ diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.unresolvedBinding, binding);
6669
6886
  continue;
6670
6887
  }
6671
6888
  const selector = getSelector(binding.id);
6672
6889
  const elements = (rootElement == null ? void 0 : rootElement.querySelectorAll(selector)) || document.querySelectorAll(selector);
6673
6890
  if (elements.length === 0) {
6674
- diag.warn(PxDiagnosticKind.host, 'createWebApiAnimator: No elements found for selector "' + selector + '"');
6891
+ diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noElementsForSelector, selector);
6675
6892
  }
6676
6893
  const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);
6677
6894
  const positiveDelay = config.delay && config.delay > 0 ? config.delay : void 0;
@@ -6720,14 +6937,14 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6720
6937
  }
6721
6938
  animations.push(anim);
6722
6939
  } catch (e) {
6723
- diag.warn(PxDiagnosticKind.internal, "createWebApiAnimator: could not build the animation", e);
6940
+ diag.warn(PxDiagnosticKind.internal, PxDiagnosticCode.animationBuildFailed, e);
6724
6941
  }
6725
6942
  }
6726
6943
  }
6727
6944
  }
6728
6945
  }
6729
6946
  if (!forceEvenIfHasUnsupportedAttrs && unsupportedSet.size) {
6730
- diag.warn(PxDiagnosticKind.platform, "Unsupported CSS attrs: " + [...unsupportedSet].join(", "));
6947
+ diag.warn(PxDiagnosticKind.platform, PxDiagnosticCode.unsupportedAnimatedAttrs, [...unsupportedSet].join(", "));
6731
6948
  return null;
6732
6949
  }
6733
6950
  const api = {
@@ -6772,7 +6989,7 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6772
6989
  },
6773
6990
  "setPlaybackRate": (rate) => {
6774
6991
  if (!isValidPlaybackRate(rate)) {
6775
- diag.warn(PxDiagnosticKind.usage, PX_RATE_REJECTED);
6992
+ diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.rateRejected);
6776
6993
  return api;
6777
6994
  }
6778
6995
  animations.forEach((a) => a.playbackRate = rate);
@@ -6812,7 +7029,7 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6812
7029
  }
6813
7030
  };
6814
7031
  if (config.timelineSource === "scroll") {
6815
- if (config.trigger) diag.warn(PxDiagnosticKind.usage, "scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
7032
+ if (config.trigger) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.scrollTriggerIgnored);
6816
7033
  } else {
6817
7034
  const detachTriggers = setupAnimationTriggers(api, (_b = config.trigger) != null ? _b : {}, diag);
6818
7035
  const destroyEngine = api.destroy.bind(api);
@@ -6842,7 +7059,7 @@ function createNativeScrollTimeline(subject, config, diag) {
6842
7059
  const scroll = config.scroll || {};
6843
7060
  const kind = (_a2 = scroll.kind) != null ? _a2 : "view";
6844
7061
  if (scroll.smoothing) {
6845
- report.warn(PxDiagnosticKind.platform, "scroll timeline: `smoothing` needs the built-in driver \u2014 using the built-in driver instead of the browser timeline");
7062
+ report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollSmoothingNeedsOwnDriver);
6846
7063
  return null;
6847
7064
  }
6848
7065
  const g = globalThis;
@@ -6859,7 +7076,7 @@ function createNativeScrollTimeline(subject, config, diag) {
6859
7076
  timeline = new Ctor({ source, axis });
6860
7077
  }
6861
7078
  } catch (e) {
6862
- report.warn(PxDiagnosticKind.platform, "scroll timeline: native timeline construction failed \u2014 falling back to the player measuring progress itself", e);
7079
+ report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollNativeUnavailable, e);
6863
7080
  return null;
6864
7081
  }
6865
7082
  return {
@@ -6906,11 +7123,11 @@ function resolveScrollSubject(svgRoot, subject, diag) {
6906
7123
  try {
6907
7124
  found = document.querySelector(spec);
6908
7125
  } catch (e) {
6909
- report.warn(PxDiagnosticKind.document, 'scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
7126
+ report.warn(PxDiagnosticKind.document, PxDiagnosticCode.scrollSubjectInvalid, spec);
6910
7127
  return svgRoot;
6911
7128
  }
6912
7129
  if (!found) {
6913
- report.warn(PxDiagnosticKind.host, 'scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
7130
+ report.warn(PxDiagnosticKind.host, PxDiagnosticCode.scrollSubjectNoMatch, spec);
6914
7131
  return svgRoot;
6915
7132
  }
6916
7133
  return found;
@@ -7152,7 +7369,7 @@ function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
7152
7369
  };
7153
7370
  }
7154
7371
  } else {
7155
- diag.warn(PxDiagnosticKind.host, "scroll timeline: no root element to observe \u2014 animation will stay at frame 0");
7372
+ diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.scrollNoRootToObserve);
7156
7373
  }
7157
7374
  return api;
7158
7375
  });
@@ -7174,7 +7391,7 @@ function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
7174
7391
  var SVG_NS = "http://www.w3.org/2000/svg";
7175
7392
  function createElement(tagName, normalizedProps, style, children, textContent, diag) {
7176
7393
  if (PX_DISALLOWED_SVG_TAGS_LOWER.has(tagName.toLowerCase())) {
7177
- (diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]")).warn(PxDiagnosticKind.document, "SVG tag blocked (dangerous): " + tagName);
7394
+ (diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]")).warn(PxDiagnosticKind.document, PxDiagnosticCode.blockedTag, tagName);
7178
7395
  return null;
7179
7396
  }
7180
7397
  const element = document.createElementNS(SVG_NS, tagName);
@@ -7237,11 +7454,11 @@ function createAnimatorFromConfig(doc, adapter, callbacks, rootElement) {
7237
7454
  function createAnimatorImpl(doc, adapter, callbacks, containerElement, patch, resetTimeline) {
7238
7455
  const diag = createDiagnostics(callbacks, "[PxAnimator]");
7239
7456
  const effectsWarnings = validateNodeEffects(doc);
7240
- for (const w of effectsWarnings) diag.warn(PxDiagnosticKind.document, "effects shape: " + w);
7457
+ for (const w of effectsWarnings) diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.effectsShape, w);
7241
7458
  reportDocumentDiagnostics(doc, "[PxAnimator] createAnimator");
7242
7459
  if (patch !== void 0 || resetTimeline) {
7243
7460
  const patched = applyAnimatorConfig(doc, patch != null ? patch : {}, { resetTimeline: !!resetTimeline });
7244
- for (const w of patched.warnings) diag.warn(PxDiagnosticKind.usage, "timeline override: " + w);
7461
+ for (const w of patched.warnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.timelineOverrideIgnored, w);
7245
7462
  doc = patched.doc;
7246
7463
  }
7247
7464
  const animatorConfig = getAnimatorConfig(doc) || {};
@@ -7273,14 +7490,15 @@ function isInternalOptions(options) {
7273
7490
  return "adapter" in options;
7274
7491
  }
7275
7492
  function resolveTimelineOption(options) {
7276
- const { timeline, duration, delay, iterations, startOn } = options;
7277
- return foldTimelineOverride(timeline, { duration, delay, iterations, startOn });
7493
+ const { timeline, duration, delay, iterations, start } = options;
7494
+ return foldTimelineOverride(timeline, { duration, delay, iterations, start });
7278
7495
  }
7279
7496
  function createAnimator(options) {
7280
7497
  const { src, doc, container, resetTimeline } = options;
7281
7498
  const adapter = isInternalOptions(options) ? options.adapter : void 0;
7282
7499
  const patch = resolveTimelineOption(options);
7283
- const callbacks = toEngineCallbacks(options);
7500
+ let proxy;
7501
+ const callbacks = withRegistryEvents(toEngineCallbacks(options), () => proxy);
7284
7502
  if (doc !== void 0 && src !== void 0) {
7285
7503
  throw new Error("createAnimator: provide either `src` or `doc`, not both");
7286
7504
  }
@@ -7304,31 +7522,19 @@ function createAnimator(options) {
7304
7522
  pending = null;
7305
7523
  queued == null ? void 0 : queued.forEach((call) => call(api));
7306
7524
  };
7307
- const failed = (kind, message, detail) => {
7525
+ const failed = (kind, code, ...data) => {
7308
7526
  pending = null;
7309
- diag.error(kind, message, detail);
7527
+ diag.error(kind, code, ...data);
7310
7528
  };
7311
7529
  const build = (document2) => {
7312
7530
  try {
7313
7531
  ready(createAnimatorImpl(document2, adapter, callbacks, container, patch, resetTimeline));
7314
7532
  } catch (e) {
7315
7533
  const err = asThrownError(e);
7316
- failed(PxDiagnosticKind.internal, "createAnimator: could not build the player \u2014 " + err.message, err);
7534
+ failed(PxDiagnosticKind.internal, PxDiagnosticCode.buildFailed, err);
7317
7535
  }
7318
7536
  };
7319
- if (doc !== void 0) {
7320
- build(doc);
7321
- } else {
7322
- fetch(src).then((res) => res.json()).then((json) => {
7323
- if (destroyed) return;
7324
- if (isPxDocument(json)) build(json);
7325
- else failed(PxDiagnosticKind.document, 'createAnimator: invalid animation document format at "' + src + '"');
7326
- }).catch((err) => {
7327
- var _a2;
7328
- failed(PxDiagnosticKind.host, 'createAnimator: failed to load "' + src + '" \u2014 ' + ((_a2 = err == null ? void 0 : err.message) != null ? _a2 : String(err)));
7329
- });
7330
- }
7331
- return {
7537
+ proxy = {
7332
7538
  "isReady": () => !!animator,
7333
7539
  "getRootElement": () => animator ? animator.getRootElement() : null,
7334
7540
  "isPlaying": () => (animator == null ? void 0 : animator.isPlaying()) || false,
@@ -7361,6 +7567,20 @@ function createAnimator(options) {
7361
7567
  animator == null ? void 0 : animator.destroy();
7362
7568
  }
7363
7569
  };
7570
+ registerAnimator(proxy);
7571
+ if (doc !== void 0) {
7572
+ build(doc);
7573
+ } else {
7574
+ fetch(src).then((res) => res.json()).then((json) => {
7575
+ if (destroyed) return;
7576
+ if (isPxDocument(json)) build(json);
7577
+ else failed(PxDiagnosticKind.document, PxDiagnosticCode.invalidDocumentAtSrc, src);
7578
+ }).catch((err) => {
7579
+ var _a2;
7580
+ failed(PxDiagnosticKind.host, PxDiagnosticCode.loadFailed, src, (_a2 = err == null ? void 0 : err.message) != null ? _a2 : String(err));
7581
+ });
7582
+ }
7583
+ return proxy;
7364
7584
  }
7365
7585
  function loadTagAnimators(options) {
7366
7586
  const elements = document.querySelectorAll("[" + PX_ANIM_SRC_ATTR_NAME + "]");
@@ -7387,7 +7607,9 @@ function loadTagAnimators(options) {
7387
7607
  createAdapterAnimator,
7388
7608
  createAnimator,
7389
7609
  generateNewIds,
7610
+ getAllAnimators,
7390
7611
  loadTagAnimators,
7612
+ onAnimatorsChange,
7391
7613
  px,
7392
7614
  renderNode,
7393
7615
  setupAnimationTriggers,