@pixodesk/svg-animator-web 1.0.28 → 1.0.30

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/README.md CHANGED
@@ -137,7 +137,7 @@ same shape as the JSON export. It comes in two modes:
137
137
  element ids to animation specs via `animator.animateById`.
138
138
 
139
139
  Elements may also carry a `node.effects` bucket (structural effects such as
140
- `transformBy`, `repeater`, `maskedBy`, `trimPath`, `clone`, `fillGradient` /
140
+ `transformBy`, `repeater`, `maskedBy`, `strokeTrim`, `clone`, `fillGradient` /
141
141
  `strokeGradient`, `textAlongPath`). This player materialises and removes them at
142
142
  runtime before any other normalisation.
143
143
 
package/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
50
50
  var index_exports = {};
51
51
  __export(index_exports, {
52
52
  COLOUR_ATTR_NAMES: () => COLOUR_ATTR_NAMES,
53
+ MISSING_GLYPH_CLASS_NAME: () => MISSING_GLYPH_CLASS_NAME,
53
54
  PX_ANIMATOR_DATA_KEY: () => PX_ANIMATOR_DATA_KEY,
54
55
  PX_ANIM_ATTR_NAME: () => PX_ANIM_ATTR_NAME,
55
56
  PX_ANIM_SRC_ATTR_NAME: () => PX_ANIM_SRC_ATTR_NAME,
@@ -82,6 +83,7 @@ __export(index_exports, {
82
83
  PxRepeaterEffectSchema: () => PxRepeaterEffectSchema,
83
84
  PxRetimeEffectSchema: () => PxRetimeEffectSchema,
84
85
  PxStrokeGradientEffectSchema: () => PxStrokeGradientEffectSchema,
86
+ PxStrokeTrimEffectSchema: () => PxStrokeTrimEffectSchema,
85
87
  PxSvgNodeExtra: () => PxSvgNodeExtra,
86
88
  PxTextEffectSchema: () => PxTextEffectSchema,
87
89
  PxTextPathEffectSchema: () => PxTextPathEffectSchema,
@@ -89,7 +91,6 @@ __export(index_exports, {
89
91
  PxTransformPartsSchema: () => PxTransformPartsSchema,
90
92
  PxTransformValueSchema: () => PxTransformValueSchema,
91
93
  PxTriggerSchema: () => PxTriggerSchema,
92
- PxTrimPathEffectSchema: () => PxTrimPathEffectSchema,
93
94
  STYLE_ATTR_NAMES: () => STYLE_ATTR_NAMES,
94
95
  TRANSFORM_FN_NAMES: () => TRANSFORM_FN_NAMES,
95
96
  applyPlayerEffects: () => applyPlayerEffects,
@@ -1161,7 +1162,7 @@ var PxTextPathSpacing = {
1161
1162
  auto: "auto",
1162
1163
  exact: "exact"
1163
1164
  };
1164
- var PxTrimSubPaths = {
1165
+ var PxStrokeTrimSubPaths = {
1165
1166
  separate: "separate",
1166
1167
  combined: "combined"
1167
1168
  };
@@ -1320,6 +1321,7 @@ var PxScrollSchema = implementsInterface()(px.object({
1320
1321
  subject: px.string().optional(),
1321
1322
  smoothing: px.number().optional(),
1322
1323
  pin: px.boolean().optional(),
1324
+ pinAlign: px.enum(["top", "center", "bottom"]).optional(),
1323
1325
  pinTop: px.number().optional(),
1324
1326
  pinDistance: px.number().optional(),
1325
1327
  range: PxScrollRangeSchema.optional()
@@ -1402,10 +1404,10 @@ var PxClipPathEffectSchema = implementsInterface()(px.object({
1402
1404
  d: PxAnimatableStringSchema.optional(),
1403
1405
  animate: PxPropertyAnimationSchema.optional()
1404
1406
  }));
1405
- var PxTrimPathEffectSchema = implementsInterface()(px.object({
1407
+ var PxStrokeTrimEffectSchema = implementsInterface()(px.object({
1406
1408
  offset: PxAnimatableNumberSchema.optional(),
1407
1409
  range: PxAnimatableVec2Schema.optional(),
1408
- subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
1410
+ subPaths: px.enum([PxStrokeTrimSubPaths.separate, PxStrokeTrimSubPaths.combined]).optional()
1409
1411
  }));
1410
1412
  var PxRetimeEffectSchema = implementsInterface()(px.object({
1411
1413
  sourceId: px.string().optional(),
@@ -1459,7 +1461,7 @@ var PxEffectsSchema = implementsInterface()(px.object({
1459
1461
  repeater: PxRepeaterEffectSchema.optional(),
1460
1462
  maskedBy: PxMaskedByEffectSchema.optional(),
1461
1463
  clipPath: PxClipPathEffectSchema.optional(),
1462
- trimPath: PxTrimPathEffectSchema.optional(),
1464
+ strokeTrim: PxStrokeTrimEffectSchema.optional(),
1463
1465
  clone: PxCloneEffectSchema.optional(),
1464
1466
  fillGradient: PxFillGradientEffectSchema.optional(),
1465
1467
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
@@ -4554,6 +4556,19 @@ var TEXT_ATTR_KEYS = [
4554
4556
  TEXT_CONTENT_ATTR,
4555
4557
  "xml:space"
4556
4558
  ];
4559
+ var PAINT_STATIC_KEYS = [
4560
+ "fillOpacity",
4561
+ "fillRule",
4562
+ "strokeOpacity",
4563
+ "strokeDasharray",
4564
+ "strokeDashoffset",
4565
+ "strokeLinecap",
4566
+ "strokeLinejoin",
4567
+ "strokeMiterlimit",
4568
+ "mixBlendMode",
4569
+ "filter"
4570
+ ];
4571
+ var PAINT_ANIMATE_KEYS = ["fill", "stroke", "strokeWidth", "opacity", "fillOpacity", "strokeOpacity", "strokeDasharray", "strokeDashoffset"];
4557
4572
  function parseLen(v) {
4558
4573
  if (typeof v === "number") return v;
4559
4574
  if (typeof v === "string") {
@@ -4565,17 +4580,39 @@ function parseLen(v) {
4565
4580
  function str(v) {
4566
4581
  return typeof v === "string" ? v : void 0;
4567
4582
  }
4583
+ function paintAnimateOf(node) {
4584
+ const bag = node.animate;
4585
+ if (!bag || typeof bag !== "object") return void 0;
4586
+ let out;
4587
+ for (const k of PAINT_ANIMATE_KEYS) {
4588
+ if (bag[k] !== void 0) (out != null ? out : out = {})[k] = bag[k];
4589
+ }
4590
+ return out;
4591
+ }
4568
4592
  function resolveStyle2(node, parent) {
4569
- var _a, _b, _c, _d, _e, _f, _g;
4570
- return {
4593
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
4594
+ const isTextRoot = node.type === "text";
4595
+ const nodeStyle = node.style;
4596
+ const own = (key) => {
4597
+ var _a2;
4598
+ return isTextRoot ? void 0 : (_a2 = node[key]) != null ? _a2 : nodeStyle == null ? void 0 : nodeStyle[key];
4599
+ };
4600
+ const res = {
4571
4601
  fontFamily: (_a = str(node.fontFamily)) != null ? _a : parent.fontFamily,
4572
4602
  fontSize: (_b = parseLen(node.fontSize)) != null ? _b : parent.fontSize,
4573
4603
  fill: (_c = node.fill) != null ? _c : parent.fill,
4574
4604
  stroke: (_d = node.stroke) != null ? _d : parent.stroke,
4575
4605
  strokeWidth: (_e = node.strokeWidth) != null ? _e : parent.strokeWidth,
4576
4606
  letterSpacing: (_f = parseLen(node.letterSpacing)) != null ? _f : parent.letterSpacing,
4577
- wordSpacing: (_g = parseLen(node.wordSpacing)) != null ? _g : parent.wordSpacing
4607
+ wordSpacing: (_g = parseLen(node.wordSpacing)) != null ? _g : parent.wordSpacing,
4608
+ opacity: (_h = parseLen(own("opacity"))) != null ? _h : parent.opacity,
4609
+ animate: (_i = isTextRoot ? void 0 : paintAnimateOf(node)) != null ? _i : parent.animate
4578
4610
  };
4611
+ for (const key of PAINT_STATIC_KEYS) {
4612
+ const v = (_j = own(key)) != null ? _j : parent[key];
4613
+ if (v !== void 0) res[key] = v;
4614
+ }
4615
+ return res;
4579
4616
  }
4580
4617
  function rootStyleOf(node) {
4581
4618
  var _a, _b, _c;
@@ -4594,6 +4631,11 @@ function paintOf(s) {
4594
4631
  if (s.fill !== void 0) p.fill = s.fill;
4595
4632
  if (s.stroke !== void 0) p.stroke = s.stroke;
4596
4633
  if (s.strokeWidth !== void 0) p.strokeWidth = s.strokeWidth;
4634
+ if (s.opacity !== void 0 && s.opacity !== 1) p.opacity = s.opacity;
4635
+ for (const key of PAINT_STATIC_KEYS) {
4636
+ if (s[key] !== void 0) p[key] = s[key];
4637
+ }
4638
+ if (s.animate !== void 0) p.animate = s.animate;
4597
4639
  return p;
4598
4640
  }
4599
4641
  function glyphFontFor(s, glyphs, soleFont, warnings) {
@@ -4607,6 +4649,7 @@ function soleFontOf(glyphs) {
4607
4649
  return names.length === 1 ? glyphs[names[0]] : void 0;
4608
4650
  }
4609
4651
  var MISSING_GLYPH_ADVANCE_EM = 0.6;
4652
+ var MISSING_GLYPH_CLASS_NAME = "px-missing-glyph";
4610
4653
  function missingGlyphBoxEm(advanceEm, ascentEm) {
4611
4654
  if (advanceEm <= 0 || ascentEm <= 0) return "";
4612
4655
  const inset = 0.08;
@@ -4642,7 +4685,7 @@ function materialiseGlyphTextHorizontal(node, opts) {
4642
4685
  pen.x += g.width * scale;
4643
4686
  } else if (/\S/.test(ch)) {
4644
4687
  const advEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * upm;
4645
- placements.push({ glyphD: missingGlyphBoxEm(advEm, ascentEm), m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4688
+ placements.push({ glyphD: missingGlyphBoxEm(advEm, ascentEm), m: [scale, 0, 0, scale, pen.x, pen.y], paint, isMissing: true, line, x: pen.x, y: pen.y, scale });
4646
4689
  pen.x += advEm * scale;
4647
4690
  } else {
4648
4691
  pen.x += (g ? g.width : 0) * scale;
@@ -4833,7 +4876,7 @@ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
4833
4876
  } else if (/\S/.test(ch)) {
4834
4877
  const wEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * gf.unitsPerEm;
4835
4878
  const boxAdv = wEm * scale;
4836
- cells.push({ glyphD: missingGlyphBoxEm(wEm, ascentEm), widthEm: wEm, scale, paint, midBase: adv + boxAdv / 2 });
4879
+ cells.push({ glyphD: missingGlyphBoxEm(wEm, ascentEm), widthEm: wEm, scale, paint, isMissing: true, midBase: adv + boxAdv / 2 });
4837
4880
  adv += boxAdv;
4838
4881
  } else {
4839
4882
  adv += (g ? g.width : 0) * scale;
@@ -4889,6 +4932,7 @@ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLengt
4889
4932
  const placements = placeCells.map((c) => ({
4890
4933
  glyphD: c.glyphD,
4891
4934
  paint: c.paint,
4935
+ isMissing: c.isMissing,
4892
4936
  m: alongAffine(sampler, base + c.midBase * k, c.scale, c.widthEm, perp)
4893
4937
  }));
4894
4938
  return toGroup(node, buildPaths(placements, create, warnings), create);
@@ -4977,12 +5021,13 @@ function buildAnimatedAlongPath(cells, sampler, distOf, times, loop, create, isC
4977
5021
  const transform = { keyframes: kfs };
4978
5022
  if (loop !== void 0) transform.loop = loop;
4979
5023
  const animate = { transform };
5024
+ if (c.paint.animate) Object.assign(animate, c.paint.animate);
4980
5025
  if (isClip && opKfs.some((k) => k.value === 0)) {
4981
5026
  const op = { keyframes: opKfs };
4982
5027
  if (loop !== void 0) op.loop = loop;
4983
5028
  animate.opacity = op;
4984
5029
  }
4985
- out.push(create("path", __spreadProps2(__spreadValues2({ d }, paintProps(c.paint)), { animate }), []));
5030
+ out.push(create("path", __spreadProps2(__spreadValues2(__spreadValues2({ d }, paintProps(c.paint)), missingGlyphProps(c.isMissing)), { animate }), []));
4986
5031
  }
4987
5032
  return out;
4988
5033
  }
@@ -4991,24 +5036,34 @@ function paintProps(paint) {
4991
5036
  if (paint.fill !== void 0) p.fill = paint.fill;
4992
5037
  if (paint.stroke !== void 0) p.stroke = paint.stroke;
4993
5038
  if (paint.strokeWidth !== void 0) p.strokeWidth = paint.strokeWidth;
5039
+ if (paint.opacity !== void 0) p.opacity = paint.opacity;
5040
+ for (const key of PAINT_STATIC_KEYS) {
5041
+ if (paint[key] !== void 0) p[key] = paint[key];
5042
+ }
4994
5043
  return p;
4995
5044
  }
5045
+ function missingGlyphProps(isMissing) {
5046
+ return isMissing ? { class: MISSING_GLYPH_CLASS_NAME } : {};
5047
+ }
4996
5048
  function buildPaths(placements, create, warnings) {
4997
- var _a, _b, _c;
4998
5049
  if (!placements.length) {
4999
5050
  warnings == null ? void 0 : warnings.push("textGlyphs: nothing to render");
5000
5051
  return [];
5001
5052
  }
5002
5053
  const byPaint = /* @__PURE__ */ new Map();
5003
5054
  for (const p of placements) {
5004
- const key = JSON.stringify([(_a = p.paint.fill) != null ? _a : null, (_b = p.paint.stroke) != null ? _b : null, (_c = p.paint.strokeWidth) != null ? _c : null]);
5055
+ const key = JSON.stringify([p.paint, !!p.isMissing]);
5005
5056
  const baked = transformPathData(p.glyphD, p.m);
5006
5057
  const entry = byPaint.get(key);
5007
5058
  if (entry) entry.d += baked;
5008
- else byPaint.set(key, { paint: p.paint, d: baked });
5059
+ else byPaint.set(key, { paint: p.paint, d: baked, isMissing: p.isMissing });
5009
5060
  }
5010
5061
  const out = [];
5011
- for (const { paint, d } of byPaint.values()) out.push(create("path", __spreadValues2({ d }, paintProps(paint)), []));
5062
+ for (const { paint, d, isMissing } of byPaint.values()) {
5063
+ out.push(create("path", __spreadValues2(__spreadValues2(__spreadValues2({
5064
+ d
5065
+ }, paintProps(paint)), missingGlyphProps(isMissing)), paint.animate !== void 0 ? { animate: __spreadValues2({}, paint.animate) } : {}), []));
5066
+ }
5012
5067
  return out;
5013
5068
  }
5014
5069
  function toGroup(node, children, create) {
@@ -5044,9 +5099,9 @@ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pat
5044
5099
  }
5045
5100
  return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength, pathOverflow);
5046
5101
  }
5047
- function applyTrimPathEffect(node, trimPath, ctx) {
5048
- if (!trimPath) return node;
5049
- const combined = trimPath.subPaths === PxTrimSubPaths.combined;
5102
+ function applyStrokeTrimEffect(node, strokeTrim, ctx) {
5103
+ if (!strokeTrim) return node;
5104
+ const combined = strokeTrim.subPaths === PxStrokeTrimSubPaths.combined;
5050
5105
  const leafEntries = [];
5051
5106
  const measure = (n) => {
5052
5107
  if (Array.isArray(n.children) && n.children.length > 0) {
@@ -5077,9 +5132,9 @@ function applyTrimPathEffect(node, trimPath, ctx) {
5077
5132
  }
5078
5133
  const chainLengthPx = acc;
5079
5134
  if (combined && chainLengthPx < 1e-3) return node;
5080
- const offsetReadRaw = readAnimatable(trimPath.offset);
5135
+ const offsetReadRaw = readAnimatable(strokeTrim.offset);
5081
5136
  const offsetRead = offsetReadRaw.kind === "absent" ? { kind: "static", value: 0 } : offsetReadRaw;
5082
- const rangeReadRaw = readRangeWithCrossings(trimPath.range);
5137
+ const rangeReadRaw = readRangeWithCrossings(strokeTrim.range);
5083
5138
  const rangeRead = rangeReadRaw.kind === "absent" ? { kind: "static", value: [0, 1] } : rangeReadRaw;
5084
5139
  const offsetValues = readScalarValues(offsetRead);
5085
5140
  const minOffset = offsetValues.length ? Math.min(...offsetValues) : 0;
@@ -5409,7 +5464,7 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5409
5464
  const originalId = typeof node.id === "string" ? node.id : void 0;
5410
5465
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
5411
5466
  if (!fx && !innerIdForContentRef) return node;
5412
- const { transformBy, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5467
+ const { transformBy, repeater, maskedBy, clipPath, strokeTrim, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5413
5468
  if (fx) delete node.effects;
5414
5469
  let n = node;
5415
5470
  let consumedByGlyphs = false;
@@ -5429,7 +5484,7 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5429
5484
  if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
5430
5485
  n = applyFillGradientEffect(n, fillGradient, ctx);
5431
5486
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
5432
- n = applyTrimPathEffect(n, trimPath, ctx);
5487
+ n = applyStrokeTrimEffect(n, strokeTrim, ctx);
5433
5488
  n = applyRepeaterEffect(n, repeater, ctx);
5434
5489
  n = applyMaskedByEffect(n, maskedBy, transformBy, ctx);
5435
5490
  n = applyClipPathEffect(n, clipPath, ctx);
@@ -6728,8 +6783,11 @@ function createScrollDriver(subject, config, onProgress) {
6728
6783
  driver.refresh();
6729
6784
  return driver;
6730
6785
  }
6786
+ function pinScrollportHeight(svgRoot) {
6787
+ const scroller = findNearestScroller(svgRoot, "y");
6788
+ return scroller ? scroller.clientHeight : document.documentElement.clientHeight;
6789
+ }
6731
6790
  function applyScrollPin(svgRoot, scroll) {
6732
- var _a;
6733
6791
  const styled = svgRoot;
6734
6792
  if (!(scroll == null ? void 0 : scroll.pin) || !styled.style) return () => {
6735
6793
  };
@@ -6737,7 +6795,28 @@ function applyScrollPin(svgRoot, scroll) {
6737
6795
  const prevPosition = style.position;
6738
6796
  const prevTop = style.top;
6739
6797
  style.position = "sticky";
6740
- style.top = ((_a = scroll.pinTop) != null ? _a : 0) + "px";
6798
+ const alignFactor = scroll.pinAlign === "center" ? 0.5 : scroll.pinAlign === "bottom" ? 1 : 0;
6799
+ const applyTop = () => {
6800
+ var _a;
6801
+ const extra = (_a = scroll.pinTop) != null ? _a : 0;
6802
+ if (!alignFactor) {
6803
+ style.top = extra + "px";
6804
+ return;
6805
+ }
6806
+ const portSize = pinScrollportHeight(svgRoot);
6807
+ const ownSize = svgRoot.getBoundingClientRect().height;
6808
+ style.top = Math.round((portSize - ownSize) * alignFactor + extra) + "px";
6809
+ };
6810
+ applyTop();
6811
+ let resizeObserver = null;
6812
+ const onWindowResize = alignFactor ? applyTop : null;
6813
+ if (alignFactor) {
6814
+ if (onWindowResize) window.addEventListener("resize", onWindowResize);
6815
+ if (typeof ResizeObserver !== "undefined") {
6816
+ resizeObserver = new ResizeObserver(applyTop);
6817
+ resizeObserver.observe(svgRoot);
6818
+ }
6819
+ }
6741
6820
  let wrapper = null;
6742
6821
  const parent = svgRoot.parentElement;
6743
6822
  if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {
@@ -6748,6 +6827,8 @@ function applyScrollPin(svgRoot, scroll) {
6748
6827
  wrapper.appendChild(svgRoot);
6749
6828
  }
6750
6829
  return () => {
6830
+ if (onWindowResize) window.removeEventListener("resize", onWindowResize);
6831
+ resizeObserver == null ? void 0 : resizeObserver.disconnect();
6751
6832
  style.position = prevPosition;
6752
6833
  style.top = prevTop;
6753
6834
  if (wrapper == null ? void 0 : wrapper.parentElement) {
@@ -7015,6 +7096,7 @@ if (typeof window !== "undefined") {
7015
7096
  // Annotate the CommonJS export names for ESM import in node:
7016
7097
  0 && (module.exports = {
7017
7098
  COLOUR_ATTR_NAMES,
7099
+ MISSING_GLYPH_CLASS_NAME,
7018
7100
  PX_ANIMATOR_DATA_KEY,
7019
7101
  PX_ANIM_ATTR_NAME,
7020
7102
  PX_ANIM_SRC_ATTR_NAME,
@@ -7047,6 +7129,7 @@ if (typeof window !== "undefined") {
7047
7129
  PxRepeaterEffectSchema,
7048
7130
  PxRetimeEffectSchema,
7049
7131
  PxStrokeGradientEffectSchema,
7132
+ PxStrokeTrimEffectSchema,
7050
7133
  PxSvgNodeExtra,
7051
7134
  PxTextEffectSchema,
7052
7135
  PxTextPathEffectSchema,
@@ -7054,7 +7137,6 @@ if (typeof window !== "undefined") {
7054
7137
  PxTransformPartsSchema,
7055
7138
  PxTransformValueSchema,
7056
7139
  PxTriggerSchema,
7057
- PxTrimPathEffectSchema,
7058
7140
  STYLE_ATTR_NAMES,
7059
7141
  TRANSFORM_FN_NAMES,
7060
7142
  applyPlayerEffects,