@sarmal/core 0.18.0 → 0.20.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.
package/dist/index.cjs CHANGED
@@ -300,12 +300,20 @@ function computeNormal(trail, i) {
300
300
  const tangent = computeTangent(trail, i);
301
301
  return { x: -tangent.y, y: tangent.x };
302
302
  }
303
- function computeTrailQuad(trail, i, trailCount, toX, toY) {
303
+ function computeTrailQuad(
304
+ trail,
305
+ i,
306
+ trailCount,
307
+ toX,
308
+ toY,
309
+ minWidth = TRAIL_MIN_WIDTH,
310
+ maxWidth = TRAIL_MAX_WIDTH,
311
+ ) {
304
312
  const progress = i / (trailCount - 1);
305
313
  const nextProgress = (i + 1) / (trailCount - 1);
306
314
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
307
- const w0 = (TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
308
- const w1 = (TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
315
+ const w0 = (minWidth + progress * (maxWidth - minWidth)) / 2;
316
+ const w1 = (minWidth + nextProgress * (maxWidth - minWidth)) / 2;
309
317
  const curr = trail[i];
310
318
  const next = trail[i + 1];
311
319
  const n0 = computeNormal(trail, i);
@@ -869,62 +877,61 @@ function createSVGRenderer(options) {
869
877
  let trailPalette = resolveTrailPalette(trailColor);
870
878
  const ariaLabel = options.ariaLabel ?? "Loading";
871
879
  warnIfTrailColorMismatch(trailColor, trailStyle);
872
- const htmlContainer = container;
873
- const width = htmlContainer.offsetWidth || 200;
874
- const height = htmlContainer.offsetHeight || 200;
875
- const headRadius = options.headRadius ?? getHeadDotRadius(width, height);
876
- const svg = el("svg");
877
- svg.setAttribute("width", String(width));
878
- svg.setAttribute("height", String(height));
879
- svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
880
- svg.setAttribute("role", "img");
881
- svg.setAttribute("aria-label", ariaLabel);
880
+ const viewSize = 100;
881
+ const headRadius = options.headRadius ?? 1.5;
882
+ const svgTrailMinWidth = 0.25;
883
+ const svgTrailMaxWidth = 1.25;
884
+ const svgSkeletonStrokeWidth = "0.75";
885
+ container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
886
+ container.setAttribute("role", "img");
887
+ container.setAttribute("aria-label", ariaLabel);
888
+ const group = el("g");
882
889
  const titleEl = el("title");
883
890
  titleEl.textContent = ariaLabel;
884
- svg.appendChild(titleEl);
891
+ group.appendChild(titleEl);
885
892
  const skeletonPath = el("path");
886
893
  skeletonPath.setAttribute("data-sarmal-role", "skeleton");
887
894
  skeletonPath.setAttribute("fill", "none");
888
895
  skeletonPath.setAttribute("stroke", skeletonColor);
889
896
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
890
- skeletonPath.setAttribute("stroke-width", "1.5");
897
+ skeletonPath.setAttribute("stroke-width", svgSkeletonStrokeWidth);
891
898
  if (skeletonColor === "transparent") {
892
899
  skeletonPath.setAttribute("visibility", "hidden");
893
900
  }
894
- svg.appendChild(skeletonPath);
901
+ group.appendChild(skeletonPath);
895
902
  const skeletonPathA = el("path");
896
903
  skeletonPathA.setAttribute("fill", "none");
897
904
  skeletonPathA.setAttribute("stroke", skeletonColor);
898
- skeletonPathA.setAttribute("stroke-width", "1.5");
905
+ skeletonPathA.setAttribute("stroke-width", svgSkeletonStrokeWidth);
899
906
  skeletonPathA.setAttribute("visibility", "hidden");
900
- svg.appendChild(skeletonPathA);
907
+ group.appendChild(skeletonPathA);
901
908
  const skeletonPathB = el("path");
902
909
  skeletonPathB.setAttribute("fill", "none");
903
910
  skeletonPathB.setAttribute("stroke", skeletonColor);
904
- skeletonPathB.setAttribute("stroke-width", "1.5");
911
+ skeletonPathB.setAttribute("stroke-width", svgSkeletonStrokeWidth);
905
912
  skeletonPathB.setAttribute("visibility", "hidden");
906
- svg.appendChild(skeletonPathB);
913
+ group.appendChild(skeletonPathB);
907
914
  let morphPathABuilt = "";
908
915
  let morphPathBBuilt = "";
909
916
  const trailPaths = [];
910
917
  for (let i = 0; i < poolSize; i++) {
911
918
  const path = el("path");
912
919
  path.setAttribute("fill", trailSolid);
913
- svg.appendChild(path);
920
+ group.appendChild(path);
914
921
  trailPaths.push(path);
915
922
  }
916
923
  const headCircle = el("circle");
917
924
  headCircle.setAttribute("data-sarmal-role", "head");
918
925
  headCircle.setAttribute("fill", headColor);
919
926
  headCircle.setAttribute("r", String(headRadius));
920
- svg.appendChild(headCircle);
921
- container.appendChild(svg);
927
+ group.appendChild(headCircle);
928
+ container.appendChild(group);
922
929
  let gradientAnimTime = 0;
923
930
  let scale = 1;
924
931
  let offsetX = 0;
925
932
  let offsetY = 0;
926
933
  function applyBoundaries(skeleton2) {
927
- const b = computeBoundaries(skeleton2, width, height);
934
+ const b = computeBoundaries(skeleton2, viewSize, viewSize);
928
935
  if (b) {
929
936
  scale = b.scale;
930
937
  offsetX = b.offsetX;
@@ -960,6 +967,8 @@ function createSVGRenderer(options) {
960
967
  trailCount,
961
968
  px,
962
969
  py,
970
+ svgTrailMinWidth,
971
+ svgTrailMaxWidth,
963
972
  );
964
973
  const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
965
974
  trailPaths[i].setAttribute("d", d);
@@ -1082,7 +1091,7 @@ function createSVGRenderer(options) {
1082
1091
  morphResolve = null;
1083
1092
  morphReject = null;
1084
1093
  }
1085
- svg.remove();
1094
+ group.remove();
1086
1095
  },
1087
1096
  ...enginePassthroughs(engine),
1088
1097
  morphTo(target, options2) {
@@ -1435,6 +1444,55 @@ var curves = {
1435
1444
  lame,
1436
1445
  };
1437
1446
 
1447
+ // src/catmull-rom.ts
1448
+ var PERIOD = 2 * Math.PI;
1449
+ function catmullRom1D(p0, p1, p2, p3, u) {
1450
+ const u2 = u * u;
1451
+ const u3 = u2 * u;
1452
+ return (
1453
+ 0.5 *
1454
+ (2 * p1 +
1455
+ (-p0 + p2) * u +
1456
+ (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +
1457
+ (-p0 + 3 * p1 - 3 * p2 + p3) * u3)
1458
+ );
1459
+ }
1460
+ function evaluateCatmullRom(points, t) {
1461
+ const N = points.length;
1462
+ if (N === 0) {
1463
+ return { x: 0, y: 0 };
1464
+ }
1465
+ if (N === 1) {
1466
+ return { x: points[0][0], y: points[0][1] };
1467
+ }
1468
+ t = ((t % PERIOD) + PERIOD) % PERIOD;
1469
+ const segmentSize = PERIOD / N;
1470
+ let i = Math.floor(t / segmentSize);
1471
+ if (i >= N) {
1472
+ i = N - 1;
1473
+ }
1474
+ let u = (t - i * segmentSize) / segmentSize;
1475
+ u = Math.max(0, Math.min(1, u));
1476
+ const p0 = points[(i - 1 + N) % N];
1477
+ const p1 = points[i];
1478
+ const p2 = points[(i + 1) % N];
1479
+ const p3 = points[(i + 2) % N];
1480
+ return {
1481
+ x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1482
+ y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u),
1483
+ };
1484
+ }
1485
+ function drawCurve(points) {
1486
+ if (points.length < 3) {
1487
+ throw new Error(`drawCurve requires at least 3 points, received ${points.length}.`);
1488
+ }
1489
+ return {
1490
+ name: "custom",
1491
+ fn: (t) => evaluateCatmullRom(points, t),
1492
+ period: PERIOD,
1493
+ };
1494
+ }
1495
+
1438
1496
  // src/index.ts
1439
1497
  function createSarmal(canvas, curveDef, options) {
1440
1498
  const { trailLength, ...rendererOpts } = options ?? {};
@@ -1451,8 +1509,10 @@ exports.createSarmal = createSarmal;
1451
1509
  exports.createSarmalSVG = createSarmalSVG;
1452
1510
  exports.curves = curves;
1453
1511
  exports.deltoid = deltoid;
1512
+ exports.drawCurve = drawCurve;
1454
1513
  exports.epicycloid3 = epicycloid3;
1455
1514
  exports.epitrochoid7 = epitrochoid7;
1515
+ exports.evaluateCatmullRom = evaluateCatmullRom;
1456
1516
  exports.lame = lame;
1457
1517
  exports.lissajous32 = lissajous32;
1458
1518
  exports.lissajous43 = lissajous43;