@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"
583
+ };
584
+ var PxOffScreenAction = {
585
+ pause: "pause",
586
+ continue: "continue",
587
+ reset: "reset"
584
588
  };
585
- var PxOutAction = {
589
+ var PxMouseOutAction = {
586
590
  continue: "continue",
587
591
  pause: "pause",
588
592
  reset: "reset",
@@ -647,16 +651,20 @@ var PixodeskAnimator = (() => {
647
651
  return engine !== PxTimelineEngineSetting.js;
648
652
  }
649
653
  var PX_TRIGGER_DEFAULTS = {
650
- startOn: "load",
651
- outAction: "continue",
652
- scrollIntoViewThreshold: 0
654
+ start: "load",
655
+ offScreen: "pause",
656
+ mouseOut: "continue",
657
+ visibilityThreshold: 0.5,
658
+ visibilityDebounce: 150
653
659
  };
654
660
  function resolveTrigger(trigger) {
655
- var _a, _b, _c;
661
+ var _a, _b, _c, _d, _e;
656
662
  return {
657
- startOn: (_a = trigger == null ? void 0 : trigger.startOn) != null ? _a : PX_TRIGGER_DEFAULTS.startOn,
658
- outAction: (_b = trigger == null ? void 0 : trigger.outAction) != null ? _b : PX_TRIGGER_DEFAULTS.outAction,
659
- scrollIntoViewThreshold: (_c = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _c : PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold
663
+ start: (_a = trigger == null ? void 0 : trigger.start) != null ? _a : PX_TRIGGER_DEFAULTS.start,
664
+ offScreen: (_b = trigger == null ? void 0 : trigger.offScreen) != null ? _b : PX_TRIGGER_DEFAULTS.offScreen,
665
+ mouseOut: (_c = trigger == null ? void 0 : trigger.mouseOut) != null ? _c : PX_TRIGGER_DEFAULTS.mouseOut,
666
+ visibilityThreshold: (_d = trigger == null ? void 0 : trigger.visibilityThreshold) != null ? _d : PX_TRIGGER_DEFAULTS.visibilityThreshold,
667
+ visibilityDebounce: (_e = trigger == null ? void 0 : trigger.visibilityDebounce) != null ? _e : PX_TRIGGER_DEFAULTS.visibilityDebounce
660
668
  };
661
669
  }
662
670
  var PxLoopRepeatAt = {
@@ -774,9 +782,9 @@ var PixodeskAnimator = (() => {
774
782
  } else {
775
783
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
776
784
  if (timeline.trigger !== void 0) {
777
- const _b = timeline.trigger, { finishAction } = _b, restTrigger = __objRest(_b, ["finishAction"]);
785
+ const _b = timeline.trigger, { finish } = _b, restTrigger = __objRest(_b, ["finish"]);
778
786
  if (Object.keys(restTrigger).length) flat.trigger = restTrigger;
779
- if (finishAction !== void 0) flat.resetOnFinish = finishAction === "reset";
787
+ if (finish !== void 0) flat.resetOnFinish = finish === "reset";
780
788
  }
781
789
  if (timeline.delay !== void 0) flat.delay = timeline.delay;
782
790
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
@@ -877,13 +885,15 @@ var PixodeskAnimator = (() => {
877
885
  PxAnimationDefinitionSchema
878
886
  ]));
879
887
  var PxTriggerSchema = implementsInterface()(px.object({
880
- startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic], PX_TRIGGER_DEFAULTS.startOn).optional(),
881
- outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse], PX_TRIGGER_DEFAULTS.outAction).optional(),
888
+ start: px.enum([PxTriggerStart.load, PxTriggerStart.mouseOver, PxTriggerStart.click, PxTriggerStart.none], PX_TRIGGER_DEFAULTS.start).optional(),
889
+ offScreen: px.enum([PxOffScreenAction.pause, PxOffScreenAction.continue, PxOffScreenAction.reset], PX_TRIGGER_DEFAULTS.offScreen).optional(),
890
+ mouseOut: px.enum([PxMouseOutAction.continue, PxMouseOutAction.pause, PxMouseOutAction.reset, PxMouseOutAction.reverse], PX_TRIGGER_DEFAULTS.mouseOut).optional(),
882
891
  // What happens after a NATURAL finish — `'hold'` (default: keep the end state per
883
- // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` ("what
884
- // happens when the trigger condition ends"); both end-of-life knobs now read alike.
885
- finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
886
- scrollIntoViewThreshold: px.number().optional()
892
+ // `fill`) or `'reset'` (snap back to the start state). One of four occasion keys
893
+ // (`start`, `offScreen`, `mouseOut`, `finish`), all named the same way.
894
+ finish: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
895
+ visibilityThreshold: px.number().optional(),
896
+ visibilityDebounce: px.number().optional()
887
897
  }));
888
898
  var PxGlyphSchema = implementsInterface()(px.object({
889
899
  width: px.number(),
@@ -3085,6 +3095,196 @@ var PixodeskAnimator = (() => {
3085
3095
  return e instanceof Error ? e : new Error(String(e));
3086
3096
  }
3087
3097
 
3098
+ // src/registry/PxAnimatorRegistry.ts
3099
+ var STORE_KEY = /* @__PURE__ */ Symbol.for("@pixodesk/svg-animator-web:animators");
3100
+ var GLOBAL_NAME = "__pixodeskAnimators";
3101
+ function store() {
3102
+ const g = globalThis;
3103
+ let s = g[STORE_KEY];
3104
+ if (!s) {
3105
+ const animators = /* @__PURE__ */ new Set();
3106
+ const listeners = /* @__PURE__ */ new Set();
3107
+ s = {
3108
+ animators,
3109
+ listeners,
3110
+ getAll: () => Array.from(animators),
3111
+ subscribe: (listener) => {
3112
+ listeners.add(listener);
3113
+ return () => {
3114
+ listeners.delete(listener);
3115
+ };
3116
+ }
3117
+ };
3118
+ g[STORE_KEY] = s;
3119
+ if (g[GLOBAL_NAME] === void 0) g[GLOBAL_NAME] = s;
3120
+ }
3121
+ return s;
3122
+ }
3123
+ function notify(event, animator) {
3124
+ for (const listener of store().listeners) {
3125
+ try {
3126
+ listener(event, animator);
3127
+ } catch (e) {
3128
+ setTimeout(() => {
3129
+ throw e;
3130
+ }, 0);
3131
+ }
3132
+ }
3133
+ }
3134
+ function registerAnimator(animator) {
3135
+ const s = store();
3136
+ if (s.animators.has(animator)) return;
3137
+ s.animators.add(animator);
3138
+ notify("add", animator);
3139
+ const destroy = animator.destroy.bind(animator);
3140
+ animator.destroy = () => {
3141
+ destroy();
3142
+ if (s.animators.delete(animator)) notify("remove", animator);
3143
+ };
3144
+ }
3145
+ function withRegistryEvents(callbacks, api) {
3146
+ const fire = (event) => {
3147
+ const a = api();
3148
+ if (a) notify(event, a);
3149
+ };
3150
+ return __spreadProps(__spreadValues({}, callbacks), {
3151
+ onPlay: () => {
3152
+ var _a;
3153
+ (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
3154
+ fire("play");
3155
+ },
3156
+ onPause: () => {
3157
+ var _a;
3158
+ (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
3159
+ fire("pause");
3160
+ },
3161
+ onCancel: () => {
3162
+ var _a;
3163
+ (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
3164
+ fire("cancel");
3165
+ },
3166
+ onFinish: () => {
3167
+ var _a;
3168
+ (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
3169
+ fire("finish");
3170
+ }
3171
+ });
3172
+ }
3173
+
3174
+ // src/triggers/PxVisibilityGate.ts
3175
+ var PLAY_WHEN_VISIBLE_DEFAULTS = {
3176
+ offScreen: PX_TRIGGER_DEFAULTS.offScreen,
3177
+ visibilityThreshold: PX_TRIGGER_DEFAULTS.visibilityThreshold,
3178
+ visibilityDebounce: PX_TRIGGER_DEFAULTS.visibilityDebounce
3179
+ };
3180
+ var THRESHOLD_STEPS = Array.from({ length: 21 }, (_, i) => i / 20);
3181
+ function effectiveRatio(entry) {
3182
+ var _a, _b;
3183
+ const target = entry.boundingClientRect;
3184
+ const visible = entry.intersectionRect;
3185
+ if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
3186
+ const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
3187
+ const declared = (_b = (_a = entry.rootBounds) == null ? void 0 : _a.height) != null ? _b : Infinity;
3188
+ const viewport = Math.min(live, declared);
3189
+ const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
3190
+ return denom > 0 ? visible.height / denom : entry.intersectionRatio;
3191
+ }
3192
+ function openGate(host) {
3193
+ return {
3194
+ requestStart: () => host.play(),
3195
+ dispose: () => {
3196
+ }
3197
+ };
3198
+ }
3199
+ function isGated(offScreen) {
3200
+ return offScreen !== PxOffScreenAction.continue;
3201
+ }
3202
+ function createVisibilityGate(root, trigger, host) {
3203
+ if (!isGated(trigger.offScreen)) return openGate(host);
3204
+ if (typeof IntersectionObserver === "undefined") return openGate(host);
3205
+ const threshold = trigger.visibilityThreshold;
3206
+ const debounceMs = trigger.visibilityDebounce;
3207
+ let isOpen = void 0;
3208
+ let startPending = false;
3209
+ let pausedByGate = false;
3210
+ let lastRatio = 0;
3211
+ let openTimer;
3212
+ const cancelPendingOpen = () => {
3213
+ if (openTimer !== void 0) {
3214
+ clearTimeout(openTimer);
3215
+ openTimer = void 0;
3216
+ }
3217
+ };
3218
+ const open = () => {
3219
+ openTimer = void 0;
3220
+ if (isOpen === true) return;
3221
+ isOpen = true;
3222
+ if (startPending || pausedByGate) {
3223
+ startPending = false;
3224
+ pausedByGate = false;
3225
+ host.play();
3226
+ }
3227
+ };
3228
+ const close = () => {
3229
+ cancelPendingOpen();
3230
+ if (isOpen === false) return;
3231
+ isOpen = false;
3232
+ if (!host.isPlaying()) return;
3233
+ if (trigger.offScreen === PxOffScreenAction.reset) {
3234
+ host.cancel();
3235
+ } else {
3236
+ host.pause();
3237
+ }
3238
+ pausedByGate = true;
3239
+ };
3240
+ const apply = (ratio) => {
3241
+ lastRatio = ratio;
3242
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
3243
+ close();
3244
+ return;
3245
+ }
3246
+ if (ratio >= threshold) {
3247
+ if (isOpen === true || openTimer !== void 0) return;
3248
+ if (debounceMs > 0) openTimer = setTimeout(open, debounceMs);
3249
+ else open();
3250
+ return;
3251
+ }
3252
+ if (ratio <= 0) {
3253
+ close();
3254
+ return;
3255
+ }
3256
+ if (isOpen !== true) cancelPendingOpen();
3257
+ };
3258
+ const observer = new IntersectionObserver((entries) => {
3259
+ const last = entries[entries.length - 1];
3260
+ if (last) apply(last.isIntersecting ? effectiveRatio(last) : 0);
3261
+ }, { threshold: THRESHOLD_STEPS });
3262
+ observer.observe(root);
3263
+ const onVisibilityChange = () => {
3264
+ apply(lastRatio);
3265
+ };
3266
+ const hasDocument = typeof document !== "undefined";
3267
+ if (hasDocument) document.addEventListener("visibilitychange", onVisibilityChange);
3268
+ return {
3269
+ requestStart: (immediate) => {
3270
+ if (immediate || isOpen === true) {
3271
+ cancelPendingOpen();
3272
+ isOpen = true;
3273
+ startPending = false;
3274
+ pausedByGate = false;
3275
+ host.play();
3276
+ return;
3277
+ }
3278
+ startPending = true;
3279
+ },
3280
+ dispose: () => {
3281
+ cancelPendingOpen();
3282
+ observer.disconnect();
3283
+ if (hasDocument) document.removeEventListener("visibilitychange", onVisibilityChange);
3284
+ }
3285
+ };
3286
+ }
3287
+
3088
3288
  // src/triggers/PxAnimatorTriggers.ts
3089
3289
  function setupAnimationTriggers(api, trigger, diag) {
3090
3290
  const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
@@ -3092,7 +3292,7 @@ var PixodeskAnimator = (() => {
3092
3292
  const dispose = () => {
3093
3293
  for (const undo of cleanups.splice(0)) undo();
3094
3294
  };
3095
- const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);
3295
+ const resolved = resolveTrigger(trigger);
3096
3296
  const root = api.getRootElement();
3097
3297
  if (!root) {
3098
3298
  report.warn(PxDiagnosticKind.host, 1201 /* triggersNoRoot */);
@@ -3106,27 +3306,34 @@ var PixodeskAnimator = (() => {
3106
3306
  }
3107
3307
  api.play();
3108
3308
  };
3109
- const handleEndAction = () => {
3110
- switch (outAction) {
3111
- case "pause":
3309
+ const gate = createVisibilityGate(root, resolved, {
3310
+ isPlaying: () => api.isPlaying(),
3311
+ play: start,
3312
+ pause: () => api.pause(),
3313
+ cancel: () => api.cancel()
3314
+ });
3315
+ cleanups.push(() => gate.dispose());
3316
+ const handleMouseOut = () => {
3317
+ switch (resolved.mouseOut) {
3318
+ case PxMouseOutAction.pause:
3112
3319
  api.pause();
3113
3320
  break;
3114
- case "reset":
3321
+ case PxMouseOutAction.reset:
3115
3322
  api.cancel();
3116
3323
  break;
3117
- case "reverse":
3324
+ case PxMouseOutAction.reverse:
3118
3325
  reversed = true;
3119
3326
  api.setPlaybackRate(-1);
3120
3327
  api.play();
3121
3328
  break;
3122
- case "continue":
3329
+ case PxMouseOutAction.continue:
3123
3330
  default:
3124
3331
  break;
3125
3332
  }
3126
3333
  };
3127
- switch (startOn) {
3128
- case "load": {
3129
- const startHandler = () => start();
3334
+ switch (resolved.start) {
3335
+ case PxTriggerStart.load: {
3336
+ const startHandler = () => gate.requestStart(false);
3130
3337
  if (document.readyState === "complete") {
3131
3338
  startHandler();
3132
3339
  } else {
@@ -3135,14 +3342,14 @@ var PixodeskAnimator = (() => {
3135
3342
  }
3136
3343
  break;
3137
3344
  }
3138
- case "mouseOver": {
3345
+ case PxTriggerStart.mouseOver: {
3139
3346
  let enteredOnce = false;
3140
3347
  const mouseOverHandler = () => {
3141
3348
  enteredOnce = true;
3142
- start();
3349
+ gate.requestStart(true);
3143
3350
  };
3144
3351
  const mouseOutHandler = () => {
3145
- if (enteredOnce) handleEndAction();
3352
+ if (enteredOnce) handleMouseOut();
3146
3353
  };
3147
3354
  root.addEventListener("mouseenter", mouseOverHandler);
3148
3355
  root.addEventListener("mouseleave", mouseOutHandler);
@@ -3152,51 +3359,16 @@ var PixodeskAnimator = (() => {
3152
3359
  });
3153
3360
  break;
3154
3361
  }
3155
- case "click": {
3362
+ case PxTriggerStart.click: {
3156
3363
  const clickHandler = () => {
3157
- if (api.isPlaying()) {
3158
- handleEndAction();
3159
- } else {
3160
- start();
3161
- }
3364
+ if (api.isPlaying()) api.pause();
3365
+ else gate.requestStart(true);
3162
3366
  };
3163
3367
  root.addEventListener("click", clickHandler);
3164
3368
  cleanups.push(() => root.removeEventListener("click", clickHandler));
3165
3369
  break;
3166
3370
  }
3167
- case "scrollIntoView": {
3168
- const effectiveRatio = (entry) => {
3169
- var _a, _b;
3170
- const target = entry.boundingClientRect;
3171
- const visible = entry.intersectionRect;
3172
- if (!(target == null ? void 0 : target.height) || !visible) return entry.intersectionRatio;
3173
- const live = typeof window !== "undefined" && window.innerHeight ? window.innerHeight : Infinity;
3174
- const declared = (_b = (_a = entry.rootBounds) == null ? void 0 : _a.height) != null ? _b : Infinity;
3175
- const viewport = Math.min(live, declared);
3176
- const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);
3177
- return denom > 0 ? visible.height / denom : entry.intersectionRatio;
3178
- };
3179
- const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);
3180
- let wasIntersecting = false;
3181
- const observer = new IntersectionObserver(
3182
- (entries) => {
3183
- entries.forEach((entry) => {
3184
- if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {
3185
- wasIntersecting = true;
3186
- start();
3187
- } else if (wasIntersecting) {
3188
- wasIntersecting = false;
3189
- handleEndAction();
3190
- }
3191
- });
3192
- },
3193
- { threshold: thresholdSteps }
3194
- );
3195
- observer.observe(root);
3196
- cleanups.push(() => observer.disconnect());
3197
- break;
3198
- }
3199
- case "programmatic":
3371
+ case PxTriggerStart.none:
3200
3372
  break;
3201
3373
  }
3202
3374
  return dispose;
@@ -3204,7 +3376,12 @@ var PixodeskAnimator = (() => {
3204
3376
 
3205
3377
  // src/engines/PxAnimatorFrameLoop.ts
3206
3378
  function getSelector(id) {
3207
- return "#" + id;
3379
+ return "#" + escapeCssId(id);
3380
+ }
3381
+ function escapeCssId(id) {
3382
+ const css = globalThis.CSS;
3383
+ if (typeof (css == null ? void 0 : css.escape) === "function") return css.escape(id);
3384
+ return id.replace(/^([0-9])/, (_all, digit) => "\\3" + digit + " ").replace(/([^\w\-\\ ])/g, "\\$1");
3208
3385
  }
3209
3386
  function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
3210
3387
  var _a;
@@ -3911,7 +4088,16 @@ var PixodeskAnimator = (() => {
3911
4088
  }
3912
4089
  function createPrerenderedAnimator(options) {
3913
4090
  const doc = requireDoc(options);
3914
- return buildOrReport(options, () => bindWithEngineChoice(doc, void 0, toEngineCallbacks(options), null));
4091
+ return registered(options, (callbacks) => buildOrReport(options, () => bindWithEngineChoice(doc, void 0, callbacks, null)));
4092
+ }
4093
+ function registered(options, build) {
4094
+ let apiRef;
4095
+ const api = build(withRegistryEvents(toEngineCallbacks(options), () => apiRef));
4096
+ if (api.isReady()) {
4097
+ apiRef = api;
4098
+ registerAnimator(api);
4099
+ }
4100
+ return api;
3915
4101
  }
3916
4102
 
3917
4103
  // src/shared/PxAnimatorKeys.ts