@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/index.cjs CHANGED
@@ -385,6 +385,35 @@ function hexToRgb(hex) {
385
385
  const n = parseInt(hex.slice(1), 16);
386
386
  return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
387
387
  }
388
+ var HEX_3_RE = /^#([0-9a-fA-F]{3})$/;
389
+ var HEX_6_RE = /^#([0-9a-fA-F]{6})$/;
390
+ var HEX_8_RE = /^#([0-9a-fA-F]{8})$/;
391
+ var RGB_RE = /^rgba?\(\s*(-?\d{1,3})\s*,\s*(-?\d{1,3})\s*,\s*(-?\d{1,3})(?:\s*,\s*[\d.]+)?\s*\)$/i;
392
+ function parseColorToRgb(s) {
393
+ const trimmed = s.trim();
394
+ const m3 = HEX_3_RE.exec(trimmed);
395
+ if (m3) {
396
+ const [r, g, b] = m3[1];
397
+ return hexToRgb(`#${r}${r}${g}${g}${b}${b}`);
398
+ }
399
+ const m6 = HEX_6_RE.exec(trimmed);
400
+ if (m6) {
401
+ return hexToRgb(trimmed);
402
+ }
403
+ const m8 = HEX_8_RE.exec(trimmed);
404
+ if (m8) {
405
+ return hexToRgb(`#${trimmed.slice(1, 7)}`);
406
+ }
407
+ const mRgb = RGB_RE.exec(trimmed);
408
+ if (mRgb) {
409
+ return {
410
+ r: Math.max(0, Math.min(255, parseInt(mRgb[1], 10))),
411
+ g: Math.max(0, Math.min(255, parseInt(mRgb[2], 10))),
412
+ b: Math.max(0, Math.min(255, parseInt(mRgb[3], 10)))
413
+ };
414
+ }
415
+ return null;
416
+ }
388
417
  function srgbByteToLinear(c) {
389
418
  const n = c / 255;
390
419
  return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
@@ -444,7 +473,6 @@ function getPaletteColor(palette, position, timeOffset = 0) {
444
473
  const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
445
474
  return lerpOklab(c1, c2, t);
446
475
  }
447
- var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
448
476
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
449
477
  var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
450
478
  "trailColor",
@@ -477,9 +505,9 @@ function validateRenderOptions(partial) {
477
505
  }
478
506
  function assertTrailColor(value) {
479
507
  if (typeof value === "string") {
480
- if (!HEX_COLOR_RE.test(value)) {
508
+ if (parseColorToRgb(value) === null) {
481
509
  throw new TypeError(
482
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
510
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got "${value}"`
483
511
  );
484
512
  }
485
513
  return;
@@ -492,25 +520,25 @@ function assertTrailColor(value) {
492
520
  }
493
521
  for (let i = 0; i < value.length; i++) {
494
522
  const entry = value[i];
495
- if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
523
+ if (typeof entry !== "string" || parseColorToRgb(entry) === null) {
496
524
  throw new TypeError(
497
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
525
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got ${JSON.stringify(entry)}`
498
526
  );
499
527
  }
500
528
  }
501
529
  return;
502
530
  }
503
531
  throw new TypeError(
504
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
532
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or an array of color strings, got ${JSON.stringify(value)}`
505
533
  );
506
534
  }
507
535
  function assertHeadColor(value) {
508
536
  if (value === null) {
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: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
541
+ `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or null, got ${JSON.stringify(value)}`
514
542
  );
515
543
  }
516
544
  }
@@ -518,9 +546,9 @@ function assertSkeletonColor(value) {
518
546
  if (value === "transparent") {
519
547
  return;
520
548
  }
521
- if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
549
+ if (typeof value !== "string" || parseColorToRgb(value) === null) {
522
550
  throw new TypeError(
523
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
551
+ `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or "transparent", got ${JSON.stringify(value)}`
524
552
  );
525
553
  }
526
554
  }
@@ -555,7 +583,7 @@ function resolveHeadColor(trailColor, trailStyle) {
555
583
  }
556
584
  const palette = resolveTrailPalette(trailColor);
557
585
  const last = palette[palette.length - 1];
558
- const { r, g, b } = hexToRgb(last);
586
+ const { r, g, b } = parseColorToRgb(last);
559
587
  return `rgb(${r},${g},${b})`;
560
588
  }
561
589
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
@@ -575,9 +603,9 @@ function warnIfTrailColorMismatch(trailColor, trailStyle) {
575
603
  // src/renderer.ts
576
604
  var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
577
605
  var WHITE_HEX = "#ffffff";
578
- function hexToRgbComponents(hex) {
579
- const n = parseInt(hex.slice(1), 16);
580
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
606
+ function colorToRgbComponents(color) {
607
+ const c = parseColorToRgb(color);
608
+ return `${c.r},${c.g},${c.b}`;
581
609
  }
582
610
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
583
611
  target.style.width = `${logicalWidth}px`;
@@ -597,7 +625,7 @@ function createRenderer(options) {
597
625
  let skeletonColor = options.skeletonColor ?? WHITE_HEX;
598
626
  let userHeadColor = options.headColor ?? null;
599
627
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
600
- let trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
628
+ let trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
601
629
  let trailPalette = resolveTrailPalette(trailColor);
602
630
  warnIfTrailColorMismatch(trailColor, trailStyle);
603
631
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
@@ -642,7 +670,7 @@ function createRenderer(options) {
642
670
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
643
671
  const skeletonCtx = skeletonCanvas.getContext("2d");
644
672
  skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
645
- skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
673
+ skeletonCtx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
646
674
  skeletonCtx.lineWidth = 1.5;
647
675
  skeletonCtx.beginPath();
648
676
  const first = skeleton[0];
@@ -657,7 +685,7 @@ function createRenderer(options) {
657
685
  if (pts.length < 2) {
658
686
  return;
659
687
  }
660
- ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${opacity})`;
688
+ ctx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${opacity})`;
661
689
  ctx.lineWidth = 1.5;
662
690
  ctx.beginPath();
663
691
  ctx.moveTo(pts[0].x * scale + offsetX, pts[0].y * scale + offsetY);
@@ -678,7 +706,7 @@ function createRenderer(options) {
678
706
  if (skeleton.length < 2) {
679
707
  return;
680
708
  }
681
- ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
709
+ ctx.strokeStyle = `rgba(${colorToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
682
710
  ctx.lineWidth = 1.5;
683
711
  ctx.beginPath();
684
712
  const first = skeleton[0];
@@ -755,7 +783,7 @@ function createRenderer(options) {
755
783
  morphReject = null;
756
784
  morphAlpha = 0;
757
785
  skeleton = engine.getSarmalSkeleton();
758
- if (!engine.isLiveSkeleton) {
786
+ if (!engine.isLiveSkeleton && skeletonColor !== "transparent") {
759
787
  buildSkeletonCanvas();
760
788
  }
761
789
  }
@@ -781,7 +809,7 @@ function createRenderer(options) {
781
809
  }
782
810
  skeleton = engine.getSarmalSkeleton();
783
811
  calculateBoundaries();
784
- if (!engine.isLiveSkeleton) {
812
+ if (!engine.isLiveSkeleton && skeletonColor !== "transparent") {
785
813
  buildSkeletonCanvas();
786
814
  }
787
815
  if (options.initialPhase !== void 0) {
@@ -843,7 +871,7 @@ function createRenderer(options) {
843
871
  validateRenderOptions(partial);
844
872
  if (partial.trailColor !== void 0) {
845
873
  trailColor = partial.trailColor;
846
- trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
874
+ trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
847
875
  trailPalette = resolveTrailPalette(trailColor);
848
876
  }
849
877
  if (partial.skeletonColor !== void 0) {
@@ -927,6 +955,10 @@ function sampleCurveSkeleton(curveDef) {
927
955
  function el(tag) {
928
956
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
929
957
  }
958
+ function colorToRgbAttr(color) {
959
+ const c = parseColorToRgb(color);
960
+ return `rgb(${c.r},${c.g},${c.b})`;
961
+ }
930
962
  function createSVGRenderer(options) {
931
963
  const { container, engine } = options;
932
964
  const poolSize = engine.trailLength;
@@ -941,7 +973,7 @@ function createSVGRenderer(options) {
941
973
  let userHeadColor = options.headColor ?? null;
942
974
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
943
975
  let headRadius;
944
- let trailSolid = resolveTrailMainColor(trailColor);
976
+ let trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
945
977
  let trailPalette = resolveTrailPalette(trailColor);
946
978
  const ariaLabel = options.ariaLabel ?? "Loading";
947
979
  warnIfTrailColorMismatch(trailColor, trailStyle);
@@ -965,7 +997,10 @@ function createSVGRenderer(options) {
965
997
  const skeletonPath = el("path");
966
998
  skeletonPath.setAttribute("data-sarmal-role", "skeleton");
967
999
  skeletonPath.setAttribute("fill", "none");
968
- skeletonPath.setAttribute("stroke", skeletonColor);
1000
+ skeletonPath.setAttribute(
1001
+ "stroke",
1002
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
1003
+ );
969
1004
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
970
1005
  skeletonPath.setAttribute("stroke-width", svgSkeletonStrokeWidth);
971
1006
  if (skeletonColor === "transparent") {
@@ -974,13 +1009,19 @@ function createSVGRenderer(options) {
974
1009
  group.appendChild(skeletonPath);
975
1010
  const skeletonPathA = el("path");
976
1011
  skeletonPathA.setAttribute("fill", "none");
977
- skeletonPathA.setAttribute("stroke", skeletonColor);
1012
+ skeletonPathA.setAttribute(
1013
+ "stroke",
1014
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
1015
+ );
978
1016
  skeletonPathA.setAttribute("stroke-width", svgSkeletonStrokeWidth);
979
1017
  skeletonPathA.setAttribute("visibility", "hidden");
980
1018
  group.appendChild(skeletonPathA);
981
1019
  const skeletonPathB = el("path");
982
1020
  skeletonPathB.setAttribute("fill", "none");
983
- skeletonPathB.setAttribute("stroke", skeletonColor);
1021
+ skeletonPathB.setAttribute(
1022
+ "stroke",
1023
+ skeletonColor === "transparent" ? "transparent" : colorToRgbAttr(skeletonColor)
1024
+ );
984
1025
  skeletonPathB.setAttribute("stroke-width", svgSkeletonStrokeWidth);
985
1026
  skeletonPathB.setAttribute("visibility", "hidden");
986
1027
  group.appendChild(skeletonPathB);
@@ -995,7 +1036,7 @@ function createSVGRenderer(options) {
995
1036
  }
996
1037
  const headCircle = el("circle");
997
1038
  headCircle.setAttribute("data-sarmal-role", "head");
998
- headCircle.setAttribute("fill", headColor);
1039
+ headCircle.setAttribute("fill", colorToRgbAttr(headColor));
999
1040
  headCircle.setAttribute("r", String(headRadius));
1000
1041
  group.appendChild(headCircle);
1001
1042
  container.appendChild(group);
@@ -1198,7 +1239,7 @@ function createSVGRenderer(options) {
1198
1239
  const prevTrailStyle = trailStyle;
1199
1240
  if (partial.trailColor !== void 0) {
1200
1241
  trailColor = partial.trailColor;
1201
- trailSolid = resolveTrailMainColor(trailColor);
1242
+ trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
1202
1243
  trailPalette = resolveTrailPalette(trailColor);
1203
1244
  if (trailStyle === "default") {
1204
1245
  for (const p of trailPaths) {
@@ -1211,10 +1252,10 @@ function createSVGRenderer(options) {
1211
1252
  if (skeletonColor === "transparent") {
1212
1253
  skeletonPath.setAttribute("visibility", "hidden");
1213
1254
  } else {
1214
- skeletonPath.setAttribute("stroke", skeletonColor);
1255
+ skeletonPath.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1215
1256
  skeletonPath.removeAttribute("visibility");
1216
- skeletonPathA.setAttribute("stroke", skeletonColor);
1217
- skeletonPathB.setAttribute("stroke", skeletonColor);
1257
+ skeletonPathA.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1258
+ skeletonPathB.setAttribute("stroke", colorToRgbAttr(skeletonColor));
1218
1259
  }
1219
1260
  }
1220
1261
  if (partial.trailStyle !== void 0) {
@@ -1237,7 +1278,7 @@ function createSVGRenderer(options) {
1237
1278
  } else {
1238
1279
  headColor = userHeadColor;
1239
1280
  }
1240
- headCircle.setAttribute("fill", headColor);
1281
+ headCircle.setAttribute("fill", colorToRgbAttr(headColor));
1241
1282
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1242
1283
  warnIfTrailColorMismatch(trailColor, trailStyle);
1243
1284
  }