@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.
@@ -2258,6 +2258,10 @@ var PixodeskAnimator = (() => {
2258
2258
  if (!config || !isScrollTimeline(config)) return null;
2259
2259
  const scroll = config.scroll || {};
2260
2260
  const kind = (_a = scroll.kind) != null ? _a : "view";
2261
+ if (scroll.smoothing) {
2262
+ console.warn('scroll timeline: `smoothing` needs the built-in driver \u2014 ignoring `driver: "native"`');
2263
+ return null;
2264
+ }
2261
2265
  const g = globalThis;
2262
2266
  const view = kind === "view";
2263
2267
  const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;
@@ -2266,7 +2270,7 @@ var PixodeskAnimator = (() => {
2266
2270
  let timeline;
2267
2271
  try {
2268
2272
  if (view) {
2269
- timeline = new Ctor({ subject, axis });
2273
+ timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject), axis });
2270
2274
  } else {
2271
2275
  const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
2272
2276
  timeline = new Ctor({ source, axis });
@@ -2282,7 +2286,10 @@ var PixodeskAnimator = (() => {
2282
2286
  };
2283
2287
  }
2284
2288
  function findNearestScroller(el, axis) {
2289
+ const body = document.body;
2290
+ const root = document.documentElement;
2285
2291
  for (let p = el.parentElement; p; p = p.parentElement) {
2292
+ if (p === body || p === root) return null;
2286
2293
  const style = getComputedStyle(p);
2287
2294
  const overflow = axis === "y" ? style.overflowY : style.overflowX;
2288
2295
  if (overflow === "auto" || overflow === "scroll" || overflow === "hidden" || overflow === "overlay") {
@@ -2294,11 +2301,42 @@ var PixodeskAnimator = (() => {
2294
2301
  function documentScroller() {
2295
2302
  return document.scrollingElement || document.documentElement;
2296
2303
  }
2304
+ var SUBJECT_PARENT = "parent";
2305
+ var SUBJECT_SCROLLER = "scroller";
2306
+ function resolveScrollSubject(svgRoot, subject) {
2307
+ var _a, _b;
2308
+ const spec = subject == null ? void 0 : subject.trim();
2309
+ if (!spec) return svgRoot;
2310
+ if (spec === SUBJECT_PARENT) {
2311
+ let outermostPinned = null;
2312
+ for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {
2313
+ const position = getComputedStyle(p).position;
2314
+ if (position === "sticky" || position === "fixed") outermostPinned = p;
2315
+ }
2316
+ return (_b = (_a = outermostPinned == null ? void 0 : outermostPinned.parentElement) != null ? _a : svgRoot.parentElement) != null ? _b : svgRoot;
2317
+ }
2318
+ if (spec === SUBJECT_SCROLLER) {
2319
+ return findNearestScroller(svgRoot, "y") || findNearestScroller(svgRoot, "x") || documentScroller();
2320
+ }
2321
+ let found = null;
2322
+ try {
2323
+ found = document.querySelector(spec);
2324
+ } catch (e) {
2325
+ console.warn('scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
2326
+ return svgRoot;
2327
+ }
2328
+ if (!found) {
2329
+ console.warn('scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
2330
+ return svgRoot;
2331
+ }
2332
+ return found;
2333
+ }
2297
2334
  function createScrollDriver(subject, config, onProgress) {
2298
- var _a;
2335
+ var _a, _b;
2299
2336
  if (!config || !isScrollTimeline(config)) return null;
2300
2337
  const scroll = config.scroll || {};
2301
2338
  const kind = (_a = scroll.kind) != null ? _a : "view";
2339
+ const measured = resolveScrollSubject(subject, scroll.subject);
2302
2340
  const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
2303
2341
  const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
2304
2342
  const isRootScroller = scroller === documentScroller();
@@ -2309,7 +2347,7 @@ var PixodeskAnimator = (() => {
2309
2347
  const maxOffset = axis === "y" ? scroller.scrollHeight - scroller.clientHeight : scroller.scrollWidth - scroller.clientWidth;
2310
2348
  return scrollOffsetProgress(offset, maxOffset, scroll.range);
2311
2349
  }
2312
- const subjectRect = subject.getBoundingClientRect();
2350
+ const subjectRect = measured.getBoundingClientRect();
2313
2351
  let portStart, portSize;
2314
2352
  if (isRootScroller) {
2315
2353
  portStart = 0;
@@ -2323,12 +2361,43 @@ var PixodeskAnimator = (() => {
2323
2361
  const subjectSize = axis === "y" ? subjectRect.height : subjectRect.width;
2324
2362
  return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);
2325
2363
  };
2326
- let rafId = null;
2364
+ const smoothingSec = Math.max(0, (_b = scroll.smoothing) != null ? _b : 0) / 1e3;
2365
+ const SETTLE_EPSILON = 1e-3;
2327
2366
  let destroyed = false;
2367
+ let smoothed = null;
2368
+ let smoothRaf = null;
2369
+ let lastFrameMs = 0;
2370
+ const emit = (target) => {
2371
+ if (!smoothingSec) {
2372
+ onProgress(target);
2373
+ return;
2374
+ }
2375
+ if (smoothed === null) {
2376
+ smoothed = target;
2377
+ onProgress(target);
2378
+ return;
2379
+ }
2380
+ if (smoothRaf !== null) return;
2381
+ lastFrameMs = 0;
2382
+ const step = (nowMs) => {
2383
+ smoothRaf = null;
2384
+ if (destroyed) return;
2385
+ const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1e3) : 1 / 60;
2386
+ lastFrameMs = nowMs;
2387
+ const goal = compute();
2388
+ const k = 1 - Math.exp(-dtSec / smoothingSec);
2389
+ smoothed = smoothed + (goal - smoothed) * k;
2390
+ if (Math.abs(goal - smoothed) < SETTLE_EPSILON) smoothed = goal;
2391
+ onProgress(smoothed);
2392
+ if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);
2393
+ };
2394
+ smoothRaf = requestAnimationFrame(step);
2395
+ };
2396
+ let rafId = null;
2328
2397
  const tick = () => {
2329
2398
  rafId = null;
2330
2399
  if (destroyed) return;
2331
- onProgress(compute());
2400
+ emit(compute());
2332
2401
  };
2333
2402
  const schedule = () => {
2334
2403
  if (destroyed || rafId !== null) return;
@@ -2340,7 +2409,8 @@ var PixodeskAnimator = (() => {
2340
2409
  let resizeObserver;
2341
2410
  if (typeof ResizeObserver !== "undefined") {
2342
2411
  resizeObserver = new ResizeObserver(schedule);
2343
- resizeObserver.observe(subject);
2412
+ resizeObserver.observe(measured);
2413
+ if (measured !== subject) resizeObserver.observe(subject);
2344
2414
  if (!isRootScroller) resizeObserver.observe(scroller);
2345
2415
  }
2346
2416
  const driver = {
@@ -2354,14 +2424,50 @@ var PixodeskAnimator = (() => {
2354
2424
  cancelAnimationFrame(rafId);
2355
2425
  rafId = null;
2356
2426
  }
2427
+ if (smoothRaf !== null) {
2428
+ cancelAnimationFrame(smoothRaf);
2429
+ smoothRaf = null;
2430
+ }
2357
2431
  },
2432
+ // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.
2358
2433
  refresh: () => {
2359
- if (!destroyed) onProgress(compute());
2434
+ if (!destroyed) {
2435
+ smoothed = compute();
2436
+ onProgress(smoothed);
2437
+ }
2360
2438
  }
2361
2439
  };
2362
2440
  driver.refresh();
2363
2441
  return driver;
2364
2442
  }
2443
+ function applyScrollPin(svgRoot, scroll) {
2444
+ var _a;
2445
+ const styled = svgRoot;
2446
+ if (!(scroll == null ? void 0 : scroll.pin) || !styled.style) return () => {
2447
+ };
2448
+ const style = styled.style;
2449
+ const prevPosition = style.position;
2450
+ const prevTop = style.top;
2451
+ style.position = "sticky";
2452
+ style.top = ((_a = scroll.pinTop) != null ? _a : 0) + "px";
2453
+ let wrapper = null;
2454
+ const parent = svgRoot.parentElement;
2455
+ if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {
2456
+ wrapper = document.createElement("div");
2457
+ wrapper.setAttribute("data-px-pin", "");
2458
+ wrapper.style.height = scroll.pinDistance * 100 + "vh";
2459
+ parent.insertBefore(wrapper, svgRoot);
2460
+ wrapper.appendChild(svgRoot);
2461
+ }
2462
+ return () => {
2463
+ style.position = prevPosition;
2464
+ style.top = prevTop;
2465
+ if (wrapper == null ? void 0 : wrapper.parentElement) {
2466
+ wrapper.parentElement.insertBefore(svgRoot, wrapper);
2467
+ wrapper.remove();
2468
+ }
2469
+ };
2470
+ }
2365
2471
 
2366
2472
  // src/PxAnimatorBind.ts
2367
2473
  function finaliseAnimator(animatorConfig, callbacks, make) {
@@ -2388,7 +2494,10 @@ var PixodeskAnimator = (() => {
2388
2494
  if (isScrollTimeline(animatorConfig)) {
2389
2495
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
2390
2496
  var _a, _b;
2497
+ let unpin = () => {
2498
+ };
2391
2499
  if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
2500
+ unpin = applyScrollPin(rootElement, animatorConfig.scroll);
2392
2501
  const native = createNativeScrollTimeline(rootElement, animatorConfig);
2393
2502
  if (native) {
2394
2503
  const api2 = createWebApiAnimator(
@@ -2398,12 +2507,23 @@ var PixodeskAnimator = (() => {
2398
2507
  animatorConfig.mode === PxAnimatorMode.waapi,
2399
2508
  native
2400
2509
  );
2401
- if (api2) return api2;
2510
+ if (api2) {
2511
+ const destroyNative = api2.destroy.bind(api2);
2512
+ api2.destroy = () => {
2513
+ unpin();
2514
+ destroyNative();
2515
+ };
2516
+ return api2;
2517
+ }
2402
2518
  }
2519
+ unpin();
2520
+ unpin = () => {
2521
+ };
2403
2522
  }
2404
2523
  const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
2405
2524
  const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
2406
2525
  if (subject) {
2526
+ unpin = applyScrollPin(subject, animatorConfig.scroll);
2407
2527
  const totalMs = scrollTotalDurationMs(animatorConfig);
2408
2528
  const driver = createScrollDriver(
2409
2529
  subject,
@@ -2414,6 +2534,7 @@ var PixodeskAnimator = (() => {
2414
2534
  const destroy = api.destroy.bind(api);
2415
2535
  api.destroy = () => {
2416
2536
  driver.destroy();
2537
+ unpin();
2417
2538
  destroy();
2418
2539
  };
2419
2540
  }