@pixodesk/svg-animator-web 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.js CHANGED
@@ -1207,6 +1207,12 @@ var PxScrollSchema = implementsInterface()(px.object({
1207
1207
  kind: px.enum(["view", "scroll"]).optional(),
1208
1208
  axis: px.enum(["block", "inline", "x", "y"]).optional(),
1209
1209
  source: px.enum(["nearest", "root"]).optional(),
1210
+ // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.
1211
+ subject: px.string().optional(),
1212
+ smoothing: px.number().optional(),
1213
+ pin: px.boolean().optional(),
1214
+ pinTop: px.number().optional(),
1215
+ pinDistance: px.number().optional(),
1210
1216
  range: PxScrollRangeSchema.optional()
1211
1217
  }));
1212
1218
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
@@ -6431,6 +6437,10 @@ function createNativeScrollTimeline(subject, config) {
6431
6437
  if (!config || !isScrollTimeline(config)) return null;
6432
6438
  const scroll = config.scroll || {};
6433
6439
  const kind = (_a = scroll.kind) != null ? _a : "view";
6440
+ if (scroll.smoothing) {
6441
+ console.warn('scroll timeline: `smoothing` needs the built-in driver \u2014 ignoring `driver: "native"`');
6442
+ return null;
6443
+ }
6434
6444
  const g = globalThis;
6435
6445
  const view = kind === "view";
6436
6446
  const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;
@@ -6439,7 +6449,7 @@ function createNativeScrollTimeline(subject, config) {
6439
6449
  let timeline;
6440
6450
  try {
6441
6451
  if (view) {
6442
- timeline = new Ctor({ subject, axis });
6452
+ timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject), axis });
6443
6453
  } else {
6444
6454
  const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
6445
6455
  timeline = new Ctor({ source, axis });
@@ -6455,7 +6465,10 @@ function createNativeScrollTimeline(subject, config) {
6455
6465
  };
6456
6466
  }
