@sarmal/core 0.29.1 → 0.30.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/auto-init.js CHANGED
@@ -375,6 +375,35 @@ function hexToRgb(hex) {
375
375
  const n = parseInt(hex.slice(1), 16);
376
376
  return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
377
377
  }
378
+ var HEX_3_RE = /^#([0-9a-fA-F]{3})$/;
379
+ var HEX_6_RE = /^#([0-9a-fA-F]{6})$/;
380
+ var HEX_8_RE = /^#([0-9a-fA-F]{8})$/;
381
+ var RGB_RE = /^rgba?\(\s*(-?\d{1,3})\s*,\s*(-?\d{1,3})\s*,\s*(-?\d{1,3})(?:\s*,\s*[\d.]+)?\s*\)$/i;
382
+ function parseColorToRgb(s) {
383
+ const trimmed = s.trim();
384
+ const m3 = HEX_3_RE.exec(trimmed);
385
+ if (m3) {
386
+ const [r, g, b] = m3[1];
387
+ return hexToRgb(`#${r}${r}${g}${g}${b}${b}`);
388
+ }
389
+ const m6 = HEX_6_RE.exec(trimmed);
390
+ if (m6) {
391
+ return hexToRgb(trimmed);
392
+ }
393
+ const m8 = HEX_8_RE.exec(trimmed);
394
+ if (m8) {
395
+ return hexToRgb(`#${trimmed.slice(1, 7)}`);
396
+ }
397
+ const mRgb = RGB_RE.exec(trimmed);
398
+ if (mRgb) {
399
+ return {
400
+ r: Math.max(0, Math.min(255, parseInt(mRgb[1], 10))),
401
+ g: Math.max(0, Math.min(255, parseInt(mRgb[2], 10))),
402
+ b: Math.max(0, Math.min(255, parseInt(mRgb[3], 10)))
403
+ };
404
+ }
405
+ return null;
406
+ }
378
407
  function srgbByteToLinear(c) {
379
408
  const n = c / 255;
380
409
  return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
@@ -434,7 +463,6 @@ function getPaletteColor(palette, position, timeOffset = 0) {
434
463
  const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
435
464
  return lerpOklab(c1, c2, t);
436
465
  }
437
- var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
438
466
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
439
467
  var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
440
468
  "trailColor",
@@ -467,9 +495,9 @@ function validateRenderOptions(partial) {
467
495
  }
468
496
  function assertTrailColor(value) {
469
497
  if (typeof value === "string") {
470
- if (!HEX_COLOR_RE.test(value)) {
498
+ if (parseColorToRgb(value) === null) {
471
499
  throw new TypeError(
472
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
500
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got "${value}"`
473
501
  );
474
502
  }
475
503
  return;
@@ -482,25 +510,25 @@ function assertTrailColor(value) {
482
510
  }
483
511
  for (let i = 0; i < value.length; i++) {
484
512
  const entry = value[i];
485
- if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
513
+ if (typeof entry !== "string" || parseColorToRgb(entry) === null) {
486
514
  throw new TypeError(
487
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
515
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got ${JSON.stringify(entry)}`
488
516
  );
489
517
  }
490
518
  }
491
519
  return;
492
520
  }
493
521
  throw new TypeError(
494
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
522
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or an array of color strings, got ${JSON.stringify(value)}`
495
523
  );
496
524
  }
497
525
  function assertHeadColor(value) {
498
526
  if (value === null) {
499
527
  return;
500
528
  }
501
- if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
529
+ if (typeof value !== "string" || parseColorToRgb(value) === null) {
502
530
  throw new TypeError(
503
- `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
531
+ `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or null, got ${JSON.stringify(value)}`
504
532
  );
505
533
  }
506
534
  }
@@ -508,9 +536,9 @@ function assertSkeletonColor(value) {
508
536
  if (value === "transparent") {
509
537
  return;
510
538
  }
511
- if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
539
+ if (typeof value !== "string" || parseColorToRgb(value) === null) {
512
540
  throw new TypeError(
513
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
541
+ `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or "transparent", got ${JSON.stringify(value)}`
514
542
  );
515
543
  }
516
544
  }
@@ -545,7 +573,7 @@ function resolveHeadColor(trailColor, trailStyle) {
545
573
  }
546
574
  const palette = resolveTrailPalette(trailColor);
