@glissade/lottie 0.48.0-pre.1 → 0.48.0

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.
Files changed (2) hide show
  1. package/dist/index.js +64 -11
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1402,7 +1402,56 @@ function sampleToLottieKeys(tr, fr, ip, op, toS) {
1402
1402
  }
1403
1403
  out.push(frame);
1404
1404
  }
1405
- return decimateLinearKeys(out);
1405
+ return anchorSampledSpan(decimateLinearKeys(out), f0, f1, ip, op, (frame) => toS(sampleTrack(tr, frame / fr)));
1406
+ }
1407
+ /**
1408
+ * Anchor the document boundaries of a densely-SAMPLED channel whose keyed span
1409
+ * [f0,f1] doesn't reach the document bounds [ip,op]. Lottie extrapolates a channel
1410
+ * by HOLDING its first key backward and its last key forward, so when the span
1411
+ * starts AFTER ip the first EMITTED sample holds backward across the whole leading
1412
+ * dormant run — and for a fade-in whose first key rounds to a frame PAST the fade
1413
+ * start (a fractional key time, `round(t·fr) > t·fr`), that first sample is already
1414
+ * non-zero (e.g. ~9%), so a "hidden" element GHOSTS at ~9% from t=0 instead of 0.
1415
+ * Make the true base explicit:
1416
+ * • f0 > ip → PREPEND a HOLD key at ip carrying `sampleAt(ip)` — the value
1417
+ * `sampleTrack` holds across the dormant run (0 for a dormant-at-0 fade). HELD,
1418
+ * not linearly ramped, so a long dormant window stays at the base value the
1419
+ * whole way instead of sloping up to the first sample.
1420
+ * • f1 < op → APPEND a key at op carrying `sampleAt(op)`. Lottie already holds
1421
+ * the last key forward, but a fractional last-key round-DOWN leaves the true
1422
+ * tail value unsampled (e.g. a fade-out whose final 0 is skipped); this pins it.
1423
+ * `body` is the already-decimated sampled keyframes; the boundary keys sit OUTSIDE
1424
+ * it, so decimation (which assumes pure linear segments) never touches them. A span
1425
+ * that already covers [ip,op] (the common integer-keyed case) returns `body`
1426
+ * unchanged — byte-identical to before this fix.
1427
+ */
1428
+ function anchorSampledSpan(body, f0, f1, ip, op, sampleAt) {
1429
+ if (body.length === 0 || f0 <= ip && f1 >= op) return body;
1430
+ const out = body.slice();
1431
+ if (f1 < op) {
1432
+ const last = out[out.length - 1];
1433
+ out[out.length - 1] = {
1434
+ ...last,
1435
+ o: {
1436
+ x: 0,
1437
+ y: 0
1438
+ },
1439
+ i: {
1440
+ x: 1,
1441
+ y: 1
1442
+ }
1443
+ };
1444
+ out.push({
1445
+ t: op,
1446
+ s: sampleAt(op)
1447
+ });
1448
+ }
1449
+ if (f0 > ip) out.unshift({
1450
+ t: ip,
1451
+ s: sampleAt(ip),
1452
+ h: 1
1453
+ });
1454
+ return out;
1406
1455
  }
1407
1456
  /**
1408
1457
  * Ramer–Douglas–Peucker over linear-interpolated keyframes: keep the endpoints
@@ -1677,14 +1726,17 @@ function combineOpacity(ctx, node, tracks, accum) {
1677
1726
  k: leafStatic * accum.factor * 100
1678
1727
  };
1679
1728
  const [f0, f1] = frameSpan(ctx, leafTrack ? [leafTrack, ...accum.tracks] : [...accum.tracks]);
1680
- const out = [];
1681
- for (let f = f0; f <= f1; f++) {
1682
- const t = f / ctx.fr;
1729
+ const sampleAt = (frame) => {
1730
+ const t = frame / ctx.fr;
1683
1731
  let product = (leafTrack ? sampleTrack(leafTrack, t) : leafStatic) * accum.factor;
1684
1732
  for (const at of accum.tracks) product *= sampleTrack(at, t);
1733
+ return [product * 100];
1734
+ };
1735
+ const out = [];
1736
+ for (let f = f0; f <= f1; f++) {
1685
1737
  const frame = {
1686
1738
  t: f,
1687
- s: [product * 100]
1739
+ s: sampleAt(f)
1688
1740
  };
1689
1741
  if (f < f1) {
1690
1742
  frame.o = {
@@ -1700,7 +1752,7 @@ function combineOpacity(ctx, node, tracks, accum) {
1700
1752
  }
1701
1753
  return {
1702
1754
  a: 1,
1703
- k: decimateLinearKeys(out)
1755
+ k: anchorSampledSpan(decimateLinearKeys(out), f0, f1, ctx.ip, ctx.op, sampleAt)
1704
1756
  };
1705
1757
  }
1706
1758
  function scalarProp(ctx, tracks, prop, staticVal, map) {
@@ -1774,14 +1826,15 @@ function positionProp(ctx, tracks, staticPos) {
1774
1826
  }
1775
1827
  function sampleComponentVec(ctx, xt, yt, staticVal, map) {
1776
1828
  const [f0, f1] = frameSpan(ctx, [xt, yt]);
1829
+ const sampleAt = (frame) => {
1830
+ const t = frame / ctx.fr;
1831
+ return map([xt ? sampleTrack(xt, t) : staticVal[0], yt ? sampleTrack(yt, t) : staticVal[1]]);
1832
+ };
1777
1833
  const out = [];
1778
1834
  for (let f = f0; f <= f1; f++) {
1779
- const t = f / ctx.fr;
1780
- const x = xt ? sampleTrack(xt, t) : staticVal[0];
1781
- const y = yt ? sampleTrack(yt, t) : staticVal[1];
1782
1835
  const frame = {
1783
1836
  t: f,
1784
- s: map([x, y])
1837
+ s: sampleAt(f)
1785
1838
  };
1786
1839
  if (f < f1) {
1787
1840
  frame.o = {
@@ -1795,7 +1848,7 @@ function sampleComponentVec(ctx, xt, yt, staticVal, map) {
1795
1848
  }
1796
1849
  out.push(frame);
1797
1850
  }
1798
- return decimateLinearKeys(out);
1851
+ return anchorSampledSpan(decimateLinearKeys(out), f0, f1, ctx.ip, ctx.op, sampleAt);
1799
1852
  }
1800
1853
  /** Union frame span of a set of tracks (their first→last key), else [ip, op]. */
1801
1854
  function frameSpan(ctx, tracks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/lottie",
3
- "version": "0.48.0-pre.1",
3
+ "version": "0.48.0",
4
4
  "description": "glissade Lottie import (S1 MVP): pure .json (Lottie/bodymovin) → node specs + a v1 Timeline. Fail-fast feature audit; no DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -18,11 +18,11 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
- "@glissade/core": "0.48.0-pre.1",
22
- "@glissade/scene": "0.48.0-pre.1"
21
+ "@glissade/core": "0.48.0",
22
+ "@glissade/scene": "0.48.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@glissade/backend-skia": "0.48.0-pre.1"
25
+ "@glissade/backend-skia": "0.48.0"
26
26
  },
27
27
  "repository": {
28
28
  "type": "git",