@sarmal/core 0.30.0 → 0.31.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
@@ -414,6 +414,49 @@ function parseColorToRgb(s) {
414
414
  }
415
415
  return null;
416
416
  }
417
+ var OKLCH_RE = /^oklch\(\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*[\d.]+)?\s*\)$/i;
418
+ function parseOklchToOklab(s) {
419
+ const m = OKLCH_RE.exec(s.trim());
420
+ if (!m) {
421
+ return null;
422
+ }
423
+ const L = parseFloat(m[1]);
424
+ const C = parseFloat(m[2]);
425
+ const H = parseFloat(m[3]);
426
+ if (Number.isNaN(L) || Number.isNaN(C) || Number.isNaN(H)) {
427
+ return null;
428
+ }
429
+ const clampedL = Math.max(0, Math.min(1, L));
430
+ const clampedC = Math.max(0, Math.min(0.4, C));
431
+ const H_rad = H * (Math.PI / 180);
432
+ return {
433
+ L: clampedL,
434
+ a: clampedC * Math.cos(H_rad),
435
+ b: clampedC * Math.sin(H_rad)
436
+ };
437
+ }
438
+ function parseColorToOklab(s) {
439
+ const oklab = parseOklchToOklab(s);
440
+ if (oklab !== null) {
441
+ return oklab;
442
+ }
443
+ const rgb = parseColorToRgb(s);
444
+ if (rgb === null) {
445
+ return null;
446
+ }
447
+ return rgbToOklab(rgb);
448
+ }
449
+ function colorToRgb(color) {
450
+ const rgb = parseColorToRgb(color);
451
+ if (rgb !== null) {
452
+ return rgb;
453
+ }
454
+ const lab = parseOklchToOklab(color);
455
+ if (lab !== null) {
456
+ return oklabToRgb(lab);
457
+ }
458
+ throw new Error(`[sarmal] unrecognized color "${color}"`);
459
+ }
417
460
  function srgbByteToLinear(c) {
418
461
  const n = c / 255;
419
462
  return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
@@ -451,26 +494,25 @@ var lerpOklab = (a, b, t) => {
451
494
  if (t >= 1) {
452
495
  return b;
453
496
  }
454
- const la = rgbToOklab(a), lb = rgbToOklab(b);
455
- return oklabToRgb({
456
- L: la.L + (lb.L - la.L) * t,
457
- a: la.a + (lb.a - la.a) * t,
458
- b: la.b + (lb.b - la.b) * t
459
- });
497
+ return {
498
+ L: a.L + (b.L - a.L) * t,
499
+ a: a.a + (b.a - a.a) * t,
500
+ b: a.b + (b.b - a.b) * t
501
+ };
460
502
  };
461
503
  function getPaletteColor(palette, position, timeOffset = 0) {
462
504
  if (palette.length === 0) {
463
- return { r: 255, g: 255, b: 255 };
505
+ return { L: 1, a: 0, b: 0 };
464
506
  }
465
507
  if (palette.length === 1) {
466
- return hexToRgb(palette[0]);
508
+ return palette[0];
467
509
  }
468
510
  const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
469
511
  const scaled = cyclePos * palette.length;
470
512
  const idx = Math.floor(scaled);
471
513
  const t = scaled - idx;
472
- const c1 = hexToRgb(palette[idx % palette.length]);
473
- const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
514
+ const c1 = palette[idx % palette.length];
515
+ const c2 = palette[(idx + 1) % palette.length];
474
516
  return lerpOklab(c1, c2, t);
475
517
  }
476
518
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
@@ -505,9 +547,9 @@ function validateRenderOptions(partial) {
505
547
  }
506
548
  function assertTrailColor(value) {
507
549
  if (typeof value === "string") {
508
- if (parseColorToRgb(value) === null) {
550
+ if (parseColorToOklab(value) === null) {
509
551
  throw new TypeError(
510
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got "${value}"`
552
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got "${value}"`
511
553
  );
512
554
  }
513
555
  return;
@@ -520,25 +562,25 @@ function assertTrailColor(value) {
520
562
  }
521
563
  for (let i = 0; i < value.length; i++) {
522
564
  const entry = value[i];
523
- if (typeof entry !== "string" || parseColorToRgb(entry) === null) {
565
+ if (typeof entry !== "string" || parseColorToOklab(entry) === null) {
524
566
  throw new TypeError(
525
- `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba()), got ${JSON.stringify(entry)}`
567
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()), got ${JSON.stringify(entry)}`
526
568
  );
527
569
  }
528
570
  }
529
571
  return;
530
572
  }
531
573
  throw new TypeError(
532
- `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or an array of color strings, got ${JSON.stringify(value)}`
574
+ `[sarmal] setRenderOptions: trailColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or an array of color strings, got ${JSON.stringify(value)}`
533
575
  );
534
576
  }
535
577
  function assertHeadColor(value) {
536
578
  if (value === null) {
537
579
  return;
538
580
  }
539
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
581
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
540
582
  throw new TypeError(
541
- `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or null, got ${JSON.stringify(value)}`
583
+ `[sarmal] setRenderOptions: headColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or null, got ${JSON.stringify(value)}`
542
584
  );
543
585
  }
544
586
  }
@@ -546,9 +588,9 @@ function assertSkeletonColor(value) {
546
588
  if (value === "transparent") {
547
589
  return;
548
590
  }
549
- if (typeof value !== "string" || parseColorToRgb(value) === null) {
591
+ if (typeof value !== "string" || parseColorToOklab(value) === null) {
550
592
  throw new TypeError(
551
- `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba()) or "transparent", got ${JSON.stringify(value)}`
593
+ `[sarmal] setRenderOptions: skeletonColor must be a valid color string (#rrggbb, #rgb, rgb(), rgba(), oklch()) or "transparent", got ${JSON.stringify(value)}`
552
594
  );
553
595
  }
554
596
  }
@@ -583,7 +625,7 @@ function resolveHeadColor(trailColor, trailStyle) {
583
625
  }
584
626
  const palette = resolveTrailPalette(trailColor);
585
627
  const last = palette[palette.length - 1];
586
- const { r, g, b } = parseColorToRgb(last);
628
+ const { r, g, b } = colorToRgb(last);
587
629
  return `rgb(${r},${g},${b})`;
588
630
  }
589
631
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
@@ -604,8 +646,8 @@ function warnIfTrailColorMismatch(trailColor, trailStyle) {
604
646
  var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
605
647
  var WHITE_HEX = "#ffffff";
606
648
  function colorToRgbComponents(color) {
607
- const c = parseColorToRgb(color);
608
- return `${c.r},${c.g},${c.b}`;
649
+ const { r, g, b } = colorToRgb(color);
650
+ return `${r},${g},${b}`;
609
651
  }
610
652
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
611
653
  target.style.width = `${logicalWidth}px`;
@@ -627,6 +669,7 @@ function createRenderer(options) {
627
669
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
628
670
  let trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
629
671
  let trailPalette = resolveTrailPalette(trailColor);
672
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
630
673
  warnIfTrailColorMismatch(trailColor, trailStyle);
631
674
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
632
675
  function setupCanvas() {
@@ -738,8 +781,8 @@ function createRenderer(options) {
738
781
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
739
782
  } else {
740
783
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
741
- const color = getPaletteColor(trailPalette, progress, timeOffset);
742
- ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
784
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
785
+ ctx.fillStyle = `rgba(${r},${g},${b},${opacity})`;
743
786
  }
744
787
  ctx.beginPath();
745
788
  ctx.moveTo(l0x, l0y);
@@ -873,6 +916,7 @@ function createRenderer(options) {
873
916
  trailColor = partial.trailColor;
874
917
  trailSolidRgb = colorToRgbComponents(resolveTrailMainColor(trailColor));
875
918
  trailPalette = resolveTrailPalette(trailColor);
919
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
876
920
  }
877
921
  if (partial.skeletonColor !== void 0) {
878
922
  skeletonColor = partial.skeletonColor;
@@ -956,8 +1000,8 @@ function el(tag) {
956
1000
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
957
1001
  }
958
1002
  function colorToRgbAttr(color) {
959
- const c = parseColorToRgb(color);
960
- return `rgb(${c.r},${c.g},${c.b})`;
1003
+ const { r, g, b } = colorToRgb(color);
1004
+ return `rgb(${r},${g},${b})`;
961
1005
  }
962
1006
  function createSVGRenderer(options) {
963
1007
  const { container, engine } = options;
@@ -975,6 +1019,7 @@ function createSVGRenderer(options) {
975
1019
  let headRadius;
976
1020
  let trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
977
1021
  let trailPalette = resolveTrailPalette(trailColor);
1022
+ let trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
978
1023
  const ariaLabel = options.ariaLabel ?? "Loading";
979
1024
  warnIfTrailColorMismatch(trailColor, trailStyle);
980
1025
  const viewSize = 100;
@@ -1089,7 +1134,7 @@ function createSVGRenderer(options) {
1089
1134
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
1090
1135
  if (trailStyle !== "default") {
1091
1136
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
1092
- const { r, g, b } = getPaletteColor(trailPalette, progress, timeOffset);
1137
+ const { r, g, b } = oklabToRgb(getPaletteColor(trailPaletteOklab, progress, timeOffset));
1093
1138
  trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
1094
1139
  }
1095
1140
  }
@@ -1241,6 +1286,7 @@ function createSVGRenderer(options) {
1241
1286
  trailColor = partial.trailColor;
1242
1287
  trailSolid = colorToRgbAttr(resolveTrailMainColor(trailColor));
1243
1288
  trailPalette = resolveTrailPalette(trailColor);
1289
+ trailPaletteOklab = trailPalette.map((c) => parseColorToOklab(c));
1244
1290
  if (trailStyle === "default") {
1245
1291
  for (const p of trailPaths) {
1246
1292
  p.setAttribute("fill", trailSolid);