@pixodesk/svg-animator-react 1.0.27 → 1.0.28

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.umd.js CHANGED
@@ -2941,6 +2941,12 @@ var PixodeskAnimatorReact = (() => {
2941
2941
  kind: px.enum(["view", "scroll"]).optional(),
2942
2942
  axis: px.enum(["block", "inline", "x", "y"]).optional(),
2943
2943
  source: px.enum(["nearest", "root"]).optional(),
2944
+ // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.
2945
+ subject: px.string().optional(),
2946
+ smoothing: px.number().optional(),
2947
+ pin: px.boolean().optional(),
2948
+ pinTop: px.number().optional(),
2949
+ pinDistance: px.number().optional(),
2944
2950
  range: PxScrollRangeSchema.optional()
2945
2951
  }));
2946
2952
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
@@ -7814,6 +7820,10 @@ var PixodeskAnimatorReact = (() => {
7814
7820
  if (!config || !isScrollTimeline(config)) return null;
7815
7821
  const scroll = config.scroll || {};
7816
7822
  const kind = (_a = scroll.kind) != null ? _a : "view";
7823
+ if (scroll.smoothing) {
7824
+ console.warn('scroll timeline: `smoothing` needs the built-in driver \u2014 ignoring `driver: "native"`');
7825
+ return null;
7826
+ }
7817
7827
  const g = globalThis;
7818
7828
  const view = kind === "view";
7819
7829
  const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;
@@ -7822,7 +7832,7 @@ var PixodeskAnimatorReact = (() => {
7822
7832
  let timeline;
7823
7833
  try {
7824
7834
  if (view) {
7825
- timeline = new Ctor({ subject, axis });
7835
+ timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject), axis });
7826
7836
  } else {
7827
7837
  const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
7828
7838
  timeline = new Ctor({ source, axis });
@@ -7838,7 +7848,10 @@ var PixodeskAnimatorReact = (() => {
7838
7848
  };
7839
7849
  }
7840
7850
  function findNearestScroller(el, axis) {
7851
+ const body = document.body;
7852
+ const root = document.documentElement;
7841
7853
  for (let p = el.parentElement; p; p = p.parentElement) {
7854
+ if (p === body || p === root) return null;
7842
7855
  const style = getComputedStyle(p);
7843
7856
  const overflow = axis === "y" ? style.overflowY : style.overflowX;
7844
7857
  if (overflow === "auto" || overflow === "scroll" || overflow === "hidden" || overflow === "overlay") {
@@ -7850,11 +7863,42 @@ var PixodeskAnimatorReact = (() => {
7850
7863
  function documentScroller() {
7851
7864
  return document.scrollingElement || document.documentElement;
7852
7865
  }
7866
+ var SUBJECT_PARENT = "parent";
7867
+ var SUBJECT_SCROLLER = "scroller";
7868
+ function resolveScrollSubject(svgRoot, subject) {
7869
+ var _a, _b;
7870
+ const spec = subject == null ? void 0 : subject.trim();
7871
+ if (!spec) return svgRoot;
7872
+ if (spec === SUBJECT_PARENT) {
7873
+ let outermostPinned = null;
7874
+ for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {
7875
+ const position = getComputedStyle(p).position;
7876
+ if (position === "sticky" || position === "fixed") outermostPinned = p;
7877
+ }
7878
+ return (_b = (_a = outermostPinned == null ? void 0 : outermostPinned.parentElement) != null ? _a : svgRoot.parentElement) != null ? _b : svgRoot;
7879
+ }
7880
+ if (spec === SUBJECT_SCROLLER) {
7881
+ return findNearestScroller(svgRoot, "y") || findNearestScroller(svgRoot, "x") || documentScroller();
7882
+ }
7883
+ let found = null;
7884
+ try {
7885
+ found = document.querySelector(spec);
7886
+ } catch (e) {
7887
+ console.warn('scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
7888
+ return svgRoot;
7889
+ }
7890
+ if (!found) {
7891
+ console.warn('scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
7892
+ return svgRoot;
7893
+ }
7894
+ return found;
7895
+ }
7853
7896
  function createScrollDriver(subject, config, onProgress) {
7854
- var _a;
7897
+ var _a, _b;
7855
7898
  if (!config || !isScrollTimeline(config)) return null;
7856
7899
  const scroll = config.scroll || {};
7857
7900
  const kind = (_a = scroll.kind) != null ? _a : "view";
7901
+ const measured = resolveScrollSubject(subject, scroll.subject);
7858
7902
  const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
7859
7903
  const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
7860
7904
  const isRootScroller = scroller === documentScroller();
@@ -7865,7 +7909,7 @@ var PixodeskAnimatorReact = (() => {
7865
7909
  const maxOffset = axis === "y" ? scroller.scrollHeight - scroller.clientHeight : scroller.scrollWidth - scroller.clientWidth;
7866
7910
  return scrollOffsetProgress(offset, maxOffset, scroll.range);
7867
7911
  }
7868
- const subjectRect = subject.getBoundingClientRect();
7912
+ const subjectRect = measured.getBoundingClientRect();
7869
7913
  let portStart, portSize;
7870
7914
  if (isRootScroller) {
7871
7915
  portStart = 0;
@@ -7879,12 +7923,43 @@ var PixodeskAnimatorReact = (() => {
7879
7923
  const subjectSize = axis === "y" ? subjectRect.height : subjectRect.width;
7880
7924
  return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);
7881
7925
  };
7882
- let rafId = null;
7926
+ const smoothingSec = Math.max(0, (_b = scroll.smoothing) != null ? _b : 0) / 1e3;
7927
+ const SETTLE_EPSILON = 1e-3;
7883
7928
  let destroyed = false;
7929
+ let smoothed = null;
7930
+ let smoothRaf = null;
7931
+ let lastFrameMs = 0;
7932
+ const emit = (target) => {
7933
+ if (!smoothingSec) {
7934
+ onProgress(target);
7935
+ return;
7936
+ }
7937
+ if (smoothed === null) {
7938
+ smoothed = target;
7939
+ onProgress(target);
7940
+ return;
7941
+ }
7942
+ if (smoothRaf !== null) return;
7943
+ lastFrameMs = 0;
7944
+ const step = (nowMs) => {
7945
+ smoothRaf = null;
7946
+ if (destroyed) return;
7947
+ const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1e3) : 1 / 60;
7948
+ lastFrameMs = nowMs;
7949
+ const goal = compute();
7950
+ const k = 1 - Math.exp(-dtSec / smoothingSec);
7951
+ smoothed = smoothed + (goal - smoothed) * k;
7952
+ if (Math.abs(goal - smoothed) < SETTLE_EPSILON) smoothed = goal;
7953
+ onProgress(smoothed);
7954
+ if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);
7955
+ };
7956
+ smoothRaf = requestAnimationFrame(step);
7957
+ };
7958
+ let rafId = null;
7884
7959
  const tick = () => {
7885
7960
  rafId = null;
7886
7961
  if (destroyed) return;
7887
- onProgress(compute());
7962
+ emit(compute());
7888
7963
  };
7889
7964
  const schedule = () => {
7890
7965
  if (destroyed || rafId !== null) return;
@@ -7896,7 +7971,8 @@ var PixodeskAnimatorReact = (() => {
7896
7971
  let resizeObserver;
7897
7972
  if (typeof ResizeObserver !== "undefined") {
7898
7973
  resizeObserver = new ResizeObserver(schedule);
7899
- resizeObserver.observe(subject);
7974
+ resizeObserver.observe(measured);
7975
+ if (measured !== subject) resizeObserver.observe(subject);
7900
7976
  if (!isRootScroller) resizeObserver.observe(scroller);
7901
7977
  }
7902
7978
  const driver = {
@@ -7910,14 +7986,50 @@ var PixodeskAnimatorReact = (() => {
7910
7986
  cancelAnimationFrame(rafId);
7911
7987
  rafId = null;
7912
7988
  }
7989
+ if (smoothRaf !== null) {
7990
+ cancelAnimationFrame(smoothRaf);
7991
+ smoothRaf = null;
7992
+ }
7913
7993
  },
7994
+ // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.
7914
7995
  refresh: () => {
7915
- if (!destroyed) onProgress(compute());
7996
+ if (!destroyed) {
7997
+ smoothed = compute();
7998
+ onProgress(smoothed);
7999
+ }
7916
8000
  }
7917
8001
  };
7918
8002
  driver.refresh();
7919
8003
  return driver;
7920
8004
  }
8005
+ function applyScrollPin(svgRoot, scroll) {
8006
+ var _a;
8007
+ const styled = svgRoot;
8008
+ if (!(scroll == null ? void 0 : scroll.pin) || !styled.style) return () => {
8009
+ };
8010
+ const style = styled.style;
8011
+ const prevPosition = style.position;
8012
+ const prevTop = style.top;
8013
+ style.position = "sticky";
8014
+ style.top = ((_a = scroll.pinTop) != null ? _a : 0) + "px";
8015
+ let wrapper = null;
8016
+ const parent = svgRoot.parentElement;
8017
+ if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {
8018
+ wrapper = document.createElement("div");
8019
+ wrapper.setAttribute("data-px-pin", "");
8020
+ wrapper.style.height = scroll.pinDistance * 100 + "vh";
8021
+ parent.insertBefore(wrapper, svgRoot);
8022
+ wrapper.appendChild(svgRoot);
8023
+ }
8024
+ return () => {
8025
+ style.position = prevPosition;
8026
+ style.top = prevTop;
8027
+ if (wrapper == null ? void 0 : wrapper.parentElement) {
8028
+ wrapper.parentElement.insertBefore(svgRoot, wrapper);
8029
+ wrapper.remove();
8030
+ }
8031
+ };
8032
+ }
7921
8033
  function finaliseAnimator(animatorConfig, callbacks, make) {
7922
8034
  let apiRef;
7923
8035
  let effectiveCallbacks = callbacks;
@@ -7942,7 +8054,10 @@ var PixodeskAnimatorReact = (() => {
7942
8054
  if (isScrollTimeline(animatorConfig)) {
7943
8055
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
7944
8056
  var _a, _b;
8057
+ let unpin = () => {
8058
+ };
7945
8059
  if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
8060
+ unpin = applyScrollPin(rootElement, animatorConfig.scroll);
7946
8061
  const native = createNativeScrollTimeline(rootElement, animatorConfig);
7947
8062
  if (native) {
7948
8063
  const api2 = createWebApiAnimator(
@@ -7952,12 +8067,23 @@ var PixodeskAnimatorReact = (() => {
7952
8067
  animatorConfig.mode === PxAnimatorMode.waapi,
7953
8068
  native
7954
8069
  );
7955
- if (api2) return api2;
8070
+ if (api2) {
8071
+ const destroyNative = api2.destroy.bind(api2);
8072
+ api2.destroy = () => {
8073
+ unpin();
8074
+ destroyNative();
8075
+ };
8076
+ return api2;
8077
+ }
7956
8078
  }
8079
+ unpin();
8080
+ unpin = () => {
8081
+ };
7957
8082
  }
7958
8083
  const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
7959
8084
  const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
7960
8085
  if (subject) {
8086
+ unpin = applyScrollPin(subject, animatorConfig.scroll);
7961
8087
  const totalMs = scrollTotalDurationMs(animatorConfig);
7962
8088
  const driver = createScrollDriver(
7963
8089
  subject,
@@ -7968,6 +8094,7 @@ var PixodeskAnimatorReact = (() => {
7968
8094
  const destroy = api.destroy.bind(api);
7969
8095
  api.destroy = () => {
7970
8096
  driver.destroy();
8097
+ unpin();
7971
8098
  destroy();
7972
8099
  };
7973
8100
  }