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