6457
6467
  function findNearestScroller(el, axis) {
6468
+ const body = document.body;
6469
+ const root = document.documentElement;
6458
6470
  for (let p = el.parentElement; p; p = p.parentElement) {
6471
+ if (p === body || p === root) return null;
6459
6472
  const style = getComputedStyle(p);
6460
6473
  const overflow = axis === "y" ? style.overflowY : style.overflowX;
6461
6474
  if (overflow === "auto" || overflow === "scroll" || overflow === "hidden" || overflow === "overlay") {
@@ -6467,11 +6480,42 @@ function findNearestScroller(el, axis) {
6467
6480
  function documentScroller() {
6468
6481
  return document.scrollingElement || document.documentElement;
6469
6482
  }
6483
+ var SUBJECT_PARENT = "parent";
6484
+ var SUBJECT_SCROLLER = "scroller";
6485
+ function resolveScrollSubject(svgRoot, subject) {
6486
+ var _a, _b;
6487
+ const spec = subject == null ? void 0 : subject.trim();
6488
+ if (!spec) return svgRoot;
6489
+ if (spec === SUBJECT_PARENT) {
6490
+ let outermostPinned = null;
6491
+ for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {
6492
+ const position = getComputedStyle(p).position;
6493
+ if (position === "sticky" || position === "fixed") outermostPinned = p;
6494
+ }
6495
+ return (_b = (_a = outermostPinned == null ? void 0 : outermostPinned.parentElement) != null ? _a : svgRoot.parentElement) != null ? _b : svgRoot;
6496
+ }
6497
+ if (spec === SUBJECT_SCROLLER) {
6498
+ return findNearestScroller(svgRoot, "y") || findNearestScroller(svgRoot, "x") || documentScroller();
6499
+ }
6500
+ let found = null;
6501
+ try {
6502
+ found = document.querySelector(spec);
6503
+ } catch (e) {
6504
+ console.warn('scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
6505
+ return svgRoot;
6506
+ }
6507
+ if (!found) {
6508
+ console.warn('scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
6509
+ return svgRoot;
6510
+ }
6511
+ return found;
6512
+ }
6470
6513
  function createScrollDriver(subject, config, onProgress) {
6471
- var _a;
6514
+ var _a, _b;
6472
6515
  if (!config || !isScrollTimeline(config)) return null;
6473
6516
  const scroll = config.scroll || {};
6474
6517
  const kind = (_a = scroll.kind) != null ? _a : "view";
6518
+ const measured = resolveScrollSubject(subject, scroll.subject);
6475
6519
  const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
6476
6520
  const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
6477
6521
  const isRootScroller = scroller === documentScroller();
@@ -6482,7 +6526,7 @@ function createScrollDriver(subject, config, onProgress) {
6482
6526
  const maxOffset = axis === "y" ? scroller.scrollHeight - scroller.clientHeight : scroller.scrollWidth - scroller.clientWidth;
6483
6527
  return scrollOffsetProgress(offset, maxOffset, scroll.range);
6484
6528
  }
6485
- const subjectRect = subject.getBoundingClientRect();
6529
+ const subjectRect = measured.getBoundingClientRect();
6486
6530
  let portStart, portSize;
6487
6531
  if (isRootScroller) {
6488
6532
  portStart = 0;
@@ -6496,12 +6540,43 @@ function createScrollDriver(subject, config, onProgress) {
6496
6540
  const subjectSize = axis === "y" ? subjectRect.height : subjectRect.width;
6497
6541
  return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);
6498
6542
  };
6499
- let rafId = null;
6543
+ const smoothingSec = Math.max(0, (_b = scroll.smoothing) != null ? _b : 0) / 1e3;
6544
+ const SETTLE_EPSILON = 1e-3;
6500
6545
  let destroyed = false;
6546
+ let smoothed = null;
6547
+ let smoothRaf = null;
6548
+ let lastFrameMs = 0;
6549
+ const emit = (target) => {
6550
+ if (!smoothingSec) {
6551
+ onProgress(target);
6552
+ return;
6553
+ }
6554
+ if (smoothed === null) {
6555
+ smoothed = target;
6556
+ onProgress(target);
6557
+ return;
6558
+ }
6559
+ if (smoothRaf !== null) return;
6560
+ lastFrameMs = 0;
6561
+ const step = (nowMs) => {
6562
+ smoothRaf = null;
6563
+ if (destroyed) return;
6564
+ const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1e3) : 1 / 60;
6565
+ lastFrameMs = nowMs;
6566
+ const goal = compute();
6567
+ const k = 1 - Math.exp(-dtSec / smoothingSec);
6568
+ smoothed = smoothed + (goal - smoothed) * k;
6569
+ if (Math.abs(goal - smoothed) < SETTLE_EPSILON) smoothed = goal;
6570
+ onProgress(smoothed);
6571
+ if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);
6572
+ };
6573
+ smoothRaf = requestAnimationFrame(step);
6574
+ };
6575
+ let rafId = null;
6501
6576
  const tick = () => {
6502
6577
  rafId = null;
6503
6578
  if (destroyed) return;
6504
- onProgress(compute());
6579
+ emit(compute());
6505
6580
  };
6506
6581
  const schedule = () => {
6507
6582
  if (destroyed || rafId !== null) return;
@@ -6513,7 +6588,8 @@ function createScrollDriver(subject, config, onProgress) {
6513
6588
  let resizeObserver;
6514
6589
  if (typeof ResizeObserver !== "undefined") {
6515
6590
  resizeObserver = new ResizeObserver(schedule);
6516
- resizeObserver.observe(subject);
6591
+ resizeObserver.observe(measured);
6592
+ if (measured !== subject) resizeObserver.observe(subject);
6517
6593
  if (!isRootScroller) resizeObserver.observe(scroller);
6518
6594
  }
6519
6595
  const driver = {
@@ -6527,14 +6603,50 @@ function createScrollDriver(subject, config, onProgress) {
6527
6603
  cancelAnimationFrame(rafId);
6528
6604
  rafId = null;
6529
6605
  }
6606
+ if (smoothRaf !== null) {
6607
+ cancelAnimationFrame(smoothRaf);
6608
+ smoothRaf = null;
6609
+ }
6530
6610
  },
6611
+ // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.
6531
6612
  refresh: () => {
6532
- if (!destroyed) onProgress(compute());
6613
+ if (!destroyed) {
6614
+ smoothed = compute();
6615
+ onProgress(smoothed);
6616
+ }
6533
6617
  }
6534
6618
  };
6535
6619
  driver.refresh();
6536
6620
  return driver;
6537
6621
  }
6622
+ function applyScrollPin(svgRoot, scroll) {
6623
+ var _a;
6624
+ const styled = svgRoot;
6625
+ if (!(scroll == null ? void 0 : scroll.pin) || !styled.style) return () => {
6626
+ };
6627
+ const style = styled.style;
6628
+ const prevPosition = style.position;
6629
+ const prevTop = style.top;
6630
+ style.position = "sticky";
6631
+ style.top = ((_a = scroll.pinTop) != null ? _a : 0) + "px";
6632
+ let wrapper = null;
6633
+ const parent = svgRoot.parentElement;
6634
+ if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {
6635
+ wrapper = document.createElement("div");
6636
+ wrapper.setAttribute("data-px-pin", "");
6637
+ wrapper.style.height = scroll.pinDistance * 100 + "vh";
6638
+ parent.insertBefore(wrapper, svgRoot);
6639
+ wrapper.appendChild(svgRoot);
6640
+ }
6641
+ return () => {
6642
+ style.position = prevPosition;
6643
+ style.top = prevTop;
6644
+ if (wrapper == null ? void 0 : wrapper.parentElement) {
6645
+ wrapper.parentElement.insertBefore(svgRoot, wrapper);
6646
+ wrapper.remove();
6647
+ }
6648
+ };
6649
+ }
6538
6650
 
6539
6651
  // src/PxAnimatorBind.ts
6540
6652
  function finaliseAnimator(animatorConfig, callbacks, make) {
@@ -6561,7 +6673,10 @@ function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
6561
6673
  if (isScrollTimeline(animatorConfig)) {
6562
6674
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
6563
6675
  var _a, _b;
6676
+ let unpin = () => {
6677
+ };
6564
6678
  if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
6679
+ unpin = applyScrollPin(rootElement, animatorConfig.scroll);
6565
6680
  const native = createNativeScrollTimeline(rootElement, animatorConfig);
6566
6681
  if (native) {
6567
6682
  const api2 = createWebApiAnimator(
@@ -6571,12 +6686,23 @@ function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
6571
6686
  animatorConfig.mode === PxAnimatorMode.waapi,
6572
6687
  native
6573
6688
  );
6574
- if (api2) return api2;
6689
+ if (api2) {
6690
+ const destroyNative = api2.destroy.bind(api2);
6691
+ api2.destroy = () => {
6692
+ unpin();
6693
+ destroyNative();
6694
+ };
6695
+ return api2;
6696
+ }
6575
6697
  }
6698
+ unpin();
6699
+ unpin = () => {
6700
+ };
6576
6701
  }
6577
6702
  const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
6578
6703
  const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
6579
6704
  if (subject) {
6705
+ unpin = applyScrollPin(subject, animatorConfig.scroll);
6580
6706
  const totalMs = scrollTotalDurationMs(animatorConfig);
6581
6707
  const driver = createScrollDriver(
6582
6708
  subject,
@@ -6587,6 +6713,7 @@ function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
6587
6713
  const destroy = api.destroy.bind(api);
6588
6714
  api.destroy = () => {
6589
6715
  driver.destroy();
6716
+ unpin();
6590
6717
  destroy();
6591
6718
  };
6592
6719
  }