@pixodesk/svg-animator-web 1.0.43 → 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.
@@ -575,14 +575,18 @@ var PixodeskAnimator = (() => {
575
575
  alternate: "alternate",
576
576
  alternateReverse: "alternate-reverse"
577
577
  };
578
- var PxStartOn = {
578
+ var PxTriggerStart = {
579
579
  load: "load",
580
580
  mouseOver: "mouseOver",
581
581
  click: "click",
582
- scrollIntoView: "scrollIntoView",
583
- programmatic: "programmatic"
582
+ none: "none"
584
583
  };
585
- var PxOutAction = {
584
+ var PxOffScreenAction = {
585
+ pause: "pause",
586
+ continue: "continue",
587
+ reset: "reset"
588
+ };
589
+ var PxMouseOutAction = {
586
590
  continue: "continue",
587
591
  pause: "pause",
588
592
  reset: "reset",
@@ -641,16 +645,20 @@ var PixodeskAnimator = (() => {
641
645
  auto: "auto"
642
646
  });
643
647
  var PX_TRIGGER_DEFAULTS = {
644
- startOn: "load",
645
- outAction: "continue",
646
- scrollIntoViewThreshold: 0
648
+ start: "load",
649
+ offScreen: "pause",
650
+ mouseOut: "continue",
651
+ visibilityThreshold: 0.5,
652
+ visibilityDebounce: 150
647
653
  };
648
654
  function resolveTrigger(trigger) {
649
- var _a, _b, _c;
655
+ var _a, _b, _c, _d, _e;
650
656
  return {
651
- startOn: (_a = trigger == null ? void 0 : trigger.startOn) != null ? _a : PX_TRIGGER_DEFAULTS.startOn,
652
- outAction: (_b = trigger == null ? void 0 : trigger.outAction) != null ? _b : PX_TRIGGER_DEFAULTS.outAction,
653
- scrollIntoViewThreshold: (_c = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _c : PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold
657
+ start: (_a = trigger == null ? void 0 : trigger.start) != null ? _a : PX_TRIGGER_DEFAULTS.start,
658
+ offScreen: (_b = trigger == null ? void 0 : trigger.offScreen) != null ? _b : PX_TRIGGER_DEFAULTS.offScreen,
659
+ mouseOut: (_c = trigger == null ? void 0 : trigger.mouseOut) != null ? _c : PX_TRIGGER_DEFAULTS.mouseOut,
660
+ visibilityThreshold: (_d = trigger == null ? void 0 : trigger.visibilityThreshold) != null ? _d : PX_TRIGGER_DEFAULTS.visibilityThreshold,
661
+ visibilityDebounce: (_e = trigger == null ? void 0 : trigger.visibilityDebounce) != null ? _e : PX_TRIGGER_DEFAULTS.visibilityDebounce
654
662
  };
655
663
  }
656
664
  var PxLoopRepeatAt = {
@@ -768,9 +776,9 @@ var PixodeskAnimator = (() => {
768
776
  } else {
769
777
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
770
778
  if (timeline.trigger !== void 0) {
771
- const _b = timeline.trigger, { finishAction } = _b, restTrigger = __objRest(_b, ["finishAction"]);
779
+ const _b = timeline.trigger, { finish } = _b, restTrigger = __objRest(_b, ["finish"]);
772
780
  if (Object.keys(restTrigger).length) flat.trigger = restTrigger;
773
- if (finishAction !== void 0) flat.resetOnFinish = finishAction === "reset";
781
+ if (finish !== void 0) flat.resetOnFinish = finish === "reset";
774
782
  }
775
783
  if (timeline.delay !== void 0) flat.delay = timeline.delay;
776
784
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
@@ -871,13 +879,15 @@ var PixodeskAnimator = (() => {
871
879
  PxAnimationDefinitionSchema
872
880
  ]));
873
881
  var PxTriggerSchema = implementsInterface()(px.object({
874
- startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic], PX_TRIGGER_DEFAULTS.startOn).optional(),
875
- outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse], PX_TRIGGER_DEFAULTS.outAction).optional(),
882
+ start: px.enum([PxTriggerStart.load, PxTriggerStart.mouseOver, PxTriggerStart.click, PxTriggerStart.none], PX_TRIGGER_DEFAULTS.start).optional(),
883
+ offScreen: px.enum([PxOffScreenAction.pause, PxOffScreenAction.continue, PxOffScreenAction.reset], PX_TRIGGER_DEFAULTS.offScreen).optional(),
884
+ mouseOut: px.enum([PxMouseOutAction.continue, PxMouseOutAction.pause, PxMouseOutAction.reset, PxMouseOutAction.reverse], PX_TRIGGER_DEFAULTS.mouseOut).optional(),
876
885
  // What happens after a NATURAL finish — `'hold'` (default: keep the end state per
877
- // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` ("what
878
- // happens when the trigger condition ends"); both end-of-life knobs now read alike.
879
- finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
880
- scrollIntoViewThreshold: px.number().optional()
886
+ // `fill`) or `'reset'` (snap back to the start state). One of four occasion keys
887
+ // (`start`, `offScreen`, `mouseOut`, `finish`), all named the same way.
888
+ finish: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
889
+ visibilityThreshold: px.number().optional(),
890
+ visibilityDebounce: px.number().optional()
881
891
  }));
882
892
  var PxGlyphSchema = implementsInterface()(px.object({
883
893
  width: px.number(),
@@ -2565,6 +2575,196 @@ var PixodeskAnimator = (() => {
2565
2575
  return e instanceof Error ? e : new Error(String(e));
2566
2576
  }
2567
2577
 
2578
+ // src/registry/PxAnimatorRegistry.ts
2579
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@pixodesk/svg-animator-web:animators");
2580
+ var GLOBAL_NAME = "__pixodeskAnimators";
2581
+ function store() {
2582
+ const g = globalThis;
2583
+ let s = g[STORE_KEY];
2584
+ if (!s) {
2585
+ const animators = /* @__PURE__ */ new Set();
2586
+ const listeners = /* @__PURE__ */ new Set();
2587
+ s = {
2588
+ animators,
2589
+ listeners,
2590
+ getAll: () => Array.from(animators),
2591
+ subscribe: (listener) => {
2592
+ listeners.add(listener);
2593
+ return () => {
2594
+ listeners.delete(listener);
2595
+ };
2596
+ }
2597
+ };
2598
+ g[STORE_KEY] = s;
2599
+ if (g[GLOBAL_NAME] === void 0) g[GLOBAL_NAME] = s;
2600
+ }
2601
+ return s;
2602
+ }
2603
+ function notify(event, animator) {
2604
+ for (const listener of store().listeners) {
2605
+ try {
2606
+ listener(event, animator);
2607
+ } catch (e) {
2608
+ setTimeout(() => {
2609
+ throw e;
2610
+ }, 0);
2611
+ }
2612
+ }
2613
+ }
2614
+ function registerAnimator(animator) {
2615
+ const s = store();
2616
+ if (s.animators.has(animator)) return;
2617
+ s.animators.add(animator);
2618
+ notify("add", animator);
2619
+ const destroy = animator.destroy.bind(animator);
2620
+ animator.destroy = () => {
2621
+ destroy();
2622
+ if (s.animators.delete(animator)) notify("remove", animator);
2623
+ };
2624
+ }
2625
+ function withRegistryEvents(callbacks, api) {
2626
+ const fire = (event) => {
2627
+ const a = api();
2628
+ if (a) notify(event, a);
2629
+ };
2630
+ return __spreadProps(__spreadValues({}, callbacks), {
2631
+ onPlay: () => {
2632
+ var _a;
2633
+ (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
2634
+ fire("play");
2635
+ },
2636
+ onPause: () => {
2637
+ var _a;
2638
+ (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
2639
+ fire("pause");
2640
+ },
2641
+ onCancel: () => {
2642
+ var _a;
2643
+ (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
2644
+ fire("cancel");
2645
+ },
2646
+ onFinish: () => {
2647
+ var _a;
2648
+ (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
2649
+ fire("finish");
2650
+ }
2651
+ });
2652
+ }
2653
+
2654
+ // src/triggers/PxVisibilityGate.ts
2655
+ var PLAY_WHEN_VISIBLE_DEFAULTS = {
2656
+ offScreen: PX_TRIGGER_DEFAULTS.offScreen,
2657
+ visibilityThreshold: PX_TRIGGER_DEFAULTS.visibilityThreshold,
2658
+ visibilityDebounce: PX_TRIGGER_DEFAULTS.visibilityDebounce
2659
+ };
2660
+ var THRESHOLD_STEPS = Array.from({ length: 21 }, (_, i) => i / 20);
2661
+ function effectiveRatio(entry) {
2662
+ var _a, _b;
2663
+ const target = entry.boundingClientRect;
2664
+ const visible = entry.intersectionRect;
2665
+ if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
2666
+ const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
2667
+ const declared = (_b = (_a = entry.rootBounds) == null ? void 0 : _a.height) != null ? _b : Infinity;
2668
+ const viewport = Math.min(live, declared);
2669
+ const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
2670
+ return denom > 0 ? visible.height / denom : entry.intersectionRatio;
2671
+ }
2672
+ function openGate(host) {
2673
+ return {
2674
+ requestStart: () => host.play(),
2675
+ dispose: () => {
2676
+ }
2677
+ };
2678
+ }
2679
+ function isGated(offScreen) {
2680
+ return offScreen !== PxOffScreenAction.continue;
2681
+ }
2682
+ function createVisibilityGate(root, trigger, host) {
2683
+ if (!isGated(trigger.offScreen)) return openGate(host);
2684
+ if (typeof IntersectionObserver === "undefined") return openGate(host);
2685
+ const threshold = trigger.visibilityThreshold;
2686
+ const debounceMs = trigger.visibilityDebounce;
2687
+ let isOpen = void 0;
2688
+ let startPending = false;
2689
+ let pausedByGate = false;
2690
+ let lastRatio = 0;
2691
+ let openTimer;
2692
+ const cancelPendingOpen = () => {
2693
+ if (openTimer !== void 0) {
2694
+ clearTimeout(openTimer);
2695
+ openTimer = void 0;
2696
+ }
2697
+ };
2698
+ const open = () => {
2699
+ openTimer = void 0;
2700
+ if (isOpen === true) return;
2701
+ isOpen = true;
2702
+ if (startPending || pausedByGate) {
2703
+ startPending = false;
2704
+ pausedByGate = false;
2705
+ host.play();
2706
+ }
2707
+ };
2708
+ const close = () => {
2709
+ cancelPendingOpen();
2710
+ if (isOpen === false) return;
2711
+ isOpen = false;
2712
+ if (!host.isPlaying()) return;
2713
+ if (trigger.offScreen === PxOffScreenAction.reset) {
2714
+ host.cancel();
2715
+ } else {
2716
+ host.pause();
2717
+ }
2718
+ pausedByGate = true;
2719
+ };
2720
+ const apply = (ratio) => {
2721
+ lastRatio = ratio;
2722
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
2723
+ close();
2724
+ return;
2725
+ }
2726
+ if (ratio >= threshold) {
2727
+ if (isOpen === true || openTimer !== void 0) return;
2728
+ if (debounceMs > 0) openTimer = setTimeout(open, debounceMs);
2729
+ else open();
2730
+ return;
2731
+ }
2732
+ if (ratio <= 0) {
2733
+ close();
2734
+ return;
2735
+ }
2736
+ if (isOpen !== true) cancelPendingOpen();
2737
+ };
2738
+ const observer = new IntersectionObserver((entries) => {
2739
+ const last = entries[entries.length - 1];
2740
+ if (last) apply(last.isIntersecting ? effectiveRatio(last) : 0);
2741
+ }, { threshold: THRESHOLD_STEPS });
2742
+ observer.observe(root);
2743
+ const onVisibilityChange = () => {
2744
+ apply(lastRatio);
2745
+ };
2746
+ const hasDocument = typeof document !== "undefined";
2747
+ if (hasDocument) document.addEventListener("visibilitychange", onVisibilityChange);
2748
+ return {
2749
+ requestStart: (immediate) => {
2750
+ if (immediate || isOpen === true) {
2751
+ cancelPendingOpen();
2752
+ isOpen = true;
2753
+ startPending = false;
2754
+ pausedByGate = false;
2755
+ host.play();
2756
+ return;
2757
+ }
2758
+ startPending = true;
2759
+ },
2760
+ dispose: () => {
2761
+ cancelPendingOpen();
2762
+ observer.disconnect();
2763
+ if (hasDocument) document.removeEventListener("visibilitychange", onVisibilityChange);
2764
+ }
2765
+ };
2766
+ }
2767
+
2568
2768
  // src/triggers/PxAnimatorTriggers.ts
2569
2769
  function setupAnimationTriggers(api, trigger, diag) {
2570
2770
  const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
@@ -2572,7 +2772,7 @@ var PixodeskAnimator = (() => {
2572
2772
  const dispose = () => {
2573
2773
  for (const undo of cleanups.splice(0)) undo();
2574
2774
  };
2575
- const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);
2775
+ const resolved = resolveTrigger(trigger);
2576
2776
  const root = api.getRootElement();
2577
2777
  if (!root) {
2578
2778
  report.warn(PxDiagnosticKind.host, 1201 /* triggersNoRoot */);
@@ -2586,27 +2786,34 @@ var PixodeskAnimator = (() => {
2586
2786
  }
2587
2787
  api.play();
2588
2788
  };
2589
- const handleEndAction = () => {
2590
- switch (outAction) {
2591
- case "pause":
2789
+ const gate = createVisibilityGate(root, resolved, {
2790
+ isPlaying: () => api.isPlaying(),
2791
+ play: start,
2792
+ pause: () => api.pause(),
2793
+ cancel: () => api.cancel()
2794
+ });
2795
+ cleanups.push(() => gate.dispose());
2796
+ const handleMouseOut = () => {
2797
+ switch (resolved.mouseOut) {
2798
+ case PxMouseOutAction.pause:
2592
2799
  api.pause();
2593
2800
  break;
2594
- case "reset":
2801
+ case PxMouseOutAction.reset:
2595
2802
  api.cancel();
2596
2803
  break;
2597
- case "reverse":
2804
+ case PxMouseOutAction.reverse:
2598
2805
  reversed = true;
2599
2806
  api.setPlaybackRate(-1);
2600
2807
  api.play();
2601
2808
  break;
2602
- case "continue":
2809
+ case PxMouseOutAction.continue:
2603
2810
  default:
2604
2811
  break;
2605
2812
  }
2606
2813
  };
2607
- switch (startOn) {
2608
- case "load": {
2609
- const startHandler = () => start();
2814
+ switch (resolved.start) {
2815
+ case PxTriggerStart.load: {
2816
+ const startHandler = () => gate.requestStart(false);
2610
2817
  if (document.readyState === "complete") {
2611
2818
  startHandler();
2612
2819
  } else {
@@ -2615,14 +2822,14 @@ var PixodeskAnimator = (() => {
2615
2822
  }
2616
2823
  break;
2617
2824
  }
2618
- case "mouseOver": {
2825
+ case PxTriggerStart.mouseOver: {
2619
2826
  let enteredOnce = false;
2620
2827
  const mouseOverHandler = () => {
2621
2828
  enteredOnce = true;
2622
- start();
2829
+ gate.requestStart(true);
2623
2830
  };
2624
2831
  const mouseOutHandler = () => {
2625
- if (enteredOnce) handleEndAction();
2832
+ if (enteredOnce) handleMouseOut();
2626
2833
  };
2627
2834
  root.addEventListener("mouseenter", mouseOverHandler);
2628
2835
  root.addEventListener("mouseleave", mouseOutHandler);
@@ -2632,51 +2839,16 @@ var PixodeskAnimator = (() => {
2632
2839
  });
2633
2840
  break;
2634
2841
  }
2635
- case "click": {
2842
+ case PxTriggerStart.click: {
2636
2843
  const clickHandler = () => {
2637
- if (api.isPlaying()) {
2638
- handleEndAction();
2639
- } else {
2640
- start();
2641
- }
2844
+ if (api.isPlaying()) api.pause();
2845
+ else gate.requestStart(true);
2642
2846
  };
2643
2847
  root.addEventListener("click", clickHandler);
2644
2848
  cleanups.push(() => root.removeEventListener("click", clickHandler));
2645
2849
  break;
2646
2850
  }
2647
- case "scrollIntoView": {
2648
- const effectiveRatio = (entry) => {
2649
- var _a, _b;
2650
- const target = entry.boundingClientRect;
2651
- const visible = entry.intersectionRect;
2652
- if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
2653
- const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
2654
- const declared = (_b = (_a = entry.rootBounds) == null ? void 0 : _a.height) != null ? _b : Infinity;
2655
- const viewport = Math.min(live, declared);
2656
- const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
2657
- return denom > 0 ? visible.height / denom : entry.intersectionRatio;
2658
- };
2659
- const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);
2660
- let wasIntersecting = false;
2661
- const observer = new IntersectionObserver(
2662
- (entries) => {
2663
- entries.forEach((entry) => {
2664
- if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {
2665
- wasIntersecting = true;
2666
- start();
2667
- } else if (wasIntersecting) {
2668
- wasIntersecting = false;
2669
- handleEndAction();
2670
- }
2671
- });
2672
- },
2673
- { threshold: thresholdSteps }
2674
- );
2675
- observer.observe(root);
2676
- cleanups.push(() => observer.disconnect());
2677
- break;
2678
- }
2679
- case "programmatic":
2851
+ case PxTriggerStart.none:
2680
2852
  break;
2681
2853
  }
2682
2854
  return dispose;
@@ -2684,7 +2856,12 @@ var PixodeskAnimator = (() => {
2684
2856
 
2685
2857
  // src/engines/PxAnimatorFrameLoop.ts
2686
2858
  function getSelector(id) {
2687
- return "#" + id;
2859
+ return "#" + escapeCssId(id);
2860
+ }
2861
+ function escapeCssId(id) {
2862
+ const css = globalThis.CSS;
2863
+ if (typeof (css == null ? void 0 : css.escape) === "function") return css.escape(id);
2864
+ return id.replace(/^([0-9])/, (_all, digit) => "\\3" + digit + " ").replace(/([^\w\-\\ ])/g, "\\$1");
2688
2865
  }
2689
2866
 
2690
2867
  // src/engines/PxAnimatorWebApi.ts
@@ -3006,16 +3183,21 @@ var PixodeskAnimator = (() => {
3006
3183
  return createInertAnimator();
3007
3184
  }
3008
3185
  }
3186
+ function registered(options, build) {
3187
+ let apiRef;
3188
+ const api = build(withRegistryEvents(toEngineCallbacks(options), () => apiRef));
3189
+ if (api.isReady()) {
3190
+ apiRef = api;
3191
+ registerAnimator(api);
3192
+ }
3193
+ return api;
3194
+ }
3009
3195
  function createPrerenderedWaapiAnimator(options) {
3010
3196
  const doc = requireDoc(options);
3011
- return buildOrReport(options, () => {
3197
+ return registered(options, (callbacks) => buildOrReport(options, () => {
3012
3198
  const animatorConfig = getAnimatorConfig(doc) || {};
3013
- return finaliseAnimator(
3014
- animatorConfig,
3015
- toEngineCallbacks(options),
3016
- (cb) => createWebApiAnimator(doc, cb, null, true)
3017
- );
3018
- });
3199
+ return finaliseAnimator(animatorConfig, callbacks, (cb) => createWebApiAnimator(doc, cb, null, true));
3200
+ }));
3019
3201
  }
3020
3202
 
3021
3203
  // src/shared/PxAnimatorKeys.ts