547
575
  const last = palette[palette.length - 1];
548
- const { r, g, b } = hexToRgb(last);
576
+ const { r, g, b } = parseColorToRgb(last);
549
577
  return `rgb(${r},${g},${b})`;
550
578
  }
551
579
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
@@ -565,9 +593,9 @@ function warnIfTrailColorMismatch(trailColor, trailStyle) {
565
593
  // src/renderer.ts
566
594
  var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
567
595
  var WHITE_HEX = "#ffffff";
568
- function hexToRgbComponents(hex) {
569
- const n = parseInt(hex.slice(1), 16);
570
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
596
+ function colorToRgbComponents(color) {
597
+ const c = parseColorToRgb(color);
598
+ return `${c.r},${c.g},${c.b}`;
571
599
  }
572
600
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
573
601
  target.style.width = `${logicalWidth}px`;
@@ -587,7 +615,7 @@ function createRenderer(options) {
587
615
  let skeletonColor = options.skeletonColor ?? WHITE_HEX;
588
616
  let userHeadColor = options.headColor ?? null;
589
617
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
590
- let trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
618
+ let trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
591
619
  let trailPalette = resolveTrailPalette(trailColor);
592
620
  warnIfTrailColorMismatch(trailColor, trailStyle);
593
621
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
@@ -632,7 +660,7 @@ function createRenderer(options) {
632
660
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
633
661
  const skeletonCtx = skeletonCanvas.getContext("2d");
634
662
  skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
635
- skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
663
+ skeletonCtx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
636
664
  skeletonCtx.lineWidth = 1.5;
637
665
  skeletonCtx.beginPath();
638
666
  const first = skeleton[0];
@@ -647,7 +675,7 @@ function createRenderer(options) {
647
675
  if (pts.length < 2) {
648
676
  return;
649
677
  }
650
- ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${opacity})`;
678
+ ctx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${opacity})`;
651
679
  ctx.lineWidth = 1.5;
652
680
  ctx.beginPath();
653
681
  ctx.moveTo(pts[0].x * scale + offsetX, pts[0].y * scale + offsetY);
@@ -668,7 +696,7 @@ function createRenderer(options) {
668
696
  if (skeleton.length < 2) {
669
697
  return;
670
698
  }
671
- ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
699
+ ctx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
672
700
  ctx.lineWidth = 1.5;
673
701
  ctx.beginPath();
674
702
  const first = skeleton[0];
@@ -745,7 +773,7 @@ function createRenderer(options) {
745
773
  morphReject = null;
746
774
  morphAlpha = 0;
747
775
  skeleton = engine.getSarmalSkeleton();
748
- if (!engine.isLiveSkeleton) {
776
+ if (!engine.isLiveSkeleton && skeletonColor !== "transparent") {
749
777
  buildSkeletonCanvas();
750
778
  }
751
779
  }
@@ -771,7 +799,7 @@ function createRenderer(options) {
771
799
  }
772
800
  skeleton = engine.getSarmalSkeleton();
773
801
  calculateBoundaries();
774
- if (!engine.isLiveSkeleton) {
802
+ if (!engine.isLiveSkeleton && skeletonColor !== "transparent") {
775
803
  buildSkeletonCanvas();
776
804
  }
777
805
  if (options.initialPhase !== void 0) {
@@ -833,7 +861,7 @@ function createRenderer(options) {
833
861
  validateRenderOptions(partial);
834
862
  if (partial.trailColor !== void 0) {
835
863
  trailColor = partial.trailColor;
836
- trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
864
+ trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
837
865
  trailPalette = resolveTrailPalette(trailColor);
838
866
  }
839
867
  if (partial.skeletonColor !== void 0) {
@@ -917,6 +945,10 @@ function sampleCurveSkeleton(curveDef) {
917
945
  function el(tag) {
918
946
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
919
947
  }
948
+ function colorToRgbAttr(color) {
949
+ const c = parseColorToRgb(color);
950
+ return `rgb(${c.r},${c.g},${c.b})`;
951
+ }
920
952
  function createSVGRenderer(options) {
921
953
  const { container, engine } = options;
922
954
  const poolSize = engine.trailLength;
@@ -931,7 +963,7 @@ function createSVGRenderer(options) {
931
963
  let userHeadColor = options.headColor ?? null;
932
964
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
933
965
  let headRadius;
934
- let trailSolid = resolveTrailMainColor(trailColor);
966
+ let trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
935
967
  let trailPalette = resolveTrailPalette(trailColor);
936
968
  const ariaLabel = options.ariaLabel ?? "Loading";
937
969
  warnIfTrailColorMismatch(trailColor, trailStyle);
@@ -955,7 +987,10 @@ function createSVGRenderer(options) {
955
987
  const skeletonPath = el("path");
956
988
  skeletonPath.setAttribute("data-sarmal-role", "skeleton");
957
989
  skeletonPath.setAttribute("fill", "none");
958
- skeletonPath.setAttribute("stroke", skeletonColor);
990
+ skeletonPath.setAttribute(
991
+ "stroke",
992
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
993
+ );
959
994
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
960
995
  skeletonPath.setAttribute("stroke-width", svgSkeletonStrokeWidth);
961
996
  if (skeletonColor === "transparent") {
@@ -964,13 +999,19 @@ function createSVGRenderer(options) {
964
999
  group.appendChild(skeletonPath);
965
1000
  const skeletonPathA = el("path");
966
1001
  skeletonPathA.setAttribute("fill", "none");
967
- skeletonPathA.setAttribute("stroke", skeletonColor);
1002
+ skeletonPathA.setAttribute(
1003
+ "stroke",
1004
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
1005
+ );
968
1006
  skeletonPathA.setAttribute("stroke-width", svgSkeletonStrokeWidth);
969
1007
  skeletonPathA.setAttribute("visibility", "hidden");
970
1008
  group.appendChild(skeletonPathA);
971
1009
  const skeletonPathB = el("path");
972
1010
  skeletonPathB.setAttribute("fill", "none");
973
- skeletonPathB.setAttribute("stroke", skeletonColor);
1011
+ skeletonPathB.setAttribute(
1012
+ "stroke",
1013
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
1014
+ );
974
1015
  skeletonPathB.setAttribute("stroke-width", svgSkeletonStrokeWidth);
975
1016
  skeletonPathB.setAttribute("visibility", "hidden");
976
1017
  group.appendChild(skeletonPathB);
@@ -985,7 +1026,7 @@ function createSVGRenderer(options) {
985
1026
  }
986
1027
  const headCircle = el("circle");
987
1028
  headCircle.setAttribute("data-sarmal-role", "head");
988
- headCircle.setAttribute("fill", headColor);
1029
+ headCircle.setAttribute("fill", colorToRgbAttr(headColor));
989
1030
  headCircle.setAttribute("r", String(headRadius));
990
1031
  group.appendChild(headCircle);
991
1032
  container.appendChild(group);
@@ -1188,7 +1229,7 @@ function createSVGRenderer(options) {
1188
1229
  const prevTrailStyle = trailStyle;
1189
1230
  if (partial.trailColor !== void 0) {
1190
1231
  trailColor = partial.trailColor;
1191
- trailSolid = resolveTrailMainColor(trailColor);
1232
+ trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
1192
1233
  trailPalette = resolveTrailPalette(trailColor);
1193
1234
  if (trailStyle === "default") {
1194
1235
  for (const p of trailPaths) {
@@ -1201,10 +1242,10 @@ function createSVGRenderer(options) {
1201
1242
  if (skeletonColor === "transparent") {
1202
1243
  skeletonPath.setAttribute("visibility", "hidden");
1203
1244
  } else {
1204
- skeletonPath.setAttribute("stroke", skeletonColor);
1245
+ skeletonPath.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1205
1246
  skeletonPath.removeAttribute("visibility");
1206
- skeletonPathA.setAttribute("stroke", skeletonColor);
1207
- skeletonPathB.setAttribute("stroke", skeletonColor);
1247
+ skeletonPathA.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1248
+ skeletonPathB.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1208
1249
  }
1209
1250
  }
1210
1251
  if (partial.trailStyle !== void 0) {
@@ -1227,7 +1268,7 @@ function createSVGRenderer(options) {
1227
1268
  } else {
1228
1269
  headColor = userHeadColor;
1229
1270
  }
1230
- headCircle.setAttribute("fill", headColor);
1271
+ headCircle.setAttribute("fill", colorToRgbAttr(headColor));
1231
1272
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1232
1273
  warnIfTrailColorMismatch(trailColor, trailStyle);
1233
1274
  }