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